diff --git a/pkg/cmd/common_componentplan.go b/pkg/cmd/common_componentplan.go new file mode 100644 index 00000000..f96091ca --- /dev/null +++ b/pkg/cmd/common_componentplan.go @@ -0,0 +1,323 @@ +/* +Copyright 2023 The Kubebb 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 cmd + +import ( + "context" + "fmt" + "strings" + "sync" + + "github.com/ghodss/yaml" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kubebb/core/api/v1alpha1" +) + +var ( + once sync.Once + cc client.Client +) + +const ( + mustAddForClusterComponent = "ingress-nginx.controller.nodeSelector.kubernetes\\.io/hostname" + clusterComponentName = "cluster-component" + installTemplate = `apiVersion: core.kubebb.k8s.com.cn/v1alpha1 +kind: ComponentPlan +metadata: + name: cluster-component + namespace: u4a-system +spec: + approved: true + name: cluster-component + version: version + component: + name: kubebb.cluster-component + namespace: kubebb-system` + + u4aValueYaml = `# Default values for u4a-system. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# You must check and update the value of each variable below +deploymentConfig: + # bffServer info + bffHost: portal..nip.io + clientName: bff-client + clientId: bff-client + clientSecret: 61324af0-1234-4f61-b110-ef57013267d6 + # BFF server info + # The host Kubernetes cluster with OIDC enabled or use kube-oidc-proxy in front of k8s-apiserver + # kube-oidc-proxy will be installed by default + hostK8sApiWithOidc: https://k8s..nip.io + # Enable https for bff server + bffHttpsEnabled: true + +registryServer: docker.io + +issuerConfig: + # oidc certificate info + # if using kube-odic-proxy, should add both oidc and proxy IP address here + certificate: + # MUST update this value + oidcIPs: + - + # MUST update this value + dnsNames: + - portal..nip.io + - oidc-server + - oidc-server.u4a-system + - oidc-server.u4a-system.svc + - kube-oidc-proxy + - kube-oidc-proxy.u4a-system + - kube-oidc-proxy.u4a-system.svc + - k8s..nip.io + spec: + # Use selfSigned or specified CA(such as CA from kubernetes) + selfSigned: {} + # ca: + # secretName: k8s-ca-key-pair + +# The ingress class id of the nginx ingress to expose the services for external access +ingress: + name: portal-ingress + +############################################################################################### +### Below is the configuration for each service, in most cases, you don't need to update them +### But update as you need if it's required, such as image, connector etc... +############################################################################################### +# Optional but the default: Use Kubernetes CRD for user provider - iam provider +iamProvider: + enabled: true + image: kubebb/iam-provider-ce:v0.1.0 + +# Required: Use dex as the odic service +oidcServer: + enabled: true + host: portal..nip.io + cert: + ipAddresses: + - + dnsNames: + - portal..nip.io + - oidc-server + - oidc-server.u4a-system + - oidc-server.u4a-system.svc + - kube-oidc-proxy + - kube-oidc-proxy.u4a-system + - kube-oidc-proxy.u4a-system.svc + - k8s..nip.io + image: kubebb/oidc-server-ce:v0.1.0 + issuer: https://{{ .Values.deploymentConfig.bffHost }}/oidc + storageType: kubernetes + webHttps: 0.0.0.0:5556 + clientId: bff-client + connectors: + - type: k8scrd + name: k8scrd + id: k8scrd + config: + host: https://127.0.0.1:443 + insecureSkipVerify: true + staticClients: + - id: bff-client + redirectURIs: + - https://{{ .Values.deploymentConfig.bffHost }}/ + name: bff-client + secret: 61324af0-1234-4f61-b110-ef57013267d6 + # Enable and update the ip if nip.io is NOT accessible in deployed environment + hostConfig: + enabled: true + hostAliases: + - hostnames: + - portal..nip.io + ip: + # only enable for debug purpose + debug: false + +# Optional but the default: BFF server for all API endpoints +bffServer: + enabled: true + image: kubebb/bff-server-ce:v0.1.4 + host: portal..nip.io + connectorId: k8scrd + clientId: bff-client + clientSecret: 61324af0-1234-4f61-b110-ef57013267d6 + # Enable and update the ip if nip.io is NOT accessible in deployed environment + hostConfig: + enabled: true + hostAliases: + - hostnames: + - portal..nip.io + ip: + +# Required: the host Kubernetes cluster with OIDC enabled +# or use kube-oidc-proxy in front of k8s-apiserver +k8s: + hostK8sApiWithOidc: https://k8s..nip.io + +# Generate tenant/namespace/user view for query +# Install if it's host cluster +resourceView: + image: kubebb/resource-viewer-ce:v0.1.0 + +addon-component: + enabled: true + tenantManagement: + image: kubebb/capsule-ce:v0.1.2-20221122 + kubeOidcProxy: + image: kubebb/kube-oidc-proxy-ce:v0.3.0-20221008 + issuerUrl: https://portal..nip.io/oidc + clientId: bff-client + ingress: + enabled: true + name: portal-ingress + host: k8s..nip.io + certificate: + ipAddresses: + # MUST update this value to the host ip of kube-oidc-proxy + - + dnsNames: + - kube-oidc-proxy + - kube-oidc-proxy.u4a-system + - kube-oidc-proxy.u4a-system.svc + hostConfig: + enabled: true + hostAliases: + - hostnames: + # MUST update this value + - portal..nip.io + # MUST update this value + ip: + +# Optional: Enable it if use LDAP as user provider +ldapProvider: + enabled: false + storageClass: openebs-hostpath +# ldapImage: 172.22.96.19/u4a_system/openldap:1.5.0 +# ldapOrg: test +# ldapDomain: test.com +# ldapAdminPwd: xxx +# ldapAdminImage: 172.22.96.19/u4a_system/phpldapadmin:stable + +cluster-component: + enabled: false` +) + +func init() { + Enroll(CLUSTERCOMPONENT, CommonInstaller) + Enroll(U4A, CommonInstaller) + Enroll(COMPONENTSTORE, CommonInstaller) +} + +func initCli() { + cfg, err := ctrl.GetConfig() + if err != nil { + panic(err) + } + + fmt.Println("init controller runtime client") + scheme := runtime.NewScheme() + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(appsv1.AddToScheme(scheme)) + utilruntime.Must(corev1.AddToScheme(scheme)) + cc, err = client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + panic(err) + } +} + +func CommonInstaller(cfg *Config) Installer { + return &ClusterComponent{cfg: cfg} +} + +type ClusterComponent struct { + cfg *Config +} + +func (c *ClusterComponent) Description() string { + return fmt.Sprintf("Install %s", c.cfg.RegisterName) +} + +func (c *ClusterComponent) Install(ctx context.Context) error { + once.Do(initCli) + componentPlan := v1alpha1.ComponentPlan{} + if err := yaml.Unmarshal([]byte(installTemplate), &componentPlan); err != nil { + return err + } + + componentPlan.SetNamespace(c.cfg.Namespace) + componentPlan.SetName(c.cfg.RegisterName) + componentPlan.Spec.InstallVersion = c.cfg.Version + componentPlan.Spec.Name = c.cfg.RegisterName + componentPlan.Spec.ComponentRef.Namespace = c.cfg.Namespace + componentPlan.Spec.Creator = "system:serviceaccount:u4a-system:kubebb-core" + componentPlan.Spec.ComponentRef.Name = fmt.Sprintf("%s.%s", DEFAULTINSTALLREPO, c.cfg.RegisterName) + + if c.cfg.RegisterName == U4A { + a := strings.ReplaceAll(u4aValueYaml, "", c.cfg.NodeIP) + cm := corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: "u4acm", + Namespace: c.cfg.Namespace, + }, + Data: map[string]string{ + "values.yaml": a, + }, + } + if err := cc.Create(ctx, &cm); err != nil { + return err + } + componentPlan.Spec.Override.ValuesFrom = []*v1alpha1.ValuesReference{ + { + Kind: "ConfigMap", + Name: "u4acm", + ValuesKey: "values.yaml", + }, + } + } else { + componentPlan.Spec.Override.Set = c.cfg.Args.Values + } + err := cc.Create(ctx, &componentPlan) + if c.cfg.RegisterName == CLUSTERCOMPONENT { + // here need to wait for cert-manager to update the core to ensure that the webhook will work. + return WaitDeployment(ctx, cc, c.cfg.Namespace, []string{"cert-manager", "cert-manager-cainjector", "cert-manager-webhook", "cluster-component-ingress-nginx-controller"}, 300) + } + return err +} + +func (c *ClusterComponent) Upgrade(ctx context.Context) error { + // not support + return nil +} + +func (c *ClusterComponent) Uninstall(ctx context.Context) { + once.Do(initCli) + componentPlan := v1alpha1.ComponentPlan{ + ObjectMeta: v1.ObjectMeta{ + Name: c.cfg.RegisterName, + Namespace: c.cfg.Namespace, + }, + } + _ = cc.Delete(ctx, &componentPlan) +} diff --git a/pkg/cmd/config.go b/pkg/cmd/config.go index 6a2322b7..21b77a00 100644 --- a/pkg/cmd/config.go +++ b/pkg/cmd/config.go @@ -17,102 +17,87 @@ limitations under the License. package cmd import ( + "context" "fmt" + "os" + "time" - "github.com/spf13/cobra" - "k8s.io/client-go/rest" + "github.com/go-logr/logr" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/repo" + + "github.com/kubebb/core/pkg/helm" ) -type config struct { - // {"core": []string{"webhook.enalbe=true"}} - installers map[string][]string - // maybe for helm client - cfg *rest.Config // nolint - // If necessary, define other parameters here +func init() { + EnsureRepo() +} + +type Config struct { + Install bool + Upgrade bool + RegisterName string + Namespace string + Version string + NodeName string // for ingress + NodeIP string // for ingress + Args values.Options } -type InstallerFunc func(*config) Installer +func NewConf(registerName string) Config { + return Config{ + Install: true, + Upgrade: false, + RegisterName: registerName, + Args: values.Options{}, + } +} var ( - store map[string]InstallerFunc + store = map[string]InstallFunc{} ) const ( - CORE = "core" + CORE = "kubebb-core" CLUSTERCOMPONENT = "cluster-component" - U4A = "u4a" + U4A = "u4a-component" COMPONENTSTORE = "component-store" + + KUBEBBOFFICIALREPO = "kubebb-official-repo" + // TODO set as flag param + KUBEBBOFFICIALREPOADDRESS = "https://kubebb.github.io/components" + DEFAULTINSTALLREPO = "kubebb" ) +type InstallFunc func(*Config) Installer + // Enroll Only name conflicts will return an error -func Enroll(name string, instance InstallerFunc) { - store[name] = instance +func Enroll(name string, i InstallFunc) { + store[name] = i } -func GetInstaller(name string) InstallerFunc { +func GetInstaller(name string) InstallFunc { return store[name] } type Installer interface { Description() string - Install() error - Uninstall() + Install(context.Context) error + Upgrade(context.Context) error + Uninstall(context.Context) } -func NewInstallCmd() *cobra.Command { - var ( - installClusterComponent, installU4A, installComponentStore bool - ) - cmd := &cobra.Command{ - Use: "install", - Long: "install core, cluster-component", - Run: func(cmd *cobra.Command, args []string) { - initConf := config{ - // Must install core - installers: map[string][]string{ - CORE: {}, - CLUSTERCOMPONENT: {}, - U4A: {}, - COMPONENTSTORE: {}, - }, - } - if installClusterComponent { // nolint - // if install cluster-component, we will upgrade core - // TODO - } - if installU4A { // nolint - // make sure cluster-component is set - // TODO - } - if installComponentStore { // nolint - // TODO - } - - // get complete config - installTasks := []string{CORE, CLUSTERCOMPONENT, U4A, COMPONENTSTORE} - tasks := make([]Installer, 0) - for _, task := range installTasks { - if _, ok := initConf.installers[task]; ok { - if fn := GetInstaller(task); fn != nil { - tasks = append(tasks, fn(&initConf)) - } - } - } - - for idx, installer := range tasks { - fmt.Printf("Step %d: %s\n", idx+1, installer.Description()) - if err := installer.Install(); err != nil { - // uninstall - for i := idx; i >= 0; i-- { - tasks[i].Uninstall() - } - } - } - }, +func EnsureRepo() { + entry := repo.Entry{ + Name: KUBEBBOFFICIALREPO, + URL: KUBEBBOFFICIALREPOADDRESS, } - - cmd.Flags().BoolVar(&installClusterComponent, "cluster-component", false, "install cluster-component?") - cmd.Flags().BoolVar(&installU4A, "u4a", false, "install u4a?") - cmd.Flags().BoolVar(&installComponentStore, "component-store", false, "install component-store?") - return cmd + logger := logr.Logger{} + ctx := context.Background() + if err := helm.RepoRemove(ctx, logger, entry.Name); err != nil && !(err.Error() == "no repositories configured" || err.Error() == "no repo named \"kubebb-official-repo\" found") { + fmt.Fprintf(os.Stderr, "failed to add repo %s", err) + return + } + fmt.Println("try to add repo") + _ = helm.RepoAdd(ctx, logger, entry, 5*time.Second) } diff --git a/pkg/cmd/core.go b/pkg/cmd/core.go new file mode 100644 index 00000000..690ce403 --- /dev/null +++ b/pkg/cmd/core.go @@ -0,0 +1,137 @@ +/* +Copyright 2023 The Kubebb 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 cmd + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/go-logr/logr" + "k8s.io/cli-runtime/pkg/genericclioptions" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/kubebb/core/pkg/helm" +) + +func init() { + Enroll(CORE, CoreInstaller) +} + +func CoreInstaller(cfg *Config) Installer { + return &Core{cfg: cfg} +} + +type Core struct { + cfg *Config +} + +func (c *Core) Description() string { + return fmt.Sprintf("Install %s", c.cfg.RegisterName) +} + +func (c *Core) Install(ctx context.Context) error { + cfg, err := ctrl.GetConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "can't get cluster config %s", err) + return fmt.Errorf("[%s] Failed to get cluster config %w", CORE, err) + } + getter := genericclioptions.ConfigFlags{ + APIServer: &cfg.Host, + CAFile: &cfg.CAFile, + BearerToken: &cfg.BearerToken, + Namespace: &c.cfg.Namespace, + } + fmt.Printf("\t[%s] construct new helm wrapper\n", CORE) + logger := logr.Logger{} + hl, err := helm.NewHelmWrapper(&getter, c.cfg.Namespace, logger) + if err != nil { + fmt.Fprintf(os.Stderr, "new helm wrapper error %s", err) + return err + } + + actionClient := hl.GetDefaultInstallCfg() + actionClient.Version = c.cfg.Version + fmt.Printf("\t[%s] helm install %s %s/kubebb-core\n", CORE, CORE, KUBEBBOFFICIALREPO) + _, _, err = hl.Install(ctx, logger, actionClient, &c.cfg.Args, "kubebb-core", fmt.Sprintf("%s/kubebb-core", KUBEBBOFFICIALREPO)) + if err != nil { + fmt.Fprintf(os.Stderr, "faile to install %s with error: %s", CORE, err) + return err + } + once.Do(initCli) + fmt.Printf("\t[%s] wait %s's deployments\n", CORE, CORE) + + return WaitDeployment(ctx, cc, c.cfg.Namespace, []string{CORE}, 60) +} + +func (c *Core) Upgrade(ctx context.Context) error { + cfg, err := ctrl.GetConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "can't get cluster config %s", err) + return fmt.Errorf("[%s] Failed to get cluster config %w", CORE, err) + } + getter := genericclioptions.ConfigFlags{ + APIServer: &cfg.Host, + CAFile: &cfg.CAFile, + BearerToken: &cfg.BearerToken, + Namespace: &c.cfg.Namespace, + } + fmt.Printf("\t[%s] upgrade construct new helm wrapper\n", CORE) + logger := logr.Logger{} + hl, err := helm.NewHelmWrapper(&getter, c.cfg.Namespace, logger) + if err != nil { + fmt.Fprintf(os.Stderr, "new helm wrapper error %s", err) + return err + } + + actionClient, createNs := hl.GetDefaultUpgradeCfg() + fmt.Printf("\t[%s] helm upgrade %s\n", CORE, CORE) + + _, _, err = hl.Upgrade(ctx, logger, actionClient, &c.cfg.Args, CORE, fmt.Sprintf("%s/kubebb-core", KUBEBBOFFICIALREPO), createNs) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to upgrade %s with error: %s", CORE, err) + return err + } + fmt.Printf("\t sleep 30s for %s pod running\n", CORE) + time.Sleep(30 * time.Second) + + return WaitDeployment(ctx, cc, c.cfg.Namespace, []string{CORE}, 30) +} + +func (c *Core) Uninstall(ctx context.Context) { + cfg, err := ctrl.GetConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "[core operator] can't get cluster config %s", err) + } + getter := genericclioptions.ConfigFlags{ + APIServer: &cfg.Host, + CAFile: &cfg.CAFile, + BearerToken: &cfg.BearerToken, + Namespace: &c.cfg.Namespace, + } + fmt.Println("\tuninstall construct new helm wrapper") + logger := logr.Logger{} + hl, err := helm.NewHelmWrapper(&getter, c.cfg.Namespace, logger) + if err != nil { + fmt.Fprintf(os.Stderr, "new helm wrapper error %s", err) + return + } + + actionClient := hl.GetDefaultUninstallCfg() + _, _ = hl.Uninstall(ctx, logger, actionClient, CORE) +} diff --git a/pkg/cmd/create_core.go b/pkg/cmd/create_core.go deleted file mode 100644 index c2d37820..00000000 --- a/pkg/cmd/create_core.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2023 The Kubebb 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 cmd - -type core struct { - args []string -} - -func NewCoreInstaller(conf *config) Installer { - return &core{args: conf.installers[CORE]} -} - -func (c *core) Description() string { - return "Installing core" -} - -func (c *core) Install() error { - return nil -} - -func (c *core) Uninstall() {} -func init() { - Enroll(CORE, NewCoreInstaller) -} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go new file mode 100644 index 00000000..d40ee721 --- /dev/null +++ b/pkg/cmd/root.go @@ -0,0 +1,160 @@ +/* +Copyright 2023 The Kubebb 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 cmd + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +func NewInstallCmd() *cobra.Command { + var ( + coreConf = NewConf(CORE) + upgradeCoreConf = NewConf(CORE) + clusterComponentConf = NewConf(CLUSTERCOMPONENT) + componentStoreConf = NewConf(COMPONENTSTORE) + u4aConf = NewConf(U4A) + namespace string + ) + var ( + coreExtraConfString string + clusterCompnentExtrConfString string + u4aExtraConfString string + componentStroeExtraConfString string + kubeconfig string + nodeName string + ) + cmd := &cobra.Command{ + Use: "install", + Long: "install core, cluster-component", + Run: func(cmd *cobra.Command, args []string) { + background := context.Background() + if len(coreExtraConfString) > 0 { + coreConf.Args.Values = append(coreConf.Args.Values, strings.Split(coreExtraConfString, ",")...) + } + if len(clusterCompnentExtrConfString) > 0 { + clusterComponentConf.Args.Values = append(clusterComponentConf.Args.Values, strings.Split(clusterCompnentExtrConfString, ",")...) + } + if len(u4aExtraConfString) > 0 { + u4aConf.Args.Values = append(u4aConf.Args.Values, strings.Split(u4aExtraConfString, ",")...) + } + if len(componentStroeExtraConfString) > 0 { + componentStoreConf.Args.Values = append(componentStoreConf.Args.Values, strings.Split(componentStroeExtraConfString, ",")...) + } + + upgradeCoreConf.Install = false + upgradeCoreConf.Upgrade = false + // install cluster-component + if !clusterComponentConf.Install { + u4aConf.Install = false + } else { + clusterComponentConf.Args.Values = append(clusterComponentConf.Args.Values, fmt.Sprintf("%s=%s", mustAddForClusterComponent, nodeName)) + upgradeCoreConf.Upgrade = true + upgradeCoreConf.Args.Values = append(upgradeCoreConf.Args.Values, "webhook.enable=true") + } + + var nodeIP string + if !u4aConf.Install { + componentStoreConf.Install = false + } else { + once.Do(initCli) + node := corev1.Node{} + if err := cc.Get(background, types.NamespacedName{Name: nodeName}, &node); err != nil { + fmt.Fprintf(os.Stderr, "Failed to get node %s", nodeName) + return + } + + for _, address := range node.Status.Addresses { + if address.Type == corev1.NodeInternalIP { + nodeIP = address.Address + break + } + } + } + + step := 0 + taskConfs := []Config{coreConf, clusterComponentConf, upgradeCoreConf, u4aConf, componentStoreConf} + for idx := range taskConfs { + taskConf := taskConfs[idx] + taskConf.Namespace = namespace + taskConf.NodeName = nodeName + taskConf.NodeIP = nodeIP + + if !taskConf.Install && !taskConf.Upgrade { + fmt.Printf("skip %s", taskConf.RegisterName) + continue + } + instance := GetInstaller(taskConf.RegisterName) + if taskConf.Install && instance != nil { + installer := instance(&taskConf) + fmt.Printf("[%d] install %s", step, taskConf.RegisterName) + step++ + + if err := installer.Install(background); err != nil { + fmt.Fprintf(os.Stderr, "failed to install %s, with error %s. start to uninstall\n", taskConf.RegisterName, err) + // TODO uninstall + return + } + fmt.Printf("\t%s install done\n", taskConf.RegisterName) + if idx == 0 { + fmt.Println("\twait components") + // TODO: check components + time.Sleep(20 * time.Second) + } + continue + } + + if taskConf.Upgrade && instance != nil { + installer := instance(&taskConf) + fmt.Printf("[%d] upgrade %s", step, taskConf.RegisterName) + step++ + + if err := installer.Upgrade(background); err != nil { + fmt.Fprintf(os.Stderr, "failed to upgrade %s, with error %s. start to uninstall\n", taskConf.RegisterName, err) + // TODO uninstall + return + } + fmt.Printf("\t%s upgrade done\n", taskConf.RegisterName) + } + } + }, + } + + cmd.Flags().BoolVar(&clusterComponentConf.Install, "cluster-component", true, "install cluster-component?") + cmd.Flags().BoolVar(&u4aConf.Install, "u4a", true, "install u4a") + cmd.Flags().BoolVar(&componentStoreConf.Install, "component-store", true, "install component-store?") + cmd.Flags().StringVar(&namespace, "namespace", "default", "install namespace") + cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "") + cmd.Flags().StringVar(&coreConf.Version, "core-version", "", "") + cmd.Flags().StringVar(&clusterComponentConf.Version, "cc-version", "", "") + cmd.Flags().StringVar(&u4aConf.Version, "u4a-version", "", "") + cmd.Flags().StringVar(&componentStoreConf.Version, "cs-version", "", "") + cmd.Flags().StringVar(&nodeName, "node-name", "", "") + + cmd.Flags().StringVar(&coreExtraConfString, "core-extra-conf", "", "a=b,c=d") + cmd.Flags().StringVar(&clusterCompnentExtrConfString, "cluster-component-extra-conf", "", "a=b,c=d") + cmd.Flags().StringVar(&u4aExtraConfString, "u4a-extra-conf", "", "a=b,c=d") + cmd.Flags().StringVar(&componentStroeExtraConfString, "component-store-extra-conf", "", "a=b,c=d") + return cmd +} diff --git a/pkg/cmd/utils.go b/pkg/cmd/utils.go new file mode 100644 index 00000000..83d1994c --- /dev/null +++ b/pkg/cmd/utils.go @@ -0,0 +1,78 @@ +/* +Copyright 2023 The Kubebb 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 cmd + +import ( + "context" + "fmt" + "sync" + "time" + + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func WaitDeployment(ctx context.Context, c client.Client, namespace string, deployNames []string, timeourSeconds int) error { + if len(deployNames) == 0 { + return nil + } + + steps := timeourSeconds / 5 + if timeourSeconds%5 != 0 { + steps++ + } + + backOff := wait.Backoff{ + Steps: steps, + Duration: time.Second * 5, + } + + result := make(chan struct{}, len(deployNames)) + var wg sync.WaitGroup + for _, name := range deployNames { + wg.Add(1) + go func(name string) { + defer wg.Done() + dep := appsv1.Deployment{} + if err := retry.OnError(backOff, func(error) bool { + return true + }, func() error { + if err := c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, &dep); err != nil { + fmt.Printf("\t in namespace: %s not found deployment %s, wait\n", namespace, name) + return err + } + fmt.Printf("\tname: %s availableReplicas: %d --- replicas: %d\n", name, dep.Status.AvailableReplicas, *dep.Spec.Replicas) + if dep.Status.AvailableReplicas == *dep.Spec.Replicas { + return nil + } + return fmt.Errorf("deployment %s doest not match condition. availableReplicas: %d, replicas: %d", + name, dep.Status.AvailableReplicas, *dep.Spec.Replicas) + }); err != nil { + result <- struct{}{} + } + }(name) + } + wg.Wait() + if len(result) > 0 { + return fmt.Errorf("not all deployment match condition") + } + + return nil +}