mirror of https://github.com/k3s-io/k3s
Merge pull request #49961 from mtaufen/kubectl-hash
Automatic merge from submit-queue (batch tested with PRs 49961, 50005, 50738, 51045, 49927) Add --append-hash flag to kubectl create configmap/secret **What this PR does / why we need it**: 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 **Release note**: ```release-note Adds --append-hash flag to kubectl create configmap/secret, which will append a short hash of the configmap/secret contents to the name during creation. ```pull/6/head
commit
2cf5118abb
|
@ -133,6 +133,7 @@ go_library(
|
|||
"//pkg/credentialprovider:go_default_library",
|
||||
"//pkg/kubectl/resource:go_default_library",
|
||||
"//pkg/kubectl/util:go_default_library",
|
||||
"//pkg/kubectl/util/hash:go_default_library",
|
||||
"//pkg/kubectl/util/slice:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//pkg/printers/internalversion:go_default_library",
|
||||
|
|
|
@ -291,7 +291,7 @@ func RunCreateSubcommand(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, o
|
|||
}
|
||||
|
||||
if useShortOutput := options.OutputFormat == "name"; useShortOutput || len(options.OutputFormat) == 0 {
|
||||
cmdutil.PrintSuccess(mapper, useShortOutput, out, mapping.Resource, options.Name, options.DryRun, "created")
|
||||
cmdutil.PrintSuccess(mapper, useShortOutput, out, mapping.Resource, info.Name, options.DryRun, "created")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ func NewCmdCreateConfigMap(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
|
|||
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.")
|
||||
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
|
||||
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).")
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the configmap to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -94,6 +95,7 @@ func CreateConfigMap(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
|
|||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
|
||||
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
|
|
|
@ -89,6 +89,7 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer) *cobra.Comma
|
|||
cmd.Flags().StringArray("from-literal", []string{}, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
|
||||
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).")
|
||||
cmd.Flags().String("type", "", i18n.T("The type of secret to create"))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -107,6 +108,7 @@ func CreateSecretGeneric(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command
|
|||
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
|
||||
LiteralSources: cmdutil.GetFlagStringArray(cmd, "from-literal"),
|
||||
EnvFileSource: cmdutil.GetFlagString(cmd, "from-env-file"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
|
@ -163,6 +165,7 @@ func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer) *cobr
|
|||
cmd.MarkFlagRequired("docker-password")
|
||||
cmd.Flags().String("docker-email", "", i18n.T("Email for Docker registry"))
|
||||
cmd.Flags().String("docker-server", "https://index.docker.io/v1/", i18n.T("Server location for Docker registry"))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
cmdutil.AddInclude3rdPartyFlags(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
@ -183,11 +186,12 @@ func CreateSecretDockerRegistry(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.
|
|||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.SecretForDockerRegistryV1GeneratorName:
|
||||
generator = &kubectl.SecretForDockerRegistryGeneratorV1{
|
||||
Name: name,
|
||||
Username: cmdutil.GetFlagString(cmd, "docker-username"),
|
||||
Email: cmdutil.GetFlagString(cmd, "docker-email"),
|
||||
Password: cmdutil.GetFlagString(cmd, "docker-password"),
|
||||
Server: cmdutil.GetFlagString(cmd, "docker-server"),
|
||||
Name: name,
|
||||
Username: cmdutil.GetFlagString(cmd, "docker-username"),
|
||||
Email: cmdutil.GetFlagString(cmd, "docker-email"),
|
||||
Password: cmdutil.GetFlagString(cmd, "docker-password"),
|
||||
Server: cmdutil.GetFlagString(cmd, "docker-server"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
|
@ -229,6 +233,7 @@ func NewCmdCreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command {
|
|||
cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForTLSV1GeneratorName)
|
||||
cmd.Flags().String("cert", "", i18n.T("Path to PEM encoded public key certificate."))
|
||||
cmd.Flags().String("key", "", i18n.T("Path to private key associated with given certificate."))
|
||||
cmd.Flags().Bool("append-hash", false, "Append a hash of the secret to its name.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -248,9 +253,10 @@ func CreateSecretTLS(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, ar
|
|||
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
|
||||
case cmdutil.SecretForTLSV1GeneratorName:
|
||||
generator = &kubectl.SecretForTLSGeneratorV1{
|
||||
Name: name,
|
||||
Key: cmdutil.GetFlagString(cmd, "key"),
|
||||
Cert: cmdutil.GetFlagString(cmd, "cert"),
|
||||
Name: name,
|
||||
Key: cmdutil.GetFlagString(cmd, "key"),
|
||||
Cert: cmdutil.GetFlagString(cmd, "cert"),
|
||||
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
|
||||
}
|
||||
default:
|
||||
return errUnsupportedGenerator(cmd, generatorName)
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/hash"
|
||||
)
|
||||
|
||||
// ConfigMapGeneratorV1 supports stable generation of a configMap.
|
||||
|
@ -40,6 +41,8 @@ type ConfigMapGeneratorV1 struct {
|
|||
LiteralSources []string
|
||||
// EnvFileSource to derive the configMap from (optional)
|
||||
EnvFileSource string
|
||||
// AppendHash; if true, derive a hash from the ConfigMap and append it to the name
|
||||
AppendHash bool
|
||||
}
|
||||
|
||||
// Ensure it supports the generator pattern that uses parameter injection.
|
||||
|
@ -73,14 +76,6 @@ func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (ru
|
|||
delegate.LiteralSources = fromLiteralArray
|
||||
delete(genericParams, "from-literal")
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, value := range genericParams {
|
||||
strVal, isString := value.(string)
|
||||
if !isString {
|
||||
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
|
||||
}
|
||||
params[key] = strVal
|
||||
}
|
||||
fromEnvFileString, found := genericParams["from-env-file"]
|
||||
if found {
|
||||
fromEnvFile, isString := fromEnvFileString.(string)
|
||||
|
@ -90,8 +85,26 @@ func (s ConfigMapGeneratorV1) Generate(genericParams map[string]interface{}) (ru
|
|||
delegate.EnvFileSource = fromEnvFile
|
||||
delete(genericParams, "from-env-file")
|
||||
}
|
||||
hashParam, found := genericParams["append-hash"]
|
||||
if found {
|
||||
hashBool, isBool := hashParam.(bool)
|
||||
if !isBool {
|
||||
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
|
||||
}
|
||||
delegate.AppendHash = hashBool
|
||||
delete(genericParams, "append-hash")
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, value := range genericParams {
|
||||
strVal, isString := value.(string)
|
||||
if !isString {
|
||||
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
|
||||
}
|
||||
params[key] = strVal
|
||||
}
|
||||
delegate.Name = params["name"]
|
||||
delegate.Type = params["type"]
|
||||
|
||||
return delegate.StructuredGenerate()
|
||||
}
|
||||
|
||||
|
@ -104,6 +117,7 @@ func (s ConfigMapGeneratorV1) ParamNames() []GeneratorParam {
|
|||
{"from-literal", false},
|
||||
{"from-env-file", false},
|
||||
{"force", false},
|
||||
{"hash", false},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,6 +144,13 @@ func (s ConfigMapGeneratorV1) StructuredGenerate() (runtime.Object, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if s.AppendHash {
|
||||
h, err := hash.ConfigMapHash(configMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, h)
|
||||
}
|
||||
return configMap, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -45,6 +45,19 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-867km9574f",
|
||||
},
|
||||
Data: map[string]string{},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -58,6 +71,20 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"type": "my-type",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-867km9574f",
|
||||
},
|
||||
Data: map[string]string{},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -74,6 +101,23 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"from-literal": []string{"key1=value1", "key2=value2"},
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-gcb75dd9gb",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -110,6 +154,22 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"from-literal": []string{"key1==value1"},
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-bdgk9ttt7m",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "=value1",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
|
||||
params: map[string]interface{}{
|
||||
|
@ -127,6 +187,24 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
|
||||
params: map[string]interface{}{
|
||||
"name": "valid_env",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid_env-2cgh8552ch",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: func() func(t *testing.T, params map[string]interface{}) func() {
|
||||
os.Setenv("g_key1", "1")
|
||||
|
@ -148,6 +226,28 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: func() func(t *testing.T, params map[string]interface{}) func() {
|
||||
os.Setenv("g_key1", "1")
|
||||
os.Setenv("g_key2", "2")
|
||||
return setupEnvFile("g_key1", "g_key2=")
|
||||
}(),
|
||||
params: map[string]interface{}{
|
||||
"name": "getenv",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "getenv-b4hh92hgdk",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"g_key1": "1",
|
||||
"g_key2": "",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "too_many_args",
|
||||
|
@ -180,9 +280,26 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile(" key1= value1"),
|
||||
params: map[string]interface{}{
|
||||
"name": "with_spaces",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "with_spaces-bfc558b4ct",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": " value1",
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
generator := ConfigMapGeneratorV1{}
|
||||
for _, test := range tests {
|
||||
for i, test := range tests {
|
||||
if test.setup != nil {
|
||||
if teardown := test.setup(t, test.params); teardown != nil {
|
||||
defer teardown()
|
||||
|
@ -190,13 +307,13 @@ func TestConfigMapGenerate(t *testing.T) {
|
|||
}
|
||||
obj, err := generator.Generate(test.params)
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Errorf("case %d, unexpected error: %v", i, err)
|
||||
}
|
||||
if test.expectErr && err != nil {
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(obj.(*api.ConfigMap), test.expected) {
|
||||
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.ConfigMap))
|
||||
t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*api.ConfigMap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/hash"
|
||||
)
|
||||
|
||||
// SecretGeneratorV1 supports stable generation of an opaque secret
|
||||
|
@ -40,6 +41,8 @@ type SecretGeneratorV1 struct {
|
|||
LiteralSources []string
|
||||
// EnvFileSource to derive the secret from (optional)
|
||||
EnvFileSource string
|
||||
// AppendHash; if true, derive a hash from the Secret data and type and append it to the name
|
||||
AppendHash bool
|
||||
}
|
||||
|
||||
// Ensure it supports the generator pattern that uses parameter injection
|
||||
|
@ -82,6 +85,17 @@ func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runti
|
|||
delegate.EnvFileSource = fromEnvFile
|
||||
delete(genericParams, "from-env-file")
|
||||
}
|
||||
|
||||
hashParam, found := genericParams["append-hash"]
|
||||
if found {
|
||||
hashBool, isBool := hashParam.(bool)
|
||||
if !isBool {
|
||||
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
|
||||
}
|
||||
delegate.AppendHash = hashBool
|
||||
delete(genericParams, "append-hash")
|
||||
}
|
||||
|
||||
params := map[string]string{}
|
||||
for key, value := range genericParams {
|
||||
strVal, isString := value.(string)
|
||||
|
@ -92,6 +106,7 @@ func (s SecretGeneratorV1) Generate(genericParams map[string]interface{}) (runti
|
|||
}
|
||||
delegate.Name = params["name"]
|
||||
delegate.Type = params["type"]
|
||||
|
||||
return delegate.StructuredGenerate()
|
||||
}
|
||||
|
||||
|
@ -104,6 +119,7 @@ func (s SecretGeneratorV1) ParamNames() []GeneratorParam {
|
|||
{"from-literal", false},
|
||||
{"from-env-file", false},
|
||||
{"force", false},
|
||||
{"append-hash", false},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +149,13 @@ func (s SecretGeneratorV1) StructuredGenerate() (runtime.Object, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if s.AppendHash {
|
||||
h, err := hash.SecretHash(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/hash"
|
||||
)
|
||||
|
||||
// SecretForDockerRegistryGeneratorV1 supports stable generation of a docker registry secret
|
||||
|
@ -37,6 +38,8 @@ type SecretForDockerRegistryGeneratorV1 struct {
|
|||
Password string
|
||||
// Server for registry (required)
|
||||
Server string
|
||||
// AppendHash; if true, derive a hash from the Secret and append it to the name
|
||||
AppendHash bool
|
||||
}
|
||||
|
||||
// Ensure it supports the generator pattern that uses parameter injection
|
||||
|
@ -51,6 +54,16 @@ func (s SecretForDockerRegistryGeneratorV1) Generate(genericParams map[string]in
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delegate := &SecretForDockerRegistryGeneratorV1{}
|
||||
hashParam, found := genericParams["append-hash"]
|
||||
if found {
|
||||
hashBool, isBool := hashParam.(bool)
|
||||
if !isBool {
|
||||
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
|
||||
}
|
||||
delegate.AppendHash = hashBool
|
||||
delete(genericParams, "append-hash")
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, value := range genericParams {
|
||||
strVal, isString := value.(string)
|
||||
|
@ -59,13 +72,11 @@ func (s SecretForDockerRegistryGeneratorV1) Generate(genericParams map[string]in
|
|||
}
|
||||
params[key] = strVal
|
||||
}
|
||||
delegate := &SecretForDockerRegistryGeneratorV1{
|
||||
Name: params["name"],
|
||||
Username: params["docker-username"],
|
||||
Email: params["docker-email"],
|
||||
Password: params["docker-password"],
|
||||
Server: params["docker-server"],
|
||||
}
|
||||
delegate.Name = params["name"]
|
||||
delegate.Username = params["docker-username"]
|
||||
delegate.Email = params["docker-email"]
|
||||
delegate.Password = params["docker-password"]
|
||||
delegate.Server = params["docker-server"]
|
||||
return delegate.StructuredGenerate()
|
||||
}
|
||||
|
||||
|
@ -83,6 +94,13 @@ func (s SecretForDockerRegistryGeneratorV1) StructuredGenerate() (runtime.Object
|
|||
secret.Type = api.SecretTypeDockercfg
|
||||
secret.Data = map[string][]byte{}
|
||||
secret.Data[api.DockerConfigKey] = dockercfgContent
|
||||
if s.AppendHash {
|
||||
h, err := hash.SecretHash(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
|
@ -94,6 +112,7 @@ func (s SecretForDockerRegistryGeneratorV1) ParamNames() []GeneratorParam {
|
|||
{"docker-email", false},
|
||||
{"docker-password", true},
|
||||
{"docker-server", true},
|
||||
{"append-hash", false},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,26 @@ func TestSecretForDockerRegistryGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-use-append-hash": {
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"docker-server": server,
|
||||
"docker-username": username,
|
||||
"docker-password": password,
|
||||
"docker-email": email,
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-gb4kftc655",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
api.DockerConfigKey: secretData,
|
||||
},
|
||||
Type: api.SecretTypeDockercfg,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-use-no-email": {
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/kubectl/util/hash"
|
||||
)
|
||||
|
||||
// SecretForTLSGeneratorV1 supports stable generation of a TLS secret.
|
||||
|
@ -33,6 +34,8 @@ type SecretForTLSGeneratorV1 struct {
|
|||
Key string
|
||||
// Cert is the path to the user's public key certificate.
|
||||
Cert string
|
||||
// AppendHash; if true, derive a hash from the Secret and append it to the name
|
||||
AppendHash bool
|
||||
}
|
||||
|
||||
// Ensure it supports the generator pattern that uses parameter injection
|
||||
|
@ -47,6 +50,16 @@ func (s SecretForTLSGeneratorV1) Generate(genericParams map[string]interface{})
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
delegate := &SecretForTLSGeneratorV1{}
|
||||
hashParam, found := genericParams["append-hash"]
|
||||
if found {
|
||||
hashBool, isBool := hashParam.(bool)
|
||||
if !isBool {
|
||||
return nil, fmt.Errorf("expected bool, found :%v", hashParam)
|
||||
}
|
||||
delegate.AppendHash = hashBool
|
||||
delete(genericParams, "append-hash")
|
||||
}
|
||||
params := map[string]string{}
|
||||
for key, value := range genericParams {
|
||||
strVal, isString := value.(string)
|
||||
|
@ -55,11 +68,9 @@ func (s SecretForTLSGeneratorV1) Generate(genericParams map[string]interface{})
|
|||
}
|
||||
params[key] = strVal
|
||||
}
|
||||
delegate := &SecretForTLSGeneratorV1{
|
||||
Name: params["name"],
|
||||
Key: params["key"],
|
||||
Cert: params["cert"],
|
||||
}
|
||||
delegate.Name = params["name"]
|
||||
delegate.Key = params["key"]
|
||||
delegate.Cert = params["cert"]
|
||||
return delegate.StructuredGenerate()
|
||||
}
|
||||
|
||||
|
@ -82,6 +93,13 @@ func (s SecretForTLSGeneratorV1) StructuredGenerate() (runtime.Object, error) {
|
|||
secret.Data = map[string][]byte{}
|
||||
secret.Data[api.TLSCertKey] = []byte(tlsCrt)
|
||||
secret.Data[api.TLSPrivateKeyKey] = []byte(tlsKey)
|
||||
if s.AppendHash {
|
||||
h, err := hash.SecretHash(secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret.Name = fmt.Sprintf("%s-%s", secret.Name, h)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
|
@ -100,6 +118,7 @@ func (s SecretForTLSGeneratorV1) ParamNames() []GeneratorParam {
|
|||
{"name", true},
|
||||
{"key", true},
|
||||
{"cert", true},
|
||||
{"append-hash", false},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,25 @@ func TestSecretForTLSGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-valid-tls-secret-append-hash": {
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"key": validKeyPath,
|
||||
"cert": validCertPath,
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-272h6tt825",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
api.TLSCertKey: []byte(rsaCertPEM),
|
||||
api.TLSPrivateKeyKey: []byte(rsaKeyPEM),
|
||||
},
|
||||
Type: api.SecretTypeTLS,
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
"test-invalid-key-pair": {
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
|
|
@ -44,6 +44,19 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-949tdgdkgg",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -58,6 +71,21 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"type": "my-type",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-dg474f9t76",
|
||||
},
|
||||
Data: map[string][]byte{},
|
||||
Type: "my-type",
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -74,6 +102,23 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"from-literal": []string{"key1=value1", "key2=value2"},
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-tf72c228m4",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key1": []byte("value1"),
|
||||
"key2": []byte("value2"),
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
|
@ -110,6 +155,22 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "foo",
|
||||
"from-literal": []string{"key1==value1"},
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo-fdcc8tkhh5",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key1": []byte("=value1"),
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
|
||||
params: map[string]interface{}{
|
||||
|
@ -127,6 +188,24 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile("key1=value1", "#", "", "key2=value2"),
|
||||
params: map[string]interface{}{
|
||||
"name": "valid_env",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid_env-bkb2m2965h",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key1": []byte("value1"),
|
||||
"key2": []byte("value2"),
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: func() func(t *testing.T, params map[string]interface{}) func() {
|
||||
os.Setenv("g_key1", "1")
|
||||
|
@ -148,6 +227,28 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: func() func(t *testing.T, params map[string]interface{}) func() {
|
||||
os.Setenv("g_key1", "1")
|
||||
os.Setenv("g_key2", "2")
|
||||
return setupEnvFile("g_key1", "g_key2=")
|
||||
}(),
|
||||
params: map[string]interface{}{
|
||||
"name": "getenv",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "getenv-m7kg2khdb4",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"g_key1": []byte("1"),
|
||||
"g_key2": []byte(""),
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
params: map[string]interface{}{
|
||||
"name": "too_many_args",
|
||||
|
@ -180,9 +281,26 @@ func TestSecretGenerate(t *testing.T) {
|
|||
},
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
setup: setupEnvFile(" key1= value1"),
|
||||
params: map[string]interface{}{
|
||||
"name": "with_spaces",
|
||||
"from-env-file": "file.env",
|
||||
"append-hash": true,
|
||||
},
|
||||
expected: &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "with_spaces-4488d5b57d",
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"key1": []byte(" value1"),
|
||||
},
|
||||
},
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
generator := SecretGeneratorV1{}
|
||||
for _, test := range tests {
|
||||
for i, test := range tests {
|
||||
if test.setup != nil {
|
||||
if teardown := test.setup(t, test.params); teardown != nil {
|
||||
defer teardown()
|
||||
|
@ -190,13 +308,14 @@ func TestSecretGenerate(t *testing.T) {
|
|||
}
|
||||
obj, err := generator.Generate(test.params)
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Errorf("case %d, unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if test.expectErr && err != nil {
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(obj.(*api.Secret), test.expected) {
|
||||
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*api.Secret))
|
||||
t.Errorf("\ncase %d, expected:\n%#v\nsaw:\n%#v", i, test.expected, obj.(*api.Secret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ filegroup(
|
|||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/kubectl/util/crlf:all-srcs",
|
||||
"//pkg/kubectl/util/hash:all-srcs",
|
||||
"//pkg/kubectl/util/i18n:all-srcs",
|
||||
"//pkg/kubectl/util/logs:all-srcs",
|
||||
"//pkg/kubectl/util/slice:all-srcs",
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["hash_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/api:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["hash.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/api:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// ConfigMapHash returns a hash of the ConfigMap.
|
||||
// The Data, Kind, and Name are taken into account.
|
||||
func ConfigMapHash(cm *api.ConfigMap) (string, error) {
|
||||
encoded, err := encodeConfigMap(cm)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// SecretHash returns a hash of the Secret.
|
||||
// The Data, Kind, Name, and Type are taken into account.
|
||||
func SecretHash(sec *api.Secret) (string, error) {
|
||||
encoded, err := encodeSecret(sec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
h, err := encodeHash(hash(encoded))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// encodeConfigMap encodes a ConfigMap.
|
||||
// Data, Kind, and Name are taken into account.
|
||||
func encodeConfigMap(cm *api.ConfigMap) (string, error) {
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeSecret encodes a Secret.
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
func encodeSecret(sec *api.Secret) (string, error) {
|
||||
// json.Marshal sorts the keys in a stable order in the encoding
|
||||
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// encodeHash extracts the first 40 bits of the hash from the hex string
|
||||
// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex
|
||||
// characters to consonants to prevent bad words from being formed (the theory
|
||||
// is that no vowels makes it really hard to make bad words). Since the string
|
||||
// is hex, the only vowels it can contain are 'a' and 'e'.
|
||||
// We picked some arbitrary consonants to map to from the same character set as GenerateName.
|
||||
// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75
|
||||
// If the hex string contains fewer than ten characters, returns an error.
|
||||
func encodeHash(hex string) (string, error) {
|
||||
if len(hex) < 10 {
|
||||
return "", fmt.Errorf("the hex string must contain at least 10 characters")
|
||||
}
|
||||
enc := []rune(hex[:10])
|
||||
for i := range enc {
|
||||
switch enc[i] {
|
||||
case '0':
|
||||
enc[i] = 'g'
|
||||
case '1':
|
||||
enc[i] = 'h'
|
||||
case '3':
|
||||
enc[i] = 'k'
|
||||
case 'a':
|
||||
enc[i] = 'm'
|
||||
case 'e':
|
||||
enc[i] = 't'
|
||||
}
|
||||
}
|
||||
return string(enc), nil
|
||||
}
|
||||
|
||||
// hash hashes `data` with sha256 and returns the hex string
|
||||
func hash(data string) string {
|
||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package hash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestConfigMapHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
cm *api.ConfigMap
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", &api.ConfigMap{Data: map[string]string{}}, "42745tchd9", ""},
|
||||
// one key
|
||||
{"one key", &api.ConfigMap{Data: map[string]string{"one": ""}}, "9g67k2htb6", ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", &api.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, "f5h7t85m9b", ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
h, err := ConfigMapHash(c.cm)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretHash(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
secret *api.Secret
|
||||
hash string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", &api.Secret{Type: "my-type", Data: map[string][]byte{}}, "t75bgf6ctb", ""},
|
||||
// one key
|
||||
{"one key", &api.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, "74bd68bm66", ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", &api.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, "dgcb6h9tmk", ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
h, err := SecretHash(c.secret)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if c.hash != h {
|
||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeConfigMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
cm *api.ConfigMap
|
||||
expect string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", &api.ConfigMap{Data: map[string]string{}}, `{"data":{},"kind":"ConfigMap","name":""}`, ""},
|
||||
// one key
|
||||
{"one key", &api.ConfigMap{Data: map[string]string{"one": ""}}, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||
// three keys (tests sorting order)
|
||||
{"three keys", &api.ConfigMap{Data: map[string]string{"two": "2", "one": "", "three": "3"}}, `{"data":{"one":"","three":"3","two":"2"},"kind":"ConfigMap","name":""}`, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
s, err := encodeConfigMap(c.cm)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if s != c.expect {
|
||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeSecret(t *testing.T) {
|
||||
cases := []struct {
|
||||
desc string
|
||||
secret *api.Secret
|
||||
expect string
|
||||
err string
|
||||
}{
|
||||
// empty map
|
||||
{"empty data", &api.Secret{Type: "my-type", Data: map[string][]byte{}}, `{"data":{},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
// one key
|
||||
{"one key", &api.Secret{Type: "my-type", Data: map[string][]byte{"one": []byte("")}}, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
|
||||
{"three keys", &api.Secret{Type: "my-type", Data: map[string][]byte{"two": []byte("2"), "one": []byte(""), "three": []byte("3")}}, `{"data":{"one":"","three":"Mw==","two":"Mg=="},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
s, err := encodeSecret(c.secret)
|
||||
if SkipRest(t, c.desc, err, c.err) {
|
||||
continue
|
||||
}
|
||||
if s != c.expect {
|
||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
// hash the empty string to be sure that sha256 is being used
|
||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
sum := hash("")
|
||||
if expect != sum {
|
||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||
}
|
||||
}
|
||||
|
||||
// warn devs who change types that they might have to update a hash function
|
||||
// not perfect, as it only checks the number of top-level fields
|
||||
func TestTypeStability(t *testing.T) {
|
||||
errfmt := `case %q, expected %d fields but got %d
|
||||
Depending on the field(s) you added, you may need to modify the hash function for this type.
|
||||
To guide you: the hash function targets fields that comprise the contents of objects,
|
||||
not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
||||
`
|
||||
cases := []struct {
|
||||
typeName string
|
||||
obj interface{}
|
||||
expect int
|
||||
}{
|
||||
{"ConfigMap", api.ConfigMap{}, 3},
|
||||
{"Secret", api.Secret{}, 4},
|
||||
}
|
||||
for _, c := range cases {
|
||||
val := reflect.ValueOf(c.obj)
|
||||
if num := val.NumField(); c.expect != num {
|
||||
t.Errorf(errfmt, c.typeName, c.expect, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
||||
// and logs the appropriate error on the test object.
|
||||
// The return value indicates whether we should skip the rest of the test case due to the error result.
|
||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||
if err != nil {
|
||||
if len(contains) == 0 {
|
||||
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
||||
} else if !strings.Contains(err.Error(), contains) {
|
||||
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
|
||||
}
|
||||
return true
|
||||
} else if len(contains) > 0 {
|
||||
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue