Merge pull request #3784 from monopole/flagEnableHelm

Add flag --enable-helm
This commit is contained in:
Kubernetes Prow Robot
2021-04-12 13:52:15 -07:00
committed by GitHub
14 changed files with 1081 additions and 740 deletions

View File

@@ -6,7 +6,6 @@ package builtins
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@@ -16,7 +15,6 @@ import (
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@@ -25,201 +23,234 @@ import (
// HelmChartInflationGeneratorPlugin is a plugin to generate resources // HelmChartInflationGeneratorPlugin is a plugin to generate resources
// from a remote or local helm chart. // from a remote or local helm chart.
type HelmChartInflationGeneratorPlugin struct { type HelmChartInflationGeneratorPlugin struct {
h *resmap.PluginHelpers h *resmap.PluginHelpers
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` types.HelmGlobals
runHelmCommand func([]string) ([]byte, error) types.HelmChart
types.HelmChartArgs
tmpDir string tmpDir string
} }
var KustomizePlugin HelmChartInflationGeneratorPlugin 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 // Config uses the input plugin configurations `config` to setup the generator
// options // 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 p.h = h
err := yaml.Unmarshal(config, p) if err = yaml.Unmarshal(config, p); err != nil {
if err != nil { return
return err
} }
tmpDir, err := filesys.NewTmpConfirmedDir() return p.validateArgs()
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
} }
// EncodeValues for writing // This uses the real file system since tmpDir may be used
func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error { // by the helm subprocess. Cannot use a chroot jail or fake
d, err := yaml.Marshal(p.ValuesLocal) // filesystem since we allow the user to use previously
if err != nil { // downloaded charts. This is safe since this plugin is
return err // owned by kustomize.
} func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
_, err = w.Write(d) if p.tmpDir != "" {
if err != nil { // already done.
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) {
return nil return nil
} }
// we must use use loader to read values file p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
b, err := p.h.Loader().Load(p.Values) return err
if err != nil { }
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 return err
} }
path, err := p.writeValuesBytes(b)
if err != nil { // ConfigHome is not loaded by the plugin, and can be located anywhere.
return err 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 return nil
} }
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(b []byte) (string, error) { func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
path := filepath.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml") if p.ValuesMerge == "" {
err := ioutil.WriteFile(path, b, 0644) // 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 { if err != nil {
return "", err 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 // Generate implements generator
func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
// cleanup defer p.cleanup()
defer os.RemoveAll(p.tmpDir) if err = p.checkHelmVersion(); err != nil {
// check helm version. we only support V3
err := p.checkHelmVersion()
if err != nil {
return nil, err return nil, err
} }
// pull the chart if path, exists := p.chartExistsLocally(); !exists {
if !p.checkLocalChart() { if p.Repo == "" {
_, err := p.runHelmCommand(p.getPullCommandArgs()) return nil, fmt.Errorf(
if err != nil { "no repo specified for pull, no chart found at '%s'", path)
}
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
return nil, err return nil, err
} }
} }
if len(p.ValuesInline) > 0 {
// inflator config valuesLocal p.ValuesFile, err = p.createNewMergedValuesFile()
if len(p.ValuesLocal) > 0 {
err := p.useValuesLocal()
if err != nil {
return nil, err
}
} else { } else {
err := p.copyValues() p.ValuesFile, err = p.copyValuesFile()
if err != nil {
return nil, err
}
} }
if err != nil {
// render the charts return nil, err
stdout, err := p.runHelmCommand(p.getTemplateCommandArgs()) }
var stdout []byte
stdout, err = p.runHelmCommand(p.templateCommand())
if err != nil { if err != nil {
return nil, err return nil, err
} }
rm, rmfErr := p.h.ResmapFactory().NewResMapFromBytes(stdout) rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
if rmfErr == nil { if err == nil {
return rm, nil return rm, nil
} }
// try to remove the contents before first "---" because // 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 { if idx := strings.Index(stdoutStr, "---"); idx != -1 {
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:])) 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"} args := []string{"template"}
if p.ReleaseName != "" { if p.ReleaseName != "" {
args = append(args, p.ReleaseName) args = append(args, p.ReleaseName)
} }
args = append(args, filepath.Join(p.ChartHome, p.ChartName)) args = append(args, filepath.Join(p.absChartHome(), p.Name))
if p.ReleaseNamespace != "" { if p.ValuesFile != "" {
args = append(args, "--namespace", p.ReleaseNamespace) args = append(args, "--values", p.ValuesFile)
} }
if p.Values != "" { if p.ReleaseName == "" {
args = append(args, "--values", p.Values) // 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 return args
} }
func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string { func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
args := []string{"pull", "--untar", "--untardir", p.ChartHome} args := []string{
chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName) "pull",
if p.ChartVersion != "" { "--untar",
args = append(args, "--version", p.ChartVersion) "--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 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. // local chart home.
func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool { func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
path := filepath.Join(p.ChartHome, p.ChartName) path := filepath.Join(p.absChartHome(), p.Name)
s, err := os.Stat(path) s, err := os.Stat(path)
if err != nil { 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 // checkHelmVersion will return an error if the helm version is not V3

View File

@@ -116,10 +116,16 @@ var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func(
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) ( kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
result []resmap.Generator, err error) { result []resmap.Generator, err error) {
var c struct { var c struct {
types.HelmChartArgs types.HelmGlobals
types.HelmChart
} }
for _, args := range kt.kustomization.HelmChartInflationGenerator { var globals types.HelmGlobals
c.HelmChartArgs = args if kt.kustomization.HelmGlobals != nil {
globals = *kt.kustomization.HelmGlobals
}
for _, chart := range kt.kustomization.HelmCharts {
c.HelmGlobals = globals
c.HelmChart = chart
p := f() p := f()
if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil { if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil {
return nil, err return nil, err

View File

@@ -3,14 +3,13 @@
package krusty_test package krusty_test
/*
import ( import (
"testing" "testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
) )
var expected string = ` const expectedHelm = `
apiVersion: v1 apiVersion: v1
data: data:
rcon-password: Q0hBTkdFTUUh rcon-password: Q0hBTkdFTUUh
@@ -18,36 +17,19 @@ kind: Secret
metadata: metadata:
labels: labels:
app: test-minecraft app: test-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: test
name: test-minecraft name: test-minecraft
type: Opaque type: Opaque
--- ---
apiVersion: v1 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 kind: Service
metadata: metadata:
annotations: {}
labels: labels:
app: test-minecraft app: test-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: test
name: test-minecraft name: test-minecraft
@@ -59,45 +41,43 @@ spec:
targetPort: minecraft targetPort: minecraft
selector: selector:
app: test-minecraft app: test-minecraft
type: LoadBalancer type: ClusterIP
` `
func TestHelmChartInflationGenerator(t *testing.T) { func TestHelmChartInflationGeneratorOld(t *testing.T) {
th := kusttest_test.MakeHarness(t) th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t)
th.WriteK("/app", ` defer th.Reset()
if err := th.ErrIfNoHelm(); err != nil {
t.Skip("skipping: " + err.Error())
}
th.WriteK(th.GetRoot(), `
helmChartInflationGenerator: helmChartInflationGenerator:
- chartName: minecraft - chartName: minecraft
chartRepoUrl: https://kubernetes-charts.storage.googleapis.com chartRepoUrl: https://itzg.github.io/minecraft-server-charts
chartVersion: v1.2.0 chartVersion: 3.1.3
releaseName: test releaseName: test
releaseNamespace: testNamespace
`) `)
m := th.Run("/app", th.MakeDefaultOptions()) m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled())
th.AssertActualEqualsExpected(m, expected) 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.WriteK(th.GetRoot(), `
th := kusttest_test.MakeHarness(t) helmCharts:
th.WriteK("/app", ` - name: minecraft
generators: repo: https://itzg.github.io/minecraft-server-charts
- helm.yaml version: 3.1.3
releaseName: test
`) `)
th.WriteF("/app/helm.yaml", ` m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled())
apiVersion: builtin th.AssertActualEqualsExpected(m, expectedHelm)
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)
} }
*/

View File

@@ -6,6 +6,7 @@ package kusttest_test
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"strings" "strings"
"testing" "testing"
@@ -83,6 +84,11 @@ func makeBaseEnhancedHarness(t *testing.T) *HarnessEnhanced {
filesys.MakeFsOnDisk())} filesys.MakeFsOnDisk())}
} }
func (th *HarnessEnhanced) ErrIfNoHelm() error {
_, err := exec.LookPath(th.GetPluginConfig().HelmConfig.Command)
return err
}
func (th *HarnessEnhanced) GetRoot() string { func (th *HarnessEnhanced) GetRoot() string {
return th.ldr.Root() return th.ldr.Root()
} }

View File

@@ -3,14 +3,77 @@
package types 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 { type HelmChartArgs struct {
ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"` ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"`
ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"` ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"` ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"` ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
// Use chartRelease to keep compatible with old exec plugin ChartRepoName string `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"`
ChartRepoName string `json:"chartRelease,omitempty" yaml:"chartRelease,omitempty"`
HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"` HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"` HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
Values string `json:"values,omitempty" yaml:"values,omitempty"` Values string `json:"values,omitempty" yaml:"values,omitempty"`
@@ -20,3 +83,32 @@ type HelmChartArgs struct {
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"` ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,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
}

View File

@@ -129,9 +129,14 @@ type Kustomization struct {
// the map will have a suffix hash generated from its contents. // the map will have a suffix hash generated from its contents.
SecretGenerator []SecretArgs `json:"secretGenerator,omitempty" yaml:"secretGenerator,omitempty"` 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. // HelmChartInflationGenerator is a list of helm chart configurations.
// The resulting resource is a normal operand rendered from // Deprecated. Auto-converted to HelmGlobals and HelmCharts.
// a remote chart by `helm template`
HelmChartInflationGenerator []HelmChartArgs `json:"helmChartInflationGenerator,omitempty" yaml:"helmChartInflationGenerator,omitempty"` HelmChartInflationGenerator []HelmChartArgs `json:"helmChartInflationGenerator,omitempty" yaml:"helmChartInflationGenerator,omitempty"`
// GeneratorOptions modify behavior of all ConfigMap and Secret generators. // GeneratorOptions modify behavior of all ConfigMap and Secret generators.
@@ -185,6 +190,15 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() {
k.SecretGenerator[i].EnvSource = "" 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 // FixKustomizationPreMarshalling fixes things

View File

@@ -3,6 +3,11 @@
package types package types
type HelmConfig struct {
Enabled bool
Command string
}
// PluginConfig holds plugin configuration. // PluginConfig holds plugin configuration.
type PluginConfig struct { type PluginConfig struct {
// PluginRestrictions distinguishes plugin restrictions. // PluginRestrictions distinguishes plugin restrictions.
@@ -13,11 +18,17 @@ type PluginConfig struct {
// FnpLoadingOptions sets the way function-based plugin behaviors. // FnpLoadingOptions sets the way function-based plugin behaviors.
FnpLoadingOptions FnPluginLoadingOptions FnpLoadingOptions FnPluginLoadingOptions
// HelmConfig contains metadata needed for allowing and running helm.
HelmConfig HelmConfig
} }
func EnabledPluginConfig(b BuiltinPluginLoadingOptions) (pc *PluginConfig) { func EnabledPluginConfig(b BuiltinPluginLoadingOptions) (pc *PluginConfig) {
pc = MakePluginConfig(PluginRestrictionsNone, b) pc = MakePluginConfig(PluginRestrictionsNone, b)
pc.FnpLoadingOptions.EnableStar = true 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 return
} }

View File

@@ -1,49 +1,63 @@
# kustomization of a helm chart # kustomization of a helm chart
[`helm`]: https://helm.sh
[last mile]: https://testingclouds.wordpress.com/2018/07/20/844/ [last mile]: https://testingclouds.wordpress.com/2018/07/20/844/
[stable chart]: https://github.com/helm/charts/tree/master/stable [artifact hub]: https://artifacthub.io
[helm charts]: https://github.com/helm/charts [_minecraft_]: https://artifacthub.io/packages/helm/minecraft-server-charts/minecraft
[_minecraft_]: https://github.com/helm/charts/tree/master/stable/minecraft
[plugin]: ../docs/plugins [plugin]: ../docs/plugins
[built]: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization
[Helm charts] aren't natively read by kustomize, but Kustomize is [built] from _generators_ and
kustomize has a builtin HelmChartInflationGenerator that allows one to _transformers_; the former make kubernetes YAML, the
access helm charts. latter transform said YAML.
One pattern combining kustomize and helm is Kustomize, via the `helmCharts` field, has the ability to
the [last mile] modification, where use the [`helm`] command line program in a subprocess to
one uses an inflated chart as a base, then inflate a helm chart, generating YAML as part of (or as the
modifies it on the way to the cluster using entirety of) a kustomize base.
kustomize.
The example arbitrarily uses [_minecraft_] in [stable chart] repository, This YAML can then be modified either in the base directly
but should work for any chart in any valid chart repository. (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
<!-- @defineHelmCommand @testHelm -->
```
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: Make a place to work:
<!-- @makeWorkplace @helmtest --> <!-- @makeWorkplace @testHelm -->
``` ```
DEMO_HOME=$(mktemp -d) DEMO_HOME=$(mktemp -d)
mkdir -p $DEMO_HOME/base mkdir -p $DEMO_HOME/base $DEMO_HOME/dev $DEMO_HOME/prod
mkdir -p $DEMO_HOME/dev
mkdir -p $DEMO_HOME/prod
``` ```
## Use a remote chart ## Define some variants
Define a kustomization representing your _development_ Define a kustomization representing your _development_
variant (aka environment). variant.
This could involve any number of kustomizations (see This could involve any number of kustomizations (see
other examples), but in this case just add the name other examples), but in this case just add the name
prefix `dev-` to all resources: prefix '`dev-`' to all resources:
<!-- @writeKustDev @helmtest -->
<!-- @writeKustDev @testHelm -->
``` ```
cat <<'EOF' >$DEMO_HOME/dev/kustomization.yaml cat <<'EOF' >$DEMO_HOME/dev/kustomization.yaml
namePrefix: dev- namePrefix: dev-
@@ -53,10 +67,9 @@ EOF
``` ```
Likewise define a _production_ variant, with a name Likewise define a _production_ variant, with a name
prefix `prod-`: prefix '`prod-`':
<!-- @writeKustProd @helmtest -->
<!-- @writeKustProd @testHelm -->
``` ```
cat <<'EOF' >$DEMO_HOME/prod/kustomization.yaml cat <<'EOF' >$DEMO_HOME/prod/kustomization.yaml
namePrefix: prod- namePrefix: prod-
@@ -67,45 +80,34 @@ EOF
These two variants refer to a common base. These two variants refer to a common base.
Define this base: Define this base the usual way by creating a
`kustomization` file:
<!-- @writeKustDev @helmtest -->
<!-- @writeKustBase @testHelm -->
``` ```
cat <<'EOF' >$DEMO_HOME/base/kustomization.yaml cat <<'EOF' >$DEMO_HOME/base/kustomization.yaml
generators: helmCharts:
- chartInflator.yaml - 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 EOF
``` ```
The base refers to a generator configuration file The only thing in this particular file is a `helmCharts`
called `chartInflator.yaml`. field, specifying a single chart.
This file lets one specify the name of a [stable chart], The `valuesInline` field overrides some native chart values.
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:
<!-- @writeGeneratorConfig @helmtest -->
```
cat <<'EOF' >$DEMO_HOME/base/chartInflator.yaml
apiVersion: builtin
kind: HelmChartInflationGenerator
metadata:
name: notImportantHere
chartName: minecraft
chartRepoUrl: https://kubernetes-charts.storage.googleapis.com
EOF
```
Check the directory layout: Check the directory layout:
<!-- @tree --> <!-- @tree -->
``` ```
tree $DEMO_HOME tree $DEMO_HOME
``` ```
@@ -115,155 +117,249 @@ Expect something like:
> ``` > ```
> /tmp/whatever > /tmp/whatever
> ├── base > ├── base
>    ├── chartInflator.yaml > └── kustomization.yaml
> │   └── kustomization.yaml
> ├── dev > ├── dev
>    └── kustomization.yaml > └── kustomization.yaml
> └── prod > └── prod
> └── kustomization.yaml > └── kustomization.yaml
> ``` > ```
Define a helper function to run kustomize with the ### Helm related flags
correct environment and flags for plugins:
<!-- @defineKustomizeIt @helmtest --> Attempt to build the `base`:
<!-- @checkFailure @testHelm -->
```
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:
<!-- @defineKustomizeIt @testHelm -->
``` ```
function kustomizeIt { function kustomizeIt {
XDG_CONFIG_HOME=$DEMO_HOME \ kustomize build \
kustomize build --enable_alpha_plugins \ --enable-helm \
--helm-command $helmCommand \
$DEMO_HOME/$1 $DEMO_HOME/$1
} }
``` ```
### Build the base and the variants
Finally, build the `prod` variant. Notice that all Now build the `base`:
resource names now have the `prod-` prefix:
<!-- @doProd @helmtest -->
```
clear
kustomizeIt prod
```
Compare `dev` to `prod`:
<!-- @doCompare -->
```
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.
<!-- @showBase @helmtest -->
<!-- @showBase @testHelm -->
``` ```
kustomizeIt 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 As a side effect of this build, kustomize pulled the chart
and render it with each kustomize and placed it in the `charts` subdirectory of the base.
build, because a local chart home isn't specified Take a look:
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**
<!-- @helmInit @helmtest -->
```
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):
<!-- @fetchChart @helmtest -->
```
doHelm pull --untar \
--untardir $chartHome \
--repo $repoUrl \
minecraft
```
The tree has more stuff now; helm config data
and a complete copy of the chart:
<!-- @tree --> <!-- @tree -->
``` ```
tree $DEMO_HOME tree $DEMO_HOME
``` ```
Add a `chartHome` field to the generator config file so If the chart had already been there, kustomize would
that it knows where to find the local chart: not have tried to pull it.
<!-- @modifyGenConfig @helmtest --> 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:
<!-- @doCompare -->
``` ```
echo "chartHome: $chartHome" >>$DEMO_HOME/base/chartInflator.yaml diff <(kustomizeIt dev) <(kustomizeIt prod) | more
``` ```
Change the values file, to show that the results This shows so-called _last mile hydration_ of two variants
generated below are from the _locally_ stored chart: made from a common base that happens to be generated from a
helm chart.
<!-- @valueChange @helmtest --> ## 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:
<!-- @checkPassword @testHelm -->
``` ```
sed -i 's/CHANGEME!/SOMETHINGELSE/' $chartHome/minecraft/values.yaml test 1 == $(kustomizeIt prod | grep -c "rcon-password: Q0hBTkdFTUUh")
sed -i 's/LoadBalancer/NodePort/' $chartHome/minecraft/values.yaml
``` ```
Finally, built it The above command succeeds if the value of the generated
password is as shown (`Q0hBTkdFTUUh`).
<!-- @finalProd @helmtest --> Now change the password in the local values file:
<!-- @valueChange @testHelm -->
``` ```
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 Run the build, and confirm that the same `rcon-password`
the change in the encoded 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.
<!-- @showBase @helmtest --> <!-- @checkPassword2 @testHelm -->
```
test 1 == $(kustomizeIt prod | grep -c "rcon-password: U09NRVRISU5HX0VMU0Uh")
```
Finally, clean up:
<!-- @showBase @testHelm -->
``` ```
rm -r $DEMO_HOME 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 To recap, the helm-related kustomization fields make
[go-based builtin helm chart inflator]: https://github.com/kubernetes-sigs/kustomize/tree/master/plugin/builtin/helmchartinflationgenerator 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, _as a convenience for the user_ to generate YAML from a helm chart.
so there's now a [go-based builtin helm chart inflator].
This newer generator is supported as part of the core code, so anyone using the Helm's `pull` command downloads the chart. Helm's `template`
old bash-based example plugin would probably benefit from switching to the command inflates the chart template, spitting the inflated
newer built-in plugin. 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.

View File

@@ -35,9 +35,11 @@ mdrip --mode test --blockTimeOut 15m \
# TODO: make work for non-linux # TODO: make work for non-linux
if onLinuxAndNotOnRemoteCI; then if onLinuxAndNotOnRemoteCI; then
echo "On linux, and not on remote CI. Running expensive tests." echo "On linux, and not on remote CI. Running expensive tests."
# Requires helm. # TODO: remove the HEAD check once --enable-helm flag released.
make $MYGOBIN/helmV3 if [ "$version" == "HEAD" ]; then
mdrip --mode test --label helmtest examples/chart.md make $MYGOBIN/helmV3
mdrip --mode test --label testHelm examples/chart.md
fi
fi fi
# Force outside logic to rebuild kustomize rather than # Force outside logic to rebuild kustomize rather than

View File

@@ -23,7 +23,9 @@ var theFlags struct {
enable struct { enable struct {
plugins bool plugins bool
managedByLabel bool managedByLabel bool
helm bool
} }
helmCommand string
loadRestrictor string loadRestrictor string
reorderOutput string reorderOutput string
fnOptions types.FnPluginLoadingOptions fnOptions types.FnPluginLoadingOptions
@@ -102,6 +104,7 @@ func NewCmdBuild(
AddFlagEnablePlugins(cmd.Flags()) AddFlagEnablePlugins(cmd.Flags())
AddFlagReorderOutput(cmd.Flags()) AddFlagReorderOutput(cmd.Flags())
AddFlagEnableManagedbyLabel(cmd.Flags()) AddFlagEnableManagedbyLabel(cmd.Flags())
AddFlagEnableHelm(cmd.Flags())
return cmd return cmd
} }
@@ -132,7 +135,10 @@ func HonorKustomizeFlags(kOpts *krusty.Options) *krusty.Options {
c := types.EnabledPluginConfig(types.BploUseStaticallyLinked) c := types.EnabledPluginConfig(types.BploUseStaticallyLinked)
c.FnpLoadingOptions = theFlags.fnOptions c.FnpLoadingOptions = theFlags.fnOptions
kOpts.PluginConfig = c kOpts.PluginConfig = c
} else {
kOpts.PluginConfig.HelmConfig.Enabled = theFlags.enable.helm
} }
kOpts.PluginConfig.HelmConfig.Command = theFlags.helmCommand
kOpts.AddManagedbyLabel = isManagedByLabelEnabled() kOpts.AddManagedbyLabel = isManagedByLabelEnabled()
return kOpts return kOpts
} }

View File

@@ -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)")
}

View File

@@ -1,11 +1,8 @@
// Copyright 2019 The Kubernetes Authors. // Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Helm chart generator // Helm chart inflation generator.
// // Uses helm V3 to generate k8s YAML from a helm chart.
// Fetches the given chart from {ChartRepo}/{ChartName},
// and inflates it to stdout, using the given values file.
// This generator expects helm V3 or later.
//go:generate pluginator //go:generate pluginator
package main package main
@@ -13,7 +10,6 @@ package main
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@@ -23,7 +19,6 @@ import (
"github.com/imdario/mergo" "github.com/imdario/mergo"
"github.com/pkg/errors" "github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/resmap" "sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
@@ -32,202 +27,235 @@ import (
// HelmChartInflationGeneratorPlugin is a plugin to generate resources // HelmChartInflationGeneratorPlugin is a plugin to generate resources
// from a remote or local helm chart. // from a remote or local helm chart.
type HelmChartInflationGeneratorPlugin struct { type HelmChartInflationGeneratorPlugin struct {
h *resmap.PluginHelpers h *resmap.PluginHelpers
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` types.HelmGlobals
runHelmCommand func([]string) ([]byte, error) types.HelmChart
types.HelmChartArgs
tmpDir string tmpDir string
} }
//noinspection GoUnusedGlobalVariable //noinspection GoUnusedGlobalVariable
var KustomizePlugin HelmChartInflationGeneratorPlugin 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 // Config uses the input plugin configurations `config` to setup the generator
// options // 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 p.h = h
err := yaml.Unmarshal(config, p) if err = yaml.Unmarshal(config, p); err != nil {
if err != nil { return
return err
} }
tmpDir, err := filesys.NewTmpConfirmedDir() return p.validateArgs()
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
} }
// EncodeValues for writing // This uses the real file system since tmpDir may be used
func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error { // by the helm subprocess. Cannot use a chroot jail or fake
d, err := yaml.Marshal(p.ValuesLocal) // filesystem since we allow the user to use previously
if err != nil { // downloaded charts. This is safe since this plugin is
return err // owned by kustomize.
} func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
_, err = w.Write(d) if p.tmpDir != "" {
if err != nil { // already done.
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) {
return nil return nil
} }
// we must use use loader to read values file p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
b, err := p.h.Loader().Load(p.Values) return err
if err != nil { }
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 return err
} }
path, err := p.writeValuesBytes(b)
if err != nil { // ConfigHome is not loaded by the plugin, and can be located anywhere.
return err 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 return nil
} }
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(b []byte) (string, error) { func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
path := filepath.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml") if p.ValuesMerge == "" {
err := ioutil.WriteFile(path, b, 0644) // 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 { if err != nil {
return "", err 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 // Generate implements generator
func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) { func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
// cleanup defer p.cleanup()
defer os.RemoveAll(p.tmpDir) if err = p.checkHelmVersion(); err != nil {
// check helm version. we only support V3
err := p.checkHelmVersion()
if err != nil {
return nil, err return nil, err
} }
// pull the chart if path, exists := p.chartExistsLocally(); !exists {
if !p.checkLocalChart() { if p.Repo == "" {
_, err := p.runHelmCommand(p.getPullCommandArgs()) return nil, fmt.Errorf(
if err != nil { "no repo specified for pull, no chart found at '%s'", path)
}
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
return nil, err return nil, err
} }
} }
if len(p.ValuesInline) > 0 {
// inflator config valuesLocal p.ValuesFile, err = p.createNewMergedValuesFile()
if len(p.ValuesLocal) > 0 {
err := p.useValuesLocal()
if err != nil {
return nil, err
}
} else { } else {
err := p.copyValues() p.ValuesFile, err = p.copyValuesFile()
if err != nil {
return nil, err
}
} }
if err != nil {
// render the charts return nil, err
stdout, err := p.runHelmCommand(p.getTemplateCommandArgs()) }
var stdout []byte
stdout, err = p.runHelmCommand(p.templateCommand())
if err != nil { if err != nil {
return nil, err return nil, err
} }
rm, rmfErr := p.h.ResmapFactory().NewResMapFromBytes(stdout) rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
if rmfErr == nil { if err == nil {
return rm, nil return rm, nil
} }
// try to remove the contents before first "---" because // 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 { if idx := strings.Index(stdoutStr, "---"); idx != -1 {
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:])) 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"} args := []string{"template"}
if p.ReleaseName != "" { if p.ReleaseName != "" {
args = append(args, p.ReleaseName) args = append(args, p.ReleaseName)
} }
args = append(args, filepath.Join(p.ChartHome, p.ChartName)) args = append(args, filepath.Join(p.absChartHome(), p.Name))
if p.ReleaseNamespace != "" { if p.ValuesFile != "" {
args = append(args, "--namespace", p.ReleaseNamespace) args = append(args, "--values", p.ValuesFile)
} }
if p.Values != "" { if p.ReleaseName == "" {
args = append(args, "--values", p.Values) // 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 return args
} }
func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string { func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
args := []string{"pull", "--untar", "--untardir", p.ChartHome} args := []string{
chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName) "pull",
if p.ChartVersion != "" { "--untar",
args = append(args, "--version", p.ChartVersion) "--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 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. // local chart home.
func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool { func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
path := filepath.Join(p.ChartHome, p.ChartName) path := filepath.Join(p.absChartHome(), p.Name)
s, err := os.Stat(path) s, err := os.Stat(path)
if err != nil { 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 // checkHelmVersion will return an error if the helm version is not V3

View File

@@ -1,34 +1,30 @@
package main_test package main_test
/*
import ( import (
"fmt" "fmt"
"io/ioutil" "path/filepath"
"os"
"path"
"testing" "testing"
"sigs.k8s.io/kustomize/api/filesys"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest" 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) { func TestHelmChartInflationGenerator(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t). th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t).
PrepBuiltin("HelmChartInflationGenerator") PrepBuiltin("HelmChartInflationGenerator")
defer th.Reset() defer th.Reset()
if err := th.ErrIfNoHelm(); err != nil {
t.Skip("skipping: " + err.Error())
}
rm := th.LoadAndRunGenerator(` rm := th.LoadAndRunGenerator(`
apiVersion: builtin apiVersion: builtin
kind: HelmChartInflationGenerator kind: HelmChartInflationGenerator
metadata: metadata:
name: myMap name: myMc
chartName: minecraft name: minecraft
chartRepoUrl: https://charts.helm.sh/stable version: 3.1.3
chartVersion: v1.2.0 repo: https://itzg.github.io/minecraft-server-charts
releaseName: test releaseName: moria
releaseNamespace: testNamespace
`) `)
th.AssertActualEqualsExpected(rm, ` th.AssertActualEqualsExpected(rm, `
@@ -38,40 +34,23 @@ data:
kind: Secret kind: Secret
metadata: metadata:
labels: labels:
app: test-minecraft app: moria-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: moria
name: test-minecraft name: moria-minecraft
type: Opaque type: Opaque
--- ---
apiVersion: v1 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 kind: Service
metadata: metadata:
annotations: {}
labels: labels:
app: test-minecraft app: moria-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: moria
name: test-minecraft name: moria-minecraft
spec: spec:
ports: ports:
- name: minecraft - name: minecraft
@@ -79,93 +58,35 @@ spec:
protocol: TCP protocol: TCP
targetPort: minecraft targetPort: minecraft
selector: selector:
app: test-minecraft app: moria-minecraft
type: LoadBalancer type: ClusterIP
`) `)
} }
func TestHelmChartInflationGeneratorWithValues(t *testing.T) { const expectedInflationFmt = `
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, `
apiVersion: v1 apiVersion: v1
data: data:
rcon-password: Q0hBTkdFTUUh rcon-password: Q0hBTkdFTUUh
kind: Secret kind: Secret
metadata: metadata:
labels: labels:
app: test-minecraft app: moria-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: moria
name: test-minecraft name: moria-minecraft
type: Opaque type: Opaque
--- ---
apiVersion: v1 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 kind: Service
metadata: metadata:
annotations: {}
labels: labels:
app: test-minecraft app: moria-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: moria
name: test-minecraft name: moria-minecraft
spec: spec:
ports: ports:
- name: minecraft - name: minecraft
@@ -173,28 +94,48 @@ spec:
protocol: TCP protocol: TCP
targetPort: minecraft targetPort: minecraft
selector: 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 type: LoadBalancer
--- ---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
labels: labels:
app: test-minecraft app: moria-minecraft
chart: minecraft-1.2.0 chart: minecraft-3.1.3
heritage: Helm heritage: Helm
release: test release: moria
name: test-minecraft name: moria-minecraft
spec: spec:
selector: selector:
matchLabels: matchLabels:
app: test-minecraft app: moria-minecraft
strategy: strategy:
type: Recreate type: Recreate
template: template:
metadata: metadata:
labels: labels:
app: test-minecraft app: moria-minecraft
spec: spec:
containers: containers:
- env: - env:
@@ -203,9 +144,9 @@ spec:
- name: TYPE - name: TYPE
value: VANILLA value: VANILLA
- name: VERSION - name: VERSION
value: 1.14.4 value: LATEST
- name: DIFFICULTY - name: DIFFICULTY
value: easy value: %s
- name: WHITELIST - name: WHITELIST
value: "" value: ""
- name: OPS - name: OPS
@@ -222,7 +163,7 @@ spec:
value: "true" value: "true"
- name: ENABLE_COMMAND_BLOCK - name: ENABLE_COMMAND_BLOCK
value: "true" value: "true"
- name: FORCE_gameMode - name: FORCE_GAMEMODE
value: "false" value: "false"
- name: GENERATE_STRUCTURES - name: GENERATE_STRUCTURES
value: "true" value: "true"
@@ -257,47 +198,48 @@ spec:
- name: ONLINE_MODE - name: ONLINE_MODE
value: "true" value: "true"
- name: MEMORY - name: MEMORY
value: 512M value: 1024M
- name: JVM_OPTS - name: JVM_OPTS
value: "" value: ""
- name: JVM_XX_OPTS - name: JVM_XX_OPTS
value: "" value: ""
- name: ENABLE_RCON
value: "true"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: moria-minecraft
image: itzg/minecraft-server:latest image: itzg/minecraft-server:latest
imagePullPolicy: Always imagePullPolicy: Always
livenessProbe: livenessProbe:
exec:
command:
- mcstatus
- localhost:25565
- status
failureThreshold: 10 failureThreshold: 10
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 5 periodSeconds: 5
successThreshold: 1 successThreshold: 1
tcpSocket:
port: 25565
timeoutSeconds: 1 timeoutSeconds: 1
name: test-minecraft name: moria-minecraft
ports: ports:
- containerPort: 25565 - containerPort: 25565
name: minecraft name: minecraft
protocol: TCP protocol: TCP
- containerPort: 25575
name: rcon
protocol: TCP
readinessProbe: readinessProbe:
exec:
command:
- mcstatus
- localhost:25565
- status
failureThreshold: 10 failureThreshold: 10
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 5 periodSeconds: 5
successThreshold: 1 successThreshold: 1
tcpSocket:
port: 25565
timeoutSeconds: 1 timeoutSeconds: 1
resources: resources:
limits:
cpu: 1000m
memory: 512Mi
requests: requests:
cpu: 200m cpu: %dm
memory: 512Mi memory: %dMi
volumeMounts: volumeMounts:
- mountPath: /data - mountPath: /data
name: datadir name: datadir
@@ -305,9 +247,110 @@ spec:
fsGroup: 2000 fsGroup: 2000
runAsUser: 1000 runAsUser: 1000
volumes: volumes:
- name: datadir - emptyDir: {}
persistentVolumeClaim: name: datadir
claimName: test-minecraft-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
))
} }
*/

View File

@@ -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/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.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/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/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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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.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 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/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/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/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.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/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/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= 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.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.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/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.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/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=