...

Source file src/sigs.k8s.io/cli-utils/pkg/object/validation/validate_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/object/validation

     1  // Copyright 2022 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package validation_test
     5  
     6  import (
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  	"k8s.io/apimachinery/pkg/api/meta"
    12  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    13  	"k8s.io/apimachinery/pkg/runtime/schema"
    14  	"k8s.io/apimachinery/pkg/util/validation/field"
    15  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    16  	"sigs.k8s.io/cli-utils/pkg/multierror"
    17  	"sigs.k8s.io/cli-utils/pkg/object"
    18  	"sigs.k8s.io/cli-utils/pkg/object/validation"
    19  	"sigs.k8s.io/cli-utils/pkg/testutil"
    20  )
    21  
    22  func TestValidate(t *testing.T) {
    23  	testCases := map[string]struct {
    24  		resources     []*unstructured.Unstructured
    25  		expectedError error
    26  	}{
    27  		"missing kind": {
    28  			resources: []*unstructured.Unstructured{
    29  				{
    30  					Object: map[string]interface{}{
    31  						"apiVersion": "apps/v1",
    32  						"metadata": map[string]interface{}{
    33  							"name":      "foo",
    34  							"namespace": "default",
    35  						},
    36  					},
    37  				},
    38  			},
    39  			expectedError: validation.NewError(
    40  				&field.Error{
    41  					Type:     field.ErrorTypeRequired,
    42  					Field:    "kind",
    43  					BadValue: "",
    44  					Detail:   "kind is required",
    45  				},
    46  				object.ObjMetadata{
    47  					GroupKind: schema.GroupKind{
    48  						Group: "apps",
    49  						Kind:  "",
    50  					},
    51  					Name:      "foo",
    52  					Namespace: "default",
    53  				},
    54  			),
    55  		},
    56  		"multiple errors in one object": {
    57  			resources: []*unstructured.Unstructured{
    58  				{
    59  					Object: map[string]interface{}{
    60  						"apiVersion": "apps/v1",
    61  						"kind":       "Deployment",
    62  					},
    63  				},
    64  			},
    65  			expectedError: validation.NewError(
    66  				multierror.New(
    67  					&field.Error{
    68  						Type:     field.ErrorTypeRequired,
    69  						Field:    "metadata.name",
    70  						BadValue: "",
    71  						Detail:   "name is required",
    72  					},
    73  					&field.Error{
    74  						Type:     field.ErrorTypeRequired,
    75  						Field:    "metadata.namespace",
    76  						BadValue: "",
    77  						Detail:   "namespace is required",
    78  					},
    79  				),
    80  				object.ObjMetadata{
    81  					GroupKind: schema.GroupKind{
    82  						Group: "apps",
    83  						Kind:  "Deployment",
    84  					},
    85  					Name:      "",
    86  					Namespace: "",
    87  				},
    88  			),
    89  		},
    90  		"one error in multiple object": {
    91  			resources: []*unstructured.Unstructured{
    92  				{
    93  					Object: map[string]interface{}{
    94  						"apiVersion": "apps/v1",
    95  						"kind":       "Deployment",
    96  						"metadata": map[string]interface{}{
    97  							"namespace": "default",
    98  						},
    99  					},
   100  				},
   101  				{
   102  					Object: map[string]interface{}{
   103  						"apiVersion": "apps/v1",
   104  						"kind":       "StatefulSet",
   105  						"metadata": map[string]interface{}{
   106  							"namespace": "default",
   107  						},
   108  					},
   109  				},
   110  			},
   111  			expectedError: multierror.New(
   112  				validation.NewError(
   113  					&field.Error{
   114  						Type:     field.ErrorTypeRequired,
   115  						Field:    "metadata.name",
   116  						BadValue: "",
   117  						Detail:   "name is required",
   118  					},
   119  					object.ObjMetadata{
   120  						GroupKind: schema.GroupKind{
   121  							Group: "apps",
   122  							Kind:  "Deployment",
   123  						},
   124  						Name:      "",
   125  						Namespace: "default",
   126  					},
   127  				),
   128  				validation.NewError(
   129  					&field.Error{
   130  						Type:     field.ErrorTypeRequired,
   131  						Field:    "metadata.name",
   132  						BadValue: "",
   133  						Detail:   "name is required",
   134  					},
   135  					object.ObjMetadata{
   136  						GroupKind: schema.GroupKind{
   137  							Group: "apps",
   138  							Kind:  "StatefulSet",
   139  						},
   140  						Name:      "",
   141  						Namespace: "default",
   142  					},
   143  				),
   144  			),
   145  		},
   146  		"namespace must be empty (cluster-scoped)": {
   147  			resources: []*unstructured.Unstructured{
   148  				{
   149  					Object: map[string]interface{}{
   150  						"apiVersion": "v1",
   151  						"kind":       "Namespace",
   152  						"metadata": map[string]interface{}{
   153  							"name":      "foo",
   154  							"namespace": "default",
   155  						},
   156  					},
   157  				},
   158  			},
   159  			expectedError: validation.NewError(
   160  				&field.Error{
   161  					Type:     field.ErrorTypeInvalid,
   162  					Field:    "metadata.namespace",
   163  					BadValue: "default",
   164  					Detail:   "namespace must be empty",
   165  				},
   166  				object.ObjMetadata{
   167  					GroupKind: schema.GroupKind{
   168  						Group: "",
   169  						Kind:  "Namespace",
   170  					},
   171  					Name:      "foo",
   172  					Namespace: "default",
   173  				},
   174  			),
   175  		},
   176  		"namespace is required (namespace-scoped)": {
   177  			resources: []*unstructured.Unstructured{
   178  				{
   179  					Object: map[string]interface{}{
   180  						"apiVersion": "apps/v1",
   181  						"kind":       "Deployment",
   182  						"metadata": map[string]interface{}{
   183  							"name": "foo",
   184  						},
   185  					},
   186  				},
   187  			},
   188  			expectedError: validation.NewError(
   189  				&field.Error{
   190  					Type:     field.ErrorTypeRequired,
   191  					Field:    "metadata.namespace",
   192  					BadValue: "",
   193  					Detail:   "namespace is required",
   194  				},
   195  				object.ObjMetadata{
   196  					GroupKind: schema.GroupKind{
   197  						Group: "apps",
   198  						Kind:  "Deployment",
   199  					},
   200  					Name:      "foo",
   201  					Namespace: "",
   202  				},
   203  			),
   204  		},
   205  		"scope for CRs are found in CRDs if available": {
   206  			resources: []*unstructured.Unstructured{
   207  				testutil.Unstructured(t, `
   208  apiVersion: apiextensions.k8s.io/v1
   209  kind: CustomResourceDefinition
   210  metadata:
   211    name: customs.custom.io
   212  spec:
   213    group: custom.io
   214    names:
   215      kind: Custom
   216    scope: Cluster
   217    versions:
   218    - name: v1
   219  `,
   220  				),
   221  				testutil.Unstructured(t, `
   222  apiVersion: custom.io/v1
   223  kind: Custom
   224  metadata:
   225    name: foo
   226    namespace: default
   227  `,
   228  				),
   229  			},
   230  			expectedError: validation.NewError(
   231  				&field.Error{
   232  					Type:     field.ErrorTypeInvalid,
   233  					Field:    "metadata.namespace",
   234  					BadValue: "default",
   235  					Detail:   "namespace must be empty",
   236  				},
   237  				object.ObjMetadata{
   238  					GroupKind: schema.GroupKind{
   239  						Group: "custom.io",
   240  						Kind:  "Custom",
   241  					},
   242  					Name:      "foo",
   243  					Namespace: "default",
   244  				},
   245  			),
   246  		},
   247  	}
   248  
   249  	for tn, tc := range testCases {
   250  		t.Run(tn, func(t *testing.T) {
   251  			tf := cmdtesting.NewTestFactory().WithNamespace("test-ns")
   252  			defer tf.Cleanup()
   253  
   254  			mapper, err := tf.ToRESTMapper()
   255  			require.NoError(t, err)
   256  			crdGV := schema.GroupVersion{Group: "apiextensions.k8s.io", Version: "v1"}
   257  			crdMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{crdGV})
   258  			crdMapper.AddSpecific(crdGV.WithKind("CustomResourceDefinition"),
   259  				crdGV.WithResource("customresourcedefinitions"),
   260  				crdGV.WithResource("customresourcedefinition"), meta.RESTScopeRoot)
   261  			mapper = meta.MultiRESTMapper([]meta.RESTMapper{mapper, crdMapper})
   262  
   263  			vCollector := &validation.Collector{}
   264  			validator := &validation.Validator{
   265  				Mapper:    mapper,
   266  				Collector: vCollector,
   267  			}
   268  			validator.Validate(tc.resources)
   269  			err = vCollector.ToError()
   270  			if tc.expectedError == nil {
   271  				assert.NoError(t, err)
   272  				return
   273  			}
   274  			require.EqualError(t, err, tc.expectedError.Error())
   275  		})
   276  	}
   277  }
   278  

View as plain text