diff --git a/docs/design/klient-package.md b/docs/design/klient-package.md index 7e096ce0..7cbfc7a3 100644 --- a/docs/design/klient-package.md +++ b/docs/design/klient-package.md @@ -536,15 +536,16 @@ func main() { Name: podName, } } - pod2 := &v1.Pod{ - Spec: v1.PodSpec{ - Containers: []v1.Container{{ - Name: "nginx", - Image: imageutils.GetPauseImageName(), - }}, - }, - } - if err := res.Patch(context.TODO(), &pod1, &pod2); err != nil { + mergePatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "foo": "bar", + }, + }, + }) + + patch := k8s.Patch{PatchType: types.StrategicMergePatchType, Data: mergePatch} + if err := res.Patch(context.TODO(), &pod1, patch); err != nil { log.Fatal("unable to update pod: ", err) } } diff --git a/klient/k8s/object.go b/klient/k8s/object.go index 97342512..aa0df806 100644 --- a/klient/k8s/object.go +++ b/klient/k8s/object.go @@ -19,6 +19,7 @@ package k8s import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" ) // Object is a union type that can represent either typed objects @@ -35,3 +36,11 @@ type ObjectList interface { metav1.ListInterface runtime.Object } + +// Patch is a patch that can be applied to a Kubernetes object. +type Patch struct { + // PatchType is the type of the patch. + PatchType types.PatchType + // Data is the raw data representing the patch. + Data []byte +} diff --git a/klient/k8s/resources/resources.go b/klient/k8s/resources/resources.go index 7efacb2a..8adc94b5 100644 --- a/klient/k8s/resources/resources.go +++ b/klient/k8s/resources/resources.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "time" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -28,6 +29,7 @@ import ( "k8s.io/client-go/kubernetes/scheme" cr "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/e2e-framework/klient/k8s" + "sigs.k8s.io/e2e-framework/utils" ) type Resources struct { @@ -109,6 +111,16 @@ func (r *Resources) Delete(ctx context.Context, obj k8s.Object, opts ...DeleteOp return r.client.Delete(ctx, obj, o) } +func WithGracePeriod(gpt time.Duration) DeleteOption { + t := gpt.Milliseconds() + return func(do *metav1.DeleteOptions) { do.GracePeriodSeconds = &t } +} + +func WithDeletePropagation(prop string) DeleteOption { + p := metav1.DeletionPropagation(prop) + return func(do *metav1.DeleteOptions) { do.PropagationPolicy = &p } +} + type ListOption func(*metav1.ListOptions) func (r *Resources) List(ctx context.Context, objs k8s.ObjectList, opts ...ListOption) error { @@ -121,3 +133,43 @@ func (r *Resources) List(ctx context.Context, objs k8s.ObjectList, opts ...ListO o := &cr.ListOptions{Raw: listOptions} return r.client.List(ctx, objs, o) } + +func WithLabelSelector(sel string) ListOption { + return func(lo *metav1.ListOptions) { lo.LabelSelector = sel } +} + +func WithFieldSelector(sel string) ListOption { + return func(lo *metav1.ListOptions) { lo.FieldSelector = sel } +} + +func WithTimeout(to time.Duration) ListOption { + t := to.Milliseconds() + return func(lo *metav1.ListOptions) { lo.TimeoutSeconds = &t } +} + +// PatchOption is used to provide additional arguments to the Patch call. +type PatchOption func(*metav1.PatchOptions) + +// Patch patches portion of object `orig` with data from object `patch` +func (r *Resources) Patch(ctx context.Context, objs k8s.Object, patch k8s.Patch, opts ...PatchOption) error { + patchOptions := &metav1.PatchOptions{} + + for _, fn := range opts { + fn(patchOptions) + } + + p := cr.RawPatch(patch.PatchType, patch.Data) + + o := &cr.PatchOptions{Raw: patchOptions} + return r.client.Patch(ctx, objs, p, o) +} + +// Annotate attach annotations to an existing resource objec +func (r *Resources) Annotate(obj k8s.Object, annotation map[string]string) { + obj.SetAnnotations(annotation) +} + +// Label apply labels to an existing resources. +func (r *Resources) Label(obj k8s.Object, label map[string]string) { + obj.SetLabels(label) +} diff --git a/klient/k8s/resources/resources_test.go b/klient/k8s/resources/resources_test.go index 8be2a611..d2e12495 100644 --- a/klient/k8s/resources/resources_test.go +++ b/klient/k8s/resources/resources_test.go @@ -18,13 +18,16 @@ package resources import ( "context" + "encoding/json" "fmt" "testing" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" + "sigs.k8s.io/e2e-framework/klient/k8s" ) func TestCreate(t *testing.T) { @@ -181,3 +184,36 @@ func TestList(t *testing.T) { t.Error("there are no deployment exist", hasDep) } } + +func TestPatch(t *testing.T) { + res, err := New(cfg) + if err != nil { + t.Errorf("config is nill") + } + + mergePatch, err := json.Marshal(map[string]interface{}{ + "metadata": map[string]interface{}{ + "annotations": map[string]interface{}{ + "ping": "pong", + }, + }, + }) + if err != nil { + t.Error("error while json marshalling", err) + } + + err = res.Patch(context.Background(), dep, k8s.Patch{PatchType: types.StrategicMergePatchType, Data: mergePatch}) + if err != nil { + t.Error("error while patching the deployment", err) + } + + obj := &appsv1.Deployment{} + err = res.Get(context.Background(), dep.Name, dep.Namespace, obj) + if err != nil { + t.Error("error while getting patched deployment", err) + } + + if obj.Annotations["ping"] != "pong" { + t.Error("resource patch not applied correctly.") + } +}