Skip to content

Add delete from yaml implementation #239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions kubernetes_asyncio/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
FailToCreateError, create_from_dict, create_from_yaml,
create_from_yaml_single_item,
)
from .delete_from_yaml import (
FailToDeleteError, delete_from_dict, delete_from_yaml,
delete_from_yaml_single_item,
)
190 changes: 190 additions & 0 deletions kubernetes_asyncio/utils/delete_from_yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# 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_asyncio import client

DEFAULT_DELETION_BODY = client.V1DeleteOptions(
propagation_policy="Background",
grace_period_seconds=5,
)


async def delete_from_yaml(
k8s_client,
yaml_file,
verbose=False,
namespace="default",
body=None,
**kwargs,
):
"""
Perform delete objects from yaml file. Pass True for verbose to
print confirmation information.
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.
body: V1 delete options
Raises:
FailToDeleteError which holds list of `client.rest.ApiException`
instances for each object that failed to delete.
"""
if body is None:
body = DEFAULT_DELETION_BODY

with open(path.abspath(yaml_file)) as f:
yml_document_all = yaml.safe_load_all(f)
failures = []
for yml_document in reversed(list(yml_document_all)):
try:
# call delete from dict function
await delete_from_dict(
k8s_client,
yml_document,
verbose,
namespace=namespace,
body=body,
**kwargs,
)
except FailToDeleteError as failure:
failures.extend(failure.api_exceptions)
if failures:
raise FailToDeleteError(failures)


async def delete_from_dict(
k8s_client,
yml_document,
verbose=False,
namespace="default",
body=None,
**kwargs,
):
if body is None:
body = DEFAULT_DELETION_BODY

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:
await delete_from_yaml_single_item(
k8s_client,
yml_doc,
verbose,
namespace=namespace,
body=body,
**kwargs,
)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)
else:

try:
await delete_from_yaml_single_item(
k8s_client,
yml_document,
verbose,
namespace=namespace,
body=body,
**kwargs,
)
except client.rest.ApiException as api_exception:
api_exceptions.append(api_exception)

if api_exceptions:
raise FailToDeleteError(api_exceptions)


async def delete_from_yaml_single_item(
k8s_client,
yml_document,
verbose=False,
namespace="default",
body=None,
**kwargs,
):
if body is None:
body = DEFAULT_DELETION_BODY

# 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"
# Only replace the last instance
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("."))
fcn_to_call = "{0}{1}Api".format(group, version.capitalize())
k8s_api = getattr(client, fcn_to_call)(k8s_client)
# Replace CamelCased action_type into snake_case
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()

# Decide which namespace we are going to use for deleteing the object
# IMPORTANT: Its ignore namespace in args:
# create_from_yaml_single_item have same behaviour
if "namespace" in yml_document["metadata"]:
namespace = yml_document["metadata"]["namespace"]
name = yml_document["metadata"]["name"]

# Expect the user to delete namespaced objects more often
if hasattr(k8s_api, "delete_namespaced_{0}".format(kind)):
resp: client.V1Status = await getattr(
k8s_api, "delete_namespaced_{0}".format(kind)
)(name=name, namespace=namespace, body=body, **kwargs)
else:
resp: client.V1Status = await getattr(k8s_api, "delete_{0}".format(kind))(
name=name, body=body, **kwargs
)
if verbose:
print("{0} deleted. status='{1}'".format(kind, str(resp.status)))
return resp


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
61 changes: 61 additions & 0 deletions kubernetes_asyncio/utils/delete_from_yaml_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2019 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.

from asynctest import CoroutineMock, TestCase

from kubernetes_asyncio.utils import delete_from_dict, delete_from_yaml


class DeleteFromYamlTest(TestCase):

async def test_delete_from_yaml(self):
api_client = CoroutineMock()
api_client.call_api = CoroutineMock()

await delete_from_yaml(api_client, 'examples/nginx-deployment.yaml')

# simple check for api call
self.assertEqual(api_client.call_api.call_args[0][0],
'/apis/apps/v1/namespaces/{namespace}/deployments/{name}')

self.assertEqual(api_client.call_api.call_args[0][1],
'DELETE')

self.assertEqual(api_client.call_api.call_args[0][2],
{'name': 'nginx-deployment', 'namespace': 'default'})

async def test_delete_from_dict(self):
api_client = CoroutineMock()
api_client.call_api = CoroutineMock()

await delete_from_dict(api_client, {
'apiVersion': 'apps/v1',
'kind': 'Deployment',
'metadata': {
'name': 'nginx-deployment'},
})

# simple check for api call
self.assertEqual(api_client.call_api.call_args[0][0],
'/apis/apps/v1/namespaces/{namespace}/deployments/{name}')
self.assertEqual(api_client.call_api.call_args[0][1],
'DELETE')

self.assertEqual(api_client.call_api.call_args[0][2],
{'name': 'nginx-deployment', 'namespace': 'default'})


if __name__ == '__main__':
import asynctest
asynctest.main()