diff --git a/chartify.go b/chartify.go index 7ccf85e..bcf4efb 100644 --- a/chartify.go +++ b/chartify.go @@ -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 @@ -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 ) @@ -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, diff --git a/cmd/chartify/main.go b/cmd/chartify/main.go index bcff0be..699db34 100644 --- a/cmd/chartify/main.go +++ b/cmd/chartify/main.go @@ -39,12 +39,14 @@ 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") @@ -52,6 +54,7 @@ func main() { 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() @@ -65,6 +68,7 @@ func main() { opts.DeprecatedAdhocChartDependencies = deps opts.KustomizeBuildArgs = kustomizeBuildArgs + opts.Patches = patches c := chartify.New(chartify.HelmBin("helm")) diff --git a/integration_test.go b/integration_test.go index a6763d7..8b24b9c 100644 --- a/integration_test.go +++ b/integration_test.go @@ -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", diff --git a/kustomize_test.go b/kustomize_test.go index 50ca8fa..881aeb3 100644 --- a/kustomize_test.go +++ b/kustomize_test.go @@ -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") + }) +} diff --git a/patch.go b/patch.go index 13fd638..27ccb8c 100644 --- a/patch.go +++ b/patch.go @@ -17,6 +17,14 @@ type PatchOpts struct { 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 []string // Kustomize alpha plugin enable flag. @@ -82,7 +90,7 @@ resources: kustomizationYamlContent += `- ` + f + "\n" } - if len(u.StrategicMergePatches) > 0 || len(u.JsonPatches) > 0 { + if len(u.StrategicMergePatches) > 0 || len(u.JsonPatches) > 0 || len(u.Patches) > 0 { kustomizationYamlContent += `patches: ` } @@ -165,6 +173,70 @@ resources: kustomizationYamlContent += `- path: ` + path + "\n" } + // handle kustomize unified patches field. + // Each file may contain a single patch document or a list of patch documents. + // External file references via "path:" are copied into tempDir so kustomize can + // access them within its restricted root. + // See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/inlinePatch.md + for i, f := range u.Patches { + fileBytes, err := r.ReadFile(f) + if err != nil { + return err + } + + patchFileDir := filepath.Dir(f) + entries, err := parsePatchDocuments(fileBytes) + if err != nil { + return fmt.Errorf("parsing patches file %s: %w", f, err) + } + + for j, entry := range entries { + // If the entry references an external file via "path:", copy that file + // into tempDir and rewrite the path to be relative to the kustomization root. + if pathStr, ok := entry["path"].(string); ok && pathStr != "" { + resolvedPath, found := r.resolveTransformerPath(pathStr, patchFileDir) + if found { + // Skip directories — the "path" field should reference a file. + // A directory match is likely coincidental; leave it for kustomize to handle. + info, statErr := os.Stat(resolvedPath) + if statErr != nil { + return fmt.Errorf("checking file referenced by patch path %q: %w", pathStr, statErr) + } + if !info.IsDir() { + patchBytes, readErr := r.ReadFile(resolvedPath) + if readErr != nil { + return fmt.Errorf("reading file referenced by patch path %q: %w", pathStr, readErr) + } + destRelPath := filepath.Join("patch-files", fmt.Sprintf("patchfile.%d.%d.yaml", i, j)) + destAbsPath := filepath.Join(tempDir, destRelPath) + if err := os.MkdirAll(filepath.Dir(destAbsPath), 0755); err != nil { + return err + } + if err := r.WriteFile(destAbsPath, patchBytes, 0644); err != nil { + return err + } + r.Logf("Copied patch path reference %q to %q", pathStr, destRelPath) + entry["path"] = destRelPath + } + } + } + + encoded, err := marshalPatchEntry(entry) + if err != nil { + return err + } + + for k, line := range strings.Split(encoded, "\n") { + if k == 0 { + line = "- " + line + } else { + line = " " + line + } + kustomizationYamlContent += line + "\n" + } + } + } + if len(u.Transformers) > 0 { kustomizationYamlContent += `transformers: ` @@ -369,7 +441,7 @@ resources: return fmt.Errorf("writing %s: %w", crdsFile, err) } - removedPathList := append(append([]string{}, ContentDirs...), "strategicmergepatches", "transformer-patch-files", "kustomization.yaml", renderedFileName) + removedPathList := append(append([]string{}, ContentDirs...), "strategicmergepatches", "jsonpatches", "patch-files", "transformer-patch-files", "kustomization.yaml", renderedFileName) for _, f := range removedPathList { d := filepath.Join(tempDir, f) @@ -569,3 +641,79 @@ func (r *Runner) resolveTransformerPath(pathStr string, transformerFileDir strin return pathStr, false } + +// parsePatchDocuments parses YAML content that may be either a single patch document +// (a YAML mapping), a list of patch documents (a YAML sequence), or a multi-document +// YAML stream. It returns a slice of patch entry maps. Each entry is a kustomize +// "patches" field item, e.g.: +// +// patches: +// - patch: |- +// apiVersion: apps/v1 +// kind: Deployment +// target: +// kind: Deployment +// +// See https://github.com/kubernetes-sigs/kustomize/blob/master/examples/inlinePatch.md +func parsePatchDocuments(content []byte) ([]map[string]interface{}, error) { + decoder := yaml.NewDecoder(bytes.NewReader(content)) + + var entries []map[string]interface{} + + for { + var doc interface{} + if err := decoder.Decode(&doc); err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("decoding patches YAML: %w", err) + } + + switch v := doc.(type) { + case map[string]interface{}: + if v == nil { + // Skip null documents (e.g. from empty "---" separators). + continue + } + entries = append(entries, v) + case []interface{}: + if len(v) == 0 { + return nil, fmt.Errorf("patches list is empty") + } + for i, item := range v { + m, ok := item.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("patches list item %d must be a YAML mapping, got %T", i, item) + } + entries = append(entries, m) + } + case nil: + // Skip null documents. + continue + default: + return nil, fmt.Errorf("patches file must contain YAML mappings or lists of mappings, got %T", doc) + } + } + + if len(entries) == 0 { + return nil, fmt.Errorf("patches file contains no patch entries") + } + + return entries, nil +} + +// marshalPatchEntry encodes a single kustomize patches entry as YAML suitable for +// embedding as a list item under the "patches:" field of kustomization.yaml. +// The returned string has no leading or trailing whitespace lines. +func marshalPatchEntry(entry map[string]interface{}) (string, error) { + buf := &bytes.Buffer{} + encoder := yaml.NewEncoder(buf) + encoder.SetIndent(2) + if err := encoder.Encode(entry); err != nil { + return "", fmt.Errorf("encoding patch entry: %w", err) + } + if err := encoder.Close(); err != nil { + return "", fmt.Errorf("closing patch encoder: %w", err) + } + return strings.TrimRight(buf.String(), "\n"), nil +} diff --git a/patch_test.go b/patch_test.go index eb61d3b..40f7c1a 100644 --- a/patch_test.go +++ b/patch_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/stretchr/testify/require" ) // TestPatch_PreserveCRDLocation tests that CRDs from templates/crds/ stay in templates/crds/ @@ -266,3 +268,93 @@ spec: expectedCRDDir := filepath.Join(tempDir, "templates", "crds") t.Logf("CRDs would be placed in: %s (original location preserved)", expectedCRDDir) } + +// TestParsePatchDocuments verifies parsing of single-doc and list-doc patch files. +func TestParsePatchDocuments(t *testing.T) { + t.Run("single mapping document", func(t *testing.T) { + content := []byte("patch: |-\n apiVersion: v1\n kind: ConfigMap\ntarget:\n kind: ConfigMap\n") + got, err := parsePatchDocuments(content) + require.NoError(t, err) + require.Len(t, got, 1) + require.Contains(t, got[0], "patch") + require.Contains(t, got[0], "target") + }) + + t.Run("list of mappings", func(t *testing.T) { + content := []byte("- patch: a\n target:\n kind: A\n- patch: b\n target:\n kind: B\n") + got, err := parsePatchDocuments(content) + require.NoError(t, err) + require.Len(t, got, 2) + }) + + t.Run("multi-document YAML stream", func(t *testing.T) { + content := []byte("patch: a\ntarget:\n kind: A\n---\npatch: b\ntarget:\n kind: B\n") + got, err := parsePatchDocuments(content) + require.NoError(t, err) + require.Len(t, got, 2, "both documents should be parsed") + require.Equal(t, "a", got[0]["patch"]) + require.Equal(t, "b", got[1]["patch"]) + }) + + t.Run("multi-document with null separator skipped", func(t *testing.T) { + content := []byte("---\npatch: a\ntarget:\n kind: A\n") + got, err := parsePatchDocuments(content) + require.NoError(t, err) + require.Len(t, got, 1) + }) + + t.Run("list with non-map item returns error", func(t *testing.T) { + content := []byte("- patch: a\n- just a string\n") + _, err := parsePatchDocuments(content) + require.Error(t, err) + require.Contains(t, err.Error(), "must be a YAML mapping") + }) + + t.Run("empty content returns error", func(t *testing.T) { + _, err := parsePatchDocuments([]byte("")) + require.Error(t, err) + }) + + t.Run("scalar content returns error", func(t *testing.T) { + _, err := parsePatchDocuments([]byte("just a string")) + require.Error(t, err) + }) + + t.Run("empty list returns error", func(t *testing.T) { + _, err := parsePatchDocuments([]byte("[]\n")) + require.Error(t, err) + require.Contains(t, err.Error(), "empty") + }) +} + +// TestMarshalPatchEntry verifies YAML encoding of a patch entry. +func TestMarshalPatchEntry(t *testing.T) { + t.Run("preserves inline block scalar patch", func(t *testing.T) { + entry := map[string]interface{}{ + "patch": "apiVersion: v1\nkind: ConfigMap\ndata:\n key: val\n", + "target": map[string]interface{}{ + "kind": "ConfigMap", + }, + } + got, err := marshalPatchEntry(entry) + require.NoError(t, err) + // yaml.v3 uses block scalar style (either | or |- depending on trailing newlines) for multi-line strings. + require.Contains(t, got, "patch: |") + require.Contains(t, got, "apiVersion: v1") + require.Contains(t, got, "kind: ConfigMap") + require.Contains(t, got, "key: val") + require.Contains(t, got, "target:") + }) + + t.Run("preserves path field", func(t *testing.T) { + entry := map[string]interface{}{ + "path": "some-file.yaml", + "target": map[string]interface{}{ + "kind": "Deployment", + }, + } + got, err := marshalPatchEntry(entry) + require.NoError(t, err) + require.Contains(t, got, "path: some-file.yaml") + }) +} diff --git a/tempdir_test.go b/tempdir_test.go index cd39ef0..64938e4 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-7bb8758c6f", + want: "foo-ff86f54bd", }) run(testcase{ release: "foo", chart: "stable/envoy", opts: ChartifyOpts{}, - want: "foo-595bcf5dfd", + want: "foo-8b5d8bf5", }) run(testcase{ release: "bar", chart: "incubator/raw", opts: ChartifyOpts{}, - want: "bar-7759488ffd", + want: "bar-5f9576b4b7", }) run(testcase{ @@ -57,7 +57,7 @@ func TestGenerateID(t *testing.T) { opts: ChartifyOpts{ Namespace: "myns", }, - want: "myns-foo-54c5cbf858", + want: "myns-foo-595c9b47bc", }) for id, n := range ids { diff --git a/testdata/integration/testcases/kube_manifest_yml_with_patches_inline/want b/testdata/integration/testcases/kube_manifest_yml_with_patches_inline/want new file mode 100644 index 0000000..b8b60a4 --- /dev/null +++ b/testdata/integration/testcases/kube_manifest_yml_with_patches_inline/want @@ -0,0 +1,87 @@ +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + baz: PATCHED-INLINE + foo: bar +kind: ConfigMap +metadata: + name: myconfig1 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: bar +kind: ConfigMap +metadata: + name: myconfig2 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + baz: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: baz +kind: ConfigMap +metadata: + name: myconfig3 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + template: + metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + spec: + containers: + - image: nginx:1.16.0 + name: log +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log-test-connection +spec: + containers: + - args: + - myapp-log:80 + command: + - wget + image: busybox + name: wget + restartPolicy: Never diff --git a/testdata/integration/testcases/kube_manifest_yml_with_patches_list/want b/testdata/integration/testcases/kube_manifest_yml_with_patches_list/want new file mode 100644 index 0000000..43e6502 --- /dev/null +++ b/testdata/integration/testcases/kube_manifest_yml_with_patches_list/want @@ -0,0 +1,88 @@ +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + baz: PATCHED-LIST-1 + foo: bar +kind: ConfigMap +metadata: + name: myconfig1 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: bar + qux: PATCHED-LIST-2 +kind: ConfigMap +metadata: + name: myconfig2 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + baz: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: baz +kind: ConfigMap +metadata: + name: myconfig3 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + template: + metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + spec: + containers: + - image: nginx:1.16.0 + name: log +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log-test-connection +spec: + containers: + - args: + - myapp-log:80 + command: + - wget + image: busybox + name: wget + restartPolicy: Never diff --git a/testdata/integration/testcases/kube_manifest_yml_with_patches_path/want b/testdata/integration/testcases/kube_manifest_yml_with_patches_path/want new file mode 100644 index 0000000..1063a48 --- /dev/null +++ b/testdata/integration/testcases/kube_manifest_yml_with_patches_path/want @@ -0,0 +1,86 @@ +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: bar +kind: ConfigMap +metadata: + name: myconfig1 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + bar: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: bar +kind: ConfigMap +metadata: + name: myconfig2 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +data: + baz: | + -----BEGIN CERTIFICATE----- + FOO + -----END CERTIFICATE----- + foo: patched-via-path +kind: ConfigMap +metadata: + name: myconfig3 +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + template: + metadata: + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/name: log + spec: + containers: + - image: nginx:1.16.0 + name: log +--- +# Source: kube_manifest_yml/templates/patched_resources.yaml +apiVersion: v1 +kind: Pod +metadata: + annotations: + helm.sh/hook: test + labels: + app.kubernetes.io/instance: myapp + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: log + app.kubernetes.io/version: 1.16.0 + helm.sh/chart: log-0.1.0 + name: myapp-log-test-connection +spec: + containers: + - args: + - myapp-log:80 + command: + - wget + image: busybox + name: wget + restartPolicy: Never diff --git a/testdata/kube_manifest_patches/external-patch.yaml b/testdata/kube_manifest_patches/external-patch.yaml new file mode 100644 index 0000000..69958e3 --- /dev/null +++ b/testdata/kube_manifest_patches/external-patch.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: myconfig3 +data: + foo: patched-via-path diff --git a/testdata/kube_manifest_patches/inline.yaml b/testdata/kube_manifest_patches/inline.yaml new file mode 100644 index 0000000..0e5aa0c --- /dev/null +++ b/testdata/kube_manifest_patches/inline.yaml @@ -0,0 +1,10 @@ +patch: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: myconfig1 + data: + baz: PATCHED-INLINE +target: + kind: ConfigMap + name: myconfig1 diff --git a/testdata/kube_manifest_patches/list.yaml b/testdata/kube_manifest_patches/list.yaml new file mode 100644 index 0000000..464aa0c --- /dev/null +++ b/testdata/kube_manifest_patches/list.yaml @@ -0,0 +1,17 @@ +- patch: |- + apiVersion: v1 + kind: ConfigMap + metadata: + name: myconfig1 + data: + baz: PATCHED-LIST-1 + target: + kind: ConfigMap + name: myconfig1 +- patch: |- + - op: add + path: /data/qux + value: PATCHED-LIST-2 + target: + kind: ConfigMap + name: myconfig2 diff --git a/testdata/kube_manifest_patches/with-path.yaml b/testdata/kube_manifest_patches/with-path.yaml new file mode 100644 index 0000000..2a86232 --- /dev/null +++ b/testdata/kube_manifest_patches/with-path.yaml @@ -0,0 +1,4 @@ +path: external-patch.yaml +target: + kind: ConfigMap + name: myconfig3