From daa8e29c5b5c0b7bc549cbef010434028a9e6c25 Mon Sep 17 00:00:00 2001 From: Prashanth Balasubramanian Date: Sun, 24 Apr 2016 12:46:58 -0700 Subject: [PATCH] Add a kubectl create secret tls command --- .generated_docs | 2 + contrib/completions/bash/kubectl | 64 ++++++ .../kubectl-create-secret-docker-registry.1 | 2 +- docs/man/man1/kubectl-create-secret-tls.1 | 200 +++++++++++++++++ docs/man/man1/kubectl-create-secret.1 | 2 +- .../kubectl/kubectl_create_secret.md | 3 +- .../kubectl_create_secret_docker-registry.md | 4 +- .../kubectl/kubectl_create_secret_tls.md | 108 ++++++++++ hack/test-cmd.sh | 12 +- hack/testdata/tls.crt | 19 ++ hack/testdata/tls.key | 28 +++ pkg/kubectl/cmd/create_secret.go | 65 +++++- pkg/kubectl/cmd/util/factory.go | 5 + pkg/kubectl/secret_for_tls.go | 124 +++++++++++ pkg/kubectl/secret_for_tls_test.go | 204 ++++++++++++++++++ 15 files changed, 835 insertions(+), 7 deletions(-) create mode 100644 docs/man/man1/kubectl-create-secret-tls.1 create mode 100644 docs/user-guide/kubectl/kubectl_create_secret_tls.md create mode 100644 hack/testdata/tls.crt create mode 100644 hack/testdata/tls.key create mode 100644 pkg/kubectl/secret_for_tls.go create mode 100644 pkg/kubectl/secret_for_tls_test.go diff --git a/.generated_docs b/.generated_docs index 5a9c70fc2153a..6e67beda756b4 100644 --- a/.generated_docs +++ b/.generated_docs @@ -26,6 +26,7 @@ docs/man/man1/kubectl-create-configmap.1 docs/man/man1/kubectl-create-namespace.1 docs/man/man1/kubectl-create-secret-docker-registry.1 docs/man/man1/kubectl-create-secret-generic.1 +docs/man/man1/kubectl-create-secret-tls.1 docs/man/man1/kubectl-create-secret.1 docs/man/man1/kubectl-create-serviceaccount.1 docs/man/man1/kubectl-create.1 @@ -82,6 +83,7 @@ docs/user-guide/kubectl/kubectl_create_namespace.md docs/user-guide/kubectl/kubectl_create_secret.md docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md docs/user-guide/kubectl/kubectl_create_secret_generic.md +docs/user-guide/kubectl/kubectl_create_secret_tls.md docs/user-guide/kubectl/kubectl_create_serviceaccount.md docs/user-guide/kubectl/kubectl_delete.md docs/user-guide/kubectl/kubectl_describe.md diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 528ae4c6d6a5a..7c032a63600fb 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -763,6 +763,69 @@ _kubectl_create_secret_docker-registry() noun_aliases=() } +_kubectl_create_secret_tls() +{ + last_command="kubectl_create_secret_tls" + commands=() + + flags=() + two_word_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--cert=") + flags+=("--dry-run") + flags+=("--generator=") + flags+=("--key=") + flags+=("--no-headers") + flags+=("--output=") + two_word_flags+=("-o") + flags+=("--output-version=") + flags+=("--save-config") + flags+=("--schema-cache-dir=") + flags_with_completion+=("--schema-cache-dir") + flags_completion+=("_filedir") + flags+=("--show-all") + flags+=("-a") + flags+=("--show-labels") + flags+=("--sort-by=") + flags+=("--template=") + flags_with_completion+=("--template") + flags_completion+=("_filedir") + flags+=("--validate") + flags+=("--alsologtostderr") + flags+=("--api-version=") + flags+=("--as=") + flags+=("--certificate-authority=") + flags+=("--client-certificate=") + flags+=("--client-key=") + flags+=("--cluster=") + flags+=("--context=") + flags+=("--insecure-skip-tls-verify") + flags+=("--kubeconfig=") + flags+=("--log-backtrace-at=") + flags+=("--log-dir=") + flags+=("--log-flush-frequency=") + flags+=("--logtostderr") + flags+=("--match-server-version") + flags+=("--namespace=") + flags_with_completion+=("--namespace") + flags_completion+=("__kubectl_get_namespaces") + flags+=("--password=") + flags+=("--server=") + two_word_flags+=("-s") + flags+=("--stderrthreshold=") + flags+=("--token=") + flags+=("--user=") + flags+=("--username=") + flags+=("--v=") + flags+=("--vmodule=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + _kubectl_create_secret_generic() { last_command="kubectl_create_secret_generic" @@ -832,6 +895,7 @@ _kubectl_create_secret() last_command="kubectl_create_secret" commands=() commands+=("docker-registry") + commands+=("tls") commands+=("generic") flags=() diff --git a/docs/man/man1/kubectl-create-secret-docker-registry.1 b/docs/man/man1/kubectl-create-secret-docker-registry.1 index 32a94ee5fc6fe..643fc7f00f67b 100644 --- a/docs/man/man1/kubectl-create-secret-docker-registry.1 +++ b/docs/man/man1/kubectl-create-secret-docker-registry.1 @@ -208,7 +208,7 @@ by creating a dockercfg secret and attaching it to your service account. .nf # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using: - $ kubectl create secret docker\-registry my\-secret \-\-docker\-server=DOCKER\_REGISTRY\_SERVER \-\-docker\-username=DOCKER\_USER \-\-docker\-password=DOCKER\_PASSWORD \-\-docker\-email=DOCKER\_EMAIL + kubectl create secret docker\-registry my\-secret \-\-docker\-server=DOCKER\_REGISTRY\_SERVER \-\-docker\-username=DOCKER\_USER \-\-docker\-password=DOCKER\_PASSWORD \-\-docker\-email=DOCKER\_EMAIL .fi .RE diff --git a/docs/man/man1/kubectl-create-secret-tls.1 b/docs/man/man1/kubectl-create-secret-tls.1 new file mode 100644 index 0000000000000..75a136e2a0c77 --- /dev/null +++ b/docs/man/man1/kubectl-create-secret-tls.1 @@ -0,0 +1,200 @@ +.TH "KUBERNETES" "1" " kubernetes User Manuals" "Eric Paris" "Jan 2015" "" + + +.SH NAME +.PP +kubectl create secret tls \- Create a TLS secret. + + +.SH SYNOPSIS +.PP +\fBkubectl create secret tls\fP [OPTIONS] + + +.SH DESCRIPTION +.PP +Create a TLS secret from the given public/private key pair. + +.PP +The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key. + + +.SH OPTIONS +.PP +\fB\-\-cert\fP="" + Path to PEM encoded public key certificate. + +.PP +\fB\-\-dry\-run\fP=false + If true, only print the object that would be sent, without sending it. + +.PP +\fB\-\-generator\fP="secret\-for\-tls/v1" + The name of the API generator to use. + +.PP +\fB\-\-key\fP="" + Path to private key associated with given certificate. + +.PP +\fB\-\-no\-headers\fP=false + When using the default output, don't print headers. + +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output format. One of: json|yaml|wide|name|go\-template=...|go\-template\-file=...|jsonpath=...|jsonpath\-file=... See golang template [ +\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]] and jsonpath template [ +\[la]http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md\[ra]]. + +.PP +\fB\-\-output\-version\fP="" + Output the formatted object with the given group version (for ex: 'extensions/v1beta1'). + +.PP +\fB\-\-save\-config\fP=false + If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future. + +.PP +\fB\-\-schema\-cache\-dir\fP="\~/.kube/schema" + If non\-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' + +.PP +\fB\-a\fP, \fB\-\-show\-all\fP=false + When printing, show all resources (default hide terminated pods.) + +.PP +\fB\-\-show\-labels\fP=false + When printing, show all labels as the last column (default hide labels column) + +.PP +\fB\-\-sort\-by\fP="" + If non\-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string. + +.PP +\fB\-\-template\fP="" + Template string or path to template file to use when \-o=go\-template, \-o=go\-template\-file. The template format is golang templates [ +\[la]http://golang.org/pkg/text/template/#pkg-overview\[ra]]. + +.PP +\fB\-\-validate\fP=true + If true, use a schema to validate the input before sending it + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-alsologtostderr\fP=false + log to standard error as well as files + +.PP +\fB\-\-api\-version\fP="" + DEPRECATED: The API version to use when talking to the server + +.PP +\fB\-\-as\fP="" + Username to impersonate for the operation. + +.PP +\fB\-\-certificate\-authority\fP="" + Path to a cert. file for the certificate authority. + +.PP +\fB\-\-client\-certificate\fP="" + Path to a client certificate file for TLS. + +.PP +\fB\-\-client\-key\fP="" + Path to a client key file for TLS. + +.PP +\fB\-\-cluster\fP="" + The name of the kubeconfig cluster to use + +.PP +\fB\-\-context\fP="" + The name of the kubeconfig context to use + +.PP +\fB\-\-insecure\-skip\-tls\-verify\fP=false + If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + +.PP +\fB\-\-kubeconfig\fP="" + Path to the kubeconfig file to use for CLI requests. + +.PP +\fB\-\-log\-backtrace\-at\fP=:0 + when logging hits line file:N, emit a stack trace + +.PP +\fB\-\-log\-dir\fP="" + If non\-empty, write log files in this directory + +.PP +\fB\-\-log\-flush\-frequency\fP=5s + Maximum number of seconds between log flushes + +.PP +\fB\-\-logtostderr\fP=true + log to standard error instead of files + +.PP +\fB\-\-match\-server\-version\fP=false + Require server version to match client version + +.PP +\fB\-\-namespace\fP="" + If present, the namespace scope for this CLI request. + +.PP +\fB\-\-password\fP="" + Password for basic authentication to the API server. + +.PP +\fB\-s\fP, \fB\-\-server\fP="" + The address and port of the Kubernetes API server + +.PP +\fB\-\-stderrthreshold\fP=2 + logs at or above this threshold go to stderr + +.PP +\fB\-\-token\fP="" + Bearer token for authentication to the API server. + +.PP +\fB\-\-user\fP="" + The name of the kubeconfig user to use + +.PP +\fB\-\-username\fP="" + Username for basic authentication to the API server. + +.PP +\fB\-\-v\fP=0 + log level for V logs + +.PP +\fB\-\-vmodule\fP= + comma\-separated list of pattern=N settings for file\-filtered logging + + +.SH EXAMPLE +.PP +.RS + +.nf + # Create a new TLS secret named tls\-secret with the given key pair: + kubectl create secret tls tls\-secret \-\-cert=path/to/tls.cert \-\-key=path/to/tls.key + +.fi +.RE + + +.SH SEE ALSO +.PP +\fBkubectl\-create\-secret(1)\fP, + + +.SH HISTORY +.PP +January 2015, Originally compiled by Eric Paris (eparis at redhat dot com) based on the kubernetes source material, but hopefully they have been automatically generated since! diff --git a/docs/man/man1/kubectl-create-secret.1 b/docs/man/man1/kubectl-create-secret.1 index a353e3b7d675f..e17da7f14a8a2 100644 --- a/docs/man/man1/kubectl-create-secret.1 +++ b/docs/man/man1/kubectl-create-secret.1 @@ -116,7 +116,7 @@ Create a secret using specified subcommand. .SH SEE ALSO .PP -\fBkubectl\-create(1)\fP, \fBkubectl\-create\-secret\-docker\-registry(1)\fP, \fBkubectl\-create\-secret\-generic(1)\fP, +\fBkubectl\-create(1)\fP, \fBkubectl\-create\-secret\-docker\-registry(1)\fP, \fBkubectl\-create\-secret\-tls(1)\fP, \fBkubectl\-create\-secret\-generic(1)\fP, .SH HISTORY diff --git a/docs/user-guide/kubectl/kubectl_create_secret.md b/docs/user-guide/kubectl/kubectl_create_secret.md index 011c5cd349ce2..e8338f0c557a3 100644 --- a/docs/user-guide/kubectl/kubectl_create_secret.md +++ b/docs/user-guide/kubectl/kubectl_create_secret.md @@ -78,8 +78,9 @@ kubectl create secret * [kubectl create](kubectl_create.md) - Create a resource by filename or stdin * [kubectl create secret docker-registry](kubectl_create_secret_docker-registry.md) - Create a secret for use with a Docker registry. * [kubectl create secret generic](kubectl_create_secret_generic.md) - Create a secret from a local file, directory or literal value. +* [kubectl create secret tls](kubectl_create_secret_tls.md) - Create a TLS secret. -###### Auto generated by spf13/cobra on 5-Apr-2016 +###### Auto generated by spf13/cobra on 25-Apr-2016 [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_secret.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md b/docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md index f2c1907d47b54..9637b2003ff97 100644 --- a/docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md +++ b/docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md @@ -61,7 +61,7 @@ kubectl create secret docker-registry NAME --docker-username=user --docker-passw ``` # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using: - $ kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL + kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL ``` ### Options @@ -118,7 +118,7 @@ kubectl create secret docker-registry NAME --docker-username=user --docker-passw * [kubectl create secret](kubectl_create_secret.md) - Create a secret using specified subcommand. -###### Auto generated by spf13/cobra on 5-Apr-2016 +###### Auto generated by spf13/cobra on 16-May-2016 [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_secret_docker-registry.md?pixel)]() diff --git a/docs/user-guide/kubectl/kubectl_create_secret_tls.md b/docs/user-guide/kubectl/kubectl_create_secret_tls.md new file mode 100644 index 0000000000000..d468320ab3c6d --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_secret_tls.md @@ -0,0 +1,108 @@ + + + + +WARNING +WARNING +WARNING +WARNING +WARNING + +

PLEASE NOTE: This document applies to the HEAD of the source tree

+ +If you are using a released version of Kubernetes, you should +refer to the docs that go with that version. + +Documentation for other releases can be found at +[releases.k8s.io](http://releases.k8s.io). + +-- + + + + + +## kubectl create secret tls + +Create a TLS secret. + +### Synopsis + + + +Create a TLS secret from the given public/private key pair. + +The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key. + +``` +kubectl create secret tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run] +``` + +### Examples + +``` + # Create a new TLS secret named tls-secret with the given key pair: + kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key +``` + +### Options + +``` + --cert="": Path to PEM encoded public key certificate. + --dry-run[=false]: If true, only print the object that would be sent, without sending it. + --generator="secret-for-tls/v1": The name of the API generator to use. + --key="": Path to private key associated with given certificate. + --no-headers[=false]: When using the default output, don't print headers. + -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. + --output-version="": Output the formatted object with the given group version (for ex: 'extensions/v1beta1'). + --save-config[=false]: If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future. + --schema-cache-dir="~/.kube/schema": If non-empty, load/store cached API schemas in this directory, default is '$HOME/.kube/schema' + -a, --show-all[=false]: When printing, show all resources (default hide terminated pods.) + --show-labels[=false]: When printing, show all labels as the last column (default hide labels column) + --sort-by="": If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string. + --template="": Template string or path to template file to use when -o=go-template, -o=go-template-file. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]. + --validate[=true]: If true, use a schema to validate the input before sending it +``` + +### Options inherited from parent commands + +``` + --alsologtostderr[=false]: log to standard error as well as files + --as="": Username to impersonate for the operation. + --certificate-authority="": Path to a cert. file for the certificate authority. + --client-certificate="": Path to a client certificate file for TLS. + --client-key="": Path to a client key file for TLS. + --cluster="": The name of the kubeconfig cluster to use + --context="": The name of the kubeconfig context to use + --insecure-skip-tls-verify[=false]: If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure. + --kubeconfig="": Path to the kubeconfig file to use for CLI requests. + --log-backtrace-at=:0: when logging hits line file:N, emit a stack trace + --log-dir="": If non-empty, write log files in this directory + --log-flush-frequency=5s: Maximum number of seconds between log flushes + --logtostderr[=true]: log to standard error instead of files + --match-server-version[=false]: Require server version to match client version + --namespace="": If present, the namespace scope for this CLI request. + --password="": Password for basic authentication to the API server. + -s, --server="": The address and port of the Kubernetes API server + --stderrthreshold=2: logs at or above this threshold go to stderr + --token="": Bearer token for authentication to the API server. + --user="": The name of the kubeconfig user to use + --username="": Username for basic authentication to the API server. + --v=0: log level for V logs + --vmodule=: comma-separated list of pattern=N settings for file-filtered logging +``` + +### SEE ALSO + +* [kubectl create secret](kubectl_create_secret.md) - Create a secret using specified subcommand. + +###### Auto generated by spf13/cobra on 16-May-2016 + + +[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_create_secret_tls.md?pixel)]() + diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 415a820593c86..9b8957fc91a4e 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -1155,6 +1155,16 @@ __EOF__ # Clean-up kubectl delete secret test-secret --namespace=test-secrets + ### Create a tls secret + # Pre-condition: no SECRET exists + kube::test::get_object_assert 'secrets --namespace=test-secrets' "{{range.items}}{{$id_field}}:{{end}}" '' + # Command + kubectl create secret tls test-secret --namespace=test-secrets --key=hack/testdata/tls.key --cert=hack/testdata/tls.crt + kube::test::get_object_assert 'secret/test-secret --namespace=test-secrets' "{{$id_field}}" 'test-secret' + kube::test::get_object_assert 'secret/test-secret --namespace=test-secrets' "{{$secret_type}}" 'kubernetes.io/tls' + # Clean-up + kubectl delete secret test-secret --namespace=test-secrets + ### Create a secret using output flags # Pre-condition: no secret exists kube::test::get_object_assert 'secrets --namespace=test-secrets' "{{range.items}}{{$id_field}}:{{end}}" '' @@ -2009,7 +2019,7 @@ __EOF__ object="all -l'app=cassandra'" request="{{range.items}}{{range .metadata.labels}}{{.}}:{{end}}{{end}}" - # all 4 cassandra's might not be in the request immediately... + # all 4 cassandra's might not be in the request immediately... kube::test::get_object_assert "$object" "$request" 'cassandra:cassandra:cassandra:cassandra:' || \ kube::test::get_object_assert "$object" "$request" 'cassandra:cassandra:cassandra:' || \ kube::test::get_object_assert "$object" "$request" 'cassandra:cassandra:' diff --git a/hack/testdata/tls.crt b/hack/testdata/tls.crt new file mode 100644 index 0000000000000..4e101761abdda --- /dev/null +++ b/hack/testdata/tls.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDKzCCAhOgAwIBAgIJAP6zX1i14mEuMA0GCSqGSIb3DQEBCwUAMCwxFDASBgNV +BAMMC2V4YW1wbGUuY29tMRQwEgYDVQQKDAtleGFtcGxlLmNvbTAeFw0xNjA1MTUw +MDQ0NThaFw0xNzA1MTUwMDQ0NThaMCwxFDASBgNVBAMMC2V4YW1wbGUuY29tMRQw +EgYDVQQKDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL/rrmHnmFYPhXaSJD4H8b0018HDr14GJsSEj29aQ9TKffoY7avcc+1ETrfA +F8WlXjzqez0OhmecBgQloaNU0IKpLm1DIwFanzjejrooGsSAGm510KGKC/K6XfcW +EPUiYWc35XLRqM4n7X9lqLbzMFdZvEJYRurPuzx7ZSw+UF3QJqPiBx3fbIPYHCIe +rMHyh1OhZFRPuX6lLxGoDUGu5C+2MQityhZWUTXvRlh7v4Cmb3oD5a+1VGSjM/Kf +EcXxK4SLDiECHSVdHpvjNnPi1lVXx6/Yel9+95X/rwaILJ4UNuCWp3ixn8iBr+vC +7ttVj8HMUCkfC7aGaZVcudpre5UCAwEAAaNQME4wHQYDVR0OBBYEFMqQZOeB0B/V +pPgBNs23SW2aRZsZMB8GA1UdIwQYMBaAFMqQZOeB0B/VpPgBNs23SW2aRZsZMAwG +A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEtLa1+ovgKHHr3YKUyrbWi2 +SbNmrMrQIdomdNnhQn/3tYl2RG/twUIpi2LaxLC8+n+hhVO9Gy1hy18xQ9bXDKtC +I30dml8qmNqqCpCxTz6jSh7kxiBF8+7lj6B3zoN4JLVDdmp+8iQwecd+rgB5jW8O +c333juUWA/c9QC9VO6yeDZs4EBkD2/a1GRtMkU2nkk7jk4QhMcjSxFKH+7ROC6yN +/+80NcHdDitVt0VRqogWusZDCMeZFwGVrfPVT8iYfP8lP7p6HBkYzvVP6SaMn76P +/tGxy/BYC4mGosiyhdfLBQS/Q1bkvNhu2O6Y9hqWE6tLNy+YIC3A0ON5/xN36dY= +-----END CERTIFICATE----- diff --git a/hack/testdata/tls.key b/hack/testdata/tls.key new file mode 100644 index 0000000000000..8f94973fc664c --- /dev/null +++ b/hack/testdata/tls.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/665h55hWD4V2 +kiQ+B/G9NNfBw69eBibEhI9vWkPUyn36GO2r3HPtRE63wBfFpV486ns9DoZnnAYE +JaGjVNCCqS5tQyMBWp843o66KBrEgBpuddChigvyul33FhD1ImFnN+Vy0ajOJ+1/ +Zai28zBXWbxCWEbqz7s8e2UsPlBd0Caj4gcd32yD2BwiHqzB8odToWRUT7l+pS8R +qA1BruQvtjEIrcoWVlE170ZYe7+Apm96A+WvtVRkozPynxHF8SuEiw4hAh0lXR6b +4zZz4tZVV8ev2HpffveV/68GiCyeFDbglqd4sZ/Iga/rwu7bVY/BzFApHwu2hmmV +XLnaa3uVAgMBAAECggEAG+kvnCdtPR7Wvw6z3J2VJ3oW4qQNzfPBEZVhssUC1mB4 +f7W+Yt8VsOzdMdXq3yCUmvFS6OdC3rCPI21Bm5pLFKV8DgHUhm7idwfO4/3PHsKu +lV/m7odAA5Xc8oEwCCZu2e8EHHWnQgwGex+SsMCfSCTRvyhNb/qz9TDQ3uVVFL9e +9a4OKqZl/GlRspJSuXhy+RSVulw9NjeX1VRjIbhqpdXAmQNXgShA+gZSQh8T/tgv +XQYsMtg+FUDvcunJQf4OW5BY7IenYBV/GvsnJU8L7oD0wjNSAwe/iLKqV/NpYhre +QR4DsGnmoRYlUlHdHFTTJpReDjWm+vH3T756yDdFAQKBgQD2/sP5dM/aEW7Z1TgS +TG4ts1t8Rhe9escHxKZQR81dfOxBeCJMBDm6ySfR8rvyUM4VsogxBL/RhRQXsjJM +7wN08MhdiXG0J5yy/oNo8W6euD8m8Mk1UmqcZjSgV4vA7zQkvkr6DRJdybKsT9mE +jouEwev8sceS6iBpPw/+Ws8z1QKBgQDG6uYHMfMcS844xKQQWhargdN2XBzeG6TV +YXfNFstNpD84d9zIbpG/AKJF8fKrseUhXkJhkDjFGJTriD3QQsntOFaDOrHMnveV +zGzvC4OTFUUFHe0SVJ0HuLf8YCHoZ+DXEeCKCN6zBXnUue+bt3NvLOf2yN5o9kYx +SIa8O1vIwQKBgEdONXWG65qg/ceVbqKZvhUjen3eHmxtTZhIhVsX34nlzq73567a +aXArMnvB/9Bs05IgAIFmRZpPOQW+RBdByVWxTabzTwgbh3mFUJqzWKQpvNGZIf1q +1axhNUA1BfulEwCojyyxKWQ6HoLwanOCU3T4JxDEokEfpku8EPn1bWwhAoGAAN8A +eOGYHfSbB5ac3VF3rfKYmXkXy0U1uJV/r888vq9Mc5PazKnnS33WOBYyKNxTk4zV +H5ZBGWPdKxbipmnUdox7nIGCS9IaZXaKt5VGUzuRnM8fvafPNDxz2dAV9e2Wh3qV +kCUvzHrmqK7TxMvN3pvEvEju6GjDr+2QYXylD0ECgYAGK5r+y+EhtKkYFLeYReUt +znvSsWq+JCQH/cmtZLaVOldCaMRL625hSl3XPPcMIHE14xi3d4njoXWzvzPcg8L6 +vNXk3GiNldACS+vwk4CwEqe5YlZRm5doD07wIdsg2zRlnKsnXNM152OwgmcchDul +rLTt0TTazzwBCgCD0Jkoqg== +-----END PRIVATE KEY----- diff --git a/pkg/kubectl/cmd/create_secret.go b/pkg/kubectl/cmd/create_secret.go index 48cb8f99b08bd..3af308d29fd32 100644 --- a/pkg/kubectl/cmd/create_secret.go +++ b/pkg/kubectl/cmd/create_secret.go @@ -37,6 +37,7 @@ func NewCmdCreateSecret(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { }, } cmd.AddCommand(NewCmdCreateSecretDockerRegistry(f, cmdOut)) + cmd.AddCommand(NewCmdCreateSecretTLS(f, cmdOut)) cmd.AddCommand(NewCmdCreateSecretGeneric(f, cmdOut)) return cmd @@ -130,7 +131,7 @@ nodes to pull images on your behalf, they have to have the credentials. You can by creating a dockercfg secret and attaching it to your service account.` secretForDockerRegistryExample = ` # If you don't already have a .dockercfg file, you can create a dockercfg secret directly by using: - $ kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL` + kubectl create secret docker-registry my-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL` ) // NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries @@ -192,3 +193,65 @@ func CreateSecretDockerRegistry(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra OutputFormat: cmdutil.GetFlagString(cmd, "output"), }) } + +const ( + secretForTLSLong = ` +Create a TLS secret from the given public/private key pair. + +The public/private key pair must exist before hand. The public key certificate must be .PEM encoded and match the given private key.` + + secretForTLSExample = ` # Create a new TLS secret named tls-secret with the given key pair: + kubectl create secret tls tls-secret --cert=path/to/tls.cert --key=path/to/tls.key` +) + +// NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries +func NewCmdCreateSecretTLS(f *cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run]", + Short: "Create a TLS secret.", + Long: secretForTLSLong, + Example: secretForTLSExample, + Run: func(cmd *cobra.Command, args []string) { + err := CreateSecretTLS(f, cmdOut, cmd, args) + cmdutil.CheckErr(err) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmdutil.AddGeneratorFlags(cmd, cmdutil.SecretForTLSV1GeneratorName) + cmd.Flags().String("cert", "", "Path to PEM encoded public key certificate.") + cmd.Flags().String("key", "", "Path to private key associated with given certificate.") + return cmd +} + +// CreateSecretTLS is the implementation of the create secret tls command +func CreateSecretTLS(f *cmdutil.Factory, cmdOut io.Writer, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + requiredFlags := []string{"cert", "key"} + for _, requiredFlag := range requiredFlags { + if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 { + return cmdutil.UsageError(cmd, "flag %s is required", requiredFlag) + } + } + var generator kubectl.StructuredGenerator + 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"), + } + default: + return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName)) + } + return RunCreateSubcommand(f, cmd, cmdOut, &CreateSubcommandOptions{ + Name: name, + StructuredGenerator: generator, + DryRun: cmdutil.GetFlagBool(cmd, "dry-run"), + OutputFormat: cmdutil.GetFlagString(cmd, "output"), + }) +} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 2f2652518660c..4407c44f2760d 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -157,6 +157,7 @@ const ( NamespaceV1GeneratorName = "namespace/v1" SecretV1GeneratorName = "secret/v1" SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" + SecretForTLSV1GeneratorName = "secret-for-tls/v1" ConfigMapV1GeneratorName = "configmap/v1" ) @@ -186,6 +187,10 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator { generators["secret-for-docker-registry"] = map[string]kubectl.Generator{ SecretForDockerRegistryV1GeneratorName: kubectl.SecretForDockerRegistryGeneratorV1{}, } + generators["secret-for-tls"] = map[string]kubectl.Generator{ + SecretForTLSV1GeneratorName: kubectl.SecretForTLSGeneratorV1{}, + } + return generators[cmdName] } diff --git a/pkg/kubectl/secret_for_tls.go b/pkg/kubectl/secret_for_tls.go new file mode 100644 index 0000000000000..05061d25974b2 --- /dev/null +++ b/pkg/kubectl/secret_for_tls.go @@ -0,0 +1,124 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "crypto/tls" + "fmt" + "io/ioutil" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/runtime" +) + +// SecretForTLSGeneratorV1 supports stable generation of a TLS secret. +type SecretForTLSGeneratorV1 struct { + // Name is the name of this TLS secret. + Name string + // Key is the path to the user's private key. + Key string + // Cert is the path to the user's public key certificate. + Cert string +} + +// Ensure it supports the generator pattern that uses parameter injection +var _ Generator = &SecretForTLSGeneratorV1{} + +// Ensure it supports the generator pattern that uses parameters specified during construction +var _ StructuredGenerator = &SecretForTLSGeneratorV1{} + +// Generate returns a secret using the specified parameters +func (s SecretForTLSGeneratorV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) { + err := ValidateParams(s.ParamNames(), genericParams) + if err != nil { + return nil, err + } + 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 := &SecretForTLSGeneratorV1{ + Name: params["name"], + Key: params["key"], + Cert: params["cert"], + } + return delegate.StructuredGenerate() +} + +// StructuredGenerate outputs a secret object using the configured fields +func (s SecretForTLSGeneratorV1) StructuredGenerate() (runtime.Object, error) { + if err := s.validate(); err != nil { + return nil, err + } + tlsCrt, err := readFile(s.Cert) + if err != nil { + return nil, err + } + tlsKey, err := readFile(s.Key) + if err != nil { + return nil, err + } + secret := &api.Secret{} + secret.Name = s.Name + secret.Type = api.SecretTypeTLS + secret.Data = map[string][]byte{} + secret.Data[api.TLSCertKey] = []byte(tlsCrt) + secret.Data[api.TLSPrivateKeyKey] = []byte(tlsKey) + return secret, nil +} + +// readFile just reads a file into a byte array. +func readFile(file string) ([]byte, error) { + b, err := ioutil.ReadFile(file) + if err != nil { + return []byte{}, fmt.Errorf("Cannot read file %v, %v", file, err) + } + return b, nil +} + +// ParamNames returns the set of supported input parameters when using the parameter injection generator pattern +func (s SecretForTLSGeneratorV1) ParamNames() []GeneratorParam { + return []GeneratorParam{ + {"name", true}, + {"key", true}, + {"cert", true}, + } +} + +// validate validates required fields are set to support structured generation +func (s SecretForTLSGeneratorV1) validate() error { + // TODO: This is not strictly necessary. We can generate a self signed cert + // if no key/cert is given. The only requiredment is that we either get both + // or none. See test/e2e/ingress_utils for self signed cert generation. + if len(s.Key) == 0 { + return fmt.Errorf("key must be specified.") + } + if len(s.Cert) == 0 { + return fmt.Errorf("certificate must be specified.") + } + if _, err := tls.LoadX509KeyPair(s.Cert, s.Key); err != nil { + return fmt.Errorf("failed to load key pair %v", err) + } + // TODO: Add more validation. + // 1. If the certificate contains intermediates, it is a valid chain. + // 2. Format etc. + return nil +} diff --git a/pkg/kubectl/secret_for_tls_test.go b/pkg/kubectl/secret_for_tls_test.go new file mode 100644 index 0000000000000..f24403fd22145 --- /dev/null +++ b/pkg/kubectl/secret_for_tls_test.go @@ -0,0 +1,204 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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 kubectl + +import ( + "fmt" + "os" + "path" + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/api" + utiltesting "k8s.io/kubernetes/pkg/util/testing" +) + +var rsaCertPEM = `-----BEGIN CERTIFICATE----- +MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ +hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa +rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv +zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW +r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V +-----END CERTIFICATE----- +` + +var rsaKeyPEM = `-----BEGIN RSA PRIVATE KEY----- +MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo +k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G +6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N +MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW +SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T +xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi +D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g== +-----END RSA PRIVATE KEY----- +` + +const mismatchRSAKeyPEM = `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/665h55hWD4V2 +kiQ+B/G9NNfBw69eBibEhI9vWkPUyn36GO2r3HPtRE63wBfFpV486ns9DoZnnAYE +JaGjVNCCqS5tQyMBWp843o66KBrEgBpuddChigvyul33FhD1ImFnN+Vy0ajOJ+1/ +Zai28zBXWbxCWEbqz7s8e2UsPlBd0Caj4gcd32yD2BwiHqzB8odToWRUT7l+pS8R +qA1BruQvtjEIrcoWVlE170ZYe7+Apm96A+WvtVRkozPynxHF8SuEiw4hAh0lXR6b +4zZz4tZVV8ev2HpffveV/68GiCyeFDbglqd4sZ/Iga/rwu7bVY/BzFApHwu2hmmV +XLnaa3uVAgMBAAECggEAG+kvnCdtPR7Wvw6z3J2VJ3oW4qQNzfPBEZVhssUC1mB4 +f7W+Yt8VsOzdMdXq3yCUmvFS6OdC3rCPI21Bm5pLFKV8DgHUhm7idwfO4/3PHsKu +lV/m7odAA5Xc8oEwCCZu2e8EHHWnQgwGex+SsMCfSCTRvyhNb/qz9TDQ3uVVFL9e +9a4OKqZl/GlRspJSuXhy+RSVulw9NjeX1VRjIbhqpdXAmQNXgShA+gZSQh8T/tgv +XQYsMtg+FUDvcunJQf4OW5BY7IenYBV/GvsnJU8L7oD0wjNSAwe/iLKqV/NpYhre +QR4DsGnmoRYlUlHdHFTTJpReDjWm+vH3T756yDdFAQKBgQD2/sP5dM/aEW7Z1TgS +TG4ts1t8Rhe9escHxKZQR81dfOxBeCJMBDm6ySfR8rvyUM4VsogxBL/RhRQXsjJM +7wN08MhdiXG0J5yy/oNo8W6euD8m8Mk1UmqcZjSgV4vA7zQkvkr6DRJdybKsT9mE +jouEwev8sceS6iBpPw/+Ws8z1QKBgQDG6uYHMfMcS844xKQQWhargdN2XBzeG6TV +YXfNFstNpD84d9zIbpG/AKJF8fKrseUhXkJhkDjFGJTriD3QQsntOFaDOrHMnveV +zGzvC4OTFUUFHe0SVJ0HuLf8YCHoZ+DXEeCKCN6zBXnUue+bt3NvLOf2yN5o9kYx +SIa8O1vIwQKBgEdONXWG65qg/ceVbqKZvhUjen3eHmxtTZhIhVsX34nlzq73567a +aXArMnvB/9Bs05IgAIFmRZpPOQW+RBdByVWxTabzTwgbh3mFUJqzWKQpvNGZIf1q +1axhNUA1BfulEwCojyyxKWQ6HoLwanOCU3T4JxDEokEfpku8EPn1bWwhAoGAAN8A +eOGYHfSbB5ac3VF3rfKYmXkXy0U1uJV/r888vq9Mc5PazKnnS33WOBYyKNxTk4zV +H5ZBGWPdKxbipmnUdox7nIGCS9IaZXaKt5VGUzuRnM8fvafPNDxz2dAV9e2Wh3qV +kCUvzHrmqK7TxMvN3pvEvEju6GjDr+2QYXylD0ECgYAGK5r+y+EhtKkYFLeYReUt +znvSsWq+JCQH/cmtZLaVOldCaMRL625hSl3XPPcMIHE14xi3d4njoXWzvzPcg8L6 +vNXk3GiNldACS+vwk4CwEqe5YlZRm5doD07wIdsg2zRlnKsnXNM152OwgmcchDul +rLTt0TTazzwBCgCD0Jkoqg== +-----END PRIVATE KEY-----` + +func tearDown(tmpDir string) { + err := os.RemoveAll(tmpDir) + if err != nil { + fmt.Printf("Error in cleaning up test: %v", err) + } +} + +func write(path, contents string, t *testing.T) { + f, err := os.Create(path) + if err != nil { + t.Fatalf("Failed to create %v.", path) + } + _, err = f.WriteString(contents) + if err != nil { + t.Fatalf("Failed to write to %v.", path) + } +} + +func writeKeyPair(tmpDirPath, key, cert string, t *testing.T) (keyPath, certPath string) { + keyPath = path.Join(tmpDirPath, "tls.key") + certPath = path.Join(tmpDirPath, "tls.cert") + write(keyPath, key, t) + write(certPath, cert, t) + return +} + +func TestSecretForTLSGenerate(t *testing.T) { + invalidCertTmpDir := utiltesting.MkTmpdirOrDie("tls-test") + defer tearDown(invalidCertTmpDir) + invalidKeyPath, invalidCertPath := writeKeyPair(invalidCertTmpDir, "test", "test", t) + + validCertTmpDir := utiltesting.MkTmpdirOrDie("tls-test") + defer tearDown(validCertTmpDir) + validKeyPath, validCertPath := writeKeyPair(validCertTmpDir, rsaKeyPEM, rsaCertPEM, t) + + mismatchCertTmpDir := utiltesting.MkTmpdirOrDie("tls-mismatch-test") + defer tearDown(mismatchCertTmpDir) + mismatchKeyPath, mismatchCertPath := writeKeyPair(mismatchCertTmpDir, mismatchRSAKeyPEM, rsaCertPEM, t) + + tests := map[string]struct { + params map[string]interface{} + expected *api.Secret + expectErr bool + }{ + "test-valid-tls-secret": { + params: map[string]interface{}{ + "name": "foo", + "key": validKeyPath, + "cert": validCertPath, + }, + expected: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + 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", + "key": invalidKeyPath, + "cert": invalidCertPath, + }, + expected: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Data: map[string][]byte{ + api.TLSCertKey: []byte("test"), + api.TLSPrivateKeyKey: []byte("test"), + }, + Type: api.SecretTypeTLS, + }, + expectErr: true, + }, + "test-mismatched-key-pair": { + params: map[string]interface{}{ + "name": "foo", + "key": mismatchKeyPath, + "cert": mismatchCertPath, + }, + expected: &api.Secret{ + ObjectMeta: api.ObjectMeta{ + Name: "foo", + }, + Data: map[string][]byte{ + api.TLSCertKey: []byte(rsaCertPEM), + api.TLSPrivateKeyKey: []byte(mismatchRSAKeyPEM), + }, + Type: api.SecretTypeTLS, + }, + expectErr: true, + }, + "test-missing-required-param": { + params: map[string]interface{}{ + "name": "foo", + "key": "/tmp/foo.key", + }, + expectErr: true, + }, + } + + generator := SecretForTLSGeneratorV1{} + for _, test := range tests { + obj, err := generator.Generate(test.params) + if !test.expectErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + 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)) + } + } +}