Skip to content

Commit c5fee84

Browse files
author
Amit Kumar Das
authored
feat(crd): add v1 apis to apply CRDs (#178)
Signed-off-by: AmitKumarDas <[email protected]>
1 parent 3fb4949 commit c5fee84

File tree

3 files changed

+335
-1
lines changed

3 files changed

+335
-1
lines changed

pkg/recipe/apply.go

+207
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package recipe
1818

1919
import (
2020
"fmt"
21+
"strings"
2122

2223
"github.com/pkg/errors"
24+
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2325
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2426
apierrors "k8s.io/apimachinery/pkg/api/errors"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,6 +54,54 @@ func NewApplier(config ApplyableConfig) *Applyable {
5254
}
5355
}
5456

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+
55105
func (a *Applyable) postCreateCRD(
56106
crd *v1beta1.CustomResourceDefinition,
57107
) error {
@@ -95,6 +145,54 @@ func (a *Applyable) postCreateCRD(
95145
return err
96146
}
97147

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+
98196
func (a *Applyable) createCRD() (*types.ApplyResult, error) {
99197
var crd *v1beta1.CustomResourceDefinition
100198
err := UnstructToTyped(a.Apply.State, &crd)
@@ -143,6 +241,66 @@ func (a *Applyable) createCRD() (*types.ApplyResult, error) {
143241
}, nil
144242
}
145243

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+
146304
func (a *Applyable) updateCRD() (*types.ApplyResult, error) {
147305
var crd *v1beta1.CustomResourceDefinition
148306
// transform to typed CRD to make use of crd client
@@ -203,12 +361,61 @@ func (a *Applyable) updateCRD() (*types.ApplyResult, error) {
203361
}, nil
204362
}
205363

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+
206406
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+
207412
var crd *v1beta1.CustomResourceDefinition
208413
err := UnstructToTyped(a.Apply.State, &crd)
209414
if err != nil {
210415
return nil, err
211416
}
417+
418+
// following code belongs to v1beta1 version
212419
message := fmt.Sprintf(
213420
"Apply CRD: Kind %s: APIVersion %s",
214421
crd.Spec.Names.Singular,

pkg/recipe/create.go

+108
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package recipe
1818

1919
import (
2020
"fmt"
21+
"strings"
2122

2223
"github.com/pkg/errors"
24+
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2325
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2426
apierrors "k8s.io/apimachinery/pkg/api/errors"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -65,6 +67,55 @@ func NewCreator(config CreatableConfig) *Creatable {
6567
}
6668
}
6769

70+
func (c *Creatable) postCreateCRDV1(
71+
crd *v1.CustomResourceDefinition,
72+
) error {
73+
if len(crd.Spec.Versions) == 0 {
74+
return errors.Errorf(
75+
"Invalid CRD spec: Missing spec.versions",
76+
)
77+
}
78+
var versionToVerify = crd.Spec.Versions[0].Name
79+
message := fmt.Sprintf(
80+
"PostCreate CRD: Kind %s: APIVersion %s: TaskName %s",
81+
crd.Spec.Names.Singular,
82+
crd.Spec.Group+"/"+versionToVerify,
83+
c.TaskName,
84+
)
85+
// discover custom resource API
86+
return c.Retry.Waitf(
87+
func() (bool, error) {
88+
api := c.GetAPIForAPIVersionAndResource(
89+
crd.Spec.Group+"/"+versionToVerify,
90+
crd.Spec.Names.Plural,
91+
)
92+
if api == nil {
93+
return c.IsFailFastOnDiscoveryError(),
94+
errors.Errorf(
95+
"Failed to discover: Kind %s: APIVersion %s",
96+
crd.Spec.Names.Singular,
97+
crd.Spec.Group+"/"+versionToVerify,
98+
)
99+
}
100+
// fetch dynamic client for the custom resource
101+
// corresponding to this CRD
102+
customResourceClient, err := c.GetClientForAPIVersionAndResource(
103+
crd.Spec.Group+"/"+versionToVerify,
104+
crd.Spec.Names.Plural,
105+
)
106+
if err != nil {
107+
return c.IsFailFastOnDiscoveryError(), err
108+
}
109+
_, err = customResourceClient.List(metav1.ListOptions{})
110+
if err != nil {
111+
return false, err
112+
}
113+
return true, nil
114+
},
115+
message,
116+
)
117+
}
118+
68119
func (c *Creatable) postCreateCRD(
69120
crd *v1beta1.CustomResourceDefinition,
70121
) error {
@@ -108,7 +159,64 @@ func (c *Creatable) postCreateCRD(
108159
)
109160
}
110161

162+
func (c *Creatable) createCRDV1() (*types.CreateResult, error) {
163+
var crd *v1.CustomResourceDefinition
164+
err := UnstructToTyped(c.Create.State, &crd)
165+
if err != nil {
166+
return nil, err
167+
}
168+
// use crd client to create crd
169+
crd, err = c.crdClientV1.
170+
CustomResourceDefinitions().
171+
Create(crd)
172+
if err != nil {
173+
return nil, errors.Wrapf(
174+
err,
175+
"%s",
176+
c,
177+
)
178+
}
179+
// add to teardown functions
180+
c.AddToTeardown(func() error {
181+
_, err := c.crdClientV1.
182+
CustomResourceDefinitions().
183+
Get(
184+
crd.GetName(),
185+
metav1.GetOptions{},
186+
)
187+
if err != nil && apierrors.IsNotFound(err) {
188+
// nothing to do
189+
return nil
190+
}
191+
return c.crdClientV1.
192+
CustomResourceDefinitions().
193+
Delete(
194+
crd.Name,
195+
nil,
196+
)
197+
})
198+
// run an additional step to wait till this CRD
199+
// is discovered at apiserver
200+
err = c.postCreateCRDV1(crd)
201+
if err != nil {
202+
return nil, err
203+
}
204+
return &types.CreateResult{
205+
Phase: types.CreateStatusPassed,
206+
Message: fmt.Sprintf(
207+
"Create CRD: Kind %s: APIVersion %s",
208+
crd.Spec.Names.Singular,
209+
c.Create.State.GetAPIVersion(),
210+
),
211+
}, nil
212+
}
213+
111214
func (c *Creatable) createCRD() (*types.CreateResult, error) {
215+
ver := c.Create.State.GetAPIVersion()
216+
if strings.HasSuffix(ver, "/v1") {
217+
return c.createCRDV1()
218+
}
219+
112220
var crd *v1beta1.CustomResourceDefinition
113221
err := UnstructToTyped(c.Create.State, &crd)
114222
if err != nil {

0 commit comments

Comments
 (0)