From 7571dae8f6cc66445b392b08cf397b51fb9a8fd2 Mon Sep 17 00:00:00 2001 From: Grant Rodgers Date: Thu, 26 Jul 2018 15:09:09 -0700 Subject: [PATCH] Add options to client.Delete Allows Delete requests to carry options like GracePeriodSeconds, Preconditions, and PropagationPolicy. To use the default options, call Delete with the same signature as before: client.Delete(context.TODO(), obj) Delete options are passed to the request using the functional options pattern. This Delete call sets a GracePeriodSeconds: client.Delete(context.TODO(), obj, client.GracePeriodSeconds(1)) Multiple options can be set: client.Delete(context.TODO(), obj, client.GracePeriodSeconds(1), client.PropagationPolicy(metav1.DeletePropagationForeground)) The fake client doesn't yet implement delete options like propagation, so keep that in mind when writing tests. --- pkg/client/client.go | 5 ++- pkg/client/client_test.go | 46 +++++++++++++++++++++ pkg/client/fake/client.go | 3 +- pkg/client/interfaces.go | 84 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 135 insertions(+), 3 deletions(-) diff --git a/pkg/client/client.go b/pkg/client/client.go index a7854ef629..195d180dda 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -111,15 +111,18 @@ func (c *client) Update(ctx context.Context, obj runtime.Object) error { } // Delete implements client.Client -func (c *client) Delete(ctx context.Context, obj runtime.Object) error { +func (c *client) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error { o, err := c.cache.getObjMeta(obj) if err != nil { return err } + + deleteOpts := DeleteOptions{} return o.Delete(). NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()). Resource(o.resource()). Name(o.GetName()). + Body(deleteOpts.ApplyOptions(opts).AsDeleteOptions()). Do(). Error() } diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index 3720749943..072322c8ed 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -978,6 +978,52 @@ var _ = Describe("Client", func() { }) }) + Describe("DeleteOptions", func() { + It("should allow setting GracePeriodSeconds", func() { + do := &client.DeleteOptions{} + client.GracePeriodSeconds(1)(do) + gp := int64(1) + Expect(do.AsDeleteOptions().GracePeriodSeconds).To(Equal(&gp)) + }) + + It("should allow setting Precondition", func() { + do := &client.DeleteOptions{} + pc := metav1.NewUIDPreconditions("uid") + client.Preconditions(pc)(do) + Expect(do.AsDeleteOptions().Preconditions).To(Equal(pc)) + Expect(do.Preconditions).To(Equal(pc)) + }) + + It("should allow setting PropagationPolicy", func() { + do := &client.DeleteOptions{} + client.PropagationPolicy(metav1.DeletePropagationForeground)(do) + dp := metav1.DeletePropagationForeground + Expect(do.AsDeleteOptions().PropagationPolicy).To(Equal(&dp)) + }) + + It("should produce empty metav1.DeleteOptions if nil", func() { + var do *client.DeleteOptions + Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) + do = &client.DeleteOptions{} + Expect(do.AsDeleteOptions()).To(Equal(&metav1.DeleteOptions{})) + }) + + It("should merge multiple options together", func() { + gp := int64(1) + pc := metav1.NewUIDPreconditions("uid") + dp := metav1.DeletePropagationForeground + do := &client.DeleteOptions{} + do.ApplyOptions([]client.DeleteOptionFunc{ + client.GracePeriodSeconds(gp), + client.Preconditions(pc), + client.PropagationPolicy(dp), + }) + Expect(do.GracePeriodSeconds).To(Equal(&gp)) + Expect(do.Preconditions).To(Equal(pc)) + Expect(do.PropagationPolicy).To(Equal(&dp)) + }) + }) + Describe("ListOptions", func() { It("should be able to set a LabelSelector", func() { lo := &client.ListOptions{} diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index 598cfd48ae..8597cbde6c 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -105,7 +105,7 @@ func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error { return c.tracker.Create(gvr, obj, accessor.GetNamespace()) } -func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object) error { +func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOptionFunc) error { gvr, err := getGVRFromObject(obj) if err != nil { return err @@ -114,6 +114,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object) error { if err != nil { return err } + //TODO: implement propagation return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName()) } diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index 732ec1061c..513f4ec263 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -50,7 +50,7 @@ type Writer interface { Create(ctx context.Context, obj runtime.Object) error // Delete deletes the given obj from Kubernetes cluster. - Delete(ctx context.Context, obj runtime.Object) error + Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error // Update updates the given obj in the Kubernetes cluster. obj must be a // struct pointer so that obj can be updated with the content returned by the Server. @@ -93,6 +93,88 @@ type FieldIndexer interface { IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error } +// DeleteOptions contains options for delete requests. It's generally a subset +// of metav1.DeleteOptions. +type DeleteOptions struct { + // GracePeriodSeconds is the duration in seconds before the object should be + // deleted. Value must be non-negative integer. The value zero indicates + // delete immediately. If this value is nil, the default grace period for the + // specified type will be used. + GracePeriodSeconds *int64 + + // Preconditions must be fulfilled before a deletion is carried out. If not + // possible, a 409 Conflict status will be returned. + Preconditions *metav1.Preconditions + + // PropagationPolicy determined whether and how garbage collection will be + // performed. Either this field or OrphanDependents may be set, but not both. + // The default policy is decided by the existing finalizer set in the + // metadata.finalizers and the resource-specific default policy. + // Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - + // allow the garbage collector to delete the dependents in the background; + // 'Foreground' - a cascading policy that deletes all dependents in the + // foreground. + PropagationPolicy *metav1.DeletionPropagation + + // Raw represents raw DeleteOptions, as passed to the API server. + Raw *metav1.DeleteOptions +} + +// AsDeleteOptions returns these options as a metav1.DeleteOptions. +// This may mutate the Raw field. +func (o *DeleteOptions) AsDeleteOptions() *metav1.DeleteOptions { + + if o == nil { + return &metav1.DeleteOptions{} + } + if o.Raw == nil { + o.Raw = &metav1.DeleteOptions{} + } + + o.Raw.GracePeriodSeconds = o.GracePeriodSeconds + o.Raw.Preconditions = o.Preconditions + o.Raw.PropagationPolicy = o.PropagationPolicy + return o.Raw +} + +// ApplyOptions executes the given DeleteOptionFuncs and returns the mutated +// DeleteOptions. +func (o *DeleteOptions) ApplyOptions(optFuncs []DeleteOptionFunc) *DeleteOptions { + for _, optFunc := range optFuncs { + optFunc(o) + } + return o +} + +// DeleteOptionFunc is a function that mutates a DeleteOptions struct. It implements +// the functional options pattern. See +// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md. +type DeleteOptionFunc func(*DeleteOptions) + +// GracePeriodSeconds is a functional option that sets the GracePeriodSeconds +// field of a DeleteOptions struct. +func GracePeriodSeconds(gp int64) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.GracePeriodSeconds = &gp + } +} + +// Preconditions is a functional option that sets the Preconditions field of a +// DeleteOptions struct. +func Preconditions(p *metav1.Preconditions) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.Preconditions = p + } +} + +// PropagationPolicy is a functional option that sets the PropagationPolicy +// field of a DeleteOptions struct. +func PropagationPolicy(p metav1.DeletionPropagation) DeleteOptionFunc { + return func(opts *DeleteOptions) { + opts.PropagationPolicy = &p + } +} + // ListOptions contains options for limitting or filtering results. // It's generally a subset of metav1.ListOptions, with support for // pre-parsed selectors (since generally, selectors will be executed