From 8b5e785176f80d1859a9640365d89cbe25043ee3 Mon Sep 17 00:00:00 2001 From: tec-rubbish maker <1729765480@qq.com> Date: Tue, 30 Jul 2024 11:08:03 +0800 Subject: [PATCH] Add config list Signed-off-by: tiansuo114 <1729765480@qq.com> --- pkg/karmadactl/cmdinit/cmdinit.go | 18 ++-- pkg/karmadactl/cmdinit/kubernetes/deploy.go | 30 +++++++ pkg/karmadactl/cmdinit/options/global.go | 17 ++++ pkg/karmadactl/cmdinit/show/list_option.go | 41 +++++++++ .../cmdinit/show/list_option_test.go | 84 +++++++++++++++++++ pkg/karmadactl/cmdinit/show_images.go | 68 +++++++++++++++ test/e2e/karmadactl_test.go | 81 ++++++++++++++++++ 7 files changed, 332 insertions(+), 7 deletions(-) create mode 100644 pkg/karmadactl/cmdinit/show/list_option.go create mode 100644 pkg/karmadactl/cmdinit/show/list_option_test.go create mode 100644 pkg/karmadactl/cmdinit/show_images.go diff --git a/pkg/karmadactl/cmdinit/cmdinit.go b/pkg/karmadactl/cmdinit/cmdinit.go index 730c5884376b..e51c81f693ac 100644 --- a/pkg/karmadactl/cmdinit/cmdinit.go +++ b/pkg/karmadactl/cmdinit/cmdinit.go @@ -114,13 +114,17 @@ func NewCmdInit(parentCommand string) *cobra.Command { }, } flags := cmd.Flags() + + cmdInitParentCommand := fmt.Sprintf("%s %s", parentCommand, "addons") + cmd.AddCommand(NewCmdShowImages(cmdInitParentCommand)) + flags.StringVarP(&opts.ImageRegistry, "private-image-registry", "", "", "Private image registry where pull images from. If set, all required images will be downloaded from it, it would be useful in offline installation scenarios. In addition, you still can use --kube-image-registry to specify the registry for Kubernetes's images.") flags.StringVarP(&opts.ImagePullPolicy, "image-pull-policy", "", string(corev1.PullIfNotPresent), "The image pull policy for all Karmada components container. One of Always, Never, IfNotPresent. Defaults to IfNotPresent.") flags.StringSliceVar(&opts.PullSecrets, "image-pull-secrets", nil, "Image pull secrets are used to pull images from the private registry, could be secret list separated by comma (e.g '--image-pull-secrets PullSecret1,PullSecret2', the secrets should be pre-settled in the namespace declared by '--namespace')") // kube image registry flags.StringVarP(&opts.KubeImageMirrorCountry, "kube-image-mirror-country", "", "", "Country code of the kube image registry to be used. For Chinese mainland users, set it to cn") flags.StringVarP(&opts.KubeImageRegistry, "kube-image-registry", "", "", "Kube image registry. For Chinese mainland users, you may use local gcr.io mirrors such as registry.cn-hangzhou.aliyuncs.com/google_containers to override default kube image registry") - flags.StringVar(&opts.KubeImageTag, "kube-image-tag", "v1.30.4", "Choose a specific Kubernetes version for the control plane.") + flags.StringVar(&opts.KubeImageTag, "kube-image-tag", cmdinitoptions.DefaultKubeImageTag, "Choose a specific Kubernetes version for the control plane.") // cert flags.StringVar(&opts.ExternalIP, "cert-external-ip", "", "the external IP of Karmada certificate (e.g 192.168.1.2,172.16.1.2)") flags.StringVar(&opts.ExternalDNS, "cert-external-dns", "", "the external DNS of Karmada certificate (e.g localhost,localhost.com)") @@ -128,18 +132,18 @@ func NewCmdInit(parentCommand string) *cobra.Command { flags.StringVarP(&opts.CaCertFile, "ca-cert-file", "", "", "The root CA certificate file which will be used to issue new certificates for Karmada components. If not set, a new self-signed root CA certificate will be generated. This must be used together with --ca-key-file.") flags.StringVarP(&opts.CaKeyFile, "ca-key-file", "", "", "The root CA private key file which will be used to issue new certificates for Karmada components. If not set, a new self-signed root CA key will be generated. This must be used together with --ca-cert-file.") // Kubernetes - flags.StringVarP(&opts.Namespace, "namespace", "n", "karmada-system", "Kubernetes namespace") + flags.StringVarP(&opts.Namespace, "namespace", "n", cmdinitoptions.DefaultKarmadaKubeNamespace, "Kubernetes namespace") flags.StringVar(&opts.StorageClassesName, "storage-classes-name", "", "Kubernetes StorageClasses Name") flags.StringVar(&opts.KubeConfig, "kubeconfig", "", "absolute path to the kubeconfig file") flags.StringVar(&opts.Context, "context", "", "The name of the kubeconfig context to use") flags.StringVar(&opts.HostClusterDomain, "host-cluster-domain", options.DefaultHostClusterDomain, "The cluster domain of karmada host cluster. (e.g. --host-cluster-domain=host.karmada)") // etcd - flags.StringVarP(&opts.EtcdStorageMode, "etcd-storage-mode", "", "hostPath", + flags.StringVarP(&opts.EtcdStorageMode, "etcd-storage-mode", "", cmdinitoptions.DefaultEtcdStorageMode, fmt.Sprintf("etcd data storage mode(%s). value is PVC, specify --storage-classes-name", strings.Join(kubernetes.SupportedStorageMode(), ","))) flags.StringVarP(&opts.EtcdImage, "etcd-image", "", "", "etcd image") flags.StringVarP(&opts.EtcdInitImage, "etcd-init-image", "", kubernetes.DefaultInitImage, "etcd init container image") flags.Int32VarP(&opts.EtcdReplicas, "etcd-replicas", "", 1, "etcd replica set, cluster 3,5...singular") - flags.StringVarP(&opts.EtcdHostDataPath, "etcd-data", "", "/var/lib/karmada-etcd", "etcd data path,valid in hostPath mode.") + flags.StringVarP(&opts.EtcdHostDataPath, "etcd-data", "", cmdinitoptions.DefaultEtcdHostDataPath, "etcd data path,valid in hostPath mode.") flags.StringVarP(&opts.EtcdNodeSelectorLabels, "etcd-node-selector-labels", "", "", "the labels used for etcd pod to select nodes, valid in hostPath mode, and with each label separated by a comma. ( e.g. --etcd-node-selector-labels karmada.io/etcd=true,kubernetes.io/os=linux)") flags.StringVarP(&opts.EtcdPersistentVolumeSize, "etcd-pvc-size", "", "5Gi", "etcd data path,valid in pvc mode.") flags.StringVar(&opts.ExternalEtcdCACertPath, "external-etcd-ca-cert-path", "", "The path of CA certificate of the external etcd cluster in pem format.") @@ -150,9 +154,9 @@ func NewCmdInit(parentCommand string) *cobra.Command { // karmada flags.StringVar(&opts.CRDs, "crds", kubernetes.DefaultCrdURL, "Karmada crds resource.(local file e.g. --crds /root/crds.tar.gz)") flags.StringVarP(&opts.KarmadaAPIServerAdvertiseAddress, "karmada-apiserver-advertise-address", "", "", "The IP address the Karmada API Server will advertise it's listening on. If not set, the address on the master node will be used.") - flags.Int32VarP(&opts.KarmadaAPIServerNodePort, "port", "p", 32443, "Karmada apiserver service node port") - flags.StringVarP(&opts.KarmadaDataPath, "karmada-data", "d", "/etc/karmada", "Karmada data path. kubeconfig cert and crds files") - flags.StringVarP(&opts.KarmadaPkiPath, "karmada-pki", "", "/etc/karmada/pki", "Karmada pki path. Karmada cert files") + flags.Int32VarP(&opts.KarmadaAPIServerNodePort, "port", "p", cmdinitoptions.DefaultKarmadaAPIServerNodePort, "Karmada apiserver service node port") + flags.StringVarP(&opts.KarmadaDataPath, "karmada-data", "d", cmdinitoptions.DefaultKarmadaDataPath, "Karmada data path. kubeconfig cert and crds files") + flags.StringVarP(&opts.KarmadaPkiPath, "karmada-pki", "", cmdinitoptions.DefaultKarmadaPkiPath, "Karmada pki path. Karmada cert files") flags.StringVarP(&opts.KarmadaAPIServerImage, "karmada-apiserver-image", "", "", "Kubernetes apiserver image") flags.Int32VarP(&opts.KarmadaAPIServerReplicas, "karmada-apiserver-replicas", "", 1, "Karmada apiserver replica set") flags.StringVarP(&opts.KarmadaSchedulerImage, "karmada-scheduler-image", "", kubernetes.DefaultKarmadaSchedulerImage, "Karmada scheduler image") diff --git a/pkg/karmadactl/cmdinit/kubernetes/deploy.go b/pkg/karmadactl/cmdinit/kubernetes/deploy.go index 46ad340b5760..89a4c74ee728 100644 --- a/pkg/karmadactl/cmdinit/kubernetes/deploy.go +++ b/pkg/karmadactl/cmdinit/kubernetes/deploy.go @@ -744,3 +744,33 @@ func generateServerURL(serverIP string, nodePort int32) (string, error) { func SupportedStorageMode() []string { return []string{etcdStorageModeEmptyDir, etcdStorageModeHostPath, etcdStorageModePVC} } + +// NewDefaultCommandInitOption returns a CommandInitOption with default values +func NewDefaultCommandInitOption() *CommandInitOption { + return &CommandInitOption{ + ImagePullPolicy: string(corev1.PullIfNotPresent), + KubeImageTag: options.DefaultKubeImageTag, + Namespace: options.DefaultKarmadaKubeNamespace, + EtcdInitImage: DefaultInitImage, + CRDs: DefaultCrdURL, + KarmadaSchedulerImage: DefaultKarmadaSchedulerImage, + KarmadaControllerManagerImage: DefaultKarmadaControllerManagerImage, + KarmadaWebhookImage: DefaultKarmadaWebhookImage, + KarmadaAggregatedAPIServerImage: DefaultKarmadaAggregatedAPIServerImage, + } +} + +// GenerateControlPlaneImages generated control plane all the image list +func (i *CommandInitOption) GenerateControlPlaneImages() []string { + images := []string{ + i.kubeAPIServerImage(), + i.kubeControllerManagerImage(), + i.etcdImage(), + i.etcdInitImage(), + i.karmadaSchedulerImage(), + i.karmadaControllerManagerImage(), + i.karmadaWebhookImage(), + i.karmadaAggregatedAPIServerImage(), + } + return images +} diff --git a/pkg/karmadactl/cmdinit/options/global.go b/pkg/karmadactl/cmdinit/options/global.go index 9d132cea0583..a8eae7cef3a1 100644 --- a/pkg/karmadactl/cmdinit/options/global.go +++ b/pkg/karmadactl/cmdinit/options/global.go @@ -40,3 +40,20 @@ const ( // WaitComponentReadyTimeout wait component ready time WaitComponentReadyTimeout = 120 ) + +const ( + // DefaultKubeImageTag is the default tag for the Kubernetes image used by the Karmada control plane. + DefaultKubeImageTag = "v1.30.4" + // DefaultKarmadaKubeNamespace is the default namespace in which the Karmada control plane components will be deployed. + DefaultKarmadaKubeNamespace = "karmada-system" + // DefaultKarmadaAPIServerNodePort is the default node port used by the Karmada API server. + DefaultKarmadaAPIServerNodePort = 32443 + // DefaultEtcdStorageMode is the default storage mode for etcd data. The default mode is 'hostPath'. + DefaultEtcdStorageMode = "hostPath" + // DefaultEtcdHostDataPath is the default file system path where etcd data is stored when the storage mode is 'hostPath'. + DefaultEtcdHostDataPath = "/var/lib/karmada-etcd" + // DefaultKarmadaDataPath is the default directory where Karmada stores its configuration, certificates, and other data. + DefaultKarmadaDataPath = "/etc/karmada" + // DefaultKarmadaPkiPath is the default directory where Karmada stores its PKI (Public Key Infrastructure) files. + DefaultKarmadaPkiPath = "/etc/karmada/pki" +) diff --git a/pkg/karmadactl/cmdinit/show/list_option.go b/pkg/karmadactl/cmdinit/show/list_option.go new file mode 100644 index 000000000000..09c816149f10 --- /dev/null +++ b/pkg/karmadactl/cmdinit/show/list_option.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 The Karmada Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package show + +import ( + "fmt" + "os" + + "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes" +) + +// CommandConfigImageOption options for components +type CommandConfigImageOption struct { + InitOption *kubernetes.CommandInitOption +} + +// Run generates and prints the required control plane images. +func (o *CommandConfigImageOption) Run() error { + imageList := o.InitOption.GenerateControlPlaneImages() + for _, image := range imageList { + _, err := fmt.Fprintf(os.Stdout, "%s\n", image) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/karmadactl/cmdinit/show/list_option_test.go b/pkg/karmadactl/cmdinit/show/list_option_test.go new file mode 100644 index 000000000000..c0d3236530a9 --- /dev/null +++ b/pkg/karmadactl/cmdinit/show/list_option_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2024 The Karmada Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package show_test + +import ( + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes" + "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/show" +) + +// Helper function to capture stdout +func captureOutput(f func()) string { + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + f() + + w.Close() + os.Stdout = old + out, _ := io.ReadAll(r) + return string(out) +} + +func TestCommandConfigImageOption(t *testing.T) { + tests := []struct { + name string + privateImageRegistry string + expectedImages []string + }{ + { + name: "Default execution", + privateImageRegistry: "", + expectedImages: kubernetes.NewDefaultCommandInitOption().GenerateControlPlaneImages(), + }, + { + name: "With Private Image Registry", + privateImageRegistry: "myregistry.com", + expectedImages: func() []string { + opts := kubernetes.NewDefaultCommandInitOption() + opts.ImageRegistry = "myregistry.com" + return opts.GenerateControlPlaneImages() + }(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + opts := show.CommandConfigImageOption{ + InitOption: kubernetes.NewDefaultCommandInitOption(), + } + opts.InitOption.ImageRegistry = tt.privateImageRegistry + assert.NotNil(t, opts.InitOption) + + output := captureOutput(func() { + err := opts.Run() + assert.NoError(t, err) + }) + + for _, image := range tt.expectedImages { + assert.Contains(t, output, image) + } + }) + } +} diff --git a/pkg/karmadactl/cmdinit/show_images.go b/pkg/karmadactl/cmdinit/show_images.go new file mode 100644 index 000000000000..e690189705a5 --- /dev/null +++ b/pkg/karmadactl/cmdinit/show_images.go @@ -0,0 +1,68 @@ +/* +Copyright 2024 The Karmada Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmdinit + +import ( + "fmt" + + "github.com/spf13/cobra" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/kubernetes" + configInit "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/show" +) + +var ( + showImagesLong = templates.LongDesc(` + Shows information about the images required for Karmada deployment. + + This command lists all the images needed to deploy and run Karmada, including + images for the API server, controller manager, scheduler, and other components.`) + + showImagesExample = templates.Examples(` + # List all required images + %[1]s init show-images + + # List all required images from a specific private image registry + %[1]s init show-images --private-image-registry registry.cn-hangzhou.aliyuncs.com/google_containers + + # List all required images with a specific country code for the image registry + %[1]s init show-images --kube-image-country cn`) +) + +// NewCmdShowImages creates a new show-images command +func NewCmdShowImages(parentCommand string) *cobra.Command { + opts := configInit.CommandConfigImageOption{ + InitOption: kubernetes.NewDefaultCommandInitOption(), + } + cmd := &cobra.Command{ + Use: "show-images", + Short: "List the images required for Karmada deployment", + Long: showImagesLong, + Example: fmt.Sprintf(showImagesExample, parentCommand), + RunE: func(_ *cobra.Command, _ []string) error { + return opts.Run() + }, + SilenceUsage: true, + DisableFlagsInUseLine: true, + } + + cmd.Flags().StringVarP(&opts.InitOption.ImageRegistry, "private-image-registry", "", "", "Private image registry to pull images from. If set, all required images will be downloaded from it, useful for offline installation scenarios.") + cmd.Flags().StringVarP(&opts.InitOption.KubeImageMirrorCountry, "kube-image-country", "", "", "The country code of the image registry, such as 'global' or 'cn'.") + + return cmd +} diff --git a/test/e2e/karmadactl_test.go b/test/e2e/karmadactl_test.go index d201ee134308..8c3b722fa28c 100644 --- a/test/e2e/karmadactl_test.go +++ b/test/e2e/karmadactl_test.go @@ -1222,6 +1222,87 @@ var _ = ginkgo.Describe("Karmadactl options testing", func() { }) }) +var _ = ginkgo.Describe("Karmadactl show init images testing", func() { + var ( + timeout = 10 * time.Second + ) + + ginkgo.Context("Karmadactl init images list", func() { + var ( + cmdArgs []string + expected string + ) + + ginkgo.It("list images without any flags", func() { + cmdArgs = []string{"init", "show-images"} + expected = `registry.k8s.io/kube-apiserver:v1.30.4 +registry.k8s.io/kube-controller-manager:v1.30.4 +registry.k8s.io/etcd:3.5.13-0 +docker.io/alpine:3.19.1 +docker.io/karmada/karmada-scheduler:v0.0.0-master +docker.io/karmada/karmada-controller-manager:v0.0.0-master +docker.io/karmada/karmada-webhook:v0.0.0-master +docker.io/karmada/karmada-aggregated-apiserver:v0.0.0-master` + + cmd := framework.NewKarmadactlCommand(kubeconfig, "", karmadactlPath, "", timeout, cmdArgs...) + output, err := cmd.ExecOrDie() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(strings.Contains(output, expected)).Should(gomega.BeTrue(), "Output: %s, Expected: %s", output, expected) + }) + + ginkgo.It("list images with private-image-registry flag", func() { + cmdArgs = []string{"init", "show-images", "--private-image-registry=registry.k8s.io2"} + expected = `registry.k8s.io2/kube-apiserver:v1.30.4 +registry.k8s.io2/kube-controller-manager:v1.30.4 +registry.k8s.io2/etcd:3.5.13-0 +registry.k8s.io2/alpine:3.19.1 +registry.k8s.io2/karmada-scheduler:v0.0.0-master +registry.k8s.io2/karmada-controller-manager:v0.0.0-master +registry.k8s.io2/karmada-webhook:v0.0.0-master +registry.k8s.io2/karmada-aggregated-apiserver:v0.0.0-master` + + cmd := framework.NewKarmadactlCommand(kubeconfig, "", karmadactlPath, "", timeout, cmdArgs...) + output, err := cmd.ExecOrDie() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(strings.Contains(output, expected)).Should(gomega.BeTrue(), "Output: %s, Expected: %s", output, expected) + }) + + ginkgo.It("list images with kube-image-country flag", func() { + cmdArgs = []string{"init", "show-images", "--kube-image-country=cn"} + expected = `registry.cn-hangzhou.aliyuncs.com/google_containers/kube-apiserver:v1.30.4 +registry.cn-hangzhou.aliyuncs.com/google_containers/kube-controller-manager:v1.30.4 +registry.cn-hangzhou.aliyuncs.com/google_containers/etcd:3.5.13-0 +docker.io/alpine:3.19.1 +docker.io/karmada/karmada-scheduler:v0.0.0-master +docker.io/karmada/karmada-controller-manager:v0.0.0-master +docker.io/karmada/karmada-webhook:v0.0.0-master +docker.io/karmada/karmada-aggregated-apiserver:v0.0.0-master` + + cmd := framework.NewKarmadactlCommand(kubeconfig, "", karmadactlPath, "", timeout, cmdArgs...) + output, err := cmd.ExecOrDie() + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(strings.Contains(output, expected)).Should(gomega.BeTrue(), "Output: %s, Expected: %s", output, expected) + }) + }) + + ginkgo.Context("Karmadactl init images list with invalid flags", func() { + var ( + cmdArgs []string + expected string + ) + + ginkgo.It("invalid flag --unknown-flag", func() { + cmdArgs = []string{"init", "show-images", "--unknown-flag"} + expected = "unknown flag" + + cmd := framework.NewKarmadactlCommand(kubeconfig, "", karmadactlPath, "", timeout, cmdArgs...) + _, err := cmd.ExecOrDie() + gomega.Expect(err).Should(gomega.HaveOccurred()) + gomega.Expect(strings.Contains(err.Error(), expected)).Should(gomega.BeTrue(), "Stderr: %s, Expected: %s", err.Error(), expected) + }) + }) +}) + var _ = framework.SerialDescribe("Karmadactl taint testing", ginkgo.Labels{NeedCreateCluster}, func() { ginkgo.Context("Test karmadactl taint command with different effects", func() { var (