Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f167e6c

Browse files
committed
Add --hash flag to kubectl create configmap/secret
Specifying this new flag will automatically hash the configmap/secret contents with sha256 and append the first 40 hex-encoded bits of the hash to the name of the configmap/secret. This is especially useful for workflows that generate configmaps/secrets from files (e.g. --from-file). See this Google doc for more background: https://docs.google.com/document/d/1x1fJ3pGRx20ujR-Y89HUAw8glUL8-ygaztLkkmQeCdU/edit
1 parent dcb1d28 commit f167e6c

11 files changed

Lines changed: 623 additions & 14 deletions

File tree

pkg/kubectl/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ go_library(
141141
"//pkg/credentialprovider:go_default_library",
142142
"//pkg/kubectl/resource:go_default_library",
143143
"//pkg/kubectl/util:go_default_library",
144+
"//pkg/kubectl/util/hash:go_default_library",
144145
"//pkg/kubectl/util/slice:go_default_library",
145146
"//pkg/printers:go_default_library",
146147
"//pkg/printers/internalversion:go_default_library",

pkg/kubectl/cmd/create_configmap.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
7777
cmd.Flags().StringSlice("from-file", []string{}, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used. Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
7878
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
7979
cmd.Flags().String("from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
80+
cmd.Flags().Bool("hash", false, "Append a hash of the configmap contents to its name.")
8081
return cmd
8182
}
8283

@@ -94,6 +95,7 @@ func CreateConfigMap(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
9495
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
9596
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
9697
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
98+
Hash: cmdutil.GetFlagBool(cmd, "hash"),
9799
}
98100
default:
99101
return errUnsupportedGenerator(cmd, generatorName)

pkg/kubectl/cmd/create_secret.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
8989
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
9090
cmd.Flags().String("from-env-file", "", "Specify the path to a file to read lines of key=val pairs to create a secret (i.e. a Docker .env file).")
9191
cmd.Flags().String("type", "", i18n.T("The type of secret to create"))
92+
cmd.Flags().Bool("hash", false, "Append a hash of the secret contents to its name.")
9293
return cmd
9394
}
9495

@@ -107,6 +108,7 @@ func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command
107108
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
108109
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
109110
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
111+
Hash: cmdutil.GetFlagBool(cmd, "hash"),
110112
}
111113
default:
112114
return errUnsupportedGenerator(cmd, generatorName)

pkg/kubectl/configmap.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/runtime"
2727
"k8s.io/apimachinery/pkg/util/validation"
2828
"k8s.io/kubernetes/pkg/api"
29+
"k8s.io/kubernetes/pkg/kubectl/util/hash"
2930
)
3031

3132
// ConfigMapGeneratorV1 supports stable generation of a configMap.
@@ -40,6 +41,8 @@ type ConfigMapGeneratorV1 struct {
4041
LiteralSources []string
4142
// EnvFileSource to derive the configMap from (optional)
4243
EnvFileSource string
44+
// Hash; if true, derive a hash from the ConfigMap data and append it to the name
45+
Hash bool
4346
}
4447

4548
// Ensure it supports the generator pattern that uses parameter injection.
@@ -73,14 +76,6 @@ func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (ru
7376
delegate.LiteralSources = fromLiteralArray
7477
delete(genericParams, "from-literal")
7578
}
76-
params := map[string]string{}
77-
for key, value := range genericParams {
78-
strVal, isString := value.(string)
79-
if !isString {
80-
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
81-
}
82-
params[key] = strVal
83-
}
8479
fromEnvFileString, found := genericParams["from-env-file"]
8580
if found {
8681
fromEnvFile, isString := fromEnvFileString.(string)
@@ -90,8 +85,26 @@ func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (ru
9085
delegate.EnvFileSource = fromEnvFile
9186
delete(genericParams, "from-env-file")
9287
}
88+
hashParam, found := genericParams["hash"]
89+
if found {
90+
hashBool, isBool := hashParam.(bool)
91+
if !isBool {
92+
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
93+
}
94+
delegate.Hash = hashBool
95+
delete(genericParams, "hash")
96+
}
97+
params := map[string]string{}
98+
for key, value := range genericParams {
99+
strVal, isString := value.(string)
100+
if !isString {
101+
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
102+
}
103+
params[key] = strVal
104+
}
93105
delegate.Name = params["name"]
94106
delegate.Type = params["type"]
107+
95108
return delegate.StructuredGenerate()
96109
}
97110

@@ -104,6 +117,7 @@ func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam {
104117
{"from-literal", false},
105118
{"from-env-file", false},
106119
{"force", false},
120+
{"hash", false},
107121
}
108122
}
109123

@@ -130,6 +144,9 @@ func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
130144
return nil, err
131145
}
132146
}
147+
if s.Hash {
148+
configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, hash.ConfigMapHash(configMap))
149+
}
133150
return configMap, nil
134151
}
135152

pkg/kubectl/configmap_test.go

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ func TestConfigMapGenerate(t *testing.T) {
4545
},
4646
expectErr: false,
4747
},
48+
{
49+
params: map[string]interface{}{
50+
"name": "foo",
51+
"hash": true,
52+
},
53+
expected: &api.ConfigMap{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "foo-d4342785ca",
56+
},
57+
Data: map[string]string{},
58+
},
59+
expectErr: false,
60+
},
4861
{
4962
params: map[string]interface{}{
5063
"name": "foo",
@@ -58,6 +71,20 @@ func TestConfigMapGenerate(t *testing.T) {
5871
},
5972
expectErr: false,
6073
},
74+
{
75+
params: map[string]interface{}{
76+
"name": "foo",
77+
"type": "my-type",
78+
"hash": true,
79+
},
80+
expected: &api.ConfigMap{
81+
ObjectMeta: metav1.ObjectMeta{
82+
Name: "foo-d4342785ca",
83+
},
84+
Data: map[string]string{},
85+
},
86+
expectErr: false,
87+
},
6188
{
6289
params: map[string]interface{}{
6390
"name": "foo",
@@ -74,6 +101,23 @@ func TestConfigMapGenerate(t *testing.T) {
74101
},
75102
expectErr: false,
76103
},
104+
{
105+
params: map[string]interface{}{
106+
"name": "foo",
107+
"from-literal": []string{"key1=value1", "key2=value2"},
108+
"hash": true,
109+
},
110+
expected: &api.ConfigMap{
111+
ObjectMeta: metav1.ObjectMeta{
112+
Name: "foo-eed7d5dc3c",
113+
},
114+
Data: map[string]string{
115+
"key1": "value1",
116+
"key2": "value2",
117+
},
118+
},
119+
expectErr: false,
120+
},
77121
{
78122
params: map[string]interface{}{
79123
"name": "foo",
@@ -110,6 +154,22 @@ func TestConfigMapGenerate(t *testing.T) {
110154
},
111155
expectErr: false,
112156
},
157+
{
158+
params: map[string]interface{}{
159+
"name": "foo",
160+
"from-literal": []string{"key1==value1"},
161+
"hash": true,
162+
},
163+
expected: &api.ConfigMap{
164+
ObjectMeta: metav1.ObjectMeta{
165+
Name: "foo-829679f6d0",
166+
},
167+
Data: map[string]string{
168+
"key1": "=value1",
169+
},
170+
},
171+
expectErr: false,
172+
},
113173
{
114174
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
115175
params: map[string]interface{}{
@@ -127,6 +187,24 @@ func TestConfigMapGenerate(t *testing.T) {
127187
},
128188
expectErr: false,
129189
},
190+
{
191+
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
192+
params: map[string]interface{}{
193+
"name": "valid_env",
194+
"from-env-file": "file.env",
195+
"hash": true,
196+
},
197+
expected: &api.ConfigMap{
198+
ObjectMeta: metav1.ObjectMeta{
199+
Name: "valid_env-eed7d5dc3c",
200+
},
201+
Data: map[string]string{
202+
"key1": "value1",
203+
"key2": "value2",
204+
},
205+
},
206+
expectErr: false,
207+
},
130208
{
131209
setup: func() func(t *testing.T, params map[string]interface{}) func() {
132210
os.Setenv("g_key1", "1")
@@ -148,6 +226,28 @@ func TestConfigMapGenerate(t *testing.T) {
148226
},
149227
expectErr: false,
150228
},
229+
{
230+
setup: func() func(t *testing.T, params map[string]interface{}) func() {
231+
os.Setenv("g_key1", "1")
232+
os.Setenv("g_key2", "2")
233+
return setupEnvFile("g_key1", "g_key2=")
234+
}(),
235+
params: map[string]interface{}{
236+
"name": "getenv",
237+
"from-env-file": "file.env",
238+
"hash": true,
239+
},
240+
expected: &api.ConfigMap{
241+
ObjectMeta: metav1.ObjectMeta{
242+
Name: "getenv-a1a1ebc3a8",
243+
},
244+
Data: map[string]string{
245+
"g_key1": "1",
246+
"g_key2": "",
247+
},
248+
},
249+
expectErr: false,
250+
},
151251
{
152252
params: map[string]interface{}{
153253
"name": "too_many_args",
@@ -180,23 +280,40 @@ func TestConfigMapGenerate(t *testing.T) {
180280
},
181281
expectErr: false,
182282
},
283+
{
284+
setup: setupEnvFile(" key1= value1"),
285+
params: map[string]interface{}{
286+
"name": "with_spaces",
287+
"from-env-file": "file.env",
288+
"hash": true,
289+
},
290+
expected: &api.ConfigMap{
291+
ObjectMeta: metav1.ObjectMeta{
292+
Name: "with_spaces-c687c5a7b6",
293+
},
294+
Data: map[string]string{
295+
"key1": " value1",
296+
},
297+
},
298+
expectErr: false,
299+
},
183300
}
184301
generator := ConfigMapGeneratorV1{}
185-
for _, test := range tests {
302+
for i, test := range tests {
186303
if test.setup != nil {
187304
if teardown := test.setup(t, test.params); teardown != nil {
188305
defer teardown()
189306
}
190307
}
191308
obj, err := generator.Generate(test.params)
192309
if !test.expectErr && err != nil {
193-
t.Errorf("unexpected error: %v", err)
310+
t.Errorf("case %d, unexpected error: %v", i, err)
194311
}
195312
if test.expectErr && err != nil {
196313
continue
197314
}
198315
if !reflect.DeepEqual(obj.(*api.ConfigMap), test.expected) {
199-
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ConfigMap))
316+
t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*api.ConfigMap))
200317
}
201318
}
202319
}

pkg/kubectl/secret.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/runtime"
2727
"k8s.io/apimachinery/pkg/util/validation"
2828
"k8s.io/kubernetes/pkg/api"
29+
"k8s.io/kubernetes/pkg/kubectl/util/hash"
2930
)
3031

3132
// SecretGeneratorV1 supports stable generation of an opaque secret
@@ -40,6 +41,8 @@ type SecretGeneratorV1 struct {
4041
LiteralSources []string
4142
// EnvFileSource to derive the secret from (optional)
4243
EnvFileSource string
44+
// Hash; if true, derive a hash from the Secret data and type and append it to the name
45+
Hash bool
4346
}
4447

4548
// Ensure it supports the generator pattern that uses parameter injection
@@ -82,6 +85,17 @@ func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runti
8285
delegate.EnvFileSource = fromEnvFile
8386
delete(genericParams, "from-env-file")
8487
}
88+
89+
hashParam, found := genericParams["hash"]
90+
if found {
91+
hashBool, isBool := hashParam.(bool)
92+
if !isBool {
93+
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
94+
}
95+
delegate.Hash = hashBool
96+
delete(genericParams, "hash")
97+
}
98+
8599
params := map[string]string{}
86100
for key, value := range genericParams {
87101
strVal, isString := value.(string)
@@ -92,6 +106,7 @@ func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runti
92106
}
93107
delegate.Name = params["name"]
94108
delegate.Type = params["type"]
109+
95110
return delegate.StructuredGenerate()
96111
}
97112

@@ -104,6 +119,7 @@ func (s SecretGeneratorV1) ParamNames() []GeneratorParam {
104119
{"from-literal", false},
105120
{"from-env-file", false},
106121
{"force", false},
122+
{"hash", false},
107123
}
108124
}
109125

@@ -133,6 +149,9 @@ func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) {
133149
return nil, err
134150
}
135151
}
152+
if s.Hash {
153+
secret.Name = fmt.Sprintf("%s-%s", secret.Name, hash.SecretHash(secret))
154+
}
136155
return secret, nil
137156
}
138157

0 commit comments

Comments
 (0)