feat: store profile to configmap or home dir and merge profiles to select when upgrade and uninstall (#649)

This commit is contained in:
Jun
2023-12-05 15:59:36 +08:00
committed by GitHub
parent 967fa3f3d1
commit bd4109e1a4
11 changed files with 461 additions and 51 deletions

View File

@@ -318,7 +318,45 @@ func GenProfile(profileOrPath, fileOverlayYAML string, setFlags []string) (strin
return "", nil, err
}
finalProfile.InstallPackagePath = installPackagePath
if len(installPackagePath) > 0 {
finalProfile.InstallPackagePath = installPackagePath
}
if finalProfile.Profile == "" {
finalProfile.Profile = DefaultProfileName
}
return util.ToYAML(finalProfile), finalProfile, nil
}
func GenProfileFromProfileContent(profileContent, fileOverlayYAML string, setFlags []string) (string, *Profile, error) {
installPackagePath, err := getInstallPackagePath(fileOverlayYAML)
if err != nil {
return "", nil, err
}
if sfp := GetValueForSetFlag(setFlags, "installPackagePath"); sfp != "" {
// set flag installPackagePath has the highest precedence, if set.
installPackagePath = sfp
}
// Combine file and --set overlays and translate any K8s settings in values to Profile format
overlayYAML, err := overlaySetFlagValues(fileOverlayYAML, setFlags)
if err != nil {
return "", nil, err
}
// Merge user file and --set flags.
outYAML, err := util.OverlayYAML(profileContent, overlayYAML)
if err != nil {
return "", nil, fmt.Errorf("could not overlay user config over base: %s", err)
}
finalProfile, err := UnmarshalProfile(outYAML)
if err != nil {
return "", nil, err
}
if len(installPackagePath) > 0 {
finalProfile.InstallPackagePath = installPackagePath
}
if finalProfile.Profile == "" {
finalProfile.Profile = DefaultProfileName

View File

@@ -35,6 +35,7 @@ const (
type Profile struct {
Profile string `json:"profile,omitempty"`
InstallPackagePath string `json:"installPackagePath,omitempty"`
HigressVersion string `json:"higressVersion,omitempty"`
Global ProfileGlobal `json:"global,omitempty"`
Console ProfileConsole `json:"console,omitempty"`
Gateway ProfileGateway `json:"gateway,omitempty"`

View File

@@ -17,6 +17,7 @@ package hgctl
import (
"fmt"
"io"
"os"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
@@ -134,7 +135,7 @@ func install(writer io.Writer, iArgs *InstallArgs) error {
return fmt.Errorf("generate config: %v", err)
}
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
fmt.Fprintf(writer, "\n🧐 Validating Profile: \"%s\" \n", profileName)
err = profile.Validate()
if err != nil {
return err
@@ -144,6 +145,12 @@ func install(writer io.Writer, iArgs *InstallArgs) error {
if err != nil {
return fmt.Errorf("failed to install manifests: %v", err)
}
// Remove "~/.hgctl/profiles/install.yaml"
if oldProfileName, isExisted := installer.GetInstalledYamlPath(); isExisted {
_ = os.Remove(oldProfileName)
}
return nil
}

View File

@@ -70,6 +70,7 @@ func (h *HigressComponent) Run() error {
if err := h.renderer.Init(); err != nil {
return err
}
h.profile.HigressVersion = h.opts.Version
h.started = true
return nil
}

View File

@@ -17,17 +17,17 @@ package installer
import (
"errors"
"fmt"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"io"
"os"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
)
type DockerInstaller struct {
started bool
standalone *StandaloneComponent
profile *helm.Profile
writer io.Writer
started bool
standalone *StandaloneComponent
profile *helm.Profile
writer io.Writer
profileStore ProfileStore
}
func (d *DockerInstaller) Install() error {
@@ -37,11 +37,11 @@ func (d *DockerInstaller) Install() error {
return err
}
profileName, _ := GetInstalledYamlPath()
fmt.Fprintf(d.writer, "\n✔ Wrote Profile: \"%s\" \n", profileName)
if err := util.WriteFileString(profileName, util.ToYAML(d.profile), 0o644); err != nil {
return err
profileName, err1 := d.profileStore.Save(d.profile)
if err1 != nil {
return err1
}
fmt.Fprintf(d.writer, "\n✔ Wrote Profile: \"%s\" \n", profileName)
fmt.Fprintf(d.writer, "\n🎊 Install All Resources Complete!\n")
return nil
@@ -55,9 +55,11 @@ func (d *DockerInstaller) UnInstall() error {
return err
}
profileName, _ := GetInstalledYamlPath()
profileName, err1 := d.profileStore.Delete(d.profile)
if err1 != nil {
return err1
}
fmt.Fprintf(d.writer, "\n✔ Removed Profile: \"%s\" \n", profileName)
os.Remove(profileName)
fmt.Fprintf(d.writer, "\n🎊 Uninstall All Resources Complete!\n")
return nil
@@ -92,10 +94,19 @@ func NewDockerInstaller(profile *helm.Profile, writer io.Writer, quiet bool) (*D
return nil, fmt.Errorf("NewStandaloneComponent failed, err: %s", err)
}
profileInstalledPath, err1 := GetProfileInstalledPath()
if err1 != nil {
return nil, err1
}
profileStore, err2 := NewFileDirProfileStore(profileInstalledPath)
if err2 != nil {
return nil, err2
}
op := &DockerInstaller{
profile: profile,
standalone: standaloneComponent,
writer: writer,
profile: profile,
standalone: standaloneComponent,
writer: writer,
profileStore: profileStore,
}
return op, nil
}

View File

@@ -20,6 +20,7 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm/object"
@@ -28,11 +29,12 @@ import (
)
type K8sInstaller struct {
started bool
components map[ComponentName]Component
kubeCli kubernetes.CLIClient
profile *helm.Profile
writer io.Writer
started bool
components map[ComponentName]Component
kubeCli kubernetes.CLIClient
profile *helm.Profile
writer io.Writer
profileStore ProfileStore
}
func (o *K8sInstaller) Install() error {
@@ -44,10 +46,6 @@ func (o *K8sInstaller) Install() error {
return nil
}
if _, err := GetProfileInstalledPath(); err != nil {
return err
}
if err := o.Run(); err != nil {
return err
}
@@ -62,11 +60,16 @@ func (o *K8sInstaller) Install() error {
return err
}
profileName, _ := GetInstalledYamlPath()
fmt.Fprintf(o.writer, "\n✔ Wrote Profile: \"%s\" \n", profileName)
if err := util.WriteFileString(profileName, util.ToYAML(o.profile), 0o644); 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")
@@ -92,9 +95,11 @@ func (o *K8sInstaller) UnInstall() error {
return err
}
profileName, _ := GetInstalledYamlPath()
profileName, err1 := o.profileStore.Delete(o.profile)
if err1 != nil {
return err1
}
fmt.Fprintf(o.writer, "\n✔ Removed Profile: \"%s\" \n", profileName)
os.Remove(profileName)
fmt.Fprintf(o.writer, "\n🎊 Uninstall All Resources Complete!\n")
@@ -322,11 +327,17 @@ func NewK8sInstaller(profile *helm.Profile, cli kubernetes.CLIClient, writer io.
components[GatewayAPI] = gatewayAPIComponent
}
profileStore, err := NewConfigmapProfileStore(cli)
if err != nil {
return nil, err
}
op := &K8sInstaller{
profile: profile,
components: components,
kubeCli: cli,
writer: writer,
profile: profile,
components: components,
kubeCli: cli,
writer: writer,
profileStore: profileStore,
}
return op, nil
}

View File

@@ -0,0 +1,247 @@
// 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 (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
const (
ProfileConfigmapKey = "profile"
ProfileConfigmapName = "higress-profile"
ProfileConfigmapAnnotation = "higress.io/install"
ProfileFilePrefix = "install"
)
type ProfileContext struct {
Profile *helm.Profile
SourceType string
Namespace string
PathOrName string
Install helm.InstallMode
HigressVersion string
}
type ProfileStore interface {
Save(profile *helm.Profile) (string, error)
List() ([]*ProfileContext, error)
Delete(profile *helm.Profile) (string, error)
}
type FileDirProfileStore struct {
profilesPath string
}
func (f *FileDirProfileStore) Save(profile *helm.Profile) (string, error) {
namespace := profile.Global.Namespace
install := profile.Global.Install
var profileName = ""
if install == helm.InstallK8s || install == helm.InstallLocalK8s {
profileName = filepath.Join(f.profilesPath, fmt.Sprintf("%s-%s.yaml", ProfileFilePrefix, namespace))
} else {
profileName = filepath.Join(f.profilesPath, fmt.Sprintf("%s-%s.yaml", ProfileFilePrefix, install))
}
if err := util.WriteFileString(profileName, util.ToYAML(profile), 0o644); err != nil {
return "", err
}
return profileName, nil
}
func (f *FileDirProfileStore) List() ([]*ProfileContext, error) {
profileContexts := make([]*ProfileContext, 0)
dir, err := os.ReadDir(f.profilesPath)
if err != nil {
return nil, err
}
for _, file := range dir {
if !strings.HasSuffix(file.Name(), ".yaml") {
continue
}
if file.IsDir() {
continue
}
fileName := filepath.Join(f.profilesPath, file.Name())
content, err2 := os.ReadFile(fileName)
if err2 != nil {
continue
}
profile, err3 := helm.UnmarshalProfile(string(content))
if err3 != nil {
continue
}
profileContext := &ProfileContext{
Profile: profile,
Namespace: profile.Global.Namespace,
Install: profile.Global.Install,
HigressVersion: profile.HigressVersion,
SourceType: "file",
PathOrName: fileName,
}
profileContexts = append(profileContexts, profileContext)
}
return profileContexts, nil
}
func (f *FileDirProfileStore) Delete(profile *helm.Profile) (string, error) {
namespace := profile.Global.Namespace
install := profile.Global.Install
var profileName = ""
if install == helm.InstallK8s || install == helm.InstallLocalK8s {
profileName = filepath.Join(f.profilesPath, fmt.Sprintf("%s-%s.yaml", ProfileFilePrefix, namespace))
} else {
profileName = filepath.Join(f.profilesPath, fmt.Sprintf("%s-%s.yaml", ProfileFilePrefix, install))
}
if err := os.Remove(profileName); err != nil {
return "", err
}
return profileName, nil
}
func NewFileDirProfileStore(profilesPath string) (ProfileStore, error) {
if _, err := os.Stat(profilesPath); os.IsNotExist(err) {
if err = os.MkdirAll(profilesPath, os.ModePerm); err != nil {
return nil, err
}
}
profileStore := &FileDirProfileStore{
profilesPath: profilesPath,
}
return profileStore, nil
}
type ConfigmapProfileStore struct {
kubeCli kubernetes.CLIClient
}
func (c *ConfigmapProfileStore) Save(profile *helm.Profile) (string, error) {
bytes, err := json.Marshal(profile)
jsonProfile := ""
if err == nil {
jsonProfile = string(bytes)
}
annotation := make(map[string]string, 0)
annotation[ProfileConfigmapAnnotation] = jsonProfile
configmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: profile.Global.Namespace,
Name: ProfileConfigmapName,
Annotations: annotation,
},
}
configmap.Data = make(map[string]string, 0)
configmap.Data[ProfileConfigmapKey] = util.ToYAML(profile)
name := fmt.Sprintf("%s/%s", profile.Global.Namespace, ProfileConfigmapName)
if err := c.applyConfigmap(configmap); err != nil {
return "", err
}
return name, nil
}
func (c *ConfigmapProfileStore) List() ([]*ProfileContext, error) {
profileContexts := make([]*ProfileContext, 0)
configmapList, err := c.listConfigmaps(ProfileConfigmapName, "", 100)
if err != nil {
return profileContexts, err
}
for _, configmap := range configmapList.Items {
if data, ok := configmap.Data[ProfileConfigmapKey]; ok {
profile, err := helm.UnmarshalProfile(data)
if err != nil {
continue
}
profileContext := &ProfileContext{
Profile: profile,
Namespace: profile.Global.Namespace,
Install: profile.Global.Install,
HigressVersion: profile.HigressVersion,
SourceType: "configmap",
PathOrName: fmt.Sprintf("%s/%s", profile.Global.Namespace, configmap.Name),
}
profileContexts = append(profileContexts, profileContext)
}
}
return profileContexts, nil
}
func (c *ConfigmapProfileStore) Delete(profile *helm.Profile) (string, error) {
configmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: profile.Global.Namespace,
Name: ProfileConfigmapName,
},
}
name := fmt.Sprintf("%s/%s", profile.Global.Namespace, ProfileConfigmapName)
if err := c.deleteConfigmap(configmap); err != nil {
return "", err
}
return name, nil
}
func (c *ConfigmapProfileStore) listConfigmaps(name string, namespace string, size int64) (*corev1.ConfigMapList, error) {
var result *corev1.ConfigMapList
var err error
if len(namespace) == 0 {
result, err = c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps("").List(context.Background(), metav1.ListOptions{Limit: size, FieldSelector: fmt.Sprintf("metadata.name=%s", name)})
} else {
result, err = c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps(namespace).List(context.Background(), metav1.ListOptions{Limit: size, FieldSelector: fmt.Sprintf("metadata.name=%s", name)})
}
if err != nil {
return nil, err
}
return result, nil
}
func (c *ConfigmapProfileStore) applyConfigmap(configmap *corev1.ConfigMap) error {
_, err := c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps(configmap.Namespace).Get(context.Background(), configmap.Name, metav1.GetOptions{})
if err != nil && errors.IsNotFound(err) {
_, err = c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps(configmap.Namespace).Create(context.Background(), configmap, metav1.CreateOptions{})
return err
} else if err != nil {
return err
} else {
_, err = c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps(configmap.Namespace).Update(context.Background(), configmap, metav1.UpdateOptions{})
return err
}
}
func (c *ConfigmapProfileStore) deleteConfigmap(configmap *corev1.ConfigMap) error {
err := c.kubeCli.KubernetesInterface().CoreV1().ConfigMaps(configmap.Namespace).Delete(context.Background(), configmap.Name, metav1.DeleteOptions{})
if err != nil {
if !errors.IsNotFound(err) {
return err
}
}
return nil
}
func NewConfigmapProfileStore(kubeCli kubernetes.CLIClient) (ProfileStore, error) {
profileStore := &ConfigmapProfileStore{
kubeCli: kubeCli,
}
return profileStore, nil
}

View File

@@ -58,7 +58,10 @@ func (s *StandaloneComponent) Install() error {
if err := s.agent.Install(); err != nil {
return err
}
// Set Higress version
if version, err := s.agent.Version(); err == nil {
s.profile.HigressVersion = version
}
return nil
}
@@ -86,7 +89,10 @@ func (s *StandaloneComponent) Upgrade() error {
if err := s.agent.Upgrade(); err != nil {
return err
}
// Set Higress version
if version, err := s.agent.Version(); err != nil {
s.profile.HigressVersion = version
}
return nil
}
@@ -112,10 +118,6 @@ func NewStandaloneComponent(profile *helm.Profile, writer io.Writer, opts ...Com
}
func prepareProfile(profile *helm.Profile) error {
if _, err := GetProfileInstalledPath(); err != nil {
return err
}
if len(profile.InstallPackagePath) == 0 {
dir, err := GetDefaultInstallPackagePath()
if err != nil {

View File

@@ -253,4 +253,5 @@ func (c *client) CreateNamespace(namespace string) error {
// KubernetesInterface get kubernetes interface
func (c *client) KubernetesInterface() kubernetes.Interface {
return c.kube
}

View File

@@ -17,10 +17,12 @@ package hgctl
import (
"fmt"
"io"
"os"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
@@ -60,18 +62,22 @@ func newUninstallCmd() *cobra.Command {
// uninstall uninstalls control plane by either pruning by target revision or deleting specified manifests.
func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
profileName, ok := installer.GetInstalledYamlPath()
if !ok {
fmt.Fprintf(writer, "⌛️ Checking higress installed profiles...\n")
profileContexts, _ := getAllProfiles()
if len(profileContexts) == 0 {
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
return nil
}
setFlags := make([]string, 0)
_, profile, err := helm.GenProfile(profileName, "", setFlags)
profileContext := promptProfileContexts(writer, profileContexts)
_, profile, err := helm.GenProfileFromProfileContent(util.ToYAML(profileContext.Profile), "", setFlags)
if err != nil {
return err
}
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
fmt.Fprintf(writer, "\n🧐 Validating Profile: \"%s\" \n", profileContext.PathOrName)
err = profile.Validate()
if err != nil {
return err
@@ -95,6 +101,11 @@ func uninstall(writer io.Writer, uiArgs *uninstallArgs) error {
return err
}
// Remove "~/.hgctl/profiles/install.yaml"
if oldProfileName, isExisted := installer.GetInstalledYamlPath(); isExisted {
_ = os.Remove(oldProfileName)
}
return nil
}

View File

@@ -17,10 +17,14 @@ package hgctl
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/helm"
"github.com/alibaba/higress/pkg/cmd/hgctl/installer"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/hgctl/util"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/spf13/cobra"
)
@@ -58,8 +62,9 @@ func newUpgradeCmd() *cobra.Command {
// upgrade upgrade higress resources from the cluster.
func upgrade(writer io.Writer, iArgs *InstallArgs) error {
setFlags := applyFlagAliases(iArgs.Set, iArgs.ManifestsPath)
profileName, ok := installer.GetInstalledYamlPath()
if !ok {
fmt.Fprintf(writer, "⌛️ Checking higress installed profiles...\n")
profileContexts, _ := getAllProfiles()
if len(profileContexts) == 0 {
fmt.Fprintf(writer, "\nHigress hasn't been installed yet!\n")
return nil
}
@@ -69,12 +74,14 @@ func upgrade(writer io.Writer, iArgs *InstallArgs) error {
return err
}
_, profile, err := helm.GenProfile(profileName, valuesOverlay, setFlags)
profileContext := promptProfileContexts(writer, profileContexts)
_, profile, err := helm.GenProfileFromProfileContent(util.ToYAML(profileContext.Profile), valuesOverlay, setFlags)
if err != nil {
return err
}
fmt.Fprintf(writer, "🧐 Validating Profile: \"%s\" \n", profileName)
fmt.Fprintf(writer, "\n🧐 Validating Profile: \"%s\" \n", profileContext.PathOrName)
err = profile.Validate()
if err != nil {
return err
@@ -89,6 +96,11 @@ func upgrade(writer io.Writer, iArgs *InstallArgs) error {
return err
}
// Remove "~/.hgctl/profiles/install.yaml"
if oldProfileName, isExisted := installer.GetInstalledYamlPath(); isExisted {
_ = os.Remove(oldProfileName)
}
return nil
}
@@ -121,3 +133,71 @@ func upgradeManifests(profile *helm.Profile, writer io.Writer) error {
return nil
}
func getAllProfiles() ([]*installer.ProfileContext, error) {
profileContexts := make([]*installer.ProfileContext, 0)
profileInstalledPath, err := installer.GetProfileInstalledPath()
if err != nil {
return profileContexts, nil
}
fileProfileStore, err := installer.NewFileDirProfileStore(profileInstalledPath)
if err != nil {
return profileContexts, nil
}
fileProfileContexts, err := fileProfileStore.List()
if err == nil {
profileContexts = append(profileContexts, fileProfileContexts...)
}
cliClient, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
return profileContexts, nil
}
configmapProfileStore, err := installer.NewConfigmapProfileStore(cliClient)
if err != nil {
return profileContexts, nil
}
configmapProfileContexts, err := configmapProfileStore.List()
if err == nil {
profileContexts = append(profileContexts, configmapProfileContexts...)
}
return profileContexts, nil
}
func promptProfileContexts(writer io.Writer, profileContexts []*installer.ProfileContext) *installer.ProfileContext {
if len(profileContexts) == 1 {
fmt.Fprintf(writer, "\nFound a profile:: ")
} else {
fmt.Fprintf(writer, "\nPlease select higress installed configration profiles:\n")
}
index := 1
for _, profileContext := range profileContexts {
if len(profileContexts) > 1 {
fmt.Fprintf(writer, "\n%d: ", index)
}
fmt.Fprintf(writer, "install mode: %s, profile location: %s", profileContext.Install, profileContext.PathOrName)
if len(profileContext.Namespace) > 0 {
fmt.Fprintf(writer, ", namespace: %s", profileContext.Namespace)
}
if len(profileContext.HigressVersion) > 0 {
fmt.Fprintf(writer, ", version: %s", profileContext.HigressVersion)
}
fmt.Fprintf(writer, "\n")
index++
}
if len(profileContexts) == 1 {
return profileContexts[0]
}
answer := ""
for {
fmt.Fprintf(writer, "\nPlease input 1 to %d select, input your selection:", len(profileContexts))
fmt.Scanln(&answer)
index, err := strconv.Atoi(answer)
if err == nil && index >= 1 && index <= len(profileContexts) {
return profileContexts[index-1]
}
}
}