Skip to content

Commit 276610b

Browse files
authored
Merge pull request #338 from pusher/options
⚠️ Add Server-Side DryRun support to Client
2 parents ff6ae79 + f7bb591 commit 276610b

File tree

8 files changed

+279
-17
lines changed

8 files changed

+279
-17
lines changed

hack/check-everything.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ set -e
1919
hack_dir=$(dirname ${BASH_SOURCE})
2020
source ${hack_dir}/common.sh
2121

22-
k8s_version=1.10.1
22+
k8s_version=1.13.1
2323
goarch=amd64
2424
goos="unknown"
2525

pkg/client/client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,21 +104,21 @@ type client struct {
104104
}
105105

106106
// Create implements client.Client
107-
func (c *client) Create(ctx context.Context, obj runtime.Object) error {
107+
func (c *client) Create(ctx context.Context, obj runtime.Object, opts ...CreateOptionFunc) error {
108108
_, ok := obj.(*unstructured.Unstructured)
109109
if ok {
110-
return c.unstructuredClient.Create(ctx, obj)
110+
return c.unstructuredClient.Create(ctx, obj, opts...)
111111
}
112-
return c.typedClient.Create(ctx, obj)
112+
return c.typedClient.Create(ctx, obj, opts...)
113113
}
114114

115115
// Update implements client.Client
116-
func (c *client) Update(ctx context.Context, obj runtime.Object) error {
116+
func (c *client) Update(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error {
117117
_, ok := obj.(*unstructured.Unstructured)
118118
if ok {
119-
return c.unstructuredClient.Update(ctx, obj)
119+
return c.unstructuredClient.Update(ctx, obj, opts...)
120120
}
121-
return c.typedClient.Update(ctx, obj)
121+
return c.typedClient.Update(ctx, obj, opts...)
122122
}
123123

124124
// Delete implements client.Client

pkg/client/client_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,25 @@ var _ = Describe("Client", func() {
255255
// TODO(seans3): implement these
256256
// Example: ListOptions
257257
})
258+
259+
Context("with the DryRun option", func() {
260+
It("should not create a new object", func(done Done) {
261+
cl, err := client.New(cfg, client.Options{})
262+
Expect(err).NotTo(HaveOccurred())
263+
Expect(cl).NotTo(BeNil())
264+
265+
By("creating the object (with DryRun)")
266+
err = cl.Create(context.TODO(), dep, client.CreateDryRunAll())
267+
Expect(err).NotTo(HaveOccurred())
268+
269+
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
270+
Expect(err).To(HaveOccurred())
271+
Expect(errors.IsNotFound(err)).To(BeTrue())
272+
Expect(actual).To(Equal(&appsv1.Deployment{}))
273+
274+
close(done)
275+
})
276+
})
258277
})
259278

260279
Context("with unstructured objects", func() {
@@ -367,6 +386,33 @@ var _ = Describe("Client", func() {
367386

368387
})
369388

389+
Context("with the DryRun option", func() {
390+
It("should not create a new object from a go struct", func(done Done) {
391+
cl, err := client.New(cfg, client.Options{})
392+
Expect(err).NotTo(HaveOccurred())
393+
Expect(cl).NotTo(BeNil())
394+
395+
By("encoding the deployment as unstructured")
396+
u := &unstructured.Unstructured{}
397+
scheme.Convert(dep, u, nil)
398+
u.SetGroupVersionKind(schema.GroupVersionKind{
399+
Group: "apps",
400+
Kind: "Deployment",
401+
Version: "v1",
402+
})
403+
404+
By("creating the object")
405+
err = cl.Create(context.TODO(), u, client.CreateDryRunAll())
406+
Expect(err).NotTo(HaveOccurred())
407+
408+
actual, err := clientset.AppsV1().Deployments(ns).Get(dep.Name, metav1.GetOptions{})
409+
Expect(err).To(HaveOccurred())
410+
Expect(errors.IsNotFound(err)).To(BeTrue())
411+
Expect(actual).To(Equal(&appsv1.Deployment{}))
412+
413+
close(done)
414+
})
415+
})
370416
})
371417

372418
Describe("Update", func() {
@@ -1720,6 +1766,22 @@ var _ = Describe("Client", func() {
17201766
})
17211767
})
17221768

1769+
Describe("CreateOptions", func() {
1770+
It("should allow setting DryRun to 'all'", func() {
1771+
co := &client.CreateOptions{}
1772+
client.CreateDryRunAll()(co)
1773+
all := []string{metav1.DryRunAll}
1774+
Expect(co.AsCreateOptions().DryRun).To(Equal(all))
1775+
})
1776+
1777+
It("should produce empty metav1.CreateOptions if nil", func() {
1778+
var co *client.CreateOptions
1779+
Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{}))
1780+
co = &client.CreateOptions{}
1781+
Expect(co.AsCreateOptions()).To(Equal(&metav1.CreateOptions{}))
1782+
})
1783+
})
1784+
17231785
Describe("DeleteOptions", func() {
17241786
It("should allow setting GracePeriodSeconds", func() {
17251787
do := &client.DeleteOptions{}
@@ -1844,6 +1906,22 @@ var _ = Describe("Client", func() {
18441906
Expect(lo.Namespace).To(Equal("test"))
18451907
})
18461908
})
1909+
1910+
Describe("UpdateOptions", func() {
1911+
It("should allow setting DryRun to 'all'", func() {
1912+
uo := &client.UpdateOptions{}
1913+
client.UpdateDryRunAll()(uo)
1914+
all := []string{metav1.DryRunAll}
1915+
Expect(uo.AsUpdateOptions().DryRun).To(Equal(all))
1916+
})
1917+
1918+
It("should produce empty metav1.UpdateOptions if nil", func() {
1919+
var co *client.UpdateOptions
1920+
Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{}))
1921+
co = &client.UpdateOptions{}
1922+
Expect(co.AsUpdateOptions()).To(Equal(&metav1.UpdateOptions{}))
1923+
})
1924+
})
18471925
})
18481926

18491927
var _ = Describe("DelegatingReader", func() {

pkg/client/fake/client.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"strings"
2525

2626
"k8s.io/apimachinery/pkg/api/meta"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2728
"k8s.io/apimachinery/pkg/runtime"
2829
"k8s.io/apimachinery/pkg/runtime/schema"
2930
"k8s.io/client-go/kubernetes/scheme"
@@ -138,7 +139,16 @@ func (c *fakeClient) List(ctx context.Context, obj runtime.Object, opts ...clien
138139
return nil
139140
}
140141

141-
func (c *fakeClient) Create(ctx context.Context, obj runtime.Object) error {
142+
func (c *fakeClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOptionFunc) error {
143+
createOptions := &client.CreateOptions{}
144+
createOptions.ApplyOptions(opts)
145+
146+
for _, dryRunOpt := range createOptions.DryRun {
147+
if dryRunOpt == metav1.DryRunAll {
148+
return nil
149+
}
150+
}
151+
142152
gvr, err := getGVRFromObject(obj, c.scheme)
143153
if err != nil {
144154
return err
@@ -163,7 +173,16 @@ func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...cli
163173
return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
164174
}
165175

166-
func (c *fakeClient) Update(ctx context.Context, obj runtime.Object) error {
176+
func (c *fakeClient) Update(ctx context.Context, obj runtime.Object, opts ...client.UpdateOptionFunc) error {
177+
updateOptions := &client.UpdateOptions{}
178+
updateOptions.ApplyOptions(opts)
179+
180+
for _, dryRunOpt := range updateOptions.DryRun {
181+
if dryRunOpt == metav1.DryRunAll {
182+
return nil
183+
}
184+
}
185+
167186
gvr, err := getGVRFromObject(obj, c.scheme)
168187
if err != nil {
169188
return err

pkg/client/fake/client_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
appsv1 "k8s.io/api/apps/v1"
2424
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/errors"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
"k8s.io/apimachinery/pkg/runtime"
2728
"k8s.io/apimachinery/pkg/types"
@@ -154,6 +155,56 @@ var _ = Describe("Fake client", func() {
154155
Expect(list.Items).To(HaveLen(1))
155156
Expect(list.Items).To(ConsistOf(*dep2))
156157
})
158+
159+
Context("with the DryRun option", func() {
160+
It("should not create a new object", func() {
161+
By("Creating a new configmap with DryRun")
162+
newcm := &corev1.ConfigMap{
163+
ObjectMeta: metav1.ObjectMeta{
164+
Name: "new-test-cm",
165+
Namespace: "ns2",
166+
},
167+
}
168+
err := cl.Create(nil, newcm, client.CreateDryRunAll())
169+
Expect(err).To(BeNil())
170+
171+
By("Getting the new configmap")
172+
namespacedName := types.NamespacedName{
173+
Name: "new-test-cm",
174+
Namespace: "ns2",
175+
}
176+
obj := &corev1.ConfigMap{}
177+
err = cl.Get(nil, namespacedName, obj)
178+
Expect(err).To(HaveOccurred())
179+
Expect(errors.IsNotFound(err)).To(BeTrue())
180+
Expect(obj).NotTo(Equal(newcm))
181+
})
182+
183+
It("should not Update the object", func() {
184+
By("Updating a new configmap with DryRun")
185+
newcm := &corev1.ConfigMap{
186+
ObjectMeta: metav1.ObjectMeta{
187+
Name: "test-cm",
188+
Namespace: "ns2",
189+
},
190+
Data: map[string]string{
191+
"test-key": "new-value",
192+
},
193+
}
194+
err := cl.Update(nil, newcm, client.UpdateDryRunAll())
195+
Expect(err).To(BeNil())
196+
197+
By("Getting the new configmap")
198+
namespacedName := types.NamespacedName{
199+
Name: "test-cm",
200+
Namespace: "ns2",
201+
}
202+
obj := &corev1.ConfigMap{}
203+
err = cl.Get(nil, namespacedName, obj)
204+
Expect(err).To(BeNil())
205+
Expect(obj).To(Equal(cm))
206+
})
207+
})
157208
}
158209

159210
Context("with default scheme.Scheme", func() {

pkg/client/interfaces.go

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ type Reader interface {
5757
// Writer knows how to create, delete, and update Kubernetes objects.
5858
type Writer interface {
5959
// Create saves the object obj in the Kubernetes cluster.
60-
Create(ctx context.Context, obj runtime.Object) error
60+
Create(ctx context.Context, obj runtime.Object, opts ...CreateOptionFunc) error
6161

6262
// Delete deletes the given obj from Kubernetes cluster.
6363
Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOptionFunc) error
6464

6565
// Update updates the given obj in the Kubernetes cluster. obj must be a
6666
// struct pointer so that obj can be updated with the content returned by the Server.
67-
Update(ctx context.Context, obj runtime.Object) error
67+
Update(ctx context.Context, obj runtime.Object, opts ...UpdateOptionFunc) error
6868
}
6969

7070
// StatusClient knows how to create a client which can update status subresource
@@ -106,6 +106,57 @@ type FieldIndexer interface {
106106
IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error
107107
}
108108

109+
// CreateOptions contains options for create requests. It's generally a subset
110+
// of metav1.CreateOptions.
111+
type CreateOptions struct {
112+
// When present, indicates that modifications should not be
113+
// persisted. An invalid or unrecognized dryRun directive will
114+
// result in an error response and no further processing of the
115+
// request. Valid values are:
116+
// - All: all dry run stages will be processed
117+
DryRun []string
118+
119+
// Raw represents raw CreateOptions, as passed to the API server.
120+
Raw *metav1.CreateOptions
121+
}
122+
123+
// AsCreateOptions returns these options as a metav1.CreateOptions.
124+
// This may mutate the Raw field.
125+
func (o *CreateOptions) AsCreateOptions() *metav1.CreateOptions {
126+
127+
if o == nil {
128+
return &metav1.CreateOptions{}
129+
}
130+
if o.Raw == nil {
131+
o.Raw = &metav1.CreateOptions{}
132+
}
133+
134+
o.Raw.DryRun = o.DryRun
135+
return o.Raw
136+
}
137+
138+
// ApplyOptions executes the given CreateOptionFuncs and returns the mutated
139+
// CreateOptions.
140+
func (o *CreateOptions) ApplyOptions(optFuncs []CreateOptionFunc) *CreateOptions {
141+
for _, optFunc := range optFuncs {
142+
optFunc(o)
143+
}
144+
return o
145+
}
146+
147+
// CreateOptionFunc is a function that mutates a CreateOptions struct. It implements
148+
// the functional options pattern. See
149+
// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md.
150+
type CreateOptionFunc func(*CreateOptions)
151+
152+
// CreateDryRunAll is a functional option that sets the DryRun
153+
// field of a CreateOptions struct to metav1.DryRunAll.
154+
func CreateDryRunAll() CreateOptionFunc {
155+
return func(opts *CreateOptions) {
156+
opts.DryRun = []string{metav1.DryRunAll}
157+
}
158+
}
159+
109160
// DeleteOptions contains options for delete requests. It's generally a subset
110161
// of metav1.DeleteOptions.
111162
type DeleteOptions struct {
@@ -326,3 +377,54 @@ func UseListOptions(newOpts *ListOptions) ListOptionFunc {
326377
*opts = *newOpts
327378
}
328379
}
380+
381+
// UpdateOptions contains options for create requests. It's generally a subset
382+
// of metav1.UpdateOptions.
383+
type UpdateOptions struct {
384+
// When present, indicates that modifications should not be
385+
// persisted. An invalid or unrecognized dryRun directive will
386+
// result in an error response and no further processing of the
387+
// request. Valid values are:
388+
// - All: all dry run stages will be processed
389+
DryRun []string
390+
391+
// Raw represents raw UpdateOptions, as passed to the API server.
392+
Raw *metav1.UpdateOptions
393+
}
394+
395+
// AsUpdateOptions returns these options as a metav1.UpdateOptions.
396+
// This may mutate the Raw field.
397+
func (o *UpdateOptions) AsUpdateOptions() *metav1.UpdateOptions {
398+
399+
if o == nil {
400+
return &metav1.UpdateOptions{}
401+
}
402+
if o.Raw == nil {
403+
o.Raw = &metav1.UpdateOptions{}
404+
}
405+
406+
o.Raw.DryRun = o.DryRun
407+
return o.Raw
408+
}
409+
410+
// ApplyOptions executes the given UpdateOptionFuncs and returns the mutated
411+
// UpdateOptions.
412+
func (o *UpdateOptions) ApplyOptions(optFuncs []UpdateOptionFunc) *UpdateOptions {
413+
for _, optFunc := range optFuncs {
414+
optFunc(o)
415+
}
416+
return o
417+
}
418+
419+
// UpdateOptionFunc is a function that mutates a UpdateOptions struct. It implements
420+
// the functional options pattern. See
421+
// https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md.
422+
type UpdateOptionFunc func(*UpdateOptions)
423+
424+
// UpdateDryRunAll is a functional option that sets the DryRun
425+
// field of a UpdateOptions struct to metav1.DryRunAll.
426+
func UpdateDryRunAll() UpdateOptionFunc {
427+
return func(opts *UpdateOptions) {
428+
opts.DryRun = []string{metav1.DryRunAll}
429+
}
430+
}

0 commit comments

Comments
 (0)