Allow user to specify a custom condition when waiting (#52185)

This commit is contained in:
Fabian von Feilitzsch 2019-03-06 07:41:17 -05:00 committed by John R Barker
parent b979b26a74
commit 65424dd614
3 changed files with 88 additions and 7 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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: