kustomize icon indicating copy to clipboard operation
kustomize copied to clipboard

Configuration override does not work when resources include subdirectories

Open TLDMain opened this issue 2 months ago • 5 comments

What happened?

Override configuration for transformers does not work when resources include “folders” (for example ./sub).

After resource accumulation (kusttarget.go#L201), if resources come from subdirectories, the resulting ResAccumulator contains a non-empty configuration.
Even if the configuration in the subdirectory is not explicitly overridden, it still holds a default (but non-empty) config, which causes broader FieldSpec entries not to be replaced by narrower ones during merge kusttarget.go#L210.

Example:

# configuration in root directory
namePrefix:
- path: metadata/name
  kind: Deployment
# nameprefix fieldspecs from subdirectory with no configurations
- path: metadata/name
---
# nameprefix fieldpecs from root directory
- path: metadata/name
  kind: Deployment
---
# result
- path: metadata/name
- path: metadata/name
  kind: Deployment

What did you expect to happen?

Configuration overrides should be performed even if there are folders in resources.

How can we reproduce it (as minimally and precisely as possible)?

# ./folder/kustomization.yaml
resources:
- deployment.yaml
- secret.yaml
# ./folder/deployment.yaml
apiVersion: apps/v1
metadata:
  name: deployment1
kind: Deployment
# ./folder/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: secret
# kustomization.yaml
namePrefix: foo-
resources:
- ./folder
configurations:
- cfg.yaml
# cfg.yaml
namePrefix:
- path: metadata/name
  kind: Deployment

Test case like this

func TestConfigurationsOverrideDefaultSubfolder(t *testing.T) {
	th := kusttest_test.MakeHarness(t)
	th.WriteK("/merge-config-subfolder/sub", `
resources:
  - deployment.yaml
  - secret.yaml
`)
	th.WriteF("/merge-config-subfolder/sub/deployment.yaml", `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment1
`)
	th.WriteF("/merge-config-subfolder/sub/secret.yaml", `
apiVersion: v1
kind: Secret
metadata:
  name: secret
`)
	th.WriteK("/merge-config-subfolder", `
namePrefix: foo-
resources:
  - sub
configurations:
  - name-prefix-rules.yaml
`)
	th.WriteF("/merge-config-subfolder/name-prefix-rules.yaml", `
namePrefix:
- path: metadata/name
  kind: Deployment
`)

	pvd := provider.NewDefaultDepProvider()
	resFactory := pvd.GetResourceFactory()

	name0 := "deployment1"
	r0, err0 := resFactory.FromMapWithName(name0, map[string]interface{}{
		"apiVersion": "apps/v1",
		"kind":       "Deployment",
		"metadata": map[string]interface{}{
			"name": "foo-deployment1",
		},
	})
	if err0 != nil {
		t.Fatalf("failed to get instance with given name %v: %v", name0, err0)
	}
	name1 := "secret1"
	r1, err1 := resFactory.FromMapWithName(name1, map[string]interface{}{
		"apiVersion": "v1",
		"kind":       "Secret",
		"metadata": map[string]interface{}{
			"name": "secret1",
		},
	})
	if err1 != nil {
		t.Fatalf("failed to get instance with given name %v: %v", name1, err1)
	}

	var resources = []*resource.Resource{r0, r1}
	expected := resmap.New()
	for _, r := range resources {
		err := expected.Append(r)
		require.NoError(t, err)
	}
	expected.RemoveBuildAnnotations()
	expYaml, err := expected.AsYaml()
	require.NoError(t, err)

	kt := makeKustTargetWithRf(t, th.GetFSys(), "/merge-config-subfolder", pvd)
	require.NoError(t, kt.Load())
	actual, err := kt.MakeCustomizedResMap()
	require.NoError(t, err)
	actual.RemoveBuildAnnotations()
	actYaml, err := actual.AsYaml()
	require.NoError(t, err)
	require.Equal(t, string(expYaml), string(actYaml))
}

Result:

 Diff:
 --- Expected
 +++ Actual
 @@ -3,3 +3,3 @@
 metadata:
 -  name: foo-deployment1
 +  name: foo-foo-deployment1
 ---
 @@ -8,3 +8,3 @@
 metadata:
 -  name: secret1
 +  name: foo-secret

Because of this same bug, the prefix was added to deployment twice more.

Expected output

apiVersion: v1
kind: Secret
metadata:
  name: secret 
---
apiVersion: apps/v1
metadata:
  name: foo-deployment1
kind: Deployment

Actual output

apiVersion: v1
kind: Secret
metadata:
  name: foo-secret # <--- changed
---
apiVersion: apps/v1
metadata:
  name: foo-foo-deployment1 # <-- prefix was added twice
kind: Deployment

Kustomize version

5.7.1

Operating system

MacOS

TLDMain avatar Oct 31 '25 14:10 TLDMain

This issue is currently awaiting triage.

SIG CLI takes a lead on issue triage for this repo, but any Kubernetes member can accept issues by applying the triage/accepted label.

The triage/accepted label can be added by org members by writing /triage accepted in a comment.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

k8s-ci-robot avatar Oct 31 '25 14:10 k8s-ci-robot

I propose two solutions:

  1. Add ResAccumulator.MergeConfigLeft — introduce a new function that merges configuration from an this ResAccumulator into a input one (kusttarget.go#L210)
    Like this ra.tConfig = input.Merge(ra.tConfig)

  2. Update FieldSpec.MergeOne logic — modify the merging behavior (fieldspec.go#L81): when a similar FieldSpec already exists and is broader than the new one, replace it with the new (narrower) one instead of ignoring it.

I can make a MR

TLDMain avatar Oct 31 '25 14:10 TLDMain

Override configuration for transformers does not work when resources include “folders” (for example ./sub).

As far as I know, configuration is expected to be appended to the built-in default not overridden, it is also applied to all resources not only folders. Although for this job we can use transformers field.

example of configuration with in-folder and sub-folder resources: (click to expand)

#! /bin/sh

R=root
[ ! -d $R ] || rm -r $R
mk() { mkdir -p ${1%/*}  && echo "$2" >$1 ; }

mk $R/kustomization.yaml '
namePrefix: foo-
resources:
- ./folder
- ./secret0.yaml
- ./deployment0.yaml
configurations:
- cfg.yaml
'

mk $R/cfg.yaml '
namePrefix:
- path: metadata/name
  kind: Deployment
'

mk $R/secret0.yaml '
apiVersion: v1
kind: Secret
metadata:
  name: secret0
'
mk $R/deployment0.yaml '
apiVersion: apps/v1
metadata:
  name: deployment0
kind: Deployment
'


mk $R/folder/kustomization.yaml '
resources:
- deployment1.yaml
- secret1.yaml
'

mk $R/folder/secret1.yaml '
apiVersion: v1
kind: Secret
metadata:
  name: secret1
'
mk $R/folder/deployment1.yaml '
# ./folder/deployment.yaml
apiVersion: apps/v1
metadata:
  name: deployment1
kind: Deployment
'

#---
cat <<EOF > expected.yaml
apiVersion: v1
kind: Secret
metadata:
  name: foo-secret0
---
apiVersion: v1
kind: Secret
metadata:
  name: foo-secret1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-foo-deployment0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-foo-deployment1
EOF
#---
kustomize build --stack-trace $R/ > result.yaml
printf "%-64s%s\n" expected.yaml result.yaml
printf "%130s\n" " " | tr ' ' '-'
diff -W130 -y expected.yaml result.yaml
example using a prefix transformer: (click to expand)


#! /bin/sh

R=root
[ ! -d $R ] || rm -r $R
mk() { mkdir -p ${1%/*}  && echo "$2" >$1 ; }

mk $R/kustomization.yaml '
resources:
- ./folder
- ./secret0.yaml
- ./deployment0.yaml
transformers:
- trfr.yaml
'

mk $R/trfr.yaml '
apiVersion: builtin
kind: PrefixTransformer
metadata:
  name: notImportantHere
prefix: baked-
fieldSpecs:
- path: metadata/name
  kind: Deployment
'

mk $R/secret0.yaml '
apiVersion: v1
kind: Secret
metadata:
  name: secret0
'
mk $R/deployment0.yaml '
apiVersion: apps/v1
metadata:
  name: deployment0
kind: Deployment
'


mk $R/folder/kustomization.yaml '
resources:
- deployment1.yaml
- secret1.yaml
'

mk $R/folder/secret1.yaml '
apiVersion: v1
kind: Secret
metadata:
  name: secret1
'
mk $R/folder/deployment1.yaml '
# ./folder/deployment.yaml
apiVersion: apps/v1
metadata:
  name: deployment1
kind: Deployment
'

#---
cat <<EOF > expected.yaml
apiVersion: v1
kind: Secret
metadata:
  name: secret0
---
apiVersion: v1
kind: Secret
metadata:
  name: secret1
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: baked-deployment0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: baked-deployment1
EOF
#---
kustomize build --stack-trace $R/ > result.yaml
printf "%-64s%s\n" expected.yaml result.yaml
printf "%130s\n" " " | tr ' ' '-'
diff -W130 -y expected.yaml result.yaml

sda399 avatar Nov 03 '25 20:11 sda399

Hello, @sda399!

not overridden

You're mistaken. Fieldspecs are sorted from more specific to less specific, and duplicates are removed on config merging. There's an issue confirming this and a merge request adding a test.

The problem I found was that the configuration was being overriden incorrectly when there was a subdirectory. Even in your first example, namePrefix was applied twice to deployment0 and secret0 due to the same bug.

TLDMain avatar Nov 04 '25 14:11 TLDMain

thanks for this reference: https://github.com/kubernetes-sigs/kustomize/issues/4811 It certainly clarifies the use case you initially described.

sda399 avatar Nov 04 '25 20:11 sda399