Skip to content

Commit 5da3415

Browse files
authored
Merge pull request #78 from authzed/dfaketypefix
fix: convert typed objects to unstructured in dfake
2 parents d5b36f1 + c411a8c commit 5da3415

File tree

2 files changed

+194
-2
lines changed

2 files changed

+194
-2
lines changed

client/fake/fake.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,8 +492,40 @@ func newFakeClientWithSpec(scheme *runtime.Scheme, gvrToListKind map[schema.Grou
492492
// and custom types not in the schema
493493
fieldManagedTracker := clientgotesting.NewFieldManagedObjectTracker(scheme, decoder, typeConverter)
494494

495-
// Create upstream dynamic client with dynamic scheme
496-
upstreamClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(dynamicScheme, gvrToListKind, objects...)
495+
// Convert typed objects to unstructured before passing to upstream client
496+
// This ensures compatibility with the dynamic scheme that only knows about unstructured types
497+
unstructuredObjects := make([]runtime.Object, len(objects))
498+
for i, obj := range objects {
499+
if unstructuredObj, ok := obj.(*unstructured.Unstructured); ok {
500+
// Already unstructured, use as-is
501+
unstructuredObjects[i] = unstructuredObj
502+
} else {
503+
// Convert typed object to unstructured
504+
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
505+
if err != nil {
506+
panic(fmt.Sprintf("failed to convert object to unstructured: %v", err))
507+
}
508+
unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
509+
510+
// Set GVK from the original object
511+
gvk := obj.GetObjectKind().GroupVersionKind()
512+
if gvk.Empty() {
513+
// If GVK is empty, try to get it from the original scheme
514+
gvks, _, err := scheme.ObjectKinds(obj)
515+
if err != nil {
516+
panic(fmt.Sprintf("failed to get GVK for object: %v", err))
517+
}
518+
if len(gvks) > 0 {
519+
gvk = gvks[0]
520+
}
521+
}
522+
unstructuredObj.SetGroupVersionKind(gvk)
523+
unstructuredObjects[i] = unstructuredObj
524+
}
525+
}
526+
527+
// Create upstream dynamic client with dynamic scheme and unstructured objects
528+
upstreamClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(dynamicScheme, gvrToListKind, unstructuredObjects...)
497529

498530
// Add reactor to handle raw Apply Patch operations that bypass our Apply method
499531
upstreamClient.PrependReactor("patch", "*", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) {

client/fake/fake_test.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"testing"
1010

1111
"github.com/stretchr/testify/require"
12+
appsv1 "k8s.io/api/apps/v1"
13+
corev1 "k8s.io/api/core/v1"
1214
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1315
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1416
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -1841,6 +1843,164 @@ func TestDoubleRegistrationWithTypedStruct(t *testing.T) {
18411843
require.Equal(t, "test-cluster", retrieved.GetName())
18421844
}
18431845

1846+
func TestTypedObjectsSupport(t *testing.T) {
1847+
scheme := setupScheme()
1848+
1849+
// Create a typed Kubernetes object (Deployment)
1850+
typedDeployment := &appsv1.Deployment{
1851+
ObjectMeta: metav1.ObjectMeta{
1852+
Name: "test-deployment",
1853+
Namespace: "default",
1854+
},
1855+
Spec: appsv1.DeploymentSpec{
1856+
Replicas: ptr.To(int32(3)),
1857+
Selector: &metav1.LabelSelector{
1858+
MatchLabels: map[string]string{
1859+
"app": "test",
1860+
},
1861+
},
1862+
Template: corev1.PodTemplateSpec{
1863+
ObjectMeta: metav1.ObjectMeta{
1864+
Labels: map[string]string{
1865+
"app": "test",
1866+
},
1867+
},
1868+
Spec: corev1.PodSpec{
1869+
Containers: []corev1.Container{
1870+
{
1871+
Name: "test-container",
1872+
Image: "nginx:1.21",
1873+
},
1874+
},
1875+
},
1876+
},
1877+
},
1878+
}
1879+
1880+
// Create a typed ConfigMap
1881+
typedConfigMap := &corev1.ConfigMap{
1882+
ObjectMeta: metav1.ObjectMeta{
1883+
Name: "test-config",
1884+
Namespace: "default",
1885+
},
1886+
Data: map[string]string{
1887+
"key1": "value1",
1888+
"key2": "value2",
1889+
},
1890+
}
1891+
1892+
client := NewFakeDynamicClient(scheme, typedDeployment, typedConfigMap)
1893+
require.NotNil(t, client)
1894+
1895+
// Test that we can interact with the typed objects
1896+
deploymentGVR := schema.GroupVersionResource{
1897+
Group: "apps",
1898+
Version: "v1",
1899+
Resource: "deployments",
1900+
}
1901+
1902+
configMapGVR := schema.GroupVersionResource{
1903+
Group: "",
1904+
Version: "v1",
1905+
Resource: "configmaps",
1906+
}
1907+
1908+
// Test List operation
1909+
deploymentList, err := client.Resource(deploymentGVR).Namespace("default").List(t.Context(), metav1.ListOptions{})
1910+
require.NoError(t, err)
1911+
require.Len(t, deploymentList.Items, 1)
1912+
require.Equal(t, "test-deployment", deploymentList.Items[0].GetName())
1913+
1914+
configMapList, err := client.Resource(configMapGVR).Namespace("default").List(t.Context(), metav1.ListOptions{})
1915+
require.NoError(t, err)
1916+
require.Len(t, configMapList.Items, 1)
1917+
require.Equal(t, "test-config", configMapList.Items[0].GetName())
1918+
1919+
// Test Get operation
1920+
deployment, err := client.Resource(deploymentGVR).Namespace("default").Get(t.Context(), "test-deployment", metav1.GetOptions{})
1921+
require.NoError(t, err)
1922+
require.Equal(t, "test-deployment", deployment.GetName())
1923+
1924+
// Verify the deployment has the correct spec
1925+
replicas, found, err := unstructured.NestedInt64(deployment.Object, "spec", "replicas")
1926+
require.NoError(t, err)
1927+
require.True(t, found)
1928+
require.Equal(t, int64(3), replicas)
1929+
1930+
configMap, err := client.Resource(configMapGVR).Namespace("default").Get(t.Context(), "test-config", metav1.GetOptions{})
1931+
require.NoError(t, err)
1932+
require.Equal(t, "test-config", configMap.GetName())
1933+
1934+
// Verify the configmap has the correct data
1935+
data, found, err := unstructured.NestedStringMap(configMap.Object, "data")
1936+
require.NoError(t, err)
1937+
require.True(t, found)
1938+
require.Equal(t, "value1", data["key1"])
1939+
require.Equal(t, "value2", data["key2"])
1940+
}
1941+
1942+
func TestMixedTypedAndUnstructuredObjects(t *testing.T) {
1943+
scheme := setupScheme()
1944+
1945+
// Create a typed object
1946+
typedPod := &corev1.Pod{
1947+
ObjectMeta: metav1.ObjectMeta{
1948+
Name: "typed-pod",
1949+
Namespace: "default",
1950+
},
1951+
Spec: corev1.PodSpec{
1952+
Containers: []corev1.Container{
1953+
{
1954+
Name: "test-container",
1955+
Image: "nginx:1.21",
1956+
},
1957+
},
1958+
},
1959+
}
1960+
1961+
// Create an unstructured object
1962+
unstructuredConfigMap := &unstructured.Unstructured{
1963+
Object: map[string]interface{}{
1964+
"apiVersion": "v1",
1965+
"kind": "ConfigMap",
1966+
"metadata": map[string]interface{}{
1967+
"name": "unstructured-config",
1968+
"namespace": "default",
1969+
},
1970+
"data": map[string]interface{}{
1971+
"key": "value",
1972+
},
1973+
},
1974+
}
1975+
1976+
// This should handle both typed and unstructured objects correctly
1977+
client := NewFakeDynamicClient(scheme, typedPod, unstructuredConfigMap)
1978+
require.NotNil(t, client)
1979+
1980+
// Test that both objects are accessible
1981+
podGVR := schema.GroupVersionResource{
1982+
Group: "",
1983+
Version: "v1",
1984+
Resource: "pods",
1985+
}
1986+
1987+
configMapGVR := schema.GroupVersionResource{
1988+
Group: "",
1989+
Version: "v1",
1990+
Resource: "configmaps",
1991+
}
1992+
1993+
// Get the typed pod
1994+
pod, err := client.Resource(podGVR).Namespace("default").Get(t.Context(), "typed-pod", metav1.GetOptions{})
1995+
require.NoError(t, err)
1996+
require.Equal(t, "typed-pod", pod.GetName())
1997+
1998+
// Get the unstructured configmap
1999+
configMap, err := client.Resource(configMapGVR).Namespace("default").Get(t.Context(), "unstructured-config", metav1.GetOptions{})
2000+
require.NoError(t, err)
2001+
require.Equal(t, "unstructured-config", configMap.GetName())
2002+
}
2003+
18442004
func TestNewClientWithOptions(t *testing.T) {
18452005
scheme := setupScheme()
18462006

0 commit comments

Comments
 (0)