Skip to content

Commit

Permalink
k8s - support select_all for state=absent and state=patched
Browse files Browse the repository at this point in the history
  • Loading branch information
abikouo committed Oct 17, 2022
1 parent 2092d92 commit ff92a01
Show file tree
Hide file tree
Showing 14 changed files with 418 additions and 83 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/517-k8s-make-name-optional.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
bugfixes:
- k8s - make name optional when trying to delete or patch all resources from namespace. (https://github.com/ansible-collections/kubernetes.core/issues/504)
33 changes: 33 additions & 0 deletions plugins/module_utils/k8s/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ansible_collections.kubernetes.core.plugins.module_utils.selector import (
LabelSelectorFilter,
)
from ansible.module_utils.common.dict_transformations import dict_merge


def validate(client, module, resource):
Expand Down Expand Up @@ -57,6 +58,38 @@ def run_module(module) -> None:
msg = "Failed to load resource definition: {0}".format(e)
raise CoreException(msg) from e

select_all = module.params.get("select_all")
src = module.params.get("src")
resource_definition = module.params.get("resource_definition")
name = module.params.get("name")
state = module.params.get("state")
if (
state == "absent"
and name is None
and resource_definition is None
and src is None
and select_all
):
# Delete all resources in the namespace for the specified resource type
resource = svc.find_resource(
module.params.get("kind"), module.params.get("api_version"), fail=True
)
definitions = svc.retrieve_all(resource, module.params.get("namespace"))
elif (
state == "patched"
and len(definitions) == 1
and definitions[0].get("metadata", {}).get("name") is None
):
resource = svc.find_resource(
module.params.get("kind"), module.params.get("api_version"), fail=True
)
existing = svc.retrieve_all(
resource,
module.params.get("namespace"),
module.params.get("label_selectors"),
)
definitions = [dict_merge(d, definitions[0]) for d in existing]

for definition in definitions:
result = {"changed": False, "result": {}}
warnings = []
Expand Down
24 changes: 24 additions & 0 deletions plugins/module_utils/k8s/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,30 @@ def retrieve(self, resource: Resource, definition: Dict) -> ResourceInstance:

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,
Expand Down
30 changes: 30 additions & 0 deletions plugins/modules/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@
- When set to True, server-side apply will force the changes against conflicts.
type: bool
default: False
select_all:
description:
- When this option is set to I(true), action will be performed on all resources, in the namespace of the specified resource type.
- This option requires C(kind) and C(namespace) to be provided.
- Ignored if one of C(src), C(name) or C(resource_definition) is set.
type: bool
default: false
aliases:
- all
requirements:
- "python >= 3.6"
Expand Down Expand Up @@ -343,6 +352,25 @@
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
select_all: true
# Pause all Deployment from specified namespace
- name: Delete all Deployment from specified namespace
kubernetes.core.k8s:
api_version: apps/v1
namespace: testing
kind: Deployment
definition:
spec:
paused: True
select_all: true
"""

RETURN = r"""
Expand Down Expand Up @@ -450,6 +478,7 @@ def argspec():
argument_spec["server_side_apply"] = dict(
type="dict", default=None, options=server_apply_spec()
)
argument_spec["select_all"] = dict(type="bool", default=False, aliases=["all"])

return argument_spec

Expand All @@ -468,6 +497,7 @@ def main():
argument_spec=argspec(),
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
required_if=[["select_all", True, ["kind", "namespace"]]],
)
try:
run_module(module)
Expand Down
33 changes: 3 additions & 30 deletions plugins/modules/k8s_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@
type: bool
default: False
version_added: '2.4.0'
tail_lines:
description:
- A number of lines from the end of the logs to retrieve.
required: no
type: int
version_added: '2.4.0'
requirements:
- "python >= 3.6"
Expand Down Expand Up @@ -112,7 +106,6 @@
kind: DeploymentConfig
namespace: testing
name: example
tail_lines: 100
register: log
"""

Expand Down Expand Up @@ -152,12 +145,6 @@
K8sService,
)

try:
from kubernetes.client.exceptions import ApiException
except ImportError:
# ImportError are managed by the common module already.
pass


def argspec():
args = copy.deepcopy(AUTH_ARG_SPEC)
Expand All @@ -169,7 +156,6 @@ def argspec():
since_seconds=dict(),
label_selectors=dict(type="list", elements="str", default=[]),
previous=dict(type="bool", default=False),
tail_lines=dict(type="int"),
)
)
return args
Expand Down Expand Up @@ -218,22 +204,9 @@ def execute_module(svc, params):
if params.get("previous"):
kwargs.setdefault("query_params", {}).update({"previous": params["previous"]})

if params.get("tail_lines"):
kwargs.setdefault("query_params", {}).update(
{"tailLines": params["tail_lines"]}
)

try:
response = resource.log.get(
name=name, namespace=namespace, serialize=False, **kwargs
)
except ApiException as exc:
if exc.reason == "Not Found":
raise CoreException("Pod {0}/{1} not found.".format(namespace, name))
raise CoreException(
"Unable to retrieve log from Pod due to: {0}".format(exc.reason)
)

response = resource.log.get(
name=name, namespace=namespace, serialize=False, **kwargs
)
log = response.data.decode("utf8")

return {"changed": False, "log": log, "log_lines": log.split("\n")}
Expand Down
70 changes: 70 additions & 0 deletions tests/integration/targets/k8s_delete/files/deployments.yaml
Original file line number Diff line number Diff line change
@@ -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
56 changes: 56 additions & 0 deletions tests/integration/targets/k8s_delete/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,62 @@
that:
- _result.resources | length == 0

# test deletion using select_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 deployments without name and label_selectors and select_all=true
k8s:
kind: Deployment
api_version: apps/v1
namespace: "{{ test_namespace }}"
select_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:
Expand Down
Loading

0 comments on commit ff92a01

Please sign in to comment.