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) {
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

View File

@ -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 (<resource>/[ns/<namespace>/]<name>/<subresource>)
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)

View File

@ -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 &paramUnsupportedError{
gvk: gvk,
param: param,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &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 (
"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)
}
})
}
}

View File

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

View File

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