mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-26 21:33:02 +00:00
K8s_taint new module (#264)
K8s_taint new module SUMMARY k8s_taint - new module to apply/remove taints to/from nodes. ISSUE TYPE New Module Pull Request COMPONENT NAME k8s_taint Reviewed-by: Mike Graves <mgraves@redhat.com> Reviewed-by: Alina Buzachis <None> Reviewed-by: None <None> Reviewed-by: None <None>
This commit is contained in:
310
plugins/modules/k8s_taint.py
Normal file
310
plugins/modules/k8s_taint.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/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.6
|
||||
- kubernetes >= 12.0.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.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
||||
K8sAnsibleMixin,
|
||||
get_api_client,
|
||||
)
|
||||
from ansible_collections.kubernetes.core.plugins.module_utils.args_common import (
|
||||
AUTH_ARG_SPEC,
|
||||
)
|
||||
|
||||
try:
|
||||
from kubernetes.client.api import core_v1_api
|
||||
from kubernetes.dynamic.exceptions import ApiException
|
||||
except ImportError:
|
||||
# ImportError are managed by the common module already.
|
||||
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):
|
||||
self.module = module
|
||||
self.k8s_ansible_mixin = K8sAnsibleMixin(module=self.module)
|
||||
self.k8s_ansible_mixin.client = get_api_client(module=self.module)
|
||||
self.k8s_ansible_mixin.module = self.module
|
||||
self.k8s_ansible_mixin.argspec = self.module.argument_spec
|
||||
self.k8s_ansible_mixin.check_mode = self.module.check_mode
|
||||
self.k8s_ansible_mixin.params = self.module.params
|
||||
self.k8s_ansible_mixin.fail_json = self.module.fail_json
|
||||
self.k8s_ansible_mixin.fail = self.module.fail_json
|
||||
self.k8s_ansible_mixin.exit_json = self.module.exit_json
|
||||
self.k8s_ansible_mixin.warn = self.module.warn
|
||||
self.k8s_ansible_mixin.warnings = []
|
||||
self.api_instance = core_v1_api.CoreV1Api(self.k8s_ansible_mixin.client.client)
|
||||
self.k8s_ansible_mixin.check_library_version()
|
||||
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 = AnsibleModule(argument_spec=argspec(), supports_check_mode=True,)
|
||||
k8s_taint = K8sTaintAnsible(module)
|
||||
k8s_taint.execute_module()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user