Skip to content

Commit f20692b

Browse files
authored
Merge pull request #1662 from alvaroaleman/allow-save
🐛 Fakeclient: Allow manipulating registered types through unstructured
2 parents 5e0ef5f + dcd5e10 commit f20692b

File tree

2 files changed

+87
-1
lines changed

2 files changed

+87
-1
lines changed

pkg/client/fake/client.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ func (t versionedTracker) Add(obj runtime.Object) error {
170170
// be recognized
171171
accessor.SetResourceVersion(trackerAddResourceVersion)
172172
}
173+
174+
obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
175+
if err != nil {
176+
return err
177+
}
173178
if err := t.ObjectTracker.Add(obj); err != nil {
174179
return err
175180
}
@@ -193,13 +198,45 @@ func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Ob
193198
return apierrors.NewBadRequest("resourceVersion can not be set for Create requests")
194199
}
195200
accessor.SetResourceVersion("1")
201+
obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
202+
if err != nil {
203+
return err
204+
}
196205
if err := t.ObjectTracker.Create(gvr, obj, ns); err != nil {
197206
accessor.SetResourceVersion("")
198207
return err
199208
}
209+
200210
return nil
201211
}
202212

213+
// convertFromUnstructuredIfNecessary will convert *unstructured.Unstructured for a GVK that is recocnized
214+
// by the schema into the whatever the schema produces with New() for said GVK.
215+
// This is required because the tracker unconditionally saves on manipulations, but it's List() implementation
216+
// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
217+
// we save as the very same type, otherwise subsequent List requests will fail.
218+
func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
219+
u, isUnstructured := o.(*unstructured.Unstructured)
220+
if !isUnstructured || !s.Recognizes(u.GroupVersionKind()) {
221+
return o, nil
222+
}
223+
224+
typed, err := s.New(u.GroupVersionKind())
225+
if err != nil {
226+
return nil, fmt.Errorf("scheme recognizes %s but failed to produce an object for it: %w", u.GroupVersionKind().String(), err)
227+
}
228+
229+
unstructuredSerialized, err := json.Marshal(u)
230+
if err != nil {
231+
return nil, fmt.Errorf("failed to serialize %T: %w", unstructuredSerialized, err)
232+
}
233+
if err := json.Unmarshal(unstructuredSerialized, typed); err != nil {
234+
return nil, fmt.Errorf("failed to unmarshal the content of %T into %T: %w", u, typed, err)
235+
}
236+
237+
return typed, nil
238+
}
239+
203240
func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
204241
accessor, err := meta.Accessor(obj)
205242
if err != nil {
@@ -256,6 +293,10 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
256293
if !accessor.GetDeletionTimestamp().IsZero() && len(accessor.GetFinalizers()) == 0 {
257294
return t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
258295
}
296+
obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
297+
if err != nil {
298+
return err
299+
}
259300
return t.ObjectTracker.Update(gvr, obj, ns)
260301
}
261302

@@ -320,7 +361,7 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
320361
}
321362

322363
if _, isUnstructuredList := obj.(*unstructured.UnstructuredList); isUnstructuredList && !c.scheme.Recognizes(gvk) {
323-
// We need tor register the ListKind with UnstructuredList:
364+
// We need to register the ListKind with UnstructuredList:
324365
// https://github.com/kubernetes/kubernetes/blob/7b2776b89fb1be28d4e9203bdeec079be903c103/staging/src/k8s.io/client-go/dynamic/fake/simple.go#L44-L51
325366
c.schemeWriteLock.Lock()
326367
c.scheme.AddKnownTypeWithName(gvk.GroupVersion().WithKind(gvk.Kind+"List"), &unstructured.UnstructuredList{})

pkg/client/fake/client_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,51 @@ var _ = Describe("Fake client", func() {
140140
Expect(list.Items).To(HaveLen(2))
141141
})
142142

143+
It("should be able to retrieve registered objects that got manipulated as unstructured", func() {
144+
list := func() {
145+
By("Listing all endpoints in a namespace")
146+
list := &unstructured.UnstructuredList{}
147+
list.SetAPIVersion("v1")
148+
list.SetKind("EndpointsList")
149+
err := cl.List(context.Background(), list, client.InNamespace("ns1"))
150+
Expect(err).To(BeNil())
151+
Expect(list.Items).To(HaveLen(1))
152+
}
153+
154+
unstructuredEndpoint := func() *unstructured.Unstructured {
155+
item := &unstructured.Unstructured{}
156+
item.SetAPIVersion("v1")
157+
item.SetKind("Endpoints")
158+
item.SetName("test-endpoint")
159+
item.SetNamespace("ns1")
160+
return item
161+
}
162+
163+
By("Adding the object during client initialization")
164+
cl = NewFakeClient(unstructuredEndpoint())
165+
list()
166+
Expect(cl.Delete(context.Background(), unstructuredEndpoint())).To(BeNil())
167+
168+
By("Creating an object")
169+
item := unstructuredEndpoint()
170+
err := cl.Create(context.Background(), item)
171+
Expect(err).To(BeNil())
172+
list()
173+
174+
By("Updating the object")
175+
item.SetAnnotations(map[string]string{"foo": "bar"})
176+
err = cl.Update(context.Background(), item)
177+
Expect(err).To(BeNil())
178+
list()
179+
180+
By("Patching the object")
181+
old := item.DeepCopy()
182+
item.SetAnnotations(map[string]string{"bar": "baz"})
183+
err = cl.Patch(context.Background(), item, client.MergeFrom(old))
184+
Expect(err).To(BeNil())
185+
list()
186+
})
187+
143188
It("should be able to Create an unregistered type using unstructured", func() {
144189
item := &unstructured.Unstructured{}
145190
item.SetAPIVersion("custom/v1")

0 commit comments

Comments
 (0)