-
Notifications
You must be signed in to change notification settings - Fork 33
feat: Allow using kubectl built-in kustomize when separate kustomize binary is missing #173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
bd6474c
8fd729b
16b8235
57fe4e3
9eaf7ce
4bc0233
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| package chartify | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // stubKubectlScript is a minimal sh script that acts as a kubectl stub for tests. | ||
| // It handles `kubectl kustomize <dir> [-o|-o FILE|--output FILE]` by writing | ||
| // a minimal valid Kubernetes Deployment YAML to the specified output file. | ||
| const stubKubectlScript = `#!/bin/sh | ||
| if [ "$1" = "kustomize" ]; then | ||
| shift | ||
| OUTPUT="" | ||
| while [ $# -gt 0 ]; do | ||
| case "$1" in | ||
| -o) OUTPUT="$2"; shift 2;; | ||
| --output) OUTPUT="$2"; shift 2;; | ||
| *) shift;; | ||
| esac | ||
| done | ||
| if [ -n "$OUTPUT" ]; then | ||
| printf 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: stub\n' > "$OUTPUT" | ||
| fi | ||
| fi | ||
| ` | ||
|
|
||
| // writeStubKubectl creates a stub kubectl script in dir. | ||
| func writeStubKubectl(t *testing.T, dir string) { | ||
| t.Helper() | ||
| p := filepath.Join(dir, "kubectl") | ||
| require.NoError(t, os.WriteFile(p, []byte(stubKubectlScript), 0755)) | ||
| } | ||
|
|
||
| // TestKubectlKustomize tests behavior when kubectl kustomize is explicitly configured | ||
| // via KustomizeBin("kubectl kustomize"). The automatic fallback selection is tested | ||
| // in TestKustomizeBin. | ||
| func TestKubectlKustomize(t *testing.T) { | ||
| t.Run("KustomizeBuild succeeds with kubectl kustomize option", func(t *testing.T) { | ||
| // Create a stub kubectl so the test is self-contained and always runs in CI. | ||
| stubDir := t.TempDir() | ||
| writeStubKubectl(t, stubDir) | ||
|
|
||
| origPath := os.Getenv("PATH") | ||
| defer os.Setenv("PATH", origPath) | ||
| os.Setenv("PATH", stubDir+string(os.PathListSeparator)+origPath) | ||
|
|
||
| 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("Patch succeeds with kubectl kustomize option", func(t *testing.T) { | ||
| // Create a stub kubectl so the test is self-contained and always runs in CI. | ||
| stubDir := t.TempDir() | ||
| writeStubKubectl(t, stubDir) | ||
|
|
||
| origPath := os.Getenv("PATH") | ||
| defer os.Setenv("PATH", origPath) | ||
| os.Setenv("PATH", stubDir+string(os.PathListSeparator)+origPath) | ||
|
|
||
| tempDir := t.TempDir() | ||
|
|
||
| // Write a minimal manifest file that Patch() will reference. | ||
| manifestPath := filepath.Join(tempDir, "templates", "deploy.yaml") | ||
| require.NoError(t, os.MkdirAll(filepath.Dir(manifestPath), 0755)) | ||
| require.NoError(t, os.WriteFile(manifestPath, []byte(`apiVersion: apps/v1 | ||
| kind: Deployment | ||
| metadata: | ||
| name: test | ||
| `), 0644)) | ||
|
|
||
| r := New(KustomizeBin("kubectl kustomize")) | ||
| err := r.Patch(tempDir, []string{manifestPath}, &PatchOpts{}) | ||
| require.NoError(t, err) | ||
| }) | ||
|
|
||
| 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 Chartify values files or kustomize build options is not supported when using 'kubectl kustomize'") | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -43,6 +43,10 @@ func (r *Runner) Patch(tempDir string, generatedManifestFiles []string, opts ... | |||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Resolve the kustomize binary once so PATH lookups are not repeated for every check. | ||||||||||||
| bin := r.kustomizeBin() | ||||||||||||
| usingKubectl := bin == "kubectl kustomize" | ||||||||||||
|
|
||||||||||||
| r.Logf("patching files: %v", generatedManifestFiles) | ||||||||||||
|
|
||||||||||||
| // Detect if CRDs originally came from templates/ directory | ||||||||||||
|
|
@@ -181,19 +185,26 @@ resources: | |||||||||||
|
|
||||||||||||
| renderedFileName := "all.patched.yaml" | ||||||||||||
| renderedFile := filepath.Join(tempDir, renderedFileName) | ||||||||||||
| r.Logf("Generating %s", renderedFile) | ||||||||||||
| r.Logf("Generating %s", renderedFileName) | ||||||||||||
|
|
||||||||||||
| kustomizeArgs := []string{"--output", renderedFile} | ||||||||||||
|
|
||||||||||||
| kustomizeArgs := []string{"build", tempDir, "--output", renderedFile} | ||||||||||||
| if !usingKubectl { | ||||||||||||
| kustomizeArgs = append([]string{"build", tempDir}, kustomizeArgs...) | ||||||||||||
|
||||||||||||
| kustomizeArgs = append([]string{"build", tempDir}, kustomizeArgs...) | |
| kustomizeArgs = append([]string{"build", tempDir}, kustomizeArgs...) | |
| } else { | |
| // When using kubectl kustomize, explicitly pass tempDir as the target directory. | |
| kustomizeArgs = append([]string{tempDir}, kustomizeArgs...) |
Copilot
AI
Apr 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new branch changes the argument shape for the kustomize invocation when kubectl kustomize is used. There aren’t tests covering the Patch() execution path, so regressions in argument ordering/flags for the kubectl case could slip through. Consider adding a unit test that stubs Runner.RunCommand and asserts the exact binary/args used for both kustomize and kubectl kustomize.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message leaks the internal struct/field name
kustomizeOpts.Images, which isn’t meaningful to callers. Consider rewording to reference the user-facing input (e.g., images configured via values files / KustomizeBuildOpts) and what to do instead when usingkubectl kustomize.