...

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

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

     1  /*
     2  Copyright 2014 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  	"io"
    23  	"net/url"
    24  	"runtime"
    25  	"strings"
    26  
    27  	"github.com/spf13/cobra"
    28  	"k8s.io/klog/v2"
    29  
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	kruntime "k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/cli-runtime/pkg/genericclioptions"
    36  	"k8s.io/cli-runtime/pkg/genericiooptions"
    37  	"k8s.io/cli-runtime/pkg/printers"
    38  	"k8s.io/cli-runtime/pkg/resource"
    39  	"k8s.io/client-go/dynamic"
    40  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    41  	"k8s.io/kubectl/pkg/cmd/util/editor"
    42  	"k8s.io/kubectl/pkg/generate"
    43  	"k8s.io/kubectl/pkg/rawhttp"
    44  	"k8s.io/kubectl/pkg/scheme"
    45  	"k8s.io/kubectl/pkg/util"
    46  	"k8s.io/kubectl/pkg/util/i18n"
    47  	"k8s.io/kubectl/pkg/util/templates"
    48  )
    49  
    50  // CreateOptions is the commandline options for 'create' sub command
    51  type CreateOptions struct {
    52  	PrintFlags  *genericclioptions.PrintFlags
    53  	RecordFlags *genericclioptions.RecordFlags
    54  
    55  	DryRunStrategy cmdutil.DryRunStrategy
    56  
    57  	ValidationDirective string
    58  
    59  	fieldManager string
    60  
    61  	FilenameOptions  resource.FilenameOptions
    62  	Selector         string
    63  	EditBeforeCreate bool
    64  	Raw              string
    65  
    66  	Recorder genericclioptions.Recorder
    67  	PrintObj func(obj kruntime.Object) error
    68  
    69  	genericiooptions.IOStreams
    70  }
    71  
    72  var (
    73  	createLong = templates.LongDesc(i18n.T(`
    74  		Create a resource from a file or from stdin.
    75  
    76  		JSON and YAML formats are accepted.`))
    77  
    78  	createExample = templates.Examples(i18n.T(`
    79  		# Create a pod using the data in pod.json
    80  		kubectl create -f ./pod.json
    81  
    82  		# Create a pod based on the JSON passed into stdin
    83  		cat pod.json | kubectl create -f -
    84  
    85  		# Edit the data in registry.yaml in JSON then create the resource using the edited data
    86  		kubectl create -f registry.yaml --edit -o json`))
    87  )
    88  
    89  // NewCreateOptions returns an initialized CreateOptions instance
    90  func NewCreateOptions(ioStreams genericiooptions.IOStreams) *CreateOptions {
    91  	return &CreateOptions{
    92  		PrintFlags:  genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
    93  		RecordFlags: genericclioptions.NewRecordFlags(),
    94  
    95  		Recorder: genericclioptions.NoopRecorder{},
    96  
    97  		IOStreams: ioStreams,
    98  	}
    99  }
   100  
   101  // NewCmdCreate returns new initialized instance of create sub command
   102  func NewCmdCreate(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command {
   103  	o := NewCreateOptions(ioStreams)
   104  
   105  	cmd := &cobra.Command{
   106  		Use:                   "create -f FILENAME",
   107  		DisableFlagsInUseLine: true,
   108  		Short:                 i18n.T("Create a resource from a file or from stdin"),
   109  		Long:                  createLong,
   110  		Example:               createExample,
   111  		Run: func(cmd *cobra.Command, args []string) {
   112  			if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) {
   113  				ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n"))
   114  				defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut)
   115  				defaultRunFunc(cmd, args)
   116  				return
   117  			}
   118  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   119  			cmdutil.CheckErr(o.Validate())
   120  			cmdutil.CheckErr(o.RunCreate(f, cmd))
   121  		},
   122  	}
   123  
   124  	// bind flag structs
   125  	o.RecordFlags.AddFlags(cmd)
   126  
   127  	usage := "to use to create the resource"
   128  	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage)
   129  	cmdutil.AddValidateFlags(cmd)
   130  	cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating")
   131  	cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows",
   132  		"Only relevant if --edit=true. Defaults to the line ending native to your platform.")
   133  	cmdutil.AddApplyAnnotationFlags(cmd)
   134  	cmdutil.AddDryRunFlag(cmd)
   135  	cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
   136  	cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server.  Uses the transport specified by the kubeconfig file.")
   137  	cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-create")
   138  
   139  	o.PrintFlags.AddFlags(cmd)
   140  
   141  	// create subcommands
   142  	cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams))
   143  	cmd.AddCommand(NewCmdCreateQuota(f, ioStreams))
   144  	cmd.AddCommand(NewCmdCreateSecret(f, ioStreams))
   145  	cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams))
   146  	cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams))
   147  	cmd.AddCommand(NewCmdCreateService(f, ioStreams))
   148  	cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams))
   149  	cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams))
   150  	cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams))
   151  	cmd.AddCommand(NewCmdCreateRole(f, ioStreams))
   152  	cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams))
   153  	cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams))
   154  	cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams))
   155  	cmd.AddCommand(NewCmdCreateJob(f, ioStreams))
   156  	cmd.AddCommand(NewCmdCreateCronJob(f, ioStreams))
   157  	cmd.AddCommand(NewCmdCreateIngress(f, ioStreams))
   158  	cmd.AddCommand(NewCmdCreateToken(f, ioStreams))
   159  	return cmd
   160  }
   161  
   162  // Validate makes sure there is no discrepency in command options
   163  func (o *CreateOptions) Validate() error {
   164  	if len(o.Raw) > 0 {
   165  		if o.EditBeforeCreate {
   166  			return fmt.Errorf("--raw and --edit are mutually exclusive")
   167  		}
   168  		if len(o.FilenameOptions.Filenames) != 1 {
   169  			return fmt.Errorf("--raw can only use a single local file or stdin")
   170  		}
   171  		if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 {
   172  			return fmt.Errorf("--raw cannot read from a url")
   173  		}
   174  		if o.FilenameOptions.Recursive {
   175  			return fmt.Errorf("--raw and --recursive are mutually exclusive")
   176  		}
   177  		if len(o.Selector) > 0 {
   178  			return fmt.Errorf("--raw and --selector (-l) are mutually exclusive")
   179  		}
   180  		if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 {
   181  			return fmt.Errorf("--raw and --output are mutually exclusive")
   182  		}
   183  		if _, err := url.ParseRequestURI(o.Raw); err != nil {
   184  			return fmt.Errorf("--raw must be a valid URL path: %v", err)
   185  		}
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  // Complete completes all the required options
   192  func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   193  	if len(args) != 0 {
   194  		return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args)
   195  	}
   196  	var err error
   197  	o.RecordFlags.Complete(cmd)
   198  	o.Recorder, err = o.RecordFlags.ToRecorder()
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   208  
   209  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   210  	if err != nil {
   211  		return err
   212  	}
   213  
   214  	printer, err := o.PrintFlags.ToPrinter()
   215  	if err != nil {
   216  		return err
   217  	}
   218  
   219  	o.PrintObj = func(obj kruntime.Object) error {
   220  		return printer.PrintObj(obj, o.Out)
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  // RunCreate performs the creation
   227  func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
   228  	// raw only makes sense for a single file resource multiple objects aren't likely to do what you want.
   229  	// the validator enforces this, so
   230  	if len(o.Raw) > 0 {
   231  		restClient, err := f.RESTClient()
   232  		if err != nil {
   233  			return err
   234  		}
   235  		return rawhttp.RawPost(restClient, o.IOStreams, o.Raw, o.FilenameOptions.Filenames[0])
   236  	}
   237  
   238  	if o.EditBeforeCreate {
   239  		return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager)
   240  	}
   241  
   242  	schema, err := f.Validator(o.ValidationDirective)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	r := f.NewBuilder().
   253  		Unstructured().
   254  		Schema(schema).
   255  		ContinueOnError().
   256  		NamespaceParam(cmdNamespace).DefaultNamespace().
   257  		FilenameParam(enforceNamespace, &o.FilenameOptions).
   258  		LabelSelectorParam(o.Selector).
   259  		Flatten().
   260  		Do()
   261  	err = r.Err()
   262  	if err != nil {
   263  		return err
   264  	}
   265  
   266  	count := 0
   267  	err = r.Visit(func(info *resource.Info, err error) error {
   268  		if err != nil {
   269  			return err
   270  		}
   271  		if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil {
   272  			return cmdutil.AddSourceToErr("creating", info.Source, err)
   273  		}
   274  
   275  		if err := o.Recorder.Record(info.Object); err != nil {
   276  			klog.V(4).Infof("error recording current command: %v", err)
   277  		}
   278  
   279  		if o.DryRunStrategy != cmdutil.DryRunClient {
   280  			obj, err := resource.
   281  				NewHelper(info.Client, info.Mapping).
   282  				DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
   283  				WithFieldManager(o.fieldManager).
   284  				WithFieldValidation(o.ValidationDirective).
   285  				Create(info.Namespace, true, info.Object)
   286  			if err != nil {
   287  				return cmdutil.AddSourceToErr("creating", info.Source, err)
   288  			}
   289  			info.Refresh(obj, true)
   290  		}
   291  
   292  		count++
   293  
   294  		return o.PrintObj(info.Object)
   295  	})
   296  	if err != nil {
   297  		return err
   298  	}
   299  	if count == 0 {
   300  		return fmt.Errorf("no objects passed to create")
   301  	}
   302  	return nil
   303  }
   304  
   305  // RunEditOnCreate performs edit on creation
   306  func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags, recordFlags *genericclioptions.RecordFlags, ioStreams genericiooptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions, fieldManager string) error {
   307  	editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
   308  	editOptions.FilenameOptions = *options
   309  	validationDirective, err := cmdutil.GetValidationDirective(cmd)
   310  	if err != nil {
   311  		return err
   312  	}
   313  	editOptions.ValidateOptions = cmdutil.ValidateOptions{
   314  		ValidationDirective: string(validationDirective),
   315  	}
   316  	editOptions.PrintFlags = printFlags
   317  	editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   318  	editOptions.RecordFlags = recordFlags
   319  	editOptions.FieldManager = "kubectl-create"
   320  
   321  	err = editOptions.Complete(f, []string{}, cmd)
   322  	if err != nil {
   323  		return err
   324  	}
   325  	return editOptions.Run()
   326  }
   327  
   328  // NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name
   329  func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) {
   330  	argsLen := cmd.ArgsLenAtDash()
   331  	// ArgsLenAtDash returns -1 when -- was not specified
   332  	if argsLen == -1 {
   333  		argsLen = len(args)
   334  	}
   335  	if argsLen != 1 {
   336  		return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", argsLen)
   337  	}
   338  	return args[0], nil
   339  }
   340  
   341  // CreateSubcommandOptions is an options struct to support create subcommands
   342  type CreateSubcommandOptions struct {
   343  	// PrintFlags holds options necessary for obtaining a printer
   344  	PrintFlags *genericclioptions.PrintFlags
   345  	// Name of resource being created
   346  	Name string
   347  	// StructuredGenerator is the resource generator for the object being created
   348  	StructuredGenerator generate.StructuredGenerator
   349  	DryRunStrategy      cmdutil.DryRunStrategy
   350  	CreateAnnotation    bool
   351  	FieldManager        string
   352  	ValidationDirective string
   353  
   354  	Namespace        string
   355  	EnforceNamespace bool
   356  
   357  	Mapper        meta.RESTMapper
   358  	DynamicClient dynamic.Interface
   359  
   360  	PrintObj printers.ResourcePrinterFunc
   361  
   362  	genericiooptions.IOStreams
   363  }
   364  
   365  // NewCreateSubcommandOptions returns initialized CreateSubcommandOptions
   366  func NewCreateSubcommandOptions(ioStreams genericiooptions.IOStreams) *CreateSubcommandOptions {
   367  	return &CreateSubcommandOptions{
   368  		PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
   369  		IOStreams:  ioStreams,
   370  	}
   371  }
   372  
   373  // Complete completes all the required options
   374  func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, generator generate.StructuredGenerator) error {
   375  	name, err := NameFromCommandArgs(cmd, args)
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	o.Name = name
   381  	o.StructuredGenerator = generator
   382  	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
   383  	if err != nil {
   384  		return err
   385  	}
   386  	o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
   387  
   388  	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
   389  	printer, err := o.PrintFlags.ToPrinter()
   390  	if err != nil {
   391  		return err
   392  	}
   393  
   394  	o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
   395  	if err != nil {
   396  		return err
   397  	}
   398  
   399  	o.PrintObj = func(obj kruntime.Object, out io.Writer) error {
   400  		return printer.PrintObj(obj, out)
   401  	}
   402  
   403  	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
   404  	if err != nil {
   405  		return err
   406  	}
   407  
   408  	o.DynamicClient, err = f.DynamicClient()
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	o.Mapper, err = f.ToRESTMapper()
   414  	if err != nil {
   415  		return err
   416  	}
   417  
   418  	return nil
   419  }
   420  
   421  // Run executes a create subcommand using the specified options
   422  func (o *CreateSubcommandOptions) Run() error {
   423  	obj, err := o.StructuredGenerator.StructuredGenerate()
   424  	if err != nil {
   425  		return err
   426  	}
   427  	if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, obj, scheme.DefaultJSONEncoder()); err != nil {
   428  		return err
   429  	}
   430  	if o.DryRunStrategy != cmdutil.DryRunClient {
   431  		// create subcommands have compiled knowledge of things they create, so type them directly
   432  		gvks, _, err := scheme.Scheme.ObjectKinds(obj)
   433  		if err != nil {
   434  			return err
   435  		}
   436  		gvk := gvks[0]
   437  		mapping, err := o.Mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
   438  		if err != nil {
   439  			return err
   440  		}
   441  
   442  		asUnstructured := &unstructured.Unstructured{}
   443  		if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil {
   444  			return err
   445  		}
   446  		if mapping.Scope.Name() == meta.RESTScopeNameRoot {
   447  			o.Namespace = ""
   448  		}
   449  		createOptions := metav1.CreateOptions{}
   450  		if o.FieldManager != "" {
   451  			createOptions.FieldManager = o.FieldManager
   452  		}
   453  		createOptions.FieldValidation = o.ValidationDirective
   454  
   455  		if o.DryRunStrategy == cmdutil.DryRunServer {
   456  			createOptions.DryRun = []string{metav1.DryRunAll}
   457  		}
   458  		actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(context.TODO(), asUnstructured, createOptions)
   459  		if err != nil {
   460  			return err
   461  		}
   462  
   463  		// ensure we pass a versioned object to the printer
   464  		obj = actualObject
   465  	} else {
   466  		if meta, err := meta.Accessor(obj); err == nil && o.EnforceNamespace {
   467  			meta.SetNamespace(o.Namespace)
   468  		}
   469  	}
   470  
   471  	return o.PrintObj(obj, o.Out)
   472  }
   473  

View as plain text