diff --git a/pkg/kosmosctl/image/image.go b/pkg/kosmosctl/image/image.go new file mode 100644 index 000000000..cc4d22b06 --- /dev/null +++ b/pkg/kosmosctl/image/image.go @@ -0,0 +1,18 @@ +package image + +import ( + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/i18n" +) + +// NewCmdImage pull/push a kosmos offline installation package. +func NewCmdImage() *cobra.Command { + cmd := &cobra.Command{ + Use: "image", + Short: i18n.T("pull and push kosmos offline installation package. "), + } + + cmd.AddCommand(NewCmdPull()) + cmd.AddCommand(NewCmdPush()) + return cmd +} diff --git a/pkg/kosmosctl/image/pull.go b/pkg/kosmosctl/image/pull.go new file mode 100644 index 000000000..206113c70 --- /dev/null +++ b/pkg/kosmosctl/image/pull.go @@ -0,0 +1,139 @@ +package image + +import ( + "bufio" + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + "k8s.io/klog" + ctlutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/kosmos.io/kosmos/pkg/utils" +) + +var PullExample = templates.Examples(i18n.T(` + # Pull and save images with default config, e.g: + kosmosctl image pull --image-list=[image-list.txt] + + # Pull and save images with custom config, e.g: + kosmosctl image pull --image-list=[image-list.txt] --output=[output-dir] --container-runtime=[container-runtime] +`)) + +type CommandPullOptions struct { + Output string + ImageList string + ContainerRuntime string +} + +func NewCmdPull() *cobra.Command { + o := &CommandPullOptions{} + cmd := &cobra.Command{ + Use: "pull", + Short: i18n.T("pull a kosmos offline installation package. "), + Long: "", + Example: PullExample, + SilenceUsage: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + ctlutil.CheckErr(o.Complete()) + ctlutil.CheckErr(o.Validate()) + ctlutil.CheckErr(o.Run()) + return nil + }, + } + flags := cmd.Flags() + flags.StringVarP(&o.ImageList, "image-list", "l", "", "Image list of kosmos. ") + flags.StringVarP(&o.Output, "output", "o", "", "Path to a output path, default path is current dir") + flags.StringVarP(&o.ContainerRuntime, "container-runtime", "c", utils.DefaultContainerRuntime, "Type of container runtime(docker or containerd), docker is used by default .") + + return cmd +} + +func (o *CommandPullOptions) Complete() error { + if len(o.Output) == 0 { + currentPath, err := os.Getwd() + if err != nil { + return errors.New("current path can not find. ") + } + o.Output = currentPath + } + return nil +} + +func (o *CommandPullOptions) Validate() error { + if len(o.ImageList) == 0 { + return errors.New("image list can not be empty. ") + } + return nil +} + +func (o *CommandPullOptions) Run() error { + // 1. pull image from public registry + klog.Info("Start pulling images ...") + imageSet, err := o.ImagePull() + if err != nil { + return err + } + klog.Info("kosmos images have been pulled successfully. ") + + // 2. save image to *.tar.gz + klog.Info("Start saving images ...") + err = o.ImageSave(imageSet) + if err != nil { + return nil + } + klog.Info("kosmos-io.tar.gz has been saved successfully. ") + return nil +} + +func (o *CommandPullOptions) ImagePull() (string, error) { + var cmdStr string + var imageSet string + + imageList, err := os.Open(o.ImageList) + if err != nil { + klog.Errorf("can not read image list, err: %v", err) + return "", err + } + defer imageList.Close() + + scanner := bufio.NewScanner(imageList) + for scanner.Scan() { + imageName := scanner.Text() + imageSet = fmt.Sprintf("%s %s", imageSet, imageName) + switch o.ContainerRuntime { + case utils.Containerd: + cmdStr = fmt.Sprintf("sudo %s -n k8s.io image pull %s", utils.CtrCommand, imageName) + default: + cmdStr = fmt.Sprintf("sudo %s pull %s", utils.DefaultContainerRuntime, imageName) + } + + err = Command(cmdStr) + if err != nil { + klog.Errorf("kosmos images pull failed with %v", err) + return "", err + } + } + return imageSet, nil +} + +func (o *CommandPullOptions) ImageSave(imageSet string) error { + var cmdStr string + switch o.ContainerRuntime { + case utils.Containerd: + cmdStr = fmt.Sprintf("sudo %s -n k8s.io image export %s/kosmos-io.tar.gz %s", utils.CtrCommand, o.Output, imageSet) + default: + cmdStr = fmt.Sprintf("sudo %s save -o %s/kosmos-io.tar.gz %s", utils.DefaultContainerRuntime, o.Output, imageSet) + } + + err := Command(cmdStr) + if err != nil { + klog.Errorf("kosmos images save failed with %v", err) + return err + } + return nil +} diff --git a/pkg/kosmosctl/image/push.go b/pkg/kosmosctl/image/push.go new file mode 100644 index 000000000..2480fbdc1 --- /dev/null +++ b/pkg/kosmosctl/image/push.go @@ -0,0 +1,176 @@ +package image + +import ( + "bufio" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" + "k8s.io/klog/v2" + ctlutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/kosmos.io/kosmos/pkg/utils" +) + +var PushExample = templates.Examples(i18n.T(` + # Push images for ./*.tar.gz to private-registry, e.g: + kosmoscrl image push --image-list=[image-list.txt] --artifact=[*.tar.gz] --private-registry=[private-registry-name] + + # Push images for ./*.tar.gz to private-registry which need to logged in, e.g: + kosmoscrl image push --image-list=[image-list.txt] --artifact=[*.tar.gz] --username=[registry-username] --password=[registry-password] --private-registry=[private-registry-name] +`)) + +type CommandPushOptions struct { + UserName string + PassWord string + Artifact string + PrivateRegistry string + ContainerRuntime string + ImageList string +} + +func NewCmdPush() *cobra.Command { + o := &CommandPushOptions{} + cmd := &cobra.Command{ + Use: "push", + Short: i18n.T("push images from *.tar.gz to private registry. "), + Long: "", + Example: PushExample, + SilenceUsage: true, + DisableFlagsInUseLine: true, + RunE: func(cmd *cobra.Command, args []string) error { + ctlutil.CheckErr(o.Complete()) + ctlutil.CheckErr(o.Validate()) + ctlutil.CheckErr(o.Run()) + return nil + }, + } + flags := cmd.Flags() + flags.StringVarP(&o.ImageList, "image-list", "l", "", "Image list of kosmos. ") + flags.StringVarP(&o.Artifact, "artifact", "a", "", "Path to a artifact tar.gz ") + flags.StringVarP(&o.UserName, "username", "u", "", "Username to private registry. ") + flags.StringVarP(&o.PassWord, "password", "p", "", "Password to private registry. ") + flags.StringVarP(&o.PrivateRegistry, "private-registry", "r", "", "private registry. ") + flags.StringVarP(&o.ContainerRuntime, "container-runtime", "c", utils.DefaultContainerRuntime, "Type of container runtime(docker or containerd), docker is used by default .") + // todo add image-dir parameters + return cmd +} + +func (o *CommandPushOptions) Complete() error { + return nil +} + +func (o *CommandPushOptions) Validate() error { + if len(o.Artifact) == 0 { + return errors.New("artifact path can not be empty. ") + } + + if len(o.PrivateRegistry) == 0 { + return errors.New("private registry can not be empty. ") + } + + if len(o.ImageList) == 0 { + return errors.New("image list can not be empty. ") + } + return nil +} + +func (o *CommandPushOptions) Run() error { + // 1. load image from *.tar.gz + klog.Info("Start loading images ...") + err := o.ImageLoad() + if err != nil { + return err + } + klog.Info("kosmos images have been loaded successfully. ") + + // 2. push image to private registry + klog.Info("Start pushing images ...") + err = o.ImagePush() + if err != nil { + return nil + } + klog.Info("kosmos images have been pushed successfully. ") + return nil +} + +func (o *CommandPushOptions) ImageLoad() error { + var cmdStr string + switch o.ContainerRuntime { + case utils.Containerd: + cmdStr = fmt.Sprintf("sudo %s -n k8s.io image import %s ", utils.CtrCommand, o.Artifact) + default: + cmdStr = fmt.Sprintf("sudo %s load -i %s ", utils.DefaultContainerRuntime, o.Artifact) + } + cmd := exec.Command("/bin/sh", "-c", cmdStr) + out, err := cmd.CombinedOutput() + klog.Info(string(out)) + if err != nil { + klog.Errorf("kosmos images load failed with %v", err) + return err + } + return nil +} + +func (o *CommandPushOptions) ImagePush() error { + var cmdTag, cmdPush, cmdStr string + + imageList, err := os.Open(o.ImageList) + if err != nil { + klog.Errorf("can not read image list, err: %v", err) + return err + } + defer imageList.Close() + + scanner := bufio.NewScanner(imageList) + for scanner.Scan() { + imageName := scanner.Text() + splits := strings.Split(imageName, "/") + imageTagName := fmt.Sprintf("%s/%s", o.PrivateRegistry, splits[len(splits)-1]) + switch o.ContainerRuntime { + case utils.Containerd: + cmdTag = fmt.Sprintf("sudo %s -n k8s.io image tag %s %s", utils.CtrCommand, imageName, imageTagName) + if len(o.UserName) != 0 || len(o.PassWord) != 0 { + cmdPush = fmt.Sprintf("sudo %s -n k8s.io image push %s -u %s:%s", utils.CtrCommand, imageTagName, o.UserName, o.PassWord) + } else { + cmdPush = fmt.Sprintf("sudo %s -n k8s.io image push %s", utils.CtrCommand, imageTagName) + } + cmdStr = fmt.Sprintf("%s; %s", cmdTag, cmdPush) + default: + // docker login + if len(o.UserName) != 0 || len(o.PassWord) != 0 { + cmdStr = fmt.Sprintf("sudo %s login --username '%s' --password '%s' %s ", utils.DefaultContainerRuntime, o.UserName, o.PassWord, o.PrivateRegistry) + err := Command(cmdStr) + if err != nil { + klog.Errorf("docker login failed with %v", err) + return err + } + } + cmdTag = fmt.Sprintf("sudo %s tag %s %s", utils.DefaultContainerRuntime, imageName, imageTagName) + cmdPush = fmt.Sprintf("sudo %s push %s", utils.DefaultContainerRuntime, imageTagName) + cmdStr = fmt.Sprintf("%s; %s", cmdTag, cmdPush) + } + + err = Command(cmdStr) + if err != nil { + klog.Errorf("kosmos images push failed with %v", err) + return err + } + } + return nil +} + +func Command(cmdStr string) error { + cmd := exec.Command("/bin/sh", "-c", cmdStr) + out, err := cmd.CombinedOutput() + klog.Info(string(out)) + if err != nil { + return err + } + return nil +} diff --git a/pkg/kosmosctl/kosmosctl.go b/pkg/kosmosctl/kosmosctl.go index 87b1d58cb..b8a069580 100644 --- a/pkg/kosmosctl/kosmosctl.go +++ b/pkg/kosmosctl/kosmosctl.go @@ -15,6 +15,7 @@ import ( "github.com/kosmos.io/kosmos/pkg/kosmosctl/floater" "github.com/kosmos.io/kosmos/pkg/kosmosctl/get" + "github.com/kosmos.io/kosmos/pkg/kosmosctl/image" "github.com/kosmos.io/kosmos/pkg/kosmosctl/install" "github.com/kosmos.io/kosmos/pkg/kosmosctl/join" "github.com/kosmos.io/kosmos/pkg/kosmosctl/uninstall" @@ -73,6 +74,12 @@ func NewKosmosCtlCommand() *cobra.Command { floater.NewCmdDoctor(), }, }, + { + Message: "Image Pull/Push commands", + Commands: []*cobra.Command{ + image.NewCmdImage(), + }, + }, } groups.Add(cmds) diff --git a/pkg/utils/constants.go b/pkg/utils/constants.go index 037b620c7..68aa0f954 100644 --- a/pkg/utils/constants.go +++ b/pkg/utils/constants.go @@ -1,9 +1,10 @@ package utils const ( - DefaultNamespace = "kosmos-system" - DefaultImageRepository = "ghcr.io/kosmos-io" - DefaultInstallModule = "all" + DefaultNamespace = "kosmos-system" + DefaultImageRepository = "ghcr.io/kosmos-io" + DefaultInstallModule = "all" + DefaultContainerRuntime = "docker" ) const ExternalIPPoolNamePrefix = "clusterlink" @@ -16,6 +17,11 @@ const ( HostKubeConfigName = "host-kubeconfig" ) +const ( + CtrCommand = "ctr" + Containerd = "containerd" +) + const ( EnvUseProxy = "USE_PROXY" EnvClusterName = "CLUSTER_NAME"