Skip to content

Commit

Permalink
Update KubeOps to support core group & wait for delete completion (#1199
Browse files Browse the repository at this point in the history
)

* Update KubeOps function to allow create & delete for core group resources

* Add test case for KubeOps Delete core group resource

* Remove core group permissions from clusterrole

* Update KubeOps Delete operation to wait for resource to be deleted

* Update testcase for kubeops delete on core group resource

* Update KubeOps test to use existing svc spec

* Update poll.Wait logic for waiting on delete operation to complete

* Fix gofmt issues

* Update wait.Poll to poll.Wait method & pass context from parent

* Handle error from Delete() and add comment for using poll.Wait()

* Add waitForResourceDeletion function

* Remove redundant check for error

* Revert createPhase method in kubeops_test.go

* Update KubeOps test

* Add comment to explain the waitForResourceDeletion function

* Update comment for waitForResourceDeletion

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Shlok Chaudhari and mergify[bot] committed Jan 28, 2022
1 parent 4da55be commit 5068148
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 12 deletions.
7 changes: 3 additions & 4 deletions pkg/function/kubeops.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (crs *kubeops) Exec(ctx context.Context, tp param.TemplateParams, args map[
if err != nil {
return nil, err
}
objRef, err := execKubeOperation(dynCli, op, namespace, spec, objRefArg)
objRef, err := execKubeOperation(ctx, dynCli, op, namespace, spec, objRefArg)
if err != nil {
return nil, err
}
Expand All @@ -95,7 +95,7 @@ func (crs *kubeops) Exec(ctx context.Context, tp param.TemplateParams, args map[
return out, nil
}

func execKubeOperation(dynCli dynamic.Interface, op kube.Operation, namespace, spec string, objRef crv1alpha1.ObjectReference) (*crv1alpha1.ObjectReference, error) {
func execKubeOperation(ctx context.Context, dynCli dynamic.Interface, op kube.Operation, namespace, spec string, objRef crv1alpha1.ObjectReference) (*crv1alpha1.ObjectReference, error) {
kubeopsOp := kube.NewKubectlOperations(dynCli)
switch op {
case kube.CreateOperation:
Expand All @@ -105,12 +105,11 @@ func execKubeOperation(dynCli dynamic.Interface, op kube.Operation, namespace, s
return kubeopsOp.Create(strings.NewReader(spec), namespace)
case kube.DeleteOperation:
if objRef.Name == "" ||
objRef.Group == "" ||
objRef.APIVersion == "" ||
objRef.Resource == "" {
return nil, errors.New(fmt.Sprintf("missing one or more required fields name/namespace/group/apiVersion/resource in objectReference for %s operation", kube.DeleteOperation))
}
return kubeopsOp.Delete(objRef, namespace)
return kubeopsOp.Delete(ctx, objRef, namespace)
}
return nil, errors.New(fmt.Sprintf("invalid operation '%s'", op))
}
Expand Down
47 changes: 44 additions & 3 deletions pkg/function/kubeops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,14 @@ func (s *KubeOpsSuite) TearDownSuite(c *C) {
_ = s.crdCli.ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), getSampleCRD().GetName(), metav1.DeleteOptions{})
}

func createPhase(namespace string) crv1alpha1.BlueprintPhase {
func createPhase(namespace string, spec string) crv1alpha1.BlueprintPhase {
return crv1alpha1.BlueprintPhase{
Name: "createDeploy",
Func: KubeOpsFuncName,
Args: map[string]interface{}{
KubeOpsOperationArg: "create",
KubeOpsNamespaceArg: namespace,
KubeOpsSpecArg: deploySpec,
KubeOpsSpecArg: spec,
},
}
}
Expand Down Expand Up @@ -239,6 +239,47 @@ func (s *KubeOpsSuite) TestKubeOps(c *C) {
c.Assert(out, DeepEquals, expOut)
}
}
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
serviceName := "test-deployment-2"
err := s.dynCli.Resource(gvr).Namespace(s.namespace).Delete(ctx, serviceName, metav1.DeleteOptions{})
c.Assert(err, IsNil)
}

func (s *KubeOpsSuite) TestKubeOpsCreateDeleteWithCoreResource(c *C) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
tp := param.TemplateParams{}
action := "test"
gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
serviceName := "test-deployment-2"
spec := fmt.Sprintf(serviceSpec, s.namespace)

bp := newCreateResourceBlueprint(createPhase(s.namespace, spec),
deletePhase(gvr, serviceName, s.namespace))
phases, err := kanister.GetPhases(bp, action, kanister.DefaultVersion, tp)
c.Assert(err, IsNil)
for _, p := range phases {
out, err := p.Exec(ctx, bp, action, tp)
c.Assert(err, IsNil, Commentf("Phase %s failed", p.Name()))

_, err = s.dynCli.Resource(gvr).Namespace(s.namespace).Get(ctx, serviceName, metav1.GetOptions{})
if p.Name() == "deleteDeploy" {
c.Assert(err, NotNil)
c.Assert(apierrors.IsNotFound(err), Equals, true)
} else {
c.Assert(err, IsNil)
}

expOut := map[string]interface{}{
"apiVersion": gvr.Version,
"group": gvr.Group,
"resource": gvr.Resource,
"kind": "",
"name": serviceName,
"namespace": s.namespace,
}
c.Assert(out, DeepEquals, expOut)
}
}

func (s *KubeOpsSuite) TestKubeOpsCreateWaitDelete(c *C) {
Expand All @@ -249,7 +290,7 @@ func (s *KubeOpsSuite) TestKubeOpsCreateWaitDelete(c *C) {
gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
deployName := "test-deployment"

bp := newCreateResourceBlueprint(createPhase(s.namespace),
bp := newCreateResourceBlueprint(createPhase(s.namespace, deploySpec),
waitDeployPhase(s.namespace, deployName),
deletePhase(gvr, deployName, s.namespace))
phases, err := kanister.GetPhases(bp, action, kanister.DefaultVersion, tp)
Expand Down
22 changes: 17 additions & 5 deletions pkg/kube/kubectl.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
cmdutil "k8s.io/kubectl/pkg/cmd/util"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/poll"
)

// Operation represents kubectl operation
Expand Down Expand Up @@ -102,18 +103,29 @@ func (k *KubectlOperation) Create(spec io.Reader, namespace string) (*crv1alpha1
return objRef, err
}

// Delete k8s resource referred by objectReference
func (k *KubectlOperation) Delete(objRef crv1alpha1.ObjectReference, namespace string) (*crv1alpha1.ObjectReference, error) {
// Delete k8s resource referred by objectReference. Waits for the resource to be deleted
func (k *KubectlOperation) Delete(ctx context.Context, objRef crv1alpha1.ObjectReference, namespace string) (*crv1alpha1.ObjectReference, error) {
if namespace == "" {
namespace = metav1.NamespaceDefault
}
if objRef.Namespace != "" {
namespace = objRef.Namespace
}
err := k.dynCli.Resource(schema.GroupVersionResource{Group: objRef.Group, Version: objRef.APIVersion, Resource: objRef.Resource}).Namespace(namespace).Delete(context.Background(), objRef.Name, metav1.DeleteOptions{})
if apierrors.IsNotFound(err) {
return &objRef, nil
err := k.dynCli.Resource(schema.GroupVersionResource{Group: objRef.Group, Version: objRef.APIVersion, Resource: objRef.Resource}).Namespace(namespace).Delete(ctx, objRef.Name, metav1.DeleteOptions{})
if err != nil {
return &objRef, err
}
return waitForResourceDeletion(ctx, k, objRef, namespace)
}

// waitForResourceDeletion repeatedly checks for NotFound error after fetching the resource
func waitForResourceDeletion(ctx context.Context, k *KubectlOperation, objRef crv1alpha1.ObjectReference, namespace string) (*crv1alpha1.ObjectReference, error) {
err := poll.Wait(ctx, func(context.Context) (done bool, err error) {
_, err = k.dynCli.Resource(schema.GroupVersionResource{Group: objRef.Group, Version: objRef.APIVersion, Resource: objRef.Resource}).Namespace(namespace).Get(ctx, objRef.Name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return true, nil
}
return false, err
})
return &objRef, err
}

0 comments on commit 5068148

Please sign in to comment.