Skip to content

Commit 0eedab6

Browse files
authored
chore: allow PartialObjectMetadata in fake client (#1564)
1 parent c1eacc8 commit 0eedab6

File tree

5 files changed

+157
-32
lines changed

5 files changed

+157
-32
lines changed

pkg/syncer/syncertest/fake/client.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"k8s.io/apimachinery/pkg/runtime/schema"
2525
"k8s.io/apimachinery/pkg/runtime/serializer"
2626
"k8s.io/apimachinery/pkg/watch"
27-
"kpt.dev/configsync/pkg/kinds"
2827
"kpt.dev/configsync/pkg/syncer/reconcile"
2928
"sigs.k8s.io/cli-utils/pkg/testutil"
3029
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -95,11 +94,7 @@ func (c *Client) IsObjectNamespaced(_ runtime.Object) (bool, error) {
9594
func (c *Client) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
9695
options := &client.GetOptions{}
9796
options.ApplyOptions(opts)
98-
gvk, err := kinds.Lookup(obj, c.scheme)
99-
if err != nil {
100-
return err
101-
}
102-
return c.storage.Get(ctx, gvk, key, obj, options)
97+
return c.storage.Get(ctx, key, obj, options)
10398
}
10499

105100
// List implements client.Client.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package fake
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"sigs.k8s.io/controller-runtime/pkg/client"
25+
)
26+
27+
// convertUnstructuredIntoObject wraps Scheme.Convert to add support for
28+
// PartialObjectMetadata as an optional target type.
29+
//
30+
// Warning: This code converts through JSON and should only be used for testing.
31+
func convertUnstructuredIntoObject(from *unstructured.Unstructured, to client.Object, scheme *runtime.Scheme) error {
32+
// Convert to the requested type and version (DeepCopyInto the input object).
33+
switch tObj := to.(type) {
34+
case *metav1.PartialObjectMetadata:
35+
return convertFromUnstructuredIntoV1MetadataObject(from, tObj, scheme)
36+
// TODO: add support for v1beta1.PartialObjectMetadata, as needed
37+
default:
38+
return scheme.Convert(from, to, nil)
39+
}
40+
}
41+
42+
// convertUnstructuredListIntoObjectList wraps Scheme.Convert to add support for
43+
// PartialObjectMetadataList as an optional target type.
44+
//
45+
// Warning: This code converts through JSON and should only be used for testing.
46+
func convertUnstructuredListIntoObjectList(from *unstructured.UnstructuredList, to client.ObjectList, scheme *runtime.Scheme) error {
47+
// Convert to the requested type and version (DeepCopyInto the input object).
48+
switch tObj := to.(type) {
49+
case *metav1.PartialObjectMetadataList:
50+
return convertFromUnstructuredListIntoV1MetadataList(from, tObj, scheme)
51+
// TODO: add support for v1beta1.PartialObjectMetadataList, as needed
52+
default:
53+
return scheme.Convert(from, to, nil)
54+
}
55+
}
56+
57+
// convertFromUnstructuredIntoV1MetadataObject converts from UnstructuredList
58+
// to v1.PartialObjectMetadata.
59+
//
60+
// PartialObjectMetadata isn't registered with the Scheme and has no generated
61+
// conversion code, so it has to be converted through JSON.
62+
// This is effectively Convert + DeepCopyInto.
63+
//
64+
// Warning: This code converts through JSON and should only be used for testing.
65+
func convertFromUnstructuredIntoV1MetadataObject(uObj *unstructured.Unstructured, mObj *metav1.PartialObjectMetadata, scheme *runtime.Scheme) error {
66+
// Validate matching GroupKinds
67+
if uObj.GroupVersionKind().GroupKind() != mObj.GroupVersionKind().GroupKind() {
68+
return fmt.Errorf("converting Unstructured (%s) to PartialObjectMetadata (%s): GroupKind mismatch",
69+
uObj.GroupVersionKind().GroupKind(), mObj.GroupVersionKind().GroupKind())
70+
}
71+
// Convert to desired version
72+
if uObj.GroupVersionKind().Version != mObj.GroupVersionKind().Version {
73+
vObj := &unstructured.Unstructured{}
74+
vObj.SetGroupVersionKind(mObj.GroupVersionKind())
75+
if err := scheme.Convert(uObj, vObj, nil); err != nil {
76+
return fmt.Errorf("converting Unstructured (%s) to Unstructured (%s): %w",
77+
uObj.GroupVersionKind(), vObj.GroupVersionKind(), err)
78+
}
79+
uObj = vObj
80+
}
81+
// Convert from Unstructured to PartialObjectMetadata the hard way
82+
jsonBytes, err := uObj.MarshalJSON()
83+
if err != nil {
84+
return fmt.Errorf("encoding Unstructured as json: %w", err)
85+
}
86+
if err := json.Unmarshal(jsonBytes, mObj); err != nil {
87+
return fmt.Errorf("decoding json as PartialObjectMetadata: %w", err)
88+
}
89+
return nil
90+
}
91+
92+
// convertFromUnstructuredListIntoV1MetadataList converts from UnstructuredList
93+
// to v1.PartialObjectMetadataList.
94+
//
95+
// PartialObjectMetadataList isn't registered with the Scheme and has no
96+
// generated conversion code, so it has to be converted through JSON.
97+
// This is effectively Convert + DeepCopyInto.
98+
//
99+
// Warning: This code converts through JSON and should only be used for testing.
100+
func convertFromUnstructuredListIntoV1MetadataList(uList *unstructured.UnstructuredList, mList *metav1.PartialObjectMetadataList, scheme *runtime.Scheme) error {
101+
// Validate matching GroupKinds
102+
if uList.GroupVersionKind().GroupKind() != mList.GroupVersionKind().GroupKind() {
103+
return fmt.Errorf("converting UnstructuredList (%s) to PartialObjectMetadataList (%s): GroupKind mismatch",
104+
uList.GroupVersionKind().GroupKind(), mList.GroupVersionKind().GroupKind())
105+
}
106+
// Convert to desired version
107+
if uList.GroupVersionKind().Version != mList.GroupVersionKind().Version {
108+
vList := &unstructured.UnstructuredList{}
109+
vList.SetGroupVersionKind(mList.GroupVersionKind())
110+
if err := scheme.Convert(uList, vList, nil); err != nil {
111+
return fmt.Errorf("converting UnstructuredList (%s) to UnstructuredList (%s): %w",
112+
uList.GroupVersionKind(), vList.GroupVersionKind(), err)
113+
}
114+
uList = vList
115+
}
116+
// Convert from UnstructuredList to PartialObjectMetadataList the hard way
117+
jsonBytes, err := uList.MarshalJSON()
118+
if err != nil {
119+
return fmt.Errorf("encoding UnstructuredList as json: %w", err)
120+
}
121+
if err := json.Unmarshal(jsonBytes, mList); err != nil {
122+
return fmt.Errorf("decoding json as PartialObjectMetadataList: %w", err)
123+
}
124+
return nil
125+
}

pkg/syncer/syncertest/fake/dynamic_client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ func (dc *DynamicClient) get(action clienttesting.Action) (bool, runtime.Object,
192192
}
193193
id := genID(getAction.GetNamespace(), getAction.GetName(), gvk.GroupKind())
194194
uObj := &unstructured.Unstructured{}
195-
err = dc.storage.Get(context.Background(), gvk, id.ObjectKey, uObj, &client.GetOptions{})
195+
uObj.SetGroupVersionKind(gvk)
196+
err = dc.storage.Get(context.Background(), id.ObjectKey, uObj, &client.GetOptions{})
196197
return true, uObj, err
197198
}
198199

pkg/syncer/syncertest/fake/storage.go

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,18 @@ func (ms *MemoryStorage) listObjects(gk schema.GroupKind) []*unstructured.Unstru
305305
}
306306

307307
// Get an object from storage
308-
func (ms *MemoryStorage) Get(_ context.Context, gvk schema.GroupVersionKind, key client.ObjectKey, obj client.Object, opts *client.GetOptions) error {
308+
func (ms *MemoryStorage) Get(_ context.Context, key client.ObjectKey, obj client.Object, opts *client.GetOptions) error {
309309
ms.lock.RLock()
310310
defer ms.lock.RUnlock()
311311

312312
err := ms.validateGetOptions(opts)
313313
if err != nil {
314314
return err
315315
}
316+
gvk, err := kinds.Lookup(obj, ms.scheme)
317+
if err != nil {
318+
return err
319+
}
316320
id := core.ID{
317321
GroupKind: gvk.GroupKind(),
318322
ObjectKey: key,
@@ -326,17 +330,11 @@ func (ms *MemoryStorage) Get(_ context.Context, gvk schema.GroupVersionKind, key
326330
cachedObj.GetGeneration(), cachedObj.GetResourceVersion(),
327331
log.AsJSON(cachedObj))
328332

329-
// Convert to a typed object, optionally convert between versions
330-
tObj, err := kinds.ToTypedWithVersion(cachedObj, gvk, ms.scheme)
331-
if err != nil {
332-
return err
333+
// Convert to the requested type and version (DeepCopyInto the input object).
334+
if err := convertUnstructuredIntoObject(cachedObj, obj, ms.scheme); err != nil {
335+
return fmt.Errorf("MemoryStorage.Get: failed to update input object list: %w", err)
333336
}
334337

335-
// Convert from the typed object to whatever type the caller asked for.
336-
// If it's the same, it'll just do a DeepCopyInto.
337-
if err = ms.scheme.Convert(tObj, obj, nil); err != nil {
338-
return err
339-
}
340338
// TODO: Remove GVK from typed objects
341339
obj.GetObjectKind().SetGroupVersionKind(gvk)
342340

@@ -413,12 +411,11 @@ func (ms *MemoryStorage) List(_ context.Context, list client.ObjectList, opts *c
413411
uList.Items = append(uList.Items, *uObj)
414412
}
415413

416-
// Convert from the UnstructuredList to whatever type the caller asked for.
417-
// If it's the same, it'll just do a DeepCopyInto.
418-
err = ms.scheme.Convert(uList, list, nil)
419-
if err != nil {
420-
return err
414+
// Convert to the requested type and version (DeepCopyInto the input object).
415+
if err := convertUnstructuredListIntoObjectList(uList, list, ms.scheme); err != nil {
416+
return fmt.Errorf("MemoryStorage.List: failed to update input object list: %w", err)
421417
}
418+
422419
// TODO: Remove GVK from typed objects
423420
list.GetObjectKind().SetGroupVersionKind(listGVK)
424421

@@ -491,10 +488,12 @@ func (ms *MemoryStorage) Create(ctx context.Context, obj client.Object, opts *cl
491488
if err != nil {
492489
return err
493490
}
494-
// Copy everything back to input object, even if no diff
495-
if err := ms.scheme.Convert(cachedObj, obj, nil); err != nil {
496-
return fmt.Errorf("failed to update input object: %w", err)
491+
492+
// Convert to the requested type and version (DeepCopyInto the input object).
493+
if err := convertUnstructuredIntoObject(cachedObj, obj, ms.scheme); err != nil {
494+
return fmt.Errorf("MemoryStorage.Create: failed to update input object list: %w", err)
497495
}
496+
498497
// TODO: Remove GVK from typed objects
499498
obj.GetObjectKind().SetGroupVersionKind(cachedObj.GroupVersionKind())
500499
if diff {
@@ -769,10 +768,12 @@ func (ms *MemoryStorage) updateWithoutLock(ctx context.Context, obj client.Objec
769768
if err != nil {
770769
return err
771770
}
772-
// Copy everything back to input object, even if no diff
773-
if err := ms.scheme.Convert(cachedObj, obj, nil); err != nil {
774-
return fmt.Errorf("failed to update input object: %w", err)
771+
772+
// Convert to the requested type and version (DeepCopyInto the input object).
773+
if err := convertUnstructuredIntoObject(cachedObj, obj, ms.scheme); err != nil {
774+
return fmt.Errorf("MemoryStorage.Update: failed to update input object list: %w", err)
775775
}
776+
776777
// TODO: Remove GVK from typed objects
777778
obj.GetObjectKind().SetGroupVersionKind(cachedObj.GroupVersionKind())
778779
if diff {
@@ -944,10 +945,12 @@ func (ms *MemoryStorage) Patch(ctx context.Context, obj client.Object, patch cli
944945
if err != nil {
945946
return err
946947
}
947-
// Copy everything back to input object, even if no diff
948-
if err := ms.scheme.Convert(cachedObj, obj, nil); err != nil {
949-
return fmt.Errorf("failed to update input object:: %w", err)
948+
949+
// Convert to the requested type and version (DeepCopyInto the input object).
950+
if err := convertUnstructuredIntoObject(cachedObj, obj, ms.scheme); err != nil {
951+
return fmt.Errorf("MemoryStorage.Patch: failed to update input object list: %w", err)
950952
}
953+
951954
// TODO: Remove GVK from typed objects
952955
obj.GetObjectKind().SetGroupVersionKind(cachedObj.GroupVersionKind())
953956
if diff {

pkg/syncer/syncertest/fake/subresource_storage.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ func (ss *SubresourceStorage) Update(ctx context.Context, obj client.Object, opt
132132
}
133133
// Copy everything back to input object, even if no diff
134134
if err := ss.Storage.scheme.Convert(cachedObj, obj, nil); err != nil {
135-
return fmt.Errorf("failed to update input object: %w", err)
135+
return fmt.Errorf("SubresourceStorage.Update: failed to update input object list: %w", err)
136+
136137
}
137138
// TODO: Remove GVK from typed objects
138139
obj.GetObjectKind().SetGroupVersionKind(cachedObj.GroupVersionKind())

0 commit comments

Comments
 (0)