diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index b5684a7986..ca0f63912b 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -14,7 +14,9 @@ import unittest +import yaml from kubernetes import utils, client +from kubernetes.client.rest import ApiException from kubernetes.e2e_test import base @@ -48,8 +50,30 @@ def test_create_apps_deployment_from_yaml(self): dep = app_api.read_namespaced_deployment(name="nginx-app", namespace="default") self.assertIsNotNone(dep) + while True: + try: + app_api.delete_namespaced_deployment( + name="nginx-app", namespace="default", + body={}) + break + except ApiException: + continue + + def test_create_apps_deployment_from_yaml_obj(self): + k8s_client = client.api_client.ApiClient(configuration=self.config) + with open(self.path_prefix + "apps-deployment.yaml") as f: + yml_obj = yaml.safe_load(f) + + yml_obj["metadata"]["name"] = "nginx-app-3" + + utils.create_from_dict(k8s_client, yml_obj) + + app_api = client.AppsV1beta1Api(k8s_client) + dep = app_api.read_namespaced_deployment(name="nginx-app-3", + namespace="default") + self.assertIsNotNone(dep) app_api.delete_namespaced_deployment( - name="nginx-app", namespace="default", + name="nginx-app-3", namespace="default", body={}) def test_create_extensions_deployment_from_yaml(self): diff --git a/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml b/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml new file mode 100644 index 0000000000..b2acb92f82 --- /dev/null +++ b/kubernetes/e2e_test/test_yaml/apps-deployment-2.yaml @@ -0,0 +1,21 @@ +apiVersion: apps/v1beta1 +kind: Deployment +metadata: + name: nginx-app-2 + labels: + app: nginx +spec: + replicas: 3 + selector: + matchLabels: + app: nginx + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.15.4 + ports: + - containerPort: 80 diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 2b8597c2fb..72f55c7511 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -14,4 +14,5 @@ from __future__ import absolute_import -from .create_from_yaml import FailToCreateError, create_from_yaml +from .create_from_yaml import (FailToCreateError, create_from_dict, + create_from_yaml) diff --git a/kubernetes/utils/create_from_yaml.py b/kubernetes/utils/create_from_yaml.py index 3180671ae2..af2e2aa384 100644 --- a/kubernetes/utils/create_from_yaml.py +++ b/kubernetes/utils/create_from_yaml.py @@ -41,14 +41,6 @@ def create_from_yaml( the yaml file already contains a namespace definition this parameter has no effect. - Returns: - An k8s api object or list of apis objects created from YAML. - When a single object is generated, return type is dependent - on output_list. - - Throws a FailToCreateError exception if creation of any object - fails with helpful messages from the server. - Available parameters for creating : :param async_req bool :param bool include_uninitialized: If true, partially initialized @@ -59,47 +51,81 @@ def create_from_yaml( directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed - """ + Raises: + FailToCreateError which holds list of `client.rest.ApiException` + instances for each object that failed to create. + """ with open(path.abspath(yaml_file)) as f: yml_document_all = yaml.safe_load_all(f) - api_exceptions = [] - # Load all documents from a single YAML file + + failures = [] for yml_document in yml_document_all: - # If it is a list type, will need to iterate its items - if "List" in yml_document["kind"]: - # Could be "List" or "Pod/Service/...List" - # This is a list type. iterate within its items - kind = yml_document["kind"].replace("List", "") - for yml_object in yml_document["items"]: - # Mitigate cases when server returns a xxxList object - # See kubernetes-client/python#586 - if kind is not "": - yml_object["apiVersion"] = yml_document["apiVersion"] - yml_object["kind"] = kind - try: - create_from_yaml_single_item( - k8s_client, yml_object, verbose, namespace, **kwargs) - except client.rest.ApiException as api_exception: - api_exceptions.append(api_exception) - else: - # This is a single object. Call the single item method - try: - create_from_yaml_single_item( - k8s_client, yml_document, verbose, namespace, **kwargs) - except client.rest.ApiException as api_exception: - api_exceptions.append(api_exception) + try: + create_from_dict(k8s_client, yml_document, verbose, + namespace=namespace, + **kwargs) + except FailToCreateError as failure: + failures.extend(failure.api_exceptions) + if failures: + raise FailToCreateError(failures) + + +def create_from_dict(k8s_client, data, verbose=False, namespace='default', + **kwargs): + """ + Perform an action from a dictionary containing valid kubernetes + API object (i.e. List, Service, etc). + + Input: + k8s_client: an ApiClient object, initialized with the client args. + data: a dictionary holding valid kubernetes objects + verbose: If True, print confirmation from the create action. + Default is False. + namespace: string. Contains the namespace to create all + resources inside. The namespace must preexist otherwise + the resource creation will fail. If the API object in + the yaml file already contains a namespace definition + this parameter has no effect. + + Raises: + FailToCreateError which holds list of `client.rest.ApiException` + instances for each object that failed to create. + """ + # If it is a list type, will need to iterate its items + api_exceptions = [] + + if "List" in data["kind"]: + # Could be "List" or "Pod/Service/...List" + # This is a list type. iterate within its items + kind = data["kind"].replace("List", "") + for yml_object in data["items"]: + # Mitigate cases when server returns a xxxList object + # See kubernetes-client/python#586 + if kind is not "": + yml_object["apiVersion"] = data["apiVersion"] + yml_object["kind"] = kind + try: + create_from_yaml_single_item( + k8s_client, yml_object, verbose, namespace=namespace, + **kwargs) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + else: + # This is a single object. Call the single item method + try: + create_from_yaml_single_item( + k8s_client, data, verbose, namespace=namespace, **kwargs) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + # In case we have exceptions waiting for us, raise them if api_exceptions: raise FailToCreateError(api_exceptions) def create_from_yaml_single_item( - k8s_client, - yml_object, - verbose=False, - namespace="default", - **kwargs): + k8s_client, yml_object, verbose=False, **kwargs): group, _, version = yml_object["apiVersion"].partition("/") if version == "": version = group @@ -116,15 +142,17 @@ def create_from_yaml_single_item( kind = yml_object["kind"] kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind) kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower() - # Decide which namespace we are going to put the object in, - # if any - if "namespace" in yml_object["metadata"]: - namespace = yml_object["metadata"]["namespace"] # Expect the user to create namespaced objects more often if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + # Decide which namespace we are going to put the object in, + # if any + if "namespace" in yml_object["metadata"]: + namespace = yml_object["metadata"]["namespace"] + kwargs['namespace'] = namespace resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( - body=yml_object, namespace=namespace, **kwargs) + body=yml_object, **kwargs) else: + kwargs.pop('namespace', None) resp = getattr(k8s_api, "create_{0}".format(kind))( body=yml_object, **kwargs) if verbose: