1
2
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