Skip to content

Commit 8d3d09d

Browse files
authored
Merge pull request #3364 from sbueringer/pr-fix-fake-client-ssa-after-list
🐛 fake client: fix SSA after List with non-list kind
2 parents fb2beab + 9893d5b commit 8d3d09d

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

pkg/client/client_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,43 @@ U5wwSivyi7vmegHKmblOzNVKA5qPO8zWzqBC
25162516
Expect(hasDep).To(BeTrue())
25172517
})
25182518

2519+
It("should fetch unstructured collection of objects when setting a non-list kind", func(ctx SpecContext) {
2520+
// While it is not ideal to omit the List suffix it can easily happen.
2521+
// As the client is using TrimSuffix(gvk.Kind,"List") this also works.
2522+
// As it works it is part of our API and we should test it accordingly.
2523+
By("create an initial object")
2524+
_, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})
2525+
Expect(err).NotTo(HaveOccurred())
2526+
2527+
cl, err := client.New(cfg, client.Options{})
2528+
Expect(err).NotTo(HaveOccurred())
2529+
2530+
By("listing all objects of that type in the cluster")
2531+
deps := &unstructured.UnstructuredList{}
2532+
deps.SetGroupVersionKind(schema.GroupVersionKind{
2533+
Group: "apps",
2534+
Kind: "Deployment",
2535+
Version: "v1",
2536+
})
2537+
err = cl.List(ctx, deps)
2538+
Expect(err).NotTo(HaveOccurred())
2539+
2540+
Expect(deps.Items).NotTo(BeEmpty())
2541+
hasDep := false
2542+
for _, item := range deps.Items {
2543+
Expect(item.GroupVersionKind()).To(Equal(schema.GroupVersionKind{
2544+
Group: "apps",
2545+
Kind: "Deployment",
2546+
Version: "v1",
2547+
}))
2548+
if item.GetName() == dep.Name && item.GetNamespace() == dep.Namespace {
2549+
hasDep = true
2550+
break
2551+
}
2552+
}
2553+
Expect(hasDep).To(BeTrue())
2554+
})
2555+
25192556
It("should fetch unstructured collection of objects, even if scheme is empty", func(ctx SpecContext) {
25202557
By("create an initial object")
25212558
_, err := clientset.AppsV1().Deployments(ns).Create(ctx, dep, metav1.CreateOptions{})

pkg/client/fake/client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,12 @@ func (c *fakeClient) addToSchemeIfUnknownAndUnstructuredOrPartial(obj runtime.Ob
17011701
return err
17021702
}
17031703

1704+
if isUnstructuredList || isPartialList {
1705+
if !strings.HasSuffix(gvk.Kind, "List") {
1706+
gvk.Kind += "List"
1707+
}
1708+
}
1709+
17041710
if !c.scheme.Recognizes(gvk) {
17051711
c.scheme.AddKnownTypeWithName(gvk, obj)
17061712
}

pkg/client/fake/client_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,6 +2939,40 @@ var _ = Describe("Fake client", func() {
29392939
Expect(result.Object["spec"]).To(Equal(map[string]any{"other": "data"}))
29402940
})
29412941

2942+
It("supports server-side apply of a custom resource via Apply method after List with a non-list kind", func(ctx SpecContext) {
2943+
cl := NewClientBuilder().Build()
2944+
2945+
// Previously this List call lead to addToSchemeIfUnknownAndUnstructuredOrPartial adding FakeResource as UnstructuredList to the scheme.
2946+
// This broke the subsequent SSA call as it was trying to create a new FakeResource object as an UnstructuredList.
2947+
// After a fix this List call leads to addToSchemeIfUnknownAndUnstructuredOrPartial adding FakeResourceList as UnstructuredList to the
2948+
// scheme even if the UnstructuredList here is missing the List suffix.
2949+
objList := &unstructured.UnstructuredList{}
2950+
objList.SetAPIVersion("custom/v1")
2951+
objList.SetKind("FakeResource")
2952+
Expect(cl.List(ctx, objList)).To(Succeed())
2953+
2954+
obj := &unstructured.Unstructured{}
2955+
obj.SetAPIVersion("custom/v1")
2956+
obj.SetKind("FakeResource")
2957+
obj.SetName("foo")
2958+
result := obj.DeepCopy()
2959+
2960+
Expect(unstructured.SetNestedField(obj.Object, map[string]any{"some": "data"}, "spec")).To(Succeed())
2961+
2962+
applyConfig := client.ApplyConfigurationFromUnstructured(obj)
2963+
Expect(cl.Apply(ctx, applyConfig, &client.ApplyOptions{FieldManager: "test-manager"})).To(Succeed())
2964+
2965+
Expect(cl.Get(ctx, client.ObjectKeyFromObject(result), result)).To(Succeed())
2966+
Expect(result.Object["spec"]).To(Equal(map[string]any{"some": "data"}))
2967+
2968+
Expect(unstructured.SetNestedField(obj.Object, map[string]any{"other": "data"}, "spec")).To(Succeed())
2969+
applyConfig2 := client.ApplyConfigurationFromUnstructured(obj)
2970+
Expect(cl.Apply(ctx, applyConfig2, &client.ApplyOptions{FieldManager: "test-manager"})).To(Succeed())
2971+
2972+
Expect(cl.Get(ctx, client.ObjectKeyFromObject(result), result)).To(Succeed())
2973+
Expect(result.Object["spec"]).To(Equal(map[string]any{"other": "data"}))
2974+
})
2975+
29422976
It("sets the fieldManager in create, patch and update", func(ctx SpecContext) {
29432977
owner := "test-owner"
29442978
cl := client.WithFieldOwner(

0 commit comments

Comments
 (0)