From 66a45cd081b17041afd62712c5c213d310fa30b3 Mon Sep 17 00:00:00 2001 From: piglei Date: Sun, 22 Aug 2021 11:20:59 +0800 Subject: [PATCH] Make duck-typing in serialize_body method more restrictive --- dynamic/client.py | 7 ++++++- dynamic/test_client.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/dynamic/client.py b/dynamic/client.py index 9d32770..353a481 100644 --- a/dynamic/client.py +++ b/dynamic/client.py @@ -98,7 +98,12 @@ def ensure_namespace(self, resource, namespace, body): return namespace def serialize_body(self, body): - if hasattr(body, 'to_dict'): + """Serialize body to raw dict so apiserver can handle it + + :param body: kubernetes resource body, current support: Union[Dict, ResourceInstance] + """ + # This should match any `ResourceInstance` instances + if callable(getattr(body, 'to_dict', None)): return body.to_dict() return body or {} diff --git a/dynamic/test_client.py b/dynamic/test_client.py index 78be0cc..ab1df93 100644 --- a/dynamic/test_client.py +++ b/dynamic/test_client.py @@ -20,6 +20,7 @@ from kubernetes.client import api_client from . import DynamicClient +from .resource import ResourceInstance, ResourceField from .exceptions import ResourceNotFoundError @@ -392,3 +393,32 @@ def test_node_apis_partial_object_metadata(self): resp = api.get(**params) self.assertEqual('PartialObjectMetadataList', resp.kind) self.assertEqual('meta.k8s.io/v1', resp.apiVersion) + + +class TestDynamicClientSerialization(unittest.TestCase): + + @classmethod + def setUpClass(cls): + config = base.get_e2e_configuration() + cls.client = DynamicClient(api_client.ApiClient(configuration=config)) + cls.pod_manifest = { + 'apiVersion': 'v1', + 'kind': 'Pod', + 'metadata': {'name': 'foo-pod'}, + 'spec': {'containers': [{'name': "main", 'image': "busybox"}]}, + } + + def test_dict_type(self): + self.assertEqual(self.client.serialize_body(self.pod_manifest), self.pod_manifest) + + def test_resource_instance_type(self): + inst = ResourceInstance(self.client, self.pod_manifest) + self.assertEqual(self.client.serialize_body(inst), self.pod_manifest) + + def test_resource_field(self): + """`ResourceField` is a special type which overwrites `__getattr__` method to return `None` + when a non-existent attribute was accessed. which means it can pass any `hasattr(...)` tests. + """ + res = ResourceField(foo='bar') + # method will return original object when it doesn't know how to proceed + self.assertEqual(self.client.serialize_body(res), res)