diff --git a/kubectl_kustomize_test.go b/kubectl_kustomize_test.go new file mode 100644 index 0000000..efc4ad3 --- /dev/null +++ b/kubectl_kustomize_test.go @@ -0,0 +1,106 @@ +package chartify + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestKubectlKustomizeFallback(t *testing.T) { + t.Run("KustomizeBuild with kubectl kustomize", func(t *testing.T) { + if _, err := exec.LookPath("kubectl"); err != nil { + t.Skip("kubectl binary not found in PATH") + } + + tmpDir := t.TempDir() + srcDir := t.TempDir() + + kustomizationContent := `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- deployment.yaml +` + deploymentContent := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +spec: + replicas: 1 + selector: + matchLabels: + app: test + template: + metadata: + labels: + app: test + spec: + containers: + - name: test + image: test:latest +` + + templatesDir := filepath.Join(tmpDir, "templates") + require.NoError(t, os.MkdirAll(templatesDir, 0755)) + + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "kustomization.yaml"), []byte(kustomizationContent), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "deployment.yaml"), []byte(deploymentContent), 0644)) + + r := New(KustomizeBin("kubectl kustomize")) + + outputFile, err := r.KustomizeBuild(srcDir, tmpDir) + require.NoError(t, err) + require.FileExists(t, outputFile) + }) + + t.Run("edit commands not supported with kubectl kustomize", func(t *testing.T) { + tmpDir := t.TempDir() + srcDir := t.TempDir() + + kustomizationContent := `apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- deployment.yaml +` + deploymentContent := `apiVersion: apps/v1 +kind: Deployment +metadata: + name: test +spec: + replicas: 1 + selector: + matchLabels: + app: test + template: + metadata: + labels: + app: test + spec: + containers: + - name: test + image: test:latest +` + + templatesDir := filepath.Join(tmpDir, "templates") + valuesDir := t.TempDir() + valuesFile := filepath.Join(valuesDir, "values.yaml") + valuesContent := `images: +- name: test + newName: newtest + newTag: v2 +` + + require.NoError(t, os.MkdirAll(templatesDir, 0755)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "kustomization.yaml"), []byte(kustomizationContent), 0644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "deployment.yaml"), []byte(deploymentContent), 0644)) + require.NoError(t, os.WriteFile(valuesFile, []byte(valuesContent), 0644)) + + r := New(KustomizeBin("kubectl kustomize")) + + _, err := r.KustomizeBuild(srcDir, tmpDir, &KustomizeBuildOpts{ValuesFiles: []string{valuesFile}}) + require.Error(t, err) + require.Contains(t, err.Error(), "setting images via kustomizeOpts.Images is not supported when using 'kubectl kustomize'") + }) +} diff --git a/kustomize.go b/kustomize.go index f81f3fd..eb1c878 100644 --- a/kustomize.go +++ b/kustomize.go @@ -110,6 +110,9 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize } if len(kustomizeOpts.Images) > 0 { + if r.isUsingKubectlKustomize() { + return "", fmt.Errorf("setting images via kustomizeOpts.Images is not supported when using 'kubectl kustomize'. Please set images directly in your kustomization.yaml file") + } args := []string{"edit", "set", "image"} for _, image := range kustomizeOpts.Images { args = append(args, image.String()) @@ -120,6 +123,9 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize } } if kustomizeOpts.NamePrefix != "" { + if r.isUsingKubectlKustomize() { + return "", fmt.Errorf("setting namePrefix via kustomizeOpts.NamePrefix is not supported when using 'kubectl kustomize'. Please set namePrefix directly in your kustomization.yaml file") + } _, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "nameprefix", kustomizeOpts.NamePrefix) if err != nil { fmt.Println(err) @@ -127,6 +133,9 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize } } if kustomizeOpts.NameSuffix != "" { + if r.isUsingKubectlKustomize() { + return "", fmt.Errorf("setting nameSuffix via kustomizeOpts.NameSuffix is not supported when using 'kubectl kustomize'. Please set nameSuffix directly in your kustomization.yaml file") + } // "--" is there to avoid `namesuffix -acme` to fail due to `-a` being considered as a flag _, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "namesuffix", "--", kustomizeOpts.NameSuffix) if err != nil { @@ -134,13 +143,20 @@ func (r *Runner) KustomizeBuild(srcDir string, tempDir string, opts ...Kustomize } } if kustomizeOpts.Namespace != "" { + if r.isUsingKubectlKustomize() { + return "", fmt.Errorf("setting namespace via kustomizeOpts.Namespace is not supported when using 'kubectl kustomize'. Please set namespace directly in your kustomization.yaml file") + } _, err := r.runInDir(tempDir, r.kustomizeBin(), "edit", "set", "namespace", kustomizeOpts.Namespace) if err != nil { return "", err } } outputFile := filepath.Join(tempDir, "templates", "kustomized.yaml") - kustomizeArgs := []string{"-o", outputFile, "build"} + kustomizeArgs := []string{"-o", outputFile} + + if !r.isUsingKubectlKustomize() { + kustomizeArgs = append(kustomizeArgs, "build") + } if u.EnableAlphaPlugins { f, err := r.kustomizeEnableAlphaPluginsFlag() @@ -190,10 +206,18 @@ func (r *Runner) kustomizeVersion() (*semver.Version, error) { return version, nil } +// isUsingKubectlKustomize checks if we're using kubectl's built-in kustomize +func (r *Runner) isUsingKubectlKustomize() bool { + return r.kustomizeBin() == "kubectl kustomize" +} + // kustomizeEnableAlphaPluginsFlag returns the kustomize binary alpha plugin argument. // Above Kustomize v3, it is `--enable-alpha-plugins`. // Below Kustomize v3 (including v3), it is `--enable_alpha_plugins`. func (r *Runner) kustomizeEnableAlphaPluginsFlag() (string, error) { + if r.isUsingKubectlKustomize() { + return "--enable-alpha-plugins", nil + } version, err := r.kustomizeVersion() if err != nil { return "", err @@ -209,6 +233,9 @@ func (r *Runner) kustomizeEnableAlphaPluginsFlag() (string, error) { // Above Kustomize v3, it is `--load-restrictor=LoadRestrictionsNone`. // Below Kustomize v3 (including v3), it is `--load_restrictor=none`. func (r *Runner) kustomizeLoadRestrictionsNoneFlag() (string, error) { + if r.isUsingKubectlKustomize() { + return "--load-restrictor=LoadRestrictionsNone", nil + } version, err := r.kustomizeVersion() if err != nil { return "", err diff --git a/patch.go b/patch.go index e41c2eb..1229564 100644 --- a/patch.go +++ b/patch.go @@ -181,9 +181,13 @@ resources: renderedFileName := "all.patched.yaml" renderedFile := filepath.Join(tempDir, renderedFileName) - r.Logf("Generating %s", renderedFile) + r.Logf("Generating %s", renderedFileName) - kustomizeArgs := []string{"build", tempDir, "--output", renderedFile} + kustomizeArgs := []string{"--output", renderedFile} + + if !r.isUsingKubectlKustomize() { + kustomizeArgs = append([]string{"build", tempDir}, kustomizeArgs...) + } if u.EnableAlphaPlugins { f, err := r.kustomizeEnableAlphaPluginsFlag() diff --git a/runner.go b/runner.go index 61eb0ca..aafdd0f 100644 --- a/runner.go +++ b/runner.go @@ -108,6 +108,15 @@ func (r *Runner) kustomizeBin() string { if r.KustomizeBinary != "" { return r.KustomizeBinary } + if env := os.Getenv("KUSTOMIZE_BIN"); env != "" { + return env + } + if _, err := exec.LookPath("kustomize"); err == nil { + return "kustomize" + } + if _, err := exec.LookPath("kubectl"); err == nil { + return "kubectl kustomize" + } return "kustomize" } @@ -140,7 +149,7 @@ func (r *Runner) runBytes(envs map[string]string, dir, cmd string, args ...strin name := nameArgs[0] - if len(nameArgs) > 2 { + if len(nameArgs) > 1 { a := append([]string{}, nameArgs[1:]...) a = append(a, args...) @@ -154,10 +163,10 @@ func (r *Runner) runBytes(envs map[string]string, dir, cmd string, args ...strin wrappedErr := fmt.Errorf(`%w COMMAND: -%s + %s OUTPUT: -%s`, + %s`, err, indent(c, " "), indent(string(errBytes), " "), diff --git a/util_test.go b/util_test.go index 6a944c4..e04fccd 100644 --- a/util_test.go +++ b/util_test.go @@ -1,9 +1,12 @@ package chartify import ( + "os" + "path/filepath" "testing" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" ) func TestCreateFlagChain(t *testing.T) { @@ -107,3 +110,91 @@ func TestFindSemVerInfo(t *testing.T) { }) } } + +func TestKustomizeBin(t *testing.T) { + t.Run("KustomizeBinary option is set", func(t *testing.T) { + r := New(KustomizeBin("/custom/kustomize")) + got := r.kustomizeBin() + want := "/custom/kustomize" + if got != want { + t.Errorf("kustomizeBin() = %v, want %v", got, want) + } + }) + + t.Run("KUSTOMIZE_BIN environment variable", func(t *testing.T) { + if _, ok := os.LookupEnv("KUSTOMIZE_BIN"); ok { + t.Skip("KUSTOMIZE_BIN environment variable is already set") + } + os.Setenv("KUSTOMIZE_BIN", "/custom/kustomize") + defer os.Unsetenv("KUSTOMIZE_BIN") + r := New() + got := r.kustomizeBin() + want := "/custom/kustomize" + if got != want { + t.Errorf("kustomizeBin() = %v, want %v", got, want) + } + }) + + t.Run("fallback to kubectl kustomize when kustomize not found", func(t *testing.T) { + tmpDir := t.TempDir() + binDir := filepath.Join(tmpDir, "bin") + require.NoError(t, os.MkdirAll(binDir, 0755)) + + kubectlPath := filepath.Join(binDir, "kubectl") + kubectlContent := []byte("#!/bin/sh\necho 'kubectl version'\n") + require.NoError(t, os.WriteFile(kubectlPath, kubectlContent, 0755)) + + origPath := os.Getenv("PATH") + defer os.Setenv("PATH", origPath) + os.Setenv("PATH", binDir) + + r := New() + got := r.kustomizeBin() + want := "kubectl kustomize" + if got != want { + t.Errorf("kustomizeBin() = %v, want %v", got, want) + } + }) + + t.Run("use kustomize when both kustomize and kubectl exist in PATH", func(t *testing.T) { + tmpDir := t.TempDir() + binDir := filepath.Join(tmpDir, "bin") + require.NoError(t, os.MkdirAll(binDir, 0755)) + + kustomizePath := filepath.Join(binDir, "kustomize") + kustomizeContent := []byte("#!/bin/sh\necho 'kustomize version'\n") + require.NoError(t, os.WriteFile(kustomizePath, kustomizeContent, 0755)) + + kubectlPath := filepath.Join(binDir, "kubectl") + kubectlContent := []byte("#!/bin/sh\necho 'kubectl version'\n") + require.NoError(t, os.WriteFile(kubectlPath, kubectlContent, 0755)) + + origPath := os.Getenv("PATH") + defer os.Setenv("PATH", origPath) + os.Setenv("PATH", binDir) + + r := New() + got := r.kustomizeBin() + want := "kustomize" + if got != want { + t.Errorf("kustomizeBin() = %v, want %v", got, want) + } + }) + + t.Run("return kustomize as fallback when neither kustomize nor kubectl exist", func(t *testing.T) { + tmpDir := t.TempDir() + binDir := filepath.Join(tmpDir, "bin") + require.NoError(t, os.MkdirAll(binDir, 0755)) + + origPath := os.Getenv("PATH") + defer os.Setenv("PATH", origPath) + os.Setenv("PATH", binDir) + + r := New() + got := r.kustomizeBin() + want := "kustomize" + if got != want { + t.Errorf("kustomizeBin() = %v, want %v", got, want) + } + }) +}