Remove dependency of generators from create secret

Kubernetes-commit: 5d3c4eaf562dcdcdc783dee8861e1a2be031ee69
This commit is contained in:
Chok Yip Lau 2021-01-13 18:59:18 -05:00 committed by Kubernetes Publisher
parent b58ab96d24
commit 0b4fea3c25
7 changed files with 1757 additions and 269 deletions

View File

@ -17,17 +17,32 @@ limitations under the License.
package create
import (
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/generate"
generateversioned "k8s.io/kubectl/pkg/generate/versioned"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)
// NewCmdCreateSecret groups subcommands to create various types of secrets
// NewCmdCreateSecret groups subcommands to create various types of secrets.
// This is the entry point of create_secret.go which will be called by create.go
func NewCmdCreateSecret(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
@ -73,16 +88,48 @@ var (
kubectl create secret generic my-secret --from-env-file=path/to/bar.env`))
)
// SecretGenericOpts holds the options for 'create secret' sub command
type SecretGenericOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
// CreateSecretOptions holds the options for 'create secret' sub command
type CreateSecretOptions struct {
// PrintFlags holds options necessary for obtaining a printer
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
// Name of secret (required)
Name string
// Type of secret (optional)
Type string
// FileSources to derive the secret from (optional)
FileSources []string
// LiteralSources to derive the secret from (optional)
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
FieldManager string
CreateAnnotation bool
Namespace string
EnforceNamespace bool
Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.DryRunVerifier
genericclioptions.IOStreams
}
// NewSecretOptions creates a new *CreateSecretOptions with default value
func NewSecretOptions(ioStreams genericclioptions.IOStreams) *CreateSecretOptions {
return &CreateSecretOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretGenericOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
}
o := NewSecretOptions(ioStreams)
cmd := &cobra.Command{
Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
@ -91,233 +138,279 @@ func NewCmdCreateSecretGeneric(f cmdutil.Factory, ioStreams genericclioptions.IO
Long: secretLong,
Example: secretExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, generateversioned.SecretV1GeneratorName)
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
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.")
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in secret (i.e. mykey=somevalue)")
cmd.Flags().StringVar(&o.EnvFileSource, "from-env-file", o.EnvFileSource, "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().StringVar(&o.Type, "type", o.Type, i18n.T("The type of secret to create"))
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
return cmd
}
// Complete completes all the required options
func (o *SecretGenericOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
// Complete loads data from the command line environment
func (o *CreateSecretOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Name, err = NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
var generator generate.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case generateversioned.SecretV1GeneratorName:
generator = &generateversioned.SecretGeneratorV1{
Name: name,
Type: cmdutil.GetFlagString(cmd, "type"),
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"),
restConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.Client, err = corev1client.NewForConfig(restConfig)
if err != nil {
return err
}
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return nil
}
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
// Validate checks if CreateSecretOptions has sufficient value to run
func (o *CreateSecretOptions) Validate() error {
if len(o.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(o.EnvFileSource) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
}
return nil
}
// Run calls createSecret which will create secret based on CreateSecretOptions
// and makes an API call to the server
func (o *CreateSecretOptions) Run() error {
secret, err := o.createSecret()
if err != nil {
return err
}
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secret, scheme.DefaultJSONEncoder())
if err != nil {
return err
}
if o.DryRunStrategy != cmdutil.DryRunClient {
createOptions := metav1.CreateOptions{}
if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secret.GroupVersionKind())
if err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
secret, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secret, createOptions)
if err != nil {
return fmt.Errorf("failed to create secret %v", err)
}
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
return o.PrintObj(secret)
}
// Run calls the CreateSubcommandOptions.Run in SecretGenericOpts instance
func (o *SecretGenericOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
Create a new secret for use with Docker registries.
Dockercfg secrets are used to authenticate against Docker registries.
When using the Docker command line to push images, you can authenticate to a given registry by running:
'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
authenticate to the registry. The email address is optional.
When creating applications, you may have a Docker registry that requires authentication. In order for the
nodes to pull images on your behalf, they have to have the credentials. You can provide this information
by creating a dockercfg secret and attaching it to your service account.`))
secretForDockerRegistryExample = templates.Examples(i18n.T(`
# 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`))
)
// SecretDockerRegistryOpts holds the options for 'create secret docker-registry' sub command
type SecretDockerRegistryOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretDockerRegistryOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
// createSecret fills in key value pair from the information given in
// CreateSecretOptions into *corev1.Secret
func (o *CreateSecretOptions) createSecret() (*corev1.Secret, error) {
namespace := ""
if o.EnforceNamespace {
namespace = o.Namespace
}
secret := newSecretObj(o.Name, namespace, corev1.SecretType(o.Type))
if len(o.LiteralSources) > 0 {
if err := handleSecretFromLiteralSources(secret, o.LiteralSources); err != nil {
return nil, err
}
}
if len(o.FileSources) > 0 {
if err := handleSecretFromFileSources(secret, o.FileSources); err != nil {
return nil, err
}
}
if len(o.EnvFileSource) > 0 {
if err := handleSecretFromEnvFileSource(secret, o.EnvFileSource); err != nil {
return nil, err
}
}
if o.AppendHash {
hash, err := hash.SecretHash(secret)
if err != nil {
return nil, err
}
secret.Name = fmt.Sprintf("%s-%s", secret.Name, hash)
}
cmd := &cobra.Command{
Use: "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-literal=key1=value1] [--dry-run=server|client|none]",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a secret for use with a Docker registry"),
Long: secretForDockerRegistryLong,
Example: secretForDockerRegistryExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
return secret, nil
}
// newSecretObj will create a new Secret Object given name, namespace and secretType
func newSecretObj(name, namespace string, secretType corev1.SecretType) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Type: secretType,
Data: map[string][]byte{},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, generateversioned.SecretForDockerRegistryV1GeneratorName)
cmd.Flags().String("docker-username", "", i18n.T("Username for Docker registry authentication"))
cmd.Flags().String("docker-password", "", i18n.T("Password for Docker registry authentication"))
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.")
cmd.Flags().StringSlice("from-file", []string{}, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
return cmd
}
// Complete completes all the required options
func (o *SecretDockerRegistryOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
if err != nil {
return err
// handleSecretFromLiteralSources adds the specified literal source
// information into the provided secret
func handleSecretFromLiteralSources(secret *corev1.Secret, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := util.ParseLiteralSource(literalSource)
if err != nil {
return err
}
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
return err
}
}
fromFileFlag := cmdutil.GetFlagStringSlice(cmd, "from-file")
if len(fromFileFlag) == 0 {
requiredFlags := []string{"docker-username", "docker-password", "docker-server"}
for _, requiredFlag := range requiredFlags {
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
return nil
}
// handleSecretFromFileSources adds the specified file source information into the provided secret
func handleSecretFromFileSources(secret *corev1.Secret, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := util.ParseFileSource(fileSource)
if err != nil {
return err
}
fileInfo, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
}
var generator generate.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case generateversioned.SecretForDockerRegistryV1GeneratorName:
generator = &generateversioned.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"),
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
FileSources: cmdutil.GetFlagStringSlice(cmd, "from-file"),
// if the filePath is a directory
if fileInfo.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
if err := addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
return err
}
}
}
// if the filepath is a file
} else {
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
return err
}
}
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
return nil
}
// Run calls CreateSubcommandOptions.Run in SecretDockerRegistryOpts instance
func (o *SecretDockerRegistryOpts) Run() error {
return o.CreateSubcommandOptions.Run()
}
var (
secretForTLSLong = templates.LongDesc(i18n.T(`
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 = templates.Examples(i18n.T(`
# 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`))
)
// SecretTLSOpts holds the options for 'create secret tls' sub command
type SecretTLSOpts struct {
CreateSubcommandOptions *CreateSubcommandOptions
}
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
options := &SecretTLSOpts{
CreateSubcommandOptions: NewCreateSubcommandOptions(ioStreams),
// handleSecretFromEnvFileSource adds the specified env file source information
// into the provided secret
func handleSecretFromEnvFileSource(secret *corev1.Secret, envFileSource string) error {
fileInfo, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if fileInfo.IsDir() {
return fmt.Errorf("env secret file cannot be a directory")
}
cmd := &cobra.Command{
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none]",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a TLS secret"),
Long: secretForTLSLong,
Example: secretForTLSExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(options.Run())
},
}
options.CreateSubcommandOptions.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, generateversioned.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.")
cmdutil.AddFieldManagerFlagVar(cmd, &options.CreateSubcommandOptions.FieldManager, "kubectl-create")
return cmd
return cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}
// Complete completes all the required options
func (o *SecretTLSOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
name, err := NameFromCommandArgs(cmd, args)
// addKeyFromFileToSecret adds a key with the given name to a Secret, populating
// the value with the content of the given file path, or returns an error.
func addKeyFromFileToSecret(secret *corev1.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
requiredFlags := []string{"cert", "key"}
for _, requiredFlag := range requiredFlags {
if value := cmdutil.GetFlagString(cmd, requiredFlag); len(value) == 0 {
return cmdutil.UsageErrorf(cmd, "flag %s is required", requiredFlag)
}
}
var generator generate.StructuredGenerator
switch generatorName := cmdutil.GetFlagString(cmd, "generator"); generatorName {
case generateversioned.SecretForTLSV1GeneratorName:
generator = &generateversioned.SecretForTLSGeneratorV1{
Name: name,
Key: cmdutil.GetFlagString(cmd, "key"),
Cert: cmdutil.GetFlagString(cmd, "cert"),
AppendHash: cmdutil.GetFlagBool(cmd, "append-hash"),
}
default:
return errUnsupportedGenerator(cmd, generatorName)
}
return o.CreateSubcommandOptions.Complete(f, cmd, args, generator)
return addKeyFromLiteralToSecret(secret, keyName, data)
}
// Run calls CreateSubcommandOptions.Run in the SecretTLSOpts instance
func (o *SecretTLSOpts) Run() error {
return o.CreateSubcommandOptions.Run()
// addKeyFromLiteralToSecret adds the given key and data to the given secret,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToSecret(secret *corev1.Secret, keyName string, data []byte) error {
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not valid key name for a Secret %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists", keyName)
}
secret.Data[keyName] = data
return nil
}

View File

@ -0,0 +1,308 @@
/*
Copyright 2021 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 create
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)
var (
secretForDockerRegistryLong = templates.LongDesc(i18n.T(`
Create a new secret for use with Docker registries.
Dockercfg secrets are used to authenticate against Docker registries.
When using the Docker command line to push images, you can authenticate to a given registry by running:
'$ docker login DOCKER_REGISTRY_SERVER --username=DOCKER_USER --password=DOCKER_PASSWORD --email=DOCKER_EMAIL'.
That produces a ~/.dockercfg file that is used by subsequent 'docker push' and 'docker pull' commands to
authenticate to the registry. The email address is optional.
When creating applications, you may have a Docker registry that requires authentication. In order for the
nodes to pull images on your behalf, they have to have the credentials. You can provide this information
by creating a dockercfg secret and attaching it to your service account.`))
secretForDockerRegistryExample = templates.Examples(i18n.T(`
# 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
# Create a new secret named my-secret from ~/.docker/config.json
kubectl create secret docker-registry my-secret --from-file=.dockerconfigjson=path/to/.docker/config.json`))
)
// DockerConfigJSON represents a local docker auth config file
// for pulling images.
type DockerConfigJSON struct {
Auths DockerConfig `json:"auths" datapolicy:"token"`
// +optional
HttpHeaders map[string]string `json:"HttpHeaders,omitempty" datapolicy:"token"`
}
// DockerConfig represents the config file used by the docker CLI.
// This config that represents the credentials that should be used
// when pulling images from specific image repositories.
type DockerConfig map[string]DockerConfigEntry
// DockerConfigEntry holds the user information that grant the access to docker registry
type DockerConfigEntry struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty" datapolicy:"password"`
Email string `json:"email,omitempty"`
Auth string `json:"auth,omitempty" datapolicy:"token"`
}
// CreateSecretDockerRegistryOptions holds the options for 'create secret docker-registry' sub command
type CreateSecretDockerRegistryOptions struct {
// PrintFlags holds options necessary for obtaining a printer
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
// Name of secret (required)
Name string
// FileSources to derive the secret from (optional)
FileSources []string
// Username for registry (required)
Username string
// Email for registry (optional)
Email string
// Password for registry (required)
Password string `datapolicy:"password"`
// Server for registry (required)
Server string
// AppendHash; if true, derive a hash from the Secret and append it to the name
AppendHash bool
FieldManager string
CreateAnnotation bool
Namespace string
EnforceNamespace bool
Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.DryRunVerifier
genericclioptions.IOStreams
}
// NewSecretDockerRegistryOptions creates a new *CreateSecretDockerRegistryOptions with default value
func NewSecretDockerRegistryOptions(ioStreams genericclioptions.IOStreams) *CreateSecretDockerRegistryOptions {
return &CreateSecretDockerRegistryOptions{
Server: "https://index.docker.io/v1/",
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStreams,
}
}
// NewCmdCreateSecretDockerRegistry is a macro command for creating secrets to work with Docker registries
func NewCmdCreateSecretDockerRegistry(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewSecretDockerRegistryOptions(ioStreams)
cmd := &cobra.Command{
Use: "docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none]",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a secret for use with a Docker registry"),
Long: secretForDockerRegistryLong,
Example: secretForDockerRegistryExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&o.Username, "docker-username", o.Username, i18n.T("Username for Docker registry authentication"))
cmd.Flags().StringVar(&o.Password, "docker-password", o.Password, i18n.T("Password for Docker registry authentication"))
cmd.Flags().StringVar(&o.Email, "docker-email", o.Email, i18n.T("Email for Docker registry"))
cmd.Flags().StringVar(&o.Server, "docker-server", o.Server, i18n.T("Server location for Docker registry"))
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key files can be specified using their file path, in which case a default name will be given to them, or optionally with a name and file path, in which case the given name will be used. Specifying a directory will iterate each named file in the directory that is a valid secret key.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
return cmd
}
// Complete loads data from the command line environment
func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Name, err = NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
restConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.Client, err = corev1client.NewForConfig(restConfig)
if err != nil {
return err
}
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return nil
}
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
// Validate checks if CreateSecretDockerRegistryOptions has sufficient value to run
func (o *CreateSecretDockerRegistryOptions) Validate() error {
if len(o.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(o.FileSources) == 0 && (len(o.Username) == 0 || len(o.Password) == 0 || len(o.Server) == 0) {
return fmt.Errorf("either --from-file or the combination of --docker-username, --docker-password and --docker-server is required")
}
return nil
}
// Run calls createSecretDockerRegistry which will create secretDockerRegistry based on CreateSecretDockerRegistryOptions
// and makes an API call to the server
func (o *CreateSecretDockerRegistryOptions) Run() error {
secretDockerRegistry, err := o.createSecretDockerRegistry()
if err != nil {
return err
}
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretDockerRegistry, scheme.DefaultJSONEncoder())
if err != nil {
return err
}
if o.DryRunStrategy != cmdutil.DryRunClient {
createOptions := metav1.CreateOptions{}
if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secretDockerRegistry.GroupVersionKind())
if err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
secretDockerRegistry, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretDockerRegistry, createOptions)
if err != nil {
return fmt.Errorf("failed to create secret %v", err)
}
}
return o.PrintObj(secretDockerRegistry)
}
// createSecretDockerRegistry fills in key value pair from the information given in
// CreateSecretDockerRegistryOptions into *corev1.Secret
func (o *CreateSecretDockerRegistryOptions) createSecretDockerRegistry() (*corev1.Secret, error) {
namespace := ""
if o.EnforceNamespace {
namespace = o.Namespace
}
secretDockerRegistry := newSecretObj(o.Name, namespace, corev1.SecretTypeDockerConfigJson)
if len(o.FileSources) > 0 {
if err := handleSecretFromFileSources(secretDockerRegistry, o.FileSources); err != nil {
return nil, err
}
} else {
dockerConfigJSONContent, err := handleDockerCfgJSONContent(o.Username, o.Password, o.Email, o.Server)
if err != nil {
return nil, err
}
secretDockerRegistry.Data[corev1.DockerConfigJsonKey] = dockerConfigJSONContent
}
if o.AppendHash {
hash, err := hash.SecretHash(secretDockerRegistry)
if err != nil {
return nil, err
}
secretDockerRegistry.Name = fmt.Sprintf("%s-%s", secretDockerRegistry.Name, hash)
}
return secretDockerRegistry, nil
}
// handleDockerCfgJSONContent serializes a ~/.docker/config.json file
func handleDockerCfgJSONContent(username, password, email, server string) ([]byte, error) {
dockerConfigAuth := DockerConfigEntry{
Username: username,
Password: password,
Email: email,
Auth: encodeDockerConfigFieldAuth(username, password),
}
dockerConfigJSON := DockerConfigJSON{
Auths: map[string]DockerConfigEntry{server: dockerConfigAuth},
}
return json.Marshal(dockerConfigJSON)
}
// encodeDockerConfigFieldAuth returns base64 encoding of the username and password string
func encodeDockerConfigFieldAuth(username, password string) string {
fieldValue := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(fieldValue))
}

View File

@ -0,0 +1,185 @@
/*
Copyright 2021 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 create
import (
"testing"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCreateSecretDockerRegistry(t *testing.T) {
username, password, email, server := "test-user", "test-password", "test-user@example.org", "https://index.docker.io/v1/"
secretData, err := handleDockerCfgJSONContent(username, password, email, server)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
secretDataNoEmail, err := handleDockerCfgJSONContent(username, password, "", server)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tests := map[string]struct {
dockerRegistrySecretName string
dockerUsername string
dockerEmail string
dockerPassword string
dockerServer string
appendHash bool
expected *corev1.Secret
expectErr bool
}{
"create_secret_docker_registry_with_email": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerPassword: password,
dockerEmail: email,
dockerServer: server,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: secretData,
},
},
expectErr: false,
},
"create_secret_docker_registry_with_email_hash": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerPassword: password,
dockerEmail: email,
dockerServer: server,
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-548cm7fgdh",
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: secretData,
},
},
expectErr: false,
},
"create_secret_docker_registry_without_email": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerPassword: password,
dockerEmail: "",
dockerServer: server,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: secretDataNoEmail,
},
},
expectErr: false,
},
"create_secret_docker_registry_without_email_hash": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerPassword: password,
dockerEmail: "",
dockerServer: server,
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-bff5bt4f92",
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: secretDataNoEmail,
},
},
expectErr: false,
},
"create_invalid_secret_docker_registry_without_username": {
dockerRegistrySecretName: "foo",
dockerPassword: password,
dockerEmail: "",
dockerServer: server,
expectErr: true,
},
"create_invalid_secret_docker_registry_without_password": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerEmail: "",
dockerServer: server,
expectErr: true,
},
"create_invalid_secret_docker_registry_without_server": {
dockerRegistrySecretName: "foo",
dockerUsername: username,
dockerPassword: password,
dockerEmail: "",
expectErr: true,
},
}
// Run all the tests
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var secretDockerRegistry *corev1.Secret = nil
secretDockerRegistryOptions := CreateSecretDockerRegistryOptions{
Name: test.dockerRegistrySecretName,
Username: test.dockerUsername,
Email: test.dockerEmail,
Password: test.dockerPassword,
Server: test.dockerServer,
AppendHash: test.appendHash,
}
err := secretDockerRegistryOptions.Validate()
if err == nil {
secretDockerRegistry, err = secretDockerRegistryOptions.createSecretDockerRegistry()
}
if !test.expectErr && err != nil {
t.Errorf("test %s, unexpected error: %v", name, err)
}
if test.expectErr && err == nil {
t.Errorf("test %s was expecting an error but no error occurred", name)
}
if !apiequality.Semantic.DeepEqual(secretDockerRegistry, test.expected) {
t.Errorf("test %s\n expected:\n%#v\ngot:\n%#v", name, test.expected, secretDockerRegistry)
}
})
}
}

View File

@ -17,85 +17,515 @@ limitations under the License.
package create
import (
"net/http"
"io/ioutil"
"os"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"k8s.io/kubectl/pkg/scheme"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCreateSecretObject(t *testing.T) {
secretObject := newSecretObj("foo", "foo-namespace", corev1.SecretTypeDockerConfigJson)
expectedSecretObject := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "foo-namespace",
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{},
}
t.Run("Creating a Secret Object", func(t *testing.T) {
if !apiequality.Semantic.DeepEqual(secretObject, expectedSecretObject) {
t.Errorf("expected:\n%#v\ngot:\n%#v", secretObject, expectedSecretObject)
}
})
}
func TestCreateSecretGeneric(t *testing.T) {
secretObject := &v1.Secret{
Data: map[string][]byte{
"password": []byte("includes,comma"),
"username": []byte("test_user"),
tests := map[string]struct {
secretName string
secretType string
fromLiteral []string
fromFile []string
fromEnvFile string
appendHash bool
setup func(t *testing.T, secretGenericOptions *CreateSecretOptions) func()
expected *corev1.Secret
expectErr bool
}{
"create_secret_foo": {
secretName: "foo",
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{},
},
expectErr: false,
},
"create_secret_foo_hash": {
secretName: "foo",
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-949tdgdkgg",
},
Data: map[string][]byte{},
},
expectErr: false,
},
"create_secret_foo_type": {
secretName: "foo",
secretType: "my-type",
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{},
Type: "my-type",
},
expectErr: false,
},
"create_secret_foo_type_hash": {
secretName: "foo",
secretType: "my-type",
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-dg474f9t76",
},
Data: map[string][]byte{},
Type: "my-type",
},
expectErr: false,
},
"create_secret_foo_two_literal": {
secretName: "foo",
fromLiteral: []string{"key1=value1", "key2=value2"},
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
"create_secret_foo_two_literal_hash": {
secretName: "foo",
fromLiteral: []string{"key1=value1", "key2=value2"},
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-tf72c228m4",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
"create_secret_foo_key1_=value1": {
secretName: "foo",
fromLiteral: []string{"key1==value1"},
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"key1": []byte("=value1"),
},
},
expectErr: false,
},
"create_secret_foo_key1_=value1_hash": {
secretName: "foo",
fromLiteral: []string{"key1==value1"},
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-fdcc8tkhh5",
},
Data: map[string][]byte{
"key1": []byte("=value1"),
},
},
expectErr: false,
},
"create_secret_foo_from_file_foo1_foo2_secret": {
secretName: "foo",
setup: setupSecretBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"foo1": []byte("hello world"),
"foo2": []byte("hello world"),
},
},
expectErr: false,
},
"create_secret_foo_from_file_foo1_foo2_hash": {
secretName: "foo",
setup: setupSecretBinaryFile([]byte{0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-hbkh2cdb57",
},
Data: map[string][]byte{
"foo1": []byte("hello world"),
"foo2": []byte("hello world"),
},
},
expectErr: false,
},
"create_secret_foo_from_file_foo1_foo2_and": {
secretName: "foo",
setup: setupSecretBinaryFile([]byte{0xff, 0xfd}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Data: map[string][]byte{
"foo1": {0xff, 0xfd},
"foo2": {0xff, 0xfd},
},
},
expectErr: false,
},
"create_secret_foo_from_file_foo1_foo2_and_hash": {
secretName: "foo",
setup: setupSecretBinaryFile([]byte{0xff, 0xfd}, "foo1", "foo2"),
fromFile: []string{"foo1", "foo2"},
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-mkhg4ktk4d",
},
Data: map[string][]byte{
"foo1": {0xff, 0xfd},
"foo2": {0xff, 0xfd},
},
},
expectErr: false,
},
"create_secret_valid_env_from_env_file": {
secretName: "valid_env",
setup: setupSecretEnvFile("key1=value1", "#", "", "key2=value2"),
fromEnvFile: "file.env",
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
"create_secret_valid_env_from_env_file_hash": {
secretName: "valid_env",
setup: setupSecretEnvFile("key1=value1", "#", "", "key2=value2"),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_env-bkb2m2965h",
},
Data: map[string][]byte{
"key1": []byte("value1"),
"key2": []byte("value2"),
},
},
expectErr: false,
},
"create_secret_get_env_from_env_file": {
secretName: "get_env",
setup: func() func(t *testing.T, secretGenericOptions *CreateSecretOptions) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupSecretEnvFile("g_key1", "g_key2=")
}(),
fromEnvFile: "file.env",
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "get_env",
},
Data: map[string][]byte{
"g_key1": []byte("1"),
"g_key2": []byte(""),
},
},
expectErr: false,
},
"create_secret_get_env_from_env_file_hash": {
secretName: "get_env",
setup: func() func(t *testing.T, secretGenericOptions *CreateSecretOptions) func() {
os.Setenv("g_key1", "1")
os.Setenv("g_key2", "2")
return setupSecretEnvFile("g_key1", "g_key2=")
}(),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "get_env-68mt8f2kkt",
},
Data: map[string][]byte{
"g_key1": []byte("1"),
"g_key2": []byte(""),
},
},
expectErr: false,
},
"create_secret_value_with_space_from_env_file": {
secretName: "value_with_space",
setup: setupSecretEnvFile(" key1= value1"),
fromEnvFile: "file.env",
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "value_with_space",
},
Data: map[string][]byte{
"key1": []byte(" value1"),
},
},
expectErr: false,
},
"create_secret_value_with_space_from_env_file_hash": {
secretName: "valid_with_space",
setup: setupSecretEnvFile(" key1= value1"),
fromEnvFile: "file.env",
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "valid_with_space-bhkb4gfck6",
},
Data: map[string][]byte{
"key1": []byte(" value1"),
},
},
expectErr: false,
},
"create_invalid_secret_filepath_contains_=": {
secretName: "foo",
fromFile: []string{"key1=/file=2"},
expectErr: true,
},
"create_invalid_secret_filepath_key_contains_=": {
secretName: "foo",
fromFile: []string{"=key=/file1"},
expectErr: true,
},
"create_invalid_secret_literal_key_contains_=": {
secretName: "foo",
fromFile: []string{"=key=value1"},
expectErr: true,
},
"create_invalid_secret_env_key_contains_#": {
secretName: "invalid_key",
setup: setupSecretEnvFile("key#1=value1"),
fromEnvFile: "file.env",
expectErr: true,
},
"create_invalid_secret_duplicate_key1": {
secretName: "foo",
fromLiteral: []string{"key1=value1", "key1=value2"},
expectErr: true,
},
"create_invalid_secret_no_file": {
secretName: "foo",
fromFile: []string{"key1=/file1"},
expectErr: true,
},
"create_invalid_secret_invalid_literal": {
secretName: "foo",
fromLiteral: []string{"key1value1"},
expectErr: true,
},
"create_invalid_secret_invalid_filepath": {
secretName: "foo",
fromFile: []string{"key1==file1"},
expectErr: true,
},
"create_invalid_secret_no_name": {
expectErr: true,
},
"create_invalid_secret_too_many_args": {
secretName: "too_many_args",
fromFile: []string{"key1=/file1"},
fromEnvFile: "foo",
expectErr: true,
},
"create_invalid_secret_too_many_args_1": {
secretName: "too_many_args_1",
fromLiteral: []string{"key1=value1"},
fromEnvFile: "foo",
expectErr: true,
},
"create_invalid_secret_too_many_args_2": {
secretName: "too_many_args_2",
fromFile: []string{"key1=/file1"},
fromLiteral: []string{"key1=value1"},
fromEnvFile: "foo",
expectErr: true,
},
}
secretObject.Name = "my-secret"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs.WithoutConversion()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
// run all the tests
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var secret *corev1.Secret = nil
secretOptions := CreateSecretOptions{
Name: test.secretName,
Type: test.secretType,
AppendHash: test.appendHash,
FileSources: test.fromFile,
LiteralSources: test.fromLiteral,
EnvFileSource: test.fromEnvFile,
}
}),
}
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateSecretGeneric(tf, ioStreams)
cmd.Flags().Set("output", "name")
cmd.Flags().Set("from-literal", "password=includes,comma")
cmd.Flags().Set("from-literal", "username=test_user")
cmd.Run(cmd, []string{secretObject.Name})
expectedOutput := "secret/" + secretObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", expectedOutput, buf.String())
if test.setup != nil {
if teardown := test.setup(t, &secretOptions); teardown != nil {
defer teardown()
}
}
err := secretOptions.Validate()
if err == nil {
secret, err = secretOptions.createSecret()
}
if !test.expectErr && err != nil {
t.Errorf("test %s, unexpected error: %v", name, err)
}
if test.expectErr && err == nil {
t.Errorf("test %s was expecting an error but no error occurred", name)
}
if !apiequality.Semantic.DeepEqual(secret, test.expected) {
t.Errorf("test %s\n expected:\n%#v\ngot:\n%#v", name, test.expected, secret)
}
})
}
}
func TestCreateSecretDockerRegistry(t *testing.T) {
secretObject := &v1.Secret{}
secretObject.Name = "my-secret"
tf := cmdtesting.NewTestFactory().WithNamespace("test")
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs.WithoutConversion()
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/secrets" && m == "POST":
return &http.Response{StatusCode: http.StatusCreated, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, secretObject)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdCreateSecretDockerRegistry(tf, ioStreams)
cmd.Flags().Set("docker-username", "test-user")
cmd.Flags().Set("docker-password", "test-pass")
cmd.Flags().Set("docker-email", "test-email")
cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{secretObject.Name})
expectedOutput := "secret/" + secretObject.Name + "\n"
if buf.String() != expectedOutput {
t.Errorf("expected output: %s, but got: %s", buf.String(), expectedOutput)
func setupSecretEnvFile(lines ...string) func(*testing.T, *CreateSecretOptions) func() {
return func(t *testing.T, secretOptions *CreateSecretOptions) func() {
f, err := ioutil.TempFile("", "cme")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
for _, l := range lines {
f.WriteString(l)
f.WriteString("\r\n")
}
f.Close()
secretOptions.EnvFileSource = f.Name()
return func() {
os.Remove(f.Name())
}
}
}
func setupSecretBinaryFile(data []byte, files ...string) func(*testing.T, *CreateSecretOptions) func() {
return func(t *testing.T, secretOptions *CreateSecretOptions) func() {
tmp, _ := ioutil.TempDir("", "")
for i, file := range files {
f := tmp + "/" + file
ioutil.WriteFile(f, data, 0644)
secretOptions.FileSources[i] = f
}
return func() {
for _, file := range files {
f := tmp + "/" + file
os.RemoveAll(f)
}
}
}
}

View File

@ -0,0 +1,259 @@
/*
Copyright 2021 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 create
import (
"context"
"crypto/tls"
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
)
var (
secretForTLSLong = templates.LongDesc(i18n.T(`
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 = templates.Examples(i18n.T(`
# 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`))
)
// CreateSecretTLSOptions holds the options for 'create secret tls' sub command
type CreateSecretTLSOptions struct {
// PrintFlags holds options necessary for obtaining a printer
PrintFlags *genericclioptions.PrintFlags
PrintObj func(obj runtime.Object) error
// 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
// AppendHash; if true, derive a hash from the Secret and append it to the name
AppendHash bool
FieldManager string
CreateAnnotation bool
Namespace string
EnforceNamespace bool
Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.DryRunVerifier
genericclioptions.IOStreams
}
// NewSecretTLSOptions creates a new *CreateSecretTLSOptions with default value
func NewSecretTLSOptions(ioStrems genericclioptions.IOStreams) *CreateSecretTLSOptions {
return &CreateSecretTLSOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
IOStreams: ioStrems,
}
}
// NewCmdCreateSecretTLS is a macro command for creating secrets to work with TLS client or server
func NewCmdCreateSecretTLS(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := NewSecretTLSOptions(ioStreams)
cmd := &cobra.Command{
Use: "tls NAME --cert=path/to/cert/file --key=path/to/key/file [--dry-run=server|client|none]",
DisableFlagsInUseLine: true,
Short: i18n.T("Create a TLS secret"),
Long: secretForTLSLong,
Example: secretForTLSExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
o.PrintFlags.AddFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&o.Cert, "cert", o.Cert, i18n.T("Path to PEM encoded public key certificate."))
cmd.Flags().StringVar(&o.Key, "key", o.Key, i18n.T("Path to private key associated with given certificate."))
cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the secret to its name.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
return cmd
}
// Complete loads data from the command line environment
func (o *CreateSecretTLSOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Name, err = NameFromCommandArgs(cmd, args)
if err != nil {
return err
}
restConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.Client, err = corev1client.NewForConfig(restConfig)
if err != nil {
return err
}
o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
if err != nil {
return err
}
dynamicClient, err := f.DynamicClient()
if err != nil {
return err
}
discoveryClient, err := f.ToDiscoveryClient()
if err != nil {
return err
}
o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return nil
}
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return nil
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
return nil
}
// Validate checks if CreateSecretTLSOptions hass sufficient value to run
func (o *CreateSecretTLSOptions) Validate() error {
// TODO: This is not strictly necessary. We can generate a self signed cert
// if no key/cert is given. The only requirement is that we either get both
// or none. See test/e2e/ingress_utils for self signed cert generation.
if len(o.Key) == 0 || len(o.Cert) == 0 {
return fmt.Errorf("key and cert must be specified")
}
return nil
}
// Run calls createSecretTLS which will create secretTLS based on CreateSecretTLSOptions
// and makes an API call to the server
func (o *CreateSecretTLSOptions) Run() error {
secretTLS, err := o.createSecretTLS()
if err != nil {
return err
}
err = util.CreateOrUpdateAnnotation(o.CreateAnnotation, secretTLS, scheme.DefaultJSONEncoder())
if err != nil {
return err
}
if o.DryRunStrategy != cmdutil.DryRunClient {
createOptions := metav1.CreateOptions{}
if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secretTLS.GroupVersionKind())
if err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
secretTLS, err = o.Client.Secrets(o.Namespace).Create(context.TODO(), secretTLS, createOptions)
if err != nil {
return fmt.Errorf("failed to create secret %v", err)
}
}
return o.PrintObj(secretTLS)
}
// createSecretTLS fills in key value pair from the information given in
// CreateSecretTLSOptions into *corev1.Secret
func (o *CreateSecretTLSOptions) createSecretTLS() (*corev1.Secret, error) {
namespace := ""
if o.EnforceNamespace {
namespace = o.Namespace
}
tlsCert, err := readFile(o.Cert)
if err != nil {
return nil, err
}
tlsKey, err := readFile(o.Key)
if err != nil {
return nil, err
}
if _, err := tls.X509KeyPair(tlsCert, tlsKey); err != nil {
return nil, err
}
// TODO: Add more validation.
// 1. If the certificate contains intermediates, it is a valid chain.
// 2. Format etc.
secretTLS := newSecretObj(o.Name, namespace, corev1.SecretTypeTLS)
secretTLS.Data[corev1.TLSCertKey] = []byte(tlsCert)
secretTLS.Data[corev1.TLSPrivateKeyKey] = []byte(tlsKey)
if o.AppendHash {
hash, err := hash.SecretHash(secretTLS)
if err != nil {
return nil, err
}
secretTLS.Name = fmt.Sprintf("%s-%s", secretTLS.Name, hash)
}
return secretTLS, 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
}

View File

@ -0,0 +1,217 @@
/*
Copyright 2021 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 create
import (
"fmt"
"os"
"path"
"testing"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utiltesting "k8s.io/client-go/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 TestCreateSecretTLS(t *testing.T) {
validCertTmpDir := utiltesting.MkTmpdirOrDie("tls-valid-cert-test")
validKeyPath, validCertPath := writeKeyPair(validCertTmpDir, rsaKeyPEM, rsaCertPEM, t)
defer tearDown(validCertTmpDir)
invalidCertTmpDir := utiltesting.MkTmpdirOrDie("tls-invalid-cert-test")
invalidKeyPath, invalidCertPath := writeKeyPair(invalidCertTmpDir, "test", "test", t)
defer tearDown(invalidCertTmpDir)
mismatchCertTmpDir := utiltesting.MkTmpdirOrDie("tls-mismatch-test")
mismatchKeyPath, mismatchCertPath := writeKeyPair(mismatchCertTmpDir, rsaKeyPEM, mismatchRSAKeyPEM, t)
defer tearDown(mismatchCertTmpDir)
tests := map[string]struct {
tlsSecretName string
tlsKey string
tlsCert string
appendHash bool
expected *corev1.Secret
expectErr bool
}{
"create_secret_tls": {
tlsSecretName: "foo",
tlsKey: validKeyPath,
tlsCert: validCertPath,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Type: corev1.SecretTypeTLS,
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: []byte(rsaKeyPEM),
corev1.TLSCertKey: []byte(rsaCertPEM),
},
},
expectErr: false,
},
"create_secret_tls_hash": {
tlsSecretName: "foo",
tlsKey: validKeyPath,
tlsCert: validCertPath,
appendHash: true,
expected: &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "foo-272h6tt825",
},
Type: corev1.SecretTypeTLS,
Data: map[string][]byte{
corev1.TLSPrivateKeyKey: []byte(rsaKeyPEM),
corev1.TLSCertKey: []byte(rsaCertPEM),
},
},
expectErr: false,
},
"create_secret_invalid_tls": {
tlsSecretName: "foo",
tlsKey: invalidKeyPath,
tlsCert: invalidCertPath,
expectErr: true,
},
"create_secret_mismatch_tls": {
tlsSecretName: "foo",
tlsKey: mismatchKeyPath,
tlsCert: mismatchCertPath,
expectErr: true,
},
"create_invalid_filepath_and_certpath_secret_tls": {
tlsSecretName: "foo",
tlsKey: "testKeyPath",
tlsCert: "testCertPath",
expectErr: true,
},
}
// Run all the tests
for name, test := range tests {
t.Run(name, func(t *testing.T) {
secretTLSOptions := CreateSecretTLSOptions{
Name: test.tlsSecretName,
Key: test.tlsKey,
Cert: test.tlsCert,
AppendHash: test.appendHash,
}
secretTLS, err := secretTLSOptions.createSecretTLS()
if !test.expectErr && err != nil {
t.Errorf("test %s, unexpected error: %v", name, err)
}
if test.expectErr && err == nil {
t.Errorf("test %s was expecting an error but no error occurred", name)
}
if !apiequality.Semantic.DeepEqual(secretTLS, test.expected) {
t.Errorf("test %s\n expected:\n%#v\ngot:\n%#v", name, test.expected, secretTLS)
}
})
}
}
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)
}
defer f.Close()
_, 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
}

View File

@ -206,7 +206,3 @@ func (o *ServiceAccountOpts) createServiceAccount() (*corev1.ServiceAccount, err
serviceAccount.Name = o.Name
return serviceAccount, nil
}
func errUnsupportedGenerator(cmd *cobra.Command, generatorName string) error {
return cmdutil.UsageErrorf(cmd, "Generator %s not supported. ", generatorName)
}