diff --git a/changelogs/fragments/517-k8s-make-name-optional.yaml b/changelogs/fragments/517-k8s-make-name-optional.yaml new file mode 100644 index 00000000..e7ef01de --- /dev/null +++ b/changelogs/fragments/517-k8s-make-name-optional.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - k8s - add new option delete_all to support deletion of all resources when state is set to absent. (https://github.com/ansible-collections/kubernetes.core/issues/504) diff --git a/plugins/module_utils/k8s/runner.py b/plugins/module_utils/k8s/runner.py index 438b3211..49cbbb46 100644 --- a/plugins/module_utils/k8s/runner.py +++ b/plugins/module_utils/k8s/runner.py @@ -46,16 +46,51 @@ def validate(client, module, resource): return [_prepend_resource_info(resource, msg) for msg in warnings + errors] +def get_definitions(svc, params): + try: + definitions = create_definitions(params) + except Exception as e: + msg = "Failed to load resource definition: {0}".format(e) + raise CoreException(msg) from e + + delete_all = params.get("delete_all") + src = params.get("src") + resource_definition = params.get("resource_definition") + name = params.get("name") + state = params.get("state") + + if ( + delete_all + and state == "absent" + and name is None + and resource_definition is None + and src is None + ): + # Delete all resources in the namespace for the specified resource type + if params.get("kind") is None: + raise CoreException( + "'kind' option is required to specify the resource type." + ) + + resource = svc.find_resource( + params.get("kind"), params.get("api_version"), fail=True + ) + definitions = svc.retrieve_all( + resource, + params.get("namespace"), + params.get("label_selectors"), + ) + + return definitions + + def run_module(module) -> None: results = [] changed = False client = get_api_client(module) svc = K8sService(client, module) - try: - definitions = create_definitions(module.params) - except Exception as e: - msg = "Failed to load resource definition: {0}".format(e) - raise CoreException(msg) from e + + definitions = get_definitions(svc, module.params) for definition in definitions: result = {"changed": False, "result": {}} diff --git a/plugins/module_utils/k8s/service.py b/plugins/module_utils/k8s/service.py index 6a32f9a8..71d0a690 100644 --- a/plugins/module_utils/k8s/service.py +++ b/plugins/module_utils/k8s/service.py @@ -211,6 +211,30 @@ class K8sService: return existing + def retrieve_all( + self, resource: Resource, namespace: str, label_selectors: List[str] = None + ) -> List[Dict]: + definitions: List[ResourceInstance] = [] + + try: + params = dict(namespace=namespace) + if label_selectors: + params["label_selector"] = ",".join(label_selectors) + resource_list = self.client.get(resource, **params) + for item in resource_list.items: + existing = self.client.get( + resource, name=item.metadata.name, namespace=namespace + ) + definitions.append(existing.to_dict()) + except (NotFoundError, MethodNotAllowedError): + pass + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to retrieve requested object: {0}".format(reason) + raise CoreException(msg) from e + + return definitions + def find( self, kind: str, diff --git a/plugins/modules/k8s.py b/plugins/modules/k8s.py index 9b284d15..ebcf2136 100644 --- a/plugins/modules/k8s.py +++ b/plugins/modules/k8s.py @@ -172,6 +172,19 @@ options: - When set to True, server-side apply will force the changes against conflicts. type: bool default: False + delete_all: + description: + - When this option is set to I(true) and I(state=absent), + module will delete all resources of the specified resource type in the requested namespace. + - Ignored when C(state) is not set to I(absent) or when one of (src), + C(name) or C(resource_definition) is provided. + - Parameter C(kind) is required to use this option. + - This parameter can be used with C(label_selectors) to restrict the resources to be deleted. + type: bool + default: false + version_added: 2.5.0 + aliases: + - all requirements: - "python >= 3.6" @@ -343,6 +356,14 @@ EXAMPLES = r""" apply: yes server_side_apply: field_manager: ansible + +# Delete all Deployment from specified namespace +- name: Delete all Deployment from specified namespace + kubernetes.core.k8s: + api_version: apps/v1 + namespace: testing + kind: Deployment + delete_all: true """ RETURN = r""" @@ -450,6 +471,7 @@ def argspec(): argument_spec["server_side_apply"] = dict( type="dict", default=None, options=server_apply_spec() ) + argument_spec["delete_all"] = dict(type="bool", default=False, aliases=["all"]) return argument_spec diff --git a/tests/integration/targets/k8s_delete/files/deployments.yaml b/tests/integration/targets/k8s_delete/files/deployments.yaml new file mode 100644 index 00000000..594d4522 --- /dev/null +++ b/tests/integration/targets/k8s_delete/files/deployments.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-d + labels: + context: ansible +spec: + replicas: 1 + selector: + matchLabels: + context: ansible + template: + metadata: + labels: + context: ansible + spec: + containers: + - name: nginx + image: nginx + ports: + - containerPort: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openjdk-d + labels: + context: ansible +spec: + replicas: 2 + selector: + matchLabels: + context: ansible + template: + metadata: + labels: + context: ansible + spec: + containers: + - name: openjdk + image: openjdk:17 + command: + - /bin/sh + - -c + - while true;do date;sleep 5; done +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: alpine-d + labels: + context: ansible +spec: + replicas: 3 + selector: + matchLabels: + context: ansible + template: + metadata: + labels: + context: ansible + spec: + containers: + - name: alpine + image: alpine + command: + - /bin/sh + - -c + - while true;do date;sleep 5; done diff --git a/tests/integration/targets/k8s_delete/tasks/main.yml b/tests/integration/targets/k8s_delete/tasks/main.yml index 88edf59b..d7b144cb 100644 --- a/tests/integration/targets/k8s_delete/tasks/main.yml +++ b/tests/integration/targets/k8s_delete/tasks/main.yml @@ -121,6 +121,77 @@ that: - _result.resources | length == 0 + # test deletion using delete_all=true + - name: Create deployments + k8s: + namespace: "{{ test_namespace }}" + src: files/deployments.yaml + wait: true + register: result + + - name: Trying to delete deployments without name and label_selectors and select_all=false + k8s: + kind: Deployment + api_version: apps/v1 + namespace: "{{ test_namespace }}" + state: absent + register: _delete + + - name: Ensure Deployment were not deleted + assert: + that: + - _delete is not changed + + - name: Validate that Deployment still exist + k8s_info: + kind: Deployment + api_version: apps/v1 + namespace: "{{ test_namespace }}" + label_selectors: + - context=ansible + register: _deployment + failed_when: _deployment.resources | length == 0 + + - name: Trying to delete using delete_all=true but missing kind option + k8s: + api_version: apps/v1 + namespace: "{{ test_namespace }}" + delete_all: true + state: absent + register: _delete + ignore_errors: true + + - name: assert task failed with proper message + assert: + that: + - _delete is failed + - _delete.msg == "'kind' option is required to specify the resource type." + + - name: Trying to delete deployments without name and label_selectors and delete_all=true + k8s: + kind: Deployment + api_version: apps/v1 + namespace: "{{ test_namespace }}" + delete_all: true + wait: true + state: absent + register: _delete + + - name: Ensure Deployment were deleted + assert: + that: + - _delete is changed + + - name: Validate that Deployment do not exist anymore + k8s_info: + kind: Deployment + api_version: apps/v1 + namespace: "{{ test_namespace }}" + label_selectors: + - context=ansible + register: _deployment + failed_when: _deployment.resources | length > 0 + always: - name: Remove namespace k8s: diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 80122d80..ffd29612 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -590,3 +590,4 @@ tests/integration/targets/setup_kubeconfig/library/test_inventory_read_credentia tests/integration/targets/helm/library/helm_test_version.py compile-2.6!skip tests/integration/targets/helm/library/helm_test_version.py compile-2.7!skip tests/integration/targets/helm/library/helm_test_version.py compile-3.5!skip +tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 6cfa02f6..c0d1d589 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -30,3 +30,4 @@ plugins/modules/k8s.py validate-modules:return-syntax-error plugins/modules/k8s_scale.py validate-modules:return-syntax-error plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error +tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index 6cfa02f6..c0d1d589 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -30,3 +30,4 @@ plugins/modules/k8s.py validate-modules:return-syntax-error plugins/modules/k8s_scale.py validate-modules:return-syntax-error plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error +tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 6cfa02f6..c0d1d589 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -30,3 +30,4 @@ plugins/modules/k8s.py validate-modules:return-syntax-error plugins/modules/k8s_scale.py validate-modules:return-syntax-error plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error +tests/integration/targets/k8s_delete/files/deployments.yaml yamllint!skip diff --git a/tests/sanity/refresh_ignore_files b/tests/sanity/refresh_ignore_files index bdb01b8c..d06fb31e 100644 --- a/tests/sanity/refresh_ignore_files +++ b/tests/sanity/refresh_ignore_files @@ -48,6 +48,7 @@ YAML_LINT_SKIPS = [ "tests/integration/targets/helm/files/test-chart/templates/configmap.yaml", "tests/integration/targets/helm_diff/files/test-chart/templates/configmap.yaml", "tests/integration/targets/k8s_scale/files/deployment.yaml", + "tests/integration/targets/k8s_delete/files/deployments.yaml", ] # Add shebang!skip