diff --git a/pkg/karmadactl/create/create.go b/pkg/karmadactl/create/create.go new file mode 100644 index 000000000..7f5a1213f --- /dev/null +++ b/pkg/karmadactl/create/create.go @@ -0,0 +1,53 @@ +/* +Copyright 2024 The Karmada 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" + + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericiooptions" + kubectlcreate "k8s.io/kubectl/pkg/cmd/create" + "k8s.io/kubectl/pkg/util/templates" + + "github.com/karmada-io/karmada/pkg/karmadactl/util" +) + +var ( + createLong = templates.LongDesc(` + Create a resource from a file or from stdin. + + JSON and YAML formats are accepted.`) + + createExample = templates.Examples(` + # Create a pod using the data in pod.json + %[1]s create -f ./pod.json + + # Create a pod based on the JSON passed into stdin + cat pod.json | %[1]s create -f - + + # Edit the data in registry.yaml in JSON then create the resource using the edited data + %[1]s create -f registry.yaml --edit -o json`) +) + +// NewCmdCreate returns new initialized instance of create sub command +func NewCmdCreate(f util.Factory, parentCommnd string, ioStreams genericiooptions.IOStreams) *cobra.Command { + cmd := kubectlcreate.NewCmdCreate(f, ioStreams) + cmd.Long = fmt.Sprintf(createLong, parentCommnd) + cmd.Example = fmt.Sprintf(createExample, parentCommnd) + return cmd +} diff --git a/pkg/karmadactl/karmadactl.go b/pkg/karmadactl/karmadactl.go index 87ab2573f..43eede7cf 100644 --- a/pkg/karmadactl/karmadactl.go +++ b/pkg/karmadactl/karmadactl.go @@ -32,6 +32,7 @@ import ( "github.com/karmada-io/karmada/pkg/karmadactl/apply" "github.com/karmada-io/karmada/pkg/karmadactl/cmdinit" "github.com/karmada-io/karmada/pkg/karmadactl/cordon" + "github.com/karmada-io/karmada/pkg/karmadactl/create" "github.com/karmada-io/karmada/pkg/karmadactl/deinit" "github.com/karmada-io/karmada/pkg/karmadactl/describe" "github.com/karmada-io/karmada/pkg/karmadactl/exec" @@ -87,6 +88,7 @@ func NewKarmadaCtlCommand(cmdUse, parentCommand string) *cobra.Command { Message: "Basic Commands:", Commands: []*cobra.Command{ get.NewCmdGet(f, parentCommand, ioStreams), + create.NewCmdCreate(f, parentCommand, ioStreams), }, }, { diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create.go new file mode 100644 index 000000000..17b21c4e0 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create.go @@ -0,0 +1,472 @@ +/* +Copyright 2014 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" + "fmt" + "io" + "net/url" + "runtime" + "strings" + + "github.com/spf13/cobra" + "k8s.io/klog/v2" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kruntime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + "k8s.io/cli-runtime/pkg/printers" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/client-go/dynamic" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/cmd/util/editor" + "k8s.io/kubectl/pkg/generate" + "k8s.io/kubectl/pkg/rawhttp" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +// CreateOptions is the commandline options for 'create' sub command +type CreateOptions struct { + PrintFlags *genericclioptions.PrintFlags + RecordFlags *genericclioptions.RecordFlags + + DryRunStrategy cmdutil.DryRunStrategy + + ValidationDirective string + + fieldManager string + + FilenameOptions resource.FilenameOptions + Selector string + EditBeforeCreate bool + Raw string + + Recorder genericclioptions.Recorder + PrintObj func(obj kruntime.Object) error + + genericiooptions.IOStreams +} + +var ( + createLong = templates.LongDesc(i18n.T(` + Create a resource from a file or from stdin. + + JSON and YAML formats are accepted.`)) + + createExample = templates.Examples(i18n.T(` + # Create a pod using the data in pod.json + kubectl create -f ./pod.json + + # Create a pod based on the JSON passed into stdin + cat pod.json | kubectl create -f - + + # Edit the data in registry.yaml in JSON then create the resource using the edited data + kubectl create -f registry.yaml --edit -o json`)) +) + +// NewCreateOptions returns an initialized CreateOptions instance +func NewCreateOptions(ioStreams genericiooptions.IOStreams) *CreateOptions { + return &CreateOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + RecordFlags: genericclioptions.NewRecordFlags(), + + Recorder: genericclioptions.NoopRecorder{}, + + IOStreams: ioStreams, + } +} + +// NewCmdCreate returns new initialized instance of create sub command +func NewCmdCreate(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "create -f FILENAME", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a resource from a file or from stdin"), + Long: createLong, + Example: createExample, + Run: func(cmd *cobra.Command, args []string) { + if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { + ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n")) + defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut) + defaultRunFunc(cmd, args) + return + } + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunCreate(f, cmd)) + }, + } + + // bind flag structs + o.RecordFlags.AddFlags(cmd) + + usage := "to use to create the resource" + cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) + cmdutil.AddValidateFlags(cmd) + cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating") + cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows", + "Only relevant if --edit=true. Defaults to the line ending native to your platform.") + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector) + cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to POST to the server. Uses the transport specified by the kubeconfig file.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-create") + + o.PrintFlags.AddFlags(cmd) + + // create subcommands + cmd.AddCommand(NewCmdCreateNamespace(f, ioStreams)) + cmd.AddCommand(NewCmdCreateQuota(f, ioStreams)) + cmd.AddCommand(NewCmdCreateSecret(f, ioStreams)) + cmd.AddCommand(NewCmdCreateConfigMap(f, ioStreams)) + cmd.AddCommand(NewCmdCreateServiceAccount(f, ioStreams)) + cmd.AddCommand(NewCmdCreateService(f, ioStreams)) + cmd.AddCommand(NewCmdCreateDeployment(f, ioStreams)) + cmd.AddCommand(NewCmdCreateClusterRole(f, ioStreams)) + cmd.AddCommand(NewCmdCreateClusterRoleBinding(f, ioStreams)) + cmd.AddCommand(NewCmdCreateRole(f, ioStreams)) + cmd.AddCommand(NewCmdCreateRoleBinding(f, ioStreams)) + cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams)) + cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams)) + cmd.AddCommand(NewCmdCreateJob(f, ioStreams)) + cmd.AddCommand(NewCmdCreateCronJob(f, ioStreams)) + cmd.AddCommand(NewCmdCreateIngress(f, ioStreams)) + cmd.AddCommand(NewCmdCreateToken(f, ioStreams)) + return cmd +} + +// Validate makes sure there is no discrepency in command options +func (o *CreateOptions) Validate() error { + if len(o.Raw) > 0 { + if o.EditBeforeCreate { + return fmt.Errorf("--raw and --edit are mutually exclusive") + } + if len(o.FilenameOptions.Filenames) != 1 { + return fmt.Errorf("--raw can only use a single local file or stdin") + } + if strings.Index(o.FilenameOptions.Filenames[0], "http://") == 0 || strings.Index(o.FilenameOptions.Filenames[0], "https://") == 0 { + return fmt.Errorf("--raw cannot read from a url") + } + if o.FilenameOptions.Recursive { + return fmt.Errorf("--raw and --recursive are mutually exclusive") + } + if len(o.Selector) > 0 { + return fmt.Errorf("--raw and --selector (-l) are mutually exclusive") + } + if o.PrintFlags.OutputFormat != nil && len(*o.PrintFlags.OutputFormat) > 0 { + return fmt.Errorf("--raw and --output are mutually exclusive") + } + if _, err := url.ParseRequestURI(o.Raw); err != nil { + return fmt.Errorf("--raw must be a valid URL path: %v", err) + } + } + + return nil +} + +// Complete completes all the required options +func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) + } + var err error + o.RecordFlags.Complete(cmd) + o.Recorder, err = o.RecordFlags.ToRecorder() + if err != nil { + return err + } + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj kruntime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + return nil +} + +// RunCreate performs the creation +func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { + // raw only makes sense for a single file resource multiple objects aren't likely to do what you want. + // the validator enforces this, so + if len(o.Raw) > 0 { + restClient, err := f.RESTClient() + if err != nil { + return err + } + return rawhttp.RawPost(restClient, o.IOStreams, o.Raw, o.FilenameOptions.Filenames[0]) + } + + if o.EditBeforeCreate { + return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager) + } + + schema, err := f.Validator(o.ValidationDirective) + if err != nil { + return err + } + + cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + r := f.NewBuilder(). + Unstructured(). + Schema(schema). + ContinueOnError(). + NamespaceParam(cmdNamespace).DefaultNamespace(). + FilenameParam(enforceNamespace, &o.FilenameOptions). + LabelSelectorParam(o.Selector). + Flatten(). + Do() + err = r.Err() + if err != nil { + return err + } + + count := 0 + err = r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info.Object, scheme.DefaultJSONEncoder()); err != nil { + return cmdutil.AddSourceToErr("creating", info.Source, err) + } + + if err := o.Recorder.Record(info.Object); err != nil { + klog.V(4).Infof("error recording current command: %v", err) + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + obj, err := resource. + NewHelper(info.Client, info.Mapping). + DryRun(o.DryRunStrategy == cmdutil.DryRunServer). + WithFieldManager(o.fieldManager). + WithFieldValidation(o.ValidationDirective). + Create(info.Namespace, true, info.Object) + if err != nil { + return cmdutil.AddSourceToErr("creating", info.Source, err) + } + info.Refresh(obj, true) + } + + count++ + + return o.PrintObj(info.Object) + }) + if err != nil { + return err + } + if count == 0 { + return fmt.Errorf("no objects passed to create") + } + return nil +} + +// RunEditOnCreate performs edit on creation +func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags, recordFlags *genericclioptions.RecordFlags, ioStreams genericiooptions.IOStreams, cmd *cobra.Command, options *resource.FilenameOptions, fieldManager string) error { + editOptions := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams) + editOptions.FilenameOptions = *options + validationDirective, err := cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + editOptions.ValidateOptions = cmdutil.ValidateOptions{ + ValidationDirective: string(validationDirective), + } + editOptions.PrintFlags = printFlags + editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + editOptions.RecordFlags = recordFlags + editOptions.FieldManager = "kubectl-create" + + err = editOptions.Complete(f, []string{}, cmd) + if err != nil { + return err + } + return editOptions.Run() +} + +// NameFromCommandArgs is a utility function for commands that assume the first argument is a resource name +func NameFromCommandArgs(cmd *cobra.Command, args []string) (string, error) { + argsLen := cmd.ArgsLenAtDash() + // ArgsLenAtDash returns -1 when -- was not specified + if argsLen == -1 { + argsLen = len(args) + } + if argsLen != 1 { + return "", cmdutil.UsageErrorf(cmd, "exactly one NAME is required, got %d", argsLen) + } + return args[0], nil +} + +// CreateSubcommandOptions is an options struct to support create subcommands +type CreateSubcommandOptions struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + // Name of resource being created + Name string + // StructuredGenerator is the resource generator for the object being created + StructuredGenerator generate.StructuredGenerator + DryRunStrategy cmdutil.DryRunStrategy + CreateAnnotation bool + FieldManager string + ValidationDirective string + + Namespace string + EnforceNamespace bool + + Mapper meta.RESTMapper + DynamicClient dynamic.Interface + + PrintObj printers.ResourcePrinterFunc + + genericiooptions.IOStreams +} + +// NewCreateSubcommandOptions returns initialized CreateSubcommandOptions +func NewCreateSubcommandOptions(ioStreams genericiooptions.IOStreams) *CreateSubcommandOptions { + return &CreateSubcommandOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// Complete completes all the required options +func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string, generator generate.StructuredGenerator) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + + o.Name = name + o.StructuredGenerator = generator + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + o.PrintObj = func(obj kruntime.Object, out io.Writer) error { + return printer.PrintObj(obj, out) + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.DynamicClient, err = f.DynamicClient() + if err != nil { + return err + } + + o.Mapper, err = f.ToRESTMapper() + if err != nil { + return err + } + + return nil +} + +// Run executes a create subcommand using the specified options +func (o *CreateSubcommandOptions) Run() error { + obj, err := o.StructuredGenerator.StructuredGenerate() + if err != nil { + return err + } + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, obj, scheme.DefaultJSONEncoder()); err != nil { + return err + } + if o.DryRunStrategy != cmdutil.DryRunClient { + // create subcommands have compiled knowledge of things they create, so type them directly + gvks, _, err := scheme.Scheme.ObjectKinds(obj) + if err != nil { + return err + } + gvk := gvks[0] + mapping, err := o.Mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) + if err != nil { + return err + } + + asUnstructured := &unstructured.Unstructured{} + if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil { + return err + } + if mapping.Scope.Name() == meta.RESTScopeNameRoot { + o.Namespace = "" + } + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + actualObject, err := o.DynamicClient.Resource(mapping.Resource).Namespace(o.Namespace).Create(context.TODO(), asUnstructured, createOptions) + if err != nil { + return err + } + + // ensure we pass a versioned object to the printer + obj = actualObject + } else { + if meta, err := meta.Accessor(obj); err == nil && o.EnforceNamespace { + meta.SetNamespace(o.Namespace) + } + } + + return o.PrintObj(obj, o.Out) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go new file mode 100644 index 000000000..6b55eab3e --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go @@ -0,0 +1,227 @@ +/* +Copyright 2017 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/cli-runtime/pkg/genericiooptions" + cliflag "k8s.io/component-base/cli/flag" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + clusterRoleLong = templates.LongDesc(i18n.T(` + Create a cluster role.`)) + + clusterRoleExample = templates.Examples(i18n.T(` + # Create a cluster role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods + kubectl create clusterrole pod-reader --verb=get,list,watch --resource=pods + + # Create a cluster role named "pod-reader" with ResourceName specified + kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod + + # Create a cluster role named "foo" with API Group specified + kubectl create clusterrole foo --verb=get,list,watch --resource=rs.apps + + # Create a cluster role named "foo" with SubResource specified + kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status + + # Create a cluster role name "foo" with NonResourceURL specified + kubectl create clusterrole "foo" --verb=get --non-resource-url=/logs/* + + # Create a cluster role name "monitoring" with AggregationRule specified + kubectl create clusterrole monitoring --aggregation-rule="rbac.example.com/aggregate-to-monitoring=true"`)) + + // Valid nonResource verb list for validation. + validNonResourceVerbs = []string{"*", "get", "post", "put", "delete", "patch", "head", "options"} +) + +// CreateClusterRoleOptions is returned by NewCmdCreateClusterRole +type CreateClusterRoleOptions struct { + *CreateRoleOptions + NonResourceURLs []string + AggregationRule map[string]string + FieldManager string +} + +// NewCmdCreateClusterRole initializes and returns new ClusterRoles command +func NewCmdCreateClusterRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + c := &CreateClusterRoleOptions{ + CreateRoleOptions: NewCreateRoleOptions(ioStreams), + AggregationRule: map[string]string{}, + } + cmd := &cobra.Command{ + Use: "clusterrole NAME --verb=verb --resource=resource.group [--resource-name=resourcename] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a cluster role"), + Long: clusterRoleLong, + Example: clusterRoleExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(c.Complete(f, cmd, args)) + cmdutil.CheckErr(c.Validate()) + cmdutil.CheckErr(c.RunCreateRole()) + }, + } + + c.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().StringSliceVar(&c.Verbs, "verb", c.Verbs, "Verb that applies to the resources contained in the rule") + cmd.Flags().StringSliceVar(&c.NonResourceURLs, "non-resource-url", c.NonResourceURLs, "A partial url that user should have access to.") + cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to") + cmd.Flags().StringArrayVar(&c.ResourceNames, "resource-name", c.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items") + cmd.Flags().Var(cliflag.NewMapStringString(&c.AggregationRule), "aggregation-rule", "An aggregation label selector for combining ClusterRoles.") + cmdutil.AddFieldManagerFlagVar(cmd, &c.FieldManager, "kubectl-create") + + return cmd +} + +// Complete completes all the required options +func (c *CreateClusterRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + // Remove duplicate nonResourceURLs + nonResourceURLs := []string{} + for _, n := range c.NonResourceURLs { + if !arrayContains(nonResourceURLs, n) { + nonResourceURLs = append(nonResourceURLs, n) + } + } + c.NonResourceURLs = nonResourceURLs + + return c.CreateRoleOptions.Complete(f, cmd, args) +} + +// Validate makes sure there is no discrepency in CreateClusterRoleOptions +func (c *CreateClusterRoleOptions) Validate() error { + if c.Name == "" { + return fmt.Errorf("name must be specified") + } + + if len(c.AggregationRule) > 0 { + if len(c.NonResourceURLs) > 0 || len(c.Verbs) > 0 || len(c.Resources) > 0 || len(c.ResourceNames) > 0 { + return fmt.Errorf("aggregation rule must be specified without nonResourceURLs, verbs, resources or resourceNames") + } + return nil + } + + // validate verbs. + if len(c.Verbs) == 0 { + return fmt.Errorf("at least one verb must be specified") + } + + if len(c.Resources) == 0 && len(c.NonResourceURLs) == 0 { + return fmt.Errorf("one of resource or nonResourceURL must be specified") + } + + // validate resources + if len(c.Resources) > 0 { + for _, v := range c.Verbs { + if !arrayContains(validResourceVerbs, v) { + fmt.Fprintf(c.ErrOut, "Warning: '%s' is not a standard resource verb\n", v) + } + } + if err := c.validateResource(); err != nil { + return err + } + } + + //validate non-resource-url + if len(c.NonResourceURLs) > 0 { + for _, v := range c.Verbs { + if !arrayContains(validNonResourceVerbs, v) { + return fmt.Errorf("invalid verb: '%s' for nonResourceURL", v) + } + } + + for _, nonResourceURL := range c.NonResourceURLs { + if nonResourceURL == "*" { + continue + } + + if nonResourceURL == "" || !strings.HasPrefix(nonResourceURL, "/") { + return fmt.Errorf("nonResourceURL should start with /") + } + + if strings.ContainsRune(nonResourceURL[:len(nonResourceURL)-1], '*') { + return fmt.Errorf("nonResourceURL only supports wildcard matches when '*' is at the end") + } + } + } + + return nil + +} + +// RunCreateRole creates a new clusterRole +func (c *CreateClusterRoleOptions) RunCreateRole() error { + clusterRole := &rbacv1.ClusterRole{ + // this is ok because we know exactly how we want to be serialized + TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRole"}, + } + clusterRole.Name = c.Name + + var err error + if len(c.AggregationRule) == 0 { + rules, err := generateResourcePolicyRules(c.Mapper, c.Verbs, c.Resources, c.ResourceNames, c.NonResourceURLs) + if err != nil { + return err + } + clusterRole.Rules = rules + } else { + clusterRole.AggregationRule = &rbacv1.AggregationRule{ + ClusterRoleSelectors: []metav1.LabelSelector{ + { + MatchLabels: c.AggregationRule, + }, + }, + } + } + + if err := util.CreateOrUpdateAnnotation(c.CreateAnnotation, clusterRole, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + // Create ClusterRole. + if c.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if c.FieldManager != "" { + createOptions.FieldManager = c.FieldManager + } + createOptions.FieldValidation = c.ValidationDirective + if c.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + clusterRole, err = c.Client.ClusterRoles().Create(context.TODO(), clusterRole, createOptions) + if err != nil { + return err + } + } + + return c.PrintObj(clusterRole) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go new file mode 100644 index 000000000..3b87019ac --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go @@ -0,0 +1,228 @@ +/* +Copyright 2016 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + rbacv1 "k8s.io/api/rbac/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/genericiooptions" + rbacclientv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/completion" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + clusterRoleBindingLong = templates.LongDesc(i18n.T(` + Create a cluster role binding for a particular cluster role.`)) + + clusterRoleBindingExample = templates.Examples(i18n.T(` + # Create a cluster role binding for user1, user2, and group1 using the cluster-admin cluster role + kubectl create clusterrolebinding cluster-admin --clusterrole=cluster-admin --user=user1 --user=user2 --group=group1`)) +) + +// ClusterRoleBindingOptions is returned by NewCmdCreateClusterRoleBinding +type ClusterRoleBindingOptions struct { + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + Name string + ClusterRole string + Users []string + Groups []string + ServiceAccounts []string + FieldManager string + CreateAnnotation bool + + Client rbacclientv1.RbacV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewClusterRoleBindingOptions creates a new *ClusterRoleBindingOptions with sane defaults +func NewClusterRoleBindingOptions(ioStreams genericiooptions.IOStreams) *ClusterRoleBindingOptions { + return &ClusterRoleBindingOptions{ + Users: []string{}, + Groups: []string{}, + ServiceAccounts: []string{}, + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateClusterRoleBinding returns an initialized command instance of ClusterRoleBinding +func NewCmdCreateClusterRoleBinding(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewClusterRoleBindingOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "clusterrolebinding NAME --clusterrole=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a cluster role binding for a particular cluster role"), + Long: clusterRoleBindingLong, + Example: clusterRoleBindingExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().StringVar(&o.ClusterRole, "clusterrole", "", i18n.T("ClusterRole this ClusterRoleBinding should reference")) + cmd.MarkFlagRequired("clusterrole") + cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the clusterrole. The flag can be repeated to add multiple users.") + cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the clusterrole. The flag can be repeated to add multiple groups.") + cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the clusterrole, in the format :. The flag can be repeated to add multiple service accounts.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + // Completion for relevant flags + cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( + "clusterrole", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completion.CompGetResource(f, "clusterrole", toComplete), cobra.ShellCompDirectiveNoFileComp + })) + + return cmd +} + +// Complete completes all the required options +func (o *ClusterRoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + o.Name, err = NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + + cs, err := f.KubernetesClientSet() + if err != nil { + return err + } + o.Client = cs.RbacV1() + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Run calls the CreateSubcommandOptions.Run in ClusterRoleBindingOptions instance +func (o *ClusterRoleBindingOptions) Run() error { + clusterRoleBinding, err := o.createClusterRoleBinding() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, clusterRoleBinding, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + clusterRoleBinding, err = o.Client.ClusterRoleBindings().Create(context.TODO(), clusterRoleBinding, createOptions) + if err != nil { + return fmt.Errorf("failed to create clusterrolebinding: %v", err) + } + } + + return o.PrintObj(clusterRoleBinding) +} + +func (o *ClusterRoleBindingOptions) createClusterRoleBinding() (*rbacv1.ClusterRoleBinding, error) { + clusterRoleBinding := &rbacv1.ClusterRoleBinding{ + TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "ClusterRoleBinding"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: o.ClusterRole, + }, + } + + for _, user := range o.Users { + clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.UserKind, + APIGroup: rbacv1.GroupName, + Name: user, + }) + } + + for _, group := range o.Groups { + clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.GroupKind, + APIGroup: rbacv1.GroupName, + Name: group, + }) + } + + for _, sa := range o.ServiceAccounts { + tokens := strings.Split(sa, ":") + if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" { + return nil, fmt.Errorf("serviceaccount must be :") + } + clusterRoleBinding.Subjects = append(clusterRoleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.ServiceAccountKind, + APIGroup: "", + Namespace: tokens[0], + Name: tokens[1], + }) + } + + return clusterRoleBinding, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_configmap.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_configmap.go new file mode 100644 index 000000000..988cbb1ef --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_configmap.go @@ -0,0 +1,414 @@ +/* +Copyright 2016 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" + "fmt" + "os" + "path" + "strings" + "unicode/utf8" + + "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/genericiooptions" + 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 ( + configMapLong = templates.LongDesc(i18n.T(` + Create a config map based on a file, directory, or specified literal value. + + A single config map may package one or more key/value pairs. + + When creating a config map 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, you may specify an alternate key. + + When creating a config map based on a directory, each file whose basename is a valid key in the directory will be + packaged into the config map. Any directory entries except regular files are ignored (e.g. subdirectories, + symlinks, devices, pipes, etc).`)) + + configMapExample = templates.Examples(i18n.T(` + # Create a new config map named my-config based on folder bar + kubectl create configmap my-config --from-file=path/to/bar + + # Create a new config map named my-config with specified keys instead of file basenames on disk + kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt + + # Create a new config map named my-config with key1=config1 and key2=config2 + kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2 + + # Create a new config map named my-config from the key=value pairs in the file + kubectl create configmap my-config --from-file=path/to/bar + + # Create a new config map named my-config from an env file + kubectl create configmap my-config --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`)) +) + +// ConfigMapOptions holds properties for create configmap sub-command +type ConfigMapOptions struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + // Name of configMap (required) + Name string + // Type of configMap (optional) + Type string + // FileSources to derive the configMap from (optional) + FileSources []string + // LiteralSources to derive the configMap from (optional) + LiteralSources []string + // EnvFileSources to derive the configMap from (optional) + EnvFileSources []string + // AppendHash; if true, derive a hash from the ConfigMap and append it to the name + AppendHash bool + + FieldManager string + CreateAnnotation bool + Namespace string + EnforceNamespace bool + + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewConfigMapOptions creates a new *ConfigMapOptions with default value +func NewConfigMapOptions(ioStreams genericiooptions.IOStreams) *ConfigMapOptions { + return &ConfigMapOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateConfigMap creates the `create configmap` Cobra command +func NewCmdCreateConfigMap(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewConfigMapOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"cm"}, + Short: i18n.T("Create a config map from a local file, directory or literal value"), + Long: configMapLong, + Example: configMapExample, + 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().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.") + cmd.Flags().StringArrayVar(&o.LiteralSources, "from-literal", o.LiteralSources, "Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)") + 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.") + cmd.Flags().BoolVar(&o.AppendHash, "append-hash", o.AppendHash, "Append a hash of the configmap to its name.") + + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + return cmd +} + +// Complete loads data from the command line environment +func (o *ConfigMapOptions) 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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate checks if ConfigMapOptions has sufficient value to run +func (o *ConfigMapOptions) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if len(o.EnvFileSources) > 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 createConfigMap and filled in value for configMap object +func (o *ConfigMapOptions) Run() error { + configMap, err := o.createConfigMap() + if err != nil { + return err + } + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, configMap, scheme.DefaultJSONEncoder()); err != nil { + return err + } + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + configMap, err = o.Client.ConfigMaps(o.Namespace).Create(context.TODO(), configMap, createOptions) + if err != nil { + return fmt.Errorf("failed to create configmap: %v", err) + } + } + + return o.PrintObj(configMap) +} + +// createConfigMap fills in key value pair from the information given in +// ConfigMapOptions into *corev1.ConfigMap +func (o *ConfigMapOptions) createConfigMap() (*corev1.ConfigMap, error) { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + configMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "ConfigMap", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + }, + } + configMap.Name = o.Name + configMap.Data = map[string]string{} + configMap.BinaryData = map[string][]byte{} + + if len(o.FileSources) > 0 { + if err := handleConfigMapFromFileSources(configMap, o.FileSources); err != nil { + return nil, err + } + } + if len(o.LiteralSources) > 0 { + if err := handleConfigMapFromLiteralSources(configMap, o.LiteralSources); err != nil { + return nil, err + } + } + if len(o.EnvFileSources) > 0 { + if err := handleConfigMapFromEnvFileSources(configMap, o.EnvFileSources); err != nil { + return nil, err + } + } + if o.AppendHash { + hash, err := hash.ConfigMapHash(configMap) + if err != nil { + return nil, err + } + configMap.Name = fmt.Sprintf("%s-%s", configMap.Name, hash) + } + + return configMap, nil +} + +// handleConfigMapFromLiteralSources adds the specified literal source +// information into the provided configMap. +func handleConfigMapFromLiteralSources(configMap *corev1.ConfigMap, literalSources []string) error { + for _, literalSource := range literalSources { + keyName, value, err := util.ParseLiteralSource(literalSource) + if err != nil { + return err + } + err = addKeyFromLiteralToConfigMap(configMap, keyName, value) + if err != nil { + return err + } + } + + return nil +} + +// handleConfigMapFromFileSources adds the specified file source information +// into the provided configMap +func handleConfigMapFromFileSources(configMap *corev1.ConfigMap, fileSources []string) error { + for _, fileSource := range fileSources { + keyName, filePath, err := util.ParseFileSource(fileSource) + if err != nil { + return err + } + info, 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 info.IsDir() { + if strings.Contains(fileSource, "=") { + return fmt.Errorf("cannot give a key name for a directory path") + } + fileList, err := os.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.Type().IsRegular() { + keyName = item.Name() + err = addKeyFromFileToConfigMap(configMap, keyName, itemPath) + if err != nil { + return err + } + } + } + } else { + if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil { + return err + } + + } + } + return nil +} + +// handleConfigMapFromEnvFileSources adds the specified env file source information +// into the provided configMap +func handleConfigMapFromEnvFileSources(configMap *corev1.ConfigMap, envFileSources []string) error { + for _, envFileSource := range envFileSources { + info, 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 info.IsDir() { + return fmt.Errorf("env config file cannot be a directory") + } + err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error { + return addKeyFromLiteralToConfigMap(configMap, key, value) + }) + if err != nil { + return err + } + } + + return nil +} + +// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating +// the value with the content of the given file path, or returns an error. +func addKeyFromFileToConfigMap(configMap *corev1.ConfigMap, keyName, filePath string) error { + data, err := os.ReadFile(filePath) + if err != nil { + return err + } + if utf8.Valid(data) { + return addKeyFromLiteralToConfigMap(configMap, keyName, string(data)) + } + err = validateNewConfigMap(configMap, keyName) + if err != nil { + return err + } + configMap.BinaryData[keyName] = data + + return nil +} + +// addKeyFromLiteralToConfigMap adds the given key and data to the given config map, +// returning an error if the key is not valid or if the key already exists. +func addKeyFromLiteralToConfigMap(configMap *corev1.ConfigMap, keyName, data string) error { + err := validateNewConfigMap(configMap, keyName) + if err != nil { + return err + } + configMap.Data[keyName] = data + + return nil +} + +// validateNewConfigMap checks whether the keyname is valid +// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys. +func validateNewConfigMap(configMap *corev1.ConfigMap, keyName string) error { + if errs := validation.IsConfigMapKey(keyName); len(errs) > 0 { + return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ",")) + } + if _, exists := configMap.Data[keyName]; exists { + return fmt.Errorf("cannot add key %q, another key by that name already exists in Data for ConfigMap %q", keyName, configMap.Name) + } + if _, exists := configMap.BinaryData[keyName]; exists { + return fmt.Errorf("cannot add key %q, another key by that name already exists in BinaryData for ConfigMap %q", keyName, configMap.Name) + } + + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go new file mode 100644 index 000000000..5c0821d9d --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go @@ -0,0 +1,226 @@ +/* +Copyright 2018 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" + "fmt" + + "github.com/spf13/cobra" + + batchv1 "k8s.io/api/batch/v1" + 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/genericiooptions" + "k8s.io/cli-runtime/pkg/resource" + batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + cronjobLong = templates.LongDesc(i18n.T(` + Create a cron job with the specified name.`)) + + cronjobExample = templates.Examples(` + # Create a cron job + kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" + + # Create a cron job with a command + kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" -- date`) +) + +// CreateCronJobOptions is returned by NewCreateCronJobOptions +type CreateCronJobOptions struct { + PrintFlags *genericclioptions.PrintFlags + + PrintObj func(obj runtime.Object) error + + Name string + Image string + Schedule string + Command []string + Restart string + + Namespace string + EnforceNamespace bool + Client batchv1client.BatchV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + Builder *resource.Builder + FieldManager string + CreateAnnotation bool + + genericiooptions.IOStreams +} + +// NewCreateCronJobOptions returns an initialized CreateCronJobOptions instance +func NewCreateCronJobOptions(ioStreams genericiooptions.IOStreams) *CreateCronJobOptions { + return &CreateCronJobOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateCronJob is a command to create CronJobs. +func NewCmdCreateCronJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateCronJobOptions(ioStreams) + cmd := &cobra.Command{ + Use: "cronjob NAME --image=image --schedule='0/5 * * * ?' -- [COMMAND] [args...]", + DisableFlagsInUseLine: false, + Aliases: []string{"cj"}, + Short: i18n.T("Create a cron job with the specified name"), + Long: cronjobLong, + Example: cronjobExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.") + cmd.MarkFlagRequired("image") + cmd.Flags().StringVar(&o.Schedule, "schedule", o.Schedule, "A schedule in the Cron format the job should be run with.") + cmd.MarkFlagRequired("schedule") + cmd.Flags().StringVar(&o.Restart, "restart", o.Restart, "job's restart policy. supported values: OnFailure, Never") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + return cmd +} + +// Complete completes all the required options +func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + if len(args) > 1 { + o.Command = args[1:] + } + if len(o.Restart) == 0 { + o.Restart = "OnFailure" + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = batchv1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + o.Builder = f.NewBuilder() + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Run performs the execution of 'create cronjob' sub command +func (o *CreateCronJobOptions) Run() error { + cronJob := o.createCronJob() + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, cronJob, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + cronJob, err = o.Client.CronJobs(o.Namespace).Create(context.TODO(), cronJob, createOptions) + if err != nil { + return fmt.Errorf("failed to create cronjob: %v", err) + } + } + + return o.PrintObj(cronJob) +} + +func (o *CreateCronJobOptions) createCronJob() *batchv1.CronJob { + cronjob := &batchv1.CronJob{ + TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "CronJob"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Spec: batchv1.CronJobSpec{ + Schedule: o.Schedule, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: o.Name, + Image: o.Image, + Command: o.Command, + }, + }, + RestartPolicy: corev1.RestartPolicy(o.Restart), + }, + }, + }, + }, + }, + } + if o.EnforceNamespace { + cronjob.Namespace = o.Namespace + } + return cronjob +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_deployment.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_deployment.go new file mode 100644 index 000000000..519c7fb62 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_deployment.go @@ -0,0 +1,278 @@ +/* +Copyright 2016 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + utilrand "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + deploymentLong = templates.LongDesc(i18n.T(` + Create a deployment with the specified name.`)) + + deploymentExample = templates.Examples(i18n.T(` + # Create a deployment named my-dep that runs the busybox image + kubectl create deployment my-dep --image=busybox + + # Create a deployment with a command + kubectl create deployment my-dep --image=busybox -- date + + # Create a deployment named my-dep that runs the nginx image with 3 replicas + kubectl create deployment my-dep --image=nginx --replicas=3 + + # Create a deployment named my-dep that runs the busybox image and expose port 5701 + kubectl create deployment my-dep --image=busybox --port=5701 + + # Create a deployment named my-dep that runs multiple containers + kubectl create deployment my-dep --image=busybox:latest --image=ubuntu:latest --image=nginx`)) +) + +// CreateDeploymentOptions is returned by NewCmdCreateDeployment +type CreateDeploymentOptions struct { + PrintFlags *genericclioptions.PrintFlags + + PrintObj func(obj runtime.Object) error + + Name string + Images []string + Port int32 + Replicas int32 + Command []string + Namespace string + EnforceNamespace bool + FieldManager string + CreateAnnotation bool + + Client appsv1client.AppsV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewCreateDeploymentOptions returns an initialized CreateDeploymentOptions instance +func NewCreateDeploymentOptions(ioStreams genericiooptions.IOStreams) *CreateDeploymentOptions { + return &CreateDeploymentOptions{ + Port: -1, + Replicas: 1, + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateDeployment is a macro command to create a new deployment. +// This command is better known to users as `kubectl create deployment`. +func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateDeploymentOptions(ioStreams) + cmd := &cobra.Command{ + Use: "deployment NAME --image=image -- [COMMAND] [args...]", + DisableFlagsInUseLine: true, + Aliases: []string{"deploy"}, + Short: i18n.T("Create a deployment with the specified name"), + Long: deploymentLong, + Example: deploymentExample, + 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().StringSliceVar(&o.Images, "image", o.Images, "Image names to run. A deployment can have multiple images set for multi-container pod.") + cmd.MarkFlagRequired("image") + cmd.Flags().Int32Var(&o.Port, "port", o.Port, "The containerPort that this deployment exposes.") + cmd.Flags().Int32VarP(&o.Replicas, "replicas", "r", o.Replicas, "Number of replicas to create. Default is 1.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + return cmd +} + +// Complete completes all the options +func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + if len(args) > 1 { + o.Command = args[1:] + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = appsv1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate makes sure there is no discrepency in provided option values +func (o *CreateDeploymentOptions) Validate() error { + if len(o.Images) > 1 && len(o.Command) > 0 { + return fmt.Errorf("cannot specify multiple --image options and command") + } + return nil +} + +// Run performs the execution of 'create deployment' sub command +func (o *CreateDeploymentOptions) Run() error { + deploy := o.createDeployment() + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, deploy, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + deploy, err = o.Client.Deployments(o.Namespace).Create(context.TODO(), deploy, createOptions) + if err != nil { + return fmt.Errorf("failed to create deployment: %v", err) + } + } + + return o.PrintObj(deploy) +} + +func (o *CreateDeploymentOptions) createDeployment() *appsv1.Deployment { + labels := map[string]string{"app": o.Name} + selector := metav1.LabelSelector{MatchLabels: labels} + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + deploy := &appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Labels: labels, + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &o.Replicas, + Selector: &selector, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: o.buildPodSpec(), + }, + }, + } + + if o.Port >= 0 && len(deploy.Spec.Template.Spec.Containers) > 0 { + deploy.Spec.Template.Spec.Containers[0].Ports = []corev1.ContainerPort{{ContainerPort: o.Port}} + } + return deploy +} + +// buildPodSpec parses the image strings and assemble them into the Containers +// of a PodSpec. This is all you need to create the PodSpec for a deployment. +func (o *CreateDeploymentOptions) buildPodSpec() corev1.PodSpec { + podSpec := corev1.PodSpec{Containers: []corev1.Container{}} + for _, imageString := range o.Images { + // Retain just the image name + imageSplit := strings.Split(imageString, "/") + name := imageSplit[len(imageSplit)-1] + // Remove any tag or hash + if strings.Contains(name, ":") { + name = strings.Split(name, ":")[0] + } + if strings.Contains(name, "@") { + name = strings.Split(name, "@")[0] + } + name = sanitizeAndUniquify(name) + podSpec.Containers = append(podSpec.Containers, corev1.Container{ + Name: name, + Image: imageString, + Command: o.Command, + }) + } + return podSpec +} + +// sanitizeAndUniquify replaces characters like "." or "_" into "-" to follow DNS1123 rules. +// Then add random suffix to make it uniquified. +func sanitizeAndUniquify(name string) string { + if strings.ContainsAny(name, "_.") { + name = strings.Replace(name, "_", "-", -1) + name = strings.Replace(name, ".", "-", -1) + name = fmt.Sprintf("%s-%s", name, utilrand.String(5)) + } + return name +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_ingress.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_ingress.go new file mode 100644 index 000000000..d71048cdc --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_ingress.go @@ -0,0 +1,458 @@ +/* +Copyright 2020 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" + "fmt" + "regexp" + "strings" + + "github.com/spf13/cobra" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + networkingv1client "k8s.io/client-go/kubernetes/typed/networking/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + // Explaining the Regex below: + // ^(?P[\w\*\-\.]*) -> Indicates the host - 0-N characters of letters, number, underscore, '-', '.' and '*' + // (?P/.*) -> Indicates the path and MUST start with '/' - / + 0-N characters + // Separator from host/path to svcname:svcport -> "=" + // (?P[\w\-]+) -> Service Name (letters, numbers, '-') -> 1-N characters + // Separator from svcname to svcport -> ":" + // (?P[\w\-]+) -> Service Port (letters, numbers, '-') -> 1-N characters + regexHostPathSvc = `^(?P[\w\*\-\.]*)(?P/.*)=(?P[\w\-]+):(?P[\w\-]+)` + + // This Regex is optional -> (....)? + // (?Ptls) -> Verify if the argument after "," is 'tls' + // Optional Separator from tls to the secret name -> "=?" + // (?P[\w\-]+)? -> Optional secret name after the separator -> 1-N characters + regexTLS = `(,(?Ptls)=?(?P[\w\-]+)?)?` + + // The validation Regex is the concatenation of hostPathSvc validation regex + // and the TLS validation regex + ruleRegex = regexHostPathSvc + regexTLS + + ingressLong = templates.LongDesc(i18n.T(` + Create an ingress with the specified name.`)) + + ingressExample = templates.Examples(i18n.T(` + # Create a single ingress called 'simple' that directs requests to foo.com/bar to svc + # svc1:8080 with a TLS secret "my-cert" + kubectl create ingress simple --rule="foo.com/bar=svc1:8080,tls=my-cert" + + # Create a catch all ingress of "/path" pointing to service svc:port and Ingress Class as "otheringress" + kubectl create ingress catch-all --class=otheringress --rule="/path=svc:port" + + # Create an ingress with two annotations: ingress.annotation1 and ingress.annotations2 + kubectl create ingress annotated --class=default --rule="foo.com/bar=svc:port" \ + --annotation ingress.annotation1=foo \ + --annotation ingress.annotation2=bla + + # Create an ingress with the same host and multiple paths + kubectl create ingress multipath --class=default \ + --rule="foo.com/=svc:port" \ + --rule="foo.com/admin/=svcadmin:portadmin" + + # Create an ingress with multiple hosts and the pathType as Prefix + kubectl create ingress ingress1 --class=default \ + --rule="foo.com/path*=svc:8080" \ + --rule="bar.com/admin*=svc2:http" + + # Create an ingress with TLS enabled using the default ingress certificate and different path types + kubectl create ingress ingtls --class=default \ + --rule="foo.com/=svc:https,tls" \ + --rule="foo.com/path/subpath*=othersvc:8080" + + # Create an ingress with TLS enabled using a specific secret and pathType as Prefix + kubectl create ingress ingsecret --class=default \ + --rule="foo.com/*=svc:8080,tls=secret1" + + # Create an ingress with a default backend + kubectl create ingress ingdefault --class=default \ + --default-backend=defaultsvc:http \ + --rule="foo.com/*=svc:8080,tls=secret1" + + `)) +) + +// CreateIngressOptions is returned by NewCmdCreateIngress +type CreateIngressOptions struct { + PrintFlags *genericclioptions.PrintFlags + + PrintObj func(obj runtime.Object) error + + Name string + IngressClass string + Rules []string + Annotations []string + DefaultBackend string + Namespace string + EnforceNamespace bool + CreateAnnotation bool + + Client networkingv1client.NetworkingV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + FieldManager string + + genericiooptions.IOStreams +} + +// NewCreateIngressOptions creates the CreateIngressOptions to be used later +func NewCreateIngressOptions(ioStreams genericiooptions.IOStreams) *CreateIngressOptions { + return &CreateIngressOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateIngress is a macro command to create a new ingress. +// This command is better known to users as `kubectl create ingress`. +func NewCmdCreateIngress(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateIngressOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "ingress NAME --rule=host/path=service:port[,tls[=secret]] ", + DisableFlagsInUseLine: true, + Aliases: []string{"ing"}, + Short: i18n.T("Create an ingress with the specified name"), + Long: ingressLong, + Example: ingressExample, + 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.IngressClass, "class", o.IngressClass, "Ingress Class to be used") + cmd.Flags().StringArrayVar(&o.Rules, "rule", o.Rules, "Rule in format host/path=service:port[,tls=secretname]. Paths containing the leading character '*' are considered pathType=Prefix. tls argument is optional.") + cmd.Flags().StringVar(&o.DefaultBackend, "default-backend", o.DefaultBackend, "Default service for backend, in format of svcname:port") + cmd.Flags().StringArrayVar(&o.Annotations, "annotation", o.Annotations, "Annotation to insert in the ingress object, in the format annotation=value") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + return cmd +} + +// Complete completes all the options +func (o *CreateIngressOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = networkingv1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + return err +} + +// Validate validates the Ingress object to be created +func (o *CreateIngressOptions) Validate() error { + if len(o.DefaultBackend) == 0 && len(o.Rules) == 0 { + return fmt.Errorf("not enough information provided: every ingress has to either specify a default-backend (which catches all traffic) or a list of rules (which catch specific paths)") + } + + rulevalidation, err := regexp.Compile(ruleRegex) + if err != nil { + return fmt.Errorf("failed to compile the regex") + } + + for _, rule := range o.Rules { + if match := rulevalidation.MatchString(rule); !match { + return fmt.Errorf("rule %s is invalid and should be in format host/path=svcname:svcport[,tls[=secret]]", rule) + } + } + + for _, annotation := range o.Annotations { + if an := strings.SplitN(annotation, "=", 2); len(an) != 2 { + return fmt.Errorf("annotation %s is invalid and should be in format key=[value]", annotation) + } + } + + if len(o.DefaultBackend) > 0 && len(strings.Split(o.DefaultBackend, ":")) != 2 { + return fmt.Errorf("default-backend should be in format servicename:serviceport") + } + + return nil +} + +// Run performs the execution of 'create ingress' sub command +func (o *CreateIngressOptions) Run() error { + ingress := o.createIngress() + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, ingress, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + ingress, err = o.Client.Ingresses(o.Namespace).Create(context.TODO(), ingress, createOptions) + if err != nil { + return fmt.Errorf("failed to create ingress: %v", err) + } + } + return o.PrintObj(ingress) +} + +func (o *CreateIngressOptions) createIngress() *networkingv1.Ingress { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + annotations := o.buildAnnotations() + spec := o.buildIngressSpec() + + ingress := &networkingv1.Ingress{ + TypeMeta: metav1.TypeMeta{APIVersion: networkingv1.SchemeGroupVersion.String(), Kind: "Ingress"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + Annotations: annotations, + }, + Spec: spec, + } + return ingress +} + +func (o *CreateIngressOptions) buildAnnotations() map[string]string { + + var annotations = make(map[string]string) + + for _, annotation := range o.Annotations { + an := strings.SplitN(annotation, "=", 2) + annotations[an[0]] = an[1] + } + return annotations +} + +// buildIngressSpec builds the .spec from the diverse arguments passed to kubectl +func (o *CreateIngressOptions) buildIngressSpec() networkingv1.IngressSpec { + var ingressSpec networkingv1.IngressSpec + + if len(o.IngressClass) > 0 { + ingressSpec.IngressClassName = &o.IngressClass + } + + if len(o.DefaultBackend) > 0 { + defaultbackend := buildIngressBackendSvc(o.DefaultBackend) + ingressSpec.DefaultBackend = &defaultbackend + } + ingressSpec.TLS = o.buildTLSRules() + ingressSpec.Rules = o.buildIngressRules() + + return ingressSpec +} + +func (o *CreateIngressOptions) buildTLSRules() []networkingv1.IngressTLS { + hostAlreadyPresent := make(map[string]struct{}) + + ingressTLSs := []networkingv1.IngressTLS{} + var secret string + + for _, rule := range o.Rules { + tls := strings.Split(rule, ",") + + if len(tls) == 2 { + ingressTLS := networkingv1.IngressTLS{} + host := strings.SplitN(rule, "/", 2)[0] + secret = "" + secretName := strings.Split(tls[1], "=") + + if len(secretName) > 1 { + secret = secretName[1] + } + + idxSecret := getIndexSecret(secret, ingressTLSs) + // We accept the same host into TLS secrets only once + if _, ok := hostAlreadyPresent[host]; !ok { + if idxSecret > -1 { + ingressTLSs[idxSecret].Hosts = append(ingressTLSs[idxSecret].Hosts, host) + hostAlreadyPresent[host] = struct{}{} + continue + } + if host != "" { + ingressTLS.Hosts = append(ingressTLS.Hosts, host) + } + if secret != "" { + ingressTLS.SecretName = secret + } + if len(ingressTLS.SecretName) > 0 || len(ingressTLS.Hosts) > 0 { + ingressTLSs = append(ingressTLSs, ingressTLS) + } + hostAlreadyPresent[host] = struct{}{} + } + } + } + return ingressTLSs +} + +// buildIngressRules builds the .spec.rules for an ingress object. +func (o *CreateIngressOptions) buildIngressRules() []networkingv1.IngressRule { + ingressRules := []networkingv1.IngressRule{} + + for _, rule := range o.Rules { + removeTLS := strings.Split(rule, ",")[0] + hostSplit := strings.SplitN(removeTLS, "/", 2) + host := hostSplit[0] + ingressPath := buildHTTPIngressPath(hostSplit[1]) + ingressRule := networkingv1.IngressRule{} + + if host != "" { + ingressRule.Host = host + } + + idxHost := getIndexHost(ingressRule.Host, ingressRules) + if idxHost > -1 { + ingressRules[idxHost].IngressRuleValue.HTTP.Paths = append(ingressRules[idxHost].IngressRuleValue.HTTP.Paths, ingressPath) + continue + } + + ingressRule.IngressRuleValue = networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + ingressPath, + }, + }, + } + ingressRules = append(ingressRules, ingressRule) + } + return ingressRules +} + +func buildHTTPIngressPath(pathsvc string) networkingv1.HTTPIngressPath { + pathsvcsplit := strings.Split(pathsvc, "=") + path := "/" + pathsvcsplit[0] + service := pathsvcsplit[1] + + var pathType networkingv1.PathType + pathType = "Exact" + + // If * in the End, turn pathType=Prefix but remove the * from the end + if path[len(path)-1:] == "*" { + pathType = "Prefix" + path = path[0 : len(path)-1] + } + + httpIngressPath := networkingv1.HTTPIngressPath{ + Path: path, + PathType: &pathType, + Backend: buildIngressBackendSvc(service), + } + return httpIngressPath +} + +func buildIngressBackendSvc(service string) networkingv1.IngressBackend { + svcname := strings.Split(service, ":")[0] + svcport := strings.Split(service, ":")[1] + + ingressBackend := networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: svcname, + Port: parseServiceBackendPort(svcport), + }, + } + return ingressBackend +} + +func parseServiceBackendPort(port string) networkingv1.ServiceBackendPort { + var backendPort networkingv1.ServiceBackendPort + portIntOrStr := intstr.Parse(port) + + if portIntOrStr.Type == intstr.Int { + backendPort.Number = portIntOrStr.IntVal + } + + if portIntOrStr.Type == intstr.String { + backendPort.Name = portIntOrStr.StrVal + } + return backendPort +} + +func getIndexHost(host string, rules []networkingv1.IngressRule) int { + for index, v := range rules { + if v.Host == host { + return index + } + } + return -1 +} + +func getIndexSecret(secretname string, tls []networkingv1.IngressTLS) int { + for index, v := range tls { + if v.SecretName == secretname { + return index + } + } + return -1 +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_job.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_job.go new file mode 100644 index 000000000..811720d4f --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_job.go @@ -0,0 +1,287 @@ +/* +Copyright 2018 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" + "fmt" + + "github.com/spf13/cobra" + + batchv1 "k8s.io/api/batch/v1" + 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/genericiooptions" + "k8s.io/cli-runtime/pkg/resource" + batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" + "k8s.io/utils/ptr" +) + +var ( + jobLong = templates.LongDesc(i18n.T(` + Create a job with the specified name.`)) + + jobExample = templates.Examples(i18n.T(` + # Create a job + kubectl create job my-job --image=busybox + + # Create a job with a command + kubectl create job my-job --image=busybox -- date + + # Create a job from a cron job named "a-cronjob" + kubectl create job test-job --from=cronjob/a-cronjob`)) +) + +// CreateJobOptions is the command line options for 'create job' +type CreateJobOptions struct { + PrintFlags *genericclioptions.PrintFlags + + PrintObj func(obj runtime.Object) error + + Name string + Image string + From string + Command []string + + Namespace string + EnforceNamespace bool + Client batchv1client.BatchV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + Builder *resource.Builder + FieldManager string + CreateAnnotation bool + + genericiooptions.IOStreams +} + +// NewCreateJobOptions initializes and returns new CreateJobOptions instance +func NewCreateJobOptions(ioStreams genericiooptions.IOStreams) *CreateJobOptions { + return &CreateJobOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateJob is a command to ease creating Jobs from CronJobs. +func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateJobOptions(ioStreams) + cmd := &cobra.Command{ + Use: "job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a job with the specified name"), + Long: jobLong, + Example: jobExample, + 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.Image, "image", o.Image, "Image name to run.") + cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + if len(args) > 1 { + o.Command = args[1:] + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = batchv1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + o.Builder = f.NewBuilder() + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate makes sure provided values and valid Job options +func (o *CreateJobOptions) Validate() error { + if (len(o.Image) == 0 && len(o.From) == 0) || (len(o.Image) != 0 && len(o.From) != 0) { + return fmt.Errorf("either --image or --from must be specified") + } + if o.Command != nil && len(o.Command) != 0 && len(o.From) != 0 { + return fmt.Errorf("cannot specify --from and command") + } + return nil +} + +// Run performs the execution of 'create job' sub command +func (o *CreateJobOptions) Run() error { + var job *batchv1.Job + if len(o.Image) > 0 { + job = o.createJob() + } else { + infos, err := o.Builder. + WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...). + NamespaceParam(o.Namespace).DefaultNamespace(). + ResourceTypeOrNameArgs(false, o.From). + Flatten(). + Latest(). + Do(). + Infos() + if err != nil { + return err + } + if len(infos) != 1 { + return fmt.Errorf("from must be an existing cronjob") + } + + switch obj := infos[0].Object.(type) { + case *batchv1.CronJob: + job = o.createJobFromCronJob(obj) + default: + return fmt.Errorf("unknown object type %T", obj) + } + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, job, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + job, err = o.Client.Jobs(o.Namespace).Create(context.TODO(), job, createOptions) + if err != nil { + return fmt.Errorf("failed to create job: %v", err) + } + } + + return o.PrintObj(job) +} + +func (o *CreateJobOptions) createJob() *batchv1.Job { + job := &batchv1.Job{ + // this is ok because we know exactly how we want to be serialized + TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: o.Name, + Image: o.Image, + Command: o.Command, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + } + if o.EnforceNamespace { + job.Namespace = o.Namespace + } + return job +} + +func (o *CreateJobOptions) createJobFromCronJob(cronJob *batchv1.CronJob) *batchv1.Job { + annotations := make(map[string]string) + annotations["cronjob.kubernetes.io/instantiate"] = "manual" + for k, v := range cronJob.Spec.JobTemplate.Annotations { + annotations[k] = v + } + + job := &batchv1.Job{ + // this is ok because we know exactly how we want to be serialized + TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Annotations: annotations, + Labels: cronJob.Spec.JobTemplate.Labels, + OwnerReferences: []metav1.OwnerReference{ + { + // we are not using metav1.NewControllerRef because it + // sets BlockOwnerDeletion to true which additionally mandates + // cronjobs/finalizer role and not backwards-compatible. + APIVersion: batchv1.SchemeGroupVersion.String(), + Kind: "CronJob", + Name: cronJob.GetName(), + UID: cronJob.GetUID(), + Controller: ptr.To(true), + }, + }, + }, + Spec: cronJob.Spec.JobTemplate.Spec, + } + if o.EnforceNamespace { + job.Namespace = o.Namespace + } + return job +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_namespace.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_namespace.go new file mode 100644 index 000000000..047e973fa --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_namespace.go @@ -0,0 +1,179 @@ +/* +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 ( + "context" + "fmt" + + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/runtime" + coreclient "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + namespaceLong = templates.LongDesc(i18n.T(` + Create a namespace with the specified name.`)) + + namespaceExample = templates.Examples(i18n.T(` + # Create a new namespace named my-namespace + kubectl create namespace my-namespace`)) +) + +// NamespaceOptions is the options for 'create namespace' sub command +type NamespaceOptions struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + // Name of resource being created + Name string + + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + CreateAnnotation bool + FieldManager string + + Client *coreclient.CoreV1Client + + PrintObj func(obj runtime.Object) error + + genericiooptions.IOStreams +} + +// NewNamespaceOptions creates a new *NamespaceOptions with sane defaults +func NewNamespaceOptions(ioStreams genericiooptions.IOStreams) *NamespaceOptions { + return &NamespaceOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateNamespace is a macro command to create a new namespace +func NewCmdCreateNamespace(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + + o := NewNamespaceOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "namespace NAME [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"ns"}, + Short: i18n.T("Create a namespace with the specified name"), + Long: namespaceLong, + Example: namespaceExample, + 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) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + + return cmd +} + +// Complete completes all the required options +func (o *NamespaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + + restConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = coreclient.NewForConfig(restConfig) + if err != nil { + return err + } + + o.Name = name + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + return err +} + +// Run calls the CreateSubcommandOptions.Run in NamespaceOpts instance +func (o *NamespaceOptions) Run() error { + namespace := o.createNamespace() + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, namespace, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + namespace, err = o.Client.Namespaces().Create(context.TODO(), namespace, createOptions) + if err != nil { + return err + } + } + return o.PrintObj(namespace) +} + +// createNamespace outputs a namespace object using the configured fields +func (o *NamespaceOptions) createNamespace() *corev1.Namespace { + namespace := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Namespace"}, + ObjectMeta: metav1.ObjectMeta{Name: o.Name}, + } + return namespace +} + +// Validate validates required fields are set to support structured generation +func (o *NamespaceOptions) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_pdb.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_pdb.go new file mode 100644 index 000000000..0023e7481 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_pdb.go @@ -0,0 +1,261 @@ +/* +Copyright 2016 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" + "fmt" + "regexp" + + "github.com/spf13/cobra" + + policyv1 "k8s.io/api/policy/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + policyv1client "k8s.io/client-go/kubernetes/typed/policy/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + pdbLong = templates.LongDesc(i18n.T(` + Create a pod disruption budget with the specified name, selector, and desired minimum available pods.`)) + + pdbExample = templates.Examples(i18n.T(` + # Create a pod disruption budget named my-pdb that will select all pods with the app=rails label + # and require at least one of them being available at any point in time + kubectl create poddisruptionbudget my-pdb --selector=app=rails --min-available=1 + + # Create a pod disruption budget named my-pdb that will select all pods with the app=nginx label + # and require at least half of the pods selected to be available at any point in time + kubectl create pdb my-pdb --selector=app=nginx --min-available=50%`)) +) + +// PodDisruptionBudgetOpts holds the command-line options for poddisruptionbudget sub command +type PodDisruptionBudgetOpts struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + // Name of resource being created + Name string + + MinAvailable string + MaxUnavailable string + + // A label selector to use for this budget + Selector string + CreateAnnotation bool + FieldManager string + Namespace string + EnforceNamespace bool + + Client *policyv1client.PolicyV1Client + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewPodDisruptionBudgetOpts creates a new *PodDisruptionBudgetOpts with sane defaults +func NewPodDisruptionBudgetOpts(ioStreams genericiooptions.IOStreams) *PodDisruptionBudgetOpts { + return &PodDisruptionBudgetOpts{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreatePodDisruptionBudget is a macro command to create a new pod disruption budget. +func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewPodDisruptionBudgetOpts(ioStreams) + + cmd := &cobra.Command{ + Use: "poddisruptionbudget NAME --selector=SELECTOR --min-available=N [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"pdb"}, + Short: i18n.T("Create a pod disruption budget with the specified name"), + Long: pdbLong, + Example: pdbExample, + 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.MinAvailable, "min-available", o.MinAvailable, i18n.T("The minimum number or percentage of available pods this budget requires.")) + cmd.Flags().StringVar(&o.MaxUnavailable, "max-unavailable", o.MaxUnavailable, i18n.T("The maximum number or percentage of unavailable pods this budget requires.")) + cmd.Flags().StringVar(&o.Selector, "selector", o.Selector, i18n.T("A label selector to use for this budget. Only equality-based selector requirements are supported.")) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *PodDisruptionBudgetOpts) 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 = policyv1client.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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate checks to the PodDisruptionBudgetOpts to see if there is sufficient information run the command +func (o *PodDisruptionBudgetOpts) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + + if len(o.Selector) == 0 { + return fmt.Errorf("a selector must be specified") + } + + if len(o.MaxUnavailable) == 0 && len(o.MinAvailable) == 0 { + return fmt.Errorf("one of min-available or max-unavailable must be specified") + } + + if len(o.MaxUnavailable) > 0 && len(o.MinAvailable) > 0 { + return fmt.Errorf("min-available and max-unavailable cannot be both specified") + } + + // The following regex matches the following values: + // 10, 20, 30%, 50% (number and percentage) + // but not 10Gb, 20Mb + re := regexp.MustCompile(`^[0-9]+%?$`) + + switch { + case len(o.MinAvailable) > 0 && !re.MatchString(o.MinAvailable): + return fmt.Errorf("invalid format specified for min-available") + case len(o.MaxUnavailable) > 0 && !re.MatchString(o.MaxUnavailable): + return fmt.Errorf("invalid format specified for max-unavailable") + } + + return nil +} + +// Run calls the CreateSubcommandOptions.Run in PodDisruptionBudgetOpts instance +func (o *PodDisruptionBudgetOpts) Run() error { + podDisruptionBudget, err := o.createPodDisruptionBudgets() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, podDisruptionBudget, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + podDisruptionBudget, err = o.Client.PodDisruptionBudgets(o.Namespace).Create(context.TODO(), podDisruptionBudget, createOptions) + if err != nil { + return fmt.Errorf("failed to create poddisruptionbudgets: %v", err) + } + } + return o.PrintObj(podDisruptionBudget) +} + +func (o *PodDisruptionBudgetOpts) createPodDisruptionBudgets() (*policyv1.PodDisruptionBudget, error) { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + podDisruptionBudget := &policyv1.PodDisruptionBudget{ + TypeMeta: metav1.TypeMeta{ + APIVersion: policyv1.SchemeGroupVersion.String(), + Kind: "PodDisruptionBudget", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + }, + } + + selector, err := metav1.ParseToLabelSelector(o.Selector) + if err != nil { + return nil, err + } + + podDisruptionBudget.Spec.Selector = selector + + switch { + case len(o.MinAvailable) > 0: + minAvailable := intstr.Parse(o.MinAvailable) + podDisruptionBudget.Spec.MinAvailable = &minAvailable + case len(o.MaxUnavailable) > 0: + maxUnavailable := intstr.Parse(o.MaxUnavailable) + podDisruptionBudget.Spec.MaxUnavailable = &maxUnavailable + } + + return podDisruptionBudget, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go new file mode 100644 index 000000000..fe1cd51fc --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go @@ -0,0 +1,198 @@ +/* +Copyright 2017 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" + "fmt" + + "github.com/spf13/cobra" + + corev1 "k8s.io/api/core/v1" + schedulingv1 "k8s.io/api/scheduling/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/genericiooptions" + schedulingv1client "k8s.io/client-go/kubernetes/typed/scheduling/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + pcLong = templates.LongDesc(i18n.T(` + Create a priority class with the specified name, value, globalDefault and description.`)) + + pcExample = templates.Examples(i18n.T(` + # Create a priority class named high-priority + kubectl create priorityclass high-priority --value=1000 --description="high priority" + + # Create a priority class named default-priority that is considered as the global default priority + kubectl create priorityclass default-priority --value=1000 --global-default=true --description="default priority" + + # Create a priority class named high-priority that cannot preempt pods with lower priority + kubectl create priorityclass high-priority --value=1000 --description="high priority" --preemption-policy="Never"`)) +) + +// PriorityClassOptions holds the options for 'create priorityclass' sub command +type PriorityClassOptions struct { + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + Name string + Value int32 + GlobalDefault bool + Description string + PreemptionPolicy string + FieldManager string + CreateAnnotation bool + + Client *schedulingv1client.SchedulingV1Client + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewPriorityClassOptions returns an initialized PriorityClassOptions instance +func NewPriorityClassOptions(ioStreams genericiooptions.IOStreams) *PriorityClassOptions { + return &PriorityClassOptions{ + Value: 0, + PreemptionPolicy: "PreemptLowerPriority", + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreatePriorityClass is a macro command to create a new priorityClass. +func NewCmdCreatePriorityClass(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewPriorityClassOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "priorityclass NAME --value=VALUE --global-default=BOOL [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"pc"}, + Short: i18n.T("Create a priority class with the specified name"), + Long: pcLong, + Example: pcExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Run()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().Int32Var(&o.Value, "value", o.Value, i18n.T("the value of this priority class.")) + cmd.Flags().BoolVar(&o.GlobalDefault, "global-default", o.GlobalDefault, i18n.T("global-default specifies whether this PriorityClass should be considered as the default priority.")) + cmd.Flags().StringVar(&o.Description, "description", o.Description, i18n.T("description is an arbitrary string that usually provides guidelines on when this priority class should be used.")) + cmd.Flags().StringVar(&o.PreemptionPolicy, "preemption-policy", o.PreemptionPolicy, i18n.T("preemption-policy is the policy for preempting pods with lower priority.")) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *PriorityClassOptions) 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 = schedulingv1client.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 + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Run calls the CreateSubcommandOptions.Run in the PriorityClassOptions instance +func (o *PriorityClassOptions) Run() error { + priorityClass, err := o.createPriorityClass() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, priorityClass, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + priorityClass, err = o.Client.PriorityClasses().Create(context.TODO(), priorityClass, createOptions) + if err != nil { + return fmt.Errorf("failed to create priorityclass: %v", err) + } + } + + return o.PrintObj(priorityClass) +} + +func (o *PriorityClassOptions) createPriorityClass() (*schedulingv1.PriorityClass, error) { + preemptionPolicy := corev1.PreemptionPolicy(o.PreemptionPolicy) + return &schedulingv1.PriorityClass{ + // this is ok because we know exactly how we want to be serialized + TypeMeta: metav1.TypeMeta{APIVersion: schedulingv1.SchemeGroupVersion.String(), Kind: "PriorityClass"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Value: o.Value, + GlobalDefault: o.GlobalDefault, + Description: o.Description, + PreemptionPolicy: &preemptionPolicy, + }, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_quota.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_quota.go new file mode 100644 index 000000000..d39662747 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_quota.go @@ -0,0 +1,268 @@ +/* +Copyright 2016 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + corev1 "k8s.io/api/core/v1" + resourceapi "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + coreclient "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/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + quotaLong = templates.LongDesc(i18n.T(` + Create a resource quota with the specified name, hard limits, and optional scopes.`)) + + quotaExample = templates.Examples(i18n.T(` + # Create a new resource quota named my-quota + kubectl create quota my-quota --hard=cpu=1,memory=1G,pods=2,services=3,replicationcontrollers=2,resourcequotas=1,secrets=5,persistentvolumeclaims=10 + + # Create a new resource quota named best-effort + kubectl create quota best-effort --hard=pods=100 --scopes=BestEffort`)) +) + +// QuotaOpts holds the command-line options for 'create quota' sub command +type QuotaOpts struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + // The name of a quota object. + Name string + // The hard resource limit string before parsing. + Hard string + // The scopes of a quota object before parsing. + Scopes string + CreateAnnotation bool + FieldManager string + Namespace string + EnforceNamespace bool + + Client *coreclient.CoreV1Client + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewQuotaOpts creates a new *QuotaOpts with sane defaults +func NewQuotaOpts(ioStreams genericiooptions.IOStreams) *QuotaOpts { + return &QuotaOpts{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateQuota is a macro command to create a new quota +func NewCmdCreateQuota(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewQuotaOpts(ioStreams) + + cmd := &cobra.Command{ + Use: "quota NAME [--hard=key1=value1,key2=value2] [--scopes=Scope1,Scope2] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"resourcequota"}, + Short: i18n.T("Create a quota with the specified name"), + Long: quotaLong, + Example: quotaExample, + 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.Hard, "hard", o.Hard, i18n.T("A comma-delimited set of resource=quantity pairs that define a hard limit.")) + cmd.Flags().StringVar(&o.Scopes, "scopes", o.Scopes, i18n.T("A comma-delimited set of quota scopes that must all match each object tracked by the quota.")) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *QuotaOpts) 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 = coreclient.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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate checks to the QuotaOpts to see if there is sufficient information run the command. +func (o *QuotaOpts) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil +} + +// Run does the work +func (o *QuotaOpts) Run() error { + resourceQuota, err := o.createQuota() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, resourceQuota, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + resourceQuota, err = o.Client.ResourceQuotas(o.Namespace).Create(context.TODO(), resourceQuota, createOptions) + if err != nil { + return fmt.Errorf("failed to create quota: %v", err) + } + } + return o.PrintObj(resourceQuota) +} + +func (o *QuotaOpts) createQuota() (*corev1.ResourceQuota, error) { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + resourceQuota := &corev1.ResourceQuota{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "ResourceQuota"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + }, + } + + resourceList, err := populateResourceListV1(o.Hard) + if err != nil { + return nil, err + } + + scopes, err := parseScopes(o.Scopes) + if err != nil { + return nil, err + } + + resourceQuota.Spec.Hard = resourceList + resourceQuota.Spec.Scopes = scopes + + return resourceQuota, nil +} + +// populateResourceListV1 takes strings of form =,= +// and returns ResourceList. +func populateResourceListV1(spec string) (corev1.ResourceList, error) { + // empty input gets a nil response to preserve generator test expected behaviors + if spec == "" { + return nil, nil + } + + result := corev1.ResourceList{} + resourceStatements := strings.Split(spec, ",") + for _, resourceStatement := range resourceStatements { + parts := strings.Split(resourceStatement, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid argument syntax %v, expected =", resourceStatement) + } + resourceName := corev1.ResourceName(parts[0]) + resourceQuantity, err := resourceapi.ParseQuantity(parts[1]) + if err != nil { + return nil, err + } + result[resourceName] = resourceQuantity + } + return result, nil +} + +func parseScopes(spec string) ([]corev1.ResourceQuotaScope, error) { + // empty input gets a nil response to preserve test expected behaviors + if spec == "" { + return nil, nil + } + + scopes := strings.Split(spec, ",") + result := make([]corev1.ResourceQuotaScope, 0, len(scopes)) + for _, scope := range scopes { + // intentionally do not verify the scope against the valid scope list. This is done by the apiserver anyway. + + if scope == "" { + return nil, fmt.Errorf("invalid resource quota scope \"\"") + } + + result = append(result, corev1.ResourceQuotaScope(scope)) + } + return result, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_role.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_role.go new file mode 100644 index 000000000..822ed11e7 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_role.go @@ -0,0 +1,445 @@ +/* +Copyright 2017 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + clientgorbacv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + roleLong = templates.LongDesc(i18n.T(` + Create a role with single rule.`)) + + roleExample = templates.Examples(i18n.T(` + # Create a role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods + kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods + + # Create a role named "pod-reader" with ResourceName specified + kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod + + # Create a role named "foo" with API Group specified + kubectl create role foo --verb=get,list,watch --resource=rs.apps + + # Create a role named "foo" with SubResource specified + kubectl create role foo --verb=get,list,watch --resource=pods,pods/status`)) + + // Valid resource verb list for validation. + validResourceVerbs = []string{"*", "get", "delete", "list", "create", "update", "patch", "watch", "proxy", "deletecollection", "use", "bind", "escalate", "impersonate"} + + // Specialized verbs and GroupResources + specialVerbs = map[string][]schema.GroupResource{ + "use": { + { + Group: "policy", + Resource: "podsecuritypolicies", + }, + { + Group: "extensions", + Resource: "podsecuritypolicies", + }, + }, + "bind": { + { + Group: "rbac.authorization.k8s.io", + Resource: "roles", + }, + { + Group: "rbac.authorization.k8s.io", + Resource: "clusterroles", + }, + }, + "escalate": { + { + Group: "rbac.authorization.k8s.io", + Resource: "roles", + }, + { + Group: "rbac.authorization.k8s.io", + Resource: "clusterroles", + }, + }, + "impersonate": { + { + Group: "", + Resource: "users", + }, + { + Group: "", + Resource: "serviceaccounts", + }, + { + Group: "", + Resource: "groups", + }, + { + Group: "authentication.k8s.io", + Resource: "userextras", + }, + }, + } +) + +// AddSpecialVerb allows the addition of items to the `specialVerbs` map for non-k8s native resources. +func AddSpecialVerb(verb string, gr schema.GroupResource) { + resources, ok := specialVerbs[verb] + if !ok { + resources = make([]schema.GroupResource, 1) + } + resources = append(resources, gr) + specialVerbs[verb] = resources +} + +// ResourceOptions holds the related options for '--resource' option +type ResourceOptions struct { + Group string + Resource string + SubResource string +} + +// CreateRoleOptions holds the options for 'create role' sub command +type CreateRoleOptions struct { + PrintFlags *genericclioptions.PrintFlags + + Name string + Verbs []string + Resources []ResourceOptions + ResourceNames []string + + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + OutputFormat string + Namespace string + EnforceNamespace bool + Client clientgorbacv1.RbacV1Interface + Mapper meta.RESTMapper + PrintObj func(obj runtime.Object) error + FieldManager string + CreateAnnotation bool + + genericiooptions.IOStreams +} + +// NewCreateRoleOptions returns an initialized CreateRoleOptions instance +func NewCreateRoleOptions(ioStreams genericiooptions.IOStreams) *CreateRoleOptions { + return &CreateRoleOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + + IOStreams: ioStreams, + } +} + +// NewCmdCreateRole returnns an initialized Command instance for 'create role' sub command +func NewCmdCreateRole(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewCreateRoleOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a role with single rule"), + Long: roleLong, + Example: roleExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.RunCreateRole()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().StringSliceVar(&o.Verbs, "verb", o.Verbs, "Verb that applies to the resources contained in the rule") + cmd.Flags().StringSlice("resource", []string{}, "Resource that the rule applies to") + cmd.Flags().StringArrayVar(&o.ResourceNames, "resource-name", o.ResourceNames, "Resource in the white list that the rule applies to, repeat this flag for multiple items") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + + // Remove duplicate verbs. + verbs := []string{} + for _, v := range o.Verbs { + // VerbAll respresents all kinds of verbs. + if v == "*" { + verbs = []string{"*"} + break + } + if !arrayContains(verbs, v) { + verbs = append(verbs, v) + } + } + o.Verbs = verbs + + // Support resource.group pattern. If no API Group specified, use "" as core API Group. + // e.g. --resource=pods,deployments.extensions + resources := cmdutil.GetFlagStringSlice(cmd, "resource") + for _, r := range resources { + sections := strings.SplitN(r, "/", 2) + + resource := &ResourceOptions{} + if len(sections) == 2 { + resource.SubResource = sections[1] + } + + parts := strings.SplitN(sections[0], ".", 2) + if len(parts) == 2 { + resource.Group = parts[1] + } + resource.Resource = parts[0] + + if resource.Resource == "*" && len(parts) == 1 && len(sections) == 1 { + o.Resources = []ResourceOptions{*resource} + break + } + + o.Resources = append(o.Resources, *resource) + } + + // Remove duplicate resource names. + resourceNames := []string{} + for _, n := range o.ResourceNames { + if !arrayContains(resourceNames, n) { + resourceNames = append(resourceNames, n) + } + } + o.ResourceNames = resourceNames + + // Complete other options for Run. + o.Mapper, err = f.ToRESTMapper() + if err != nil { + return err + } + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + o.OutputFormat = cmdutil.GetFlagString(cmd, "output") + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + clientset, err := f.KubernetesClientSet() + if err != nil { + return err + } + o.Client = clientset.RbacV1() + + return nil +} + +// Validate makes sure there is no discrepency in provided option values +func (o *CreateRoleOptions) Validate() error { + if o.Name == "" { + return fmt.Errorf("name must be specified") + } + + // validate verbs. + if len(o.Verbs) == 0 { + return fmt.Errorf("at least one verb must be specified") + } + + for _, v := range o.Verbs { + if !arrayContains(validResourceVerbs, v) { + fmt.Fprintf(o.ErrOut, "Warning: '%s' is not a standard resource verb\n", v) + } + } + + // validate resources. + if len(o.Resources) == 0 { + return fmt.Errorf("at least one resource must be specified") + } + + return o.validateResource() +} + +func (o *CreateRoleOptions) validateResource() error { + for _, r := range o.Resources { + if len(r.Resource) == 0 { + return fmt.Errorf("resource must be specified if apiGroup/subresource specified") + } + if r.Resource == "*" { + return nil + } + + resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group} + groupVersionResource, err := o.Mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}) + if err == nil { + resource = groupVersionResource + } + + for _, v := range o.Verbs { + if groupResources, ok := specialVerbs[v]; ok { + match := false + for _, extra := range groupResources { + if resource.Resource == extra.Resource && resource.Group == extra.Group { + match = true + err = nil + break + } + } + if !match { + return fmt.Errorf("can not perform '%s' on '%s' in group '%s'", v, resource.Resource, resource.Group) + } + } + } + + if err != nil { + return err + } + } + return nil +} + +// RunCreateRole performs the execution of 'create role' sub command +func (o *CreateRoleOptions) RunCreateRole() error { + role := &rbacv1.Role{ + // this is ok because we know exactly how we want to be serialized + TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "Role"}, + } + role.Name = o.Name + rules, err := generateResourcePolicyRules(o.Mapper, o.Verbs, o.Resources, o.ResourceNames, []string{}) + if err != nil { + return err + } + role.Rules = rules + if o.EnforceNamespace { + role.Namespace = o.Namespace + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, role, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + // Create role. + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + role, err = o.Client.Roles(o.Namespace).Create(context.TODO(), role, createOptions) + if err != nil { + return err + } + } + + return o.PrintObj(role) +} + +func arrayContains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resources []ResourceOptions, resourceNames []string, nonResourceURLs []string) ([]rbacv1.PolicyRule, error) { + // groupResourceMapping is a apigroup-resource map. The key of this map is api group, while the value + // is a string array of resources under this api group. + // E.g. groupResourceMapping = {"extensions": ["replicasets", "deployments"], "batch":["jobs"]} + groupResourceMapping := map[string][]string{} + + // This loop does the following work: + // 1. Constructs groupResourceMapping based on input resources. + // 2. Prevents pointing to non-existent resources. + // 3. Transfers resource short name to long name. E.g. rs.extensions is transferred to replicasets.extensions + for _, r := range resources { + resource := schema.GroupVersionResource{Resource: r.Resource, Group: r.Group} + groupVersionResource, err := mapper.ResourceFor(schema.GroupVersionResource{Resource: r.Resource, Group: r.Group}) + if err == nil { + resource = groupVersionResource + } + + if len(r.SubResource) > 0 { + resource.Resource = resource.Resource + "/" + r.SubResource + } + if !arrayContains(groupResourceMapping[resource.Group], resource.Resource) { + groupResourceMapping[resource.Group] = append(groupResourceMapping[resource.Group], resource.Resource) + } + } + + // Create separate rule for each of the api group. + rules := []rbacv1.PolicyRule{} + for _, g := range sets.StringKeySet(groupResourceMapping).List() { + rule := rbacv1.PolicyRule{} + rule.Verbs = verbs + rule.Resources = groupResourceMapping[g] + rule.APIGroups = []string{g} + rule.ResourceNames = resourceNames + rules = append(rules, rule) + } + + if len(nonResourceURLs) > 0 { + rule := rbacv1.PolicyRule{} + rule.Verbs = verbs + rule.NonResourceURLs = nonResourceURLs + rules = append(rules, rule) + } + + return rules, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go new file mode 100644 index 000000000..237a54422 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go @@ -0,0 +1,251 @@ +/* +Copyright 2016 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" + "fmt" + "strings" + + "github.com/spf13/cobra" + + rbacv1 "k8s.io/api/rbac/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/genericiooptions" + rbacclientv1 "k8s.io/client-go/kubernetes/typed/rbac/v1" + cmdutil "k8s.io/kubectl/pkg/cmd/util" + "k8s.io/kubectl/pkg/scheme" + "k8s.io/kubectl/pkg/util" + "k8s.io/kubectl/pkg/util/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + roleBindingLong = templates.LongDesc(i18n.T(` + Create a role binding for a particular role or cluster role.`)) + + roleBindingExample = templates.Examples(i18n.T(` + # Create a role binding for user1, user2, and group1 using the admin cluster role + kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1 + + # Create a role binding for serviceaccount monitoring:sa-dev using the admin role + kubectl create rolebinding admin-binding --role=admin --serviceaccount=monitoring:sa-dev`)) +) + +// RoleBindingOptions holds the options for 'create rolebinding' sub command +type RoleBindingOptions struct { + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + Name string + Namespace string + EnforceNamespace bool + ClusterRole string + Role string + Users []string + Groups []string + ServiceAccounts []string + FieldManager string + CreateAnnotation bool + + Client rbacclientv1.RbacV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewRoleBindingOptions creates a new *RoleBindingOptions with sane defaults +func NewRoleBindingOptions(ioStreams genericiooptions.IOStreams) *RoleBindingOptions { + return &RoleBindingOptions{ + Users: []string{}, + Groups: []string{}, + ServiceAccounts: []string{}, + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateRoleBinding returns an initialized Command instance for 'create rolebinding' sub command +func NewCmdCreateRoleBinding(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewRoleBindingOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname] [--serviceaccount=namespace:serviceaccountname] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a role binding for a particular role or cluster role"), + Long: roleBindingLong, + Example: roleBindingExample, + 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.ClusterRole, "clusterrole", "", i18n.T("ClusterRole this RoleBinding should reference")) + cmd.Flags().StringVar(&o.Role, "role", "", i18n.T("Role this RoleBinding should reference")) + cmd.Flags().StringArrayVar(&o.Users, "user", o.Users, "Usernames to bind to the role. The flag can be repeated to add multiple users.") + cmd.Flags().StringArrayVar(&o.Groups, "group", o.Groups, "Groups to bind to the role. The flag can be repeated to add multiple groups.") + cmd.Flags().StringArrayVar(&o.ServiceAccounts, "serviceaccount", o.ServiceAccounts, "Service accounts to bind to the role, in the format :. The flag can be repeated to add multiple service accounts.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *RoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + o.Name, err = NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = rbacclientv1.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.CreateAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + return err +} + +// Validate validates required fields are set +func (o *RoleBindingOptions) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + if (len(o.ClusterRole) == 0) == (len(o.Role) == 0) { + return fmt.Errorf("exactly one of clusterrole or role must be specified") + } + return nil +} + +// Run performs the execution of 'create rolebinding' sub command +func (o *RoleBindingOptions) Run() error { + roleBinding, err := o.createRoleBinding() + if err != nil { + return err + } + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, roleBinding, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + roleBinding, err = o.Client.RoleBindings(o.Namespace).Create(context.TODO(), roleBinding, createOptions) + if err != nil { + return fmt.Errorf("failed to create rolebinding: %v", err) + } + } + return o.PrintObj(roleBinding) +} + +func (o *RoleBindingOptions) createRoleBinding() (*rbacv1.RoleBinding, error) { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + roleBinding := &rbacv1.RoleBinding{ + TypeMeta: metav1.TypeMeta{APIVersion: rbacv1.SchemeGroupVersion.String(), Kind: "RoleBinding"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + }, + } + + switch { + case len(o.Role) > 0: + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: o.Role, + } + case len(o.ClusterRole) > 0: + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: o.ClusterRole, + } + } + + for _, user := range o.Users { + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.UserKind, + APIGroup: rbacv1.GroupName, + Name: user, + }) + } + + for _, group := range o.Groups { + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.GroupKind, + APIGroup: rbacv1.GroupName, + Name: group, + }) + } + + for _, sa := range o.ServiceAccounts { + tokens := strings.Split(sa, ":") + if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" { + return nil, fmt.Errorf("serviceaccount must be :") + } + roleBinding.Subjects = append(roleBinding.Subjects, rbacv1.Subject{ + Kind: rbacv1.ServiceAccountKind, + APIGroup: "", + Namespace: tokens[0], + Name: tokens[1], + }) + } + return roleBinding, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret.go new file mode 100644 index 000000000..b9bb191f9 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret.go @@ -0,0 +1,421 @@ +/* +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 ( + "context" + "fmt" + "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/genericiooptions" + 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" +) + +// 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 genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "secret (docker-registry | generic | tls)", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a secret using a specified subcommand"), + Long: secretLong, + 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 with specified type. + + A docker-registry type secret is for accessing a container registry. + + A generic type secret indicate an Opaque secret type. + + A tls type secret holds TLS certificate and its associated key.`)) + + secretForGenericLong = 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).`)) + + secretForGenericExample = 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 + kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-file=ssh-publickey=path/to/id_rsa.pub + + # 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 + kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa --from-literal=passphrase=topsecret + + # Create a new secret named my-secret from env files + kubectl create secret generic my-secret --from-env-file=path/to/foo.env --from-env-file=path/to/bar.env`)) +) + +// 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 + // EnvFileSources to derive the secret from (optional) + EnvFileSources []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 + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewSecretOptions creates a new *CreateSecretOptions with default value +func NewSecretOptions(ioStreams genericiooptions.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 genericiooptions.IOStreams) *cobra.Command { + o := NewSecretOptions(ioStreams) + + cmd := &cobra.Command{ + Use: "generic NAME [--type=string] [--from-file=[key=]source] [--from-literal=key1=value1] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a secret from a local file, directory, or literal value"), + Long: secretForGenericLong, + Example: secretForGenericExample, + 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().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().StringSliceVar(&o.EnvFileSources, "from-env-file", o.EnvFileSources, "Specify the path to a file to read lines of key=val pairs to create a secret.") + 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 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 + } + + 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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + 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.EnvFileSources) > 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 + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + 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) + } + } + + 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.EnvFileSources) > 0 { + if err := handleSecretFromEnvFileSources(secret, o.EnvFileSources); 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) + } + + 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{}, + } +} + +// 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 + } + } + + 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) + } + } + // 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 := os.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.Type().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 + } + } + + } + + return nil +} + +// handleSecretFromEnvFileSources adds the specified env files source information +// into the provided secret +func handleSecretFromEnvFileSources(secret *corev1.Secret, envFileSources []string) error { + for _, envFileSource := range envFileSources { + info, 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 info.IsDir() { + return fmt.Errorf("env secret file cannot be a directory") + } + err = cmdutil.AddFromEnvFile(envFileSource, func(key, value string) error { + return addKeyFromLiteralToSecret(secret, key, []byte(value)) + }) + if err != nil { + return err + } + } + + return nil +} + +// 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 := os.ReadFile(filePath) + if err != nil { + return err + } + return addKeyFromLiteralToSecret(secret, keyName, data) +} + +// 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 +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go new file mode 100644 index 000000000..d8acc90d4 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go @@ -0,0 +1,298 @@ +/* +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/genericiooptions" + 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 must 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 do not already have a .dockercfg file, create a dockercfg secret directly + 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 + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewSecretDockerRegistryOptions creates a new *CreateSecretDockerRegistryOptions with default value +func NewSecretDockerRegistryOptions(ioStreams genericiooptions.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 genericiooptions.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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + 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 + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + 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)) +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go new file mode 100644 index 000000000..a23e4ca62 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go @@ -0,0 +1,249 @@ +/* +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" + "os" + + "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/genericiooptions" + 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 beforehand. 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.crt --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 + ValidationDirective string + + genericiooptions.IOStreams +} + +// NewSecretTLSOptions creates a new *CreateSecretTLSOptions with default value +func NewSecretTLSOptions(ioStrems genericiooptions.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 genericiooptions.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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + 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 + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + 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 := os.ReadFile(file) + if err != nil { + return []byte{}, fmt.Errorf("Cannot read file %v, %v", file, err) + } + return b, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_service.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_service.go new file mode 100644 index 000000000..54b164b35 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_service.go @@ -0,0 +1,412 @@ +/* +Copyright 2016 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" + "fmt" + "strconv" + "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/intstr" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + 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/i18n" + "k8s.io/kubectl/pkg/util/templates" + utilsnet "k8s.io/utils/net" +) + +// NewCmdCreateService is a macro command to create a new service +func NewCmdCreateService(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Use: "service", + Aliases: []string{"svc"}, + Short: i18n.T("Create a service using a specified subcommand"), + Long: i18n.T("Create a service using a specified subcommand."), + Run: cmdutil.DefaultSubCommandRun(ioStreams.ErrOut), + } + cmd.AddCommand(NewCmdCreateServiceClusterIP(f, ioStreams)) + cmd.AddCommand(NewCmdCreateServiceNodePort(f, ioStreams)) + cmd.AddCommand(NewCmdCreateServiceLoadBalancer(f, ioStreams)) + cmd.AddCommand(NewCmdCreateServiceExternalName(f, ioStreams)) + + return cmd +} + +// ServiceOptions holds the options for 'create service' sub command +type ServiceOptions struct { + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + Name string + TCP []string + Type corev1.ServiceType + ClusterIP string + NodePort int + ExternalName string + + FieldManager string + CreateAnnotation bool + Namespace string + EnforceNamespace bool + + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + genericiooptions.IOStreams +} + +// NewServiceOptions creates a ServiceOptions struct +func NewServiceOptions(ioStreams genericiooptions.IOStreams, serviceType corev1.ServiceType) *ServiceOptions { + return &ServiceOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + Type: serviceType, + } +} + +// Complete completes all the required options +func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + name, err := NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + o.Name = name + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = corev1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) + if err != nil { + return err + } + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate if the options are valid +func (o *ServiceOptions) Validate() error { + if o.ClusterIP == corev1.ClusterIPNone && o.Type != corev1.ServiceTypeClusterIP { + return fmt.Errorf("ClusterIP=None can only be used with ClusterIP service type") + } + if o.ClusterIP != corev1.ClusterIPNone && len(o.TCP) == 0 && o.Type != corev1.ServiceTypeExternalName { + return fmt.Errorf("at least one tcp port specifier must be provided") + } + if o.Type == corev1.ServiceTypeExternalName { + if errs := validation.IsDNS1123Subdomain(o.ExternalName); len(errs) != 0 { + return fmt.Errorf("invalid service external name %s", o.ExternalName) + } + } + return nil +} + +func (o *ServiceOptions) createService() (*corev1.Service, error) { + ports := []corev1.ServicePort{} + for _, tcpString := range o.TCP { + port, targetPort, err := parsePorts(tcpString) + if err != nil { + return nil, err + } + + portName := strings.Replace(tcpString, ":", "-", -1) + ports = append(ports, corev1.ServicePort{ + Name: portName, + Port: port, + TargetPort: targetPort, + Protocol: corev1.Protocol("TCP"), + NodePort: int32(o.NodePort), + }) + } + + // setup default label and selector + labels := map[string]string{} + labels["app"] = o.Name + selector := map[string]string{} + selector["app"] = o.Name + + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + + service := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Labels: labels, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Type: o.Type, + Selector: selector, + Ports: ports, + ExternalName: o.ExternalName, + }, + } + if len(o.ClusterIP) > 0 { + service.Spec.ClusterIP = o.ClusterIP + } + return &service, nil +} + +// Run the service command +func (o *ServiceOptions) Run() error { + service, err := o.createService() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, service, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + var err error + service, err = o.Client.Services(o.Namespace).Create(context.TODO(), service, createOptions) + if err != nil { + return fmt.Errorf("failed to create %s service: %v", o.Type, err) + } + } + return o.PrintObj(service) +} + +var ( + serviceClusterIPLong = templates.LongDesc(i18n.T(` + Create a ClusterIP service with the specified name.`)) + + serviceClusterIPExample = templates.Examples(i18n.T(` + # Create a new ClusterIP service named my-cs + kubectl create service clusterip my-cs --tcp=5678:8080 + + # Create a new ClusterIP service named my-cs (in headless mode) + kubectl create service clusterip my-cs --clusterip="None"`)) +) + +// NewCmdCreateServiceClusterIP is a command to create a ClusterIP service +func NewCmdCreateServiceClusterIP(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewServiceOptions(ioStreams, corev1.ServiceTypeClusterIP) + + cmd := &cobra.Command{ + Use: "clusterip NAME [--tcp=:] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a ClusterIP service"), + Long: serviceClusterIPLong, + Example: serviceClusterIPExample, + 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) + cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as ':'.") + cmd.Flags().StringVar(&o.ClusterIP, "clusterip", o.ClusterIP, i18n.T("Assign your own ClusterIP or set to 'None' for a 'headless' service (no loadbalancing).")) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + cmdutil.AddDryRunFlag(cmd) + + return cmd +} + +var ( + serviceNodePortLong = templates.LongDesc(i18n.T(` + Create a NodePort service with the specified name.`)) + + serviceNodePortExample = templates.Examples(i18n.T(` + # Create a new NodePort service named my-ns + kubectl create service nodeport my-ns --tcp=5678:8080`)) +) + +// NewCmdCreateServiceNodePort is a macro command for creating a NodePort service +func NewCmdCreateServiceNodePort(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewServiceOptions(ioStreams, corev1.ServiceTypeNodePort) + + cmd := &cobra.Command{ + Use: "nodeport NAME [--tcp=port:targetPort] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a NodePort service"), + Long: serviceNodePortLong, + Example: serviceNodePortExample, + 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) + cmd.Flags().IntVar(&o.NodePort, "node-port", o.NodePort, "Port used to expose the service on each node in a cluster.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as ':'.") + cmdutil.AddDryRunFlag(cmd) + return cmd +} + +var ( + serviceLoadBalancerLong = templates.LongDesc(i18n.T(` + Create a LoadBalancer service with the specified name.`)) + + serviceLoadBalancerExample = templates.Examples(i18n.T(` + # Create a new LoadBalancer service named my-lbs + kubectl create service loadbalancer my-lbs --tcp=5678:8080`)) +) + +// NewCmdCreateServiceLoadBalancer is a macro command for creating a LoadBalancer service +func NewCmdCreateServiceLoadBalancer(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewServiceOptions(ioStreams, corev1.ServiceTypeLoadBalancer) + + cmd := &cobra.Command{ + Use: "loadbalancer NAME [--tcp=port:targetPort] [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create a LoadBalancer service"), + Long: serviceLoadBalancerLong, + Example: serviceLoadBalancerExample, + 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) + cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as ':'.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + cmdutil.AddDryRunFlag(cmd) + return cmd +} + +var ( + serviceExternalNameLong = templates.LongDesc(i18n.T(` + Create an ExternalName service with the specified name. + + ExternalName service references to an external DNS address instead of + only pods, which will allow application authors to reference services + that exist off platform, on other clusters, or locally.`)) + + serviceExternalNameExample = templates.Examples(i18n.T(` + # Create a new ExternalName service named my-ns + kubectl create service externalname my-ns --external-name bar.com`)) +) + +// NewCmdCreateServiceExternalName is a macro command for creating an ExternalName service +func NewCmdCreateServiceExternalName(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewServiceOptions(ioStreams, corev1.ServiceTypeExternalName) + + cmd := &cobra.Command{ + Use: "externalname NAME --external-name external.name [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Short: i18n.T("Create an ExternalName service"), + Long: serviceExternalNameLong, + Example: serviceExternalNameExample, + 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) + cmd.Flags().StringSliceVar(&o.TCP, "tcp", o.TCP, "Port pairs can be specified as ':'.") + cmd.Flags().StringVar(&o.ExternalName, "external-name", o.ExternalName, i18n.T("External name of service")) + cmd.MarkFlagRequired("external-name") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + cmdutil.AddDryRunFlag(cmd) + return cmd +} + +func parsePorts(portString string) (int32, intstr.IntOrString, error) { + portStringSlice := strings.Split(portString, ":") + + port, err := utilsnet.ParsePort(portStringSlice[0], true) + if err != nil { + return 0, intstr.FromInt32(0), err + } + + if len(portStringSlice) == 1 { + port32 := int32(port) + return port32, intstr.FromInt32(port32), nil + } + + var targetPort intstr.IntOrString + if portNum, err := strconv.Atoi(portStringSlice[1]); err != nil { + if errs := validation.IsValidPortName(portStringSlice[1]); len(errs) != 0 { + return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ",")) + } + targetPort = intstr.FromString(portStringSlice[1]) + } else { + if errs := validation.IsValidPortNum(portNum); len(errs) != 0 { + return 0, intstr.FromInt32(0), fmt.Errorf(strings.Join(errs, ",")) + } + targetPort = intstr.FromInt32(int32(portNum)) + } + return int32(port), targetPort, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go new file mode 100644 index 000000000..da2e6a332 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go @@ -0,0 +1,202 @@ +/* +Copyright 2016 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" + "fmt" + + "github.com/spf13/cobra" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + coreclient "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/i18n" + "k8s.io/kubectl/pkg/util/templates" +) + +var ( + serviceAccountLong = templates.LongDesc(i18n.T(` + Create a service account with the specified name.`)) + + serviceAccountExample = templates.Examples(i18n.T(` + # Create a new service account named my-service-account + kubectl create serviceaccount my-service-account`)) +) + +// ServiceAccountOpts holds the options for 'create serviceaccount' sub command +type ServiceAccountOpts struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + // Name of resource being created + Name string + DryRunStrategy cmdutil.DryRunStrategy + ValidationDirective string + CreateAnnotation bool + FieldManager string + + Namespace string + EnforceNamespace bool + + Mapper meta.RESTMapper + Client *coreclient.CoreV1Client + + genericiooptions.IOStreams +} + +// NewServiceAccountOpts creates a new *ServiceAccountOpts with sane defaults +func NewServiceAccountOpts(ioStreams genericiooptions.IOStreams) *ServiceAccountOpts { + return &ServiceAccountOpts{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateServiceAccount is a macro command to create a new service account +func NewCmdCreateServiceAccount(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewServiceAccountOpts(ioStreams) + + cmd := &cobra.Command{ + Use: "serviceaccount NAME [--dry-run=server|client|none]", + DisableFlagsInUseLine: true, + Aliases: []string{"sa"}, + Short: i18n.T("Create a service account with the specified name"), + Long: serviceAccountLong, + Example: serviceAccountExample, + 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) + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") + return cmd +} + +// Complete completes all the required options +func (o *ServiceAccountOpts) 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 = coreclient.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 + } + + o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + + return nil +} + +// Validate checks ServiceAccountOpts to see if there is sufficient information run the command. +func (o *ServiceAccountOpts) Validate() error { + if len(o.Name) == 0 { + return fmt.Errorf("name must be specified") + } + return nil +} + +// Run makes the api call to the server +func (o *ServiceAccountOpts) Run() error { + serviceAccount, err := o.createServiceAccount() + if err != nil { + return err + } + + if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, serviceAccount, scheme.DefaultJSONEncoder()); err != nil { + return err + } + + if o.DryRunStrategy != cmdutil.DryRunClient { + createOptions := metav1.CreateOptions{} + if o.FieldManager != "" { + createOptions.FieldManager = o.FieldManager + } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { + createOptions.DryRun = []string{metav1.DryRunAll} + } + serviceAccount, err = o.Client.ServiceAccounts(o.Namespace).Create(context.TODO(), serviceAccount, createOptions) + if err != nil { + return fmt.Errorf("failed to create serviceaccount: %v", err) + } + } + return o.PrintObj(serviceAccount) +} + +func (o *ServiceAccountOpts) createServiceAccount() (*corev1.ServiceAccount, error) { + namespace := "" + if o.EnforceNamespace { + namespace = o.Namespace + } + serviceAccount := &corev1.ServiceAccount{ + TypeMeta: metav1.TypeMeta{APIVersion: corev1.SchemeGroupVersion.String(), Kind: "ServiceAccount"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + Namespace: namespace, + }, + } + serviceAccount.Name = o.Name + return serviceAccount, nil +} diff --git a/vendor/k8s.io/kubectl/pkg/cmd/create/create_token.go b/vendor/k8s.io/kubectl/pkg/cmd/create/create_token.go new file mode 100644 index 000000000..adfd5964d --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/cmd/create/create_token.go @@ -0,0 +1,282 @@ +/* +Copyright 2022 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" + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + authenticationv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericiooptions" + 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/completion" + "k8s.io/kubectl/pkg/util/templates" + "k8s.io/kubectl/pkg/util/term" + "k8s.io/utils/pointer" +) + +// TokenOptions is the data required to perform a token request operation. +type TokenOptions struct { + // PrintFlags holds options necessary for obtaining a printer + PrintFlags *genericclioptions.PrintFlags + PrintObj func(obj runtime.Object) error + + // Flags hold the parsed CLI flags. + Flags *pflag.FlagSet + + // Name and namespace of service account to create a token for + Name string + Namespace string + + // BoundObjectKind is the kind of object to bind the token to. Optional. Can be Pod or Secret. + BoundObjectKind string + // BoundObjectName is the name of the object to bind the token to. Required if BoundObjectKind is set. + BoundObjectName string + // BoundObjectUID is the uid of the object to bind the token to. If unset, defaults to the current uid of the bound object. + BoundObjectUID string + + // Audiences indicate the valid audiences for the requested token. If unset, defaults to the Kubernetes API server audiences. + Audiences []string + + // Duration is the requested token lifetime. Optional. + Duration time.Duration + + // CoreClient is the API client used to request the token. Required. + CoreClient corev1client.CoreV1Interface + + // IOStreams are the output streams for the operation. Required. + genericiooptions.IOStreams +} + +var ( + tokenLong = templates.LongDesc(`Request a service account token.`) + + tokenExample = templates.Examples(` + # Request a token to authenticate to the kube-apiserver as the service account "myapp" in the current namespace + kubectl create token myapp + + # Request a token for a service account in a custom namespace + kubectl create token myapp --namespace myns + + # Request a token with a custom expiration + kubectl create token myapp --duration 10m + + # Request a token with a custom audience + kubectl create token myapp --audience https://example.com + + # Request a token bound to an instance of a Secret object + kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret + + # Request a token bound to an instance of a Secret object with a specific UID + kubectl create token myapp --bound-object-kind Secret --bound-object-name mysecret --bound-object-uid 0d4691ed-659b-4935-a832-355f77ee47cc +`) +) + +func boundObjectKindToAPIVersions() map[string]string { + kinds := map[string]string{ + "Pod": "v1", + "Secret": "v1", + } + if os.Getenv("KUBECTL_NODE_BOUND_TOKENS") == "true" { + kinds["Node"] = "v1" + } + return kinds +} + +func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions { + return &TokenOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateToken returns an initialized Command for 'create token' sub command +func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.Command { + o := NewTokenOpts(ioStreams) + + cmd := &cobra.Command{ + Use: "token SERVICE_ACCOUNT_NAME", + DisableFlagsInUseLine: true, + Short: "Request a service account token", + Long: tokenLong, + Example: tokenExample, + ValidArgsFunction: completion.ResourceNameCompletionFunc(f, "serviceaccount"), + Run: func(cmd *cobra.Command, args []string) { + if err := o.Complete(f, cmd, args); err != nil { + cmdutil.CheckErr(err) + return + } + if err := o.Validate(); err != nil { + cmdutil.CheckErr(err) + return + } + if err := o.Run(); err != nil { + cmdutil.CheckErr(err) + return + } + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmd.Flags().StringArrayVar(&o.Audiences, "audience", o.Audiences, "Audience of the requested token. If unset, defaults to requesting a token for use with the Kubernetes API server. May be repeated to request a token valid for multiple audiences.") + + cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.") + + cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+ + "Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+ + "If set, --bound-object-name must be provided.") + cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+ + "The token will expire when the object is deleted. "+ + "Requires --bound-object-kind.") + cmd.Flags().StringVar(&o.BoundObjectUID, "bound-object-uid", o.BoundObjectUID, "UID of an object to bind the token to. "+ + "Requires --bound-object-kind and --bound-object-name. "+ + "If unset, the UID of the existing object is used.") + + o.Flags = cmd.Flags() + + return cmd +} + +// Complete completes all the required options +func (o *TokenOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { + var err error + + o.Name, err = NameFromCommandArgs(cmd, args) + if err != nil { + return err + } + + o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + client, err := f.KubernetesClientSet() + if err != nil { + return err + } + o.CoreClient = client.CoreV1() + + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + return nil +} + +// Validate makes sure provided values for TokenOptions are valid +func (o *TokenOptions) Validate() error { + if o.CoreClient == nil { + return fmt.Errorf("no client provided") + } + if len(o.Name) == 0 { + return fmt.Errorf("service account name is required") + } + if len(o.Namespace) == 0 { + return fmt.Errorf("--namespace is required") + } + if o.Duration < 0 { + return fmt.Errorf("--duration must be greater than or equal to 0") + } + if o.Duration%time.Second != 0 { + return fmt.Errorf("--duration cannot be expressed in units less than seconds") + } + for _, aud := range o.Audiences { + if len(aud) == 0 { + return fmt.Errorf("--audience must not be an empty string") + } + } + + if len(o.BoundObjectKind) == 0 { + if len(o.BoundObjectName) > 0 { + return fmt.Errorf("--bound-object-name can only be set if --bound-object-kind is provided") + } + if len(o.BoundObjectUID) > 0 { + return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided") + } + } else { + if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok { + return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")) + } + if len(o.BoundObjectName) == 0 { + return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided") + } + } + + return nil +} + +// Run requests a token +func (o *TokenOptions) Run() error { + request := &authenticationv1.TokenRequest{ + Spec: authenticationv1.TokenRequestSpec{ + Audiences: o.Audiences, + }, + } + if o.Duration > 0 { + request.Spec.ExpirationSeconds = pointer.Int64(int64(o.Duration / time.Second)) + } + if len(o.BoundObjectKind) > 0 { + request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{ + Kind: o.BoundObjectKind, + APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind], + Name: o.BoundObjectName, + UID: types.UID(o.BoundObjectUID), + } + } + + response, err := o.CoreClient.ServiceAccounts(o.Namespace).CreateToken(context.TODO(), o.Name, request, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create token: %v", err) + } + if len(response.Status.Token) == 0 { + return fmt.Errorf("failed to create token: no token in server response") + } + + if o.PrintFlags.OutputFlagSpecified() { + return o.PrintObj(response) + } + + if term.IsTerminal(o.Out) { + // include a newline when printing interactively + fmt.Fprintf(o.Out, "%s\n", response.Status.Token) + } else { + // otherwise just print the token + fmt.Fprintf(o.Out, "%s", response.Status.Token) + } + + return nil +} diff --git a/vendor/k8s.io/kubectl/pkg/generate/generate.go b/vendor/k8s.io/kubectl/pkg/generate/generate.go new file mode 100644 index 000000000..4087c2196 --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/generate/generate.go @@ -0,0 +1,207 @@ +/* +Copyright 2018 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 generate + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "k8s.io/apimachinery/pkg/runtime" + utilerrors "k8s.io/apimachinery/pkg/util/errors" +) + +// GeneratorFunc returns the generators for the provided command +type GeneratorFunc func(cmdName string) map[string]Generator + +// GeneratorParam is a parameter for a generator +// TODO: facilitate structured json generator input schemes +type GeneratorParam struct { + Name string + Required bool +} + +// Generator is an interface for things that can generate API objects from input +// parameters. One example is the "expose" generator that is capable of exposing +// new replication controllers and services, among other things. +type Generator interface { + // Generate creates an API object given a set of parameters + Generate(params map[string]interface{}) (runtime.Object, error) + // ParamNames returns the list of parameters that this generator uses + ParamNames() []GeneratorParam +} + +// StructuredGenerator is an interface for things that can generate API objects not using parameter injection +type StructuredGenerator interface { + // StructuredGenerator creates an API object using pre-configured parameters + StructuredGenerate() (runtime.Object, error) +} + +func IsZero(i interface{}) bool { + if i == nil { + return true + } + return reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) +} + +// ValidateParams ensures that all required params are present in the params map +func ValidateParams(paramSpec []GeneratorParam, params map[string]interface{}) error { + allErrs := []error{} + for ix := range paramSpec { + if paramSpec[ix].Required { + value, found := params[paramSpec[ix].Name] + if !found || IsZero(value) { + allErrs = append(allErrs, fmt.Errorf("Parameter: %s is required", paramSpec[ix].Name)) + } + } + } + return utilerrors.NewAggregate(allErrs) +} + +// AnnotateFlags annotates all flags that are used by generators. +func AnnotateFlags(cmd *cobra.Command, generators map[string]Generator) { + // Iterate over all generators and mark any flags used by them. + for name, generator := range generators { + generatorParams := map[string]struct{}{} + for _, param := range generator.ParamNames() { + generatorParams[param.Name] = struct{}{} + } + + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if _, found := generatorParams[flag.Name]; !found { + // This flag is not used by the current generator + // so skip it. + return + } + if flag.Annotations == nil { + flag.Annotations = map[string][]string{} + } + if annotations := flag.Annotations["generator"]; annotations == nil { + flag.Annotations["generator"] = []string{} + } + flag.Annotations["generator"] = append(flag.Annotations["generator"], name) + }) + } +} + +// EnsureFlagsValid ensures that no invalid flags are being used against a +func EnsureFlagsValid(cmd *cobra.Command, generators map[string]Generator, generatorInUse string) error { + AnnotateFlags(cmd, generators) + + allErrs := []error{} + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + // If the flag hasn't changed, don't validate it. + if !flag.Changed { + return + } + // Look into the flag annotations for the generators that can use it. + if annotations := flag.Annotations["generator"]; len(annotations) > 0 { + annotationMap := map[string]struct{}{} + for _, ann := range annotations { + annotationMap[ann] = struct{}{} + } + // If the current generator is not annotated, then this flag shouldn't + // be used with it. + if _, found := annotationMap[generatorInUse]; !found { + allErrs = append(allErrs, fmt.Errorf("cannot use --%s with --generator=%s", flag.Name, generatorInUse)) + } + } + }) + return utilerrors.NewAggregate(allErrs) +} + +// MakeParams is a utility that creates generator parameters from a command line +func MakeParams(cmd *cobra.Command, params []GeneratorParam) map[string]interface{} { + result := map[string]interface{}{} + for ix := range params { + f := cmd.Flags().Lookup(params[ix].Name) + if f != nil { + result[params[ix].Name] = f.Value.String() + } + } + return result +} + +func MakeProtocols(protocols map[string]string) string { + out := []string{} + for key, value := range protocols { + out = append(out, fmt.Sprintf("%s/%s", key, value)) + } + return strings.Join(out, ",") +} + +func ParseProtocols(protocols interface{}) (map[string]string, error) { + protocolsString, isString := protocols.(string) + if !isString { + return nil, fmt.Errorf("expected string, found %v", protocols) + } + if len(protocolsString) == 0 { + return nil, fmt.Errorf("no protocols passed") + } + portProtocolMap := map[string]string{} + protocolsSlice := strings.Split(protocolsString, ",") + for ix := range protocolsSlice { + portProtocol := strings.Split(protocolsSlice[ix], "/") + if len(portProtocol) != 2 { + return nil, fmt.Errorf("unexpected port protocol mapping: %s", protocolsSlice[ix]) + } + if len(portProtocol[0]) == 0 { + return nil, fmt.Errorf("unexpected empty port") + } + if len(portProtocol[1]) == 0 { + return nil, fmt.Errorf("unexpected empty protocol") + } + portProtocolMap[portProtocol[0]] = portProtocol[1] + } + return portProtocolMap, nil +} + +// ParseLabels turns a string representation of a label set into a map[string]string +func ParseLabels(labelSpec interface{}) (map[string]string, error) { + labelString, isString := labelSpec.(string) + if !isString { + return nil, fmt.Errorf("expected string, found %v", labelSpec) + } + if len(labelString) == 0 { + return nil, fmt.Errorf("no label spec passed") + } + labels := map[string]string{} + labelSpecs := strings.Split(labelString, ",") + for ix := range labelSpecs { + labelSpec := strings.Split(labelSpecs[ix], "=") + if len(labelSpec) != 2 { + return nil, fmt.Errorf("unexpected label spec: %s", labelSpecs[ix]) + } + if len(labelSpec[0]) == 0 { + return nil, fmt.Errorf("unexpected empty label key") + } + labels[labelSpec[0]] = labelSpec[1] + } + return labels, nil +} + +func GetBool(params map[string]string, key string, defValue bool) (bool, error) { + if val, found := params[key]; !found { + return defValue, nil + } else { + return strconv.ParseBool(val) + } +} diff --git a/vendor/k8s.io/kubectl/pkg/util/hash/hash.go b/vendor/k8s.io/kubectl/pkg/util/hash/hash.go new file mode 100644 index 000000000..1b20f384b --- /dev/null +++ b/vendor/k8s.io/kubectl/pkg/util/hash/hash.go @@ -0,0 +1,130 @@ +/* +Copyright 2017 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 hash + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + + "k8s.io/api/core/v1" +) + +// ConfigMapHash returns a hash of the ConfigMap. +// The Data, Kind, and Name are taken into account. +func ConfigMapHash(cm *v1.ConfigMap) (string, error) { + encoded, err := encodeConfigMap(cm) + if err != nil { + return "", err + } + h, err := encodeHash(hash(encoded)) + if err != nil { + return "", err + } + return h, nil +} + +// SecretHash returns a hash of the Secret. +// The Data, Kind, Name, and Type are taken into account. +func SecretHash(sec *v1.Secret) (string, error) { + encoded, err := encodeSecret(sec) + if err != nil { + return "", err + } + h, err := encodeHash(hash(encoded)) + if err != nil { + return "", err + } + return h, nil +} + +// encodeConfigMap encodes a ConfigMap. +// Data, Kind, and Name are taken into account. +func encodeConfigMap(cm *v1.ConfigMap) (string, error) { + // json.Marshal sorts the keys in a stable order in the encoding + m := map[string]interface{}{ + "kind": "ConfigMap", + "name": cm.Name, + "data": cm.Data, + } + if cm.Immutable != nil { + m["immutable"] = *cm.Immutable + } + if len(cm.BinaryData) > 0 { + m["binaryData"] = cm.BinaryData + } + data, err := json.Marshal(m) + if err != nil { + return "", err + } + return string(data), nil +} + +// encodeSecret encodes a Secret. +// Data, Kind, Name, and Type are taken into account. +func encodeSecret(sec *v1.Secret) (string, error) { + m := map[string]interface{}{ + "kind": "Secret", + "type": sec.Type, + "name": sec.Name, + "data": sec.Data, + } + if sec.Immutable != nil { + m["immutable"] = *sec.Immutable + } + // json.Marshal sorts the keys in a stable order in the encoding + data, err := json.Marshal(m) + if err != nil { + return "", err + } + return string(data), nil +} + +// encodeHash extracts the first 40 bits of the hash from the hex string +// (1 hex char represents 4 bits), and then maps vowels and vowel-like hex +// characters to consonants to prevent bad words from being formed (the theory +// is that no vowels makes it really hard to make bad words). Since the string +// is hex, the only vowels it can contain are 'a' and 'e'. +// We picked some arbitrary consonants to map to from the same character set as GenerateName. +// See: https://github.com/kubernetes/apimachinery/blob/dc1f89aff9a7509782bde3b68824c8043a3e58cc/pkg/util/rand/rand.go#L75 +// If the hex string contains fewer than ten characters, returns an error. +func encodeHash(hex string) (string, error) { + if len(hex) < 10 { + return "", fmt.Errorf("the hex string must contain at least 10 characters") + } + enc := []rune(hex[:10]) + for i := range enc { + switch enc[i] { + case '0': + enc[i] = 'g' + case '1': + enc[i] = 'h' + case '3': + enc[i] = 'k' + case 'a': + enc[i] = 'm' + case 'e': + enc[i] = 't' + } + } + return string(enc), nil +} + +// hash hashes `data` with sha256 and returns the hex string +func hash(data string) string { + return fmt.Sprintf("%x", sha256.Sum256([]byte(data))) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 388d3fd9f..ed4884843 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1682,6 +1682,7 @@ k8s.io/kube-openapi/pkg/validation/strfmt/bson k8s.io/kubectl/pkg/apps k8s.io/kubectl/pkg/cmd/apiresources k8s.io/kubectl/pkg/cmd/apply +k8s.io/kubectl/pkg/cmd/create k8s.io/kubectl/pkg/cmd/delete k8s.io/kubectl/pkg/cmd/describe k8s.io/kubectl/pkg/cmd/exec @@ -1694,6 +1695,7 @@ k8s.io/kubectl/pkg/cmd/util/editor/crlf k8s.io/kubectl/pkg/cmd/util/podcmd k8s.io/kubectl/pkg/cmd/wait k8s.io/kubectl/pkg/describe +k8s.io/kubectl/pkg/generate k8s.io/kubectl/pkg/metricsutil k8s.io/kubectl/pkg/polymorphichelpers k8s.io/kubectl/pkg/rawhttp @@ -1704,6 +1706,7 @@ k8s.io/kubectl/pkg/util/completion k8s.io/kubectl/pkg/util/deployment k8s.io/kubectl/pkg/util/event k8s.io/kubectl/pkg/util/fieldpath +k8s.io/kubectl/pkg/util/hash k8s.io/kubectl/pkg/util/i18n k8s.io/kubectl/pkg/util/interrupt k8s.io/kubectl/pkg/util/openapi