Skip to content

Commit 11ea0c1

Browse files
committed
feat: support stringData in secretGenerator
1 parent ef57ad8 commit 11ea0c1

File tree

9 files changed

+107
-111
lines changed

9 files changed

+107
-111
lines changed

api/internal/generators/secret.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,36 @@
44
package generators
55

66
import (
7+
"fmt"
8+
79
"sigs.k8s.io/kustomize/api/ifc"
810
"sigs.k8s.io/kustomize/api/types"
911
"sigs.k8s.io/kustomize/kyaml/yaml"
1012
)
1113

1214
// MakeSecret makes a kubernetes Secret.
1315
//
14-
// Secret: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#secret-v1-core
16+
// Secret: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/secret-v1/
1517
//
1618
// ConfigMaps and Secrets are similar.
1719
//
1820
// Like a ConfigMap, a Secret has a `data` field, but unlike a ConfigMap it has
19-
// no `binaryData` field.
21+
// no `binaryData` field. Secret also provides a `stringData` field.
2022
//
21-
// All of a Secret's data is assumed to be opaque in nature, and assumed to be
23+
// A Secret's `data` is assumed to be opaque in nature, and assumed to be
2224
// base64 encoded from its original representation, regardless of whether the
2325
// original data was UTF-8 text or binary.
2426
//
2527
// This encoding provides no secrecy. It's just a neutral, common means to
2628
// represent opaque text and binary data. Beneath the base64 encoding
2729
// is presumably further encoding under control of the Secret's consumer.
2830
//
31+
// A Secret's `stringData` field is similar to ConfigMap's `data` field.
32+
// `stringData` allows specifying non-binary, UTF-8 secret data in string form.
33+
// It is provided as a write-only input field for convenience.
34+
// All keys and values are merged into the data field on write, overwriting any
35+
// existing values. The stringData field is never output when reading from the API.
36+
//
2937
// A Secret has string field `type` which holds an identifier, used by the
3038
// client, to choose the algorithm to interpret the `data` field. Kubernetes
3139
// cannot make use of this data; it's up to a controller or some pod's service
@@ -50,8 +58,14 @@ func MakeSecret(
5058
if err != nil {
5159
return nil, err
5260
}
53-
if err = rn.LoadMapIntoSecretData(m); err != nil {
54-
return nil, err
61+
if args.StringData {
62+
if err = rn.LoadMapIntoSecretStringData(m); err != nil {
63+
return nil, fmt.Errorf("Failed to load map into Secret stringData: %w", err)
64+
}
65+
} else {
66+
if err = rn.LoadMapIntoSecretData(m); err != nil {
67+
return nil, fmt.Errorf("Failed to load map into Secret data: %w", err)
68+
}
5569
}
5670
copyLabelsAndAnnotations(rn, args.Options)
5771
setImmutable(rn, args.Options)

api/krusty/secrets_test.go

Lines changed: 11 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,18 @@ type: Opaque
236236
`)
237237
}
238238

239-
var binaryHunter2 = []byte{
240-
0xff, // non-utf8
241-
0x68, // h
242-
0x75, // u
243-
0x6e, // n
244-
0x74, // t
245-
0x65, // e
246-
0x72, // r
247-
0x32, // 2
248-
}
249-
250239
func manyHunter2s(count int) (result []byte) {
240+
binaryHunter2 := []byte{
241+
0xff, // non-utf8
242+
0x68, // h
243+
0x75, // u
244+
0x6e, // n
245+
0x74, // t
246+
0x65, // e
247+
0x72, // r
248+
0x32, // 2
249+
}
250+
251251
for i := 0; i < count; i++ {
252252
result = append(result, binaryHunter2...)
253253
}
@@ -358,100 +358,6 @@ type: Opaque
358358
`)
359359
}
360360

361-
func secretGeneratorMergeNamePrefix(t *testing.T) {
362-
th := kusttest_test.MakeHarness(t)
363-
th.WriteK("base", `
364-
secretGenerator:
365-
- name: cm
366-
behavior: create
367-
literals:
368-
- foo=bar
369-
`)
370-
th.WriteK("o1", `
371-
resources:
372-
- ../base
373-
namePrefix: o1-
374-
`)
375-
th.WriteK("o2", `
376-
resources:
377-
- ../base
378-
nameSuffix: -o2
379-
`)
380-
th.WriteK(".", `
381-
resources:
382-
- o1
383-
- o2
384-
secretGenerator:
385-
- name: o1-cm
386-
behavior: merge
387-
literals:
388-
- big=bang
389-
- name: cm-o2
390-
behavior: merge
391-
literals:
392-
- big=crunch
393-
`)
394-
m := th.Run(".", th.MakeDefaultOptions())
395-
th.AssertActualEqualsExpected(m, `
396-
apiVersion: v1
397-
data:
398-
big: bang
399-
foo: bar
400-
kind: Secret
401-
metadata:
402-
name: o1-cm-ft9mmdc8c6
403-
type: Opaque
404-
---
405-
apiVersion: v1
406-
data:
407-
big: crunch
408-
foo: bar
409-
kind: Secret
410-
metadata:
411-
name: cm-o2-5k95kd76ft
412-
type: Opaque
413-
`)
414-
}
415-
416-
func secretGeneratorLiteralNewline(t *testing.T) {
417-
th := kusttest_test.MakeHarness(t)
418-
th.WriteK(".", `
419-
generators:
420-
- secrets.yaml
421-
`)
422-
th.WriteF("secrets.yaml", `
423-
apiVersion: builtin
424-
kind: SecretGenerator
425-
metadata:
426-
name: testing
427-
type: Opaque
428-
literals:
429-
- |
430-
initial.txt=greetings
431-
everyone
432-
- |
433-
final.txt=different
434-
behavior
435-
---
436-
`)
437-
m := th.Run(".", th.MakeDefaultOptions())
438-
th.AssertActualEqualsExpected(
439-
m, `
440-
apiVersion: v1
441-
data:
442-
final.txt: |
443-
different
444-
behavior
445-
initial.txt: |
446-
greetings
447-
everyone
448-
kind: Secret
449-
metadata:
450-
name: testing-tt4769fb52
451-
type: Opaque
452-
`)
453-
}
454-
455361
// regression test for https://github.com/kubernetes-sigs/kustomize/issues/4233
456362
func TestSecretDataEndsWithQuotes(t *testing.T) {
457363
th := kusttest_test.MakeHarness(t)

api/resmap/reswrangler.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,7 @@ func (m *resWrangler) appendReplaceOrMerge(res *resource.Resource) error {
591591
res.CopyMergeMetaDataFieldsFrom(old)
592592
res.MergeDataMapFrom(old)
593593
res.MergeBinaryDataMapFrom(old)
594+
res.MergeStringDataMapFrom(old)
594595
if orig != nil {
595596
res.SetOrigin(orig)
596597
}

api/resource/resource.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func (r *Resource) DeepCopy() *Resource {
157157
// the resource.
158158
// TODO: move to RNode, use GetMeta to improve performance.
159159
// TODO: make a version of mergeStringMaps that is build-annotation aware
160-
// to avoid repeatedly setting refby and genargs annotations
160+
// to avoid repeatedly setting refby and genargs annotations
161161
// Must remove the kustomize bit at the end.
162162
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) error {
163163
if err := r.SetLabels(
@@ -197,6 +197,10 @@ func (r *Resource) MergeBinaryDataMapFrom(o *Resource) {
197197
r.SetBinaryDataMap(mergeStringMaps(o.GetBinaryDataMap(), r.GetBinaryDataMap()))
198198
}
199199

200+
func (r *Resource) MergeStringDataMapFrom(o *Resource) {
201+
r.SetStringDataMap(mergeStringMaps(o.GetStringDataMap(), r.GetStringDataMap()))
202+
}
203+
200204
func (r *Resource) ErrIfNotEquals(o *Resource) error {
201205
meYaml, err := r.AsYAML()
202206
if err != nil {

api/types/secretargs.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ type SecretArgs struct {
1616
// If type is "kubernetes.io/tls", then "literals" or "files" must have exactly two
1717
// keys: "tls.key" and "tls.crt"
1818
Type string `json:"type,omitempty" yaml:"type,omitempty"`
19+
20+
// StringData if true generates a v1/Secret with plain-text stringData fields
21+
// instead of base64-encoded data fields. If a generating field does not have a
22+
// UTF-8 value, it falls back to being stored as base64-encoded data field. This
23+
// is similar to the default binaryData fallback of a ConfigMapGenerator.
24+
StringData bool `json:"stringData,omitempty" yaml:"stringData,omitempty"`
1925
}

kyaml/yaml/const.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const (
2424
MetadataField = "metadata"
2525
DataField = "data"
2626
BinaryDataField = "binaryData"
27+
StringDataField = "stringData"
2728
NameField = "name"
2829
NamespaceField = "namespace"
2930
LabelsField = "labels"

kyaml/yaml/datamap.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,35 @@ func makeSecretValueRNode(s string) *RNode {
9595
return NewRNode(yN)
9696
}
9797

98+
func (rn *RNode) LoadMapIntoSecretStringData(m map[string]string) error {
99+
for _, k := range SortedMapKeys(m) {
100+
fldName, vrN := makeSecretStringValueRNode(m[k])
101+
if _, err := rn.Pipe(
102+
LookupCreate(MappingNode, fldName),
103+
SetField(k, vrN)); err != nil {
104+
return err
105+
}
106+
}
107+
return nil
108+
}
109+
110+
// valid UTF-8 strings can be stringData, but fallback to Base64 for non UTF-8
111+
func makeSecretStringValueRNode(s string) (field string, rN *RNode) {
112+
yN := &Node{Kind: ScalarNode}
113+
yN.Tag = NodeTagString
114+
if utf8.ValidString(s) {
115+
field = StringDataField
116+
yN.Value = s
117+
} else {
118+
field = DataField
119+
yN.Value = encodeBase64(s)
120+
}
121+
if strings.Contains(yN.Value, "\n") {
122+
yN.Style = LiteralStyle
123+
}
124+
return field, NewRNode(yN)
125+
}
126+
98127
// encodeBase64 encodes s as base64 that is broken up into multiple lines
99128
// as appropriate for the resulting length.
100129
func encodeBase64(s string) string {

kyaml/yaml/rnode.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,19 @@ func (rn *RNode) GetBinaryDataMap() map[string]string {
632632
return result
633633
}
634634

635+
func (rn *RNode) GetStringDataMap() map[string]string {
636+
n, err := rn.Pipe(Lookup(StringDataField))
637+
if err != nil {
638+
return nil
639+
}
640+
result := map[string]string{}
641+
_ = n.VisitFields(func(node *MapNode) error {
642+
result[GetValue(node.Key)] = GetValue(node.Value)
643+
return nil
644+
})
645+
return result
646+
}
647+
635648
// GetValidatedDataMap retrieves the data map and returns an error if the data
636649
// map contains entries which are not included in the expectedKeys set.
637650
func (rn *RNode) GetValidatedDataMap(expectedKeys []string) (map[string]string, error) {
@@ -688,6 +701,21 @@ func (rn *RNode) SetBinaryDataMap(m map[string]string) {
688701
}
689702
}
690703

704+
func (rn *RNode) SetStringDataMap(m map[string]string) {
705+
if rn == nil {
706+
log.Fatal("cannot set stringData map on nil Rnode")
707+
}
708+
if err := rn.PipeE(Clear(StringDataField)); err != nil {
709+
log.Fatal(err)
710+
}
711+
if len(m) == 0 {
712+
return
713+
}
714+
if err := rn.LoadMapIntoSecretStringData(m); err != nil {
715+
log.Fatal(err)
716+
}
717+
}
718+
691719
// AppendToFieldPath appends a field name to the FieldPath.
692720
func (rn *RNode) AppendToFieldPath(parts ...string) {
693721
rn.fieldPath = append(rn.fieldPath, parts...)

site/content/en/docs/Reference/API/included/secretargs.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ _build:
1616
* **type** (string), optional
1717

1818
Type of the secret. Must be `Opaque` or `kubernetes.io/tls`. Defaults to `Opaque`.
19+
20+
* **stringData** (bool), optional
21+
22+
stringData if true generates a v1/Secret with plain-text stringData fields
23+
instead of base64-encoded data fields. If a generating field does not have a
24+
UTF-8 value, it falls back to being stored as base64-encoded data field. This
25+
is similar to the default binaryData fallback of a configMapGenerator.

0 commit comments

Comments
 (0)