add server-side validation support to kubectl

This commit is contained in:
Kevin Delgado 2022-03-09 14:52:32 +00:00
parent 083c3ac4e8
commit fe3772890f
39 changed files with 715 additions and 139 deletions

View File

@ -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

View File

@ -124,7 +124,16 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err er
} }
o.validator = func() (validation.Schema, error) { 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 // build the printer

View File

@ -54,6 +54,10 @@ type Helper struct {
// FieldManager is the name associated with the actor or entity that is making // FieldManager is the name associated with the actor or entity that is making
// changes. // changes.
FieldManager string 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 // NewHelper creates a Helper from a ResourceMapping
@ -79,6 +83,13 @@ func (m *Helper) WithFieldManager(fieldManager string) *Helper {
return m 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 (<resource>/[ns/<namespace>/]<name>/<subresource>) // Subresource sets the helper to access (<resource>/[ns/<namespace>/]<name>/<subresource>)
func (m *Helper) WithSubresource(subresource string) *Helper { func (m *Helper) WithSubresource(subresource string) *Helper {
m.Subresource = subresource m.Subresource = subresource
@ -206,6 +217,9 @@ func (m *Helper) CreateWithOptions(namespace string, modify bool, obj runtime.Ob
if m.FieldManager != "" { if m.FieldManager != "" {
options.FieldManager = m.FieldManager options.FieldManager = m.FieldManager
} }
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
if modify { if modify {
// Attempt to version the object based on client logic. // Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj) version, err := metadataAccessor.ResourceVersion(obj)
@ -242,6 +256,9 @@ func (m *Helper) Patch(namespace, name string, pt types.PatchType, data []byte,
if m.FieldManager != "" { if m.FieldManager != "" {
options.FieldManager = m.FieldManager options.FieldManager = m.FieldManager
} }
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
return m.RESTClient.Patch(pt). return m.RESTClient.Patch(pt).
NamespaceIfScoped(namespace, m.NamespaceScoped). NamespaceIfScoped(namespace, m.NamespaceScoped).
Resource(m.Resource). Resource(m.Resource).
@ -262,6 +279,9 @@ func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Obj
if m.FieldManager != "" { if m.FieldManager != "" {
options.FieldManager = m.FieldManager options.FieldManager = m.FieldManager
} }
if m.FieldValidation != "" {
options.FieldValidation = m.FieldValidation
}
// Attempt to version the object based on client logic. // Attempt to version the object based on client logic.
version, err := metadataAccessor.ResourceVersion(obj) version, err := metadataAccessor.ResourceVersion(obj)

View File

@ -62,6 +62,11 @@ type QueryParamVerifier struct {
queryParam VerifiableQueryParam 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 // VerifiableQueryParam is a query parameter who's enablement on the
// apiserver can be determined by evaluating the OpenAPI for a specific // apiserver can be determined by evaluating the OpenAPI for a specific
// GVK. // GVK.
@ -72,6 +77,7 @@ const (
QueryParamFieldValidation VerifiableQueryParam = "fieldValidation" QueryParamFieldValidation VerifiableQueryParam = "fieldValidation"
) )
// HasSupport checks if the given gvk supports the query param configured on v
func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error { func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
oapi, err := v.openAPIGetter.OpenAPISchema() oapi, err := v.openAPIGetter.OpenAPISchema()
if err != nil { if err != nil {
@ -90,7 +96,7 @@ func (v *QueryParamVerifier) HasSupport(gvk schema.GroupVersionKind) error {
} }
} }
if !supports { if !supports {
return newParamUnsupportedError(gvk, v.queryParam) return NewParamUnsupportedError(gvk, v.queryParam)
} }
return nil return nil
} }
@ -100,7 +106,7 @@ type paramUnsupportedError struct {
param VerifiableQueryParam param VerifiableQueryParam
} }
func newParamUnsupportedError(gvk schema.GroupVersionKind, param VerifiableQueryParam) error { func NewParamUnsupportedError(gvk schema.GroupVersionKind, param VerifiableQueryParam) error {
return &paramUnsupportedError{ return &paramUnsupportedError{
gvk: gvk, gvk: gvk,
param: param, param: param,

View File

@ -86,6 +86,7 @@ type ApplyOptions struct {
Selector string Selector string
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
FieldValidationVerifier *resource.QueryParamVerifier
Prune bool Prune bool
PruneResources []prune.Resource PruneResources []prune.Resource
cmdBaseName string cmdBaseName string
@ -94,6 +95,7 @@ type ApplyOptions struct {
OpenAPIPatch bool OpenAPIPatch bool
PruneWhitelist []string PruneWhitelist []string
ValidationDirective string
Validator validation.Schema Validator validation.Schema
Builder *resource.Builder Builder *resource.Builder
Mapper meta.RESTMapper Mapper meta.RESTMapper
@ -238,6 +240,7 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s
} }
dryRunVerifier := resource.NewQueryParamVerifier(dynamicClient, flags.Factory.OpenAPIGetter(), resource.QueryParamDryRun) dryRunVerifier := resource.NewQueryParamVerifier(dynamicClient, flags.Factory.OpenAPIGetter(), resource.QueryParamDryRun)
fieldValidationVerifier := resource.NewQueryParamVerifier(dynamicClient, flags.Factory.OpenAPIGetter(), resource.QueryParamFieldValidation)
fieldManager := GetApplyFieldManagerFlag(cmd, serverSideApply) fieldManager := GetApplyFieldManagerFlag(cmd, serverSideApply)
// allow for a success message operation to be specified at print time // 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() 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 { if err != nil {
return nil, err return nil, err
} }
@ -312,6 +320,7 @@ func (flags *ApplyFlags) ToOptions(cmd *cobra.Command, baseName string, args []s
Namespace: namespace, Namespace: namespace,
EnforceNamespace: enforceNamespace, EnforceNamespace: enforceNamespace,
Validator: validator, Validator: validator,
ValidationDirective: validationDirective,
Builder: builder, Builder: builder,
Mapper: mapper, Mapper: mapper,
DynamicClient: dynamicClient, DynamicClient: dynamicClient,
@ -407,7 +416,6 @@ func (o *ApplyOptions) SetObjects(infos []*resource.Info) {
// Run executes the `apply` command. // Run executes the `apply` command.
func (o *ApplyOptions) Run() error { func (o *ApplyOptions) Run() error {
if o.PreProcessorFn != nil { if o.PreProcessorFn != nil {
klog.V(4).Infof("Running apply pre-processor function") klog.V(4).Infof("Running apply pre-processor function")
if err := o.PreProcessorFn(); err != nil { 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). helper := resource.NewHelper(info.Client, info.Mapping).
DryRun(o.DryRunStrategy == cmdutil.DryRunServer). DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
WithFieldManager(o.FieldManager) WithFieldManager(o.FieldManager).
WithFieldValidation(o.ValidationDirective)
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
// Ensure the APIServer supports server-side dry-run for the resource, // Ensure the APIServer supports server-side dry-run for the resource,

View File

@ -83,6 +83,7 @@ func NewCmdApplyEditLastApplied(f cmdutil.Factory, ioStreams genericclioptions.I
cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings, cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
"Defaults to the line ending native to your platform.") "Defaults to the line ending native to your platform.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, FieldManagerClientSideApply) cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, FieldManagerClientSideApply)
cmdutil.AddValidateFlags(cmd)
return cmd return cmd
} }

View File

@ -53,6 +53,9 @@ type CreateOptions struct {
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
FieldValidationVerifier *resource.QueryParamVerifier
ValidationDirective string
fieldManager string fieldManager string
@ -208,6 +211,12 @@ func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err return err
} }
o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun) 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() printer, err := o.PrintFlags.ToPrinter()
if err != nil { if err != nil {
@ -236,7 +245,8 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
if o.EditBeforeCreate { if o.EditBeforeCreate {
return RunEditOnCreate(f, o.PrintFlags, o.RecordFlags, o.IOStreams, cmd, &o.FilenameOptions, o.fieldManager) 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 { if err != nil {
return err return err
} }
@ -283,6 +293,7 @@ func (o *CreateOptions) RunCreate(f cmdutil.Factory, cmd *cobra.Command) error {
NewHelper(info.Client, info.Mapping). NewHelper(info.Client, info.Mapping).
DryRun(o.DryRunStrategy == cmdutil.DryRunServer). DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
WithFieldManager(o.fieldManager). WithFieldManager(o.fieldManager).
WithFieldValidation(o.ValidationDirective).
Create(info.Namespace, true, info.Object) Create(info.Namespace, true, info.Object)
if err != nil { if err != nil {
return cmdutil.AddSourceToErr("creating", info.Source, err) 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 { 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 := editor.NewEditOptions(editor.EditBeforeCreateMode, ioStreams)
editOptions.FilenameOptions = *options editOptions.FilenameOptions = *options
validationDirective, err := cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
editOptions.ValidateOptions = cmdutil.ValidateOptions{ editOptions.ValidateOptions = cmdutil.ValidateOptions{
EnableValidation: cmdutil.GetFlagBool(cmd, "validate"), ValidationDirective: string(validationDirective),
} }
editOptions.PrintFlags = printFlags editOptions.PrintFlags = printFlags
editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag) editOptions.ApplyAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
editOptions.RecordFlags = recordFlags editOptions.RecordFlags = recordFlags
editOptions.FieldManager = "kubectl-create" editOptions.FieldManager = "kubectl-create"
err := editOptions.Complete(f, []string{}, cmd) err = editOptions.Complete(f, []string{}, cmd)
if err != nil { if err != nil {
return err return err
} }
@ -347,6 +362,7 @@ type CreateSubcommandOptions struct {
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
CreateAnnotation bool CreateAnnotation bool
FieldManager string FieldManager string
ValidationDirective string
Namespace string Namespace string
EnforceNamespace bool EnforceNamespace bool
@ -393,6 +409,11 @@ func (o *CreateSubcommandOptions) Complete(f cmdutil.Factory, cmd *cobra.Command
return err return err
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
o.PrintObj = func(obj kruntime.Object, out io.Writer) error { o.PrintObj = func(obj kruntime.Object, out io.Writer) error {
return printer.PrintObj(obj, out) return printer.PrintObj(obj, out)
} }
@ -447,6 +468,8 @@ func (o *CreateSubcommandOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(mapping.GroupVersionKind); err != nil { if err := o.DryRunVerifier.HasSupport(mapping.GroupVersionKind); err != nil {
return err return err

View File

@ -213,6 +213,7 @@ func (c *CreateClusterRoleOptions) RunCreateRole() error {
if c.FieldManager != "" { if c.FieldManager != "" {
createOptions.FieldManager = c.FieldManager createOptions.FieldManager = c.FieldManager
} }
createOptions.FieldValidation = c.ValidationDirective
if c.DryRunStrategy == cmdutil.DryRunServer { if c.DryRunStrategy == cmdutil.DryRunServer {
if err := c.DryRunVerifier.HasSupport(clusterRole.GroupVersionKind()); err != nil { if err := c.DryRunVerifier.HasSupport(clusterRole.GroupVersionKind()); err != nil {
return err return err

View File

@ -62,6 +62,7 @@ type ClusterRoleBindingOptions struct {
Client rbacclientv1.RbacV1Interface Client rbacclientv1.RbacV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -149,6 +150,10 @@ func (o *ClusterRoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Comma
o.PrintObj = func(obj runtime.Object) error { o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -169,6 +174,7 @@ func (o *ClusterRoleBindingOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(clusterRoleBinding.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(clusterRoleBinding.GroupVersionKind()); err != nil {
return err return err

View File

@ -99,6 +99,7 @@ type ConfigMapOptions struct {
Client corev1client.CoreV1Interface Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -194,6 +195,11 @@ func (o *ConfigMapOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -222,6 +228,7 @@ func (o *ConfigMapOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(configMap.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(configMap.GroupVersionKind()); err != nil {
return err return err

View File

@ -65,6 +65,7 @@ type CreateCronJobOptions struct {
Client batchv1client.BatchV1Interface Client batchv1client.BatchV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
Builder *resource.Builder Builder *resource.Builder
FieldManager string FieldManager string
CreateAnnotation bool CreateAnnotation bool
@ -160,6 +161,11 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -175,6 +181,7 @@ func (o *CreateCronJobOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(cronJob.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(cronJob.GroupVersionKind()); err != nil {
return err return err

View File

@ -75,6 +75,7 @@ type CreateDeploymentOptions struct {
Client appsv1client.AppsV1Interface Client appsv1client.AppsV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -167,6 +168,11 @@ func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -191,6 +197,7 @@ func (o *CreateDeploymentOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(deploy.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(deploy.GroupVersionKind()); err != nil {
return err return err

View File

@ -119,6 +119,7 @@ type CreateIngressOptions struct {
Client networkingv1client.NetworkingV1Interface Client networkingv1client.NetworkingV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
FieldManager 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 { o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -254,6 +260,7 @@ func (o *CreateIngressOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(ingress.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(ingress.GroupVersionKind()); err != nil {
return err return err

View File

@ -67,6 +67,7 @@ type CreateJobOptions struct {
Client batchv1client.BatchV1Interface Client batchv1client.BatchV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
Builder *resource.Builder Builder *resource.Builder
FieldManager string FieldManager string
CreateAnnotation bool CreateAnnotation bool
@ -155,6 +156,11 @@ func (o *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -207,6 +213,7 @@ func (o *CreateJobOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(job.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(job.GroupVersionKind()); err != nil {
return err return err

View File

@ -54,6 +54,7 @@ type NamespaceOptions struct {
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
CreateAnnotation bool CreateAnnotation bool
FieldManager string FieldManager string
@ -140,6 +141,11 @@ func (o *NamespaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
o.PrintObj = func(obj runtime.Object) error { o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -155,6 +161,7 @@ func (o *NamespaceOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(namespace.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(namespace.GroupVersionKind()); err != nil {
return err return err

View File

@ -73,6 +73,7 @@ type PodDisruptionBudgetOpts struct {
Client *policyv1client.PolicyV1Client Client *policyv1client.PolicyV1Client
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resourcecli.QueryParamVerifier DryRunVerifier *resourcecli.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -165,6 +166,11 @@ func (o *PodDisruptionBudgetOpts) Complete(f cmdutil.Factory, cmd *cobra.Command
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -217,6 +223,7 @@ func (o *PodDisruptionBudgetOpts) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(podDisruptionBudget.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(podDisruptionBudget.GroupVersionKind()); err != nil {
return err return err

View File

@ -67,6 +67,7 @@ type PriorityClassOptions struct {
Client *schedulingv1client.SchedulingV1Client Client *schedulingv1client.SchedulingV1Client
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -149,6 +150,11 @@ func (o *PriorityClassOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -168,6 +174,7 @@ func (o *PriorityClassOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(priorityClass.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(priorityClass.GroupVersionKind()); err != nil {
return err return err

View File

@ -69,6 +69,7 @@ type QuotaOpts struct {
Client *coreclient.CoreV1Client Client *coreclient.CoreV1Client
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resourcecli.QueryParamVerifier DryRunVerifier *resourcecli.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -155,6 +156,11 @@ func (o *QuotaOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []strin
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -182,6 +188,7 @@ func (o *QuotaOpts) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(resourceQuota.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(resourceQuota.GroupVersionKind()); err != nil {
return err return err

View File

@ -138,6 +138,7 @@ type CreateRoleOptions struct {
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
OutputFormat string OutputFormat string
Namespace string Namespace string
EnforceNamespace bool EnforceNamespace bool
@ -271,6 +272,11 @@ func (o *CreateRoleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
return printer.PrintObj(obj, o.Out) 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() o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil { if err != nil {
return err return err
@ -374,6 +380,7 @@ func (o *CreateRoleOptions) RunCreateRole() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(role.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(role.GroupVersionKind()); err != nil {
return err return err

View File

@ -64,6 +64,7 @@ type RoleBindingOptions struct {
Client rbacclientv1.RbacV1Interface Client rbacclientv1.RbacV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -149,6 +150,11 @@ func (o *RoleBindingOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
o.PrintObj = func(obj runtime.Object) error { o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -178,6 +184,7 @@ func (o *RoleBindingOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(roleBinding.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(roleBinding.GroupVersionKind()); err != nil {
return err return err

View File

@ -115,6 +115,7 @@ type CreateSecretOptions struct {
Client corev1client.CoreV1Interface Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -212,6 +213,11 @@ func (o *CreateSecretOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, ar
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -242,6 +248,7 @@ func (o *CreateSecretOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secret.GroupVersionKind()) err := o.DryRunVerifier.HasSupport(secret.GroupVersionKind())
if err != nil { if err != nil {

View File

@ -111,6 +111,7 @@ type CreateSecretDockerRegistryOptions struct {
Client corev1client.CoreV1Interface Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -211,6 +212,11 @@ func (o *CreateSecretDockerRegistryOptions) Complete(f cmdutil.Factory, cmd *cob
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -241,6 +247,7 @@ func (o *CreateSecretDockerRegistryOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secretDockerRegistry.GroupVersionKind()) err := o.DryRunVerifier.HasSupport(secretDockerRegistry.GroupVersionKind())
if err != nil { if err != nil {

View File

@ -72,6 +72,7 @@ type CreateSecretTLSOptions struct {
Client corev1client.CoreV1Interface Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -168,6 +169,11 @@ func (o *CreateSecretTLSOptions) Complete(f cmdutil.Factory, cmd *cobra.Command,
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -198,6 +204,7 @@ func (o *CreateSecretTLSOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
err := o.DryRunVerifier.HasSupport(secretTLS.GroupVersionKind()) err := o.DryRunVerifier.HasSupport(secretTLS.GroupVersionKind())
if err != nil { if err != nil {

View File

@ -77,6 +77,7 @@ type ServiceOptions struct {
Client corev1client.CoreV1Interface Client corev1client.CoreV1Interface
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
genericclioptions.IOStreams genericclioptions.IOStreams
} }
@ -131,6 +132,11 @@ func (o *ServiceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -214,6 +220,7 @@ func (o *ServiceOptions) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
createOptions.DryRun = []string{metav1.DryRunAll} createOptions.DryRun = []string{metav1.DryRunAll}
} }

View File

@ -54,6 +54,7 @@ type ServiceAccountOpts struct {
Name string Name string
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
CreateAnnotation bool CreateAnnotation bool
FieldManager string FieldManager string
@ -146,6 +147,11 @@ func (o *ServiceAccountOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
return printer.PrintObj(obj, o.Out) return printer.PrintObj(obj, o.Out)
} }
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
return nil return nil
} }
@ -173,6 +179,7 @@ func (o *ServiceAccountOpts) Run() error {
if o.FieldManager != "" { if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager createOptions.FieldManager = o.FieldManager
} }
createOptions.FieldValidation = o.ValidationDirective
if o.DryRunStrategy == cmdutil.DryRunServer { if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(serviceAccount.GroupVersionKind()); err != nil { if err := o.DryRunVerifier.HasSupport(serviceAccount.GroupVersionKind()); err != nil {
return err return err

View File

@ -72,8 +72,6 @@ var (
// NewCmdEdit creates the `edit` command // NewCmdEdit creates the `edit` command
func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
o := editor.NewEditOptions(editor.NormalEditMode, ioStreams) o := editor.NewEditOptions(editor.NormalEditMode, ioStreams)
o.ValidateOptions = cmdutil.ValidateOptions{EnableValidation: true}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "edit (RESOURCE/NAME | -f FILENAME)", Use: "edit (RESOURCE/NAME | -f FILENAME)",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -94,7 +92,7 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra
usage := "to use to edit the resource" usage := "to use to edit the resource"
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) 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().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, cmd.Flags().BoolVar(&o.WindowsLineEndings, "windows-line-endings", o.WindowsLineEndings,
"Defaults to the line ending native to your platform.") "Defaults to the line ending native to your platform.")

View File

@ -79,11 +79,12 @@ type ReplaceOptions struct {
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
FieldValidationVerifier *resource.QueryParamVerifier
validationDirective string
PrintObj func(obj runtime.Object) error PrintObj func(obj runtime.Object) error
createAnnotation bool createAnnotation bool
validate bool
Schema validation.Schema Schema validation.Schema
Builder func() *resource.Builder Builder func() *resource.Builder
@ -151,7 +152,10 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return err 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.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd) o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
@ -163,6 +167,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return err return err
} }
o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun) o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun)
o.FieldValidationVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamFieldValidation)
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
printer, err := o.PrintFlags.ToPrinter() printer, err := o.PrintFlags.ToPrinter()
@ -196,7 +201,7 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
return err return err
} }
schema, err := f.Validator(o.validate) schema, err := f.Validator(o.validationDirective, o.FieldValidationVerifier)
if err != nil { if err != nil {
return err return err
} }
@ -306,6 +311,7 @@ func (o *ReplaceOptions) Run(f cmdutil.Factory) error {
NewHelper(info.Client, info.Mapping). NewHelper(info.Client, info.Mapping).
DryRun(o.DryRunStrategy == cmdutil.DryRunServer). DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
WithFieldManager(o.fieldManager). WithFieldManager(o.fieldManager).
WithFieldValidation(o.validationDirective).
WithSubresource(o.Subresource). WithSubresource(o.Subresource).
Replace(info.Namespace, info.Name, true, info.Object) Replace(info.Namespace, info.Name, true, info.Object)
if err != nil { if err != nil {
@ -409,6 +415,7 @@ func (o *ReplaceOptions) forceReplace() error {
obj, err := resource.NewHelper(info.Client, info.Mapping). obj, err := resource.NewHelper(info.Client, info.Mapping).
WithFieldManager(o.fieldManager). WithFieldManager(o.fieldManager).
WithFieldValidation(o.validationDirective).
Create(info.Namespace, true, info.Object) Create(info.Namespace, true, info.Object)
if err != nil { if err != nil {
return err return err

View File

@ -49,6 +49,7 @@ type TaintOptions struct {
DryRunStrategy cmdutil.DryRunStrategy DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.QueryParamVerifier DryRunVerifier *resource.QueryParamVerifier
ValidationDirective string
resources []string resources []string
taintsToAdd []v1.Taint 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) o.DryRunVerifier = resource.NewQueryParamVerifier(dynamicClient, f.OpenAPIGetter(), resource.QueryParamDryRun)
cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy) cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
o.ValidationDirective, err = cmdutil.GetValidationDirective(cmd)
if err != nil {
return err
}
// retrieves resource and taint args from args // retrieves resource and taint args from args
// also checks args to verify that all resources are specified before taints // also checks args to verify that all resources are specified before taints
taintArgs := []string{} taintArgs := []string{}
@ -337,8 +343,9 @@ func (o TaintOptions) RunTaint() error {
} }
helper := resource. helper := resource.
NewHelper(client, mapping). NewHelper(client, mapping).
DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
WithFieldManager(o.fieldManager). WithFieldManager(o.fieldManager).
DryRun(o.DryRunStrategy == cmdutil.DryRunServer) WithFieldValidation(o.ValidationDirective)
var outputObj runtime.Object var outputObj runtime.Object
if createdPatch { if createdPatch {

View File

@ -499,7 +499,7 @@ func (f *TestFactory) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r
} }
// Validator returns a validation schema // 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 return validation.NullSchema{}, nil
} }

View File

@ -68,6 +68,8 @@ type EditOptions struct {
WindowsLineEndings bool WindowsLineEndings bool
cmdutil.ValidateOptions cmdutil.ValidateOptions
ValidationDirective string
FieldValidationVerifier *resource.QueryParamVerifier
OriginalResult *resource.Result OriginalResult *resource.Result
@ -215,6 +217,17 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm
return o.PrintFlags.ToPrinter() 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.CmdNamespace = cmdNamespace
o.f = f o.f = f
@ -309,7 +322,7 @@ func (o *EditOptions) Run() error {
klog.V(4).Infof("User edited:\n%s", string(edited)) klog.V(4).Infof("User edited:\n%s", string(edited))
// Apply validation // Apply validation
schema, err := o.f.Validator(o.EnableValidation) schema, err := o.f.Validator(o.ValidationDirective, o.FieldValidationVerifier)
if err != nil { if err != nil {
return preservedFile(err, file, o.ErrOut) return preservedFile(err, file, o.ErrOut)
} }
@ -571,7 +584,10 @@ func (o *EditOptions) annotationPatch(update *resource.Info) error {
if err != nil { if err != nil {
return err 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) _, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch, nil)
if err != nil { if err != nil {
return err return err
@ -709,7 +725,9 @@ func (o *EditOptions) visitToPatch(originalInfos []*resource.Info, patchVisitor
} }
patched, err := resource.NewHelper(info.Client, info.Mapping). 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) Patch(info.Namespace, info.Name, patchType, patch, nil)
if err != nil { if err != nil {
fmt.Fprintln(o.ErrOut, results.addError(err, info)) 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 { err := createVisitor.Visit(func(info *resource.Info, incomingErr error) error {
obj, err := resource.NewHelper(info.Client, info.Mapping). obj, err := resource.NewHelper(info.Client, info.Mapping).
WithFieldManager(o.FieldManager). WithFieldManager(o.FieldManager).
WithFieldValidation(o.ValidationDirective).
Create(info.Namespace, true, info.Object) Create(info.Namespace, true, info.Object)
if err != nil { if err != nil {
return err return err

View File

@ -61,7 +61,7 @@ type Factory interface {
UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error) UnstructuredClientForMapping(mapping *meta.RESTMapping) (resource.RESTClient, error)
// Returns a schema that can validate objects stored on disk. // 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 returns the parsed openapi schema definition
OpenAPISchema() (openapi.Resources, error) OpenAPISchema() (openapi.Resources, error)
// OpenAPIGetter returns a getter for the openapi schema document // OpenAPIGetter returns a getter for the openapi schema document

View File

@ -24,6 +24,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "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/genericclioptions"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
@ -141,8 +142,14 @@ func (f *factoryImpl) UnstructuredClientForMapping(mapping *meta.RESTMapping) (r
return restclient.RESTClientFor(cfg) return restclient.RESTClientFor(cfg)
} }
func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) { func (f *factoryImpl) Validator(validationDirective string, verifier *resource.QueryParamVerifier) (validation.Schema, error) {
if !validate { // 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 return validation.NullSchema{}, nil
} }
@ -151,10 +158,11 @@ func (f *factoryImpl) Validator(validate bool) (validation.Schema, error) {
return nil, err return nil, err
} }
return validation.ConjunctiveSchema{ schema := validation.ConjunctiveSchema{
openapivalidation.NewSchemaValidation(resources), openapivalidation.NewSchemaValidation(resources),
validation.NoDoubleKeySchema{}, validation.NoDoubleKeySchema{},
}, nil }
return validation.NewParamVerifyingSchema(schema, verifier, string(validationDirective)), nil
} }
// OpenAPISchema returns metadata and structural information about // OpenAPISchema returns metadata and structural information about

View File

@ -399,11 +399,14 @@ func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
} }
func AddValidateFlags(cmd *cobra.Command) { func AddValidateFlags(cmd *cobra.Command) {
cmd.Flags().Bool("validate", true, "If true, use a schema to validate the input before sending it") cmd.Flags().String(
} "validate",
"strict",
func AddValidateOptionFlags(cmd *cobra.Command, options *ValidateOptions) { `Must be one of: strict (or true), warn, ignore (or false).
cmd.Flags().BoolVar(&options.EnableValidation, "validate", options.EnableValidation, "If true, use a schema to validate the input before sending it") "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) { 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 { 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, // 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") 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 type DryRunStrategy int
const ( const (

View File

@ -17,6 +17,7 @@ limitations under the License.
package util package util
import ( import (
goerrors "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -27,6 +28,7 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality" 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)
}
}
}
}

View File

@ -43,12 +43,12 @@ func NewSchemaValidation(resources openapi.Resources) *SchemaValidation {
// ValidateBytes will validates the object against using the Resources // ValidateBytes will validates the object against using the Resources
// object. // object.
func (v *SchemaValidation) ValidateBytes(data []byte) error { func (v *SchemaValidation) ValidateBytes(data []byte) error {
obj, err := parse(data) obj, err := Parse(data)
if err != nil { if err != nil {
return err return err
} }
gvk, errs := getObjectKind(obj) gvk, errs := GetObjectKind(obj)
if errs != nil { if errs != nil {
return utilerrors.NewAggregate(errs) return utilerrors.NewAggregate(errs)
} }
@ -71,7 +71,7 @@ func (v *SchemaValidation) validateList(object interface{}) []error {
return []error{errors.New("invalid object to validate")} return []error{errors.New("invalid object to validate")}
} }
for _, item := range fields["items"].([]interface{}) { for _, item := range fields["items"].([]interface{}) {
if gvk, errs := getObjectKind(item); errs != nil { if gvk, errs := GetObjectKind(item); errs != nil {
allErrors = append(allErrors, errs...) allErrors = append(allErrors, errs...)
} else { } else {
allErrors = append(allErrors, v.validateResource(item, gvk)...) 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) return validation.ValidateModel(obj, resource, gvk.Kind)
} }
func parse(data []byte) (interface{}, error) { func Parse(data []byte) (interface{}, error) {
var obj interface{} var obj interface{}
out, err := yaml.ToJSON(data) out, err := yaml.ToJSON(data)
if err != nil { if err != nil {
@ -102,7 +102,7 @@ func parse(data []byte) (interface{}, error) {
return obj, nil return obj, nil
} }
func getObjectKind(object interface{}) (schema.GroupVersionKind, []error) { func GetObjectKind(object interface{}) (schema.GroupVersionKind, []error) {
var listErrors []error var listErrors []error
fields, ok := object.(map[string]interface{}) fields, ok := object.(map[string]interface{})
if !ok || fields == nil { if !ok || fields == nil {

View File

@ -22,7 +22,11 @@ import (
"fmt" "fmt"
ejson "github.com/exponent-io/jsonpath" ejson "github.com/exponent-io/jsonpath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors" 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. // 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) return utilerrors.NewAggregate(list)
} }
func NewParamVerifyingSchema(s Schema, verifier resource.Verifier, directive string) Schema {
return &paramVerifyingSchema{
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
}

View File

@ -19,6 +19,9 @@ package validation
import ( import (
"fmt" "fmt"
"testing" "testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
) )
func TestValidateDuplicateLabelsFailCases(t *testing.T) { 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)
}
})
}
}

View File

@ -153,3 +153,62 @@ run_kubectl_create_kustomization_directory_tests() {
set +o nounset set +o nounset
set +o errexit 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
}

View File

@ -573,6 +573,7 @@ runTests() {
fi fi
if kube::test::if_supports_resource "${deployments}"; then if kube::test::if_supports_resource "${deployments}"; then
record_command run_kubectl_create_kustomization_directory_tests record_command run_kubectl_create_kustomization_directory_tests
record_command run_kubectl_create_validate_tests
fi fi
###################### ######################