@@ -18,8 +18,10 @@ package recipe
18
18
19
19
import (
20
20
"fmt"
21
+ "strings"
21
22
22
23
"github.com/pkg/errors"
24
+ v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
23
25
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
24
26
apierrors "k8s.io/apimachinery/pkg/api/errors"
25
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,6 +54,54 @@ func NewApplier(config ApplyableConfig) *Applyable {
52
54
}
53
55
}
54
56
57
+ func (a * Applyable ) postCreateCRDV1 (crd * v1.CustomResourceDefinition ) error {
58
+ if len (crd .Spec .Versions ) == 0 {
59
+ return errors .Errorf (
60
+ "Invalid CRD spec: Missing spec.versions" ,
61
+ )
62
+ }
63
+ var versionToVerify = crd .Spec .Versions [0 ].Name
64
+ message := fmt .Sprintf (
65
+ "PostCreate CRD: Kind %s: APIVersion %s" ,
66
+ crd .Spec .Names .Singular ,
67
+ crd .Spec .Group + "/" + versionToVerify ,
68
+ )
69
+ // Is custom resource definition discovered &
70
+ // can its resource(s) be listed
71
+ err := a .Retry .Waitf (
72
+ func () (bool , error ) {
73
+ got := a .GetAPIForAPIVersionAndResource (
74
+ crd .Spec .Group + "/" + versionToVerify ,
75
+ crd .Spec .Names .Plural ,
76
+ )
77
+ if got == nil {
78
+ return a .IsFailFastOnDiscoveryError (),
79
+ errors .Errorf (
80
+ "Failed to discover: Kind %s: APIVersion %s" ,
81
+ crd .Spec .Names .Singular ,
82
+ crd .Spec .Group + "/" + versionToVerify ,
83
+ )
84
+ }
85
+ // fetch dynamic client for the custom resource
86
+ // corresponding to this CRD
87
+ customResourceClient , err := a .GetClientForAPIVersionAndResource (
88
+ crd .Spec .Group + "/" + versionToVerify ,
89
+ crd .Spec .Names .Plural ,
90
+ )
91
+ if err != nil {
92
+ return a .IsFailFastOnDiscoveryError (), err
93
+ }
94
+ _ , err = customResourceClient .List (metav1.ListOptions {})
95
+ if err != nil {
96
+ return false , err
97
+ }
98
+ return true , nil
99
+ },
100
+ message ,
101
+ )
102
+ return err
103
+ }
104
+
55
105
func (a * Applyable ) postCreateCRD (
56
106
crd * v1beta1.CustomResourceDefinition ,
57
107
) error {
@@ -95,6 +145,54 @@ func (a *Applyable) postCreateCRD(
95
145
return err
96
146
}
97
147
148
+ func (a * Applyable ) createCRDV1 () (* types.ApplyResult , error ) {
149
+ var crd * v1.CustomResourceDefinition
150
+ err := UnstructToTyped (a .Apply .State , & crd )
151
+ if err != nil {
152
+ return nil , err
153
+ }
154
+ // use crd client to create crd
155
+ crd , err = a .crdClientV1 .
156
+ CustomResourceDefinitions ().
157
+ Create (crd )
158
+ if err != nil {
159
+ return nil , err
160
+ }
161
+ // add to teardown functions
162
+ a .AddToTeardown (func () error {
163
+ _ , err := a .crdClientV1 .
164
+ CustomResourceDefinitions ().
165
+ Get (
166
+ crd .GetName (),
167
+ metav1.GetOptions {},
168
+ )
169
+ if err != nil && apierrors .IsNotFound (err ) {
170
+ // nothing to do
171
+ return nil
172
+ }
173
+ return a .crdClientV1 .
174
+ CustomResourceDefinitions ().
175
+ Delete (
176
+ crd .Name ,
177
+ nil ,
178
+ )
179
+ })
180
+ // run an additional step to wait till this CRD
181
+ // is discovered at apiserver
182
+ err = a .postCreateCRDV1 (crd )
183
+ if err != nil {
184
+ return nil , err
185
+ }
186
+ return & types.ApplyResult {
187
+ Phase : types .ApplyStatusPassed ,
188
+ Message : fmt .Sprintf (
189
+ "Create CRD: Kind %s: APIVersion %s" ,
190
+ crd .Spec .Names .Singular ,
191
+ a .Apply .State .GetAPIVersion (),
192
+ ),
193
+ }, nil
194
+ }
195
+
98
196
func (a * Applyable ) createCRD () (* types.ApplyResult , error ) {
99
197
var crd * v1beta1.CustomResourceDefinition
100
198
err := UnstructToTyped (a .Apply .State , & crd )
@@ -143,6 +241,66 @@ func (a *Applyable) createCRD() (*types.ApplyResult, error) {
143
241
}, nil
144
242
}
145
243
244
+ func (a * Applyable ) updateCRDV1 () (* types.ApplyResult , error ) {
245
+ var crd * v1.CustomResourceDefinition
246
+ // transform to typed CRD to make use of crd client
247
+ err := UnstructToTyped (a .Apply .State , & crd )
248
+ if err != nil {
249
+ return nil , err
250
+ }
251
+ // get the CRD observed at the cluster
252
+ target , err := a .crdClientV1 .
253
+ CustomResourceDefinitions ().
254
+ Get (
255
+ a .Apply .State .GetName (),
256
+ metav1.GetOptions {},
257
+ )
258
+ if err != nil {
259
+ return nil , err
260
+ }
261
+ // tansform back to unstruct type to run 3-way merge
262
+ targetAsUnstruct , err := TypedToUnstruct (target )
263
+ if err != nil {
264
+ return nil , err
265
+ }
266
+ merged := & unstructured.Unstructured {}
267
+ // 3-way merge
268
+ merged .Object , err = dynamicapply .Merge (
269
+ targetAsUnstruct .UnstructuredContent (), // observed
270
+ a .Apply .State .UnstructuredContent (), // last applied
271
+ a .Apply .State .UnstructuredContent (), // desired
272
+ )
273
+ if err != nil {
274
+ return nil , err
275
+ }
276
+ // transform again to typed CRD to execute update
277
+ err = UnstructToTyped (merged , crd )
278
+ if err != nil {
279
+ return nil , err
280
+ }
281
+ // update the final merged state of CRD
282
+ //
283
+ // NOTE:
284
+ // At this point we are performing a server side
285
+ // apply against the CRD
286
+ _ , err = a .crdClientV1 .
287
+ CustomResourceDefinitions ().
288
+ Update (
289
+ crd ,
290
+ )
291
+ if err != nil {
292
+ return nil , err
293
+ }
294
+ return & types.ApplyResult {
295
+ Phase : types .ApplyStatusPassed ,
296
+ Message : fmt .Sprintf (
297
+ "Update CRD: Kind %s: APIVersion %s" ,
298
+ crd .Spec .Names .Singular ,
299
+ a .Apply .State .GetAPIVersion (),
300
+ ),
301
+ }, nil
302
+ }
303
+
146
304
func (a * Applyable ) updateCRD () (* types.ApplyResult , error ) {
147
305
var crd * v1beta1.CustomResourceDefinition
148
306
// transform to typed CRD to make use of crd client
@@ -203,12 +361,61 @@ func (a *Applyable) updateCRD() (*types.ApplyResult, error) {
203
361
}, nil
204
362
}
205
363
364
+ func (a * Applyable ) applyCRDV1 () (* types.ApplyResult , error ) {
365
+ var crd * v1.CustomResourceDefinition
366
+ err := UnstructToTyped (a .Apply .State , & crd )
367
+ if err != nil {
368
+ return nil , err
369
+ }
370
+ message := fmt .Sprintf (
371
+ "Apply CRD: Kind %s: APIVersion %s" ,
372
+ crd .Spec .Names .Singular ,
373
+ a .Apply .State .GetAPIVersion (),
374
+ )
375
+ // use crd client to get crd
376
+ err = a .Retry .Waitf (
377
+ func () (bool , error ) {
378
+ _ , err = a .crdClientV1 .
379
+ CustomResourceDefinitions ().
380
+ Get (
381
+ crd .GetName (),
382
+ metav1.GetOptions {},
383
+ )
384
+ if err != nil {
385
+ if apierrors .IsNotFound (err ) {
386
+ // condition exits since this is valid
387
+ return true , err
388
+ }
389
+ return false , err
390
+ }
391
+ return true , nil
392
+ },
393
+ message ,
394
+ )
395
+ if err != nil {
396
+ if apierrors .IsNotFound (err ) {
397
+ // this is a **create** operation
398
+ return a .createCRDV1 ()
399
+ }
400
+ return nil , err
401
+ }
402
+ // this is an **update** operation
403
+ return a .updateCRDV1 ()
404
+ }
405
+
206
406
func (a * Applyable ) applyCRD () (* types.ApplyResult , error ) {
407
+ ver := a .Apply .State .GetAPIVersion ()
408
+ if strings .HasSuffix (ver , "/v1" ) {
409
+ return a .applyCRDV1 ()
410
+ }
411
+
207
412
var crd * v1beta1.CustomResourceDefinition
208
413
err := UnstructToTyped (a .Apply .State , & crd )
209
414
if err != nil {
210
415
return nil , err
211
416
}
417
+
418
+ // following code belongs to v1beta1 version
212
419
message := fmt .Sprintf (
213
420
"Apply CRD: Kind %s: APIVersion %s" ,
214
421
crd .Spec .Names .Singular ,
0 commit comments