diff --git a/kubetest/client.py b/kubetest/client.py index f19f48e..a8a9fd7 100644 --- a/kubetest/client.py +++ b/kubetest/client.py @@ -30,10 +30,20 @@ def __init__(self, namespace): self.namespace = namespace self.pre_registered = [] + self._api_client = None + + @property + def api_client(self): + """""" + return self._api_client + + @api_client.setter + def api_client(self, value): + self._api_client = value + # ****** Manifest Loaders ****** - @staticmethod - def load_clusterrolebinding(path): + def load_clusterrolebinding(self, path): """Load a manifest YAML into a ClusterRoleBinding object. Args: @@ -45,6 +55,10 @@ def load_clusterrolebinding(path): """ log.info('loading clusterrolebinding from path: %s', path) clusterrolebinding = objects.ClusterRoleBinding.load(path) + + # Use the api_client defined for the test client for the object + # wrapper it creates. + clusterrolebinding._client = self.api_client return clusterrolebinding def load_configmap(self, path, set_namespace=True): @@ -66,6 +80,10 @@ def load_configmap(self, path, set_namespace=True): configmap = objects.ConfigMap.load(path) if set_namespace: configmap.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + configmap._client = self.api_client return configmap def load_deployment(self, path, set_namespace=True): @@ -87,6 +105,10 @@ def load_deployment(self, path, set_namespace=True): deployment = objects.Deployment.load(path) if set_namespace: deployment.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + deployment._client = self.api_client return deployment def load_statefulset(self, path, set_namespace=True): @@ -108,6 +130,10 @@ def load_statefulset(self, path, set_namespace=True): statefulset = objects.StatefulSet.load(path) if set_namespace: statefulset.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + statefulset._client = self.api_client return statefulset def load_daemonset(self, path, set_namespace=True): @@ -129,6 +155,10 @@ def load_daemonset(self, path, set_namespace=True): daemonset = objects.DaemonSet.load(path) if set_namespace: daemonset.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + daemonset._client = self.api_client return daemonset def load_pod(self, path, set_namespace=True): @@ -150,6 +180,10 @@ def load_pod(self, path, set_namespace=True): pod = objects.Pod.load(path) if set_namespace: pod.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + pod._client = self.api_client return pod def load_rolebinding(self, path, set_namespace=True): @@ -171,6 +205,10 @@ def load_rolebinding(self, path, set_namespace=True): rolebinding = objects.RoleBinding.load(path) if set_namespace: rolebinding.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + rolebinding._client = self.api_client return rolebinding def load_secret(self, path, set_namespace=True): @@ -192,6 +230,10 @@ def load_secret(self, path, set_namespace=True): secret = objects.Secret.load(path) if set_namespace: secret.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + secret._client = self.api_client return secret def load_service(self, path, set_namespace=True): @@ -213,6 +255,10 @@ def load_service(self, path, set_namespace=True): service = objects.Service.load(path) if set_namespace: service.namespace = self.namespace + + # Use the api_client defined for the test client for the object + # wrapper it creates. + service._client = self.api_client return service # ****** Generic Helpers on ApiObjects ****** @@ -229,6 +275,14 @@ def create(self, obj): if obj.namespace is None: obj.namespace = self.namespace + # If the object which the TestClient is trying to create does not + # have its api client set, use the TestClient's api_client. This requires + # setting the client to None so it can be rebuilt with the TestClient's + # api_client. + if obj._client is None: + obj._api_client = None + obj._client = self.api_client + obj.create() def delete(self, obj, options=None): @@ -247,15 +301,30 @@ def delete(self, obj, options=None): if options is None: options = client.V1DeleteOptions() + # If the object which the TestClient is trying to create does not + # have its api client set, use the TestClient's api_client. This requires + # setting the client to None so it can be rebuilt with the TestClient's + # api_client. + if obj._client is None: + obj._api_client = None + obj._client = self.api_client + obj.delete(options=options) - @staticmethod - def refresh(obj): + def refresh(self, obj): """Refresh the underlying Kubernetes resource status and state. Args: obj (objects.ApiObject): A kubetest API Object wrapper. """ + # If the object which the TestClient is trying to create does not + # have its api client set, use the TestClient's api_client. This requires + # setting the client to None so it can be rebuilt with the TestClient's + # api_client. + if obj._client is None: + obj._api_client = None + obj._client = self.api_client + obj.refresh() # ****** General Helpers ****** @@ -277,13 +346,13 @@ def get_namespaces(self, fields=None, labels=None): """ selectors = utils.selector_kwargs(fields, labels) - namespace_list = client.CoreV1Api().list_namespace( + namespace_list = client.CoreV1Api(api_client=self.api_client).list_namespace( **selectors, ) namespaces = {} for obj in namespace_list.items: - namespace = objects.Namespace(obj) + namespace = objects.Namespace(obj, client=self.api_client) namespaces[namespace.name] = namespace return namespaces @@ -311,14 +380,14 @@ def get_deployments(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - deployment_list = client.AppsV1Api().list_namespaced_deployment( + deployment_list = client.AppsV1Api(api_client=self.api_client).list_namespaced_deployment( namespace=namespace, **selectors, ) deployments = {} for obj in deployment_list.items: - deployment = objects.Deployment(obj) + deployment = objects.Deployment(obj, client=self.api_client) deployments[deployment.name] = deployment return deployments @@ -346,14 +415,14 @@ def get_statefulsets(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - statefulset_list = client.AppsV1Api().list_namespaced_stateful_set( + statefulset_list = client.AppsV1Api(api_client=self.api_client).list_namespaced_stateful_set( namespace=namespace, **selectors, ) statefulsets = {} for obj in statefulset_list.items: - statefulset = objects.StatefulSet(obj) + statefulset = objects.StatefulSet(obj, client=self.api_client) statefulsets[statefulset.name] = statefulset return statefulsets @@ -381,14 +450,14 @@ def get_daemonsets(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - daemonset_list = client.AppsV1Api().list_namespaced_daemon_set( + daemonset_list = client.AppsV1Api(api_client=self.api_client).list_namespaced_daemon_set( namespace=namespace, **selectors, ) daemonsets = {} for obj in daemonset_list.items: - daemonset = objects.DaemonSet(obj) + daemonset = objects.DaemonSet(obj, client=self.api_client) daemonsets[daemonset.name] = daemonset return daemonsets @@ -416,14 +485,14 @@ def get_endpoints(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - endpoints_list = client.CoreV1Api().list_namespaced_endpoints( + endpoints_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_endpoints( namespace=namespace, **selectors, ) endpoints = {} for obj in endpoints_list.items: - endpoint = objects.Endpoints(obj) + endpoint = objects.Endpoints(obj, client=self.api_client) endpoints[endpoint.name] = endpoint return endpoints @@ -451,14 +520,14 @@ def get_secrets(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - secret_list = client.CoreV1Api().list_namespaced_secret( + secret_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_secret( namespace=namespace, **selectors, ) secrets = {} for obj in secret_list.items: - secret = objects.Secret(obj) + secret = objects.Secret(obj, client=self.api_client) secrets[secret.name] = secret return secrets @@ -486,14 +555,14 @@ def get_configmaps(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - configmap_list = client.CoreV1Api().list_namespaced_config_map( + configmap_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_config_map( namespace=namespace, **selectors, ) configmaps = {} for obj in configmap_list.items: - cm = objects.ConfigMap(obj) + cm = objects.ConfigMap(obj, client=self.api_client) configmaps[cm.name] = cm return configmaps @@ -521,14 +590,14 @@ def get_pods(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - pod_list = client.CoreV1Api().list_namespaced_pod( + pod_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_pod( namespace=namespace, **selectors, ) pods = {} for obj in pod_list.items: - pod = objects.Pod(obj) + pod = objects.Pod(obj, client=self.api_client) pods[pod.name] = pod return pods @@ -556,20 +625,19 @@ def get_services(self, namespace=None, fields=None, labels=None): selectors = utils.selector_kwargs(fields, labels) - service_list = client.CoreV1Api().list_namespaced_service( + service_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_service( namespace=namespace, **selectors, ) services = {} for obj in service_list.items: - service = objects.Service(obj) + service = objects.Service(obj, client=self.api_client) services[service.name] = service return services - @staticmethod - def get_nodes(fields=None, labels=None): + def get_nodes(self, fields=None, labels=None): """Get the Nodes that make up the cluster. Args: @@ -586,13 +654,13 @@ def get_nodes(fields=None, labels=None): """ selectors = utils.selector_kwargs(fields, labels) - node_list = client.CoreV1Api().list_node( + node_list = client.CoreV1Api(api_client=self.api_client).list_node( **selectors, ) nodes = {} for obj in node_list.items: - node = objects.Node(obj) + node = objects.Node(obj, client=self.api_client) nodes[node.name] = node return nodes @@ -617,11 +685,11 @@ def get_events(self, fields=None, labels=None, all_namespaces=False): selectors = utils.selector_kwargs(fields, labels) if all_namespaces: - event_list = client.CoreV1Api().list_event_for_all_namespaces( + event_list = client.CoreV1Api(api_client=self.api_client).list_event_for_all_namespaces( **selectors ) else: - event_list = client.CoreV1Api().list_namespaced_event( + event_list = client.CoreV1Api(api_client=self.api_client).list_namespaced_event( namespace=self.namespace, **selectors ) diff --git a/kubetest/objects/api_object.py b/kubetest/objects/api_object.py index 3867d05..27bc225 100644 --- a/kubetest/objects/api_object.py +++ b/kubetest/objects/api_object.py @@ -47,7 +47,7 @@ class ApiObject(abc.ABC): is not specified for the resource. ''' - def __init__(self, api_object): + def __init__(self, api_object, client=None): # The underlying Kubernetes Api Object self.obj = api_object @@ -55,6 +55,13 @@ def __init__(self, api_object): # by the apiVersion of the object's manifest. self._api_client = None + # The underlying client which is used by the api client, above. + # The naming of this is a bit confusing, but here the "api_client" + # can be thought of as the Kubernetes versioned client (e.g. AppsV1Api) + # whereas this "client" is the generic ApiClient which underlays the + # versioned client. + self._client = client + @property def version(self): """str: The API version of the Kubernetes object (`obj.apiVersion``).""" @@ -111,7 +118,7 @@ def api_client(self): 'defined for resource ({})'.format(self.version) ) # If we did find it, initialize that client version. - self._api_client = c() + self._api_client = c(api_client=self._client) return self._api_client def wait_until_ready(self, timeout=None, interval=1, fail_on_api_error=False): diff --git a/kubetest/plugin.py b/kubetest/plugin.py index 5be77a3..5169cec 100644 --- a/kubetest/plugin.py +++ b/kubetest/plugin.py @@ -297,11 +297,10 @@ def pytest_keyboard_interrupt(): # if the namespace has a 'kubetest-' prefix, remove it. name = ns.metadata.name status = ns.status - if ( - name.startswith('kubetest-') and - status is not None and - status.phase.lower() == 'active' - ): + if name.startswith('kubetest-') and \ + status is not None and \ + status.phase.lower() == 'active': + print('keyboard interrupt: cleaning up namespace "{}"'.format(name)) kubernetes.client.CoreV1Api().delete_namespace( body=kubernetes.client.V1DeleteOptions(), diff --git a/kubetest/utils.py b/kubetest/utils.py index 17b0c27..f543d39 100644 --- a/kubetest/utils.py +++ b/kubetest/utils.py @@ -35,7 +35,7 @@ def new_namespace(test_name): name_len = len(prefix) + len(timestamp) + len(test_name) + 2 if name_len > 63: - test_name = test_name[:-(name_len-63)] + test_name = test_name[:-(name_len - 63)] return '-'.join((prefix, test_name, timestamp)) diff --git a/setup.cfg b/setup.cfg index 079c3ae..2bdefbd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,4 +11,5 @@ license_file = LICENSE [flake8] max-line-length = 90 +ignore=E501 exclude = lib/,src/,docs/,bin/,.tox/ \ No newline at end of file diff --git a/tests/test_utils.py b/tests/test_utils.py index e32714e..2c045d6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -14,7 +14,7 @@ ('Test1_FOO-BAR_2', 'kubetest-test1-foo-bar-2-1536849367'), ('123456', 'kubetest-123456-1536849367'), ('___', 'kubetest-----1536849367'), - ('test-'*14, 'kubetest-test-test-test-test-test-test-test-test-tes-1536849367') + ('test-' * 14, 'kubetest-test-test-test-test-test-test-test-test-tes-1536849367') ] ) def test_new_namespace(name, expected):