From c638758031c5ea9d1b1c316e25591150d514dbe5 Mon Sep 17 00:00:00 2001 From: DiptoChakrabarty Date: Mon, 15 Mar 2021 23:34:36 +0530 Subject: [PATCH 1/3] delete from yaml --- kubernetes/e2e_test/test_utils.py | 264 +++++++++++++++++++++++++- kubernetes/utils/__init__.py | 4 +- kubernetes/utils/delete_from_yaml.py | 163 ++++++++++++++++ kubernetes/utils/operate_from_yaml.py | 230 ++++++++++++++++++++++ 4 files changed, 652 insertions(+), 9 deletions(-) create mode 100755 kubernetes/utils/delete_from_yaml.py create mode 100644 kubernetes/utils/operate_from_yaml.py diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 73d1a0c62..0ffd6fa99 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -14,7 +14,7 @@ import unittest from os import path - +import time import yaml from kubernetes import utils, client @@ -95,8 +95,8 @@ def test_create_apps_deployment_from_yaml_obj(self): yml_obj = yaml.safe_load(f) yml_obj["metadata"]["name"] = "nginx-app-3" - - utils.create_from_dict(k8s_client, yml_obj) + operation = "create" + utils.operate_from_dict(k8s_client, yml_obj , operation) app_api = client.AppsV1Api(k8s_client) dep = app_api.read_namespaced_deployment(name="nginx-app-3", @@ -211,7 +211,7 @@ def test_create_apiservice_from_yaml_with_conflict(self): svc = reg_api.read_api_service( name="v1alpha1.wardle.k8s.io") self.assertIsNotNone(svc) - with self.assertRaises(utils.FailToCreateError) as cm: + with self.assertRaises(utils.FailToExecuteError) as cm: utils.create_from_yaml( k8s_client, "kubernetes/e2e_test/test_yaml/api-service.yaml") exp_error = ('Error from server (Conflict): ' @@ -273,7 +273,7 @@ def test_create_implicit_service_list_from_yaml_with_conflict(self): json file that implicitly indicates the kind of individual objects """ k8s_client = client.api_client.ApiClient(configuration=self.config) - with self.assertRaises(utils.FailToCreateError): + with self.assertRaises(utils.FailToExecuteError): utils.create_from_yaml( k8s_client, self.path_prefix + "implicit-svclist.json") core_api = client.CoreV1Api(k8s_client) @@ -350,7 +350,7 @@ def test_create_from_multi_resource_yaml_with_conflict(self): svc = core_api.read_namespaced_service(name="mock-2", namespace="default") self.assertIsNotNone(svc) - with self.assertRaises(utils.FailToCreateError) as cm: + with self.assertRaises(utils.FailToExecuteError) as cm: utils.create_from_yaml( k8s_client, self.path_prefix + "yaml-conflict-multi.yaml") exp_error = ('Error from server (Conflict): {"kind":"Status",' @@ -375,7 +375,7 @@ def test_create_from_multi_resource_yaml_with_multi_conflicts(self): Should raise an exception that contains two error messages. """ k8s_client = client.api_client.ApiClient(configuration=self.config) - with self.assertRaises(utils.FailToCreateError) as cm: + with self.assertRaises(utils.FailToExecuteError) as cm: utils.create_from_yaml( k8s_client, self.path_prefix + "triple-nginx.yaml") exp_error = ('Error from server (Conflict): {"kind":"Status",' @@ -438,3 +438,253 @@ def test_create_from_list_in_multi_resource_yaml_namespaced(self): name="mock-pod-1", namespace=self.test_namespace, body={}) app_api.delete_namespaced_deployment( name="mock", namespace=self.test_namespace, body={}) + + + def test_delete_apps_deployment_from_yaml(self): + """ + Should delete a deployment + First create deployment from file and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "apps-deployment.yaml") + app_api = client.AppsV1Api(k8s_client) + dep = app_api.read_namespaced_deployment(name="nginx-app", + namespace="default") + self.assertIsNotNone(dep) + """ + Deployment should be created + Now delete deployment using delete_from_yaml method + """ + utils.delete_from_yaml(k8s_client, self.path_prefix + "apps-deployment.yaml") + deployment_status=False + try: + response=app_api.read_namespaced_deployment(name="nginx-app",namespace="default") + deployment_status=True + except Exception as e: + self.assertFalse(deployment_status) + + self.assertFalse(deployment_status) + + def test_delete_service_from_yaml(self): + """ + Should be able to delete a service + Create service from yaml first and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "core-service.yaml") + core_api = client.CoreV1Api(k8s_client) + svc = core_api.read_namespaced_service(name="my-service", + namespace="default") + self.assertIsNotNone(svc) + """ + Delete service from yaml + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "core-service.yaml") + service_status=False + try: + response = core_api.read_namespaced_service(name="my-service",namespace="default") + service_status=True + except Exception as e: + self.assertFalse(service_status) + self.assertFalse(service_status) + + def test_delete_namespace_from_yaml(self): + """ + Should be able to delete a namespace + Create namespace from file first and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + time.sleep(120) + utils.create_from_yaml( + k8s_client, self.path_prefix + "core-namespace.yaml") + core_api = client.CoreV1Api(k8s_client) + nmsp = core_api.read_namespace(name="development") + self.assertIsNotNone(nmsp) + """ + Delete namespace from yaml + """ + utils.delete_from_yaml(k8s_client, self.path_prefix + "core-namespace.yaml") + time.sleep(120) + namespace_status=False + try: + response=core_api.read_namespace(name="development") + namespace_status=True + except Exception as e: + self.assertFalse(namespace_status) + self.assertFalse(namespace_status) + + + def test_delete_pod_from_yaml(self): + """ + Should be able to delete pod + Create pod from file first and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + time.sleep(120) + utils.create_from_yaml( + k8s_client, self.path_prefix + "core-pod.yaml") + core_api = client.CoreV1Api(k8s_client) + pod = core_api.read_namespaced_pod(name="myapp-pod", + namespace="default") + self.assertIsNotNone(pod) + """ + Delete pod using delete_from_yaml + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "core-pod.yaml") + time.sleep(120) + pod_status=False + try: + response = core_api.read_namespaced_pod(name="myapp-pod", + namespace="default") + pod_status=True + except Exception as e: + self.assertFalse(pod_status) + self.assertFalse(pod_status) + + + def test_delete_rbac_role_from_yaml(self): + """ + Should be able to delete rbac role + Create rbac role from file first and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "rbac-role.yaml") + rbac_api = client.RbacAuthorizationV1Api(k8s_client) + rbac_role = rbac_api.read_namespaced_role( + name="pod-reader", namespace="default") + self.assertIsNotNone(rbac_role) + """ + Delete rbac role from yaml + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "rbac-role.yaml") + rbac_role_status=False + try: + response = rbac_api.read_namespaced_role( + name="pod-reader", namespace="default") + rbac_role_status=True + except Exception as e: + self.assertFalse(rbac_role_status) + self.assertFalse(rbac_role_status) + + def test_delete_rbac_role_from_yaml_with_verbose_enabled(self): + """ + Should delete a rbac role with verbose enabled + Create rbac role with verbose enabled and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "rbac-role.yaml", verbose=True) + rbac_api = client.RbacAuthorizationV1Api(k8s_client) + rbac_role = rbac_api.read_namespaced_role( + name="pod-reader", namespace="default") + self.assertIsNotNone(rbac_role) + """ + Delete the rbac role from yaml + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "rbac-role.yaml", verbose=True) + + rbac_role_status=False + try: + response=rbac_api.read_namespaced_role( + name="pod-reader", namespace="default") + rbac_role_status=True + except Exception as e: + self.assertFalse(rbac_role_status) + self.assertFalse(rbac_role_status) + + + # Deletion Tests for multi resource objects in yaml files + + def test_delete_from_multi_resource_yaml(self): + """ + Should be able to delete service and replication controller + from the multi resource yaml files + Create the resources first and ensure they exist + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + utils.create_from_yaml( + k8s_client, self.path_prefix + "multi-resource.yaml") + core_api = client.CoreV1Api(k8s_client) + svc = core_api.read_namespaced_service(name="mock", + namespace="default") + self.assertIsNotNone(svc) + ctr = core_api.read_namespaced_replication_controller( + name="mock", namespace="default") + self.assertIsNotNone(ctr) + """ + Delete service and replication controller using yaml file + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "multi-resource.yaml") + svc_status=False + replication_status=False + try: + resp_svc= core_api.read_namespaced_service(name="mock", + namespace="default") + svc_status=True + resp_repl= core_api.read_namespaced_replication_controller( + name="mock", namespace="default") + repl_status = True + except Exception as e: + self.assertFalse(svc_status) + self.assertFalse(replication_status) + self.assertFalse(svc_status) + self.assertFalse(replication_status) + + + + + + def test_delete_from_list_in_multi_resource_yaml(self): + """ + Should delete the items in PodList and the deployment in the yaml file + Create the items first and ensure they exist + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + time.sleep(120) + utils.create_from_yaml( + k8s_client, self.path_prefix + "multi-resource-with-list.yaml") + core_api = client.CoreV1Api(k8s_client) + app_api = client.AppsV1Api(k8s_client) + pod_0 = core_api.read_namespaced_pod( + name="mock-pod-0", namespace="default") + self.assertIsNotNone(pod_0) + pod_1 = core_api.read_namespaced_pod( + name="mock-pod-1", namespace="default") + self.assertIsNotNone(pod_1) + dep = app_api.read_namespaced_deployment( + name="mock", namespace="default") + self.assertIsNotNone(dep) + """ + Delete the PodList and Deployment using the yaml file + """ + utils.delete_from_yaml( + k8s_client, self.path_prefix + "multi-resource-with-list.yaml") + time.sleep(120) + pod0_status=False + pod1_status=False + deploy_status=False + try: + core_api.read_namespaced_pod( + name="mock-pod-0", namespace="default") + core_api.read_namespaced_pod( + name="mock-pod-1", namespace="default") + app_api.read_namespaced_deployment( + name="mock", namespace="default") + pod0_status=True + pod1_status=True + deploy_status=True + except Exception as e: + self.assertFalse(pod0_status) + self.assertFalse(pod1_status) + self.assertFalse(deploy_status) + self.assertFalse(pod0_status) + self.assertFalse(pod1_status) + self.assertFalse(deploy_status) diff --git a/kubernetes/utils/__init__.py b/kubernetes/utils/__init__.py index 8add80bcf..db32005b0 100644 --- a/kubernetes/utils/__init__.py +++ b/kubernetes/utils/__init__.py @@ -14,6 +14,6 @@ from __future__ import absolute_import -from .create_from_yaml import (FailToCreateError, create_from_dict, - create_from_yaml) +from .operate_from_yaml import (FailToExecuteError, create_from_yaml, + delete_from_yaml, operate_from_dict) from .quantity import parse_quantity diff --git a/kubernetes/utils/delete_from_yaml.py b/kubernetes/utils/delete_from_yaml.py new file mode 100755 index 000000000..3f74e3c03 --- /dev/null +++ b/kubernetes/utils/delete_from_yaml.py @@ -0,0 +1,163 @@ +# Copyright 2018 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re +from os import path + +import yaml + +from kubernetes import client + + +def delete_from_yaml(k8s_client, yaml_file, verbose=False, + namespace="default", **kwargs): + """ + Delete a resource from a yaml file. + Input: + yaml_file: string. Contains the path to yaml file. + k8s_client: an ApiClient object, initialized with the client args. + 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. + Available parameters for creating : + :param async_req bool + :param str pretty: If 'true', then the output is pretty printed. + :param str dry_run: When present, indicates that modifications + should not be persisted. An invalid or unrecognized dryRun + 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: + FailToDeleteError which holds list of `client.rest.ApiException` + instances for each object that failed to delete. + """ + with open(path.abspath(yaml_file)) as f: + yml_document_all = yaml.safe_load_all(f) + failures = [] + for yml_document in yml_document_all: + try: + delete_from_dict(k8s_client, yml_document, verbose, + namespace=namespace, **kwargs) + except FailToDeleteError as failure: + failures.extend(failure.api_exceptions) + if failures: + raise FailToDeleteError(failures) + + +def delete_from_dict(k8s_client, yml_document, verbose, + namespace="default", **kwargs): + """ + Delete a kubernetes resource from a dictionary containing valid kubernetes + API object (i.e. List, Service, etc). + Input: + k8s_client: an ApiClient object, initialized with the client args. + yml_document: 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: + FailToDeleteError which holds list of `client.rest.ApiException` + instances for each object that failed to create. + """ + api_exceptions = [] + if "List" in yml_document["kind"]: + kind = yml_document["kind"].replace("List", "") + for yml_doc in yml_document["items"]: + if kind != "": + yml_doc["apiVersion"] = yml_document["apiVersion"] + yml_doc["kind"] = kind + try: + delete_from_yaml_single_item( + k8s_client, yml_doc, verbose, namespace=namespace, **kwargs + ) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + else: + try: + delete_from_yaml_single_item( + k8s_client, yml_document, verbose, + namespace=namespace, **kwargs + ) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + + if api_exceptions: + raise FailToDeleteError(api_exceptions) + + +def delete_from_yaml_single_item(k8s_client, + yml_document, verbose=False, **kwargs): + # get group and version from apiVersion + group, _, version = yml_document["apiVersion"].partition("/") + if version == "": + version = group + group = "core" + # Take care for the case e.g. api_type is "apiextensions.k8s.io" + group = "".join(group.rsplit(".k8s.io", 1)) + # convert group name from DNS subdomain format to + # python class name convention + group = "".join(word.capitalize() for word in group.split('.')) + func = "{0}{1}Api".format(group, version.capitalize()) + k8s_api = getattr(client, func)(k8s_client) + kind = yml_document["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() + if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + if "namespace" in yml_document["metadata"]: + namespace = yml_document["metadata"]["namespace"] + kwargs["namespace"] = namespace + name = yml_document["metadata"]["name"] + res = getattr(k8s_api, "delete_namespaced_{}".format(kind))( + name=name, + body=client.V1DeleteOptions(propagation_policy="Background", + grace_period_seconds=5), **kwargs) + else: + # get name of object to delete + name = yml_document["metadata"]["name"] + kwargs.pop('namespace', None) + res = getattr(k8s_api, "delete_{}".format(kind))( + name=name, + body=client.V1DeleteOptions(propagation_policy="Background", + grace_period_seconds=5), **kwargs) + if verbose: + msg = "{0} deleted.".format(kind) + if hasattr(res, 'status'): + msg += " status='{0}'".format(str(res.status)) + print(msg) + + +class FailToDeleteError(Exception): + """ + An exception class for handling error if an error occurred when + handling a yaml file during deletion of the resource. + """ + + def __init__(self, api_exceptions): + self.api_exceptions = api_exceptions + + def __str__(self): + msg = "" + for api_exception in self.api_exceptions: + msg += "Error from server ({0}):{1}".format( + api_exception.reason, api_exception.body) + return msg diff --git a/kubernetes/utils/operate_from_yaml.py b/kubernetes/utils/operate_from_yaml.py new file mode 100644 index 000000000..76b11c6c7 --- /dev/null +++ b/kubernetes/utils/operate_from_yaml.py @@ -0,0 +1,230 @@ +# Copyright 2018 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import re +from os import path + +import yaml + +from kubernetes import client + + +def create_from_yaml(k8s_client, yaml_file,yaml_objects=None ,verbose=False, + namespace="default", **kwargs): + operation = "create" + operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects,verbose=False, + namespace="default", **kwargs) + + +def delete_from_yaml(k8s_client, yaml_file,yaml_objects=None ,verbose=False, + namespace="default", **kwargs): + operation = "delete" + operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects,verbose=False, + namespace="default", **kwargs) + + +def operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects=None,verbose=False, + namespace="default", **kwargs): + """ + Input: + yaml_file: string. Contains the path to yaml file. + k8s_client: an ApiClient object, initialized with the client args. + 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. + Available parameters for creating : + :param async_req bool + :param str pretty: If 'true', then the output is pretty printed. + :param str dry_run: When present, indicates that modifications + should not be persisted. An invalid or unrecognized dryRun + 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: + FailToExecuteError which holds list of `client.rest.ApiException` + instances for each object that failed to delete. + """ + def create_with(objects): + failures = [] + k8s_objects = [] + for yml_document in objects: + if yml_document is None: + continue + try: + created = operate_from_dict(k8s_client, yml_document,operation, verbose, + namespace=namespace, + **kwargs) + k8s_objects.append(created) + except FailToExecuteError as failure: + failures.extend(failure.api_exceptions) + if failures: + raise FailToExecuteError(failures) + return k8s_objects + if yaml_objects: + yml_document_all = yaml_objects + return create_with(yml_document_all) + elif yaml_file: + with open(path.abspath(yaml_file)) as f: + yml_document_all = yaml.safe_load_all(f) + return create_with(yml_document_all) + else: + raise ValueError( + 'One of `yaml_file` or `yaml_objects` arguments must be provided') + +def operate_from_dict(k8s_client, yml_document, operation, verbose, + namespace="default", **kwargs): + """ + Perform an operation kubernetes resource from a dictionary containing valid kubernetes + API object (i.e. List, Service, etc). + Input: + k8s_client: an ApiClient object, initialized with the client args. + yml_document: 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: + FailToExecuteError which holds list of `client.rest.ApiException` + instances for each object that failed to create. + """ + api_exceptions = [] + if "List" in yml_document["kind"]: + kind = yml_document["kind"].replace("List", "") + for yml_doc in yml_document["items"]: + if kind != "": + yml_doc["apiVersion"] = yml_document["apiVersion"] + yml_doc["kind"] = kind + try: + operate_from_yaml_single_item( + k8s_client, + yml_doc, + operation, + verbose, + namespace=namespace, + **kwargs) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + else: + try: + operate_from_yaml_single_item( + k8s_client, yml_document, operation, verbose, + namespace=namespace, **kwargs + ) + except client.rest.ApiException as api_exception: + api_exceptions.append(api_exception) + + if api_exceptions: + raise FailToExecuteError(api_exceptions) + + +def operate_from_yaml_single_item( + k8s_client, + yml_document, + operation, + verbose=False, + namespace="default", + **kwargs): + # get group and version from apiVersion + group, _, version = yml_document["apiVersion"].partition("/") + if version == "": + version = group + group = "core" + # Take care for the case e.g. api_type is "apiextensions.k8s.io" + group = "".join(group.rsplit(".k8s.io", 1)) + # convert group name from DNS subdomain format to + # python class name convention + group = "".join(word.capitalize() for word in group.split('.')) + func = "{0}{1}Api".format(group, version.capitalize()) + k8s_api = getattr(client, func)(k8s_client) + kind = yml_document["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() + if operation == "create": + resp = create_k8s_object( + k8s_api, yml_document, kind, namespace=namespace) + if verbose: + msg = "{0} created.".format(kind) + if hasattr(resp, 'status'): + msg += " status='{0}'".format(str(resp.status)) + print(msg) + elif operation == "delete": + resp = delete_k8s_object( + k8s_api, yml_document, kind, namespace=namespace) + if verbose: + msg = "{0} deleted.".format(kind) + if hasattr(resp, 'status'): + msg += " status='{0}'".format(str(resp.status)) + print(msg) + + +def create_k8s_object(k8s_api, yml_document, kind, **kwargs): + + if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + if "namespace" in yml_document["metadata"]: + namespace = yml_document["metadata"]["namespace"] + kwargs['namespace'] = namespace + resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))( + body=yml_document, **kwargs) + else: + kwargs.pop('namespace', None) + resp = getattr(k8s_api, "create_{0}".format(kind))( + body=yml_document, **kwargs) + return resp + + +def delete_k8s_object(k8s_api, yml_document, kind, **kwargs): + + if hasattr(k8s_api, "create_namespaced_{0}".format(kind)): + if "namespace" in yml_document["metadata"]: + namespace = yml_document["metadata"]["namespace"] + kwargs["namespace"] = namespace + name = yml_document["metadata"]["name"] + resp = getattr(k8s_api, "delete_namespaced_{}".format(kind))( + name=name, + body=client.V1DeleteOptions(propagation_policy="Background", + grace_period_seconds=5), **kwargs) + else: + # get name of object to delete + name = yml_document["metadata"]["name"] + kwargs.pop('namespace', None) + resp = getattr(k8s_api, "delete_{}".format(kind))( + name=name, + body=client.V1DeleteOptions(propagation_policy="Background", + grace_period_seconds=5), **kwargs) + return resp + + +class FailToExecuteError(Exception): + """ + An exception class for handling error if an error occurred when + handling a yaml file during creation or deletion of the resource. + """ + + def __init__(self, api_exceptions): + self.api_exceptions = api_exceptions + + def __str__(self): + msg = "" + for api_exception in self.api_exceptions: + msg += "Error from server ({0}):{1}".format( + api_exception.reason, api_exception.body) + return msg From 4d091efe009aa0d24a8c92d89ca5c27e95b8817c Mon Sep 17 00:00:00 2001 From: DiptoChakrabarty Date: Wed, 25 Aug 2021 23:51:47 +0530 Subject: [PATCH 2/3] fix tests --- kubernetes/e2e_test/test_utils.py | 55 ++++++++++++++------------- kubernetes/utils/operate_from_yaml.py | 12 +++--- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/kubernetes/e2e_test/test_utils.py b/kubernetes/e2e_test/test_utils.py index 0ffd6fa99..5974fa9aa 100644 --- a/kubernetes/e2e_test/test_utils.py +++ b/kubernetes/e2e_test/test_utils.py @@ -439,6 +439,30 @@ def test_create_from_list_in_multi_resource_yaml_namespaced(self): app_api.delete_namespaced_deployment( name="mock", namespace=self.test_namespace, body={}) + def test_delete_namespace_from_yaml(self): + """ + Should be able to delete a namespace + Create namespace from file first and ensure it is created + """ + k8s_client = client.api_client.ApiClient(configuration=self.config) + time.sleep(120) + utils.create_from_yaml( + k8s_client, self.path_prefix + "core-namespace.yaml") + core_api = client.CoreV1Api(k8s_client) + nmsp = core_api.read_namespace(name="development") + self.assertIsNotNone(nmsp) + """ + Delete namespace from yaml + """ + utils.delete_from_yaml(k8s_client, self.path_prefix + "core-namespace.yaml") + time.sleep(120) + namespace_status=False + try: + response=core_api.read_namespace(name="development") + namespace_status=True + except Exception as e: + self.assertFalse(namespace_status) + self.assertFalse(namespace_status) def test_delete_apps_deployment_from_yaml(self): """ @@ -458,6 +482,7 @@ def test_delete_apps_deployment_from_yaml(self): """ utils.delete_from_yaml(k8s_client, self.path_prefix + "apps-deployment.yaml") deployment_status=False + time.sleep(120) try: response=app_api.read_namespaced_deployment(name="nginx-app",namespace="default") deployment_status=True @@ -484,38 +509,13 @@ def test_delete_service_from_yaml(self): utils.delete_from_yaml( k8s_client, self.path_prefix + "core-service.yaml") service_status=False + time.sleep(120) try: response = core_api.read_namespaced_service(name="my-service",namespace="default") service_status=True except Exception as e: self.assertFalse(service_status) self.assertFalse(service_status) - - def test_delete_namespace_from_yaml(self): - """ - Should be able to delete a namespace - Create namespace from file first and ensure it is created - """ - k8s_client = client.api_client.ApiClient(configuration=self.config) - time.sleep(120) - utils.create_from_yaml( - k8s_client, self.path_prefix + "core-namespace.yaml") - core_api = client.CoreV1Api(k8s_client) - nmsp = core_api.read_namespace(name="development") - self.assertIsNotNone(nmsp) - """ - Delete namespace from yaml - """ - utils.delete_from_yaml(k8s_client, self.path_prefix + "core-namespace.yaml") - time.sleep(120) - namespace_status=False - try: - response=core_api.read_namespace(name="development") - namespace_status=True - except Exception as e: - self.assertFalse(namespace_status) - self.assertFalse(namespace_status) - def test_delete_pod_from_yaml(self): """ @@ -564,6 +564,7 @@ def test_delete_rbac_role_from_yaml(self): utils.delete_from_yaml( k8s_client, self.path_prefix + "rbac-role.yaml") rbac_role_status=False + time.sleep(120) try: response = rbac_api.read_namespaced_role( name="pod-reader", namespace="default") @@ -591,6 +592,7 @@ def test_delete_rbac_role_from_yaml_with_verbose_enabled(self): k8s_client, self.path_prefix + "rbac-role.yaml", verbose=True) rbac_role_status=False + time.sleep(120) try: response=rbac_api.read_namespaced_role( name="pod-reader", namespace="default") @@ -625,6 +627,7 @@ def test_delete_from_multi_resource_yaml(self): k8s_client, self.path_prefix + "multi-resource.yaml") svc_status=False replication_status=False + time.sleep(120) try: resp_svc= core_api.read_namespaced_service(name="mock", namespace="default") diff --git a/kubernetes/utils/operate_from_yaml.py b/kubernetes/utils/operate_from_yaml.py index 76b11c6c7..3dc0c5749 100644 --- a/kubernetes/utils/operate_from_yaml.py +++ b/kubernetes/utils/operate_from_yaml.py @@ -21,21 +21,21 @@ from kubernetes import client -def create_from_yaml(k8s_client, yaml_file,yaml_objects=None ,verbose=False, +def create_from_yaml(k8s_client, yaml_file=None,yaml_objects=None ,verbose=False, namespace="default", **kwargs): operation = "create" operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects,verbose=False, namespace="default", **kwargs) -def delete_from_yaml(k8s_client, yaml_file,yaml_objects=None ,verbose=False, +def delete_from_yaml(k8s_client, yaml_file=None,yaml_objects=None ,verbose=False, namespace="default", **kwargs): operation = "delete" operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects,verbose=False, namespace="default", **kwargs) -def operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects=None,verbose=False, +def operate_from_yaml(k8s_client, yaml_file=None, operation=None,yaml_objects=None,verbose=False, namespace="default", **kwargs): """ Input: @@ -60,7 +60,7 @@ def operate_from_yaml(k8s_client, yaml_file, operation,yaml_objects=None,verbose FailToExecuteError which holds list of `client.rest.ApiException` instances for each object that failed to delete. """ - def create_with(objects): + def operate_with(objects): failures = [] k8s_objects = [] for yml_document in objects: @@ -78,11 +78,11 @@ def create_with(objects): return k8s_objects if yaml_objects: yml_document_all = yaml_objects - return create_with(yml_document_all) + return operate_with(yml_document_all) elif yaml_file: with open(path.abspath(yaml_file)) as f: yml_document_all = yaml.safe_load_all(f) - return create_with(yml_document_all) + return operate_with(yml_document_all) else: raise ValueError( 'One of `yaml_file` or `yaml_objects` arguments must be provided') From 3856bf515d91d67aa74e1953fa010f80f00a5a42 Mon Sep 17 00:00:00 2001 From: Dipto Chakrabarty <45638240+DiptoChakrabarty@users.noreply.github.com> Date: Tue, 21 Dec 2021 23:52:21 +0530 Subject: [PATCH 3/3] fix formats and updates --- kubernetes/utils/operate_from_yaml.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kubernetes/utils/operate_from_yaml.py b/kubernetes/utils/operate_from_yaml.py index 3dc0c5749..0225c6e14 100644 --- a/kubernetes/utils/operate_from_yaml.py +++ b/kubernetes/utils/operate_from_yaml.py @@ -1,4 +1,4 @@ -# Copyright 2018 The Kubernetes Authors. +# Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,10 +14,9 @@ import re -from os import path - import yaml +from os import path from kubernetes import client