mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-09-15 12:18:57 +00:00
fix: preserve Secret stringData during generator merge (#6237)
* test: reproduce secretGenerator stringData merge failure * fix: preserve Secret stringData during generator merge
This commit is contained in:
@@ -453,6 +453,42 @@ metadata:
|
||||
`)
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/kubernetes-sigs/kustomize/issues/5955
|
||||
func TestSecretGeneratorMergeStringData(t *testing.T) {
|
||||
th := kusttest_test.MakeHarness(t)
|
||||
th.WriteK(".", `
|
||||
resources:
|
||||
- secret.yaml
|
||||
secretGenerator:
|
||||
- name: test
|
||||
behavior: merge
|
||||
literals:
|
||||
- property2=value2
|
||||
options:
|
||||
disableNameSuffixHash: false
|
||||
`)
|
||||
th.WriteF("secret.yaml", `
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
type: Opaque
|
||||
stringData:
|
||||
property1: value1
|
||||
`)
|
||||
m := th.Run(".", th.MakeDefaultOptions())
|
||||
th.AssertActualEqualsExpected(m, `
|
||||
apiVersion: v1
|
||||
data:
|
||||
property1: dmFsdWUx
|
||||
property2: dmFsdWUy
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
type: Opaque
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMergeAndReplaceDisableNameSuffixHashGenerators(t *testing.T) {
|
||||
th := kusttest_test.MakeHarness(t)
|
||||
th.WriteK("app", `
|
||||
|
||||
@@ -588,8 +588,15 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldForData := old
|
||||
if res.GetApiVersion() == "v1" && res.GetKind() == "Secret" {
|
||||
oldForData, err = normalizeSecretStringData(old)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to normalize stringData for %s: %w", id, err)
|
||||
}
|
||||
}
|
||||
res.CopyMergeMetaDataFieldsFrom(old)
|
||||
res.MergeDataMapFrom(old)
|
||||
res.MergeDataMapFrom(oldForData)
|
||||
res.MergeBinaryDataMapFrom(old)
|
||||
if orig != nil {
|
||||
res.SetOrigin(orig)
|
||||
@@ -614,6 +621,38 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error {
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeSecretStringData returns a copy of res with stringData encoded into
|
||||
// data, matching the normalization performed by the Kubernetes API server.
|
||||
// Values in stringData take precedence over values with the same key in data.
|
||||
func normalizeSecretStringData(res *resource.Resource) (*resource.Resource, error) {
|
||||
normalized := res.DeepCopy()
|
||||
stringData, err := normalized.Pipe(kyaml.Lookup("stringData"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lookup stringData: %w", err)
|
||||
}
|
||||
if !kyaml.IsMissingOrNull(stringData) {
|
||||
values := map[string]string{}
|
||||
if err := stringData.VisitFields(func(node *kyaml.MapNode) error {
|
||||
key := kyaml.GetValue(node.Key)
|
||||
value := node.Value
|
||||
if value == nil || value.YNode() == nil || value.YNode().Kind != kyaml.ScalarNode {
|
||||
return fmt.Errorf("stringData value for key %q must be a scalar", key)
|
||||
}
|
||||
values[key] = kyaml.GetValue(value)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("read stringData: %w", err)
|
||||
}
|
||||
if err := normalized.LoadMapIntoSecretData(values); err != nil {
|
||||
return nil, fmt.Errorf("encode stringData: %w", err)
|
||||
}
|
||||
}
|
||||
if err := normalized.PipeE(kyaml.Clear("stringData")); err != nil {
|
||||
return nil, fmt.Errorf("clear stringData: %w", err)
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// AnnotateAll implements ResMap
|
||||
func (m *resWrangler) AnnotateAll(key string, value string) error {
|
||||
return m.ApplyFilter(annotations.Filter{
|
||||
|
||||
@@ -990,6 +990,144 @@ func TestAbsorbAll(t *testing.T) {
|
||||
t, strings.Contains(err.Error(), "behavior must be merge or replace"))
|
||||
}
|
||||
|
||||
func TestAbsorbAllMergeSecretStringData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existing string
|
||||
incoming string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "core v1 Secret normalizes stringData",
|
||||
existing: `
|
||||
apiVersion: v1
|
||||
data:
|
||||
dataOnly: ZGF0YQ==
|
||||
generatorWins: b2xkLWRhdGE=
|
||||
stringWins: b2xkLWRhdGE=
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
stringData:
|
||||
generatorWins: string
|
||||
long: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
||||
multiline: |
|
||||
line one
|
||||
line two
|
||||
stringOnly: string
|
||||
stringWins: string
|
||||
type: Opaque
|
||||
`,
|
||||
incoming: `
|
||||
apiVersion: v1
|
||||
data:
|
||||
generatedOnly: Z2VuZXJhdGVk
|
||||
generatorWins: Z2VuZXJhdGVk
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
type: Opaque
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
data:
|
||||
dataOnly: ZGF0YQ==
|
||||
generatedOnly: Z2VuZXJhdGVk
|
||||
generatorWins: Z2VuZXJhdGVk
|
||||
long: |
|
||||
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWj
|
||||
AxMjM0NTY3ODk=
|
||||
multiline: bGluZSBvbmUKbGluZSB0d28K
|
||||
stringOnly: c3RyaW5n
|
||||
stringWins: c3RyaW5n
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
type: Opaque
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "custom resource does not normalize stringData",
|
||||
existing: `
|
||||
apiVersion: example.com/v1
|
||||
data:
|
||||
oldOnly: b2xk
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
stringData:
|
||||
customOnly: plain
|
||||
`,
|
||||
incoming: `
|
||||
apiVersion: example.com/v1
|
||||
data:
|
||||
generatedOnly: Z2VuZXJhdGVk
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
data:
|
||||
generatedOnly: Z2VuZXJhdGVk
|
||||
oldOnly: b2xk
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
existing, err := rmF.NewResMapFromBytes([]byte(tc.existing))
|
||||
require.NoError(t, err)
|
||||
incoming, err := rmF.NewResMapFromBytes([]byte(tc.incoming))
|
||||
require.NoError(t, err)
|
||||
incoming.Resources()[0].SetBehavior(types.BehaviorMerge)
|
||||
expected, err := rmF.NewResMapFromBytes([]byte(tc.expected))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, existing.AbsorbAll(incoming))
|
||||
existing.RemoveBuildAnnotations()
|
||||
require.NoError(t, expected.ErrorIfNotEqualLists(existing))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbsorbAllRejectsNonScalarSecretStringData(t *testing.T) {
|
||||
existing, err := rmF.NewResMapFromBytes([]byte(`
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
stringData:
|
||||
invalid:
|
||||
nested: value
|
||||
type: Opaque
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
incoming, err := rmF.NewResMapFromBytes([]byte(`
|
||||
apiVersion: v1
|
||||
data:
|
||||
generated: Z2VuZXJhdGVk
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: test
|
||||
type: Opaque
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
incoming.Resources()[0].SetBehavior(types.BehaviorMerge)
|
||||
before, err := existing.AsYaml()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = existing.AbsorbAll(incoming)
|
||||
require.ErrorContains(t, err, `stringData value for key "invalid" must be a scalar`)
|
||||
after, yamlErr := existing.AsYaml()
|
||||
require.NoError(t, yamlErr)
|
||||
assert.Equal(t, before, after)
|
||||
}
|
||||
|
||||
func TestToRNodeSlice(t *testing.T) {
|
||||
input := `apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
|
||||
Reference in New Issue
Block a user