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) {
|
func TestMergeAndReplaceDisableNameSuffixHashGenerators(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
th.WriteK("app", `
|
th.WriteK("app", `
|
||||||
|
|||||||
@@ -588,8 +588,15 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.CopyMergeMetaDataFieldsFrom(old)
|
||||||
res.MergeDataMapFrom(old)
|
res.MergeDataMapFrom(oldForData)
|
||||||
res.MergeBinaryDataMapFrom(old)
|
res.MergeBinaryDataMapFrom(old)
|
||||||
if orig != nil {
|
if orig != nil {
|
||||||
res.SetOrigin(orig)
|
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
|
// AnnotateAll implements ResMap
|
||||||
func (m *resWrangler) AnnotateAll(key string, value string) error {
|
func (m *resWrangler) AnnotateAll(key string, value string) error {
|
||||||
return m.ApplyFilter(annotations.Filter{
|
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"))
|
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) {
|
func TestToRNodeSlice(t *testing.T) {
|
||||||
input := `apiVersion: rbac.authorization.k8s.io/v1
|
input := `apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
|
|||||||
Reference in New Issue
Block a user