Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion chartify.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ type ChartifyOpts struct {
JsonPatches []string
StrategicMergePatches []string

// Patches is the list of YAML files each defining one or more kustomize "patches" entries.
// Each file may contain either a single patch document or a list of patch documents.
// This leverages kustomize's unified "patches" field which auto-detects whether each
// patch is a Strategic Merge Patch or a JSON Patch, and supports both inline patch
// content (via the "patch:" field) and external file references (via the "path:" field).
// See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/inlinePatch.md
Patches []string

// Transformers is the list of YAML files each defines a Kustomize transformer
// See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead for more information.
Transformers []string
Expand Down Expand Up @@ -465,7 +473,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s

var (
needsNamespaceOverride = overrideNamespace != ""
needsKustomizeBuild = len(u.JsonPatches) > 0 || len(u.StrategicMergePatches) > 0 || len(u.Transformers) > 0
needsKustomizeBuild = len(u.JsonPatches) > 0 || len(u.StrategicMergePatches) > 0 || len(u.Patches) > 0 || len(u.Transformers) > 0
needsInjections = len(u.Injectors) > 0 || len(u.Injects) > 0
)

Expand Down Expand Up @@ -498,6 +506,7 @@ func (r *Runner) Chartify(release, dirOrChart string, opts ...ChartifyOption) (s
patchOpts := &PatchOpts{
JsonPatches: u.JsonPatches,
StrategicMergePatches: u.StrategicMergePatches,
Patches: u.Patches,
Transformers: u.Transformers,
EnableAlphaPlugins: u.EnableKustomizeAlphaPlugins,
SortOptions: u.SortOptions,
Expand Down
4 changes: 4 additions & 0 deletions cmd/chartify/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,22 @@ func main() {
AdhocChartDependencies: nil,
JsonPatches: nil,
StrategicMergePatches: nil,
Patches: nil,
WorkaroundOutputDirIssue: false,
IncludeCRDs: false,
}

deps := stringSlice{}
kustomizeBuildArgs := stringSlice{}
patches := 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.Var(&patches, "patch", "Path to a kustomize unified \"patches:\" entry file. Each file may contain a single patch document or a list of patch documents (inline \"patch:\" content or external \"path:\" reference). See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/inlinePatch.md. Can be specified multiple times.")

flag.Parse()

Expand All @@ -65,6 +68,7 @@ func main() {

opts.DeprecatedAdhocChartDependencies = deps
opts.KustomizeBuildArgs = kustomizeBuildArgs
opts.Patches = patches

c := chartify.New(chartify.HelmBin("helm"))

Expand Down
74 changes: 74 additions & 0 deletions integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,80 @@ func TestIntegration(t *testing.T) {
},
})

// SAVE_SNAPSHOT=1 go1.25 test -run ^TestIntegration/kube_manifest_yml_with_patches_inline$ ./
// Verifies kustomize unified "patches:" field with inline strategic merge patch content.
// See https://github.com/helmfile/chartify/issues/94
runTest(t, integrationTestCase{
description: "kube_manifest_yml_with_patches_inline",
release: "myapp",
chart: "./testdata/kube_manifest_yml",
opts: ChartifyOpts{
AdhocChartDependencies: []ChartDependency{
{
Alias: "log",
Chart: repo + chartSuffix,
Version: "0.1.0",
},
},
Patches: []string{
"./testdata/kube_manifest_patches/inline.yaml",
},
SetFlags: []string{
"--set", "log.enabled=true",
},
},
})

// SAVE_SNAPSHOT=1 go1.25 test -run ^TestIntegration/kube_manifest_yml_with_patches_list$ ./
// Verifies kustomize unified "patches:" field with a list of patch documents (both
// strategic merge and JSON6902) in a single file.
// See https://github.com/helmfile/chartify/issues/94
runTest(t, integrationTestCase{
description: "kube_manifest_yml_with_patches_list",
release: "myapp",
chart: "./testdata/kube_manifest_yml",
opts: ChartifyOpts{
AdhocChartDependencies: []ChartDependency{
{
Alias: "log",
Chart: repo + chartSuffix,
Version: "0.1.0",
},
},
Patches: []string{
"./testdata/kube_manifest_patches/list.yaml",
},
SetFlags: []string{
"--set", "log.enabled=true",
},
},
})

// SAVE_SNAPSHOT=1 go1.25 test -run ^TestIntegration/kube_manifest_yml_with_patches_path$ ./
// Verifies kustomize unified "patches:" field with an external file reference ("path:").
// The referenced file is copied into the chartify temp dir so kustomize can access it.
// See https://github.com/helmfile/chartify/issues/94
runTest(t, integrationTestCase{
description: "kube_manifest_yml_with_patches_path",
release: "myapp",
chart: "./testdata/kube_manifest_yml",
opts: ChartifyOpts{
AdhocChartDependencies: []ChartDependency{
{
Alias: "log",
Chart: repo + chartSuffix,
Version: "0.1.0",
},
},
Patches: []string{
"./testdata/kube_manifest_patches/with-path.yaml",
},
SetFlags: []string{
"--set", "log.enabled=true",
},
},
})

// SAVE_SNAPSHOT=1 go1.25 test -run ^TestIntegration/kube_manifest_transformer_alpha_plugin$ ./
runTest(t, integrationTestCase{
description: "kube_manifest_transformer_alpha_plugin",
Expand Down
116 changes: 116 additions & 0 deletions kustomize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,119 @@ spec:
require.NotContains(t, capturedKustomization, "sortOptions:")
})
}

func TestPatch_Patches(t *testing.T) {
t.Run("inline patch writes patches field with block scalar", func(t *testing.T) {
tempDir := t.TempDir()

templatesDir := filepath.Join(tempDir, "templates")
require.NoError(t, os.MkdirAll(templatesDir, 0755))

manifest := filepath.Join(templatesDir, "deployment.yaml")
require.NoError(t, os.WriteFile(manifest, []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
`), 0644))

patchContent := `patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 3
target:
kind: Deployment
`
patchFile := filepath.Join(tempDir, "inline-patch.yaml")
require.NoError(t, os.WriteFile(patchFile, []byte(patchContent), 0644))

r := New(HelmBin(helm))

var capturedKustomization string
origWriteFile := r.WriteFile
r.WriteFile = func(filename string, data []byte, perm os.FileMode) error {
if strings.HasSuffix(filename, "kustomization.yaml") {
capturedKustomization = string(data)
}
return origWriteFile(filename, data, perm)
}

patchOpts := &PatchOpts{
Patches: []string{patchFile},
}
err := r.Patch(tempDir, []string{manifest}, patchOpts)
require.NoError(t, err)

// Verify the generated kustomization.yaml has the patches: section with correct content.
require.Contains(t, capturedKustomization, "patches:")
require.Contains(t, capturedKustomization, "- patch: |")
require.Contains(t, capturedKustomization, "replicas: 3")
require.Contains(t, capturedKustomization, "target:")
require.Contains(t, capturedKustomization, "kind: Deployment")
// Verify the output file was actually rendered with the patch applied.
output, err := os.ReadFile(filepath.Join(tempDir, "templates", "patched_resources.yaml"))
require.NoError(t, err)
require.Contains(t, string(output), "replicas: 3")
})

t.Run("patch with external path copies file into tempDir", func(t *testing.T) {
tempDir := t.TempDir()

templatesDir := filepath.Join(tempDir, "templates")
require.NoError(t, os.MkdirAll(templatesDir, 0755))

manifest := filepath.Join(templatesDir, "deployment.yaml")
require.NoError(t, os.WriteFile(manifest, []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 1
`), 0644))

// Create an external patch file outside tempDir.
extDir := t.TempDir()
extPatch := filepath.Join(extDir, "external-patch.yaml")
require.NoError(t, os.WriteFile(extPatch, []byte(`apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
spec:
replicas: 5
`), 0644))

// Create the patch entry referencing the external file by relative path.
patchEntry := "path: external-patch.yaml\ntarget:\n kind: Deployment\n"
patchFile := filepath.Join(extDir, "entry.yaml")
require.NoError(t, os.WriteFile(patchFile, []byte(patchEntry), 0644))

r := New(HelmBin(helm))

var capturedKustomization string
origWriteFile := r.WriteFile
r.WriteFile = func(filename string, data []byte, perm os.FileMode) error {
if strings.HasSuffix(filename, "kustomization.yaml") {
capturedKustomization = string(data)
}
return origWriteFile(filename, data, perm)
}

patchOpts := &PatchOpts{
Patches: []string{patchFile},
}
err := r.Patch(tempDir, []string{manifest}, patchOpts)
require.NoError(t, err)

// Verify the path was rewritten to point to the copied location within tempDir.
require.Contains(t, capturedKustomization, "patches:")
require.Contains(t, capturedKustomization, "path: patch-files/patchfile.0.0.yaml")
// Verify the external patch was actually applied (replicas: 5 comes from external-patch.yaml).
output, err := os.ReadFile(filepath.Join(tempDir, "templates", "patched_resources.yaml"))
require.NoError(t, err)
require.Contains(t, string(output), "replicas: 5")
})
}
Loading