From fe992ae53f6be7309d7b730850382eb97a32810d Mon Sep 17 00:00:00 2001 From: nio Date: Mon, 27 Jun 2022 16:44:34 +0800 Subject: [PATCH] Replace the Helm command (#4852) * Replace the helm command line with helm action * fix log misspelling * helm wrapper formate log msg * fix: helm action faild in multi cluster --- .../openpitrix/helmwrapper/helm_wrapper.go | 400 +++++------------- .../helmwrapper/helm_wrapper_darwin.go | 21 - .../helmwrapper/helm_wrapper_linux.go | 21 - .../helmwrapper/helm_wrapper_test.go | 44 +- .../helmwrapper/post_renderer_kustomize.go | 114 +++++ .../helmwrapper/rest_client_getter.go | 87 ++++ 6 files changed, 345 insertions(+), 342 deletions(-) delete mode 100644 pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_darwin.go delete mode 100644 pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_linux.go create mode 100644 pkg/simple/client/openpitrix/helmwrapper/post_renderer_kustomize.go create mode 100644 pkg/simple/client/openpitrix/helmwrapper/rest_client_getter.go diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go index c07523c84..f3fae3416 100644 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go +++ b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper.go @@ -18,24 +18,21 @@ package helmwrapper import ( "bytes" - "encoding/json" "fmt" "os" - "os/exec" "path/filepath" - "strings" "time" - "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/kube" "k8s.io/apimachinery/pkg/util/wait" - yaml "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chartutil" helmrelease "helm.sh/helm/v3/pkg/release" "k8s.io/klog" kpath "k8s.io/utils/path" - "sigs.k8s.io/kustomize/api/types" "kubesphere.io/kubesphere/pkg/server/errors" "kubesphere.io/kubesphere/pkg/utils/idutils" @@ -48,18 +45,9 @@ const ( var ( ErrorTimedOutToWaitResource = errors.New("timed out waiting for resources to be ready") - UninstallNotFoundFormat = "Error: uninstall: Release not loaded: %s: release: not found" - StatusNotFoundFormat = "Error: release: not found" + UninstallNotFoundFormat = "uninstall: Release not loaded: %s: release: not found" + StatusNotFoundFormat = "release: not found" releaseExists = "release exists" - - kustomizationFile = "kustomization.yaml" - postRenderExecFile = "helm-post-render.sh" - // kustomize cannot read stdio now, so we save helm stdout to file, then kustomize reads that file and build the resources - kustomizeBuild = `#!/bin/sh -# save helm stdout to file, then kustomize read this file -cat > ./.local-helm-output.yaml -kustomize build -` ) type HelmRes struct { @@ -89,23 +77,7 @@ func (c *helmWrapper) IsReleaseReady(waitTime time.Duration) (bool, error) { if err != nil { return false, err } - - var client *kube.Client - if c.Kubeconfig == "" { - client = kube.New(nil) - } else { - // kube.New() needs kubeconfig. - err := c.ensureWorkspace() - if err != nil { - return false, err - } - defer c.cleanup() - helmSettings := cli.New() - helmSettings.KubeConfig = c.kubeConfigPath() - client = kube.New(helmSettings.RESTClientGetter()) - } - - client.Namespace = c.Namespace + client := c.helmConf.KubeClient resources, err := client.Build(bytes.NewBufferString(manifest), true) err = client.Wait(resources, waitTime) @@ -121,56 +93,22 @@ func (c *helmWrapper) IsReleaseReady(waitTime time.Duration) (bool, error) { return false, err } -func (c *helmWrapper) Status() (status *helmrelease.Release, err error) { - if err = c.ensureWorkspace(); err != nil { +func (c *helmWrapper) Status() (*helmrelease.Release, error) { + helmStatus := action.NewStatus(c.helmConf) + + rel, err := helmStatus.Run(c.ReleaseName) + if err != nil { + if err.Error() == StatusNotFoundFormat { + klog.V(2).Infof("namespace: %s, name: %s, run command failed, error: %v", c.Namespace, c.ReleaseName, err) + return nil, err + } + klog.Errorf("namespace: %s, name: %s, run command failed, error: %v", c.Namespace, c.ReleaseName, err) return nil, err } - defer c.cleanup() - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd := exec.Cmd{ - Path: c.cmdPath, - Dir: c.Workspace(), - Args: []string{ - c.cmdPath, - "status", - fmt.Sprintf("%s", c.ReleaseName), - "--namespace", - c.Namespace, - "--output", - "json", - }, - Stderr: stderr, - Stdout: stdout, - } - - if c.kubeConfigPath() != "" { - cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath()) - } - - err = cmd.Run() - - if err != nil { - helmErr := strings.TrimSpace(stderr.String()) - if helmErr == StatusNotFoundFormat { - klog.V(2).Infof("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err) - return nil, errors.New(helmErr) - } - klog.Errorf("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err) - return - } else { - klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) - klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout) - } - - status = &helmrelease.Release{} - err = json.Unmarshal(stdout.Bytes(), status) - if err != nil { - klog.Errorf("namespace: %s, name: %s, json unmarshal failed, error: %s", c.Namespace, c.ReleaseName, err) - } - - return + klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) + klog.V(8).Infof("namespace: %s, name: %s, run command success, manifest: %s", c.Namespace, c.ReleaseName, rel.Manifest) + return rel, nil } func (c *helmWrapper) Workspace() string { @@ -189,26 +127,20 @@ type helmWrapper struct { ReleaseName string ChartName string + // helm action Config + helmConf *action.Configuration + // add labels to helm chart labels map[string]string // add annotations to helm chart annotations map[string]string - // helm cmd path - cmdPath string base string workspaceSuffix string dryRun bool mock bool } -func (c *helmWrapper) kubeConfigPath() string { - if len(c.Kubeconfig) == 0 { - return "" - } - return filepath.Join(c.Workspace(), "kube.config") -} - // The dir where chart saved func (c *helmWrapper) chartDir() string { return filepath.Join(c.Workspace(), "chart") @@ -264,10 +196,14 @@ func NewHelmWrapper(kubeconfig, ns, rls string, options ...Option) *helmWrapper Namespace: ns, ReleaseName: rls, base: workspaceBase, - cmdPath: helmPath, workspaceSuffix: idutils.GetUuid36(""), } + klog.V(8).Infof("namespace: %s, name: %s, release: %s, kubeconfig:%s", c.Namespace, c.ReleaseName, rls, kubeconfig) + getter := NewClusterRESTClientGetter(kubeconfig, ns) + c.helmConf = new(action.Configuration) + c.helmConf.Init(getter, ns, "", klog.Infof) + for _, option := range options { option(c) } @@ -275,43 +211,6 @@ func NewHelmWrapper(kubeconfig, ns, rls string, options ...Option) *helmWrapper return c } -func (c *helmWrapper) setupPostRenderEnvironment() error { - if len(c.labels) == 0 && len(c.annotations) == 0 { - return nil - } - - // build the executable file - postRender, err := os.OpenFile(filepath.Join(c.Workspace(), postRenderExecFile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return err - } - _, err = postRender.WriteString(kustomizeBuild) - if err != nil { - return err - } - postRender.Close() - - // create kustomization.yaml - kustomization, err := os.OpenFile(filepath.Join(c.Workspace(), kustomizationFile), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - - kustomizationConfig := types.Kustomization{ - Resources: []string{"./.local-helm-output.yaml"}, - CommonAnnotations: c.annotations, // add extra annotations to output - Labels: []types.Label{{Pairs: c.labels}}, // Labels to add to all objects but not selectors. - } - - err = yaml.NewEncoder(kustomization).Encode(kustomizationConfig) - if err != nil { - return err - } - kustomization.Close() - - return nil -} - // ensureWorkspace check whether workspace exists or not. // If not exists, create workspace dir. func (c *helmWrapper) ensureWorkspace() error { @@ -332,18 +231,6 @@ func (c *helmWrapper) ensureWorkspace() error { return err } - if len(c.Kubeconfig) > 0 { - kubeFile, err := os.OpenFile(c.kubeConfigPath(), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return err - } - _, err = kubeFile.WriteString(c.Kubeconfig) - if err != nil { - return err - } - kubeFile.Close() - } - return nil } @@ -382,76 +269,41 @@ func (c *helmWrapper) createChart(chartName, chartData, values string) error { } // helm uninstall -func (c *helmWrapper) Uninstall() (err error) { +func (c *helmWrapper) Uninstall() error { start := time.Now() defer func() { klog.V(2).Infof("run command end, namespace: %s, name: %s elapsed: %v", c.Namespace, c.ReleaseName, time.Now().Sub(start)) }() - if err = c.ensureWorkspace(); err != nil { - return - } - defer c.cleanup() - - stderr := &bytes.Buffer{} - stdout := &bytes.Buffer{} - cmd := exec.Cmd{ - Path: c.cmdPath, - Dir: c.Workspace(), - Stdout: stdout, - Stderr: stderr, - } - - cmd.Args = make([]string, 0, 10) - - // only for mock - if c.mock { - cmd.Path = os.Args[0] - cmd.Args = []string{os.Args[0], "-test.run=TestHelperProcess", "--"} - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - } - - cmd.Args = append(cmd.Args, c.cmdPath, - "uninstall", - c.ReleaseName, - "--namespace", - c.Namespace) - + uninstall := action.NewUninstall(c.helmConf) if c.dryRun { - cmd.Args = append(cmd.Args, "--dry-run") + uninstall.DryRun = true } - if c.kubeConfigPath() != "" { - cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath()) - } - - klog.V(4).Infof("run command: %s", cmd.String()) - err = cmd.Run() - + _, err := uninstall.Run(c.ReleaseName) if err != nil { - eMsg := strings.TrimSpace(stderr.String()) // release does not exist. It's ok. - if fmt.Sprintf(UninstallNotFoundFormat, c.ReleaseName) == eMsg { + if fmt.Sprintf(UninstallNotFoundFormat, c.ReleaseName) == err.Error() { return nil } - klog.Errorf("run command failed, stderr: %s, error: %v", eMsg, err) - return errors.New("%s", eMsg) + klog.Errorf("run command failed, error: %v", err) + return err } else { klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) } - return + return nil } // helm upgrade -func (c *helmWrapper) Upgrade(chartName, chartData, values string) (err error) { +func (c *helmWrapper) Upgrade(chartName, chartData, values string) error { sts, err := c.Status() if err != nil { return err } if sts.Info.Status == "deployed" { - return c.install(chartName, chartData, values, true) + return c.writeAction(chartName, chartData, values, true) } else { err = errors.New("cannot upgrade release %s/%s, current state is %s", c.Namespace, c.ReleaseName, sts.Info.Status) return err @@ -459,7 +311,7 @@ func (c *helmWrapper) Upgrade(chartName, chartData, values string) (err error) { } // helm install -func (c *helmWrapper) Install(chartName, chartData, values string) (err error) { +func (c *helmWrapper) Install(chartName, chartData, values string) error { sts, err := c.Status() if err == nil { // helm release has been installed @@ -470,142 +322,106 @@ func (c *helmWrapper) Install(chartName, chartData, values string) (err error) { } else { if err.Error() == StatusNotFoundFormat { // continue to install - return c.install(chartName, chartData, values, false) + return c.writeAction(chartName, chartData, values, false) } return err } } -func (c *helmWrapper) install(chartName, chartData, values string, upgrade bool) (err error) { +func (c *helmWrapper) mockRelease() (*helmrelease.Release, error) { + return helmrelease.Mock(&helmrelease.MockReleaseOptions{Name: c.ReleaseName, Namespace: c.Namespace}), nil +} + +func (c *helmWrapper) helmUpgrade(chart *chart.Chart, values map[string]interface{}) (*helmrelease.Release, error) { + upgrade := action.NewUpgrade(c.helmConf) + upgrade.Namespace = c.Namespace + + if c.dryRun { + upgrade.DryRun = true + } + if len(c.labels) > 0 || len(c.annotations) > 0 { + postRenderer := newPostRendererKustomize(c.labels, c.annotations) + upgrade.PostRenderer = postRenderer + } + + return upgrade.Run(c.ReleaseName, chart, values) +} + +func (c *helmWrapper) helmInstall(chart *chart.Chart, values map[string]interface{}) (*helmrelease.Release, error) { + install := action.NewInstall(c.helmConf) + install.ReleaseName = c.ReleaseName + install.Namespace = c.Namespace + + if c.dryRun { + install.DryRun = true + } + if len(c.labels) > 0 || len(c.annotations) > 0 { + postRenderer := newPostRendererKustomize(c.labels, c.annotations) + install.PostRenderer = postRenderer + } + + return install.Run(chart, values) +} + +func (c *helmWrapper) writeAction(chartName, chartData, values string, upgrade bool) error { if klog.V(2) { start := time.Now() defer func() { - klog.V(2).Infof("run command end, namespace: %s, name: %s elapsed: %v", c.Namespace, c.ReleaseName, time.Now().Sub(start)) + klog.V(2).Infof("run command end, namespace: %s, name: %s, upgrade: %t, elapsed: %v", c.Namespace, c.ReleaseName, upgrade, time.Now().Sub(start)) }() } - if err = c.ensureWorkspace(); err != nil { - return + if err := c.ensureWorkspace(); err != nil { + return err } defer c.cleanup() - err = c.setupPostRenderEnvironment() - if err != nil { - return - } - - if err = c.createChart(chartName, chartData, values); err != nil { - return + if err := c.createChart(chartName, chartData, values); err != nil { + return err } klog.V(8).Infof("namespace: %s, name: %s, chart values: %s", c.Namespace, c.ReleaseName, values) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - - cmd := exec.Cmd{ - Path: c.cmdPath, - Dir: c.Workspace(), - Stdout: stdout, - Stderr: stderr, + chartRequested, err := loader.Load(c.chartPath()) + if err != nil { + return err + } + valuePath := filepath.Join(c.Workspace(), "values.yaml") + helmValues, err := chartutil.ReadValuesFile(valuePath) + if err != nil { + return err } - cmd.Args = make([]string, 0, 10) - - // only for mock + var rel *helmrelease.Release if c.mock { - cmd.Path = os.Args[0] - cmd.Args = []string{os.Args[0], "-test.run=TestHelperProcess", "--"} - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - } - - cmd.Args = append(cmd.Args, c.cmdPath) - if upgrade { - cmd.Args = append(cmd.Args, "upgrade") + rel, err = c.mockRelease() } else { - cmd.Args = append(cmd.Args, "install") + if upgrade { + rel, err = c.helmUpgrade(chartRequested, helmValues.AsMap()) + } else { + rel, err = c.helmInstall(chartRequested, helmValues.AsMap()) + } } - cmd.Args = append(cmd.Args, c.ReleaseName, c.chartPath(), "--namespace", c.Namespace) - - if len(values) > 0 { - cmd.Args = append(cmd.Args, "--values", filepath.Join(c.Workspace(), "values.yaml")) - } - - if c.dryRun { - cmd.Args = append(cmd.Args, "--dry-run") - } - - if c.kubeConfigPath() != "" { - cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath()) - } - - // Post render, add annotations or labels to resources - if len(c.labels) > 0 || len(c.annotations) > 0 { - cmd.Args = append(cmd.Args, "--post-renderer", filepath.Join(c.Workspace(), postRenderExecFile)) - } - - if klog.V(8) { - // output debug info - cmd.Args = append(cmd.Args, "--debug") - } - - klog.V(4).Infof("run command: %s", cmd.String()) - err = cmd.Run() - if err != nil { - klog.Errorf("namespace: %s, name: %s, run command: %s failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, cmd.String(), stderr, err) - // return the error of helm install - return errors.New("%s", stderr.String()) - } else { - klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) - klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout) + klog.Errorf("namespace: %s, name: %s, error: %v", c.Namespace, c.ReleaseName, err) + return err } - return + klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) + klog.V(8).Infof("namespace: %s, name: %s, run command success, manifest: %s", c.Namespace, c.ReleaseName, rel.Manifest) + return nil } -func (c *helmWrapper) Manifest() (manifest string, err error) { - if err = c.ensureWorkspace(); err != nil { - return "", err - } - defer c.cleanup() +func (c *helmWrapper) Manifest() (string, error) { + get := action.NewGet(c.helmConf) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd := exec.Cmd{ - Path: c.cmdPath, - Dir: c.Workspace(), - Args: []string{ - c.cmdPath, - "get", - "manifest", - c.ReleaseName, - "--namespace", - c.Namespace, - }, - Stderr: stderr, - Stdout: stdout, - } - - if c.kubeConfigPath() != "" { - cmd.Args = append(cmd.Args, "--kubeconfig", c.kubeConfigPath()) - } - - if klog.V(8) { - // output debug info - cmd.Args = append(cmd.Args, "--debug") - } - - klog.V(4).Infof("run command: %s", cmd.String()) - err = cmd.Run() + rel, err := get.Run(c.ReleaseName) if err != nil { - klog.Errorf("namespace: %s, name: %s, run command failed, stderr: %s, error: %v", c.Namespace, c.ReleaseName, stderr, err) + klog.Errorf("namespace: %s, name: %s, run command failed, error: %v", c.Namespace, c.ReleaseName, err) return "", err - } else { - klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) - klog.V(8).Infof("namespace: %s, name: %s, run command success, stdout: %s", c.Namespace, c.ReleaseName, stdout) } - - return stdout.String(), nil + klog.V(2).Infof("namespace: %s, name: %s, run command success", c.Namespace, c.ReleaseName) + klog.V(8).Infof("namespace: %s, name: %s, run command success, manifest: %s", c.Namespace, c.ReleaseName, rel.Manifest) + return rel.Manifest, nil } diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_darwin.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_darwin.go deleted file mode 100644 index 0016f5290..000000000 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_darwin.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2020 The KubeSphere 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 helmwrapper - -const ( - helmPath = "/usr/local/bin/helm" -) diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_linux.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_linux.go deleted file mode 100644 index 0016f5290..000000000 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_linux.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2020 The KubeSphere 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 helmwrapper - -const ( - helmPath = "/usr/local/bin/helm" -) diff --git a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go index b5c539327..dee6ab6e4 100644 --- a/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go +++ b/pkg/simple/client/openpitrix/helmwrapper/helm_wrapper_test.go @@ -17,10 +17,13 @@ limitations under the License. package helmwrapper import ( - "fmt" + "io/ioutil" "os" "testing" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" + "kubesphere.io/kubesphere/pkg/constants" ) @@ -28,19 +31,44 @@ func TestHelmInstall(t *testing.T) { wr := NewHelmWrapper("", "dummy", "dummy", SetAnnotations(map[string]string{constants.CreatorAnnotationKey: "1234"}), SetMock(true)) + charData := GenerateChartData(t, "dummy-chart") + chartValues := `helm-wrapper: "test-val"` - err := wr.install("dummy-chart", "", "dummy-value", false) + err := wr.writeAction("dummy-chart", charData, chartValues, false) if err != nil { t.Fail() } } -func TestHelperProcess(t *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { - return +func TempDir(t *testing.T) string { + t.Helper() + d, err := ioutil.TempDir("", "kubesphere") + if err != nil { + t.Fatal(err) + } + return d +} + +func GenerateChartData(t *testing.T, name string) string { + tmpChart := TempDir(t) + defer os.RemoveAll(tmpChart) + + cfile := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV1, + Name: name, + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + }, } - // some code here to check arguments perhaps? - fmt.Fprintf(os.Stdout, "helm mock success") - os.Exit(0) + filename, err := chartutil.Save(cfile, tmpChart) + if err != nil { + t.Fatalf("Error creating chart for test: %v", err) + } + charData, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatalf("Error loading chart data %v", err) + } + return string(charData) } diff --git a/pkg/simple/client/openpitrix/helmwrapper/post_renderer_kustomize.go b/pkg/simple/client/openpitrix/helmwrapper/post_renderer_kustomize.go new file mode 100644 index 000000000..144e2aec5 --- /dev/null +++ b/pkg/simple/client/openpitrix/helmwrapper/post_renderer_kustomize.go @@ -0,0 +1,114 @@ +// Copyright 2022 The KubeSphere 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 helmwrapper + +import ( + "bytes" + "encoding/json" + "sync" + + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/api/krusty" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/types" + kustypes "sigs.k8s.io/kustomize/api/types" +) + +type postRendererKustomize struct { + labels map[string]string + annotations map[string]string +} + +func newPostRendererKustomize(labels, annotations map[string]string) *postRendererKustomize { + return &postRendererKustomize{ + labels, + annotations, + } +} + +func writeToFile(fs filesys.FileSystem, path string, content []byte) error { + helmOutput, err := fs.Create(path) + if err != nil { + return err + } + helmOutput.Write(content) + if err := helmOutput.Close(); err != nil { + return err + } + return nil +} + +func writeFile(fs filesys.FileSystem, path string, content *bytes.Buffer) error { + helmOutput, err := fs.Create(path) + if err != nil { + return err + } + content.WriteTo(helmOutput) + if err := helmOutput.Close(); err != nil { + return err + } + return nil +} + +func (k *postRendererKustomize) Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) { + fs := filesys.MakeFsInMemory() + input := "./.local-helm-output.yaml" + cfg := types.Kustomization{ + Resources: []string{input}, + CommonAnnotations: k.annotations, // add extra annotations to output + Labels: []types.Label{{Pairs: k.labels}}, // Labels to add to all objects but not selectors. + } + cfg.APIVersion = kustypes.KustomizationVersion + cfg.Kind = kustypes.KustomizationKind + if err := writeFile(fs, input, renderedManifests); err != nil { + return nil, err + } + + // Write kustomization config to file. + kustomization, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + if err := writeToFile(fs, "kustomization.yaml", kustomization); err != nil { + return nil, err + } + resMap, err := buildKustomization(fs, ".") + if err != nil { + return nil, err + } + yaml, err := resMap.AsYaml() + if err != nil { + return nil, err + } + return bytes.NewBuffer(yaml), nil +} + +var kustomizeRenderMutex sync.Mutex + +func buildKustomization(fs filesys.FileSystem, dirPath string) (resmap.ResMap, error) { + kustomizeRenderMutex.Lock() + defer kustomizeRenderMutex.Unlock() + + buildOptions := &krusty.Options{ + DoLegacyResourceSort: true, + LoadRestrictions: kustypes.LoadRestrictionsNone, + AddManagedbyLabel: false, + DoPrune: false, + PluginConfig: kustypes.DisabledPluginConfig(), + } + + k := krusty.MakeKustomizer(buildOptions) + return k.Run(fs, dirPath) +} diff --git a/pkg/simple/client/openpitrix/helmwrapper/rest_client_getter.go b/pkg/simple/client/openpitrix/helmwrapper/rest_client_getter.go new file mode 100644 index 000000000..f7a69c9d7 --- /dev/null +++ b/pkg/simple/client/openpitrix/helmwrapper/rest_client_getter.go @@ -0,0 +1,87 @@ +/* +Copyright 2020 The KubeSphere 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 helmwrapper + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/discovery" + memory "k8s.io/client-go/discovery/cached" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + "k8s.io/client-go/tools/clientcmd" +) + +func NewClusterRESTClientGetter(kubeconfig, namespace string) genericclioptions.RESTClientGetter { + if kubeconfig != "" { + return NewMemoryRESTClientGetter([]byte(kubeconfig), namespace) + } + flags := genericclioptions.NewConfigFlags(true) + flags.Namespace = &namespace + return flags +} + +// MemoryRESTClientGetter is an implementation of the genericclioptions.RESTClientGetter, +type MemoryRESTClientGetter struct { + kubeConfig []byte + namespace string +} + +func NewMemoryRESTClientGetter(kubeConfig []byte, namespace string) genericclioptions.RESTClientGetter { + return &MemoryRESTClientGetter{ + kubeConfig: kubeConfig, + namespace: namespace, + } +} + +func (c *MemoryRESTClientGetter) ToRESTConfig() (*rest.Config, error) { + cfg, err := clientcmd.RESTConfigFromKubeConfig(c.kubeConfig) + if err != nil { + return nil, err + } + return cfg, nil +} + +func (c *MemoryRESTClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + config, err := c.ToRESTConfig() + if err != nil { + return nil, err + } + + discoveryClient, _ := discovery.NewDiscoveryClientForConfig(config) + return memory.NewMemCacheClient(discoveryClient), nil +} + +func (c *MemoryRESTClientGetter) ToRESTMapper() (meta.RESTMapper, error) { + discoveryClient, err := c.ToDiscoveryClient() + if err != nil { + return nil, err + } + mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient) + expander := restmapper.NewShortcutExpander(mapper, discoveryClient) + return expander, nil +} + +func (c *MemoryRESTClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig + + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + overrides.Context.Namespace = c.namespace + + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) +}