mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-03-27 05:43:02 +00:00
Upgrade black version SUMMARY Move off of beta version of black and pin to current calendar year version. The only manual changes here are to tox.ini. Everything else is from running the new version of black. ISSUE TYPE COMPONENT NAME ADDITIONAL INFORMATION Reviewed-by: Abhijeet Kasurde <None>
401 lines
12 KiB
Python
401 lines
12 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2018, Chris Houseknecht <@chouseknecht>
|
|
# 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_scale
|
|
|
|
short_description: Set a new size for a Deployment, ReplicaSet, Replication Controller, or Job.
|
|
|
|
author:
|
|
- "Chris Houseknecht (@chouseknecht)"
|
|
- "Fabian von Feilitzsch (@fabianvf)"
|
|
|
|
description:
|
|
- Similar to the kubectl scale command. Use to set the number of replicas for a Deployment, ReplicaSet,
|
|
or Replication Controller, or the parallelism attribute of a Job. Supports check mode.
|
|
- C(wait) parameter is not supported for Jobs.
|
|
|
|
extends_documentation_fragment:
|
|
- kubernetes.core.k8s_name_options
|
|
- kubernetes.core.k8s_auth_options
|
|
- kubernetes.core.k8s_resource_options
|
|
- kubernetes.core.k8s_scale_options
|
|
|
|
options:
|
|
label_selectors:
|
|
description: List of label selectors to use to filter results.
|
|
type: list
|
|
elements: str
|
|
version_added: 2.0.0
|
|
continue_on_error:
|
|
description:
|
|
- Whether to continue on errors when multiple resources are defined.
|
|
type: bool
|
|
default: False
|
|
version_added: 2.0.0
|
|
|
|
requirements:
|
|
- "python >= 3.6"
|
|
- "kubernetes >= 12.0.0"
|
|
- "PyYAML >= 3.11"
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
- name: Scale deployment up, and extend timeout
|
|
kubernetes.core.k8s_scale:
|
|
api_version: v1
|
|
kind: Deployment
|
|
name: elastic
|
|
namespace: myproject
|
|
replicas: 3
|
|
wait_timeout: 60
|
|
|
|
- name: Scale deployment down when current replicas match
|
|
kubernetes.core.k8s_scale:
|
|
api_version: v1
|
|
kind: Deployment
|
|
name: elastic
|
|
namespace: myproject
|
|
current_replicas: 3
|
|
replicas: 2
|
|
|
|
- name: Increase job parallelism
|
|
kubernetes.core.k8s_scale:
|
|
api_version: batch/v1
|
|
kind: job
|
|
name: pi-with-timeout
|
|
namespace: testing
|
|
replicas: 2
|
|
|
|
# Match object using local file or inline definition
|
|
|
|
- name: Scale deployment based on a file from the local filesystem
|
|
kubernetes.core.k8s_scale:
|
|
src: /myproject/elastic_deployment.yml
|
|
replicas: 3
|
|
wait: no
|
|
|
|
- name: Scale deployment based on a template output
|
|
kubernetes.core.k8s_scale:
|
|
resource_definition: "{{ lookup('template', '/myproject/elastic_deployment.yml') | from_yaml }}"
|
|
replicas: 3
|
|
wait: no
|
|
|
|
- name: Scale deployment based on a file from the Ansible controller filesystem
|
|
kubernetes.core.k8s_scale:
|
|
resource_definition: "{{ lookup('file', '/myproject/elastic_deployment.yml') | from_yaml }}"
|
|
replicas: 3
|
|
wait: no
|
|
|
|
- name: Scale deployment using label selectors (continue operation in case error occured on one resource)
|
|
kubernetes.core.k8s_scale:
|
|
replicas: 3
|
|
kind: Deployment
|
|
namespace: test
|
|
label_selectors:
|
|
- app=test
|
|
continue_on_error: true
|
|
"""
|
|
|
|
RETURN = r"""
|
|
result:
|
|
description:
|
|
- If a change was made, will return the patched object, otherwise returns the existing object.
|
|
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
|
|
duration:
|
|
description: elapsed time of task in seconds
|
|
returned: when C(wait) is true
|
|
type: int
|
|
sample: 48
|
|
"""
|
|
|
|
import copy
|
|
|
|
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,
|
|
RESOURCE_ARG_SPEC,
|
|
NAME_ARG_SPEC,
|
|
)
|
|
|
|
|
|
SCALE_ARG_SPEC = {
|
|
"replicas": {"type": "int", "required": True},
|
|
"current_replicas": {"type": "int"},
|
|
"resource_version": {},
|
|
"wait": {"type": "bool", "default": True},
|
|
"wait_timeout": {"type": "int", "default": 20},
|
|
"wait_sleep": {"type": "int", "default": 5},
|
|
}
|
|
|
|
|
|
def execute_module(
|
|
module,
|
|
k8s_ansible_mixin,
|
|
):
|
|
k8s_ansible_mixin.set_resource_definitions(module)
|
|
|
|
definition = k8s_ansible_mixin.resource_definitions[0]
|
|
|
|
name = definition["metadata"]["name"]
|
|
namespace = definition["metadata"].get("namespace")
|
|
api_version = definition["apiVersion"]
|
|
kind = definition["kind"]
|
|
current_replicas = module.params.get("current_replicas")
|
|
replicas = module.params.get("replicas")
|
|
resource_version = module.params.get("resource_version")
|
|
|
|
label_selectors = module.params.get("label_selectors")
|
|
if not label_selectors:
|
|
label_selectors = []
|
|
continue_on_error = module.params.get("continue_on_error")
|
|
|
|
wait = module.params.get("wait")
|
|
wait_time = module.params.get("wait_timeout")
|
|
wait_sleep = module.params.get("wait_sleep")
|
|
existing = None
|
|
existing_count = None
|
|
return_attributes = dict(result=dict())
|
|
if module._diff:
|
|
return_attributes["diff"] = dict()
|
|
if wait:
|
|
return_attributes["duration"] = 0
|
|
|
|
resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True)
|
|
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
|
NotFoundError,
|
|
)
|
|
|
|
multiple_scale = False
|
|
try:
|
|
existing = resource.get(
|
|
name=name, namespace=namespace, label_selector=",".join(label_selectors)
|
|
)
|
|
if existing.kind.endswith("List"):
|
|
existing_items = existing.items
|
|
multiple_scale = len(existing_items) > 1
|
|
else:
|
|
existing_items = [existing]
|
|
except NotFoundError as exc:
|
|
module.fail_json(
|
|
msg="Failed to retrieve requested object: {0}".format(exc),
|
|
error=exc.value.get("status"),
|
|
)
|
|
|
|
if multiple_scale:
|
|
# when scaling multiple resource, the 'result' is changed to 'results' and is a list
|
|
return_attributes = {"results": []}
|
|
changed = False
|
|
|
|
def _continue_or_fail(error):
|
|
if multiple_scale and continue_on_error:
|
|
if "errors" not in return_attributes:
|
|
return_attributes["errors"] = []
|
|
return_attributes["errors"].append({"error": error, "failed": True})
|
|
else:
|
|
module.fail_json(msg=error, **return_attributes)
|
|
|
|
def _continue_or_exit(warn):
|
|
if multiple_scale:
|
|
return_attributes["results"].append({"warning": warn, "changed": False})
|
|
else:
|
|
module.exit_json(warning=warn, **return_attributes)
|
|
|
|
for existing in existing_items:
|
|
if module.params["kind"].lower() == "job":
|
|
existing_count = existing.spec.parallelism
|
|
elif hasattr(existing.spec, "replicas"):
|
|
existing_count = existing.spec.replicas
|
|
|
|
if existing_count is None:
|
|
error = "Failed to retrieve the available count for object kind={0} name={1} namespace={2}.".format(
|
|
existing.kind, existing.metadata.name, existing.metadata.namespace
|
|
)
|
|
_continue_or_fail(error)
|
|
continue
|
|
|
|
if resource_version and resource_version != existing.metadata.resourceVersion:
|
|
warn = "expected resource version {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
|
|
resource_version,
|
|
existing.metadata.resourceVersion,
|
|
existing.kind,
|
|
existing.metadata.name,
|
|
existing.metadata.namespace,
|
|
)
|
|
_continue_or_exit(warn)
|
|
continue
|
|
|
|
if current_replicas is not None and existing_count != current_replicas:
|
|
warn = "current replicas {0} does not match with actual {1} for object kind={2} name={3} namespace={4}.".format(
|
|
current_replicas,
|
|
existing_count,
|
|
existing.kind,
|
|
existing.metadata.name,
|
|
existing.metadata.namespace,
|
|
)
|
|
_continue_or_exit(warn)
|
|
continue
|
|
|
|
if existing_count != replicas:
|
|
if module.params["kind"].lower() == "job":
|
|
existing.spec.parallelism = replicas
|
|
result = {"changed": True}
|
|
if module.check_mode:
|
|
result["result"] = existing.to_dict()
|
|
else:
|
|
result["result"] = resource.patch(existing.to_dict()).to_dict()
|
|
else:
|
|
result = scale(
|
|
module,
|
|
k8s_ansible_mixin,
|
|
resource,
|
|
existing,
|
|
replicas,
|
|
wait,
|
|
wait_time,
|
|
wait_sleep,
|
|
)
|
|
changed = changed or result["changed"]
|
|
else:
|
|
name = existing.metadata.name
|
|
namespace = existing.metadata.namespace
|
|
existing = resource.get(name=name, namespace=namespace)
|
|
result = {"changed": False, "result": existing.to_dict()}
|
|
if module._diff:
|
|
result["diff"] = {}
|
|
if wait:
|
|
result["duration"] = 0
|
|
# append result to the return attribute
|
|
if multiple_scale:
|
|
return_attributes["results"].append(result)
|
|
else:
|
|
module.exit_json(**result)
|
|
|
|
module.exit_json(changed=changed, **return_attributes)
|
|
|
|
|
|
def argspec():
|
|
args = copy.deepcopy(SCALE_ARG_SPEC)
|
|
args.update(RESOURCE_ARG_SPEC)
|
|
args.update(NAME_ARG_SPEC)
|
|
args.update(AUTH_ARG_SPEC)
|
|
args.update({"label_selectors": {"type": "list", "elements": "str", "default": []}})
|
|
args.update(({"continue_on_error": {"type": "bool", "default": False}}))
|
|
return args
|
|
|
|
|
|
def scale(
|
|
module,
|
|
k8s_ansible_mixin,
|
|
resource,
|
|
existing_object,
|
|
replicas,
|
|
wait,
|
|
wait_time,
|
|
wait_sleep,
|
|
):
|
|
name = existing_object.metadata.name
|
|
namespace = existing_object.metadata.namespace
|
|
kind = existing_object.kind
|
|
|
|
if not hasattr(resource, "scale"):
|
|
module.fail_json(
|
|
msg="Cannot perform scale on resource of kind {0}".format(resource.kind)
|
|
)
|
|
|
|
scale_obj = {
|
|
"kind": kind,
|
|
"metadata": {"name": name, "namespace": namespace},
|
|
"spec": {"replicas": replicas},
|
|
}
|
|
|
|
existing = resource.get(name=name, namespace=namespace)
|
|
|
|
result = dict()
|
|
if module.check_mode:
|
|
k8s_obj = copy.deepcopy(existing.to_dict())
|
|
k8s_obj["spec"]["replicas"] = replicas
|
|
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
|
if wait:
|
|
result["duration"] = 0
|
|
result["result"] = k8s_obj
|
|
else:
|
|
try:
|
|
resource.scale.patch(body=scale_obj)
|
|
except Exception as exc:
|
|
module.fail_json(msg="Scale request failed: {0}".format(exc))
|
|
|
|
k8s_obj = resource.get(name=name, namespace=namespace).to_dict()
|
|
result["result"] = k8s_obj
|
|
if wait and not module.check_mode:
|
|
success, result["result"], result["duration"] = k8s_ansible_mixin.wait(
|
|
resource, scale_obj, wait_sleep, wait_time
|
|
)
|
|
if not success:
|
|
module.fail_json(msg="Resource scaling timed out", **result)
|
|
|
|
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
|
result["changed"] = not match
|
|
if module._diff:
|
|
result["diff"] = diffs
|
|
|
|
return result
|
|
|
|
|
|
def main():
|
|
mutually_exclusive = [
|
|
("resource_definition", "src"),
|
|
]
|
|
module = AnsibleModule(
|
|
argument_spec=argspec(),
|
|
mutually_exclusive=mutually_exclusive,
|
|
supports_check_mode=True,
|
|
)
|
|
from ansible_collections.kubernetes.core.plugins.module_utils.common import (
|
|
K8sAnsibleMixin,
|
|
get_api_client,
|
|
)
|
|
|
|
k8s_ansible_mixin = K8sAnsibleMixin(module)
|
|
k8s_ansible_mixin.client = get_api_client(module=module)
|
|
execute_module(module, k8s_ansible_mixin)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|