diff --git a/chartify.go b/chartify.go index 43a35dd..7ccf85e 100644 --- a/chartify.go +++ b/chartify.go @@ -56,6 +56,10 @@ type ChartifyOpts struct { // EnableKustomizAlphaPlugins will add the `--enable_alpha_plugins` flag when running `kustomize build` EnableKustomizeAlphaPlugins bool + // KustomizeBuildArgs are extra arguments to pass to `kustomize build` command + // For example, ["--enable-exec"] for plugins like ksops + KustomizeBuildArgs []string + Injectors []string Injects []string @@ -279,6 +283,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s Namespace: u.Namespace, HelmBinary: r.helmBin(), SortOptions: u.SortOptions, + ExtraArgs: u.KustomizeBuildArgs, } kustomizeFile, err := r.KustomizeBuild(dirOrChart, tempDir, kustomizeOpts) if err != nil { @@ -496,6 +501,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s Transformers: u.Transformers, EnableAlphaPlugins: u.EnableKustomizeAlphaPlugins, SortOptions: u.SortOptions, + ExtraArgs: u.KustomizeBuildArgs, } if err := r.Patch(tempDir, generatedManifestFiles, patchOpts); err != nil { return "", err diff --git a/cmd/chartify/main.go b/cmd/chartify/main.go index 7d64a5a..bcff0be 100644 --- a/cmd/chartify/main.go +++ b/cmd/chartify/main.go @@ -33,6 +33,7 @@ func main() { Namespace: "", ChartVersion: "", EnableKustomizeAlphaPlugins: false, + KustomizeBuildArgs: nil, Injectors: nil, Injects: nil, AdhocChartDependencies: nil, @@ -43,12 +44,14 @@ func main() { } deps := stringSlice{} + kustomizeBuildArgs := stringSlice{} flag.StringVar(&file, "f", "-", "The path to the input file or stdout(-)") flag.StringVar(&outDir, "o", "", "The path to the output directory") flag.Var(&deps, "d", "one or more \"alias=chart:version\" to add adhoc chart dependencies") flag.BoolVar(&opts.IncludeCRDs, "include-crds", false, "Whether to render CRDs contained in the chart and include the results into the output") flag.StringVar(&strategicMergePatch, "strategic-merge-patch", "", "Path to a kustomize strategic merge patch file") + flag.Var(&kustomizeBuildArgs, "kustomize-build-arg", "Extra arguments to pass to 'kustomize build' command (e.g. --enable-exec). Can be specified multiple times.") flag.Parse() @@ -61,6 +64,7 @@ func main() { } opts.DeprecatedAdhocChartDependencies = deps + opts.KustomizeBuildArgs = kustomizeBuildArgs c := chartify.New(chartify.HelmBin("helm")) diff --git a/kustomize.go b/kustomize.go index 9c4d438..cec6e06 100644 --- a/kustomize.go +++ b/kustomize.go @@ -80,6 +80,7 @@ type KustomizeBuildOpts struct { Namespace string HelmBinary string SortOptions *SortOptions + ExtraArgs []string } func (o *KustomizeBuildOpts) SetKustomizeBuildOption(opts *KustomizeBuildOpts) error { @@ -238,6 +239,11 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize kustomizeArgs = append(kustomizeArgs, "--helm-command="+u.HelmBinary) } + // Add any extra arguments provided by the user + if len(u.ExtraArgs) > 0 { + kustomizeArgs = append(kustomizeArgs, u.ExtraArgs...) + } + out, err := r.runInDir(tempDir, bin, append(kustomizeArgs, tempDir)...) if err != nil { return "", err diff --git a/kustomize_extra_args_test.go b/kustomize_extra_args_test.go new file mode 100644 index 0000000..1c7c65e --- /dev/null +++ b/kustomize_extra_args_test.go @@ -0,0 +1,118 @@ +package chartify + +import ( + "io" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +type spyCall struct { + name string + args []string +} + +// newSpyRunner returns a Runner whose RunCommand captures every invocation +// instead of running a real process. The spy: +// - replies to `kustomize version` so kustomizeLoadRestrictionsNoneFlag / +// kustomizeEnableAlphaPluginsFlag can derive a flag, and +// - honors `--output ` (used by Patch) by writing a minimal resource +// YAML there, so callers that read the rendered file succeed. +// +// The kustomize binary name still has to resolve via exec.LookPath, so this +// relies on kustomize being on PATH (already a documented test prerequisite). +func newSpyRunner(t *testing.T, versionOut string) (*Runner, *[]spyCall) { + t.Helper() + var calls []spyCall + r := New() + r.RunCommand = func(name string, args []string, dir string, stdout, stderr io.Writer, env map[string]string) error { + calls = append(calls, spyCall{name: name, args: append([]string{}, args...)}) + + if len(args) > 0 && args[0] == "version" { + _, _ = io.WriteString(stdout, versionOut) + return nil + } + for i, a := range args { + if a == "--output" && i+1 < len(args) { + _ = os.WriteFile(args[i+1], []byte("kind: ConfigMap\nmetadata:\n name: test\n"), 0644) + } + } + return nil + } + return r, &calls +} + +// buildCall returns the first recorded invocation that runs a kustomize "build". +func buildCall(calls []spyCall) (spyCall, bool) { + for _, c := range calls { + for _, a := range c.args { + if a == "build" { + return c, true + } + } + } + return spyCall{}, false +} + +func TestKustomizeBuildExtraArgs(t *testing.T) { + r, callsPtr := newSpyRunner(t, "v5.0.0") + + srcDir := t.TempDir() + tempDir := t.TempDir() + + _, err := r.KustomizeBuild(srcDir, tempDir, &KustomizeBuildOpts{ + ExtraArgs: []string{"--enable-exec"}, + }) + require.NoError(t, err) + + build, ok := buildCall(*callsPtr) + require.True(t, ok, "expected a kustomize build invocation; calls=%v", *callsPtr) + + // ExtraArgs must be appended to the build command. + require.Contains(t, build.args, "--enable-exec", + "ExtraArgs should be appended to the kustomize build command") + // Standard flags must still be present. + require.Contains(t, build.args, "--enable-helm") + require.Contains(t, build.args, "--load-restrictor=LoadRestrictionsNone") + + // ExtraArgs must be inserted before the positional target (tempDir, passed + // last) so a flag is never mistaken for the build target. + require.NotEmpty(t, build.args) + require.Equal(t, tempDir, build.args[len(build.args)-1], + "the kustomize target must remain the last positional arg") +} + +func TestKustomizeBuildNoExtraArgsByDefault(t *testing.T) { + r, callsPtr := newSpyRunner(t, "v5.0.0") + + _, err := r.KustomizeBuild(t.TempDir(), t.TempDir()) + require.NoError(t, err) + + build, ok := buildCall(*callsPtr) + require.True(t, ok) + require.NotContains(t, build.args, "--enable-exec", + "no extra flags should be added when ExtraArgs is unset") +} + +func TestPatchExtraArgs(t *testing.T) { + r, callsPtr := newSpyRunner(t, "v5.0.0") + + tempDir := t.TempDir() + manifest := filepath.Join(tempDir, "templates", "cm.yaml") + require.NoError(t, os.MkdirAll(filepath.Dir(manifest), 0755)) + require.NoError(t, os.WriteFile(manifest, []byte("kind: ConfigMap\nmetadata:\n name: test\n"), 0644)) + + err := r.Patch(tempDir, []string{"templates/cm.yaml"}, &PatchOpts{ + ExtraArgs: []string{"--enable-exec"}, + }) + require.NoError(t, err) + + build, ok := buildCall(*callsPtr) + require.True(t, ok, "expected a kustomize build invocation; calls=%v", *callsPtr) + require.Contains(t, build.args, "--enable-exec", + "PatchOpts.ExtraArgs should be appended to the kustomize build command") + require.Equal(t, tempDir, build.args[len(build.args)-1], + "the kustomize target must remain the last positional arg") +} diff --git a/patch.go b/patch.go index f61054b..161a4d3 100644 --- a/patch.go +++ b/patch.go @@ -25,6 +25,10 @@ type PatchOpts struct { // SortOptions configures kustomize's sortOptions for resource ordering. SortOptions *SortOptions + + // ExtraArgs are extra arguments to pass to `kustomize build` command + // For example, ["--enable-exec"] for plugins like ksops + ExtraArgs []string } func (o *PatchOpts) SetPatchOption(opts *PatchOpts) error { @@ -212,6 +216,11 @@ resources: kustomizeArgs = append(kustomizeArgs, f) } + // Add any extra arguments provided by the user + if len(u.ExtraArgs) > 0 { + kustomizeArgs = append(kustomizeArgs, u.ExtraArgs...) + } + // tempDir is the kustomize target, appended last (mirrors KustomizeBuild argument order). _, err := r.run(nil, bin, append(kustomizeArgs, tempDir)...) if err != nil { diff --git a/tempdir_test.go b/tempdir_test.go index 67d7c38..cd39ef0 100644 --- a/tempdir_test.go +++ b/tempdir_test.go @@ -35,21 +35,21 @@ func TestGenerateID(t *testing.T) { release: "foo", chart: "incubator/raw", opts: ChartifyOpts{}, - want: "foo-5586b9d54d", + want: "foo-7bb8758c6f", }) run(testcase{ release: "foo", chart: "stable/envoy", opts: ChartifyOpts{}, - want: "foo-748fb9844f", + want: "foo-595bcf5dfd", }) run(testcase{ release: "bar", chart: "incubator/raw", opts: ChartifyOpts{}, - want: "bar-77ddc8bd65", + want: "bar-7759488ffd", }) run(testcase{ @@ -57,7 +57,7 @@ func TestGenerateID(t *testing.T) { opts: ChartifyOpts{ Namespace: "myns", }, - want: "myns-foo-5dbbf694b5", + want: "myns-foo-54c5cbf858", }) for id, n := range ids {