...

Source file src/k8s.io/kubectl/pkg/cmd/create/create_configmap.go

Documentation: k8s.io/kubectl/pkg/cmd/create

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package create
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"os"
    23  	"path"
    24  	"strings"
    25  	"unicode/utf8"
    26  
    27  	"github.com/spf13/cobra"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/util/validation"
    33  	"k8s.io/cli-runtime/pkg/genericclioptions"
    34  	"k8s.io/cli-runtime/pkg/genericiooptions"
    35  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    36  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    37  	"k8s.io/kubectl/pkg/scheme"
    38  	"k8s.io/kubectl/pkg/util"
    39  	"k8s.io/kubectl/pkg/util/hash"
    40  	"k8s.io/kubectl/pkg/util/i18n"
    41  	"k8s.io/kubectl/pkg/util/templates"
    42  )
    43  
    44  var (
    45  	configMapLong = templates.LongDesc(i18n.T(`
    46  		Create a config map based on a file, directory, or specified literal value.
    47  
    48  		A single config map may package one or more key/value pairs.
    49  
    50  		When creating a config map based on a file, the key will default to the basename of the file, and the value will
    51  		default to the file content.  If the basename is an invalid key, you may specify an alternate key.
    52  
    53  		When creating a config map based on a directory, each file whose basename is a valid key in the directory will be
    54  		packaged into the config map.  Any directory entries except regular files are ignored (e.g. subdirectories,
    55  		symlinks, devices, pipes, etc).`))
    56  
    57  	configMapExample = templates.Examples(i18n.T(`
    58  		  # Create a new config map named my-config based on folder bar
    59  		  kubectl create configmap my-config --from-file=path/to/bar
    60  
    61  		  # Create a new config map named my-config with specified keys instead of file basenames on disk
    62  		  kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
    63  
    64  		  # Create a new config map named my-config with key1=config1 and key2=config2
    65  		  kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
    66  
    67  		  # Create a new config map named my-config from the key=value pairs in the file
    68  		  kubectl create configmap my-config --from-file=path/to/bar
    69  
    70  		  # Create a new config map named my-config from an env file
    71  		  kubectl create configmap my-config --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`))
    72  )
    73  
    74  // ConfigMapOptions holds properties for create configmap sub-command
    75  type ConfigMapOptions struct {
    76  	// PrintFlags holds options necessary for obtaining a printer
    77  	PrintFlags *genericclioptions.PrintFlags
    78  	PrintObj   func(obj runtime.Object) error
    79  
    80  	// Name of configMap (required)
    81  	Name string
    82  	// Type of configMap (optional)
    83  	Type string
    84  	// FileSources to derive the configMap from (optional)
    85  	FileSources []string
    86  	// LiteralSources to derive the configMap from (optional)
    87  	LiteralSources []string
    88  	// EnvFileSources to derive the configMap from (optional)
    89  	EnvFileSources []string
    90  	// AppendHash; if true, derive a hash from the ConfigMap and append it to the name
    91  	AppendHash bool
    92  
    93  	FieldManager     string
    94  	CreateAnnotation bool
    95  	Namespace        string
    96  	EnforceNamespace bool
    97  
    98  	Client              corev1client.CoreV1Interface
    99  	DryRunStrategy      cmdutil.DryRunStrategy
   100  	ValidationDirective string
   101  
   102  	genericiooptions.IOStreams
   103  }
   104  
   105  // NewConfigMapOptions creates a new *ConfigMapOptions with default value
   106  func NewConfigMapOptions(ioStreams genericiooptions.IOStreams) *ConfigMapOptions {
   107  	return &ConfigMapOptions{
   108  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
   109  		IOStreams:  ioStreams,
   110  	}
   111  }
   112  
   113  // NewCmdCreateConfigMap creates the `create configmap` Cobra command
   114  func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   115  	o := NewConfigMapOptions(ioStreams)
   116  
   117  	cmd := &cobra.Command{
   118  		Use:                   "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]",
   119  		DisableFlagsInUseLine: true,
   120  		Aliases:               []string{"cm"},
   121  		Short:                 i18n.T("Create a config map from a local file, directory or literal value"),
   122  		Long:                  configMapLong,
   123  		Example:               configMapExample,
   124  		Run: func(cmd *cobra.Command, args []string) {
   125  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   126  			cmdutil.CheckErr(o.Validate())
   127  			cmdutil.CheckErr(o.Run())
   128  		},
   129  	}
   130  	o.PrintFlags.AddFlags(cmd)
   131  
   132  	cmdutil.AddApplyAnnotationFlags(cmd)
   133  	cmdutil.AddValidateFlags(cmd)
   134  	cmdutil.AddDryRunFlag(cmd)
   135  
   136  	cmd.Flags().StringSliceVar(&o.FileSources, "from-file", o.FileSources, "Key file can be specified using its file path, in which case file basename will be used as configmap key, or optionally with a key and file path, in which case the given key will be used.  Specifying a directory will iterate each named file in the directory whose basename is a valid configmap key.")
   137  	cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
   138  	cmd.Flags().StringSliceVar(&o.EnvFileSources, "from-env-file", o.EnvFileSources, "Specify the path to a file to read lines of key=val pairs to create a configmap.")
   139  	cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the configmap to its name.")
   140  
   141  	cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
   142  
   143  	return cmd
   144  }
   145  
   146  // Complete loads data from the command line environment
   147  func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   148  	var err error
   149  	o.Name, err = NameFromCommandArgs(cmd, args)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	restConfig, err := f.ToRESTConfig()
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	o.Client, err = corev1client.NewForConfig(restConfig)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   165  
   166  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   177  	printer, err := o.PrintFlags.ToPrinter()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	o.PrintObj = func(obj runtime.Object) error {
   182  		return printer.PrintObj(obj, o.Out)
   183  	}
   184  
   185  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // Validate checks if ConfigMapOptions has sufficient value to run
   194  func (o *ConfigMapOptions) Validate() error {
   195  	if len(o.Name) == 0 {
   196  		return fmt.Errorf("name must be specified")
   197  	}
   198  	if len(o.EnvFileSources) > 0 && (len(o.FileSources) > 0 || len(o.LiteralSources) > 0) {
   199  		return fmt.Errorf("from-env-file cannot be combined with from-file or from-literal")
   200  	}
   201  	return nil
   202  }
   203  
   204  // Run calls createConfigMap and filled in value for configMap object
   205  func (o *ConfigMapOptions) Run() error {
   206  	configMap, err := o.createConfigMap()
   207  	if err != nil {
   208  		return err
   209  	}
   210  	if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, configMap, scheme.DefaultJSONEncoder()); err != nil {
   211  		return err
   212  	}
   213  	if o.DryRunStrategy != cmdutil.DryRunClient {
   214  		createOptions := metav1.CreateOptions{}
   215  		if o.FieldManager != "" {
   216  			createOptions.FieldManager = o.FieldManager
   217  		}
   218  		createOptions.FieldValidation = o.ValidationDirective
   219  		if o.DryRunStrategy == cmdutil.DryRunServer {
   220  			createOptions.DryRun = []string{metav1.DryRunAll}
   221  		}
   222  		configMap, err = o.Client.ConfigMaps(o.Namespace).Create(context.TODO(), configMap, createOptions)
   223  		if err != nil {
   224  			return fmt.Errorf("failed to create configmap: %v", err)
   225  		}
   226  	}
   227  
   228  	return o.PrintObj(configMap)
   229  }
   230  
   231  // createConfigMap fills in key value pair from the information given in
   232  // ConfigMapOptions into *corev1.ConfigMap
   233  func (o *ConfigMapOptions) createConfigMap() (*corev1.ConfigMap, error) {
   234  	namespace := ""
   235  	if o.EnforceNamespace {
   236  		namespace = o.Namespace
   237  	}
   238  
   239  	configMap := &corev1.ConfigMap{
   240  		TypeMeta: metav1.TypeMeta{
   241  			APIVersion: corev1.SchemeGroupVersion.String(),
   242  			Kind:       "ConfigMap",
   243  		},
   244  		ObjectMeta: metav1.ObjectMeta{
   245  			Name:      o.Name,
   246  			Namespace: namespace,
   247  		},
   248  	}
   249  	configMap.Name = o.Name
   250  	configMap.Data = map[string]string{}
   251  	configMap.BinaryData = map[string][]byte{}
   252  
   253  	if len(o.FileSources) > 0 {
   254  		if err := handleConfigMapFromFileSources(configMap, o.FileSources); err != nil {
   255  			return nil, err
   256  		}
   257  	}
   258  	if len(o.LiteralSources) > 0 {
   259  		if err := handleConfigMapFromLiteralSources(configMap, o.LiteralSources); err != nil {
   260  			return nil, err
   261  		}
   262  	}
   263  	if len(o.EnvFileSources) > 0 {
   264  		if err := handleConfigMapFromEnvFileSources(configMap, o.EnvFileSources); err != nil {
   265  			return nil, err
   266  		}
   267  	}
   268  	if o.AppendHash {
   269  		hash, err := hash.ConfigMapHash(configMap)
   270  		if err != nil {
   271  			return nil, err
   272  		}
   273  		configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, hash)
   274  	}
   275  
   276  	return configMap, nil
   277  }
   278  
   279  // handleConfigMapFromLiteralSources adds the specified literal source
   280  // information into the provided configMap.
   281  func handleConfigMapFromLiteralSources(configMap *corev1.ConfigMap, literalSources []string) error {
   282  	for _, literalSource := range literalSources {
   283  		keyName, value, err := util.ParseLiteralSource(literalSource)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
   288  		if err != nil {
   289  			return err
   290  		}
   291  	}
   292  
   293  	return nil
   294  }
   295  
   296  // handleConfigMapFromFileSources adds the specified file source information
   297  // into the provided configMap
   298  func handleConfigMapFromFileSources(configMap *corev1.ConfigMap, fileSources []string) error {
   299  	for _, fileSource := range fileSources {
   300  		keyName, filePath, err := util.ParseFileSource(fileSource)
   301  		if err != nil {
   302  			return err
   303  		}
   304  		info, err := os.Stat(filePath)
   305  		if err != nil {
   306  			switch err := err.(type) {
   307  			case *os.PathError:
   308  				return fmt.Errorf("error reading %s: %v", filePath, err.Err)
   309  			default:
   310  				return fmt.Errorf("error reading %s: %v", filePath, err)
   311  			}
   312  
   313  		}
   314  		if info.IsDir() {
   315  			if strings.Contains(fileSource, "=") {
   316  				return fmt.Errorf("cannot give a key name for a directory path")
   317  			}
   318  			fileList, err := os.ReadDir(filePath)
   319  			if err != nil {
   320  				return fmt.Errorf("error listing files in %s: %v", filePath, err)
   321  			}
   322  			for _, item := range fileList {
   323  				itemPath := path.Join(filePath, item.Name())
   324  				if item.Type().IsRegular() {
   325  					keyName = item.Name()
   326  					err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
   327  					if err != nil {
   328  						return err
   329  					}
   330  				}
   331  			}
   332  		} else {
   333  			if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
   334  				return err
   335  			}
   336  
   337  		}
   338  	}
   339  	return nil
   340  }
   341  
   342  // handleConfigMapFromEnvFileSources adds the specified env file source information
   343  // into the provided configMap
   344  func handleConfigMapFromEnvFileSources(configMap *corev1.ConfigMap, envFileSources []string) error {
   345  	for _, envFileSource := range envFileSources {
   346  		info, err := os.Stat(envFileSource)
   347  		if err != nil {
   348  			switch err := err.(type) {
   349  			case *os.PathError:
   350  				return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
   351  			default:
   352  				return fmt.Errorf("error reading %s: %v", envFileSource, err)
   353  			}
   354  		}
   355  		if info.IsDir() {
   356  			return fmt.Errorf("env config file cannot be a directory")
   357  		}
   358  		err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error {
   359  			return addKeyFromLiteralToConfigMap(configMap, key, value)
   360  		})
   361  		if err != nil {
   362  			return err
   363  		}
   364  	}
   365  
   366  	return nil
   367  }
   368  
   369  // addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
   370  // the value with the content of the given file path, or returns an error.
   371  func addKeyFromFileToConfigMap(configMap *corev1.ConfigMap, keyName, filePath string) error {
   372  	data, err := os.ReadFile(filePath)
   373  	if err != nil {
   374  		return err
   375  	}
   376  	if utf8.Valid(data) {
   377  		return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
   378  	}
   379  	err = validateNewConfigMap(configMap, keyName)
   380  	if err != nil {
   381  		return err
   382  	}
   383  	configMap.BinaryData[keyName] = data
   384  
   385  	return nil
   386  }
   387  
   388  // addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
   389  // returning an error if the key is not valid or if the key already exists.
   390  func addKeyFromLiteralToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error {
   391  	err := validateNewConfigMap(configMap, keyName)
   392  	if err != nil {
   393  		return err
   394  	}
   395  	configMap.Data[keyName] = data
   396  
   397  	return nil
   398  }
   399  
   400  // validateNewConfigMap checks whether the keyname is valid
   401  // Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
   402  func validateNewConfigMap(configMap *corev1.ConfigMap, keyName string) error {
   403  	if errs := validation.IsConfigMapKey(keyName); len(errs) > 0 {
   404  		return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ","))
   405  	}
   406  	if _, exists := configMap.Data[keyName]; exists {
   407  		return fmt.Errorf("cannot add key %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name)
   408  	}
   409  	if _, exists := configMap.BinaryData[keyName]; exists {
   410  		return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name)
   411  	}
   412  
   413  	return nil
   414  }
   415  

View as plain text