hgctl/pkg/installer/installer_k8s.go (286 lines of code) (raw):

// Copyright (c) 2022 Alibaba Group Holding Ltd. // // 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 installer import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "github.com/alibaba/higress/hgctl/pkg/helm" "github.com/alibaba/higress/hgctl/pkg/helm/object" "github.com/alibaba/higress/hgctl/pkg/kubernetes" "github.com/alibaba/higress/hgctl/pkg/util" ) type K8sInstaller struct { started bool components map[ComponentName]Component kubeCli kubernetes.CLIClient profile *helm.Profile writer io.Writer profileStore ProfileStore } func (o *K8sInstaller) Install() error { // check if higress is installed by helm fmt.Fprintf(o.writer, "\n⌛️ Detecting higress installed by helm or not... \n\n") helmAgent := NewHelmAgent(o.profile, o.writer, false) if helmInstalled, _ := helmAgent.IsHigressInstalled(); helmInstalled { fmt.Fprintf(o.writer, "\n🧐 You have already installed higress by helm, please use \"helm upgrade\" to upgrade higress!\n") return nil } if err := o.Run(); err != nil { return err } manifestMap, err := o.RenderManifests() if err != nil { return err } fmt.Fprintf(o.writer, "\n⌛️ Processing installation... \n\n") if err := o.ApplyManifests(manifestMap); err != nil { return err } profileName, err1 := o.profileStore.Save(o.profile) if err1 != nil { return err1 } fmt.Fprintf(o.writer, "\n✔️ Wrote Profile in kubernetes configmap: \"%s\" \n", profileName) fmt.Fprintf(o.writer, "\n Use bellow kubectl command to edit profile for upgrade. \n") fmt.Fprintf(o.writer, " ================================================================================== \n") names := strings.Split(profileName, "/") fmt.Fprintf(o.writer, " kubectl edit configmap %s -n %s \n", names[1], names[0]) fmt.Fprintf(o.writer, " ================================================================================== \n") fmt.Fprintf(o.writer, "\n🎊 Install All Resources Complete!\n") return nil } func (o *K8sInstaller) UnInstall() error { if _, err := GetProfileInstalledPath(); err != nil { return err } if err := o.Run(); err != nil { return err } manifestMap, err := o.RenderManifests() if err != nil { return err } fmt.Fprintf(o.writer, "\n⌛️ Processing uninstallation... \n\n") if err := o.DeleteManifests(manifestMap); err != nil { return err } profileName, err1 := o.profileStore.Delete(o.profile) if err1 != nil { return err1 } fmt.Fprintf(o.writer, "\n✔️ Removed Profile: \"%s\" \n", profileName) fmt.Fprintf(o.writer, "\n🎊 Uninstall All Resources Complete!\n") return nil } func (o *K8sInstaller) Upgrade() error { return o.Install() } // Run must be invoked before invoking other functions. func (o *K8sInstaller) Run() error { for name, component := range o.components { if !component.Enabled() { continue } if err := component.Run(); err != nil { return fmt.Errorf("component %s run failed, err: %s", name, err) } } o.started = true return nil } // RenderManifests renders component manifests specified by profile. func (o *K8sInstaller) RenderManifests() (map[ComponentName]string, error) { if !o.started { return nil, errors.New("higress installer is not running") } res := make(map[ComponentName]string) for name, component := range o.components { if !component.Enabled() { continue } manifest, err := component.RenderManifest() if err != nil { return nil, fmt.Errorf("component %s RenderManifest err: %v", name, err) } res[name] = manifest } return res, nil } // GenerateManifests generates component manifests to k8s cluster func (o *K8sInstaller) GenerateManifests(manifestMap map[ComponentName]string) error { if o.kubeCli == nil { return errors.New("no injected k8s cli into K8sInstaller") } for _, manifest := range manifestMap { fmt.Fprint(o.writer, manifest) } return nil } // ApplyManifests apply component manifests to k8s cluster func (o *K8sInstaller) ApplyManifests(manifestMap map[ComponentName]string) error { if o.kubeCli == nil { return errors.New("no injected k8s cli into K8sInstaller") } for name, manifest := range manifestMap { namespace := o.components[name].Namespace() if err := o.applyManifest(manifest, namespace); err != nil { return fmt.Errorf("component %s ApplyManifest err: %v", name, err) } } return nil } func (o *K8sInstaller) applyManifest(manifest string, ns string) error { if err := o.kubeCli.CreateNamespace(ns); err != nil { return err } objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest) if err != nil { return err } for _, obj := range objs { // check namespaced object if namespace property has been existed if obj.Namespace == "" && o.isNamespacedObject(obj) { obj.Namespace = ns obj.UnstructuredObject().SetNamespace(ns) } if o.isNamespacedObject(obj) { fmt.Fprintf(o.writer, "✔️ Installed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace) } else { fmt.Fprintf(o.writer, "✔️ Installed %s::%s.\n", obj.Kind, obj.Name) } if err := o.kubeCli.ApplyObject(obj.UnstructuredObject()); err != nil { return err } } return nil } // DeleteManifests delete component manifests to k8s cluster func (o *K8sInstaller) DeleteManifests(manifestMap map[ComponentName]string) error { if o.kubeCli == nil { return errors.New("no injected k8s cli into K8sInstaller") } for name, manifest := range manifestMap { namespace := o.components[name].Namespace() if err := o.deleteManifest(manifest, namespace); err != nil { return fmt.Errorf("component %s DeleteManifest err: %v", name, err) } } return nil } // WriteManifests write component manifests to local files func (o *K8sInstaller) WriteManifests(manifestMap map[ComponentName]string) error { if o.kubeCli == nil { return errors.New("no injected k8s cli into K8sInstaller") } rootPath, _ := os.Getwd() for name, manifest := range manifestMap { fileName := filepath.Join(rootPath, string(name)+".yaml") util.WriteFileString(fileName, manifest, 0o644) } return nil } // deleteManifest delete manifest to certain namespace func (o *K8sInstaller) deleteManifest(manifest string, ns string) error { objs, err := object.ParseK8sObjectsFromYAMLManifest(manifest) if err != nil { return err } for _, obj := range objs { // check namespaced object if namespace property has been existed if obj.Namespace == "" && o.isNamespacedObject(obj) { obj.Namespace = ns obj.UnstructuredObject().SetNamespace(ns) } if o.isNamespacedObject(obj) { fmt.Fprintf(o.writer, "✔️ Removed %s:%s:%s.\n", obj.Kind, obj.Name, obj.Namespace) } else { fmt.Fprintf(o.writer, "✔️ Removed %s::%s.\n", obj.Kind, obj.Name) } if err := o.kubeCli.DeleteObject(obj.UnstructuredObject()); err != nil { return err } } return nil } func (o *K8sInstaller) isNamespacedObject(obj *object.K8sObject) bool { if obj.Kind != "CustomResourceDefinition" && obj.Kind != "ClusterRole" && obj.Kind != "ClusterRoleBinding" { return true } return false } func NewK8sInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.Writer, quiet bool, devel bool, installerMode InstallerMode) (*K8sInstaller, error) { if profile == nil { return nil, errors.New("install profile is empty") } // initialize server info serverInfo, _ := NewServerInfo(cli) fmt.Fprintf(writer, "\n⌛️ Detecting kubernetes version ... ") capabilities, err := serverInfo.GetCapabilities() if err != nil { return nil, err } fmt.Fprintf(writer, "%s\n", capabilities.KubeVersion.Version) // initialize components higressVersion := profile.Charts.Higress.Version if installerMode == UninstallInstallerMode { // uninstall higressVersion = profile.HigressVersion } components := make(map[ComponentName]Component) opts := []ComponentOption{ WithComponentNamespace(profile.Global.Namespace), WithComponentChartPath(profile.InstallPackagePath), WithComponentVersion(higressVersion), WithComponentRepoURL(profile.Charts.Higress.Url), WithComponentChartName(profile.Charts.Higress.Name), WithComponentCapabilities(capabilities), WithDevel(devel), } if quiet { opts = append(opts, WithQuiet()) } higressComponent, err := NewHigressComponent(cli, profile, writer, opts...) if err != nil { return nil, fmt.Errorf("NewHigressComponent failed, err: %s", err) } components[Higress] = higressComponent if profile.IstioEnabled() { istioNamespace := profile.GetIstioNamespace() if len(istioNamespace) == 0 { istioNamespace = DefaultIstioNamespace } opts := []ComponentOption{ WithComponentNamespace(istioNamespace), WithComponentVersion("1.18.2"), WithComponentRepoURL("embed://istiobase"), WithComponentChartName("istio"), WithComponentCapabilities(capabilities), } if quiet { opts = append(opts, WithQuiet()) } istioCRDComponent, err := NewIstioCRDComponent(cli, profile, writer, opts...) if err != nil { return nil, fmt.Errorf("NewIstioCRDComponent failed, err: %s", err) } components[Istio] = istioCRDComponent } if profile.GatewayAPIEnabled() { opts := []ComponentOption{ WithComponentNamespace(DefaultGatewayAPINamespace), WithComponentVersion("1.0.0"), WithComponentRepoURL("embed://gatewayapi"), WithComponentChartName("gatewayAPI"), WithComponentCapabilities(capabilities), } if quiet { opts = append(opts, WithQuiet()) } gatewayAPIComponent, err := NewGatewayAPIComponent(cli, profile, writer, opts...) if err != nil { return nil, fmt.Errorf("NewGatewayAPIComponent failed, err: %s", err) } components[GatewayAPI] = gatewayAPIComponent } profileStore, err := NewConfigmapProfileStore(cli) if err != nil { return nil, err } op := &K8sInstaller{ profile: profile, components: components, kubeCli: cli, writer: writer, profileStore: profileStore, } return op, nil }