Skip to content

Commit 1929682

Browse files
committed
⚠️ Fakeclient: Only set TypeMeta for unstructured
Currently, the fakeclient unconditionally sets metadata for all objects retrieved through it. This causes two problems: * It differs from the behavior of the real client, except for the cached one if `DeepCopy` is enabled and might lead ppl to think that they can rely on `TypeMeta` being populated, which is absolutely not the case * It causes panics for types that have `TypeMeta` defined as pointer, making it impossible to use the client with them This PR changes the behavior to only populate TypeMeta for `unstructured`.
1 parent 7f316f1 commit 1929682

File tree

2 files changed

+151
-30
lines changed

2 files changed

+151
-30
lines changed

pkg/client/fake/client.go

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,12 @@ func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Ob
334334
// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
335335
// we save as the very same type, otherwise subsequent List requests will fail.
336336
func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
337-
gvk := o.GetObjectKind().GroupVersionKind()
338-
339337
u, isUnstructured := o.(runtime.Unstructured)
340-
if !isUnstructured || !s.Recognizes(gvk) {
338+
if !isUnstructured {
339+
return o, nil
340+
}
341+
gvk := o.GetObjectKind().GroupVersionKind()
342+
if !s.Recognizes(gvk) {
341343
return o, nil
342344
}
343345

@@ -464,25 +466,25 @@ func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.O
464466
return err
465467
}
466468

467-
gvk, err := apiutil.GVKForObject(obj, c.scheme)
468-
if err != nil {
469-
return err
470-
}
471-
ta, err := meta.TypeAccessor(o)
472-
if err != nil {
473-
return err
469+
if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
470+
gvk, err := apiutil.GVKForObject(obj, c.scheme)
471+
if err != nil {
472+
return err
473+
}
474+
ta, err := meta.TypeAccessor(o)
475+
if err != nil {
476+
return err
477+
}
478+
ta.SetKind(gvk.Kind)
479+
ta.SetAPIVersion(gvk.GroupVersion().String())
474480
}
475-
ta.SetKind(gvk.Kind)
476-
ta.SetAPIVersion(gvk.GroupVersion().String())
477481

478482
j, err := json.Marshal(o)
479483
if err != nil {
480484
return err
481485
}
482-
decoder := scheme.Codecs.UniversalDecoder()
483486
zero(obj)
484-
_, _, err = decoder.Decode(j, nil, obj)
485-
return err
487+
return json.Unmarshal(j, obj)
486488
}
487489

488490
func (c *fakeClient) Watch(ctx context.Context, list client.ObjectList, opts ...client.ListOption) (watch.Interface, error) {
@@ -527,21 +529,21 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
527529
return err
528530
}
529531

530-
ta, err := meta.TypeAccessor(o)
531-
if err != nil {
532-
return err
532+
if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
533+
ta, err := meta.TypeAccessor(o)
534+
if err != nil {
535+
return err
536+
}
537+
ta.SetKind(originalKind)
538+
ta.SetAPIVersion(gvk.GroupVersion().String())
533539
}
534-
ta.SetKind(originalKind)
535-
ta.SetAPIVersion(gvk.GroupVersion().String())
536540

537541
j, err := json.Marshal(o)
538542
if err != nil {
539543
return err
540544
}
541-
decoder := scheme.Codecs.UniversalDecoder()
542545
zero(obj)
543-
_, _, err = decoder.Decode(j, nil, obj)
544-
if err != nil {
546+
if err := json.Unmarshal(j, obj); err != nil {
545547
return err
546548
}
547549

pkg/client/fake/client_test.go

Lines changed: 126 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ import (
3737
"k8s.io/apimachinery/pkg/fields"
3838
"k8s.io/apimachinery/pkg/labels"
3939
"k8s.io/apimachinery/pkg/runtime"
40+
"k8s.io/apimachinery/pkg/runtime/schema"
4041
"k8s.io/apimachinery/pkg/types"
4142
"k8s.io/apimachinery/pkg/watch"
4243
"k8s.io/client-go/kubernetes/fake"
4344
"k8s.io/utils/ptr"
4445

4546
"sigs.k8s.io/controller-runtime/pkg/client"
4647
"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
48+
"sigs.k8s.io/controller-runtime/pkg/scheme"
4749
)
4850

4951
const (
@@ -1354,10 +1356,6 @@ var _ = Describe("Fake client", func() {
13541356
Expect(cl.Get(context.Background(), types.NamespacedName{Name: "cm"}, retrieved)).To(Succeed())
13551357

13561358
reference := &corev1.Secret{
1357-
TypeMeta: metav1.TypeMeta{
1358-
APIVersion: "v1",
1359-
Kind: "Secret",
1360-
},
13611359
ObjectMeta: metav1.ObjectMeta{
13621360
Name: "cm",
13631361
ResourceVersion: "999",
@@ -1771,8 +1769,6 @@ var _ = Describe("Fake client", func() {
17711769

17721770
actual := &corev1.Pod{}
17731771
Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(obj), actual)).To(Succeed())
1774-
obj.APIVersion = "v1"
1775-
obj.Kind = "Pod"
17761772
obj.ResourceVersion = actual.ResourceVersion
17771773
// only the status mutation should persist
17781774
obj.Status.Phase = corev1.PodRunning
@@ -1877,13 +1873,136 @@ var _ = Describe("Fake client", func() {
18771873
}
18781874

18791875
It("should error when creating an eviction with the wrong type", func() {
1880-
18811876
cl := NewClientBuilder().Build()
18821877
err := cl.SubResource("eviction").Create(context.Background(), &corev1.Pod{}, &corev1.Namespace{})
18831878
Expect(apierrors.IsBadRequest(err)).To(BeTrue())
18841879
})
1880+
1881+
It("should leave typemeta empty on typed get", func() {
1882+
cl := NewClientBuilder().WithObjects(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{
1883+
Namespace: "default",
1884+
Name: "foo",
1885+
}}).Build()
1886+
1887+
var pod corev1.Pod
1888+
Expect(cl.Get(context.Background(), client.ObjectKey{Namespace: "default", Name: "foo"}, &pod)).NotTo(HaveOccurred())
1889+
1890+
Expect(pod.TypeMeta).To(Equal(metav1.TypeMeta{}))
1891+
})
1892+
1893+
It("should leave typemeta empty on typed list", func() {
1894+
cl := NewClientBuilder().WithObjects(&corev1.Pod{ObjectMeta: metav1.ObjectMeta{
1895+
Namespace: "default",
1896+
Name: "foo",
1897+
}}).Build()
1898+
1899+
var podList corev1.PodList
1900+
Expect(cl.List(context.Background(), &podList)).NotTo(HaveOccurred())
1901+
Expect(podList.ListMeta).To(Equal(metav1.ListMeta{}))
1902+
Expect(podList.Items[0].TypeMeta).To(Equal(metav1.TypeMeta{}))
1903+
})
1904+
1905+
It("should be able to Get an object that has pointer fields for metadata", func() {
1906+
schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1907+
schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1908+
scheme := runtime.NewScheme()
1909+
Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1910+
1911+
cl := NewClientBuilder().
1912+
WithScheme(scheme).
1913+
WithObjects(&WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
1914+
Name: "foo",
1915+
}}).
1916+
Build()
1917+
1918+
var object WithPointerMeta
1919+
Expect(cl.Get(context.Background(), client.ObjectKey{Name: "foo"}, &object)).NotTo(HaveOccurred())
1920+
})
1921+
1922+
It("should be able to List an object type that has pointer fields for metadata", func() {
1923+
schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1924+
schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1925+
scheme := runtime.NewScheme()
1926+
Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1927+
1928+
cl := NewClientBuilder().
1929+
WithScheme(scheme).
1930+
WithObjects(&WithPointerMeta{ObjectMeta: &metav1.ObjectMeta{
1931+
Name: "foo",
1932+
}}).
1933+
Build()
1934+
1935+
var objectList WithPointerMetaList
1936+
Expect(cl.List(context.Background(), &objectList)).NotTo(HaveOccurred())
1937+
Expect(objectList.Items).To(HaveLen(1))
1938+
})
1939+
1940+
It("should be able to List an object type that has pointer fields for metadata with no results", func() {
1941+
schemeBuilder := &scheme.Builder{GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"}}
1942+
schemeBuilder.Register(&WithPointerMeta{}, &WithPointerMetaList{})
1943+
scheme := runtime.NewScheme()
1944+
Expect(schemeBuilder.AddToScheme(scheme)).NotTo(HaveOccurred())
1945+
1946+
cl := NewClientBuilder().
1947+
WithScheme(scheme).
1948+
Build()
1949+
1950+
var objectList WithPointerMetaList
1951+
Expect(cl.List(context.Background(), &objectList)).NotTo(HaveOccurred())
1952+
Expect(objectList.Items).To(HaveLen(0))
1953+
})
18851954
})
18861955

1956+
type WithPointerMetaList struct {
1957+
*metav1.ListMeta
1958+
*metav1.TypeMeta
1959+
Items []*WithPointerMeta
1960+
}
1961+
1962+
func (t *WithPointerMetaList) DeepCopy() *WithPointerMetaList {
1963+
l := &WithPointerMetaList{
1964+
ListMeta: t.ListMeta.DeepCopy(),
1965+
}
1966+
if t.TypeMeta != nil {
1967+
l.TypeMeta = &metav1.TypeMeta{
1968+
APIVersion: t.APIVersion,
1969+
Kind: t.Kind,
1970+
}
1971+
}
1972+
for _, item := range t.Items {
1973+
l.Items = append(l.Items, item.DeepCopy())
1974+
}
1975+
1976+
return l
1977+
}
1978+
1979+
func (t *WithPointerMetaList) DeepCopyObject() runtime.Object {
1980+
return t.DeepCopy()
1981+
}
1982+
1983+
type WithPointerMeta struct {
1984+
*metav1.TypeMeta
1985+
*metav1.ObjectMeta
1986+
}
1987+
1988+
func (t *WithPointerMeta) DeepCopy() *WithPointerMeta {
1989+
w := &WithPointerMeta{
1990+
ObjectMeta: t.ObjectMeta.DeepCopy(),
1991+
}
1992+
if t.TypeMeta != nil {
1993+
w.TypeMeta = &metav1.TypeMeta{
1994+
APIVersion: t.APIVersion,
1995+
Kind: t.Kind,
1996+
}
1997+
}
1998+
1999+
return w
2000+
}
2001+
2002+
func (t *WithPointerMeta) DeepCopyObject() runtime.Object {
2003+
return t.DeepCopy()
2004+
}
2005+
18872006
var _ = Describe("Fake client builder", func() {
18882007
It("panics when an index with the same name and GroupVersionKind is registered twice", func() {
18892008
// We need any realistic GroupVersionKind, the choice of apps/v1 Deployment is arbitrary.

0 commit comments

Comments
 (0)