From 240cda089a562a809f474ac3ddae7e071623cdd2 Mon Sep 17 00:00:00 2001 From: monopole Date: Fri, 2 Apr 2021 15:32:01 -0700 Subject: [PATCH] Add flag --enable-helm --- api/builtins/HelmChartInflationGenerator.go | 402 +++++++++-------- .../target/kusttarget_configplugin.go | 12 +- .../helmchartinflationgenerator_test.go | 82 ++-- api/testutils/kusttest/harnessenhanced.go | 6 + api/types/helmchartargs.go | 106 ++++- api/types/kustomization.go | 18 +- api/types/pluginconfig.go | 11 + examples/chart.md | 408 ++++++++++------- hack/testExamplesAgainstKustomize.sh | 8 +- kustomize/commands/build/build.go | 6 + kustomize/commands/build/flagenablehelm.go | 24 + .../HelmChartInflationGenerator.go | 409 ++++++++++-------- .../HelmChartInflationGenerator_test.go | 325 ++++++++------ .../helmchartinflationgenerator/go.sum | 4 + 14 files changed, 1081 insertions(+), 740 deletions(-) create mode 100644 kustomize/commands/build/flagenablehelm.go diff --git a/api/builtins/HelmChartInflationGenerator.go b/api/builtins/HelmChartInflationGenerator.go index 65d8d45f5..7037a01b5 100644 --- a/api/builtins/HelmChartInflationGenerator.go +++ b/api/builtins/HelmChartInflationGenerator.go @@ -6,7 +6,6 @@ package builtins import ( "bytes" "fmt" - "io" "io/ioutil" "os" "os/exec" @@ -16,7 +15,6 @@ import ( "github.com/imdario/mergo" "github.com/pkg/errors" - "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/yaml" @@ -25,201 +23,234 @@ import ( // HelmChartInflationGeneratorPlugin is a plugin to generate resources // from a remote or local helm chart. type HelmChartInflationGeneratorPlugin struct { - h *resmap.PluginHelpers - types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - runHelmCommand func([]string) ([]byte, error) - types.HelmChartArgs + h *resmap.PluginHelpers + types.HelmGlobals + types.HelmChart tmpDir string } var KustomizePlugin HelmChartInflationGeneratorPlugin +const ( + valuesMergeOptionMerge = "merge" + valuesMergeOptionOverride = "override" + valuesMergeOptionReplace = "replace" +) + +var legalMergeOptions = []string{ + valuesMergeOptionMerge, + valuesMergeOptionOverride, + valuesMergeOptionReplace, +} + // Config uses the input plugin configurations `config` to setup the generator // options -func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) error { +func (p *HelmChartInflationGeneratorPlugin) Config( + h *resmap.PluginHelpers, config []byte) (err error) { + if h.GeneralConfig() == nil { + return fmt.Errorf("unable to access general config") + } + if !h.GeneralConfig().HelmConfig.Enabled { + return fmt.Errorf("must specify --enable-helm") + } + if h.GeneralConfig().HelmConfig.Command == "" { + return fmt.Errorf("must specify --helm-command") + } p.h = h - err := yaml.Unmarshal(config, p) - if err != nil { - return err + if err = yaml.Unmarshal(config, p); err != nil { + return } - tmpDir, err := filesys.NewTmpConfirmedDir() - if err != nil { - return err - } - p.tmpDir = string(tmpDir) - if p.ChartName == "" { - return fmt.Errorf("chartName cannot be empty") - } - if p.ChartHome == "" { - p.ChartHome = filepath.Join(p.tmpDir, "chart") - } - if p.ChartRepoName == "" { - p.ChartRepoName = "stable" - } - if p.HelmBin == "" { - p.HelmBin = "helm" - } - if p.HelmHome == "" { - p.HelmHome = filepath.Join(p.tmpDir, ".helm") - } - if p.Values == "" { - p.Values = filepath.Join(p.ChartHome, p.ChartName, "values.yaml") - } - if p.ValuesMerge == "" { - p.ValuesMerge = "override" - } - // runHelmCommand will run `helm` command with args provided. Return stdout - // and error if there is any. - p.runHelmCommand = func(args []string) ([]byte, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.Command(p.HelmBin, args...) - cmd.Stdout = stdout - cmd.Stderr = stderr - cmd.Env = append(os.Environ(), - fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome), - fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome), - fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome), - ) - err := cmd.Run() - if err != nil { - return stdout.Bytes(), - errors.Wrap( - fmt.Errorf("failed to run command %s %s", p.HelmBin, strings.Join(args, " ")), - stderr.String(), - ) - } - return stdout.Bytes(), nil - } - return nil + return p.validateArgs() } -// EncodeValues for writing -func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error { - d, err := yaml.Marshal(p.ValuesLocal) - if err != nil { - return err - } - _, err = w.Write(d) - if err != nil { - return err - } - return nil -} - -// useValuesLocal process (merge) inflator config provided values with chart default values.yaml -func (p *HelmChartInflationGeneratorPlugin) useValuesLocal() error { - // not override, merge, none - if !(p.ValuesMerge == "none" || p.ValuesMerge == "no" || p.ValuesMerge == "false") { - var pValues []byte - var err error - - if filepath.IsAbs(p.Values) { - pValues, err = ioutil.ReadFile(p.Values) - } else { - pValues, err = p.h.Loader().Load(p.Values) - } - if err != nil { - return err - } - chValues := make(map[string]interface{}) - err = yaml.Unmarshal(pValues, &chValues) - if err != nil { - return err - } - if p.ValuesMerge == "override" { - err = mergo.Merge(&chValues, p.ValuesLocal, mergo.WithOverride) - if err != nil { - return err - } - } - if p.ValuesMerge == "merge" { - err = mergo.Merge(&chValues, p.ValuesLocal) - if err != nil { - return err - } - } - p.ValuesLocal = chValues - } - b, err := yaml.Marshal(p.ValuesLocal) - if err != nil { - return err - } - path, err := p.writeValuesBytes(b) - if err != nil { - return err - } - p.Values = path - return nil -} - -// copyValues will copy the relative values file into the temp directory -// to avoid messing up with CWD. -func (p *HelmChartInflationGeneratorPlugin) copyValues() error { - // only copy when the values path is not absolute - if filepath.IsAbs(p.Values) { +// This uses the real file system since tmpDir may be used +// by the helm subprocess. Cannot use a chroot jail or fake +// filesystem since we allow the user to use previously +// downloaded charts. This is safe since this plugin is +// owned by kustomize. +func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) { + if p.tmpDir != "" { + // already done. return nil } - // we must use use loader to read values file - b, err := p.h.Loader().Load(p.Values) - if err != nil { + p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-") + return err +} + +func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) { + if p.Name == "" { + return fmt.Errorf("chart name cannot be empty") + } + + // ChartHome might be consulted by the plugin (to read + // values files below it), so it must be located under + // the loader root (unless root restrictions are + // disabled, in which case this can be an absolute path). + if p.ChartHome == "" { + p.ChartHome = "charts" + } + + // The ValuesFile may be consulted by the plugin, so it must + // be under the loader root (unless root restrictions are + // disabled). + if p.ValuesFile == "" { + p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml") + } + + if err = p.errIfIllegalValuesMerge(); err != nil { return err } - path, err := p.writeValuesBytes(b) - if err != nil { - return err + + // ConfigHome is not loaded by the plugin, and can be located anywhere. + if p.ConfigHome == "" { + if err = p.establishTmpDir(); err != nil { + return errors.Wrap( + err, "unable to create tmp dir for HELM_CONFIG_HOME") + } + p.ConfigHome = filepath.Join(p.tmpDir, "helm") } - p.Values = path return nil } -func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(b []byte) (string, error) { - path := filepath.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml") - err := ioutil.WriteFile(path, b, 0644) +func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error { + if p.ValuesMerge == "" { + // Use the default. + p.ValuesMerge = valuesMergeOptionOverride + return nil + } + for _, opt := range legalMergeOptions { + if p.ValuesMerge == opt { + return nil + } + } + return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions) +} + +func (p *HelmChartInflationGeneratorPlugin) absChartHome() string { + if filepath.IsAbs(p.ChartHome) { + return p.ChartHome + } + return filepath.Join(p.h.Loader().Root(), p.ChartHome) +} + +func (p *HelmChartInflationGeneratorPlugin) runHelmCommand( + args []string) ([]byte, error) { + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + env := []string{ + fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome), + fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome), + fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)} + cmd.Env = append(os.Environ(), env...) + err := cmd.Run() + if err != nil { + helm := p.h.GeneralConfig().HelmConfig.Command + err = errors.Wrap( + fmt.Errorf( + "unable to run: '%s %s' with env=%s (is '%s' installed?)", + helm, strings.Join(args, " "), env, helm), + stderr.String(), + ) + } + return stdout.Bytes(), err +} + +// createNewMergedValuesFile replaces/merges original values file with ValuesInline. +func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() ( + path string, err error) { + if p.ValuesMerge == valuesMergeOptionMerge || + p.ValuesMerge == valuesMergeOptionOverride { + if err = p.replaceValuesInline(); err != nil { + return "", err + } + } + var b []byte + b, err = yaml.Marshal(p.ValuesInline) if err != nil { return "", err } - return path, nil + return p.writeValuesBytes(b) +} + +func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error { + pValues, err := p.h.Loader().Load(p.ValuesFile) + if err != nil { + return err + } + chValues := make(map[string]interface{}) + if err = yaml.Unmarshal(pValues, &chValues); err != nil { + return err + } + switch p.ValuesMerge { + case valuesMergeOptionOverride: + err = mergo.Merge( + &chValues, p.ValuesInline, mergo.WithOverride) + case valuesMergeOptionMerge: + err = mergo.Merge(&chValues, p.ValuesInline) + } + p.ValuesInline = chValues + return err +} + +// copyValuesFile to avoid branching. TODO: get rid of this. +func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) { + b, err := p.h.Loader().Load(p.ValuesFile) + if err != nil { + return "", err + } + return p.writeValuesBytes(b) +} + +// Write a absolute path file in the tmp file system. +func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes( + b []byte) (string, error) { + if err := p.establishTmpDir(); err != nil { + return "", fmt.Errorf("cannot create tmp dir to write helm values") + } + path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml") + return path, ioutil.WriteFile(path, b, 0644) +} + +func (p *HelmChartInflationGeneratorPlugin) cleanup() { + if p.tmpDir != "" { + os.RemoveAll(p.tmpDir) + } } // Generate implements generator -func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { - // cleanup - defer os.RemoveAll(p.tmpDir) - // check helm version. we only support V3 - err := p.checkHelmVersion() - if err != nil { +func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) { + defer p.cleanup() + if err = p.checkHelmVersion(); err != nil { return nil, err } - // pull the chart - if !p.checkLocalChart() { - _, err := p.runHelmCommand(p.getPullCommandArgs()) - if err != nil { + if path, exists := p.chartExistsLocally(); !exists { + if p.Repo == "" { + return nil, fmt.Errorf( + "no repo specified for pull, no chart found at '%s'", path) + } + if _, err := p.runHelmCommand(p.pullCommand()); err != nil { return nil, err } } - - // inflator config valuesLocal - if len(p.ValuesLocal) > 0 { - err := p.useValuesLocal() - if err != nil { - return nil, err - } + if len(p.ValuesInline) > 0 { + p.ValuesFile, err = p.createNewMergedValuesFile() } else { - err := p.copyValues() - if err != nil { - return nil, err - } + p.ValuesFile, err = p.copyValuesFile() } - - // render the charts - stdout, err := p.runHelmCommand(p.getTemplateCommandArgs()) + if err != nil { + return nil, err + } + var stdout []byte + stdout, err = p.runHelmCommand(p.templateCommand()) if err != nil { return nil, err } - rm, rmfErr := p.h.ResmapFactory().NewResMapFromBytes(stdout) - if rmfErr == nil { + rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout) + if err == nil { return rm, nil } // try to remove the contents before first "---" because @@ -228,50 +259,49 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { if idx := strings.Index(stdoutStr, "---"); idx != -1 { return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:])) } - return nil, rmfErr + return nil, err } -func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string { +func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string { args := []string{"template"} if p.ReleaseName != "" { args = append(args, p.ReleaseName) } - args = append(args, filepath.Join(p.ChartHome, p.ChartName)) - if p.ReleaseNamespace != "" { - args = append(args, "--namespace", p.ReleaseNamespace) + args = append(args, filepath.Join(p.absChartHome(), p.Name)) + if p.ValuesFile != "" { + args = append(args, "--values", p.ValuesFile) } - if p.Values != "" { - args = append(args, "--values", p.Values) + if p.ReleaseName == "" { + // AFAICT, this doesn't work as intended due to a bug in helm. + // See https://github.com/helm/helm/issues/6019 + // I've tried placing the flag before and after the name argument. + args = append(args, "--generate-name") } - args = append(args, p.ExtraArgs...) return args } -func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string { - args := []string{"pull", "--untar", "--untardir", p.ChartHome} - chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName) - if p.ChartVersion != "" { - args = append(args, "--version", p.ChartVersion) +func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string { + args := []string{ + "pull", + "--untar", + "--untardir", p.absChartHome(), + "--repo", p.Repo, + p.Name} + if p.Version != "" { + args = append(args, "--version", p.Version) } - if p.ChartRepoURL != "" { - args = append(args, "--repo", p.ChartRepoURL) - chartName = p.ChartName - } - - args = append(args, chartName) - return args } -// checkLocalChart will return true if the chart does exist in +// chartExistsLocally will return true if the chart does exist in // local chart home. -func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool { - path := filepath.Join(p.ChartHome, p.ChartName) +func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) { + path := filepath.Join(p.absChartHome(), p.Name) s, err := os.Stat(path) if err != nil { - return false + return "", false } - return s.IsDir() + return path, s.IsDir() } // checkHelmVersion will return an error if the helm version is not V3 diff --git a/api/internal/target/kusttarget_configplugin.go b/api/internal/target/kusttarget_configplugin.go index c7af3e7cb..2715af471 100644 --- a/api/internal/target/kusttarget_configplugin.go +++ b/api/internal/target/kusttarget_configplugin.go @@ -116,10 +116,16 @@ var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func( kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) ( result []resmap.Generator, err error) { var c struct { - types.HelmChartArgs + types.HelmGlobals + types.HelmChart } - for _, args := range kt.kustomization.HelmChartInflationGenerator { - c.HelmChartArgs = args + var globals types.HelmGlobals + if kt.kustomization.HelmGlobals != nil { + globals = *kt.kustomization.HelmGlobals + } + for _, chart := range kt.kustomization.HelmCharts { + c.HelmGlobals = globals + c.HelmChart = chart p := f() if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil { return nil, err diff --git a/api/krusty/helmchartinflationgenerator_test.go b/api/krusty/helmchartinflationgenerator_test.go index 7ed02f85d..b2a3539e5 100644 --- a/api/krusty/helmchartinflationgenerator_test.go +++ b/api/krusty/helmchartinflationgenerator_test.go @@ -3,14 +3,13 @@ package krusty_test -/* import ( "testing" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) -var expected string = ` +const expectedHelm = ` apiVersion: v1 data: rcon-password: Q0hBTkdFTUUh @@ -18,36 +17,19 @@ kind: Secret metadata: labels: app: test-minecraft - chart: minecraft-1.2.0 + chart: minecraft-3.1.3 heritage: Helm release: test name: test-minecraft type: Opaque --- apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - annotations: - volume.alpha.kubernetes.io/storage-class: default - labels: - app: test-minecraft - chart: minecraft-1.2.0 - heritage: Helm - release: test - name: test-minecraft-datadir -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: v1 kind: Service metadata: + annotations: {} labels: app: test-minecraft - chart: minecraft-1.2.0 + chart: minecraft-3.1.3 heritage: Helm release: test name: test-minecraft @@ -59,45 +41,43 @@ spec: targetPort: minecraft selector: app: test-minecraft - type: LoadBalancer + type: ClusterIP ` -func TestHelmChartInflationGenerator(t *testing.T) { - th := kusttest_test.MakeHarness(t) - th.WriteK("/app", ` +func TestHelmChartInflationGeneratorOld(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t) + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } + + th.WriteK(th.GetRoot(), ` helmChartInflationGenerator: - chartName: minecraft - chartRepoUrl: https://kubernetes-charts.storage.googleapis.com - chartVersion: v1.2.0 + chartRepoUrl: https://itzg.github.io/minecraft-server-charts + chartVersion: 3.1.3 releaseName: test - releaseNamespace: testNamespace `) - m := th.Run("/app", th.MakeDefaultOptions()) - th.AssertActualEqualsExpected(m, expected) + m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled()) + th.AssertActualEqualsExpected(m, expectedHelm) } +func TestHelmChartInflationGenerator(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t) + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } -func TestHelmChartInflationGeneratorAsPlugin(t *testing.T) { - th := kusttest_test.MakeHarness(t) - th.WriteK("/app", ` -generators: -- helm.yaml + th.WriteK(th.GetRoot(), ` +helmCharts: +- name: minecraft + repo: https://itzg.github.io/minecraft-server-charts + version: 3.1.3 + releaseName: test `) - th.WriteF("/app/helm.yaml", ` -apiVersion: builtin -kind: HelmChartInflationGenerator -metadata: - name: myMap -chartName: minecraft -chartRepoUrl: https://kubernetes-charts.storage.googleapis.com -chartVersion: v1.2.0 -releaseName: test -releaseNamespace: testNamespace -`) - - m := th.Run("/app", th.MakeDefaultOptions()) - th.AssertActualEqualsExpected(m, expected) + m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled()) + th.AssertActualEqualsExpected(m, expectedHelm) } -*/ diff --git a/api/testutils/kusttest/harnessenhanced.go b/api/testutils/kusttest/harnessenhanced.go index f7b9b56db..2a1e5fc30 100644 --- a/api/testutils/kusttest/harnessenhanced.go +++ b/api/testutils/kusttest/harnessenhanced.go @@ -6,6 +6,7 @@ package kusttest_test import ( "io/ioutil" "os" + "os/exec" "strings" "testing" @@ -83,6 +84,11 @@ func makeBaseEnhancedHarness(t *testing.T) *HarnessEnhanced { filesys.MakeFsOnDisk())} } +func (th *HarnessEnhanced) ErrIfNoHelm() error { + _, err := exec.LookPath(th.GetPluginConfig().HelmConfig.Command) + return err +} + func (th *HarnessEnhanced) GetRoot() string { return th.ldr.Root() } diff --git a/api/types/helmchartargs.go b/api/types/helmchartargs.go index 570372083..7bb22f2e4 100644 --- a/api/types/helmchartargs.go +++ b/api/types/helmchartargs.go @@ -3,14 +3,77 @@ package types -// HelmChartArgs contains the metadata of how to generate a secret. +type HelmGlobals struct { + // ChartHome is a file path, relative to the kustomization root, + // to a directory containing a subdirectory for each chart to be + // included in the kustomization. + // The default value of this field is "charts". + // So, for example, kustomize looks for the minecraft chart + // at {kustomizationRoot}/{ChartHome}/minecraft. + // If the chart is there at build time, kustomize will use it as found, + // and not check version numbers or dates. + // If the chart is not there, kustomize will attempt to pull it + // using the version number specified in the kustomization file, + // and put it there. To suppress the pull attempt, simply assure + // that the chart is already there. + ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"` + + // ConfigHome defines a value that kustomize should pass to helm via + // the HELM_CONFIG_HOME environment variable. kustomize doesn't attempt + // to read or write this directory. + // If omitted, {tmpDir}/helm is used, where {tmpDir} is some temporary + // directory created by kustomize for the benefit of helm. + // Likewise, kustomize sets + // HELM_CACHE_HOME={ConfigHome}/.cache + // HELM_DATA_HOME={ConfigHome}/.data + // for the helm subprocess. + ConfigHome string `json:"configHome,omitempty" yaml:"configHome,omitempty"` +} + +type HelmChart struct { + // Name is the name of the chart, e.g. 'minecraft'. + Name string `json:"name,omitempty" yaml:"name,omitempty"` + + // Version is the version of the chart, e.g. '3.1.3' + Version string `json:"version,omitempty" yaml:"version,omitempty"` + + // Repo is a URL locating the chart on the internet. + // This is the argument to helm's `--repo` flag, e.g. + // `https://itzg.github.io/minecraft-server-charts`. + Repo string `json:"repo,omitempty" yaml:"repo,omitempty"` + + // ReleaseName replaces RELEASE-NAME in chart template output, + // making a particular inflation of a chart unique with respect to + // other inflations of the same chart in a cluster. It's the first + // argument to the helm `install` and `template` commands, i.e. + // helm install {RELEASE-NAME} {chartName} + // helm template {RELEASE-NAME} {chartName} + // If omitted, the flag --generate-name is passed to 'helm template'. + ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"` + + // ValuesFile is local file path to a values file to use _instead of_ + // the default values that accompanied the chart. + // The default values are in '{ChartHome}/{Name}/values.yaml'. + ValuesFile string `json:"valuesFile,omitempty" yaml:"valuesFile,omitempty"` + + // ValuesInline holds value mappings specified directly, + // rather than in a separate file. + ValuesInline map[string]interface{} `json:"valuesInline,omitempty" yaml:"valuesInline,omitempty"` + + // ValuesMerge specifies how to treat ValuesInline with respect to Values. + // Legal values: 'merge', 'override', 'replace'. + // Defaults to 'override'. + ValuesMerge string `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"` +} + +// HelmChartArgs contains arguments to helm. +// Deprecated. Use HelmGlobals and HelmChart instead. type HelmChartArgs struct { - ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"` - ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"` - ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"` - ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"` - // Use chartRelease to keep compatible with old exec plugin - ChartRepoName string `json:"chartRelease,omitempty" yaml:"chartRelease,omitempty"` + ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"` + ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"` + ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"` + ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"` + ChartRepoName string `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"` HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"` HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"` Values string `json:"values,omitempty" yaml:"values,omitempty"` @@ -20,3 +83,32 @@ type HelmChartArgs struct { ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"` ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"` } + +// SplitHelmParameters splits helm parameters into +// per-chart params and global chart-independent parameters. +func SplitHelmParameters( + oldArgs []HelmChartArgs) (charts []HelmChart, globals HelmGlobals) { + for _, old := range oldArgs { + charts = append(charts, makeHelmChartFromHca(&old)) + if old.HelmHome != "" { + // last non-empty wins + globals.ConfigHome = old.HelmHome + } + if old.ChartHome != "" { + // last non-empty wins + globals.ChartHome = old.ChartHome + } + } + return charts, globals +} + +func makeHelmChartFromHca(old *HelmChartArgs) (c HelmChart) { + c.Name = old.ChartName + c.Version = old.ChartVersion + c.Repo = old.ChartRepoURL + c.ValuesFile = old.Values + c.ValuesInline = old.ValuesLocal + c.ValuesMerge = old.ValuesMerge + c.ReleaseName = old.ReleaseName + return +} diff --git a/api/types/kustomization.go b/api/types/kustomization.go index 95f47da9a..9c555a860 100644 --- a/api/types/kustomization.go +++ b/api/types/kustomization.go @@ -129,9 +129,14 @@ type Kustomization struct { // the map will have a suffix hash generated from its contents. SecretGenerator []SecretArgs `json:"secretGenerator,omitempty" yaml:"secretGenerator,omitempty"` + // HelmGlobals contains helm configuration that isn't chart specific. + HelmGlobals *HelmGlobals `json:"helmGlobals,omitempty" yaml:"helmGlobals,omitempty"` + + // HelmCharts is a list of helm chart configuration instances. + HelmCharts []HelmChart `json:"helmCharts,omitempty" yaml:"helmCharts,omitempty"` + // HelmChartInflationGenerator is a list of helm chart configurations. - // The resulting resource is a normal operand rendered from - // a remote chart by `helm template` + // Deprecated. Auto-converted to HelmGlobals and HelmCharts. HelmChartInflationGenerator []HelmChartArgs `json:"helmChartInflationGenerator,omitempty" yaml:"helmChartInflationGenerator,omitempty"` // GeneratorOptions modify behavior of all ConfigMap and Secret generators. @@ -185,6 +190,15 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() { k.SecretGenerator[i].EnvSource = "" } } + charts, globals := SplitHelmParameters(k.HelmChartInflationGenerator) + if k.HelmGlobals == nil { + if globals.ChartHome != "" || globals.ConfigHome != "" { + k.HelmGlobals = &globals + } + } + k.HelmCharts = append(k.HelmCharts, charts...) + // Wipe it for the fix command. + k.HelmChartInflationGenerator = nil } // FixKustomizationPreMarshalling fixes things diff --git a/api/types/pluginconfig.go b/api/types/pluginconfig.go index 360503ea3..741e5debc 100644 --- a/api/types/pluginconfig.go +++ b/api/types/pluginconfig.go @@ -3,6 +3,11 @@ package types +type HelmConfig struct { + Enabled bool + Command string +} + // PluginConfig holds plugin configuration. type PluginConfig struct { // PluginRestrictions distinguishes plugin restrictions. @@ -13,11 +18,17 @@ type PluginConfig struct { // FnpLoadingOptions sets the way function-based plugin behaviors. FnpLoadingOptions FnPluginLoadingOptions + + // HelmConfig contains metadata needed for allowing and running helm. + HelmConfig HelmConfig } func EnabledPluginConfig(b BuiltinPluginLoadingOptions) (pc *PluginConfig) { pc = MakePluginConfig(PluginRestrictionsNone, b) pc.FnpLoadingOptions.EnableStar = true + pc.HelmConfig.Enabled = true + // If this command is not on PATH, tests needing it should skip. + pc.HelmConfig.Command = "helmV3" return } diff --git a/examples/chart.md b/examples/chart.md index df473861d..3f63485a1 100644 --- a/examples/chart.md +++ b/examples/chart.md @@ -1,49 +1,63 @@ # kustomization of a helm chart +[`helm`]: https://helm.sh [last mile]: https://testingclouds.wordpress.com/2018/07/20/844/ -[stable chart]: https://github.com/helm/charts/tree/master/stable -[helm charts]: https://github.com/helm/charts -[_minecraft_]: https://github.com/helm/charts/tree/master/stable/minecraft +[artifact hub]: https://artifacthub.io +[_minecraft_]: https://artifacthub.io/packages/helm/minecraft-server-charts/minecraft [plugin]: ../docs/plugins +[built]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization -[Helm charts] aren't natively read by kustomize, but -kustomize has a builtin HelmChartInflationGenerator that allows one to -access helm charts. +Kustomize is [built] from _generators_ and +_transformers_; the former make kubernetes YAML, the +latter transform said YAML. -One pattern combining kustomize and helm is -the [last mile] modification, where -one uses an inflated chart as a base, then -modifies it on the way to the cluster using -kustomize. +Kustomize, via the `helmCharts` field, has the ability to +use the [`helm`] command line program in a subprocess to +inflate a helm chart, generating YAML as part of (or as the +entirety of) a kustomize base. -The example arbitrarily uses [_minecraft_] in [stable chart] repository, -but should work for any chart in any valid chart repository. +This YAML can then be modified either in the base directly +(transformers always run _after_ generators), or via +a kustomize overlay. + +Either approach can be viewed as [last mile] modification +of the chart output before applying it to a cluster. + +The example below arbitrarily uses the +[_minecraft_] chart pulled from the [artifact hub] +chart repository. + +## Preparation + +This example defines the `helm` command as + +``` +helmCommand=~/go/bin/helmV3 +``` + +This value is needed for testing this example in CI/CD. +A user doesn't need this if their binary is called +`helm` and is on their shell's `PATH`. -The following example assumes you have `helm` -on your `$PATH`. The plugin only supports helm V3 or later. Make a place to work: - - + ``` DEMO_HOME=$(mktemp -d) -mkdir -p $DEMO_HOME/base -mkdir -p $DEMO_HOME/dev -mkdir -p $DEMO_HOME/prod +mkdir -p $DEMO_HOME/base $DEMO_HOME/dev $DEMO_HOME/prod ``` -## Use a remote chart +## Define some variants Define a kustomization representing your _development_ -variant (aka environment). +variant. This could involve any number of kustomizations (see other examples), but in this case just add the name -prefix `dev-` to all resources: - - +prefix '`dev-`' to all resources: + ``` cat <<'EOF' >$DEMO_HOME/dev/kustomization.yaml namePrefix: dev- @@ -53,10 +67,9 @@ EOF ``` Likewise define a _production_ variant, with a name -prefix `prod-`: - - +prefix '`prod-`': + ``` cat <<'EOF' >$DEMO_HOME/prod/kustomization.yaml namePrefix: prod- @@ -67,45 +80,34 @@ EOF These two variants refer to a common base. -Define this base: - - +Define this base the usual way by creating a +`kustomization` file: + ``` cat <<'EOF' >$DEMO_HOME/base/kustomization.yaml -generators: -- chartInflator.yaml +helmCharts: +- name: minecraft + valuesInline: + minecraftServer: + eula: true + difficulty: hard + rcon: + enabled: true + releaseName: moria + version: 3.1.3 + repo: https://itzg.github.io/minecraft-server-charts EOF ``` -The base refers to a generator configuration file -called `chartInflator.yaml`. +The only thing in this particular file is a `helmCharts` +field, specifying a single chart. -This file lets one specify the name of a [stable chart], -and other things like a path to a values file, defaulting -to the `values.yaml` that comes with the chart. - -Create the config file `chartInflator.yaml`, specifying -the arbitrarily chosen chart name _minecraft_ and the repository -url that will be used to find the chart: - - - -``` -cat <<'EOF' >$DEMO_HOME/base/chartInflator.yaml -apiVersion: builtin -kind: HelmChartInflationGenerator -metadata: - name: notImportantHere -chartName: minecraft -chartRepoUrl: https://kubernetes-charts.storage.googleapis.com -EOF -``` +The `valuesInline` field overrides some native chart values. Check the directory layout: - ``` tree $DEMO_HOME ``` @@ -115,155 +117,249 @@ Expect something like: > ``` > /tmp/whatever > ├── base -> │   ├── chartInflator.yaml -> │   └── kustomization.yaml +> │ └── kustomization.yaml > ├── dev -> │   └── kustomization.yaml +> │ └── kustomization.yaml > └── prod > └── kustomization.yaml > ``` -Define a helper function to run kustomize with the -correct environment and flags for plugins: +### Helm related flags - +Attempt to build the `base`: + +``` +cmd="kustomize build --helm-command $helmCommand $DEMO_HOME/base" +if ($cmd); then + echo "Build should fail!" && false # Force test to fail. +else + echo "Build failed because no --enable-helm flag (desired outcome)." +fi +``` + +This `build` fails and complains about a missing +`--enable-helm` flag. + +The flag `--enable-helm` exists to have the user +acknowledge that kustomize is running an external program as +part of the `build` step. It's like the +`--enable-plugins` flag, but with a helm focus. + +The flag `--helm-command` has a default value (`helm` of +course) so it's not suitable as an enablement flag. A user +with `helm` on their `PATH` need not awkwardly specify +`'--helm-command helm'`. + +Given the above, define a helper function to run `kustomize` with the +flags required for `helm` use in this demo: + + ``` function kustomizeIt { - XDG_CONFIG_HOME=$DEMO_HOME \ - kustomize build --enable_alpha_plugins \ + kustomize build \ + --enable-helm \ + --helm-command $helmCommand \ $DEMO_HOME/$1 } ``` +### Build the base and the variants -Finally, build the `prod` variant. Notice that all -resource names now have the `prod-` prefix: - - - -``` -clear -kustomizeIt prod -``` - -Compare `dev` to `prod`: - - - -``` -diff <(kustomizeIt dev) <(kustomizeIt prod) | more -``` - -To see the unmodified but inflated chart, run kustomize -on the base. Every invocation here is re-downloading -and re-inflating the chart. - - +Now build the `base`: + ``` kustomizeIt base ``` -## Use a local chart +This works, and you see an inflated chart complete +with a `Secret`, `Service`, `Deployment`, etc. -The example above fetches a new copy of the chart -and render it with each kustomize -build, because a local chart home isn't specified -in the configuration. - -To suppress fetching, specify a _chart home_ -explicitly, and just make sure the chart is already -there. - -To demo this so that it won't interfere with your -existing helm environment, do this: - -**This demo uses Helm V3** - - - -``` -helmHome=$DEMO_HOME/dothelm -chartHome=$DEMO_HOME/base/charts -repoUrl=https://kubernetes-charts.storage.googleapis.com - -function doHelm { - helm --home $helmHome $@ -} -``` - -Now download a chart; again use _minecraft_ -(but you could use anything): - - - -``` -doHelm pull --untar \ - --untardir $chartHome \ - --repo $repoUrl \ - minecraft -``` - -The tree has more stuff now; helm config data -and a complete copy of the chart: +As a side effect of this build, kustomize pulled the chart +and placed it in the `charts` subdirectory of the base. +Take a look: - ``` tree $DEMO_HOME ``` -Add a `chartHome` field to the generator config file so -that it knows where to find the local chart: +If the chart had already been there, kustomize would +not have tried to pull it. - +To change the location of the charts, use this +in your kustomization file: +> ``` +> helmGlobals: +> chartHome: charts +> ``` + +Change `charts` as desired, but it's best to keep it +in (or below) the same directory as the `kustomization.yaml` file. +If it's outside the kustomization root, the `build` command will +fail unless given the flag `'--load-restrictor=none'` to +disable file loading restrictions. + +Now build the two variants `dev` and `prod` +and compare their differences: + + ``` -echo "chartHome: $chartHome" >>$DEMO_HOME/base/chartInflator.yaml +diff <(kustomizeIt dev) <(kustomizeIt prod) | more ``` -Change the values file, to show that the results -generated below are from the _locally_ stored chart: +This shows so-called _last mile hydration_ of two variants +made from a common base that happens to be generated from a +helm chart. - +## How does the pull work? +The command kustomize used to download the chart +is something like + +> ``` +> $helmCommand pull \ +> --untar \ +> --untardir $DEMO_HOME/base/charts \ +> --repo https://itzg.github.io/minecraft-server-charts \ +> --version 3.1.3 \ +> minecraft +> ``` + +The first use of kustomize above (when the `base` was +expanded) fetched the chart and placed it in the `charts` +directory next to the `kustomization.yaml` file. + +This chart was reused, _not_ re-fetched, with the variant +expansions `prod` and `dev`. + +If a chart exists, kustomize will not overwrite it (so to +suppress a pull, simply assure the chart is already in your +kustomization root). kustomize won't check dates or version +numbers or do anything that smells like cache management. + +> kustomize is a YAML manipulator. It's not a manager +> of a cache of things downloaded from the internet. + +## The pull happens once. + +To show that the locally stored chart is being re-used, modify +its _values_ file. + +First make note of the password encoded in the production +inflation: + + ``` -sed -i 's/CHANGEME!/SOMETHINGELSE/' $chartHome/minecraft/values.yaml -sed -i 's/LoadBalancer/NodePort/' $chartHome/minecraft/values.yaml +test 1 == $(kustomizeIt prod | grep -c "rcon-password: Q0hBTkdFTUUh") ``` -Finally, built it +The above command succeeds if the value of the generated +password is as shown (`Q0hBTkdFTUUh`). - +Now change the password in the local values file: + ``` -kustomizeIt prod +values=$DEMO_HOME/base/charts/minecraft/values.yaml + +grep CHANGEME $values +sed -i 's/CHANGEME/SOMETHING_ELSE/' $values +grep SOMETHING_ELSE $values ``` -and observe the change from `LoadBalancer` to `NodePort`, and -the change in the encoded password. +Run the build, and confirm that the same `rcon-password` +field in the output has a new value, confirming that the +chart used was a _local_ chart, not a chart freshly +downloaded from the internet: -Clean the directory. - + +``` +test 1 == $(kustomizeIt prod | grep -c "rcon-password: U09NRVRISU5HX0VMU0Uh") +``` +Finally, clean up: + + ``` rm -r $DEMO_HOME ``` -## How to migrate from old plugin to builtin plugin +## Performance -[bash-based helm chart inflator]: https://github.com/kubernetes-sigs/kustomize/tree/master/plugin/someteam.example.com/v1/chartinflator -[go-based builtin helm chart inflator]: https://github.com/kubernetes-sigs/kustomize/tree/master/plugin/builtin/helmchartinflationgenerator +To recap, the helm-related kustomization fields make +kustomize run -The [bash-based helm chart inflator] is intended as an example of using bash -to write a generator plugin. +> ``` +> helm pull ... +> helm template ... +> ``` -It proved to be popular for inflating helm charts, -so there's now a [go-based builtin helm chart inflator]. +_as a convenience for the user_ to generate YAML from a helm chart. -This newer generator is supported as part of the core code, so anyone using the -old bash-based example plugin would probably benefit from switching to the -newer built-in plugin. +Helm's `pull` command downloads the chart. Helm's `template` +command inflates the chart template, spitting the inflated +template to stdout (where kustomize captures it) rather than +immediately sending it to a cluster as `helm install` +would. -Be advised that at the time of writing, the built-in plugin only supports helm v3. +To improve performance, a user can retain the chart after +the first pull, and commit the chart to their configuration +repository (below the `kustomization.yaml` file that refers +to it). kustomize only tries to pull the chart if it's not +already there. + +To further improve performance, a user can inflate the +chart themselves at the command line, e.g. + +> ``` +> helm template {releaseName} \ +> --values {valuesFile} \ +> --version {version} \ +> --repo {repo} \ +> {chartName} > {chartName}.yaml +> ``` + +then commit the resulting `{chartName}.yaml` file to a git +repo as a configuration base, mentioning that file as a +`resource` in a `kustomization.yaml` file, e.g. + +> ``` +> resources: +> - minecraft_v3.1.3_Chart.yaml +> ``` + +The user should choose when or if to refresh their local +copy of the chart's inflation. kustomize would have no +awareness that the YAML was generated by helm, and kustomize +wouldn't run `helm` during the `build`. This is analogous +to `Go` module vendoring. + +### But it's not really about performance. + +Although the `helm` related fields discussed above are handy +for experimentation and development, it's best to avoid them +in production. + +The same argument applies to using _remote_ git URL's in +other kustomization fields. Handy for experimentation, +but ill-advised in production. + +It's irresponsible to depend on a remote configuration +that's _not under your control_. Annoying enablement flags +like `'--enable-helm'` are intended to _remind_ one of a +risk, but offer zero protection from risk. Further, they +are useless are reminders, since __annoying things are +immediately scripted away and forgotten__, as was done above +in the `kustomizeIt` shell function. + +## Best practice + +Don't use remote configuration that you don't control in +production. + +Maintain a _local, inflated fork_ of a remote configuration, +and have a human rebase / reinflate that fork from time to +time to capture upstream changes. diff --git a/hack/testExamplesAgainstKustomize.sh b/hack/testExamplesAgainstKustomize.sh index 5d1c73c9b..1e4789408 100755 --- a/hack/testExamplesAgainstKustomize.sh +++ b/hack/testExamplesAgainstKustomize.sh @@ -35,9 +35,11 @@ mdrip --mode test --blockTimeOut 15m \ # TODO: make work for non-linux if onLinuxAndNotOnRemoteCI; then echo "On linux, and not on remote CI. Running expensive tests." - # Requires helm. - make $MYGOBIN/helmV3 - mdrip --mode test --label helmtest examples/chart.md + # TODO: remove the HEAD check once --enable-helm flag released. + if [ "$version" == "HEAD" ]; then + make $MYGOBIN/helmV3 + mdrip --mode test --label testHelm examples/chart.md + fi fi # Force outside logic to rebuild kustomize rather than diff --git a/kustomize/commands/build/build.go b/kustomize/commands/build/build.go index 37c35e0b0..eea640155 100644 --- a/kustomize/commands/build/build.go +++ b/kustomize/commands/build/build.go @@ -23,7 +23,9 @@ var theFlags struct { enable struct { plugins bool managedByLabel bool + helm bool } + helmCommand string loadRestrictor string reorderOutput string fnOptions types.FnPluginLoadingOptions @@ -102,6 +104,7 @@ func NewCmdBuild( AddFlagEnablePlugins(cmd.Flags()) AddFlagReorderOutput(cmd.Flags()) AddFlagEnableManagedbyLabel(cmd.Flags()) + AddFlagEnableHelm(cmd.Flags()) return cmd } @@ -132,7 +135,10 @@ func HonorKustomizeFlags(kOpts *krusty.Options) *krusty.Options { c := types.EnabledPluginConfig(types.BploUseStaticallyLinked) c.FnpLoadingOptions = theFlags.fnOptions kOpts.PluginConfig = c + } else { + kOpts.PluginConfig.HelmConfig.Enabled = theFlags.enable.helm } + kOpts.PluginConfig.HelmConfig.Command = theFlags.helmCommand kOpts.AddManagedbyLabel = isManagedByLabelEnabled() return kOpts } diff --git a/kustomize/commands/build/flagenablehelm.go b/kustomize/commands/build/flagenablehelm.go new file mode 100644 index 000000000..1a328ce67 --- /dev/null +++ b/kustomize/commands/build/flagenablehelm.go @@ -0,0 +1,24 @@ +// Copyright 2021 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package build + +import ( + "github.com/spf13/pflag" +) + +// AddFlagEnableHelm adds the --enable-helm flag. +// The helm plugin is builtin, meaning it's +// enabled independently of --enable-alpha-plugins. +func AddFlagEnableHelm(set *pflag.FlagSet) { + set.BoolVar( + &theFlags.enable.helm, + "enable-helm", + false, + "Enable use of the Helm chart inflator generator.") + set.StringVar( + &theFlags.helmCommand, + "helm-command", + "helm", // default + "helm command (path to executable)") +} diff --git a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go index 14f457152..b7830d420 100644 --- a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go +++ b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go @@ -1,11 +1,8 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -// Helm chart generator -// -// Fetches the given chart from {ChartRepo}/{ChartName}, -// and inflates it to stdout, using the given values file. -// This generator expects helm V3 or later. +// Helm chart inflation generator. +// Uses helm V3 to generate k8s YAML from a helm chart. //go:generate pluginator package main @@ -13,7 +10,6 @@ package main import ( "bytes" "fmt" - "io" "io/ioutil" "os" "os/exec" @@ -23,7 +19,6 @@ import ( "github.com/imdario/mergo" "github.com/pkg/errors" - "sigs.k8s.io/kustomize/api/filesys" "sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/yaml" @@ -32,202 +27,235 @@ import ( // HelmChartInflationGeneratorPlugin is a plugin to generate resources // from a remote or local helm chart. type HelmChartInflationGeneratorPlugin struct { - h *resmap.PluginHelpers - types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - runHelmCommand func([]string) ([]byte, error) - types.HelmChartArgs + h *resmap.PluginHelpers + types.HelmGlobals + types.HelmChart tmpDir string } //noinspection GoUnusedGlobalVariable var KustomizePlugin HelmChartInflationGeneratorPlugin +const ( + valuesMergeOptionMerge = "merge" + valuesMergeOptionOverride = "override" + valuesMergeOptionReplace = "replace" +) + +var legalMergeOptions = []string{ + valuesMergeOptionMerge, + valuesMergeOptionOverride, + valuesMergeOptionReplace, +} + // Config uses the input plugin configurations `config` to setup the generator // options -func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) error { +func (p *HelmChartInflationGeneratorPlugin) Config( + h *resmap.PluginHelpers, config []byte) (err error) { + if h.GeneralConfig() == nil { + return fmt.Errorf("unable to access general config") + } + if !h.GeneralConfig().HelmConfig.Enabled { + return fmt.Errorf("must specify --enable-helm") + } + if h.GeneralConfig().HelmConfig.Command == "" { + return fmt.Errorf("must specify --helm-command") + } p.h = h - err := yaml.Unmarshal(config, p) - if err != nil { - return err + if err = yaml.Unmarshal(config, p); err != nil { + return } - tmpDir, err := filesys.NewTmpConfirmedDir() - if err != nil { - return err - } - p.tmpDir = string(tmpDir) - if p.ChartName == "" { - return fmt.Errorf("chartName cannot be empty") - } - if p.ChartHome == "" { - p.ChartHome = filepath.Join(p.tmpDir, "chart") - } - if p.ChartRepoName == "" { - p.ChartRepoName = "stable" - } - if p.HelmBin == "" { - p.HelmBin = "helm" - } - if p.HelmHome == "" { - p.HelmHome = filepath.Join(p.tmpDir, ".helm") - } - if p.Values == "" { - p.Values = filepath.Join(p.ChartHome, p.ChartName, "values.yaml") - } - if p.ValuesMerge == "" { - p.ValuesMerge = "override" - } - // runHelmCommand will run `helm` command with args provided. Return stdout - // and error if there is any. - p.runHelmCommand = func(args []string) ([]byte, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.Command(p.HelmBin, args...) - cmd.Stdout = stdout - cmd.Stderr = stderr - cmd.Env = append(os.Environ(), - fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome), - fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome), - fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome), - ) - err := cmd.Run() - if err != nil { - return stdout.Bytes(), - errors.Wrap( - fmt.Errorf("failed to run command %s %s", p.HelmBin, strings.Join(args, " ")), - stderr.String(), - ) - } - return stdout.Bytes(), nil - } - return nil + return p.validateArgs() } -// EncodeValues for writing -func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error { - d, err := yaml.Marshal(p.ValuesLocal) - if err != nil { - return err - } - _, err = w.Write(d) - if err != nil { - return err - } - return nil -} - -// useValuesLocal process (merge) inflator config provided values with chart default values.yaml -func (p *HelmChartInflationGeneratorPlugin) useValuesLocal() error { - // not override, merge, none - if !(p.ValuesMerge == "none" || p.ValuesMerge == "no" || p.ValuesMerge == "false") { - var pValues []byte - var err error - - if filepath.IsAbs(p.Values) { - pValues, err = ioutil.ReadFile(p.Values) - } else { - pValues, err = p.h.Loader().Load(p.Values) - } - if err != nil { - return err - } - chValues := make(map[string]interface{}) - err = yaml.Unmarshal(pValues, &chValues) - if err != nil { - return err - } - if p.ValuesMerge == "override" { - err = mergo.Merge(&chValues, p.ValuesLocal, mergo.WithOverride) - if err != nil { - return err - } - } - if p.ValuesMerge == "merge" { - err = mergo.Merge(&chValues, p.ValuesLocal) - if err != nil { - return err - } - } - p.ValuesLocal = chValues - } - b, err := yaml.Marshal(p.ValuesLocal) - if err != nil { - return err - } - path, err := p.writeValuesBytes(b) - if err != nil { - return err - } - p.Values = path - return nil -} - -// copyValues will copy the relative values file into the temp directory -// to avoid messing up with CWD. -func (p *HelmChartInflationGeneratorPlugin) copyValues() error { - // only copy when the values path is not absolute - if filepath.IsAbs(p.Values) { +// This uses the real file system since tmpDir may be used +// by the helm subprocess. Cannot use a chroot jail or fake +// filesystem since we allow the user to use previously +// downloaded charts. This is safe since this plugin is +// owned by kustomize. +func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) { + if p.tmpDir != "" { + // already done. return nil } - // we must use use loader to read values file - b, err := p.h.Loader().Load(p.Values) - if err != nil { + p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-") + return err +} + +func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) { + if p.Name == "" { + return fmt.Errorf("chart name cannot be empty") + } + + // ChartHome might be consulted by the plugin (to read + // values files below it), so it must be located under + // the loader root (unless root restrictions are + // disabled, in which case this can be an absolute path). + if p.ChartHome == "" { + p.ChartHome = "charts" + } + + // The ValuesFile may be consulted by the plugin, so it must + // be under the loader root (unless root restrictions are + // disabled). + if p.ValuesFile == "" { + p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml") + } + + if err = p.errIfIllegalValuesMerge(); err != nil { return err } - path, err := p.writeValuesBytes(b) - if err != nil { - return err + + // ConfigHome is not loaded by the plugin, and can be located anywhere. + if p.ConfigHome == "" { + if err = p.establishTmpDir(); err != nil { + return errors.Wrap( + err, "unable to create tmp dir for HELM_CONFIG_HOME") + } + p.ConfigHome = filepath.Join(p.tmpDir, "helm") } - p.Values = path return nil } -func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(b []byte) (string, error) { - path := filepath.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml") - err := ioutil.WriteFile(path, b, 0644) +func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error { + if p.ValuesMerge == "" { + // Use the default. + p.ValuesMerge = valuesMergeOptionOverride + return nil + } + for _, opt := range legalMergeOptions { + if p.ValuesMerge == opt { + return nil + } + } + return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions) +} + +func (p *HelmChartInflationGeneratorPlugin) absChartHome() string { + if filepath.IsAbs(p.ChartHome) { + return p.ChartHome + } + return filepath.Join(p.h.Loader().Root(), p.ChartHome) +} + +func (p *HelmChartInflationGeneratorPlugin) runHelmCommand( + args []string) ([]byte, error) { + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + env := []string{ + fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome), + fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome), + fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)} + cmd.Env = append(os.Environ(), env...) + err := cmd.Run() + if err != nil { + helm := p.h.GeneralConfig().HelmConfig.Command + err = errors.Wrap( + fmt.Errorf( + "unable to run: '%s %s' with env=%s (is '%s' installed?)", + helm, strings.Join(args, " "), env, helm), + stderr.String(), + ) + } + return stdout.Bytes(), err +} + +// createNewMergedValuesFile replaces/merges original values file with ValuesInline. +func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() ( + path string, err error) { + if p.ValuesMerge == valuesMergeOptionMerge || + p.ValuesMerge == valuesMergeOptionOverride { + if err = p.replaceValuesInline(); err != nil { + return "", err + } + } + var b []byte + b, err = yaml.Marshal(p.ValuesInline) if err != nil { return "", err } - return path, nil + return p.writeValuesBytes(b) +} + +func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error { + pValues, err := p.h.Loader().Load(p.ValuesFile) + if err != nil { + return err + } + chValues := make(map[string]interface{}) + if err = yaml.Unmarshal(pValues, &chValues); err != nil { + return err + } + switch p.ValuesMerge { + case valuesMergeOptionOverride: + err = mergo.Merge( + &chValues, p.ValuesInline, mergo.WithOverride) + case valuesMergeOptionMerge: + err = mergo.Merge(&chValues, p.ValuesInline) + } + p.ValuesInline = chValues + return err +} + +// copyValuesFile to avoid branching. TODO: get rid of this. +func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) { + b, err := p.h.Loader().Load(p.ValuesFile) + if err != nil { + return "", err + } + return p.writeValuesBytes(b) +} + +// Write a absolute path file in the tmp file system. +func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes( + b []byte) (string, error) { + if err := p.establishTmpDir(); err != nil { + return "", fmt.Errorf("cannot create tmp dir to write helm values") + } + path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml") + return path, ioutil.WriteFile(path, b, 0644) +} + +func (p *HelmChartInflationGeneratorPlugin) cleanup() { + if p.tmpDir != "" { + os.RemoveAll(p.tmpDir) + } } // Generate implements generator -func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { - // cleanup - defer os.RemoveAll(p.tmpDir) - // check helm version. we only support V3 - err := p.checkHelmVersion() - if err != nil { +func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) { + defer p.cleanup() + if err = p.checkHelmVersion(); err != nil { return nil, err } - // pull the chart - if !p.checkLocalChart() { - _, err := p.runHelmCommand(p.getPullCommandArgs()) - if err != nil { + if path, exists := p.chartExistsLocally(); !exists { + if p.Repo == "" { + return nil, fmt.Errorf( + "no repo specified for pull, no chart found at '%s'", path) + } + if _, err := p.runHelmCommand(p.pullCommand()); err != nil { return nil, err } } - - // inflator config valuesLocal - if len(p.ValuesLocal) > 0 { - err := p.useValuesLocal() - if err != nil { - return nil, err - } + if len(p.ValuesInline) > 0 { + p.ValuesFile, err = p.createNewMergedValuesFile() } else { - err := p.copyValues() - if err != nil { - return nil, err - } + p.ValuesFile, err = p.copyValuesFile() } - - // render the charts - stdout, err := p.runHelmCommand(p.getTemplateCommandArgs()) + if err != nil { + return nil, err + } + var stdout []byte + stdout, err = p.runHelmCommand(p.templateCommand()) if err != nil { return nil, err } - rm, rmfErr := p.h.ResmapFactory().NewResMapFromBytes(stdout) - if rmfErr == nil { + rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout) + if err == nil { return rm, nil } // try to remove the contents before first "---" because @@ -236,50 +264,49 @@ func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { if idx := strings.Index(stdoutStr, "---"); idx != -1 { return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:])) } - return nil, rmfErr + return nil, err } -func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string { +func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string { args := []string{"template"} if p.ReleaseName != "" { args = append(args, p.ReleaseName) } - args = append(args, filepath.Join(p.ChartHome, p.ChartName)) - if p.ReleaseNamespace != "" { - args = append(args, "--namespace", p.ReleaseNamespace) + args = append(args, filepath.Join(p.absChartHome(), p.Name)) + if p.ValuesFile != "" { + args = append(args, "--values", p.ValuesFile) } - if p.Values != "" { - args = append(args, "--values", p.Values) + if p.ReleaseName == "" { + // AFAICT, this doesn't work as intended due to a bug in helm. + // See https://github.com/helm/helm/issues/6019 + // I've tried placing the flag before and after the name argument. + args = append(args, "--generate-name") } - args = append(args, p.ExtraArgs...) return args } -func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string { - args := []string{"pull", "--untar", "--untardir", p.ChartHome} - chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName) - if p.ChartVersion != "" { - args = append(args, "--version", p.ChartVersion) +func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string { + args := []string{ + "pull", + "--untar", + "--untardir", p.absChartHome(), + "--repo", p.Repo, + p.Name} + if p.Version != "" { + args = append(args, "--version", p.Version) } - if p.ChartRepoURL != "" { - args = append(args, "--repo", p.ChartRepoURL) - chartName = p.ChartName - } - - args = append(args, chartName) - return args } -// checkLocalChart will return true if the chart does exist in +// chartExistsLocally will return true if the chart does exist in // local chart home. -func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool { - path := filepath.Join(p.ChartHome, p.ChartName) +func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) { + path := filepath.Join(p.absChartHome(), p.Name) s, err := os.Stat(path) if err != nil { - return false + return "", false } - return s.IsDir() + return path, s.IsDir() } // checkHelmVersion will return an error if the helm version is not V3 diff --git a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator_test.go b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator_test.go index 1d50acf36..443d068f3 100644 --- a/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator_test.go +++ b/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator_test.go @@ -1,34 +1,30 @@ package main_test -/* import ( "fmt" - "io/ioutil" - "os" - "path" + "path/filepath" "testing" - "sigs.k8s.io/kustomize/api/filesys" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" ) -//add tests for https://github.com/kubernetes-sigs/kustomize/pull/3713 if you plan to re-enable tests - func TestHelmChartInflationGenerator(t *testing.T) { - th := kusttest_test.MakeEnhancedHarness(t). + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t). PrepBuiltin("HelmChartInflationGenerator") defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } rm := th.LoadAndRunGenerator(` apiVersion: builtin kind: HelmChartInflationGenerator metadata: - name: myMap -chartName: minecraft -chartRepoUrl: https://charts.helm.sh/stable -chartVersion: v1.2.0 -releaseName: test -releaseNamespace: testNamespace + name: myMc +name: minecraft +version: 3.1.3 +repo: https://itzg.github.io/minecraft-server-charts +releaseName: moria `) th.AssertActualEqualsExpected(rm, ` @@ -38,40 +34,23 @@ data: kind: Secret metadata: labels: - app: test-minecraft - chart: minecraft-1.2.0 + app: moria-minecraft + chart: minecraft-3.1.3 heritage: Helm - release: test - name: test-minecraft + release: moria + name: moria-minecraft type: Opaque --- apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - annotations: - volume.alpha.kubernetes.io/storage-class: default - labels: - app: test-minecraft - chart: minecraft-1.2.0 - heritage: Helm - release: test - name: test-minecraft-datadir -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: v1 kind: Service metadata: + annotations: {} labels: - app: test-minecraft - chart: minecraft-1.2.0 + app: moria-minecraft + chart: minecraft-3.1.3 heritage: Helm - release: test - name: test-minecraft + release: moria + name: moria-minecraft spec: ports: - name: minecraft @@ -79,93 +58,35 @@ spec: protocol: TCP targetPort: minecraft selector: - app: test-minecraft - type: LoadBalancer + app: moria-minecraft + type: ClusterIP `) } -func TestHelmChartInflationGeneratorWithValues(t *testing.T) { - th := kusttest_test.MakeEnhancedHarness(t). - PrepBuiltin("HelmChartInflationGenerator") - defer th.Reset() - - tempDirConfirmed, err := filesys.NewTmpConfirmedDir() - if err != nil { - t.Fatal(err) - } - tempDir := string(tempDirConfirmed) - defer os.RemoveAll(tempDir) - valuesPath := path.Join(tempDir, "values.yaml") - ioutil.WriteFile(valuesPath, []byte(` -minecraftServer: - eula: TRUE -`), 0644) - - rm := th.LoadAndRunGenerator(fmt.Sprintf(` -apiVersion: builtin -kind: HelmChartInflationGenerator -metadata: - name: myMap -chartName: minecraft -chartRepoUrl: https://charts.helm.sh/stable -chartVersion: v1.2.0 -helmBin: helm -helmHome: %s -chartHome: %s -releaseName: test -releaseNamespace: testNamespace -values: %s -valuesLocal: - resources: - limits: - memory: 512Mi - cpu: 1000m - requests: - memory: 512Mi - cpu: 200m -`, tempDir, tempDir, valuesPath)) - - th.AssertActualEqualsExpected(rm, ` +const expectedInflationFmt = ` apiVersion: v1 data: rcon-password: Q0hBTkdFTUUh kind: Secret metadata: labels: - app: test-minecraft - chart: minecraft-1.2.0 + app: moria-minecraft + chart: minecraft-3.1.3 heritage: Helm - release: test - name: test-minecraft + release: moria + name: moria-minecraft type: Opaque --- apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - annotations: - volume.alpha.kubernetes.io/storage-class: default - labels: - app: test-minecraft - chart: minecraft-1.2.0 - heritage: Helm - release: test - name: test-minecraft-datadir -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 1Gi ---- -apiVersion: v1 kind: Service metadata: + annotations: {} labels: - app: test-minecraft - chart: minecraft-1.2.0 + app: moria-minecraft + chart: minecraft-3.1.3 heritage: Helm - release: test - name: test-minecraft + release: moria + name: moria-minecraft spec: ports: - name: minecraft @@ -173,28 +94,48 @@ spec: protocol: TCP targetPort: minecraft selector: - app: test-minecraft + app: moria-minecraft + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + annotations: {} + labels: + app: moria-minecraft + chart: minecraft-3.1.3 + heritage: Helm + release: moria + name: moria-minecraft-rcon +spec: + ports: + - name: rcon + port: 25575 + protocol: TCP + targetPort: rcon + selector: + app: moria-minecraft type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment metadata: labels: - app: test-minecraft - chart: minecraft-1.2.0 + app: moria-minecraft + chart: minecraft-3.1.3 heritage: Helm - release: test - name: test-minecraft + release: moria + name: moria-minecraft spec: selector: matchLabels: - app: test-minecraft + app: moria-minecraft strategy: type: Recreate template: metadata: labels: - app: test-minecraft + app: moria-minecraft spec: containers: - env: @@ -203,9 +144,9 @@ spec: - name: TYPE value: VANILLA - name: VERSION - value: 1.14.4 + value: LATEST - name: DIFFICULTY - value: easy + value: %s - name: WHITELIST value: "" - name: OPS @@ -222,7 +163,7 @@ spec: value: "true" - name: ENABLE_COMMAND_BLOCK value: "true" - - name: FORCE_gameMode + - name: FORCE_GAMEMODE value: "false" - name: GENERATE_STRUCTURES value: "true" @@ -257,47 +198,48 @@ spec: - name: ONLINE_MODE value: "true" - name: MEMORY - value: 512M + value: 1024M - name: JVM_OPTS value: "" - name: JVM_XX_OPTS value: "" + - name: ENABLE_RCON + value: "true" + - name: RCON_PASSWORD + valueFrom: + secretKeyRef: + key: rcon-password + name: moria-minecraft image: itzg/minecraft-server:latest imagePullPolicy: Always livenessProbe: - exec: - command: - - mcstatus - - localhost:25565 - - status failureThreshold: 10 initialDelaySeconds: 30 periodSeconds: 5 successThreshold: 1 + tcpSocket: + port: 25565 timeoutSeconds: 1 - name: test-minecraft + name: moria-minecraft ports: - containerPort: 25565 name: minecraft protocol: TCP + - containerPort: 25575 + name: rcon + protocol: TCP readinessProbe: - exec: - command: - - mcstatus - - localhost:25565 - - status failureThreshold: 10 initialDelaySeconds: 30 periodSeconds: 5 successThreshold: 1 + tcpSocket: + port: 25565 timeoutSeconds: 1 resources: - limits: - cpu: 1000m - memory: 512Mi requests: - cpu: 200m - memory: 512Mi + cpu: %dm + memory: %dMi volumeMounts: - mountPath: /data name: datadir @@ -305,9 +247,110 @@ spec: fsGroup: 2000 runAsUser: 1000 volumes: - - name: datadir - persistentVolumeClaim: - claimName: test-minecraft-datadir + - emptyDir: {} + name: datadir +` + +func TestHelmChartInflationGeneratorWithValuesInlineOverride(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t). + PrepBuiltin("HelmChartInflationGenerator") + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } + rm := th.LoadAndRunGenerator(` +apiVersion: builtin +kind: HelmChartInflationGenerator +metadata: + name: myMc +name: minecraft +version: 3.1.3 +repo: https://itzg.github.io/minecraft-server-charts +releaseName: moria +valuesInline: + minecraftServer: + eula: true + difficulty: hard + rcon: + enabled: true `) + th.AssertActualEqualsExpected( + rm, fmt.Sprintf(expectedInflationFmt, + "hard", // difficulty + 500, // cpu + 512, // memory + )) +} + +func TestHelmChartInflationGeneratorWithLocalValuesFile(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t). + PrepBuiltin("HelmChartInflationGenerator") + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } + th.WriteF(filepath.Join(th.GetRoot(), "myValues.yaml"), ` +minecraftServer: + eula: true + difficulty: peaceful + rcon: + enabled: true +resources: + requests: + cpu: 888m + memory: 666Mi +`) + rm := th.LoadAndRunGenerator(` +apiVersion: builtin +kind: HelmChartInflationGenerator +metadata: + name: myMc +name: minecraft +version: 3.1.3 +repo: https://itzg.github.io/minecraft-server-charts +releaseName: moria +valuesFile: myValues.yaml +`) + th.AssertActualEqualsExpected( + rm, fmt.Sprintf(expectedInflationFmt, + "peaceful", // difficulty + 888, // cpu + 666, // memory + )) +} + +func TestHelmChartInflationGeneratorWithInLineReplace(t *testing.T) { + th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t). + PrepBuiltin("HelmChartInflationGenerator") + defer th.Reset() + if err := th.ErrIfNoHelm(); err != nil { + t.Skip("skipping: " + err.Error()) + } + rm := th.LoadAndRunGenerator(` +apiVersion: builtin +kind: HelmChartInflationGenerator +metadata: + name: myMc +name: minecraft +version: 3.1.3 +repo: https://itzg.github.io/minecraft-server-charts +releaseName: moria +valuesInline: + minecraftServer: + eula: true + difficulty: OMG_PLEASE_NO + rcon: + enabled: true + resources: + requests: + cpu: 555m + memory: 111Mi +valuesMerge: replace +`) + th.AssertActualEqualsExpected( + rm, fmt.Sprintf(expectedInflationFmt, + "OMG_PLEASE_NO", // difficulty + 555, // cpu + 111, // memory + )) } -*/ diff --git a/plugin/builtin/helmchartinflationgenerator/go.sum b/plugin/builtin/helmchartinflationgenerator/go.sum index 3519e291a..64fbedcc1 100644 --- a/plugin/builtin/helmchartinflationgenerator/go.sum +++ b/plugin/builtin/helmchartinflationgenerator/go.sum @@ -33,6 +33,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -102,6 +103,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -159,6 +161,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -191,6 +194,7 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=