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
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user