Skip to content

Commit bfba246

Browse files
authored
Merge pull request #447 from adracus/feature.delete-collection
✨ Implement delete collection via delete options
2 parents 59b131b + 3b06668 commit bfba246

File tree

9 files changed

+289
-10
lines changed

9 files changed

+289
-10
lines changed

pkg/client/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ func (c *client) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteO
130130
return c.typedClient.Delete(ctx, obj, opts...)
131131
}
132132

133+
// DeleteAllOf implements client.Client
134+
func (c *client) DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error {
135+
_, ok := obj.(*unstructured.Unstructured)
136+
if ok {
137+
return c.unstructuredClient.DeleteAllOf(ctx, obj, opts...)
138+
}
139+
return c.typedClient.DeleteAllOf(ctx, obj, opts...)
140+
}
141+
133142
// Patch implements client.Client
134143
func (c *client) Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
135144
_, ok := obj.(*unstructured.Unstructured)

pkg/client/client_test.go

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ var _ = Describe("Client", func() {
7070
BeforeEach(func(done Done) {
7171
atomic.AddUint64(&count, 1)
7272
dep = &appsv1.Deployment{
73-
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("deployment-name-%v", count), Namespace: ns},
73+
ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("deployment-name-%v", count), Namespace: ns, Labels: map[string]string{"app": fmt.Sprintf("bar-%v", count)}},
7474
Spec: appsv1.DeploymentSpec{
7575
Replicas: &replicaCount,
7676
Selector: &metav1.LabelSelector{
@@ -898,6 +898,37 @@ var _ = Describe("Client", func() {
898898
PIt("should fail if the GVK cannot be mapped to a Resource", func() {
899899

900900
})
901+
902+
It("should delete a collection of objects", func(done Done) {
903+
cl, err := client.New(cfg, client.Options{})
904+
Expect(err).NotTo(HaveOccurred())
905+
Expect(cl).NotTo(BeNil())
906+
907+
By("initially creating two Deployments")
908+
909+
dep2 := dep.DeepCopy()
910+
dep2.Name = dep2.Name + "-2"
911+
912+
dep, err = clientset.AppsV1().Deployments(ns).Create(dep)
913+
Expect(err).NotTo(HaveOccurred())
914+
dep2, err = clientset.AppsV1().Deployments(ns).Create(dep2)
915+
Expect(err).NotTo(HaveOccurred())
916+
917+
depName := dep.Name
918+
dep2Name := dep2.Name
919+
920+
By("deleting Deployments")
921+
err = cl.DeleteAllOf(context.TODO(), dep, client.InNamespace(ns), client.MatchingLabels(dep.ObjectMeta.Labels))
922+
Expect(err).NotTo(HaveOccurred())
923+
924+
By("validating the Deployment no longer exists")
925+
_, err = clientset.AppsV1().Deployments(ns).Get(depName, metav1.GetOptions{})
926+
Expect(err).To(HaveOccurred())
927+
_, err = clientset.AppsV1().Deployments(ns).Get(dep2Name, metav1.GetOptions{})
928+
Expect(err).To(HaveOccurred())
929+
930+
close(done)
931+
})
901932
})
902933
Context("with unstructured objects", func() {
903934
It("should delete an existing object from a go struct", func(done Done) {
@@ -974,6 +1005,44 @@ var _ = Describe("Client", func() {
9741005

9751006
close(done)
9761007
})
1008+
1009+
It("should delete a collection of object", func(done Done) {
1010+
cl, err := client.New(cfg, client.Options{})
1011+
Expect(err).NotTo(HaveOccurred())
1012+
Expect(cl).NotTo(BeNil())
1013+
1014+
By("initially creating two Deployments")
1015+
1016+
dep2 := dep.DeepCopy()
1017+
dep2.Name = dep2.Name + "-2"
1018+
1019+
dep, err = clientset.AppsV1().Deployments(ns).Create(dep)
1020+
Expect(err).NotTo(HaveOccurred())
1021+
dep2, err = clientset.AppsV1().Deployments(ns).Create(dep2)
1022+
Expect(err).NotTo(HaveOccurred())
1023+
1024+
depName := dep.Name
1025+
dep2Name := dep2.Name
1026+
1027+
By("deleting Deployments")
1028+
u := &unstructured.Unstructured{}
1029+
Expect(scheme.Convert(dep, u, nil)).To(Succeed())
1030+
u.SetGroupVersionKind(schema.GroupVersionKind{
1031+
Group: "apps",
1032+
Kind: "Deployment",
1033+
Version: "v1",
1034+
})
1035+
err = cl.DeleteAllOf(context.TODO(), u, client.InNamespace(ns), client.MatchingLabels(dep.ObjectMeta.Labels))
1036+
Expect(err).NotTo(HaveOccurred())
1037+
1038+
By("validating the Deployment no longer exists")
1039+
_, err = clientset.AppsV1().Deployments(ns).Get(depName, metav1.GetOptions{})
1040+
Expect(err).To(HaveOccurred())
1041+
_, err = clientset.AppsV1().Deployments(ns).Get(dep2Name, metav1.GetOptions{})
1042+
Expect(err).To(HaveOccurred())
1043+
1044+
close(done)
1045+
})
9771046
})
9781047
})
9791048

@@ -1994,6 +2063,10 @@ var _ = Describe("Client", func() {
19942063
PIt("should fail if the object doesn't have meta", func() {
19952064

19962065
})
2066+
2067+
PIt("should filter results by namespace selector", func() {
2068+
2069+
})
19972070
})
19982071
})
19992072

@@ -2072,6 +2145,34 @@ var _ = Describe("Client", func() {
20722145
})
20732146
})
20742147

2148+
Describe("DeleteCollectionOptions", func() {
2149+
It("should be convertable to list options", func() {
2150+
gp := int64(1)
2151+
do := &client.DeleteAllOfOptions{}
2152+
do.ApplyOptions([]client.DeleteAllOfOption{
2153+
client.GracePeriodSeconds(gp),
2154+
client.MatchingLabels{"foo": "bar"},
2155+
})
2156+
2157+
listOpts := do.AsListOptions()
2158+
Expect(listOpts).NotTo(BeNil())
2159+
Expect(listOpts.LabelSelector).To(Equal("foo=bar"))
2160+
})
2161+
2162+
It("should be convertable to delete options", func() {
2163+
gp := int64(1)
2164+
do := &client.DeleteAllOfOptions{}
2165+
do.ApplyOptions([]client.DeleteAllOfOption{
2166+
client.GracePeriodSeconds(gp),
2167+
client.MatchingLabels{"foo": "bar"},
2168+
})
2169+
2170+
deleteOpts := do.AsDeleteOptions()
2171+
Expect(deleteOpts).NotTo(BeNil())
2172+
Expect(deleteOpts.GracePeriodSeconds).To(Equal(&gp))
2173+
})
2174+
})
2175+
20752176
Describe("ListOptions", func() {
20762177
It("should be convertable to metav1.ListOptions", func() {
20772178
lo := (&client.ListOptions{}).ApplyOptions([]client.ListOption{
@@ -2105,6 +2206,13 @@ var _ = Describe("Client", func() {
21052206
Expect(lo).NotTo(BeNil())
21062207
Expect(lo.Namespace).To(Equal("test"))
21072208
})
2209+
2210+
It("should produce empty metav1.ListOptions if nil", func() {
2211+
var do *client.ListOptions
2212+
Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{}))
2213+
do = &client.ListOptions{}
2214+
Expect(do.AsListOptions()).To(Equal(&metav1.ListOptions{}))
2215+
})
21082216
})
21092217

21102218
Describe("UpdateOptions", func() {

pkg/client/example_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,22 @@ func ExampleClient_delete() {
199199
_ = c.Delete(context.Background(), u)
200200
}
201201

202+
// This example shows how to use the client with typed and unstrucurted objects to delete collections of objects.
203+
func ExampleClient_deleteAllOf() {
204+
// Using a typed object.
205+
// c is a created client.
206+
_ = c.DeleteAllOf(context.Background(), &corev1.Pod{}, client.InNamespace("foo"), client.MatchingLabels{"app": "foo"})
207+
208+
// Using an unstructured Object
209+
u := &unstructured.UnstructuredList{}
210+
u.SetGroupVersionKind(schema.GroupVersionKind{
211+
Group: "apps",
212+
Kind: "Deployment",
213+
Version: "v1",
214+
})
215+
_ = c.DeleteAllOf(context.Background(), u, client.InNamespace("foo"), client.MatchingLabels{"app": "foo"})
216+
}
217+
202218
// This example shows how to set up and consume a field selector over a pod's volumes' secretName field.
203219
func ExampleFieldIndexer_secretName() {
204220
// someIndexer is a FieldIndexer over a Cache

pkg/client/fake/client.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,49 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...cli
161161
if err != nil {
162162
return err
163163
}
164+
delOptions := client.DeleteOptions{}
165+
delOptions.ApplyOptions(opts)
166+
164167
//TODO: implement propagation
165168
return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
166169
}
167170

171+
func (c *fakeClient) DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...client.DeleteAllOfOption) error {
172+
gvk, err := apiutil.GVKForObject(obj, scheme.Scheme)
173+
if err != nil {
174+
return err
175+
}
176+
177+
dcOptions := client.DeleteAllOfOptions{}
178+
dcOptions.ApplyOptions(opts)
179+
180+
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
181+
o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
182+
if err != nil {
183+
return err
184+
}
185+
186+
objs, err := meta.ExtractList(o)
187+
if err != nil {
188+
return err
189+
}
190+
filteredObjs, err := objectutil.FilterWithLabels(objs, dcOptions.LabelSelector)
191+
if err != nil {
192+
return err
193+
}
194+
for _, o := range filteredObjs {
195+
accessor, err := meta.Accessor(o)
196+
if err != nil {
197+
return err
198+
}
199+
err = c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
200+
if err != nil {
201+
return err
202+
}
203+
}
204+
return nil
205+
}
206+
168207
func (c *fakeClient) Update(ctx context.Context, obj runtime.Object, opts ...client.UpdateOption) error {
169208
updateOptions := &client.UpdateOptions{}
170209
updateOptions.ApplyOptions(opts)

pkg/client/fake/client_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ var _ = Describe("Fake client", func() {
158158
Expect(list.Items).To(ConsistOf(*dep2))
159159
})
160160

161+
It("should be able to Delete a Collection", func() {
162+
By("Deleting a deploymentList")
163+
err := cl.DeleteAllOf(nil, &appsv1.Deployment{}, client.InNamespace("ns1"))
164+
Expect(err).To(BeNil())
165+
166+
By("Listing all deployments in the namespace")
167+
list := &appsv1.DeploymentList{}
168+
err = cl.List(nil, list, client.InNamespace("ns1"))
169+
Expect(err).To(BeNil())
170+
Expect(list.Items).To(BeEmpty())
171+
})
172+
161173
Context("with the DryRun option", func() {
162174
It("should not create a new object", func() {
163175
By("Creating a new configmap with DryRun")

pkg/client/interfaces.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ type Writer interface {
7676
// Patch patches the given obj in the Kubernetes cluster. obj must be a
7777
// struct pointer so that obj can be updated with the content returned by the Server.
7878
Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error
79+
80+
// DeleteAllOf deletes all objects of the given type matching the given options.
81+
DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error
7982
}
8083

8184
// StatusClient knows how to create a client which can update status subresource

0 commit comments

Comments
 (0)