Skip to content

Commit a28462e

Browse files
committed
fix: prevent double registration panic in fake client
- Check if GVK is already registered before adding to scheme - Prevents panic when consuming applications pre-register types as typed structs - Add test case for double registration scenario - Fixes issue where fake client tries to register unstructured types for GVKs already registered as typed structs
1 parent c64c9e9 commit a28462e

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-2
lines changed

client/fake/fake.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,16 @@ func registerCRDTypesWithScheme(scheme *runtime.Scheme, crds []*apiextensionsv1.
714714
}
715715

716716
// Register the types as unstructured for dynamic client compatibility
717-
scheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
718-
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
717+
// Check if the type is already registered to avoid conflicts with consuming
718+
// applications that may have pre-registered the same GVK with typed structs.
719+
// This prevents "Double registration of different types" panics when the same
720+
// GVK is registered both as a typed struct and as unstructured.Unstructured.
721+
if !scheme.Recognizes(gvk) {
722+
scheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
723+
}
724+
if !scheme.Recognizes(listGVK) {
725+
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
726+
}
719727

720728
// Add to GVR to ListKind mapping
721729
gvr := schema.GroupVersionResource{

client/fake/fake_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,3 +1734,109 @@ metadata:
17341734
require.NotNil(t, created)
17351735
})
17361736
}
1737+
1738+
func TestDoubleRegistrationWithTypedStruct(t *testing.T) {
1739+
// Create a test CRD
1740+
crd := &apiextensionsv1.CustomResourceDefinition{
1741+
ObjectMeta: metav1.ObjectMeta{
1742+
Name: "clusters.cockroach.cloud",
1743+
},
1744+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
1745+
Group: "cockroach.cloud",
1746+
Names: apiextensionsv1.CustomResourceDefinitionNames{
1747+
Kind: "Cluster",
1748+
Plural: "clusters",
1749+
Singular: "cluster",
1750+
},
1751+
Scope: apiextensionsv1.NamespaceScoped,
1752+
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
1753+
{
1754+
Name: "v1alpha1",
1755+
Served: true,
1756+
Storage: true,
1757+
Schema: &apiextensionsv1.CustomResourceValidation{
1758+
OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
1759+
Type: "object",
1760+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
1761+
"apiVersion": {Type: "string"},
1762+
"kind": {Type: "string"},
1763+
"metadata": {Type: "object"},
1764+
"spec": {
1765+
Type: "object",
1766+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
1767+
"name": {Type: "string"},
1768+
},
1769+
},
1770+
},
1771+
},
1772+
},
1773+
},
1774+
},
1775+
},
1776+
}
1777+
1778+
// Create scheme
1779+
scheme := runtime.NewScheme()
1780+
if err := kscheme.AddToScheme(scheme); err != nil {
1781+
panic(err)
1782+
}
1783+
1784+
// Pre-register the same GVK that the CRD will try to register
1785+
// This simulates the case where a consuming application has already
1786+
// registered types in the scheme before creating the fake client
1787+
gvk := schema.GroupVersionKind{
1788+
Group: "cockroach.cloud",
1789+
Version: "v1alpha1",
1790+
Kind: "Cluster",
1791+
}
1792+
listGVK := schema.GroupVersionKind{
1793+
Group: "cockroach.cloud",
1794+
Version: "v1alpha1",
1795+
Kind: "ClusterList",
1796+
}
1797+
1798+
// Register as unstructured types first (simulating pre-registration)
1799+
scheme.AddKnownTypeWithName(gvk, &unstructured.Unstructured{})
1800+
scheme.AddKnownTypeWithName(listGVK, &unstructured.UnstructuredList{})
1801+
1802+
// This should NOT panic due to double registration
1803+
// Our fix should prevent the fake client from trying to register the same GVK
1804+
// again when it's already registered in the scheme
1805+
client := NewFakeDynamicClientWithCRDs(scheme, []*apiextensionsv1.CustomResourceDefinition{crd})
1806+
1807+
// Test that the client works correctly
1808+
clusterGVR := schema.GroupVersionResource{
1809+
Group: "cockroach.cloud",
1810+
Version: "v1alpha1",
1811+
Resource: "clusters",
1812+
}
1813+
1814+
cluster := &unstructured.Unstructured{
1815+
Object: map[string]interface{}{
1816+
"apiVersion": "cockroach.cloud/v1alpha1",
1817+
"kind": "Cluster",
1818+
"metadata": map[string]interface{}{
1819+
"name": "test-cluster",
1820+
"namespace": "default",
1821+
},
1822+
"spec": map[string]interface{}{
1823+
"name": "my-cluster",
1824+
},
1825+
},
1826+
}
1827+
1828+
// Test Create operation
1829+
created, err := client.Resource(clusterGVR).Namespace("default").Create(
1830+
t.Context(), cluster, metav1.CreateOptions{},
1831+
)
1832+
require.NoError(t, err)
1833+
require.NotNil(t, created)
1834+
1835+
// Test Get operation
1836+
retrieved, err := client.Resource(clusterGVR).Namespace("default").Get(
1837+
t.Context(), "test-cluster", metav1.GetOptions{},
1838+
)
1839+
require.NoError(t, err)
1840+
require.NotNil(t, retrieved)
1841+
require.Equal(t, "test-cluster", retrieved.GetName())
1842+
}

0 commit comments

Comments
 (0)