Coverage for fake_kubernetes_client/dynamic_client.py: 60%

43 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 12:31 +0300

1"""FakeDynamicClient implementation for fake Kubernetes client""" 

2 

3from typing import Any, Union 

4 

5from fake_kubernetes_client.exceptions import NotFoundError 

6from fake_kubernetes_client.kubernetes_client import FakeKubernetesClient 

7from fake_kubernetes_client.resource_field import FakeResourceField 

8from fake_kubernetes_client.resource_manager import FakeResourceManager 

9from fake_kubernetes_client.resource_registry import FakeResourceRegistry 

10from fake_kubernetes_client.resource_storage import FakeResourceStorage 

11from fake_kubernetes_client.resource_instance import FakeResourceInstance 

12 

13 

14class FakeDynamicClient: 

15 """Fake implementation of kubernetes.dynamic.DynamicClient""" 

16 

17 def __init__(self, client: Union[FakeKubernetesClient, None] = None) -> None: 

18 # Distinguish between creating a new client vs using an existing one 

19 if client is None: 

20 # Create a new client with circular reference 

21 self.client = FakeKubernetesClient(dynamic_client=self) 

22 else: 

23 # Use the provided client without modifying its dynamic_client reference 

24 # This respects any intentional null or existing value 

25 self.client = client 

26 

27 self.configuration = self.client.configuration 

28 self.storage = FakeResourceStorage() 

29 self.registry = FakeResourceRegistry() 

30 self._resources_manager = FakeResourceManager(client=self) 

31 

32 @property 

33 def resources(self) -> FakeResourceManager: 

34 """Get the resource manager""" 

35 return self._resources_manager 

36 

37 def get_openapi_spec(self) -> dict[str, Any]: 

38 """Get OpenAPI spec (minimal fake implementation)""" 

39 return { 

40 "openapi": "3.0.0", 

41 "info": {"title": "Kubernetes", "version": "v1.29.0"}, 

42 "paths": {}, 

43 } 

44 

45 def request(self, method: str, path: str, body: Any = None, **kwargs: Any) -> FakeResourceField: 

46 """Make a raw request (fake implementation)""" 

47 # Minimal implementation - just return empty response 

48 return FakeResourceField(data={}) 

49 

50 def version(self) -> dict[str, Any]: 

51 """Get server version""" 

52 return { 

53 "major": "1", 

54 "minor": "29", 

55 "gitVersion": "v1.29.0", 

56 "gitCommit": "fake", 

57 "gitTreeState": "clean", 

58 "buildDate": "2024-01-01T00:00:00Z", 

59 "goVersion": "go1.21.0", 

60 "compiler": "gc", 

61 "platform": "linux/amd64", 

62 } 

63 

64 def ensure_namespace(self, namespace: str) -> FakeResourceField: 

65 """Ensure namespace exists (fake implementation)""" 

66 # Get the namespace resource definition 

67 ns_resource = self.resources.get(api_version="v1", kind="Namespace") 

68 

69 # Check if namespace exists 

70 try: 

71 return ns_resource.get(name=namespace) 

72 except NotFoundError: 

73 # Create namespace if it doesn't exist 

74 body = { 

75 "metadata": {"name": namespace}, 

76 "spec": {"finalizers": ["kubernetes"]}, 

77 } 

78 return ns_resource.create(body=body) 

79 

80 def api_client(self) -> FakeKubernetesClient: 

81 """Get the underlying API client""" 

82 return self.client 

83 

84 def register_resources(self, resources: Union[dict[str, Any], list[dict[str, Any]]]) -> None: 

85 """ 

86 Register custom resources dynamically. 

87 

88 This method allows you to add custom resource definitions to the fake client, 

89 which is useful for testing with Custom Resource Definitions (CRDs) or 

90 resources that are not included in the default schema. 

91 

92 Args: 

93 resources: Either a single resource definition dict or a list of resource definitions. 

94 Each resource definition should contain: 

95 - kind: Resource kind (required) 

96 - api_version: API version without group (required) 

97 - group: API group (optional, empty string for core resources) 

98 - namespaced: Whether resource is namespaced (optional, defaults to True) 

99 - plural: Plural name (optional, will be generated if not provided) 

100 - singular: Singular name (optional, defaults to lowercase kind) 

101 

102 Example: 

103 # Register a single custom resource 

104 client.register_resources({ 

105 "kind": "MyCustomResource", 

106 "api_version": "v1alpha1", 

107 "group": "example.com", 

108 "namespaced": True 

109 }) 

110 

111 # Register multiple resources 

112 client.register_resources([ 

113 { 

114 "kind": "MyApp", 

115 "api_version": "v1", 

116 "group": "apps.example.com", 

117 "namespaced": True, 

118 "plural": "myapps" 

119 }, 

120 { 

121 "kind": "MyConfig", 

122 "api_version": "v1beta1", 

123 "group": "config.example.com", 

124 "namespaced": False # cluster-scoped 

125 } 

126 ]) 

127 

128 # After registration, use the resources normally 

129 myapp_api = client.resources.get( 

130 api_version="apps.example.com/v1", 

131 kind="MyApp" 

132 ) 

133 myapp_api.create(body={...}, namespace="default") 

134 """ 

135 self.registry.register_resources(resources=resources) 

136 

137 def get(self, resource: Any, *args: Any, **kwargs: Any) -> Any: 

138 """Get resources based on resource definition""" 

139 # Extract resource definition from FakeResourceField if needed 

140 if hasattr(resource, "data"): 

141 resource_def = resource.data 

142 else: 

143 resource_def = resource 

144 

145 # Create a resource instance for this resource type 

146 resource_instance = FakeResourceInstance(resource_def=resource_def, storage=self.storage, client=self) 

147 

148 # Call get on the resource instance to list resources 

149 return resource_instance.get(*args, **kwargs)