2019-01-12 04:58:27 +00:00
/ *
Copyright 2015 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 (
2021-03-18 22:40:29 +00:00
"context"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
2019-01-12 04:58:27 +00:00
"github.com/spf13/cobra"
2021-03-18 22:40:29 +00:00
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"
2019-01-12 04:58:27 +00:00
"k8s.io/cli-runtime/pkg/genericclioptions"
2021-03-18 22:40:29 +00:00
"k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2019-09-27 21:51:53 +00:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
2021-03-18 22:40:29 +00:00
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/hash"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2019-01-12 04:58:27 +00:00
)
2021-03-18 22:40:29 +00:00
// 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
2019-01-12 04:58:27 +00:00
func NewCmdCreateSecret ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
cmd := & cobra . Command {
Use : "secret" ,
Short : i18n . T ( "Create a secret using specified subcommand" ) ,
2020-12-01 01:06:26 +00:00
Long : i18n . T ( "Create a secret using specified subcommand." ) ,
2019-01-12 04:58:27 +00:00
Run : cmdutil . DefaultSubCommandRun ( ioStreams . ErrOut ) ,
}
cmd . AddCommand ( NewCmdCreateSecretDockerRegistry ( f , ioStreams ) )
cmd . AddCommand ( NewCmdCreateSecretTLS ( f , ioStreams ) )
cmd . AddCommand ( NewCmdCreateSecretGeneric ( f , ioStreams ) )
return cmd
}
var (
secretLong = templates . LongDesc ( i18n . T ( `
Create a secret based on a file , directory , or specified literal value .
A single secret may package one or more key / value pairs .
When creating a secret based on a file , the key will default to the basename of the file , and the value will
default to the file content . If the basename is an invalid key or you wish to chose your own , you may specify
an alternate key .
When creating a secret based on a directory , each file whose basename is a valid key in the directory will be
packaged into the secret . Any directory entries except regular files are ignored ( e . g . subdirectories ,
symlinks , devices , pipes , etc ) . ` ) )
secretExample = templates . Examples ( i18n . T ( `
# Create a new secret named my - secret with keys for each file in folder bar
kubectl create secret generic my - secret -- from - file = path / to / bar
# Create a new secret named my - secret with specified keys instead of names on disk
2019-08-30 18:33:25 +00:00
kubectl create secret generic my - secret -- from - file = ssh - privatekey = path / to / id_rsa -- from - file = ssh - publickey = path / to / id_rsa . pub
2019-01-12 04:58:27 +00:00
# Create a new secret named my - secret with key1 = supersecret and key2 = topsecret
kubectl create secret generic my - secret -- from - literal = key1 = supersecret -- from - literal = key2 = topsecret
# Create a new secret named my - secret using a combination of a file and a literal
2019-08-30 18:33:25 +00:00
kubectl create secret generic my - secret -- from - file = ssh - privatekey = path / to / id_rsa -- from - literal = passphrase = topsecret
2019-01-12 04:58:27 +00:00
# Create a new secret named my - secret from an env file
kubectl create secret generic my - secret -- from - env - file = path / to / bar . env ` ) )
)
2021-03-18 22:40:29 +00:00
// 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 ,
}
2019-01-12 04:58:27 +00:00
}
// NewCmdCreateSecretGeneric is a command to create generic secrets from files, directories, or literal values
func NewCmdCreateSecretGeneric ( f cmdutil . Factory , ioStreams genericclioptions . IOStreams ) * cobra . Command {
2021-03-18 22:40:29 +00:00
o := NewSecretOptions ( ioStreams )
2019-01-12 04:58:27 +00:00
cmd := & cobra . Command {
2020-03-26 21:07:15 +00:00
Use : "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]" ,
2019-01-12 04:58:27 +00:00
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Create a secret from a local file, directory or literal value" ) ,
Long : secretLong ,
Example : secretExample ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
2021-03-18 22:40:29 +00:00
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . Run ( ) )
2019-01-12 04:58:27 +00:00
} ,
}
2021-03-18 22:40:29 +00:00
o . PrintFlags . AddFlags ( cmd )
2019-01-12 04:58:27 +00:00
cmdutil . AddApplyAnnotationFlags ( cmd )
cmdutil . AddValidateFlags ( cmd )
2021-03-18 22:40:29 +00:00
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" )
2019-01-12 04:58:27 +00:00
return cmd
}
2021-03-18 22:40:29 +00:00
// 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 )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2021-03-18 22:40:29 +00:00
restConfig , err := f . ToRESTConfig ( )
if err != nil {
return err
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
o . Client , err = corev1client . NewForConfig ( restConfig )
if err != nil {
return err
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
o . CreateAnnotation = cmdutil . GetFlagBool ( cmd , cmdutil . ApplyAnnotationsFlag )
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
o . DryRunStrategy , err = cmdutil . GetDryRunStrategy ( cmd )
if err != nil {
return err
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
dynamicClient , err := f . DynamicClient ( )
if err != nil {
return err
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
discoveryClient , err := f . ToDiscoveryClient ( )
if err != nil {
return err
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
o . DryRunVerifier = resource . NewDryRunVerifier ( dynamicClient , discoveryClient )
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
o . Namespace , o . EnforceNamespace , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
cmdutil . PrintFlagsWithDryRunStrategy ( o . PrintFlags , o . DryRunStrategy )
printer , err := o . PrintFlags . ToPrinter ( )
if err != nil {
return nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
o . PrintObj = func ( obj runtime . Object ) error {
return printer . PrintObj ( obj , o . Out )
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
return nil
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
// 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
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// 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 ( )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2021-03-18 22:40:29 +00:00
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
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
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 )
2019-01-12 04:58:27 +00:00
}
}
2021-03-18 22:40:29 +00:00
return o . PrintObj ( secret )
}
// 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
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
secret . Name = fmt . Sprintf ( "%s-%s" , secret . Name , hash )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
return secret , nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// 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 { } ,
}
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// 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
}
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
return nil
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
// 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 )
}
}
// 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
}
}
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
return nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// 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 )
}
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
if fileInfo . IsDir ( ) {
return fmt . Errorf ( "env secret file cannot be a directory" )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
return cmdutil . AddFromEnvFile ( envFileSource , func ( key , value string ) error {
return addKeyFromLiteralToSecret ( secret , key , [ ] byte ( value ) )
} )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// 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 )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2021-03-18 22:40:29 +00:00
return addKeyFromLiteralToSecret ( secret , keyName , data )
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
// 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 , ";" ) )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
if _ , entryExists := secret . Data [ keyName ] ; entryExists {
return fmt . Errorf ( "cannot add key %s, another key by that name already exists" , keyName )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
secret . Data [ keyName ] = data
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
return nil
2019-01-12 04:58:27 +00:00
}