diff --git a/hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml b/hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml new file mode 100644 index 00000000000..60b752b181d --- /dev/null +++ b/hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml @@ -0,0 +1,24 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: invalid-nginx-deployment + labels: + app: nginx +spec: + replicas: 3 + replicas: 4 + foo: bar + baz: baq + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.14.2 + ports: + - containerPort: 80 diff --git a/pkg/kubectl/cmd/convert/convert.go b/pkg/kubectl/cmd/convert/convert.go index 74b16ccbbe4..211c43f059b 100644 --- a/pkg/kubectl/cmd/convert/convert.go +++ b/pkg/kubectl/cmd/convert/convert.go @@ -124,7 +124,16 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err er } o.validator = func() (validation.Schema, error) { - return f.Validator(cmdutil.GetFlagBool(cmd, "validate")) + directive, err := cmdutil.GetValidationDirective(cmd) + if err != nil { + return nil, err + } + dynamicClient, err := f.DynamicClient() + if err != nil { + return nil, err + } + verifier := resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamFieldValidation) + return f.Validator(directive, verifier) } // build the printer diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go b/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go index 34664bb2f40..aa400ae0e65 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/helper.go @@ -54,6 +54,10 @@ type Helper struct { // FieldManager is the name associated with the actor or entity that is making // changes. FieldManager string + + // FieldValidation is the directive used to indicate how the server should perform + // field validation (Ignore, Warn, or Strict) + FieldValidation string } // NewHelper creates a Helper from a ResourceMapping @@ -79,6 +83,13 @@ func (m *Helper) WithFieldManager(fieldManager string) *Helper { return m } +// WithFieldValidation sets the field validation option to indicate +// how the server should perform field validation (Ignore, Warn, or Strict). +func (m *Helper) WithFieldValidation(validationDirective string) *Helper { + m.FieldValidation = validationDirective + return m +} + // Subresource sets the helper to access (/[ns//]/) func (m *Helper) WithSubresource(subresource string) *Helper { m.Subresource = subresource @@ -206,6 +217,9 @@ func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Ob if m.FieldManager != "" { options.FieldManager = m.FieldManager } + if m.FieldValidation != "" { + options.FieldValidation = m.FieldValidation + } if modify { // Attempt to version the object based on client logic. version, err := metadataAccessor.ResourceVersion(obj) @@ -242,6 +256,9 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte, if m.FieldManager != "" { options.FieldManager = m.FieldManager } + if m.FieldValidation != "" { + options.FieldValidation = m.FieldValidation + } return m.RESTClient.Patch(pt). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). @@ -262,6 +279,9 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj if m.FieldManager != "" { options.FieldManager = m.FieldManager } + if m.FieldValidation != "" { + options.FieldValidation = m.FieldValidation + } // Attempt to version the object based on client logic. version, err := metadataAccessor.ResourceVersion(obj) diff --git a/staging/src/k8s.io/cli-runtime/pkg/resource/query_param_verifier.go b/staging/src/k8s.io/cli-runtime/pkg/resource/query_param_verifier.go index 7877e2dce70..ab2e8d30ef5 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/resource/query_param_verifier.go +++ b/staging/src/k8s.io/cli-runtime/pkg/resource/query_param_verifier.go @@ -62,6 +62,11 @@ type QueryParamVerifier struct { queryParam VerifiableQueryParam } +// Verifier is the generic verifier interface used for testing QueryParamVerifier +type Verifier interface { + HasSupport(gvk schema.GroupVersionKind) error +} + // VerifiableQueryParam is a query parameter who's enablement on the // apiserver can be determined by evaluating the OpenAPI for a specific // GVK. @@ -72,6 +77,7 @@ const ( QueryParamFieldValidation VerifiableQueryParam = "fieldValidation" ) +// HasSupport checks if the given gvk supports the query param configured on v func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error { oapi, err := v.openAPIGetter.OpenAPISchema() if err != nil { @@ -90,7 +96,7 @@ func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error { } } if !supports { - return newParamUnsupportedError(gvk, v.queryParam) + return NewParamUnsupportedError(gvk, v.queryParam) } return nil } @@ -100,7 +106,7 @@ type paramUnsupportedError struct { param VerifiableQueryParam } -func newParamUnsupportedError(gvk schema.GroupVersionKind, param VerifiableQueryParam) error { +func NewParamUnsupportedError(gvk schema.GroupVersionKind, param VerifiableQueryParam) error { return ¶mUnsupportedError{ gvk: gvk, param: param, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go index ccffc4012bc..77cbcc294c4 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply.go @@ -80,25 +80,27 @@ type ApplyOptions struct { DeleteOptions *delete.DeleteOptions - ServerSideApply bool - ForceConflicts bool - FieldManager string - Selector string - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - Prune bool - PruneResources []prune.Resource - cmdBaseName string - All bool - Overwrite bool - OpenAPIPatch bool - PruneWhitelist []string + ServerSideApply bool + ForceConflicts bool + FieldManager string + Selector string + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + FieldValidationVerifier *resource.QueryParamVerifier + Prune bool + PruneResources []prune.Resource + cmdBaseName string + All bool + Overwrite bool + OpenAPIPatch bool + PruneWhitelist []string - Validator validation.Schema - Builder *resource.Builder - Mapper meta.RESTMapper - DynamicClient dynamic.Interface - OpenAPISchema openapi.Resources + ValidationDirective string + Validator validation.Schema + Builder *resource.Builder + Mapper meta.RESTMapper + DynamicClient dynamic.Interface + OpenAPISchema openapi.Resources Namespace string EnforceNamespace bool @@ -238,6 +240,7 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s } dryRunVerifier := resource.NewQueryParamVerifier(dynamicClient, flags.Factory.OpenAPIGetter(), resource.QueryParamDryRun) + fieldValidationVerifier := resource.NewQueryParamVerifier(dynamicClient, flags.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation) fieldManager := GetApplyFieldManagerFlag(cmd, serverSideApply) // allow for a success message operation to be specified at print time @@ -264,7 +267,12 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s } openAPISchema, _ := flags.Factory.OpenAPISchema() - validator, err := flags.Factory.Validator(cmdutil.GetFlagBool(cmd, "validate")) + + validationDirective, err := cmdutil.GetValidationDirective(cmd) + if err != nil { + return nil, err + } + validator, err := flags.Factory.Validator(validationDirective, fieldValidationVerifier) if err != nil { return nil, err } @@ -308,14 +316,15 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s OpenAPIPatch: flags.OpenAPIPatch, PruneWhitelist: flags.PruneWhitelist, - Recorder: recorder, - Namespace: namespace, - EnforceNamespace: enforceNamespace, - Validator: validator, - Builder: builder, - Mapper: mapper, - DynamicClient: dynamicClient, - OpenAPISchema: openAPISchema, + Recorder: recorder, + Namespace: namespace, + EnforceNamespace: enforceNamespace, + Validator: validator, + ValidationDirective: validationDirective, + Builder: builder, + Mapper: mapper, + DynamicClient: dynamicClient, + OpenAPISchema: openAPISchema, IOStreams: flags.IOStreams, @@ -407,7 +416,6 @@ func (o *ApplyOptions) SetObjects(infos []*resource.Info) { // Run executes the `apply` command. func (o *ApplyOptions) Run() error { - if o.PreProcessorFn != nil { klog.V(4).Infof("Running apply pre-processor function") if err := o.PreProcessorFn(); err != nil { @@ -472,7 +480,8 @@ func (o *ApplyOptions) applyOneObject(info *resource.Info) error { helper := resource.NewHelper(info.Client, info.Mapping). DryRun(o.DryRunStrategy == cmdutil.DryRunServer). - WithFieldManager(o.FieldManager) + WithFieldManager(o.FieldManager). + WithFieldValidation(o.ValidationDirective) if o.DryRunStrategy == cmdutil.DryRunServer { // Ensure the APIServer supports server-side dry-run for the resource, diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go index 68f4c9f3d58..7ef35fca751 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/apply/apply_edit_last_applied.go @@ -83,6 +83,7 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, "Defaults to the line ending native to your platform.") cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, FieldManagerClientSideApply) + cmdutil.AddValidateFlags(cmd) return cmd } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create.go index f26e9a0a814..f6b9a1968ce 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create.go @@ -51,8 +51,11 @@ type CreateOptions struct { PrintFlags *genericclioptions.PrintFlags RecordFlags *genericclioptions.RecordFlags - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + FieldValidationVerifier *resource.QueryParamVerifier + + ValidationDirective string fieldManager string @@ -208,6 +211,12 @@ func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return err } o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun) + o.FieldValidationVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamFieldValidation) + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } printer, err := o.PrintFlags.ToPrinter() if err != nil { @@ -236,7 +245,8 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { if o.EditBeforeCreate { return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager) } - schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) + + schema, err := f.Validator(o.ValidationDirective, o.FieldValidationVerifier) if err != nil { return err } @@ -283,6 +293,7 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { 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) @@ -307,15 +318,19 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error { func RunEditOnCreate(f cmdutil.Factory, printFlags *genericclioptions.PrintFlags, recordFlags *genericclioptions.RecordFlags, ioStreams genericclioptions.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{ - EnableValidation: cmdutil.GetFlagBool(cmd, "validate"), + 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) + err = editOptions.Complete(f, []string{}, cmd) if err != nil { return err } @@ -347,6 +362,7 @@ type CreateSubcommandOptions struct { DryRunVerifier *resource.QueryParamVerifier CreateAnnotation bool FieldManager string + ValidationDirective string Namespace string EnforceNamespace bool @@ -393,6 +409,11 @@ func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command 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) } @@ -447,6 +468,8 @@ func (o *CreateSubcommandOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective + if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(mapping.GroupVersionKind); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go index ff6852a3f8a..a2b56d4ddea 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrole.go @@ -213,6 +213,7 @@ func (c *CreateClusterRoleOptions) RunCreateRole() error { if c.FieldManager != "" { createOptions.FieldManager = c.FieldManager } + createOptions.FieldValidation = c.ValidationDirective if c.DryRunStrategy == cmdutil.DryRunServer { if err := c.DryRunVerifier.HasSupport(clusterRole.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go index a95d96a0918..5959b11877d 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_clusterrolebinding.go @@ -59,9 +59,10 @@ type ClusterRoleBindingOptions struct { FieldManager string CreateAnnotation bool - Client rbacclientv1.RbacV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client rbacclientv1.RbacV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -149,6 +150,10 @@ func (o *ClusterRoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Comma 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 } @@ -169,6 +174,7 @@ func (o *ClusterRoleBindingOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(clusterRoleBinding.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_configmap.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_configmap.go index 8d7837c92e7..3410ee7c410 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_configmap.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_configmap.go @@ -96,9 +96,10 @@ type ConfigMapOptions struct { Namespace string EnforceNamespace bool - Client corev1client.CoreV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -194,6 +195,11 @@ func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -222,6 +228,7 @@ func (o *ConfigMapOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(configMap.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go index ba29705a0f9..09039fd903e 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go @@ -60,14 +60,15 @@ type CreateCronJobOptions struct { Command []string Restart string - Namespace string - EnforceNamespace bool - Client batchv1client.BatchV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - Builder *resource.Builder - FieldManager string - CreateAnnotation bool + Namespace string + EnforceNamespace bool + Client batchv1client.BatchV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string + Builder *resource.Builder + FieldManager string + CreateAnnotation bool genericclioptions.IOStreams } @@ -160,6 +161,11 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -175,6 +181,7 @@ func (o *CreateCronJobOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(cronJob.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go index 1b69cbc71b4..85664b8caa4 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go @@ -72,9 +72,10 @@ type CreateDeploymentOptions struct { FieldManager string CreateAnnotation bool - Client appsv1client.AppsV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client appsv1client.AppsV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -167,6 +168,11 @@ func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -191,6 +197,7 @@ func (o *CreateDeploymentOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(deploy.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_ingress.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_ingress.go index 3906a91a256..5515e3009ec 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_ingress.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_ingress.go @@ -116,9 +116,10 @@ type CreateIngressOptions struct { EnforceNamespace bool CreateAnnotation bool - Client networkingv1client.NetworkingV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client networkingv1client.NetworkingV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string FieldManager string @@ -208,6 +209,11 @@ func (o *CreateIngressOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a 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 } @@ -254,6 +260,7 @@ func (o *CreateIngressOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(ingress.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go index fbe686d4517..9cf43276093 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go @@ -62,14 +62,15 @@ type CreateJobOptions struct { From string Command []string - Namespace string - EnforceNamespace bool - Client batchv1client.BatchV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - Builder *resource.Builder - FieldManager string - CreateAnnotation bool + Namespace string + EnforceNamespace bool + Client batchv1client.BatchV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string + Builder *resource.Builder + FieldManager string + CreateAnnotation bool genericclioptions.IOStreams } @@ -155,6 +156,11 @@ func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -207,6 +213,7 @@ func (o *CreateJobOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(job.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_namespace.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_namespace.go index a0e49b169f2..2e8f1001376 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_namespace.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_namespace.go @@ -52,10 +52,11 @@ type NamespaceOptions struct { // Name of resource being created Name string - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - CreateAnnotation bool - FieldManager string + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string + CreateAnnotation bool + FieldManager string Client *coreclient.CoreV1Client @@ -140,6 +141,11 @@ func (o *NamespaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args 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 } @@ -155,6 +161,7 @@ func (o *NamespaceOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(namespace.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_pdb.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_pdb.go index e97a6e6e78c..5abb6790720 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_pdb.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_pdb.go @@ -70,9 +70,10 @@ type PodDisruptionBudgetOpts struct { Namespace string EnforceNamespace bool - Client *policyv1client.PolicyV1Client - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resourcecli.QueryParamVerifier + Client *policyv1client.PolicyV1Client + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resourcecli.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -165,6 +166,11 @@ func (o *PodDisruptionBudgetOpts) Complete(f cmdutil.Factory, cmd *cobra.Command return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -217,6 +223,7 @@ func (o *PodDisruptionBudgetOpts) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(podDisruptionBudget.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go index 31c70b4e7be..982aad43d97 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_priorityclass.go @@ -64,9 +64,10 @@ type PriorityClassOptions struct { FieldManager string CreateAnnotation bool - Client *schedulingv1client.SchedulingV1Client - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client *schedulingv1client.SchedulingV1Client + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -149,6 +150,11 @@ func (o *PriorityClassOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -168,6 +174,7 @@ func (o *PriorityClassOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(priorityClass.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_quota.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_quota.go index 3494c908bc6..373d6aff2dc 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_quota.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_quota.go @@ -66,9 +66,10 @@ type QuotaOpts struct { Namespace string EnforceNamespace bool - Client *coreclient.CoreV1Client - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resourcecli.QueryParamVerifier + Client *coreclient.CoreV1Client + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resourcecli.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -155,6 +156,11 @@ func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []strin return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -182,6 +188,7 @@ func (o *QuotaOpts) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(resourceQuota.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_role.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_role.go index 1b4b2edd482..0d0ec7d89e6 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_role.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_role.go @@ -136,16 +136,17 @@ type CreateRoleOptions struct { Resources []ResourceOptions ResourceNames []string - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - OutputFormat string - Namespace string - EnforceNamespace bool - Client clientgorbacv1.RbacV1Interface - Mapper meta.RESTMapper - PrintObj func(obj runtime.Object) error - FieldManager string - CreateAnnotation bool + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string + OutputFormat string + Namespace string + EnforceNamespace bool + Client clientgorbacv1.RbacV1Interface + Mapper meta.RESTMapper + PrintObj func(obj runtime.Object) error + FieldManager string + CreateAnnotation bool genericclioptions.IOStreams } @@ -271,6 +272,11 @@ func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args 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 @@ -374,6 +380,7 @@ func (o *CreateRoleOptions) RunCreateRole() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(role.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go index fdef4cc72af..a35bdaa57a6 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_rolebinding.go @@ -61,9 +61,10 @@ type RoleBindingOptions struct { FieldManager string CreateAnnotation bool - Client rbacclientv1.RbacV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client rbacclientv1.RbacV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -149,6 +150,11 @@ func (o *RoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg 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 } @@ -178,6 +184,7 @@ func (o *RoleBindingOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(roleBinding.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret.go index 346690ac8d9..c736b2d4d42 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret.go @@ -112,9 +112,10 @@ type CreateSecretOptions struct { Namespace string EnforceNamespace bool - Client corev1client.CoreV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -212,6 +213,11 @@ func (o *CreateSecretOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -242,6 +248,7 @@ func (o *CreateSecretOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { err := o.DryRunVerifier.HasSupport(secret.GroupVersionKind()) if err != nil { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go index 26fd5dd54d6..c2269966f0d 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_docker.go @@ -108,9 +108,10 @@ type CreateSecretDockerRegistryOptions struct { Namespace string EnforceNamespace bool - Client corev1client.CoreV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -211,6 +212,11 @@ func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cob return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -241,6 +247,7 @@ func (o *CreateSecretDockerRegistryOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { err := o.DryRunVerifier.HasSupport(secretDockerRegistry.GroupVersionKind()) if err != nil { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go index 2091a32850b..09e762a8b42 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_secret_tls.go @@ -69,9 +69,10 @@ type CreateSecretTLSOptions struct { Namespace string EnforceNamespace bool - Client corev1client.CoreV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -168,6 +169,11 @@ func (o *CreateSecretTLSOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -198,6 +204,7 @@ func (o *CreateSecretTLSOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { err := o.DryRunVerifier.HasSupport(secretTLS.GroupVersionKind()) if err != nil { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_service.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_service.go index 95f41fbdd20..d1313405acd 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_service.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_service.go @@ -74,9 +74,10 @@ type ServiceOptions struct { Namespace string EnforceNamespace bool - Client corev1client.CoreV1Interface - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + Client corev1client.CoreV1Interface + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string genericclioptions.IOStreams } @@ -131,6 +132,11 @@ func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -214,6 +220,7 @@ func (o *ServiceOptions) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { createOptions.DryRun = []string{metav1.DryRunAll} } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go index 2bc7969374d..36488df3277 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_serviceaccount.go @@ -51,11 +51,12 @@ type ServiceAccountOpts struct { PrintFlags *genericclioptions.PrintFlags PrintObj func(obj runtime.Object) error // Name of resource being created - Name string - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier - CreateAnnotation bool - FieldManager string + Name string + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string + CreateAnnotation bool + FieldManager string Namespace string EnforceNamespace bool @@ -146,6 +147,11 @@ func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, arg return printer.PrintObj(obj, o.Out) } + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + return nil } @@ -173,6 +179,7 @@ func (o *ServiceAccountOpts) Run() error { if o.FieldManager != "" { createOptions.FieldManager = o.FieldManager } + createOptions.FieldValidation = o.ValidationDirective if o.DryRunStrategy == cmdutil.DryRunServer { if err := o.DryRunVerifier.HasSupport(serviceAccount.GroupVersionKind()); err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go b/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go index 75ddf863a27..a27d477c723 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/edit/edit.go @@ -72,8 +72,6 @@ var ( // NewCmdEdit creates the `edit` command func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { o := editor.NewEditOptions(editor.NormalEditMode, ioStreams) - o.ValidateOptions = cmdutil.ValidateOptions{EnableValidation: true} - cmd := &cobra.Command{ Use: "edit (RESOURCE/NAME | -f FILENAME)", DisableFlagsInUseLine: true, @@ -94,7 +92,7 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra usage := "to use to edit the resource" cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) - cmdutil.AddValidateOptionFlags(cmd, &o.ValidateOptions) + cmdutil.AddValidateFlags(cmd) cmd.Flags().BoolVarP(&o.OutputPatch, "output-patch", "", o.OutputPatch, "Output the patch if the resource is edited.") cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, "Defaults to the line ending native to your platform.") diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go b/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go index 40f925cdcf5..c54c296c4b3 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/replace/replace.go @@ -77,13 +77,14 @@ type ReplaceOptions struct { DeleteFlags *delete.DeleteFlags DeleteOptions *delete.DeleteOptions - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + FieldValidationVerifier *resource.QueryParamVerifier + validationDirective string PrintObj func(obj runtime.Object) error createAnnotation bool - validate bool Schema validation.Schema Builder func() *resource.Builder @@ -151,7 +152,10 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return err } - o.validate = cmdutil.GetFlagBool(cmd, "validate") + o.validationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) @@ -163,6 +167,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return err } o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun) + o.FieldValidationVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamFieldValidation) cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) printer, err := o.PrintFlags.ToPrinter() @@ -196,7 +201,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return err } - schema, err := f.Validator(o.validate) + schema, err := f.Validator(o.validationDirective, o.FieldValidationVerifier) if err != nil { return err } @@ -306,6 +311,7 @@ func (o *ReplaceOptions) Run(f cmdutil.Factory) error { NewHelper(info.Client, info.Mapping). DryRun(o.DryRunStrategy == cmdutil.DryRunServer). WithFieldManager(o.fieldManager). + WithFieldValidation(o.validationDirective). WithSubresource(o.Subresource). Replace(info.Namespace, info.Name, true, info.Object) if err != nil { @@ -409,6 +415,7 @@ func (o *ReplaceOptions) forceReplace() error { obj, err := resource.NewHelper(info.Client, info.Mapping). WithFieldManager(o.fieldManager). + WithFieldValidation(o.validationDirective). Create(info.Namespace, true, info.Object) if err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go index 144a3e26130..1004ae88bd7 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/taint/taint.go @@ -47,8 +47,9 @@ type TaintOptions struct { PrintFlags *genericclioptions.PrintFlags ToPrinter func(string) (printers.ResourcePrinter, error) - DryRunStrategy cmdutil.DryRunStrategy - DryRunVerifier *resource.QueryParamVerifier + DryRunStrategy cmdutil.DryRunStrategy + DryRunVerifier *resource.QueryParamVerifier + ValidationDirective string resources []string taintsToAdd []v1.Taint @@ -150,6 +151,11 @@ func (o *TaintOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun) cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + // retrieves resource and taint args from args // also checks args to verify that all resources are specified before taints taintArgs := []string{} @@ -337,8 +343,9 @@ func (o TaintOptions) RunTaint() error { } helper := resource. NewHelper(client, mapping). + DryRun(o.DryRunStrategy == cmdutil.DryRunServer). WithFieldManager(o.fieldManager). - DryRun(o.DryRunStrategy == cmdutil.DryRunServer) + WithFieldValidation(o.ValidationDirective) var outputObj runtime.Object if createdPatch { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/testing/fake.go b/staging/src/k8s.io/kubectl/pkg/cmd/testing/fake.go index eda2d08afed..07993190e3c 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/testing/fake.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/testing/fake.go @@ -499,7 +499,7 @@ func (f *TestFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r } // Validator returns a validation schema -func (f *TestFactory) Validator(validate bool) (validation.Schema, error) { +func (f *TestFactory) Validator(validateDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) { return validation.NullSchema{}, nil } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/editor/editoptions.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/editor/editoptions.go index c3afcb7cace..c9a47fc674a 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/editor/editoptions.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/editor/editoptions.go @@ -68,6 +68,8 @@ type EditOptions struct { WindowsLineEndings bool cmdutil.ValidateOptions + ValidationDirective string + FieldValidationVerifier *resource.QueryParamVerifier OriginalResult *resource.Result @@ -215,6 +217,17 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm return o.PrintFlags.ToPrinter() } + dynamicClient, err := f.DynamicClient() + if err != nil { + return err + } + o.FieldValidationVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamFieldValidation) + + o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd) + if err != nil { + return err + } + o.CmdNamespace = cmdNamespace o.f = f @@ -309,7 +322,7 @@ func (o *EditOptions) Run() error { klog.V(4).Infof("User edited:\n%s", string(edited)) // Apply validation - schema, err := o.f.Validator(o.EnableValidation) + schema, err := o.f.Validator(o.ValidationDirective, o.FieldValidationVerifier) if err != nil { return preservedFile(err, file, o.ErrOut) } @@ -571,7 +584,10 @@ func (o *EditOptions) annotationPatch(update *resource.Info) error { if err != nil { return err } - helper := resource.NewHelper(client, mapping).WithFieldManager(o.FieldManager).WithSubresource(o.Subresource) + helper := resource.NewHelper(client, mapping). + WithFieldManager(o.FieldManager). + WithFieldValidation(o.ValidationDirective). + WithSubresource(o.Subresource) _, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch, nil) if err != nil { return err @@ -709,7 +725,9 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor } patched, err := resource.NewHelper(info.Client, info.Mapping). - WithFieldManager(o.FieldManager).WithSubresource(o.Subresource). + WithFieldManager(o.FieldManager). + WithFieldValidation(o.ValidationDirective). + WithSubresource(o.Subresource). Patch(info.Namespace, info.Name, patchType, patch, nil) if err != nil { fmt.Fprintln(o.ErrOut, results.addError(err, info)) @@ -729,6 +747,7 @@ func (o *EditOptions) visitToCreate(createVisitor resource.Visitor) error { err := createVisitor.Visit(func(info *resource.Info, incomingErr error) error { obj, err := resource.NewHelper(info.Client, info.Mapping). WithFieldManager(o.FieldManager). + WithFieldValidation(o.ValidationDirective). Create(info.Namespace, true, info.Object) if err != nil { return err diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/factory.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/factory.go index 9235b4b0ab3..4533147753b 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/factory.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/factory.go @@ -61,7 +61,7 @@ type Factory interface { UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) // Returns a schema that can validate objects stored on disk. - Validator(validate bool) (validation.Schema, error) + Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) // OpenAPISchema returns the parsed openapi schema definition OpenAPISchema() (openapi.Resources, error) // OpenAPIGetter returns a getter for the openapi schema document diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go index 2b9074cbac8..acf9daf591b 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/factory_client_access.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" @@ -141,8 +142,14 @@ func (f *factoryImpl) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r return restclient.RESTClientFor(cfg) } -func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) { - if !validate { +func (f *factoryImpl) Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) { + // client-side schema validation is only performed + // when the validationDirective is strict. + // If the directive is warn, we rely on the ParamVerifyingSchema + // to ignore the client-side validation and provide a warning + // to the user that attempting warn validation when SS validation + // is unsupported is inert. + if validationDirective == metav1.FieldValidationIgnore { return validation.NullSchema{}, nil } @@ -151,10 +158,11 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) { return nil, err } - return validation.ConjunctiveSchema{ + schema := validation.ConjunctiveSchema{ openapivalidation.NewSchemaValidation(resources), validation.NoDoubleKeySchema{}, - }, nil + } + return validation.NewParamVerifyingSchema(schema, verifier, string(validationDirective)), nil } // OpenAPISchema returns metadata and structural information about diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go index 10430a8da2b..00d6ac98fb1 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers.go @@ -399,11 +399,14 @@ func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) { } func AddValidateFlags(cmd *cobra.Command) { - cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it") -} - -func AddValidateOptionFlags(cmd *cobra.Command, options *ValidateOptions) { - cmd.Flags().BoolVar(&options.EnableValidation, "validate", options.EnableValidation, "If true, use a schema to validate the input before sending it") + cmd.Flags().String( + "validate", + "strict", + `Must be one of: strict (or true), warn, ignore (or false). + "true" or "strict" will use a schema to validate the input and fail the request if invalid. It will perform server side validation if ServerSideFieldValidation is enabled on the api-server, but will fall back to less reliable client-side validation if not. + "warn" will warn about unknown or duplicate fields without blocking the request if server-side field validation is enabled on the API server, and behave as "ignore" otherwise. + "false" or "ignore" will not perform any schema validation, silently dropping any unknown or duplicate fields.`, + ) } func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) { @@ -475,7 +478,7 @@ func AddSubresourceFlags(cmd *cobra.Command, subresource *string, usage string, } type ValidateOptions struct { - EnableValidation bool + ValidationDirective string } // Merge converts the passed in object to JSON, merges the fragment into it using an RFC7396 JSON Merge Patch, @@ -576,6 +579,30 @@ func GetFieldManagerFlag(cmd *cobra.Command) string { return GetFlagString(cmd, "field-manager") } +func GetValidationDirective(cmd *cobra.Command) (string, error) { + var validateFlag = GetFlagString(cmd, "validate") + b, err := strconv.ParseBool(validateFlag) + if err != nil { + switch validateFlag { + case cmd.Flag("validate").NoOptDefVal: + return metav1.FieldValidationStrict, nil + case "strict": + return metav1.FieldValidationStrict, nil + case "warn": + return metav1.FieldValidationWarn, nil + case "ignore": + return metav1.FieldValidationIgnore, nil + default: + return metav1.FieldValidationStrict, fmt.Errorf(`invalid - validate option %q; must be one of: strict (or true), warn, ignore (or false)`, validateFlag) + } + } + // The flag was a boolean + if b { + return metav1.FieldValidationStrict, nil + } + return metav1.FieldValidationIgnore, nil +} + type DryRunStrategy int const ( diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go index abe73acb960..790b3669978 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/util/helpers_test.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + goerrors "errors" "fmt" "io/ioutil" "net/http" @@ -27,6 +28,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" @@ -467,3 +469,68 @@ func TestDifferenceFunc(t *testing.T) { } } } + +func TestGetValidationDirective(t *testing.T) { + tests := []struct { + validateFlag string + expectedDirective string + expectedErr error + }{ + { + expectedDirective: metav1.FieldValidationStrict, + }, + { + validateFlag: "true", + expectedDirective: metav1.FieldValidationStrict, + }, + { + validateFlag: "True", + expectedDirective: metav1.FieldValidationStrict, + }, + { + validateFlag: "strict", + expectedDirective: metav1.FieldValidationStrict, + }, + { + validateFlag: "warn", + expectedDirective: metav1.FieldValidationWarn, + }, + { + validateFlag: "ignore", + expectedDirective: metav1.FieldValidationIgnore, + }, + { + validateFlag: "false", + expectedDirective: metav1.FieldValidationIgnore, + }, + { + validateFlag: "False", + expectedDirective: metav1.FieldValidationIgnore, + }, + { + validateFlag: "foo", + expectedDirective: metav1.FieldValidationStrict, + expectedErr: goerrors.New(`invalid - validate option "foo"; must be one of: strict (or true), warn, ignore (or false)`), + }, + } + + for _, tc := range tests { + cmd := &cobra.Command{} + AddValidateFlags(cmd) + cmd.Flags().Set("validate", tc.validateFlag) + directive, err := GetValidationDirective(cmd) + if directive != tc.expectedDirective { + t.Errorf("validation directive, expected: %v, but got: %v", tc.expectedDirective, directive) + } + if tc.expectedErr != nil { + if err.Error() != tc.expectedErr.Error() { + t.Errorf("GetValidationDirective error, expected: %v, but got: %v", tc.expectedErr, err) + } + } else { + if err != nil { + t.Errorf("expecte no error, but got: %v", err) + } + } + + } +} diff --git a/staging/src/k8s.io/kubectl/pkg/util/openapi/validation/validation.go b/staging/src/k8s.io/kubectl/pkg/util/openapi/validation/validation.go index 25aec97ed1f..62ce3df227a 100644 --- a/staging/src/k8s.io/kubectl/pkg/util/openapi/validation/validation.go +++ b/staging/src/k8s.io/kubectl/pkg/util/openapi/validation/validation.go @@ -43,12 +43,12 @@ func NewSchemaValidation(resources openapi.Resources) *SchemaValidation { // ValidateBytes will validates the object against using the Resources // object. func (v *SchemaValidation) ValidateBytes(data []byte) error { - obj, err := parse(data) + obj, err := Parse(data) if err != nil { return err } - gvk, errs := getObjectKind(obj) + gvk, errs := GetObjectKind(obj) if errs != nil { return utilerrors.NewAggregate(errs) } @@ -71,7 +71,7 @@ func (v *SchemaValidation) validateList(object interface{}) []error { return []error{errors.New("invalid object to validate")} } for _, item := range fields["items"].([]interface{}) { - if gvk, errs := getObjectKind(item); errs != nil { + if gvk, errs := GetObjectKind(item); errs != nil { allErrors = append(allErrors, errs...) } else { allErrors = append(allErrors, v.validateResource(item, gvk)...) @@ -90,7 +90,7 @@ func (v *SchemaValidation) validateResource(obj interface{}, gvk schema.GroupVer return validation.ValidateModel(obj, resource, gvk.Kind) } -func parse(data []byte) (interface{}, error) { +func Parse(data []byte) (interface{}, error) { var obj interface{} out, err := yaml.ToJSON(data) if err != nil { @@ -102,7 +102,7 @@ func parse(data []byte) (interface{}, error) { return obj, nil } -func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) { +func GetObjectKind(object interface{}) (schema.GroupVersionKind, []error) { var listErrors []error fields, ok := object.(map[string]interface{}) if !ok || fields == nil { diff --git a/staging/src/k8s.io/kubectl/pkg/validation/schema.go b/staging/src/k8s.io/kubectl/pkg/validation/schema.go index 6eef619390d..56123e28645 100644 --- a/staging/src/k8s.io/kubectl/pkg/validation/schema.go +++ b/staging/src/k8s.io/kubectl/pkg/validation/schema.go @@ -22,7 +22,11 @@ import ( "fmt" ejson "github.com/exponent-io/jsonpath" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/cli-runtime/pkg/resource" + "k8s.io/klog/v2" + schemavalidation "k8s.io/kubectl/pkg/util/openapi/validation" ) // Schema is an interface that knows how to validate an API object serialized to a byte array. @@ -101,3 +105,50 @@ func (c ConjunctiveSchema) ValidateBytes(data []byte) error { } return utilerrors.NewAggregate(list) } + +func NewParamVerifyingSchema(s Schema, verifier resource.Verifier, directive string) Schema { + return ¶mVerifyingSchema{ + schema: s, + verifier: verifier, + directive: directive, + } +} + +// paramVerifyingSchema only performs validation +// based on the fieldValidation query param +// being unsupported by the apiserver, because +// server-side validation will be performed instead +// of client-side validation. +type paramVerifyingSchema struct { + schema Schema + verifier resource.Verifier + directive string +} + +// ValidateBytes validates bytes per a ParamVerifyingSchema +func (c *paramVerifyingSchema) ValidateBytes(data []byte) error { + obj, err := schemavalidation.Parse(data) + if err != nil { + return err + } + + gvk, errs := schemavalidation.GetObjectKind(obj) + if errs != nil { + return utilerrors.NewAggregate(errs) + } + + err = c.verifier.HasSupport(gvk) + if resource.IsParamUnsupportedError(err) { + switch c.directive { + case metav1.FieldValidationStrict: + return c.schema.ValidateBytes(data) + case metav1.FieldValidationWarn: + klog.Warningf("cannot perform warn validation if server-side field validation is unsupported, skipping validation") + default: + // can't be reached + klog.Warningf("unexpected field validation directive: %s, skipping validation", c.directive) + } + return nil + } + return err +} diff --git a/staging/src/k8s.io/kubectl/pkg/validation/schema_test.go b/staging/src/k8s.io/kubectl/pkg/validation/schema_test.go index c0ac7646312..724f28c9545 100644 --- a/staging/src/k8s.io/kubectl/pkg/validation/schema_test.go +++ b/staging/src/k8s.io/kubectl/pkg/validation/schema_test.go @@ -19,6 +19,9 @@ package validation import ( "fmt" "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/resource" ) func TestValidateDuplicateLabelsFailCases(t *testing.T) { @@ -139,3 +142,121 @@ func TestConjunctiveSchema(t *testing.T) { }) } } + +type mockVerifier struct { + supported bool +} + +func (v *mockVerifier) HasSupport(gvk schema.GroupVersionKind) error { + if !v.supported { + return resource.NewParamUnsupportedError(gvk, resource.QueryParamFieldValidation) + } + return nil +} + +// TestParamVerifyingSchema tests that client-side schema validation +// should be bypassed (and therefore validation succeeds) in all cases +// except when the field validation is "Strict" and server-side validation is +// unsupported. +func TestParamVerifyingSchema(t *testing.T) { + bytes := []byte(` +{ + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "name", + "labels": { + "name": "redis-master" + } + }, + "spec": { + "containers": [ + { + "name": "master", + "image": "gcr.io/fake_project/fake_image:fake_tag", + "args": "this is a bad command" + } + ] + } +} +`) + supportedVerifier := &mockVerifier{true} + unsupportedVerifier := &mockVerifier{false} + tests := []struct { + name string + supported bool + schema Schema + verifier resource.Verifier + directive string + shouldPass bool + }{ + { + name: "supported, strict", + schema: NullSchema{}, + verifier: supportedVerifier, + directive: "Strict", + shouldPass: true, + }, + { + name: "supported, warn", + schema: NullSchema{}, + verifier: supportedVerifier, + directive: "Warn", + shouldPass: true, + }, + { + name: "unsupported, strict", + schema: NullSchema{}, + verifier: unsupportedVerifier, + directive: "Strict", + shouldPass: true, + }, + { + name: "unsupported, warn", + schema: NullSchema{}, + verifier: unsupportedVerifier, + directive: "Warn", + shouldPass: true, + }, + { + name: "supported, strict, invalid schema", + schema: AlwaysInvalidSchema{}, + verifier: supportedVerifier, + directive: "Strict", + shouldPass: true, + }, + { + name: "supported, warn, invalid schema", + schema: AlwaysInvalidSchema{}, + verifier: supportedVerifier, + directive: "Warn", + shouldPass: true, + }, + { + name: "unsupported, strict, invalid schema", + schema: AlwaysInvalidSchema{}, + verifier: unsupportedVerifier, + directive: "Strict", + shouldPass: false, + }, + { + name: "unsupported, warn, invalid schema", + schema: AlwaysInvalidSchema{}, + verifier: unsupportedVerifier, + directive: "Warn", + shouldPass: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schema := NewParamVerifyingSchema(tt.schema, tt.verifier, tt.directive) + err := schema.ValidateBytes(bytes) + if err != nil && tt.shouldPass { + t.Errorf("Unexpected error: %v in %s", err, tt.name) + } + if err == nil && !tt.shouldPass { + t.Errorf("Unexpected non-error: %s", tt.name) + } + }) + } +} diff --git a/test/cmd/create.sh b/test/cmd/create.sh index 67bcf8ff59d..fda8e148a1f 100755 --- a/test/cmd/create.sh +++ b/test/cmd/create.sh @@ -153,3 +153,62 @@ run_kubectl_create_kustomization_directory_tests() { set +o nounset set +o errexit } + +# Runs tests related to kubectl create --validate +run_kubectl_create_validate_tests() { + set -o nounset + set -o errexit + + create_and_use_new_namespace + + ## test --validate=true + kube::log::status "Testing kubectl create --validate=true" + # create and verify + output_message=$(! kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=true 2>&1) + kube::test::if_has_string "${output_message}" 'error validating data' + + ## test --validate=false + kube::log::status "Testing kubectl create --validate=false" + # create and verify + output_message=$(kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=false) + kube::test::if_has_string "${output_message}" "deployment.apps/invalid-nginx-deployment created" + # cleanup + kubectl delete deployment invalid-nginx-deployment + + ## test --validate=strict + kube::log::status "Testing kubectl create --validate=strict" + # create and verify + output_message=$(! kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=strict 2>&1) + kube::test::if_has_string "${output_message}" 'error validating data' + + ## test --validate=warn + kube::log::status "Testing kubectl create --validate=warn" + # create and verify + output_message=$(kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=warn) + kube::test::if_has_string "${output_message}" "deployment.apps/invalid-nginx-deployment created" + # cleanup + kubectl delete deployment invalid-nginx-deployment + + ## test --validate=ignore + kube::log::status "Testing kubectl create --validate=ignore" + # create and verify + output_message=$(kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=ignore) + kube::test::if_has_string "${output_message}" "deployment.apps/invalid-nginx-deployment created" + # cleanup + kubectl delete deployment invalid-nginx-deployment + + ## test default is strict validation + kube::log::status "Testing kubectl create" + # create and verify + output_message=$(! kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml 2>&1) + kube::test::if_has_string "${output_message}" 'error validating data' + + ## test invalid validate value + kube::log::status "Testing kubectl create --validate=foo" + # create and verify + output_message=$(! kubectl create -f hack/testdata/invalid-deployment-unknown-and-duplicate-fields.yaml --validate=foo 2>&1) + kube::test::if_has_string "${output_message}" 'invalid - validate option "foo"' + + set +o nounset + set +o errexit +} diff --git a/test/cmd/legacy-script.sh b/test/cmd/legacy-script.sh index 87f13e031ae..5c8414aa995 100755 --- a/test/cmd/legacy-script.sh +++ b/test/cmd/legacy-script.sh @@ -573,6 +573,7 @@ runTests() { fi if kube::test::if_supports_resource "${deployments}"; then record_command run_kubectl_create_kustomization_directory_tests + record_command run_kubectl_create_validate_tests fi ######################