mirror of
https://github.com/ansible-collections/kubernetes.core.git
synced 2026-07-28 10:24:45 +00:00
add support for check_mode for modules k8s_scale and k8s_rollback (#255)
k8s_scale, k8s_rollback - add support for check_mode SUMMARY closes #243 and #244 ISSUE TYPE Bugfix Pull Request COMPONENT NAME k8s_scale k8s_rollback ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis <None> Reviewed-by: None <None> Reviewed-by: Mike Graves <mgraves@redhat.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
minor_changes:
|
||||||
|
- k8s_scale - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/244).
|
||||||
|
- k8s_rollback - add support for check_mode. (https://github.com/ansible-collections/kubernetes/core/issues/243).
|
||||||
@@ -8,18 +8,12 @@
|
|||||||
k8s:
|
k8s:
|
||||||
name: "{{ namespace }}"
|
name: "{{ namespace }}"
|
||||||
kind: Namespace
|
kind: Namespace
|
||||||
api_version: v1
|
|
||||||
apply: no
|
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: show output
|
|
||||||
debug:
|
|
||||||
var: output
|
|
||||||
|
|
||||||
- name: Create a deployment
|
- name: Create a deployment
|
||||||
k8s:
|
k8s:
|
||||||
state: present
|
state: present
|
||||||
wait: yes
|
wait: yes
|
||||||
|
wait_timeout: 30
|
||||||
inline: &deploy
|
inline: &deploy
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -43,11 +37,7 @@
|
|||||||
image: nginx:1.17
|
image: nginx:1.17
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: Show output
|
|
||||||
debug:
|
|
||||||
var: output
|
|
||||||
|
|
||||||
- name: Crash the existing deployment
|
- name: Crash the existing deployment
|
||||||
k8s:
|
k8s:
|
||||||
@@ -77,7 +67,47 @@
|
|||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
register: output
|
register: crash
|
||||||
|
|
||||||
|
- name: Assert that the Deployment failed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- crash is failed
|
||||||
|
|
||||||
|
- name: Read the deployment
|
||||||
|
k8s_info:
|
||||||
|
kind: Deployment
|
||||||
|
name: nginx-deploy
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: deployment
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
failed_version: "{{ deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision'] }}"
|
||||||
|
|
||||||
|
- name: Rolling Back the crashed deployment (check mode)
|
||||||
|
k8s_rollback:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: nginx-deploy
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: result
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: Read the deployment
|
||||||
|
k8s_info:
|
||||||
|
kind: Deployment
|
||||||
|
name: nginx-deploy
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: deployment
|
||||||
|
|
||||||
|
- name: Validate that Rollback using check_mode did not changed the Deployment
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failed_version == deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision']
|
||||||
|
|
||||||
- name: Rolling Back the crashed deployment
|
- name: Rolling Back the crashed deployment
|
||||||
k8s_rollback:
|
k8s_rollback:
|
||||||
@@ -85,12 +115,24 @@
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
name: nginx-deploy
|
name: nginx-deploy
|
||||||
namespace: "{{ namespace }}"
|
namespace: "{{ namespace }}"
|
||||||
when: output.failed
|
register: result
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: Show output
|
- name: assert rollback is changed
|
||||||
debug:
|
assert:
|
||||||
var: output
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: Read the deployment once again
|
||||||
|
k8s_info:
|
||||||
|
kind: Deployment
|
||||||
|
name: nginx-deploy
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: deployment
|
||||||
|
|
||||||
|
- name: Validate that Rollback changed the Deployment
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failed_version | int + 1 == deployment.resources[0].metadata.annotations['deployment.kubernetes.io/revision'] | int
|
||||||
|
|
||||||
- name: Create a DaemonSet
|
- name: Create a DaemonSet
|
||||||
k8s:
|
k8s:
|
||||||
@@ -139,16 +181,12 @@
|
|||||||
- name: varlibdockercontainers
|
- name: varlibdockercontainers
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/docker/containers
|
path: /var/lib/docker/containers
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: Show output
|
|
||||||
debug:
|
|
||||||
var: output
|
|
||||||
|
|
||||||
- name: Crash the existing DaemonSet
|
- name: Crash the existing DaemonSet
|
||||||
k8s:
|
k8s:
|
||||||
state: present
|
state: present
|
||||||
wait: yes
|
wait: yes
|
||||||
|
wait_timeout: 30
|
||||||
definition:
|
definition:
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
@@ -192,8 +230,44 @@
|
|||||||
- name: varlibdockercontainers
|
- name: varlibdockercontainers
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /var/lib/docker/containers
|
path: /var/lib/docker/containers
|
||||||
ignore_errors: yes
|
register: crash
|
||||||
register: output
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Assert that the Daemonset failed
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- crash is failed
|
||||||
|
|
||||||
|
- name: Read the crashed DaemonSet
|
||||||
|
k8s_info:
|
||||||
|
kind: DaemonSet
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
failed_version: "{{ result.resources[0].metadata.annotations['deprecated.daemonset.template.generation'] }}"
|
||||||
|
|
||||||
|
- name: Rolling Back the crashed DaemonSet (check_mode)
|
||||||
|
k8s_rollback:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: result
|
||||||
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: Read the DaemonSet
|
||||||
|
k8s_info:
|
||||||
|
kind: DaemonSet
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Validate that Rollback using check_mode did not changed the DaemonSet version
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failed_version == result.resources[0].metadata.annotations['deprecated.daemonset.template.generation']
|
||||||
|
|
||||||
- name: Rolling Back the crashed DaemonSet
|
- name: Rolling Back the crashed DaemonSet
|
||||||
k8s_rollback:
|
k8s_rollback:
|
||||||
@@ -201,12 +275,24 @@
|
|||||||
kind: DaemonSet
|
kind: DaemonSet
|
||||||
name: fluentd-elasticsearch
|
name: fluentd-elasticsearch
|
||||||
namespace: "{{ namespace }}"
|
namespace: "{{ namespace }}"
|
||||||
when: output.failed
|
register: result
|
||||||
register: output
|
|
||||||
|
|
||||||
- name: Show output
|
- name: assert rollback is changed
|
||||||
debug:
|
assert:
|
||||||
var: output
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: Read the DaemonSet
|
||||||
|
k8s_info:
|
||||||
|
kind: DaemonSet
|
||||||
|
name: fluentd-elasticsearch
|
||||||
|
namespace: "{{ namespace }}"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Validate that Rollback changed the Daemonset version
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- failed_version | int + 1 == result.resources[0].metadata.annotations['deprecated.daemonset.template.generation'] | int
|
||||||
|
|
||||||
always:
|
always:
|
||||||
- name: Delete {{ namespace }} namespace
|
- name: Delete {{ namespace }} namespace
|
||||||
|
|||||||
@@ -45,6 +45,68 @@
|
|||||||
field_selectors:
|
field_selectors:
|
||||||
- status.phase=Running
|
- status.phase=Running
|
||||||
|
|
||||||
|
- name: Scale the deployment (check_mode)
|
||||||
|
k8s_scale:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: scale-deploy
|
||||||
|
namespace: "{{ scale_namespace }}"
|
||||||
|
replicas: 0
|
||||||
|
wait: yes
|
||||||
|
register: scale_down
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Get pods in scale-deploy
|
||||||
|
k8s_info:
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- app=scale-deploy
|
||||||
|
namespace: "{{ scale_namespace }}"
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: scale_down_deploy_pods
|
||||||
|
ignore_errors: true
|
||||||
|
until: scale_down_deploy_pods.resources | length == 0
|
||||||
|
retries: 6
|
||||||
|
delay: 5
|
||||||
|
|
||||||
|
- name: Ensure the deployment did not changed and pods are still running
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- scale_down is changed
|
||||||
|
- scale_down_deploy_pods.resources | length > 0
|
||||||
|
|
||||||
|
- name: Scale the deployment (check_mode) once again - validate idempotency
|
||||||
|
k8s_scale:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: scale-deploy
|
||||||
|
namespace: "{{ scale_namespace }}"
|
||||||
|
replicas: 0
|
||||||
|
wait: yes
|
||||||
|
register: scale_down
|
||||||
|
check_mode: true
|
||||||
|
|
||||||
|
- name: Get pods in scale-deploy
|
||||||
|
k8s_info:
|
||||||
|
kind: Pod
|
||||||
|
label_selectors:
|
||||||
|
- app=scale-deploy
|
||||||
|
namespace: "{{ scale_namespace }}"
|
||||||
|
field_selectors:
|
||||||
|
- status.phase=Running
|
||||||
|
register: scale_down_deploy_pods
|
||||||
|
ignore_errors: true
|
||||||
|
until: scale_down_deploy_pods.resources | length == 0
|
||||||
|
retries: 6
|
||||||
|
delay: 5
|
||||||
|
|
||||||
|
- name: Ensure the deployment did not changed and pods are still running
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- scale_down is changed
|
||||||
|
- scale_down_deploy_pods.resources | length > 0
|
||||||
|
|
||||||
- name: Scale the deployment
|
- name: Scale the deployment
|
||||||
k8s_scale:
|
k8s_scale:
|
||||||
api_version: apps/v1
|
api_version: apps/v1
|
||||||
@@ -76,6 +138,22 @@
|
|||||||
- '"duration" in scale_down'
|
- '"duration" in scale_down'
|
||||||
- scale_down.diff
|
- scale_down.diff
|
||||||
|
|
||||||
|
- name: Scale the deployment once again (idempotency)
|
||||||
|
k8s_scale:
|
||||||
|
api_version: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: scale-deploy
|
||||||
|
namespace: "{{ scale_namespace }}"
|
||||||
|
replicas: 0
|
||||||
|
wait: yes
|
||||||
|
register: scale_down_idempotency
|
||||||
|
diff: true
|
||||||
|
|
||||||
|
- name: Ensure that scale down did not took effect
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- scale_down_idempotency is not changed
|
||||||
|
|
||||||
- name: Reapply the earlier deployment
|
- name: Reapply the earlier deployment
|
||||||
k8s:
|
k8s:
|
||||||
definition:
|
definition:
|
||||||
|
|||||||
@@ -115,11 +115,13 @@ def execute_module(module, k8s_ansible_mixin):
|
|||||||
module.params["field_selectors"],
|
module.params["field_selectors"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
changed = False
|
||||||
for resource in resources["resources"]:
|
for resource in resources["resources"]:
|
||||||
result = perform_action(module, k8s_ansible_mixin, resource)
|
result = perform_action(module, k8s_ansible_mixin, resource)
|
||||||
|
changed = result["changed"] or changed
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
module.exit_json(**{"changed": True, "rollback_info": results})
|
module.exit_json(**{"changed": changed, "rollback_info": results})
|
||||||
|
|
||||||
|
|
||||||
def perform_action(module, k8s_ansible_mixin, resource):
|
def perform_action(module, k8s_ansible_mixin, resource):
|
||||||
@@ -143,6 +145,13 @@ def perform_action(module, k8s_ansible_mixin, resource):
|
|||||||
prev_managed_resource = get_previous_revision(
|
prev_managed_resource = get_previous_revision(
|
||||||
managed_resources["resources"], current_revision
|
managed_resources["resources"], current_revision
|
||||||
)
|
)
|
||||||
|
if not prev_managed_resource:
|
||||||
|
warn = "No rollout history found for resource %s/%s" % (
|
||||||
|
module.params["kind"],
|
||||||
|
resource["metadata"]["name"],
|
||||||
|
)
|
||||||
|
result = {"changed": False, "warnings": [warn]}
|
||||||
|
return result
|
||||||
|
|
||||||
if module.params["kind"] == "Deployment":
|
if module.params["kind"] == "Deployment":
|
||||||
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
del prev_managed_resource["spec"]["template"]["metadata"]["labels"][
|
||||||
@@ -174,22 +183,24 @@ def perform_action(module, k8s_ansible_mixin, resource):
|
|||||||
api_target = "daemonsets"
|
api_target = "daemonsets"
|
||||||
content_type = "application/strategic-merge-patch+json"
|
content_type = "application/strategic-merge-patch+json"
|
||||||
|
|
||||||
rollback = k8s_ansible_mixin.client.request(
|
rollback = resource
|
||||||
"PATCH",
|
if not module.check_mode:
|
||||||
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
rollback = k8s_ansible_mixin.client.request(
|
||||||
module.params["api_version"],
|
"PATCH",
|
||||||
module.params["namespace"],
|
"/apis/{0}/namespaces/{1}/{2}/{3}".format(
|
||||||
api_target,
|
module.params["api_version"],
|
||||||
module.params["name"],
|
module.params["namespace"],
|
||||||
),
|
api_target,
|
||||||
body=resource_patch,
|
module.params["name"],
|
||||||
content_type=content_type,
|
),
|
||||||
)
|
body=resource_patch,
|
||||||
|
content_type=content_type,
|
||||||
|
).to_dict()
|
||||||
|
|
||||||
result = {"changed": True}
|
result = {"changed": True}
|
||||||
result["method"] = "patch"
|
result["method"] = "patch"
|
||||||
result["body"] = resource_patch
|
result["body"] = resource_patch
|
||||||
result["resources"] = rollback.to_dict()
|
result["resources"] = rollback
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ def execute_module(
|
|||||||
module.exit_json(warning=warn, **return_attributes)
|
module.exit_json(warning=warn, **return_attributes)
|
||||||
|
|
||||||
for existing in existing_items:
|
for existing in existing_items:
|
||||||
if module.params["kind"] == "job":
|
if module.params["kind"].lower() == "job":
|
||||||
existing_count = existing.spec.parallelism
|
existing_count = existing.spec.parallelism
|
||||||
elif hasattr(existing.spec, "replicas"):
|
elif hasattr(existing.spec, "replicas"):
|
||||||
existing_count = existing.spec.replicas
|
existing_count = existing.spec.replicas
|
||||||
@@ -271,22 +271,25 @@ def execute_module(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if existing_count != replicas:
|
if existing_count != replicas:
|
||||||
if not module.check_mode:
|
if module.params["kind"].lower() == "job":
|
||||||
if module.params["kind"] == "job":
|
existing.spec.parallelism = replicas
|
||||||
existing.spec.parallelism = replicas
|
result = {"changed": True}
|
||||||
result = resource.patch(existing.to_dict()).to_dict()
|
if module.check_mode:
|
||||||
|
result["result"] = existing.to_dict()
|
||||||
else:
|
else:
|
||||||
result = scale(
|
result["result"] = resource.patch(existing.to_dict()).to_dict()
|
||||||
module,
|
else:
|
||||||
k8s_ansible_mixin,
|
result = scale(
|
||||||
resource,
|
module,
|
||||||
existing,
|
k8s_ansible_mixin,
|
||||||
replicas,
|
resource,
|
||||||
wait,
|
existing,
|
||||||
wait_time,
|
replicas,
|
||||||
wait_sleep,
|
wait,
|
||||||
)
|
wait_time,
|
||||||
changed = changed or result["changed"]
|
wait_sleep,
|
||||||
|
)
|
||||||
|
changed = changed or result["changed"]
|
||||||
else:
|
else:
|
||||||
name = existing.metadata.name
|
name = existing.metadata.name
|
||||||
namespace = existing.metadata.namespace
|
namespace = existing.metadata.namespace
|
||||||
@@ -342,25 +345,34 @@ def scale(
|
|||||||
|
|
||||||
existing = resource.get(name=name, namespace=namespace)
|
existing = resource.get(name=name, namespace=namespace)
|
||||||
|
|
||||||
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()
|
|
||||||
match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj)
|
|
||||||
result = dict()
|
result = dict()
|
||||||
result["result"] = k8s_obj
|
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
|
result["changed"] = not match
|
||||||
if module._diff:
|
if module._diff:
|
||||||
result["diff"] = diffs
|
result["diff"] = diffs
|
||||||
|
|
||||||
if wait:
|
|
||||||
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)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user