/* * Copyright 2026 Oleg Borodin * * This work is published and licensed under a Creative Commons * Attribution-NonCommercial-NoDerivatives 4.0 International License. * * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ package command import ( "context" "net/url" "os" "path" "strings" "time" "github.com/spf13/cobra" "github.com/spf13/viper" "mstore/pkg/client" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func packUserinfo(resurseuri, username, password string) (string, error) { var err error var res string if !strings.Contains(resurseuri, "://") { resurseuri = "https://" + resurseuri } uri, err := url.Parse(resurseuri) if err != nil { return res, err } uri.Path = path.Clean(uri.Path) if username != "" && password != "" { uri.User = url.UserPassword(username, password) } res = uri.String() return res, err } func (util *ImageUtil) CreateImageCmds() *cobra.Command { const defaultTimeout uint64 = 30 // Second var subCmd = &cobra.Command{ Use: "images", Short: "Image operations", Aliases: []string{"image"}, } subCmd.PersistentFlags().StringVarP(&util.commonImageParams.Username, "user", "u", "", "Username") subCmd.PersistentFlags().StringVarP(&util.commonImageParams.Password, "pass", "p", "", "Password") subCmd.PersistentFlags().Uint64VarP(&util.commonImageParams.Timeout, "timeout", "t", defaultTimeout, "Operation timeout") subCmd.PersistentFlags().BoolVarP(&util.commonImageParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify") subCmd.MarkFlagsRequiredTogether("user", "pass") vi := viper.New() vi.SetEnvPrefix("mstore") vi.BindEnv("user") vi.BindEnv("pass") util.commonImageParams.Username = vi.GetString("user") util.commonImageParams.Password = vi.GetString("pass") // PushImage var pushImageCmd = &cobra.Command{ Use: "push filename [user:pass@]hostname[:port]/path:tag", Short: "Push container image from local tar file into registry", Args: cobra.ExactArgs(2), Run: util.PushImage, } subCmd.AddCommand(pushImageCmd) // PullImage var pullImageCmd = &cobra.Command{ Use: "pull [user:pass@]hostname[:port]/path:tag filename", Short: "Pull container image from registry into local file", Args: cobra.ExactArgs(2), Run: util.PullImage, } subCmd.AddCommand(pullImageCmd) // DeleteImage var deleteImageCmd = &cobra.Command{ Use: "delete [user:pass@]hostname[:port]/path:tag", Short: "Delete container image from registry", Args: cobra.ExactArgs(1), Run: util.DeleteImage, } subCmd.AddCommand(deleteImageCmd) // ImageManifest var imageManifestCmd = &cobra.Command{ Use: "manifest [user:pass@]hostname[:port]/path:tag", Short: "Show container image manifest info", Args: cobra.ExactArgs(1), Run: util.ImageManifest, } subCmd.AddCommand(imageManifestCmd) // Imagetags var imageTagsCmd = &cobra.Command{ Use: "tags [user:pass@]hostname[:port]/path:tag", Short: "List container tags", Args: cobra.ExactArgs(1), Run: util.ImageTags, } subCmd.AddCommand(imageTagsCmd) // ImageConfig var imageConfigCmd = &cobra.Command{ Use: "config [user:pass@]hostname[:port]/path:tag", Short: "Show container image config info", Args: cobra.ExactArgs(1), Run: util.ImageConfig, } subCmd.AddCommand(imageConfigCmd) // CatalogImages var catalogImagesCmd = &cobra.Command{ Use: "catalog [user:pass@]hostname[:port]", Short: "List image repositories", Args: cobra.ExactArgs(1), Run: util.CatalogImages, } subCmd.AddCommand(catalogImagesCmd) return subCmd } type ImageUtil struct { imageManifestParams ImageManifestParams imageTagsParams ImageTagsParams imageConfigParams ImageConfigParams catalogImagesParams CatalogImagesParams pullImageParams PullImageParams pushImageParams PushImageParams deleteImageParams DeleteImageParams commonImageParams CommonImageParams } type CommonImageParams struct { Timeout uint64 Username string Password string SkipTLSVerify bool } // PushImage type PushImageParams struct { Imagepath string Filepath string } type PushImageResult struct{} func (util *ImageUtil) PushImage(cmd *cobra.Command, args []string) { util.pushImageParams.Filepath = args[0] util.pushImageParams.Imagepath = args[1] res, err := util.pushImage(&util.commonImageParams, &util.pushImageParams) printResponse(res, err) } func (util *ImageUtil) pushImage(common *CommonImageParams, params *PushImageParams) (*PushImageResult, error) { var err error ctx := context.Background() res := &PushImageResult{} cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) err = cli.PushImage(ctx, params.Filepath, params.Imagepath) if err != nil { return res, err } return res, err } // PullImage type PullImageParams struct { Imagepath string Filepath string } type PullImageResult struct { Filepath string `json:"filepath"` Size int64 `json:"size"` } func (util *ImageUtil) PullImage(cmd *cobra.Command, args []string) { util.pullImageParams.Imagepath = args[0] util.pullImageParams.Filepath = args[1] res, err := util.pullImage(&util.commonImageParams, &util.pullImageParams) printResponse(res, err) } func (util *ImageUtil) pullImage(common *CommonImageParams, params *PullImageParams) (*PullImageResult, error) { var err error ctx := context.Background() res := &PullImageResult{} cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) err = cli.PullImage(ctx, params.Imagepath, params.Filepath) if err != nil { return res, err } filestat, err := os.Stat(params.Filepath) if err != nil { return res, err } res.Size = filestat.Size() res.Filepath = params.Filepath return res, err } // DeleteImage type DeleteImageParams struct { Imagepath string } type DeleteImageResult struct { } func (util *ImageUtil) DeleteImage(cmd *cobra.Command, args []string) { util.deleteImageParams.Imagepath = args[0] res, err := util.deleteImage(&util.commonImageParams, &util.deleteImageParams) printResponse(res, err) } func (util *ImageUtil) deleteImage(common *CommonImageParams, params *DeleteImageParams) (*DeleteImageResult, error) { var err error res := &DeleteImageResult{} ctx := context.Background() cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) err = cli.DeleteImage(ctx, params.Imagepath) if err != nil { return res, err } return res, err } // ImageManifest type ImageManifestParams struct { Imagepath string } type ImageManifestResult struct { ImageManifest any `json:"imageManifest"` } func (util *ImageUtil) ImageManifest(cmd *cobra.Command, args []string) { util.imageManifestParams.Imagepath = args[0] res, err := util.imageInfo(&util.commonImageParams, &util.imageManifestParams) printResponse(res, err) } func (util *ImageUtil) imageInfo(common *CommonImageParams, params *ImageManifestParams) (*ImageManifestResult, error) { var err error res := &ImageManifestResult{} ctx := context.Background() cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) opres, err := cli.ImageManifest(ctx, params.Imagepath) if err != nil { return res, err } res.ImageManifest = opres return res, err } // ImageTags type ImageTagsParams struct { Imagepath string } type ImageTagsResult struct { ImageTags []string `json:"imageTags"` } func (util *ImageUtil) ImageTags(cmd *cobra.Command, args []string) { util.imageTagsParams.Imagepath = args[0] res, err := util.imageTags(&util.commonImageParams, &util.imageTagsParams) printResponse(res, err) } func (util *ImageUtil) imageTags(common *CommonImageParams, params *ImageTagsParams) (*ImageTagsResult, error) { var err error res := &ImageTagsResult{ ImageTags: make([]string, 0), } ctx := context.Background() cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) opres, err := cli.ImageTags(ctx, params.Imagepath) if err != nil { return res, err } res.ImageTags = opres return res, err } // ImageConfig type ImageConfigParams struct { Imagepath string } type ImageConfigResult struct { ImageConfig *ocispec.Image `json:"imageConfig"` } func (util *ImageUtil) ImageConfig(cmd *cobra.Command, args []string) { util.imageConfigParams.Imagepath = args[0] res, err := util.imageConfig(&util.commonImageParams, &util.imageConfigParams) printResponse(res, err) } func (util *ImageUtil) imageConfig(common *CommonImageParams, params *ImageConfigParams) (*ImageConfigResult, error) { var err error res := &ImageConfigResult{ ImageConfig: &ocispec.Image{}, } ctx := context.Background() cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) opres, err := cli.ImageConfig(ctx, params.Imagepath) if err != nil { return res, err } res.ImageConfig = opres return res, err } // CatalogImages type CatalogImagesParams struct { Source string } type CatalogImagesResult struct { Repositories []string `json:"repositories"` } func (util *ImageUtil) CatalogImages(cmd *cobra.Command, args []string) { util.catalogImagesParams.Source = args[0] res, err := util.catalogImages(&util.commonImageParams, &util.catalogImagesParams) printResponse(res, err) } func (util *ImageUtil) catalogImages(common *CommonImageParams, params *CatalogImagesParams) (*CatalogImagesResult, error) { var err error res := &CatalogImagesResult{ Repositories: make([]string, 0), } ctx := context.Background() cli := client.NewClient(common.SkipTLSVerify) timeout := time.Duration(common.Timeout) * time.Second params.Source, err = packUserinfo(params.Source, common.Username, common.Password) if err != nil { return res, err } ctx, _ = context.WithTimeout(ctx, timeout) opres, err := cli.CatalogImages(ctx, params.Source) if err != nil { return res, err } res.Repositories = opres return res, err }