add module aws_step_functions_state_machine_execution (#64431)
* add module aws_step_functions_state_machine_execution * AWS step functions tests - Use module defaults * Return all attributes from aws api calls as ansible task output * aws_sfn - make start and stop execution idempotent and fix check mode * aws sfn - use build_full_result method of the paginator * aws sfn - remove changes made to help with local debugging
This commit is contained in:
parent
95aef88a45
commit
056b035c98
7 changed files with 344 additions and 21 deletions
|
@ -235,9 +235,13 @@
|
|||
"Action": [
|
||||
"states:CreateStateMachine",
|
||||
"states:DeleteStateMachine",
|
||||
"states:DescribeExecution",
|
||||
"states:DescribeStateMachine",
|
||||
"states:ListExecutions",
|
||||
"states:ListStateMachines",
|
||||
"states:ListTagsForResource",
|
||||
"states:StartExecution",
|
||||
"states:StopExecution",
|
||||
"states:TagResource",
|
||||
"states:UntagResource",
|
||||
"states:UpdateStateMachine"
|
||||
|
|
|
@ -101,6 +101,10 @@ groupings:
|
|||
- aws
|
||||
aws_ssm_parameter_store:
|
||||
- aws
|
||||
aws_step_functions_state_machine:
|
||||
- aws
|
||||
aws_step_functions_state_machine_execution:
|
||||
- aws
|
||||
aws_waf_condition:
|
||||
- aws
|
||||
aws_waf_facts:
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright (c) 2019, Prasad Katti (@prasadkatti)
|
||||
# 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
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: aws_step_functions_state_machine_execution
|
||||
|
||||
short_description: Start or stop execution of an AWS Step Functions state machine.
|
||||
|
||||
version_added: "2.10"
|
||||
|
||||
description:
|
||||
- Start or stop execution of a state machine in AWS Step Functions.
|
||||
|
||||
options:
|
||||
action:
|
||||
description: Desired action (start or stop) for a state machine execution.
|
||||
default: start
|
||||
choices: [ start, stop ]
|
||||
type: str
|
||||
name:
|
||||
description: Name of the execution.
|
||||
type: str
|
||||
execution_input:
|
||||
description: The JSON input data for the execution.
|
||||
type: json
|
||||
default: {}
|
||||
state_machine_arn:
|
||||
description: The ARN of the state machine that will be executed.
|
||||
type: str
|
||||
execution_arn:
|
||||
description: The ARN of the execution you wish to stop.
|
||||
type: str
|
||||
cause:
|
||||
description: A detailed explanation of the cause for stopping the execution.
|
||||
type: str
|
||||
default: ''
|
||||
error:
|
||||
description: The error code of the failure to pass in when stopping the execution.
|
||||
type: str
|
||||
default: ''
|
||||
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
|
||||
author:
|
||||
- Prasad Katti (@prasadkatti)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Start an execution of a state machine
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: an_execution_name
|
||||
execution_input: '{ "IsHelloWorldExample": true }'
|
||||
state_machine_arn: "arn:aws:states:us-west-2:682285639423:stateMachine:HelloWorldStateMachine"
|
||||
|
||||
- name: Stop an execution of a state machine
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "arn:aws:states:us-west-2:682285639423:execution:HelloWorldStateMachineCopy:a1e8e2b5-5dfe-d40e-d9e3-6201061047c8"
|
||||
cause: "cause of task failure"
|
||||
error: "error code of the failure"
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
execution_arn:
|
||||
description: ARN of the AWS Step Functions state machine execution.
|
||||
type: str
|
||||
returned: if action == start and changed == True
|
||||
sample: "arn:aws:states:us-west-2:682285639423:execution:HelloWorldStateMachineCopy:a1e8e2b5-5dfe-d40e-d9e3-6201061047c8"
|
||||
start_date:
|
||||
description: The date the execution is started.
|
||||
type: str
|
||||
returned: if action == start and changed == True
|
||||
sample: "2019-11-02T22:39:49.071000-07:00"
|
||||
stop_date:
|
||||
description: The date the execution is stopped.
|
||||
type: str
|
||||
returned: if action == stop
|
||||
sample: "2019-11-02T22:39:49.071000-07:00"
|
||||
'''
|
||||
|
||||
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
||||
|
||||
try:
|
||||
from botocore.exceptions import ClientError, BotoCoreError
|
||||
except ImportError:
|
||||
pass # caught by AnsibleAWSModule
|
||||
|
||||
|
||||
def start_execution(module, sfn_client):
|
||||
'''
|
||||
start_execution uses execution name to determine if a previous execution already exists.
|
||||
If an execution by the provided name exists, call client.start_execution will not be called.
|
||||
'''
|
||||
|
||||
state_machine_arn = module.params.get('state_machine_arn')
|
||||
name = module.params.get('name')
|
||||
execution_input = module.params.get('execution_input')
|
||||
|
||||
try:
|
||||
# list_executions is eventually consistent
|
||||
page_iterators = sfn_client.get_paginator('list_executions').paginate(stateMachineArn=state_machine_arn)
|
||||
|
||||
for execution in page_iterators.build_full_result()['executions']:
|
||||
if name == execution['name']:
|
||||
check_mode(module, msg='State machine execution already exists.', changed=False)
|
||||
module.exit_json(changed=False)
|
||||
|
||||
check_mode(module, msg='State machine execution would be started.', changed=True)
|
||||
res_execution = sfn_client.start_execution(
|
||||
stateMachineArn=state_machine_arn,
|
||||
name=name,
|
||||
input=execution_input
|
||||
)
|
||||
except (ClientError, BotoCoreError) as e:
|
||||
if e.response['Error']['Code'] == 'ExecutionAlreadyExists':
|
||||
# this will never be executed anymore
|
||||
module.exit_json(changed=False)
|
||||
module.fail_json_aws(e, msg="Failed to start execution.")
|
||||
|
||||
module.exit_json(changed=True, **camel_dict_to_snake_dict(res_execution))
|
||||
|
||||
|
||||
def stop_execution(module, sfn_client):
|
||||
|
||||
cause = module.params.get('cause')
|
||||
error = module.params.get('error')
|
||||
execution_arn = module.params.get('execution_arn')
|
||||
|
||||
try:
|
||||
# describe_execution is eventually consistent
|
||||
execution_status = sfn_client.describe_execution(executionArn=execution_arn)['status']
|
||||
if execution_status != 'RUNNING':
|
||||
check_mode(module, msg='State machine execution is not running.', changed=False)
|
||||
module.exit_json(changed=False)
|
||||
|
||||
check_mode(module, msg='State machine execution would be stopped.', changed=True)
|
||||
res = sfn_client.stop_execution(
|
||||
executionArn=execution_arn,
|
||||
cause=cause,
|
||||
error=error
|
||||
)
|
||||
except (ClientError, BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Failed to stop execution.")
|
||||
|
||||
module.exit_json(changed=True, **camel_dict_to_snake_dict(res))
|
||||
|
||||
|
||||
def check_mode(module, msg='', changed=False):
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=changed, output=msg)
|
||||
|
||||
|
||||
def main():
|
||||
module_args = dict(
|
||||
action=dict(choices=['start', 'stop'], default='start'),
|
||||
name=dict(type='str'),
|
||||
execution_input=dict(type='json', default={}),
|
||||
state_machine_arn=dict(type='str'),
|
||||
cause=dict(type='str', default=''),
|
||||
error=dict(type='str', default=''),
|
||||
execution_arn=dict(type='str')
|
||||
)
|
||||
module = AnsibleAWSModule(
|
||||
argument_spec=module_args,
|
||||
required_if=[('action', 'start', ['name', 'state_machine_arn']),
|
||||
('action', 'stop', ['execution_arn']),
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
sfn_client = module.client('stepfunctions')
|
||||
|
||||
action = module.params.get('action')
|
||||
if action == "start":
|
||||
start_execution(module, sfn_client)
|
||||
else:
|
||||
stop_execution(module, sfn_client)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,2 +1,3 @@
|
|||
cloud/aws
|
||||
shippable/aws/group2
|
||||
aws_step_functions_state_machine_execution
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# the random_num is generated in a set_fact task at the start of the testsuite
|
||||
state_machine_name: "{{ resource_prefix }}_step_functions_state_machine_ansible_test_{{ random_num }}"
|
||||
step_functions_role_name: "ansible-test-sts-{{ resource_prefix }}-step_functions-role"
|
||||
execution_name: "{{ resource_prefix }}_sfn_execution"
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
"HelloWorld": {
|
||||
"Type": "Pass",
|
||||
"Result": "Some other result",
|
||||
"Next": "Wait"
|
||||
},
|
||||
"Wait": {
|
||||
"Type": "Wait",
|
||||
"Seconds": 30,
|
||||
"End": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
---
|
||||
|
||||
- name: Integration test for AWS Step Function state machine module
|
||||
module_defaults:
|
||||
group/aws:
|
||||
aws_access_key: "{{ aws_access_key }}"
|
||||
aws_secret_key: "{{ aws_secret_key }}"
|
||||
security_token: "{{ security_token | default(omit) }}"
|
||||
region: "{{ aws_region }}"
|
||||
block:
|
||||
|
||||
# ==== Setup ==================================================
|
||||
|
||||
- name: Set connection information for all tasks
|
||||
set_fact:
|
||||
aws_connection_info: &aws_connection_info
|
||||
aws_access_key: "{{ aws_access_key }}"
|
||||
aws_secret_key: "{{ aws_secret_key }}"
|
||||
security_token: "{{ security_token }}"
|
||||
region: "{{ aws_region }}"
|
||||
no_log: yes
|
||||
|
||||
- name: Create IAM service role needed for Step Functions
|
||||
iam_role:
|
||||
name: "{{ step_functions_role_name }}"
|
||||
description: Role with permissions for AWS Step Functions actions.
|
||||
assume_role_policy_document: "{{ lookup('file', 'state_machines_iam_trust_policy.json') }}"
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: step_functions_role
|
||||
|
||||
- name: Pause a few seconds to ensure IAM role is available to next task
|
||||
|
@ -41,7 +37,6 @@
|
|||
tags:
|
||||
project: helloWorld
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: creation_check
|
||||
check_mode: yes
|
||||
|
||||
|
@ -58,7 +53,6 @@
|
|||
tags:
|
||||
project: helloWorld
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: creation_output
|
||||
|
||||
- assert:
|
||||
|
@ -77,7 +71,6 @@
|
|||
tags:
|
||||
project: helloWorld
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: result
|
||||
check_mode: yes
|
||||
|
||||
|
@ -94,7 +87,6 @@
|
|||
tags:
|
||||
project: helloWorld
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -109,7 +101,6 @@
|
|||
tags:
|
||||
differentTag: different_tag
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: update_check
|
||||
check_mode: yes
|
||||
|
||||
|
@ -126,7 +117,6 @@
|
|||
tags:
|
||||
differentTag: different_tag
|
||||
state: present
|
||||
<<: *aws_connection_info
|
||||
register: update_output
|
||||
|
||||
- assert:
|
||||
|
@ -134,11 +124,136 @@
|
|||
- update_output.changed == True
|
||||
- update_output.state_machine_arn == creation_output.state_machine_arn
|
||||
|
||||
- name: Start execution of state machine -- check_mode
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: "{{ execution_name }}"
|
||||
execution_input: "{}"
|
||||
state_machine_arn: "{{ creation_output.state_machine_arn }}"
|
||||
register: start_execution_output
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- start_execution_output.changed == True
|
||||
- "start_execution_output.output == 'State machine execution would be started.'"
|
||||
|
||||
- name: Start execution of state machine
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: "{{ execution_name }}"
|
||||
execution_input: "{}"
|
||||
state_machine_arn: "{{ creation_output.state_machine_arn }}"
|
||||
register: start_execution_output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- start_execution_output.changed
|
||||
- "'execution_arn' in start_execution_output"
|
||||
- "'start_date' in start_execution_output"
|
||||
|
||||
- name: Start execution of state machine (check for idempotency) (check mode)
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: "{{ execution_name }}"
|
||||
execution_input: "{}"
|
||||
state_machine_arn: "{{ creation_output.state_machine_arn }}"
|
||||
register: start_execution_output_idem_check
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not start_execution_output_idem_check.changed
|
||||
- "start_execution_output_idem_check.output == 'State machine execution already exists.'"
|
||||
|
||||
- name: Start execution of state machine (check for idempotency)
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: "{{ execution_name }}"
|
||||
execution_input: "{}"
|
||||
state_machine_arn: "{{ creation_output.state_machine_arn }}"
|
||||
register: start_execution_output_idem
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not start_execution_output_idem.changed
|
||||
|
||||
- name: Stop execution of state machine -- check_mode
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "{{ start_execution_output.execution_arn }}"
|
||||
cause: "cause of the failure"
|
||||
error: "error code of the failure"
|
||||
register: stop_execution_output
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- stop_execution_output.changed
|
||||
- "stop_execution_output.output == 'State machine execution would be stopped.'"
|
||||
|
||||
- name: Stop execution of state machine
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "{{ start_execution_output.execution_arn }}"
|
||||
cause: "cause of the failure"
|
||||
error: "error code of the failure"
|
||||
register: stop_execution_output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- stop_execution_output.changed
|
||||
- "'stop_date' in stop_execution_output"
|
||||
|
||||
- name: Stop execution of state machine (check for idempotency)
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "{{ start_execution_output.execution_arn }}"
|
||||
cause: "cause of the failure"
|
||||
error: "error code of the failure"
|
||||
register: stop_execution_output
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not stop_execution_output.changed
|
||||
|
||||
- name: Try stopping a non-running execution -- check_mode
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "{{ start_execution_output.execution_arn }}"
|
||||
cause: "cause of the failure"
|
||||
error: "error code of the failure"
|
||||
register: stop_execution_output
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not stop_execution_output.changed
|
||||
- "stop_execution_output.output == 'State machine execution is not running.'"
|
||||
|
||||
- name: Try stopping a non-running execution
|
||||
aws_step_functions_state_machine_execution:
|
||||
action: stop
|
||||
execution_arn: "{{ start_execution_output.execution_arn }}"
|
||||
cause: "cause of the failure"
|
||||
error: "error code of the failure"
|
||||
register: stop_execution_output
|
||||
check_mode: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not stop_execution_output.changed
|
||||
|
||||
- name: Start execution of state machine with the same execution name
|
||||
aws_step_functions_state_machine_execution:
|
||||
name: "{{ execution_name }}"
|
||||
state_machine_arn: "{{ creation_output.state_machine_arn }}"
|
||||
register: start_execution_output_again
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- not start_execution_output_again.changed
|
||||
|
||||
- name: Remove state machine -- check_mode
|
||||
aws_step_functions_state_machine:
|
||||
name: "{{ state_machine_name }}"
|
||||
state: absent
|
||||
<<: *aws_connection_info
|
||||
register: deletion_check
|
||||
check_mode: yes
|
||||
|
||||
|
@ -151,7 +266,6 @@
|
|||
aws_step_functions_state_machine:
|
||||
name: "{{ state_machine_name }}"
|
||||
state: absent
|
||||
<<: *aws_connection_info
|
||||
register: deletion_output
|
||||
|
||||
- assert:
|
||||
|
@ -163,7 +277,6 @@
|
|||
aws_step_functions_state_machine:
|
||||
name: "non_existing_state_machine"
|
||||
state: absent
|
||||
<<: *aws_connection_info
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -178,12 +291,10 @@
|
|||
aws_step_functions_state_machine:
|
||||
name: "{{ state_machine_name }}"
|
||||
state: absent
|
||||
<<: *aws_connection_info
|
||||
ignore_errors: true
|
||||
|
||||
- name: Cleanup - delete IAM role needed for Step Functions test
|
||||
iam_role:
|
||||
name: "{{ step_functions_role_name }}"
|
||||
state: absent
|
||||
<<: *aws_connection_info
|
||||
ignore_errors: true
|
||||
|
|
Loading…
Reference in a new issue