From c9528cac028de7df3c301271023752754ff01115 Mon Sep 17 00:00:00 2001 From: Natasha Sarkar Date: Thu, 27 May 2021 13:55:08 -0700 Subject: [PATCH] fix replacement with '.' issue --- api/filters/replacement/replacement.go | 26 ++++- api/filters/replacement/replacement_test.go | 114 +++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/api/filters/replacement/replacement.go b/api/filters/replacement/replacement.go index 23cb4f0115b..d3f4628d3b1 100644 --- a/api/filters/replacement/replacement.go +++ b/api/filters/replacement/replacement.go @@ -66,7 +66,7 @@ func rejectId(rejects []*types.Selector, id *resid.ResId) bool { func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error { for _, fp := range target.FieldPaths { - fieldPath := strings.Split(fp, ".") + fieldPath := getFieldPath(fp) var t *yaml.RNode var err error if target.Options != nil && target.Options.Create { @@ -178,3 +178,27 @@ func makeResId(n *yaml.RNode) *resid.ResId { Namespace: n.GetNamespace(), } } + +func getFieldPath(fieldPath string) []string { + var result []string + split := strings.Split(fieldPath, ".") + + for i := 0; i < len(split); i=i+1 { + elem := split[i] + if strings.HasPrefix(elem, "[") && !strings.HasSuffix(elem, "]") { + // continue until we find the matching "]" + sequenceIndex := []string{elem} + for i < len(split)-1 { + i = i + 1 + sequenceIndex = append(sequenceIndex, split[i]) + if strings.HasSuffix(split[i], "]") { + break + } + } + result = append(result, strings.Join(sequenceIndex, ".")) + } else { + result = append(result, elem) + } + } + return result +} diff --git a/api/filters/replacement/replacement_test.go b/api/filters/replacement/replacement_test.go index 986fbf76c77..f73edf844eb 100644 --- a/api/filters/replacement/replacement_test.go +++ b/api/filters/replacement/replacement_test.go @@ -1338,6 +1338,73 @@ spec: `, expectedErr: "delimiter option can only be used with scalar nodes", }, + "list index contains '.' character": { + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: source +data: + value: example +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: some-secret +spec: + backendType: secretsManager + data: + - key: some-prefix-replaceme + name: .first + version: latest + property: first + - key: some-prefix-replaceme + name: second + version: latest + property: second +`, + replacements: `replacements: +- source: + kind: ConfigMap + version: v1 + name: source + fieldPath: data.value + targets: + - select: + group: kubernetes-client.io + version: v1 + kind: ExternalSecret + name: some-secret + fieldPaths: + - spec.data.[name=.first].key + - spec.data.[name=second].key + options: + delimiter: "-" + index: 2 +`, + expected: `apiVersion: v1 +kind: ConfigMap +metadata: + name: source +data: + value: example +--- +apiVersion: kubernetes-client.io/v1 +kind: ExternalSecret +metadata: + name: some-secret +spec: + backendType: secretsManager + data: + - key: some-prefix-example + name: .first + version: latest + property: first + - key: some-prefix-example + name: second + version: latest + property: second`, + }, + "multiple field paths in target": { input: `apiVersion: v1 kind: ConfigMap @@ -1429,7 +1496,7 @@ spec: t.Errorf("unexpected error: %s\n", err.Error()) t.FailNow() } - if !assert.Equal(t, tc.expectedErr, err.Error()) { + if !assert.Contains(t, err.Error(), tc.expectedErr) { t.FailNow() } } @@ -1439,3 +1506,48 @@ spec: }) } } + +func TestGetFieldPath(t *testing.T) { + testCases := map[string]struct { + input string + expected []string + }{ + "simple": { + input: "spec.replicas", + expected: []string{"spec", "replicas"}, + }, + "sequence": { + input: "spec.data.[name=first].key", + expected: []string{"spec", "data", "[name=first]", "key"}, + }, + "key, value with . prefix": { + input: "spec.data.[.name=.first].key", + expected: []string{"spec", "data", "[.name=.first]", "key"}, + }, + "key, value with . suffix": { + input: "spec.data.[name.=first.].key", + expected: []string{"spec", "data", "[name.=first.]", "key"}, + }, + "multiple '.' in value": { + input: "spec.data.[name=f.i.r.s.t.].key", + expected: []string{"spec", "data", "[name=f.i.r.s.t.]", "key"}, + }, + } + for tn, tc := range testCases { + t.Run(tn, func(t *testing.T) { + assert.True(t, stringSliceEquals(tc.expected, getFieldPath(tc.input))) + }) + } +} + +func stringSliceEquals(a []string, b []string) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +}