Allow user to specify a custom condition when waiting (#52185)
This commit is contained in:
parent
b979b26a74
commit
65424dd614
3 changed files with 88 additions and 7 deletions
|
@ -66,6 +66,14 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
strict=dict(type='bool', default=True)
|
strict=dict(type='bool', default=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def condition_spec(self):
|
||||||
|
return dict(
|
||||||
|
type=dict(),
|
||||||
|
status=dict(default=True, choices=[True, False, "Unknown"]),
|
||||||
|
reason=dict()
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def argspec(self):
|
def argspec(self):
|
||||||
argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
|
argument_spec = copy.deepcopy(COMMON_ARG_SPEC)
|
||||||
|
@ -73,6 +81,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
argument_spec['merge_type'] = dict(type='list', choices=['json', 'merge', 'strategic-merge'])
|
argument_spec['merge_type'] = dict(type='list', choices=['json', 'merge', 'strategic-merge'])
|
||||||
argument_spec['wait'] = dict(type='bool', default=False)
|
argument_spec['wait'] = dict(type='bool', default=False)
|
||||||
argument_spec['wait_timeout'] = dict(type='int', default=120)
|
argument_spec['wait_timeout'] = dict(type='int', default=120)
|
||||||
|
argument_spec['wait_condition'] = dict(type='dict', default=None, options=self.condition_spec)
|
||||||
argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec)
|
argument_spec['validate'] = dict(type='dict', default=None, options=self.validate_spec)
|
||||||
argument_spec['append_hash'] = dict(type='bool', default=False)
|
argument_spec['append_hash'] = dict(type='bool', default=False)
|
||||||
return argument_spec
|
return argument_spec
|
||||||
|
@ -211,6 +220,9 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
existing = None
|
existing = None
|
||||||
wait = self.params.get('wait')
|
wait = self.params.get('wait')
|
||||||
wait_timeout = self.params.get('wait_timeout')
|
wait_timeout = self.params.get('wait_timeout')
|
||||||
|
wait_condition = None
|
||||||
|
if self.params.get('wait_condition') and self.params['wait_condition'].get('type'):
|
||||||
|
wait_condition = self.params['wait_condition']
|
||||||
|
|
||||||
self.remove_aliases()
|
self.remove_aliases()
|
||||||
|
|
||||||
|
@ -280,7 +292,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
success = True
|
success = True
|
||||||
result['result'] = k8s_obj
|
result['result'] = k8s_obj
|
||||||
if wait and not self.check_mode:
|
if wait and not self.check_mode:
|
||||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout)
|
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout, condition=wait_condition)
|
||||||
result['changed'] = True
|
result['changed'] = True
|
||||||
result['method'] = 'create'
|
result['method'] = 'create'
|
||||||
if not success:
|
if not success:
|
||||||
|
@ -305,7 +317,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
success = True
|
success = True
|
||||||
result['result'] = k8s_obj
|
result['result'] = k8s_obj
|
||||||
if wait:
|
if wait:
|
||||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout)
|
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout, condition=wait_condition)
|
||||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'].to_dict())
|
match, diffs = self.diff_objects(existing.to_dict(), result['result'].to_dict())
|
||||||
result['changed'] = not match
|
result['changed'] = not match
|
||||||
result['method'] = 'replace'
|
result['method'] = 'replace'
|
||||||
|
@ -333,7 +345,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
success = True
|
success = True
|
||||||
result['result'] = k8s_obj
|
result['result'] = k8s_obj
|
||||||
if wait:
|
if wait:
|
||||||
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout)
|
success, result['result'], result['duration'] = self.wait(resource, definition, wait_timeout, condition=wait_condition)
|
||||||
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
match, diffs = self.diff_objects(existing.to_dict(), result['result'])
|
||||||
result['result'] = k8s_obj
|
result['result'] = k8s_obj
|
||||||
result['changed'] = not match
|
result['changed'] = not match
|
||||||
|
@ -398,7 +410,7 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
response = response.to_dict()
|
response = response.to_dict()
|
||||||
return False, response, _wait_for_elapsed()
|
return False, response, _wait_for_elapsed()
|
||||||
|
|
||||||
def wait(self, resource, definition, timeout, state='present'):
|
def wait(self, resource, definition, timeout, state='present', condition=None):
|
||||||
|
|
||||||
def _deployment_ready(deployment):
|
def _deployment_ready(deployment):
|
||||||
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
# FIXME: frustratingly bool(deployment.status) is True even if status is empty
|
||||||
|
@ -416,6 +428,23 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and
|
daemonset.status.numberReady == daemonset.status.desiredNumberScheduled and
|
||||||
daemonset.status.observedGeneration == daemonset.metadata.generation)
|
daemonset.status.observedGeneration == daemonset.metadata.generation)
|
||||||
|
|
||||||
|
def _custom_condition(resource):
|
||||||
|
if not resource.status or not resource.status.conditions:
|
||||||
|
return False
|
||||||
|
match = [x for x in resource.status.conditions if x.type == condition['type']]
|
||||||
|
if not match:
|
||||||
|
return False
|
||||||
|
# There should never be more than one condition of a specific type
|
||||||
|
match = match[0]
|
||||||
|
if match.status == 'Unknown':
|
||||||
|
return False
|
||||||
|
status = True if match.status == 'True' else False
|
||||||
|
if status == condition['status']:
|
||||||
|
if condition.get('reason'):
|
||||||
|
return match.reason == condition['reason']
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _resource_absent(resource):
|
def _resource_absent(resource):
|
||||||
return not resource
|
return not resource
|
||||||
|
|
||||||
|
@ -425,8 +454,10 @@ class KubernetesRawModule(KubernetesAnsibleModule):
|
||||||
Pod=_pod_ready
|
Pod=_pod_ready
|
||||||
)
|
)
|
||||||
kind = definition['kind']
|
kind = definition['kind']
|
||||||
if state == 'present':
|
if state == 'present' and not condition:
|
||||||
predicate = waiter.get(kind, lambda x: True)
|
predicate = waiter.get(kind, lambda x: x)
|
||||||
|
elif state == 'present' and condition:
|
||||||
|
predicate = _custom_condition
|
||||||
else:
|
else:
|
||||||
predicate = _resource_absent
|
predicate = _resource_absent
|
||||||
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, timeout, state)
|
return self._wait_for(resource, definition['metadata']['name'], definition['metadata'].get('namespace'), predicate, timeout, state)
|
||||||
|
|
|
@ -64,7 +64,7 @@ options:
|
||||||
- Whether to wait for certain resource kinds to end up in the desired state. By default the module exits once Kubernetes has
|
- Whether to wait for certain resource kinds to end up in the desired state. By default the module exits once Kubernetes has
|
||||||
received the request
|
received the request
|
||||||
- Implemented for C(state=present) for C(Deployment), C(DaemonSet) and C(Pod), and for C(state=absent) for all resource kinds.
|
- Implemented for C(state=present) for C(Deployment), C(DaemonSet) and C(Pod), and for C(state=absent) for all resource kinds.
|
||||||
- For resource kinds without an implementation, C(wait) returns immediately.
|
- For resource kinds without an implementation, C(wait) returns immediately unless C(wait_condition) is set.
|
||||||
default: no
|
default: no
|
||||||
type: bool
|
type: bool
|
||||||
version_added: "2.8"
|
version_added: "2.8"
|
||||||
|
@ -73,6 +73,31 @@ options:
|
||||||
- How long in seconds to wait for the resource to end up in the desired state. Ignored if C(wait) is not set.
|
- How long in seconds to wait for the resource to end up in the desired state. Ignored if C(wait) is not set.
|
||||||
default: 120
|
default: 120
|
||||||
version_added: "2.8"
|
version_added: "2.8"
|
||||||
|
wait_condition:
|
||||||
|
description:
|
||||||
|
- Specifies a custom condition on the status to wait for. Ignored if C(wait) is not set or is set to False.
|
||||||
|
suboptions:
|
||||||
|
type:
|
||||||
|
description:
|
||||||
|
- The type of condition to wait for. For example, the C(Pod) resource will set the C(Ready) condition (among others)
|
||||||
|
- Required if you are specifying a C(wait_condition). If left empty, the C(wait_condition) field will be ignored.
|
||||||
|
- The possible types for a condition are specific to each resource type in Kubernetes. See the API documentation of the status field
|
||||||
|
for a given resource to see possible choices.
|
||||||
|
status:
|
||||||
|
description:
|
||||||
|
- The value of the status field in your desired condition.
|
||||||
|
- For example, if a C(Deployment) is paused, the C(Progressing) C(type) will have the C(Unknown) status.
|
||||||
|
choices:
|
||||||
|
- True
|
||||||
|
- False
|
||||||
|
- Unknown
|
||||||
|
reason:
|
||||||
|
description:
|
||||||
|
- The value of the reason field in your desired condition
|
||||||
|
- For example, if a C(Deployment) is paused, The C(Progressing) c(type) will have the C(DeploymentPaused) reason.
|
||||||
|
- The possible reasons in a condition are specific to each resource type in Kubernetes. See the API documentation of the status field
|
||||||
|
for a given resource to see possible choices.
|
||||||
|
version_added: "2.8"
|
||||||
validate:
|
validate:
|
||||||
description:
|
description:
|
||||||
- how (if at all) to validate the resource definition against the kubernetes schema.
|
- how (if at all) to validate the resource definition against the kubernetes schema.
|
||||||
|
|
|
@ -242,6 +242,31 @@
|
||||||
- deploy.result.status.availableReplicas == deploy.result.status.replicas
|
- deploy.result.status.availableReplicas == deploy.result.status.replicas
|
||||||
- updated_deploy_pods.resources[0].spec.containers[0].image.endswith(":2")
|
- updated_deploy_pods.resources[0].spec.containers[0].image.endswith(":2")
|
||||||
|
|
||||||
|
- name: pause a deployment
|
||||||
|
k8s:
|
||||||
|
definition:
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: wait-deploy
|
||||||
|
namespace: "{{ wait_namespace }}"
|
||||||
|
spec:
|
||||||
|
paused: True
|
||||||
|
wait: yes
|
||||||
|
wait_condition:
|
||||||
|
type: Progressing
|
||||||
|
status: Unknown
|
||||||
|
reason: DeploymentPaused
|
||||||
|
register: pause_deploy
|
||||||
|
|
||||||
|
- name: check that paused deployment wait worked
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- condition.reason == "DeploymentPaused"
|
||||||
|
- condition.status == "Unknown"
|
||||||
|
vars:
|
||||||
|
condition: '{{ pause_deploy.result.status.conditions | selectattr("type", "Progressing")).0 }}'
|
||||||
|
|
||||||
- name: add a service based on the deployment
|
- name: add a service based on the deployment
|
||||||
k8s:
|
k8s:
|
||||||
definition:
|
definition:
|
||||||
|
|
Loading…
Reference in a new issue