Remove dependency of generators from create secret
Kubernetes-commit: 5d3c4eaf562dcdcdc783dee8861e1a2be031ee69
This commit is contained in:
parent
b58ab96d24
commit
0b4fea3c25
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue