mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
minor(doc): use the same style of version_added across repo SUMMARY Currently is no single style of version_added, in some places it's unquoted, somewhere single quote is used, in another places it's double quoted. Moreover, some file had different styles in one single file. The aim of this PR is to update whole repo to single style for version_added ISSUE TYPE Docs Pull Request COMPONENT NAME kustomize helm helm_info helm_plugin helm_plugin_info helm_pull helm_repository helm_template k8s_cluster_info k8s_cp k8s_drain k8s_exec k8s_log k8s_rollback k8s_taint ADDITIONAL INFORMATION The same style is proposed as used in amazon.aws collections Reviewed-by: Kelv Gooding Reviewed-by: Alina Buzachis Reviewed-by: Mike Graves <mgraves@redhat.com>
313 lines
9.3 KiB
Python
313 lines
9.3 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2021, Alina Buzachis <@alinabuzachis>
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
DOCUMENTATION = r"""
|
|
module: k8s_taint
|
|
short_description: Taint a node in a Kubernetes/OpenShift cluster
|
|
version_added: 2.3.0
|
|
author: Alina Buzachis (@alinabuzachis)
|
|
description:
|
|
- Taint allows a node to refuse Pod to be scheduled unless that Pod has a matching toleration.
|
|
- Untaint will remove taints from nodes as needed.
|
|
extends_documentation_fragment:
|
|
- kubernetes.core.k8s_auth_options
|
|
options:
|
|
state:
|
|
description:
|
|
- Determines whether to add or remove taints.
|
|
type: str
|
|
default: present
|
|
choices: [ present, absent ]
|
|
name:
|
|
description:
|
|
- The name of the node.
|
|
required: true
|
|
type: str
|
|
taints:
|
|
description:
|
|
- List containing the taints.
|
|
type: list
|
|
required: true
|
|
elements: dict
|
|
suboptions:
|
|
key:
|
|
description:
|
|
- The taint key to be applied to a node.
|
|
type: str
|
|
value:
|
|
description:
|
|
- The taint value corresponding to the taint key.
|
|
type: str
|
|
effect:
|
|
description:
|
|
- The effect of the taint on Pods that do not tolerate the taint.
|
|
- Required when I(state=present).
|
|
type: str
|
|
choices: [ NoSchedule, NoExecute, PreferNoSchedule ]
|
|
replace:
|
|
description:
|
|
- If C(true), allow taints to be replaced.
|
|
required: false
|
|
default: false
|
|
type: bool
|
|
requirements:
|
|
- python >= 3.9
|
|
- kubernetes >= 24.2.0
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
- name: Taint node "foo"
|
|
kubernetes.core.k8s_taint:
|
|
state: present
|
|
name: foo
|
|
taints:
|
|
- effect: NoExecute
|
|
key: "key1"
|
|
|
|
- name: Taint node "foo"
|
|
kubernetes.core.k8s_taint:
|
|
state: present
|
|
name: foo
|
|
taints:
|
|
- effect: NoExecute
|
|
key: "key1"
|
|
value: "value1"
|
|
- effect: NoSchedule
|
|
key: "key1"
|
|
value: "value1"
|
|
|
|
- name: Remove taint from "foo".
|
|
kubernetes.core.k8s_taint:
|
|
state: absent
|
|
name: foo
|
|
taints:
|
|
- effect: NoExecute
|
|
key: "key1"
|
|
value: "value1"
|
|
"""
|
|
|
|
RETURN = r"""
|
|
result:
|
|
description:
|
|
- The tainted Node object. Will be empty in the case of a deletion.
|
|
returned: success
|
|
type: complex
|
|
contains:
|
|
api_version:
|
|
description: The versioned schema of this representation of an object.
|
|
returned: success
|
|
type: str
|
|
kind:
|
|
description: Represents the REST resource this object represents.
|
|
returned: success
|
|
type: str
|
|
metadata:
|
|
description: Standard object metadata. Includes name, namespace, annotations, labels, etc.
|
|
returned: success
|
|
type: complex
|
|
spec:
|
|
description: Specific attributes of the object. Will vary based on the I(api_version) and I(kind).
|
|
returned: success
|
|
type: complex
|
|
status:
|
|
description: Current status details for the object.
|
|
returned: success
|
|
type: complex
|
|
"""
|
|
|
|
import copy
|
|
|
|
from ansible.module_utils._text import to_native
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import (
|
|
AnsibleModule,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
|
AUTH_ARG_SPEC,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import (
|
|
get_api_client,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import (
|
|
AnsibleK8SModule,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import (
|
|
CoreException,
|
|
)
|
|
|
|
try:
|
|
from kubernetes.client.api import core_v1_api
|
|
from kubernetes.client.exceptions import ApiException
|
|
except ImportError:
|
|
# ImportErrors are handled during module setup
|
|
pass
|
|
|
|
|
|
def _equal_dicts(a, b):
|
|
keys = ["key", "effect"]
|
|
if "effect" not in set(a).intersection(b):
|
|
keys.remove("effect")
|
|
|
|
return all((a[x] == b[x] for x in keys))
|
|
|
|
|
|
def _get_difference(a, b):
|
|
return [
|
|
a_item for a_item in a if not any(_equal_dicts(a_item, b_item) for b_item in b)
|
|
]
|
|
|
|
|
|
def _get_intersection(a, b):
|
|
return [a_item for a_item in a if any(_equal_dicts(a_item, b_item) for b_item in b)]
|
|
|
|
|
|
def _update_exists(a, b):
|
|
return any(
|
|
(
|
|
any(
|
|
_equal_dicts(a_item, b_item)
|
|
and a_item.get("value") != b_item.get("value")
|
|
for b_item in b
|
|
)
|
|
for a_item in a
|
|
)
|
|
)
|
|
|
|
|
|
def argspec():
|
|
argument_spec = copy.deepcopy(AUTH_ARG_SPEC)
|
|
argument_spec.update(
|
|
dict(
|
|
state=dict(type="str", choices=["present", "absent"], default="present"),
|
|
name=dict(type="str", required=True),
|
|
taints=dict(type="list", required=True, elements="dict"),
|
|
replace=dict(type="bool", default=False),
|
|
)
|
|
)
|
|
|
|
return argument_spec
|
|
|
|
|
|
class K8sTaintAnsible:
|
|
def __init__(self, module, client):
|
|
self.module = module
|
|
self.api_instance = core_v1_api.CoreV1Api(client.client)
|
|
self.changed = False
|
|
|
|
def get_node(self, name):
|
|
try:
|
|
node = self.api_instance.read_node(name=name)
|
|
except ApiException as exc:
|
|
if exc.reason == "Not Found":
|
|
self.module.fail_json(msg="Node '{0}' has not been found.".format(name))
|
|
self.module.fail_json(
|
|
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
|
name, exc.reason
|
|
),
|
|
status=exc.status,
|
|
)
|
|
except Exception as exc:
|
|
self.module.fail_json(
|
|
msg="Failed to retrieve node '{0}' due to: {1}".format(
|
|
name, to_native(exc)
|
|
)
|
|
)
|
|
|
|
return node
|
|
|
|
def patch_node(self, taints):
|
|
body = {"spec": {"taints": taints}}
|
|
|
|
try:
|
|
result = self.api_instance.patch_node(
|
|
name=self.module.params.get("name"), body=body
|
|
)
|
|
except Exception as exc:
|
|
self.module.fail_json(
|
|
msg="Failed to patch node due to: {0}".format(to_native(exc))
|
|
)
|
|
|
|
return result.to_dict()
|
|
|
|
def execute_module(self):
|
|
result = {"result": {}}
|
|
state = self.module.params.get("state")
|
|
taints = self.module.params.get("taints")
|
|
name = self.module.params.get("name")
|
|
|
|
node = self.get_node(name)
|
|
existing_taints = node.spec.to_dict().get("taints") or []
|
|
diff = _get_difference(taints, existing_taints)
|
|
|
|
if state == "present":
|
|
if diff:
|
|
# There are new taints to be added
|
|
self.changed = True
|
|
if self.module.check_mode:
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
|
|
if self.module.params.get("replace"):
|
|
# Patch with the new taints
|
|
result["result"] = self.patch_node(taints=taints)
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
|
|
result["result"] = self.patch_node(
|
|
taints=[*_get_difference(existing_taints, taints), *taints]
|
|
)
|
|
else:
|
|
# No new taints to be added, but maybe there is something to be updated
|
|
if _update_exists(existing_taints, taints):
|
|
self.changed = True
|
|
if self.module.check_mode:
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
result["result"] = self.patch_node(
|
|
taints=[*_get_difference(existing_taints, taints), *taints]
|
|
)
|
|
else:
|
|
result["result"] = node.to_dict()
|
|
elif state == "absent":
|
|
# Nothing to be removed
|
|
if not existing_taints:
|
|
result["result"] = node.to_dict()
|
|
if not diff:
|
|
self.changed = True
|
|
if self.module.check_mode:
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
self.patch_node(taints=_get_difference(existing_taints, taints))
|
|
else:
|
|
if _get_intersection(existing_taints, taints):
|
|
self.changed = True
|
|
if self.module.check_mode:
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
self.patch_node(taints=_get_difference(existing_taints, taints))
|
|
else:
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
|
|
self.module.exit_json(changed=self.changed, **result)
|
|
|
|
|
|
def main():
|
|
module = AnsibleK8SModule(
|
|
module_class=AnsibleModule,
|
|
argument_spec=argspec(),
|
|
supports_check_mode=True,
|
|
)
|
|
try:
|
|
client = get_api_client(module)
|
|
k8s_taint = K8sTaintAnsible(module, client.client)
|
|
k8s_taint.execute_module()
|
|
except CoreException as e:
|
|
module.fail_from_exception(e)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|