Skip to content

Commit

Permalink
feat(crd): add v1 apis to apply CRDs (#178)
Browse files Browse the repository at this point in the history
Signed-off-by: AmitKumarDas <amit.das@mayadata.io>
  • Loading branch information
Amit Kumar Das authored Nov 9, 2020
1 parent 3fb4949 commit c5fee84
Show file tree
Hide file tree
Showing 3 changed files with 335 additions and 1 deletion.
207 changes: 207 additions & 0 deletions pkg/recipe/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package recipe

import (
"fmt"
"strings"

"github.com/pkg/errors"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -52,6 +54,54 @@ func NewApplier(config ApplyableConfig) *Applyable {
}
}

func (a *Applyable) postCreateCRDV1(crd *v1.CustomResourceDefinition) error {
if len(crd.Spec.Versions) == 0 {
return errors.Errorf(
"Invalid CRD spec: Missing spec.versions",
)
}
var versionToVerify = crd.Spec.Versions[0].Name
message := fmt.Sprintf(
"PostCreate CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
crd.Spec.Group+"/"+versionToVerify,
)
// Is custom resource definition discovered &
// can its resource(s) be listed
err := a.Retry.Waitf(
func() (bool, error) {
got := a.GetAPIForAPIVersionAndResource(
crd.Spec.Group+"/"+versionToVerify,
crd.Spec.Names.Plural,
)
if got == nil {
return a.IsFailFastOnDiscoveryError(),
errors.Errorf(
"Failed to discover: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
crd.Spec.Group+"/"+versionToVerify,
)
}
// fetch dynamic client for the custom resource
// corresponding to this CRD
customResourceClient, err := a.GetClientForAPIVersionAndResource(
crd.Spec.Group+"/"+versionToVerify,
crd.Spec.Names.Plural,
)
if err != nil {
return a.IsFailFastOnDiscoveryError(), err
}
_, err = customResourceClient.List(metav1.ListOptions{})
if err != nil {
return false, err
}
return true, nil
},
message,
)
return err
}

func (a *Applyable) postCreateCRD(
crd *v1beta1.CustomResourceDefinition,
) error {
Expand Down Expand Up @@ -95,6 +145,54 @@ func (a *Applyable) postCreateCRD(
return err
}

func (a *Applyable) createCRDV1() (*types.ApplyResult, error) {
var crd *v1.CustomResourceDefinition
err := UnstructToTyped(a.Apply.State, &crd)
if err != nil {
return nil, err
}
// use crd client to create crd
crd, err = a.crdClientV1.
CustomResourceDefinitions().
Create(crd)
if err != nil {
return nil, err
}
// add to teardown functions
a.AddToTeardown(func() error {
_, err := a.crdClientV1.
CustomResourceDefinitions().
Get(
crd.GetName(),
metav1.GetOptions{},
)
if err != nil && apierrors.IsNotFound(err) {
// nothing to do
return nil
}
return a.crdClientV1.
CustomResourceDefinitions().
Delete(
crd.Name,
nil,
)
})
// run an additional step to wait till this CRD
// is discovered at apiserver
err = a.postCreateCRDV1(crd)
if err != nil {
return nil, err
}
return &types.ApplyResult{
Phase: types.ApplyStatusPassed,
Message: fmt.Sprintf(
"Create CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
a.Apply.State.GetAPIVersion(),
),
}, nil
}

func (a *Applyable) createCRD() (*types.ApplyResult, error) {
var crd *v1beta1.CustomResourceDefinition
err := UnstructToTyped(a.Apply.State, &crd)
Expand Down Expand Up @@ -143,6 +241,66 @@ func (a *Applyable) createCRD() (*types.ApplyResult, error) {
}, nil
}

func (a *Applyable) updateCRDV1() (*types.ApplyResult, error) {
var crd *v1.CustomResourceDefinition
// transform to typed CRD to make use of crd client
err := UnstructToTyped(a.Apply.State, &crd)
if err != nil {
return nil, err
}
// get the CRD observed at the cluster
target, err := a.crdClientV1.
CustomResourceDefinitions().
Get(
a.Apply.State.GetName(),
metav1.GetOptions{},
)
if err != nil {
return nil, err
}
// tansform back to unstruct type to run 3-way merge
targetAsUnstruct, err := TypedToUnstruct(target)
if err != nil {
return nil, err
}
merged := &unstructured.Unstructured{}
// 3-way merge
merged.Object, err = dynamicapply.Merge(
targetAsUnstruct.UnstructuredContent(), // observed
a.Apply.State.UnstructuredContent(), // last applied
a.Apply.State.UnstructuredContent(), // desired
)
if err != nil {
return nil, err
}
// transform again to typed CRD to execute update
err = UnstructToTyped(merged, crd)
if err != nil {
return nil, err
}
// update the final merged state of CRD
//
// NOTE:
// At this point we are performing a server side
// apply against the CRD
_, err = a.crdClientV1.
CustomResourceDefinitions().
Update(
crd,
)
if err != nil {
return nil, err
}
return &types.ApplyResult{
Phase: types.ApplyStatusPassed,
Message: fmt.Sprintf(
"Update CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
a.Apply.State.GetAPIVersion(),
),
}, nil
}

func (a *Applyable) updateCRD() (*types.ApplyResult, error) {
var crd *v1beta1.CustomResourceDefinition
// transform to typed CRD to make use of crd client
Expand Down Expand Up @@ -203,12 +361,61 @@ func (a *Applyable) updateCRD() (*types.ApplyResult, error) {
}, nil
}

func (a *Applyable) applyCRDV1() (*types.ApplyResult, error) {
var crd *v1.CustomResourceDefinition
err := UnstructToTyped(a.Apply.State, &crd)
if err != nil {
return nil, err
}
message := fmt.Sprintf(
"Apply CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
a.Apply.State.GetAPIVersion(),
)
// use crd client to get crd
err = a.Retry.Waitf(
func() (bool, error) {
_, err = a.crdClientV1.
CustomResourceDefinitions().
Get(
crd.GetName(),
metav1.GetOptions{},
)
if err != nil {
if apierrors.IsNotFound(err) {
// condition exits since this is valid
return true, err
}
return false, err
}
return true, nil
},
message,
)
if err != nil {
if apierrors.IsNotFound(err) {
// this is a **create** operation
return a.createCRDV1()
}
return nil, err
}
// this is an **update** operation
return a.updateCRDV1()
}

func (a *Applyable) applyCRD() (*types.ApplyResult, error) {
ver := a.Apply.State.GetAPIVersion()
if strings.HasSuffix(ver, "/v1") {
return a.applyCRDV1()
}

var crd *v1beta1.CustomResourceDefinition
err := UnstructToTyped(a.Apply.State, &crd)
if err != nil {
return nil, err
}

// following code belongs to v1beta1 version
message := fmt.Sprintf(
"Apply CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
Expand Down
108 changes: 108 additions & 0 deletions pkg/recipe/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package recipe

import (
"fmt"
"strings"

"github.com/pkg/errors"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -65,6 +67,55 @@ func NewCreator(config CreatableConfig) *Creatable {
}
}

func (c *Creatable) postCreateCRDV1(
crd *v1.CustomResourceDefinition,
) error {
if len(crd.Spec.Versions) == 0 {
return errors.Errorf(
"Invalid CRD spec: Missing spec.versions",
)
}
var versionToVerify = crd.Spec.Versions[0].Name
message := fmt.Sprintf(
"PostCreate CRD: Kind %s: APIVersion %s: TaskName %s",
crd.Spec.Names.Singular,
crd.Spec.Group+"/"+versionToVerify,
c.TaskName,
)
// discover custom resource API
return c.Retry.Waitf(
func() (bool, error) {
api := c.GetAPIForAPIVersionAndResource(
crd.Spec.Group+"/"+versionToVerify,
crd.Spec.Names.Plural,
)
if api == nil {
return c.IsFailFastOnDiscoveryError(),
errors.Errorf(
"Failed to discover: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
crd.Spec.Group+"/"+versionToVerify,
)
}
// fetch dynamic client for the custom resource
// corresponding to this CRD
customResourceClient, err := c.GetClientForAPIVersionAndResource(
crd.Spec.Group+"/"+versionToVerify,
crd.Spec.Names.Plural,
)
if err != nil {
return c.IsFailFastOnDiscoveryError(), err
}
_, err = customResourceClient.List(metav1.ListOptions{})
if err != nil {
return false, err
}
return true, nil
},
message,
)
}

func (c *Creatable) postCreateCRD(
crd *v1beta1.CustomResourceDefinition,
) error {
Expand Down Expand Up @@ -108,7 +159,64 @@ func (c *Creatable) postCreateCRD(
)
}

func (c *Creatable) createCRDV1() (*types.CreateResult, error) {
var crd *v1.CustomResourceDefinition
err := UnstructToTyped(c.Create.State, &crd)
if err != nil {
return nil, err
}
// use crd client to create crd
crd, err = c.crdClientV1.
CustomResourceDefinitions().
Create(crd)
if err != nil {
return nil, errors.Wrapf(
err,
"%s",
c,
)
}
// add to teardown functions
c.AddToTeardown(func() error {
_, err := c.crdClientV1.
CustomResourceDefinitions().
Get(
crd.GetName(),
metav1.GetOptions{},
)
if err != nil && apierrors.IsNotFound(err) {
// nothing to do
return nil
}
return c.crdClientV1.
CustomResourceDefinitions().
Delete(
crd.Name,
nil,
)
})
// run an additional step to wait till this CRD
// is discovered at apiserver
err = c.postCreateCRDV1(crd)
if err != nil {
return nil, err
}
return &types.CreateResult{
Phase: types.CreateStatusPassed,
Message: fmt.Sprintf(
"Create CRD: Kind %s: APIVersion %s",
crd.Spec.Names.Singular,
c.Create.State.GetAPIVersion(),
),
}, nil
}

func (c *Creatable) createCRD() (*types.CreateResult, error) {
ver := c.Create.State.GetAPIVersion()
if strings.HasSuffix(ver, "/v1") {
return c.createCRDV1()
}

var crd *v1beta1.CustomResourceDefinition
err := UnstructToTyped(c.Create.State, &crd)
if err != nil {
Expand Down
Loading

0 comments on commit c5fee84

Please sign in to comment.