Skip to content
This repository was archived by the owner on Nov 19, 2020. It is now read-only.

Commit f2ca415

Browse files
authored
Merge pull request #92 from ericchiang/delete-options
*: add options for setting the body of a DELETE request
2 parents e52016d + a7fdea8 commit f2ca415

File tree

4 files changed

+192
-25
lines changed

4 files changed

+192
-25
lines changed

client.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,16 @@ func (c *Client) Delete(ctx context.Context, req Resource, options ...Option) er
383383
if err != nil {
384384
return err
385385
}
386-
return c.do(ctx, "DELETE", url, nil, nil)
386+
o := &deleteOptions{
387+
Kind: "DeleteOptions",
388+
APIVersion: "v1",
389+
PropagationPolicy: "Background",
390+
}
391+
for _, option := range options {
392+
option.updateDelete(req, o)
393+
}
394+
395+
return c.do(ctx, "DELETE", url, o, nil)
387396
}
388397

389398
func (c *Client) Update(ctx context.Context, req Resource, options ...Option) error {

client_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,119 @@ func TestDefaultNamespace(t *testing.T) {
213213
}
214214
}
215215

216+
func TestDelete(t *testing.T) {
217+
t.Skip("this test requires a controller manager to run, see issue #95")
218+
219+
withNamespace(t, func(client *k8s.Client, namespace string) {
220+
cm := &corev1.ConfigMap{
221+
Metadata: &metav1.ObjectMeta{
222+
Name: k8s.String("my-configmap"),
223+
Namespace: &namespace,
224+
},
225+
}
226+
if err := client.Create(context.TODO(), cm); err != nil {
227+
t.Errorf("create configmap: %v", err)
228+
return
229+
}
230+
231+
cm2 := &corev1.ConfigMap{
232+
Metadata: &metav1.ObjectMeta{
233+
Name: k8s.String("my-configmap-2"),
234+
Namespace: &namespace,
235+
OwnerReferences: []*metav1.OwnerReference{
236+
{
237+
ApiVersion: k8s.String("v1"),
238+
Kind: k8s.String("ConfigMap"),
239+
Name: cm.Metadata.Name,
240+
Uid: cm.Metadata.Uid,
241+
BlockOwnerDeletion: k8s.Bool(true),
242+
},
243+
},
244+
},
245+
}
246+
if err := client.Create(context.TODO(), cm2); err != nil {
247+
t.Errorf("create configmap: %v", err)
248+
return
249+
}
250+
251+
if err := client.Delete(context.TODO(), cm, k8s.DeletePropagationForeground()); err != nil {
252+
t.Fatalf("delete configmap: %v", err)
253+
}
254+
255+
var err error
256+
for i := 0; i < 50; i++ {
257+
err = func() error {
258+
err := client.Get(context.TODO(), namespace, *cm2.Metadata.Name, cm2)
259+
if err == nil {
260+
return fmt.Errorf("expected 404 error")
261+
}
262+
apiErr, ok := err.(*k8s.APIError)
263+
if !ok {
264+
return fmt.Errorf("error was not of type APIError: %T %v", err, err)
265+
}
266+
if apiErr.Code != 404 {
267+
return fmt.Errorf("expected 404 error code, got %d", apiErr.Code)
268+
}
269+
return nil
270+
}()
271+
if err == nil {
272+
break
273+
}
274+
time.Sleep(100 * time.Millisecond)
275+
}
276+
if err != nil {
277+
t.Errorf("getting parent configmap: %v", err)
278+
}
279+
})
280+
}
281+
282+
func TestDeleteOrphan(t *testing.T) {
283+
withNamespace(t, func(client *k8s.Client, namespace string) {
284+
cm := &corev1.ConfigMap{
285+
Metadata: &metav1.ObjectMeta{
286+
Name: k8s.String("my-configmap"),
287+
Namespace: &namespace,
288+
},
289+
}
290+
if err := client.Create(context.TODO(), cm); err != nil {
291+
t.Errorf("create configmap: %v", err)
292+
return
293+
}
294+
295+
cm2 := &corev1.ConfigMap{
296+
Metadata: &metav1.ObjectMeta{
297+
Name: k8s.String("my-configmap-2"),
298+
Namespace: &namespace,
299+
OwnerReferences: []*metav1.OwnerReference{
300+
{
301+
ApiVersion: k8s.String("v1"),
302+
Kind: k8s.String("ConfigMap"),
303+
Name: cm.Metadata.Name,
304+
Uid: cm.Metadata.Uid,
305+
},
306+
},
307+
},
308+
}
309+
if err := client.Create(context.TODO(), cm2); err != nil {
310+
t.Errorf("create configmap: %v", err)
311+
return
312+
}
313+
314+
if err := client.Delete(context.TODO(), cm, k8s.DeletePropagationOrphan()); err != nil {
315+
t.Fatalf("delete configmap: %v", err)
316+
}
317+
318+
// We're attempting to affirm a negative, that this won't eventually be deleted.
319+
// Since there's no explicit even for us to wait for
320+
time.Sleep(time.Second)
321+
322+
err := client.Get(context.TODO(), namespace, *cm2.Metadata.Name, cm2)
323+
if err != nil {
324+
t.Errorf("failed to get configmap: %v", err)
325+
}
326+
})
327+
}
328+
216329
func Test404(t *testing.T) {
217330
withNamespace(t, func(client *k8s.Client, namespace string) {
218331
var configMap corev1.ConfigMap

labels.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type LabelSelector struct {
2929
}
3030

3131
func (l *LabelSelector) Selector() Option {
32-
return queryParam{"labelSelector", l.String()}
32+
return QueryParam("labelSelector", l.String())
3333
}
3434

3535
func (l *LabelSelector) String() string {

resource.go

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,90 @@ import (
1616
// Option represents optional call parameters, such as label selectors.
1717
type Option interface {
1818
updateURL(base string, v url.Values) string
19+
updateDelete(r Resource, d *deleteOptions)
1920
}
2021

21-
type queryParam struct {
22-
paramName string
23-
paramValue string
24-
}
22+
type optionFunc func(base string, v url.Values) string
23+
24+
func (f optionFunc) updateDelete(r Resource, d *deleteOptions) {}
25+
func (f optionFunc) updateURL(base string, v url.Values) string { return f(base, v) }
26+
27+
type deleteOptions struct {
28+
Kind string `json:"kind"`
29+
APIVersion string `json:"apiVersion"`
2530

26-
func (o queryParam) updateURL(base string, v url.Values) string {
27-
v.Set(o.paramName, o.paramValue)
28-
return base
31+
GracePeriod *int64 `json:"gracePeriodSeconds,omitempty"`
32+
Preconditions struct {
33+
UID string `json:"uid,omitempty"`
34+
} `json:"preconditions"`
35+
PropagationPolicy string `json:"propagationPolicy"`
2936
}
3037

3138
// QueryParam can be used to manually set a URL query parameter by name.
3239
func QueryParam(name, value string) Option {
33-
return queryParam{
34-
paramName: name,
35-
paramValue: value,
36-
}
40+
return optionFunc(func(base string, v url.Values) string {
41+
v.Set(name, value)
42+
return base
43+
})
44+
}
45+
46+
type deleteOptionFunc func(r Resource, d *deleteOptions)
47+
48+
func (f deleteOptionFunc) updateDelete(r Resource, d *deleteOptions) { f(r, d) }
49+
func (f deleteOptionFunc) updateURL(base string, v url.Values) string { return base }
50+
51+
func DeleteAtomic() Option {
52+
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
53+
d.Preconditions.UID = *r.GetMetadata().Uid
54+
})
55+
}
56+
57+
// DeletePropagationOrphan orphans the dependent resources during a delete.
58+
func DeletePropagationOrphan() Option {
59+
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
60+
d.PropagationPolicy = "Orphan"
61+
})
62+
}
63+
64+
// DeletePropagationBackground deletes the resources and causes the garbage
65+
// collector to delete dependent resources in the background.
66+
func DeletePropagationBackground() Option {
67+
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
68+
d.PropagationPolicy = "Background"
69+
})
70+
}
71+
72+
// DeletePropagationForeground deletes the resources and causes the garbage
73+
// collector to delete dependent resources and wait for all dependents whose
74+
// ownerReference.blockOwnerDeletion=true. API sever will put the "foregroundDeletion"
75+
// finalizer on the object, and sets its deletionTimestamp. This policy is
76+
// cascading, i.e., the dependents will be deleted with Foreground.
77+
func DeletePropagationForeground() Option {
78+
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
79+
d.PropagationPolicy = "Foreground"
80+
})
81+
}
82+
83+
func DeleteGracePeriod(d time.Duration) Option {
84+
seconds := int64(d / time.Second)
85+
return deleteOptionFunc(func(r Resource, d *deleteOptions) {
86+
d.GracePeriod = &seconds
87+
})
3788
}
3889

3990
// ResourceVersion causes watch operations to only show changes since
4091
// a particular version of a resource.
4192
func ResourceVersion(resourceVersion string) Option {
42-
return queryParam{"resourceVersion", resourceVersion}
93+
return QueryParam("resourceVersion", resourceVersion)
4394
}
4495

4596
// Timeout declares the timeout for list and watch operations. Timeout
4697
// is only accurate to the second.
4798
func Timeout(d time.Duration) Option {
48-
return queryParam{
99+
return QueryParam(
49100
"timeoutSeconds",
50101
strconv.FormatInt(int64(d/time.Second), 10),
51-
}
102+
)
52103
}
53104

54105
// Subresource is a way to interact with a part of an API object without needing
@@ -59,15 +110,9 @@ func Timeout(d time.Duration) Option {
59110
//
60111
// See https://kubernetes.io/docs/reference/api-concepts/
61112
func Subresource(name string) Option {
62-
return subresource{name}
63-
}
64-
65-
type subresource struct {
66-
name string
67-
}
68-
69-
func (s subresource) updateURL(base string, v url.Values) string {
70-
return base + "/" + s.name
113+
return optionFunc(func(base string, v url.Values) string {
114+
return base + "/" + name
115+
})
71116
}
72117

73118
type resourceType struct {

0 commit comments

Comments
 (0)