Step Functions - State Machines methods
This commit is contained in:
parent
d4aa55760c
commit
af4082f38e
@ -6050,19 +6050,19 @@
|
|||||||
## stepfunctions
|
## stepfunctions
|
||||||
0% implemented
|
0% implemented
|
||||||
- [ ] create_activity
|
- [ ] create_activity
|
||||||
- [ ] create_state_machine
|
- [X] create_state_machine
|
||||||
- [ ] delete_activity
|
- [ ] delete_activity
|
||||||
- [ ] delete_state_machine
|
- [X] delete_state_machine
|
||||||
- [ ] describe_activity
|
- [ ] describe_activity
|
||||||
- [ ] describe_execution
|
- [ ] describe_execution
|
||||||
- [ ] describe_state_machine
|
- [X] describe_state_machine
|
||||||
- [ ] describe_state_machine_for_execution
|
- [ ] describe_state_machine_for_execution
|
||||||
- [ ] get_activity_task
|
- [ ] get_activity_task
|
||||||
- [ ] get_execution_history
|
- [ ] get_execution_history
|
||||||
- [ ] list_activities
|
- [ ] list_activities
|
||||||
- [ ] list_executions
|
- [ ] list_executions
|
||||||
- [ ] list_state_machines
|
- [X] list_state_machines
|
||||||
- [ ] list_tags_for_resource
|
- [X] list_tags_for_resource
|
||||||
- [ ] send_task_failure
|
- [ ] send_task_failure
|
||||||
- [ ] send_task_heartbeat
|
- [ ] send_task_heartbeat
|
||||||
- [ ] send_task_success
|
- [ ] send_task_success
|
||||||
|
@ -94,6 +94,8 @@ Currently implemented Services:
|
|||||||
+---------------------------+-----------------------+------------------------------------+
|
+---------------------------+-----------------------+------------------------------------+
|
||||||
| SES | @mock_ses | all endpoints done |
|
| SES | @mock_ses | all endpoints done |
|
||||||
+---------------------------+-----------------------+------------------------------------+
|
+---------------------------+-----------------------+------------------------------------+
|
||||||
|
| SFN | @mock_stepfunctions | basic endpoints done |
|
||||||
|
+---------------------------+-----------------------+------------------------------------+
|
||||||
| SNS | @mock_sns | all endpoints done |
|
| SNS | @mock_sns | all endpoints done |
|
||||||
+---------------------------+-----------------------+------------------------------------+
|
+---------------------------+-----------------------+------------------------------------+
|
||||||
| SQS | @mock_sqs | core endpoints done |
|
| SQS | @mock_sqs | core endpoints done |
|
||||||
|
@ -42,6 +42,7 @@ from .ses import mock_ses, mock_ses_deprecated # flake8: noqa
|
|||||||
from .secretsmanager import mock_secretsmanager # flake8: noqa
|
from .secretsmanager import mock_secretsmanager # flake8: noqa
|
||||||
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
|
from .sns import mock_sns, mock_sns_deprecated # flake8: noqa
|
||||||
from .sqs import mock_sqs, mock_sqs_deprecated # flake8: noqa
|
from .sqs import mock_sqs, mock_sqs_deprecated # flake8: noqa
|
||||||
|
from .stepfunctions import mock_stepfunctions # flake8: noqa
|
||||||
from .sts import mock_sts, mock_sts_deprecated # flake8: noqa
|
from .sts import mock_sts, mock_sts_deprecated # flake8: noqa
|
||||||
from .ssm import mock_ssm # flake8: noqa
|
from .ssm import mock_ssm # flake8: noqa
|
||||||
from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa
|
from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa
|
||||||
|
@ -40,6 +40,7 @@ from moto.secretsmanager import secretsmanager_backends
|
|||||||
from moto.sns import sns_backends
|
from moto.sns import sns_backends
|
||||||
from moto.sqs import sqs_backends
|
from moto.sqs import sqs_backends
|
||||||
from moto.ssm import ssm_backends
|
from moto.ssm import ssm_backends
|
||||||
|
from moto.stepfunctions import stepfunction_backends
|
||||||
from moto.sts import sts_backends
|
from moto.sts import sts_backends
|
||||||
from moto.swf import swf_backends
|
from moto.swf import swf_backends
|
||||||
from moto.xray import xray_backends
|
from moto.xray import xray_backends
|
||||||
@ -91,6 +92,7 @@ BACKENDS = {
|
|||||||
'sns': sns_backends,
|
'sns': sns_backends,
|
||||||
'sqs': sqs_backends,
|
'sqs': sqs_backends,
|
||||||
'ssm': ssm_backends,
|
'ssm': ssm_backends,
|
||||||
|
'stepfunctions': stepfunction_backends,
|
||||||
'sts': sts_backends,
|
'sts': sts_backends,
|
||||||
'swf': swf_backends,
|
'swf': swf_backends,
|
||||||
'route53': route53_backends,
|
'route53': route53_backends,
|
||||||
|
6
moto/stepfunctions/__init__.py
Normal file
6
moto/stepfunctions/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from .models import stepfunction_backends
|
||||||
|
from ..core.models import base_decorator
|
||||||
|
|
||||||
|
stepfunction_backend = stepfunction_backends['us-east-1']
|
||||||
|
mock_stepfunctions = base_decorator(stepfunction_backends)
|
35
moto/stepfunctions/exceptions.py
Normal file
35
moto/stepfunctions/exceptions.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class AWSError(Exception):
|
||||||
|
CODE = None
|
||||||
|
STATUS = 400
|
||||||
|
|
||||||
|
def __init__(self, message, code=None, status=None):
|
||||||
|
self.message = message
|
||||||
|
self.code = code if code is not None else self.CODE
|
||||||
|
self.status = status if status is not None else self.STATUS
|
||||||
|
|
||||||
|
def response(self):
|
||||||
|
return json.dumps({'__type': self.code, 'message': self.message}), dict(status=self.status)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessDeniedException(AWSError):
|
||||||
|
CODE = 'AccessDeniedException'
|
||||||
|
STATUS = 400
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidArn(AWSError):
|
||||||
|
CODE = 'InvalidArn'
|
||||||
|
STATUS = 400
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidName(AWSError):
|
||||||
|
CODE = 'InvalidName'
|
||||||
|
STATUS = 400
|
||||||
|
|
||||||
|
|
||||||
|
class StateMachineDoesNotExist(AWSError):
|
||||||
|
CODE = 'StateMachineDoesNotExist'
|
||||||
|
STATUS = 400
|
121
moto/stepfunctions/models.py
Normal file
121
moto/stepfunctions/models.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import boto
|
||||||
|
import boto3
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from moto.core import BaseBackend
|
||||||
|
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
||||||
|
from .exceptions import AccessDeniedException, InvalidArn, InvalidName, StateMachineDoesNotExist
|
||||||
|
|
||||||
|
|
||||||
|
class StateMachine():
|
||||||
|
def __init__(self, arn, name, definition, roleArn, tags=None):
|
||||||
|
self.creation_date = iso_8601_datetime_without_milliseconds(datetime.now())
|
||||||
|
self.arn = arn
|
||||||
|
self.name = name
|
||||||
|
self.definition = definition
|
||||||
|
self.roleArn = roleArn
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
|
||||||
|
class StepFunctionBackend(BaseBackend):
|
||||||
|
|
||||||
|
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/stepfunctions.html#SFN.Client.create_state_machine
|
||||||
|
# A name must not contain:
|
||||||
|
# whitespace
|
||||||
|
# brackets < > { } [ ]
|
||||||
|
# wildcard characters ? *
|
||||||
|
# special characters " # % \ ^ | ~ ` $ & , ; : /
|
||||||
|
invalid_chars_for_name = [' ', '{', '}', '[', ']', '<', '>',
|
||||||
|
'?', '*',
|
||||||
|
'"', '#', '%', '\\', '^', '|', '~', '`', '$', '&', ',', ';', ':', '/']
|
||||||
|
# control characters (U+0000-001F , U+007F-009F )
|
||||||
|
invalid_unicodes_for_name = [u'\u0000', u'\u0001', u'\u0002', u'\u0003', u'\u0004',
|
||||||
|
u'\u0005', u'\u0006', u'\u0007', u'\u0008', u'\u0009',
|
||||||
|
u'\u000A', u'\u000B', u'\u000C', u'\u000D', u'\u000E', u'\u000F',
|
||||||
|
u'\u0010', u'\u0011', u'\u0012', u'\u0013', u'\u0014',
|
||||||
|
u'\u0015', u'\u0016', u'\u0017', u'\u0018', u'\u0019',
|
||||||
|
u'\u001A', u'\u001B', u'\u001C', u'\u001D', u'\u001E', u'\u001F',
|
||||||
|
u'\u007F',
|
||||||
|
u'\u0080', u'\u0081', u'\u0082', u'\u0083', u'\u0084', u'\u0085',
|
||||||
|
u'\u0086', u'\u0087', u'\u0088', u'\u0089',
|
||||||
|
u'\u008A', u'\u008B', u'\u008C', u'\u008D', u'\u008E', u'\u008F',
|
||||||
|
u'\u0090', u'\u0091', u'\u0092', u'\u0093', u'\u0094', u'\u0095',
|
||||||
|
u'\u0096', u'\u0097', u'\u0098', u'\u0099',
|
||||||
|
u'\u009A', u'\u009B', u'\u009C', u'\u009D', u'\u009E', u'\u009F']
|
||||||
|
accepted_role_arn_format = re.compile('arn:aws:iam:(?P<account_id>[0-9]{12}):role/.+')
|
||||||
|
accepted_mchn_arn_format = re.compile('arn:aws:states:[-0-9a-zA-Z]+:(?P<account_id>[0-9]{12}):stateMachine:.+')
|
||||||
|
|
||||||
|
def __init__(self, region_name):
|
||||||
|
self.state_machines = []
|
||||||
|
self.region_name = region_name
|
||||||
|
self._account_id = None
|
||||||
|
|
||||||
|
def create_state_machine(self, name, definition, roleArn, tags=None):
|
||||||
|
self._validate_name(name)
|
||||||
|
self._validate_role_arn(roleArn)
|
||||||
|
arn = 'arn:aws:states:' + self.region_name + ':' + str(self._get_account_id()) + ':stateMachine:' + name
|
||||||
|
try:
|
||||||
|
return self.describe_state_machine(arn)
|
||||||
|
except StateMachineDoesNotExist:
|
||||||
|
state_machine = StateMachine(arn, name, definition, roleArn, tags)
|
||||||
|
self.state_machines.append(state_machine)
|
||||||
|
return state_machine
|
||||||
|
|
||||||
|
def list_state_machines(self):
|
||||||
|
return self.state_machines
|
||||||
|
|
||||||
|
def describe_state_machine(self, arn):
|
||||||
|
self._validate_machine_arn(arn)
|
||||||
|
sm = next((x for x in self.state_machines if x.arn == arn), None)
|
||||||
|
if not sm:
|
||||||
|
raise StateMachineDoesNotExist("State Machine Does Not Exist: '" + arn + "'")
|
||||||
|
return sm
|
||||||
|
|
||||||
|
def delete_state_machine(self, arn):
|
||||||
|
self._validate_machine_arn(arn)
|
||||||
|
sm = next((x for x in self.state_machines if x.arn == arn), None)
|
||||||
|
if sm:
|
||||||
|
self.state_machines.remove(sm)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
region_name = self.region_name
|
||||||
|
self.__dict__ = {}
|
||||||
|
self.__init__(region_name)
|
||||||
|
|
||||||
|
def _validate_name(self, name):
|
||||||
|
if any(invalid_char in name for invalid_char in self.invalid_chars_for_name):
|
||||||
|
raise InvalidName("Invalid Name: '" + name + "'")
|
||||||
|
|
||||||
|
if any(name.find(char) >= 0 for char in self.invalid_unicodes_for_name):
|
||||||
|
raise InvalidName("Invalid Name: '" + name + "'")
|
||||||
|
|
||||||
|
def _validate_role_arn(self, role_arn):
|
||||||
|
self._validate_arn(arn=role_arn,
|
||||||
|
regex=self.accepted_role_arn_format,
|
||||||
|
invalid_msg="Invalid Role Arn: '" + role_arn + "'",
|
||||||
|
access_denied_msg='Cross-account pass role is not allowed.')
|
||||||
|
|
||||||
|
def _validate_machine_arn(self, machine_arn):
|
||||||
|
self._validate_arn(arn=machine_arn,
|
||||||
|
regex=self.accepted_mchn_arn_format,
|
||||||
|
invalid_msg="Invalid Role Arn: '" + machine_arn + "'",
|
||||||
|
access_denied_msg='User moto is not authorized to access this resource')
|
||||||
|
|
||||||
|
def _validate_arn(self, arn, regex, invalid_msg, access_denied_msg):
|
||||||
|
match = regex.match(arn)
|
||||||
|
if not arn or not match:
|
||||||
|
raise InvalidArn(invalid_msg)
|
||||||
|
|
||||||
|
if self._get_account_id() != match.group('account_id'):
|
||||||
|
raise AccessDeniedException(access_denied_msg)
|
||||||
|
|
||||||
|
def _get_account_id(self):
|
||||||
|
if self._account_id:
|
||||||
|
return self._account_id
|
||||||
|
sts = boto3.client("sts")
|
||||||
|
identity = sts.get_caller_identity()
|
||||||
|
self._account_id = identity['Account']
|
||||||
|
return self._account_id
|
||||||
|
|
||||||
|
|
||||||
|
stepfunction_backends = {_region.name: StepFunctionBackend(_region.name) for _region in boto.awslambda.regions()}
|
80
moto/stepfunctions/responses.py
Normal file
80
moto/stepfunctions/responses.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from moto.core.responses import BaseResponse
|
||||||
|
from moto.core.utils import amzn_request_id
|
||||||
|
from .exceptions import AWSError
|
||||||
|
from .models import stepfunction_backends
|
||||||
|
|
||||||
|
|
||||||
|
class StepFunctionResponse(BaseResponse):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stepfunction_backend(self):
|
||||||
|
return stepfunction_backends[self.region]
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def create_state_machine(self):
|
||||||
|
name = self._get_param('name')
|
||||||
|
definition = self._get_param('definition')
|
||||||
|
roleArn = self._get_param('roleArn')
|
||||||
|
tags = self._get_param('tags')
|
||||||
|
try:
|
||||||
|
state_machine = self.stepfunction_backend.create_state_machine(name=name, definition=definition,
|
||||||
|
roleArn=roleArn,
|
||||||
|
tags=tags)
|
||||||
|
response = {
|
||||||
|
'creationDate': state_machine.creation_date,
|
||||||
|
'stateMachineArn': state_machine.arn
|
||||||
|
}
|
||||||
|
return 200, {}, json.dumps(response)
|
||||||
|
except AWSError as err:
|
||||||
|
return err.response()
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def list_state_machines(self):
|
||||||
|
list_all = self.stepfunction_backend.list_state_machines()
|
||||||
|
list_all = sorted([{'creationDate': sm.creation_date,
|
||||||
|
'name': sm.name,
|
||||||
|
'stateMachineArn': sm.arn} for sm in list_all],
|
||||||
|
key=lambda x: x['name'])
|
||||||
|
response = {'stateMachines': list_all}
|
||||||
|
return 200, {}, json.dumps(response)
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def describe_state_machine(self):
|
||||||
|
arn = self._get_param('stateMachineArn')
|
||||||
|
try:
|
||||||
|
state_machine = self.stepfunction_backend.describe_state_machine(arn)
|
||||||
|
response = {
|
||||||
|
'creationDate': state_machine.creation_date,
|
||||||
|
'stateMachineArn': state_machine.arn,
|
||||||
|
'definition': state_machine.definition,
|
||||||
|
'name': state_machine.name,
|
||||||
|
'roleArn': state_machine.roleArn,
|
||||||
|
'status': 'ACTIVE'
|
||||||
|
}
|
||||||
|
return 200, {}, json.dumps(response)
|
||||||
|
except AWSError as err:
|
||||||
|
return err.response()
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def delete_state_machine(self):
|
||||||
|
arn = self._get_param('stateMachineArn')
|
||||||
|
try:
|
||||||
|
self.stepfunction_backend.delete_state_machine(arn)
|
||||||
|
return 200, {}, json.dumps('{}')
|
||||||
|
except AWSError as err:
|
||||||
|
return err.response()
|
||||||
|
|
||||||
|
@amzn_request_id
|
||||||
|
def list_tags_for_resource(self):
|
||||||
|
arn = self._get_param('resourceArn')
|
||||||
|
try:
|
||||||
|
state_machine = self.stepfunction_backend.describe_state_machine(arn)
|
||||||
|
tags = state_machine.tags or []
|
||||||
|
except AWSError:
|
||||||
|
tags = []
|
||||||
|
response = {'tags': tags}
|
||||||
|
return 200, {}, json.dumps(response)
|
10
moto/stepfunctions/urls.py
Normal file
10
moto/stepfunctions/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
from .responses import StepFunctionResponse
|
||||||
|
|
||||||
|
url_bases = [
|
||||||
|
"https?://states.(.+).amazonaws.com",
|
||||||
|
]
|
||||||
|
|
||||||
|
url_paths = {
|
||||||
|
'{0}/$': StepFunctionResponse.dispatch,
|
||||||
|
}
|
276
tests/test_stepfunctions/test_stepfunctions.py
Normal file
276
tests/test_stepfunctions/test_stepfunctions.py
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
import sure # noqa
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
from moto.config.models import DEFAULT_ACCOUNT_ID
|
||||||
|
from nose.tools import assert_raises
|
||||||
|
|
||||||
|
from moto import mock_sts, mock_stepfunctions
|
||||||
|
|
||||||
|
|
||||||
|
region = 'us-east-1'
|
||||||
|
simple_definition = '{"Comment": "An example of the Amazon States Language using a choice state.",' \
|
||||||
|
'"StartAt": "DefaultState",' \
|
||||||
|
'"States": ' \
|
||||||
|
'{"DefaultState": {"Type": "Fail","Error": "DefaultStateError","Cause": "No Matches!"}}}'
|
||||||
|
default_stepfunction_role = 'arn:aws:iam:' + str(DEFAULT_ACCOUNT_ID) + ':role/unknown_sf_role'
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_creation_succeeds():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
name = 'example_step_function'
|
||||||
|
#
|
||||||
|
response = client.create_state_machine(name=name,
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role)
|
||||||
|
#
|
||||||
|
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||||
|
response['creationDate'].should.be.a(datetime)
|
||||||
|
response['stateMachineArn'].should.equal('arn:aws:states:' + region + ':123456789012:stateMachine:' + name)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
def test_state_machine_creation_fails_with_invalid_names():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
invalid_names = [
|
||||||
|
'with space',
|
||||||
|
'with<bracket', 'with>bracket', 'with{bracket', 'with}bracket', 'with[bracket', 'with]bracket',
|
||||||
|
'with?wildcard', 'with*wildcard',
|
||||||
|
'special"char', 'special#char', 'special%char', 'special\\char', 'special^char', 'special|char',
|
||||||
|
'special~char', 'special`char', 'special$char', 'special&char', 'special,char', 'special;char',
|
||||||
|
'special:char', 'special/char',
|
||||||
|
u'uni\u0000code', u'uni\u0001code', u'uni\u0002code', u'uni\u0003code', u'uni\u0004code',
|
||||||
|
u'uni\u0005code', u'uni\u0006code', u'uni\u0007code', u'uni\u0008code', u'uni\u0009code',
|
||||||
|
u'uni\u000Acode', u'uni\u000Bcode', u'uni\u000Ccode',
|
||||||
|
u'uni\u000Dcode', u'uni\u000Ecode', u'uni\u000Fcode',
|
||||||
|
u'uni\u0010code', u'uni\u0011code', u'uni\u0012code', u'uni\u0013code', u'uni\u0014code',
|
||||||
|
u'uni\u0015code', u'uni\u0016code', u'uni\u0017code', u'uni\u0018code', u'uni\u0019code',
|
||||||
|
u'uni\u001Acode', u'uni\u001Bcode', u'uni\u001Ccode',
|
||||||
|
u'uni\u001Dcode', u'uni\u001Ecode', u'uni\u001Fcode',
|
||||||
|
u'uni\u007Fcode',
|
||||||
|
u'uni\u0080code', u'uni\u0081code', u'uni\u0082code', u'uni\u0083code', u'uni\u0084code',
|
||||||
|
u'uni\u0085code', u'uni\u0086code', u'uni\u0087code', u'uni\u0088code', u'uni\u0089code',
|
||||||
|
u'uni\u008Acode', u'uni\u008Bcode', u'uni\u008Ccode',
|
||||||
|
u'uni\u008Dcode', u'uni\u008Ecode', u'uni\u008Fcode',
|
||||||
|
u'uni\u0090code', u'uni\u0091code', u'uni\u0092code', u'uni\u0093code', u'uni\u0094code',
|
||||||
|
u'uni\u0095code', u'uni\u0096code', u'uni\u0097code', u'uni\u0098code', u'uni\u0099code',
|
||||||
|
u'uni\u009Acode', u'uni\u009Bcode', u'uni\u009Ccode',
|
||||||
|
u'uni\u009Dcode', u'uni\u009Ecode', u'uni\u009Fcode']
|
||||||
|
#
|
||||||
|
|
||||||
|
for invalid_name in invalid_names:
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
client.create_state_machine(name=invalid_name,
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role)
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('InvalidName')
|
||||||
|
exc.exception.response['Error']['Message'].should.equal("Invalid Name: '" + invalid_name + "'")
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
def test_state_machine_creation_requires_valid_role_arn():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
name = 'example_step_function'
|
||||||
|
#
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
client.create_state_machine(name=name,
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn='arn:aws:iam:1234:role/unknown_role')
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('InvalidArn')
|
||||||
|
exc.exception.response['Error']['Message'].should.equal("Invalid Role Arn: 'arn:aws:iam:1234:role/unknown_role'")
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_creation_requires_role_in_same_account():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
name = 'example_step_function'
|
||||||
|
#
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
client.create_state_machine(name=name,
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn='arn:aws:iam:000000000000:role/unknown_role')
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('AccessDeniedException')
|
||||||
|
exc.exception.response['Error']['Message'].should.equal('Cross-account pass role is not allowed.')
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
def test_state_machine_list_returns_empty_list_by_default():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
list = client.list_state_machines()
|
||||||
|
list['stateMachines'].should.be.empty
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_list_returns_created_state_machines():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
machine2 = client.create_state_machine(name='name2',
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role)
|
||||||
|
machine1 = client.create_state_machine(name='name1',
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role,
|
||||||
|
tags=[{'key': 'tag_key', 'value': 'tag_value'}])
|
||||||
|
list = client.list_state_machines()
|
||||||
|
#
|
||||||
|
list['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||||
|
list['stateMachines'].should.have.length_of(2)
|
||||||
|
list['stateMachines'][0]['creationDate'].should.be.a(datetime)
|
||||||
|
list['stateMachines'][0]['creationDate'].should.equal(machine1['creationDate'])
|
||||||
|
list['stateMachines'][0]['name'].should.equal('name1')
|
||||||
|
list['stateMachines'][0]['stateMachineArn'].should.equal(machine1['stateMachineArn'])
|
||||||
|
list['stateMachines'][1]['creationDate'].should.be.a(datetime)
|
||||||
|
list['stateMachines'][1]['creationDate'].should.equal(machine2['creationDate'])
|
||||||
|
list['stateMachines'][1]['name'].should.equal('name2')
|
||||||
|
list['stateMachines'][1]['stateMachineArn'].should.equal(machine2['stateMachineArn'])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_creation_is_idempotent_by_name():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
|
||||||
|
sm_list = client.list_state_machines()
|
||||||
|
sm_list['stateMachines'].should.have.length_of(1)
|
||||||
|
#
|
||||||
|
client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
|
||||||
|
sm_list = client.list_state_machines()
|
||||||
|
sm_list['stateMachines'].should.have.length_of(1)
|
||||||
|
#
|
||||||
|
client.create_state_machine(name='diff_name', definition=str(simple_definition), roleArn=default_stepfunction_role)
|
||||||
|
sm_list = client.list_state_machines()
|
||||||
|
sm_list['stateMachines'].should.have.length_of(2)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_creation_can_be_described_by_name():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
|
||||||
|
desc = client.describe_state_machine(stateMachineArn=sm['stateMachineArn'])
|
||||||
|
desc['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||||
|
desc['creationDate'].should.equal(sm['creationDate'])
|
||||||
|
desc['definition'].should.equal(str(simple_definition))
|
||||||
|
desc['name'].should.equal('name')
|
||||||
|
desc['roleArn'].should.equal(default_stepfunction_role)
|
||||||
|
desc['stateMachineArn'].should.equal(sm['stateMachineArn'])
|
||||||
|
desc['status'].should.equal('ACTIVE')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_throws_error_when_describing_unknown_machine():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
unknown_state_machine = 'arn:aws:states:' + region + ':' + str(DEFAULT_ACCOUNT_ID) + ':stateMachine:unknown'
|
||||||
|
client.describe_state_machine(stateMachineArn=unknown_state_machine)
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('StateMachineDoesNotExist')
|
||||||
|
exc.exception.response['Error']['Message'].\
|
||||||
|
should.equal("State Machine Does Not Exist: '" + unknown_state_machine + "'")
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_throws_error_when_describing_machine_in_different_account():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
unknown_state_machine = 'arn:aws:states:' + region + ':000000000000:stateMachine:unknown'
|
||||||
|
client.describe_state_machine(stateMachineArn=unknown_state_machine)
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('AccessDeniedException')
|
||||||
|
exc.exception.response['Error']['Message'].should.contain('is not authorized to access this resource')
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_can_be_deleted():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
sm = client.create_state_machine(name='name', definition=str(simple_definition), roleArn=default_stepfunction_role)
|
||||||
|
#
|
||||||
|
response = client.delete_state_machine(stateMachineArn=sm['stateMachineArn'])
|
||||||
|
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||||
|
#
|
||||||
|
sm_list = client.list_state_machines()
|
||||||
|
sm_list['stateMachines'].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_can_deleted_nonexisting_machine():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
unknown_state_machine = 'arn:aws:states:' + region + ':123456789012:stateMachine:unknown'
|
||||||
|
response = client.delete_state_machine(stateMachineArn=unknown_state_machine)
|
||||||
|
response['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||||
|
#
|
||||||
|
sm_list = client.list_state_machines()
|
||||||
|
sm_list['stateMachines'].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_deletion_validates_arn():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
unknown_account_id = 'arn:aws:states:' + region + ':000000000000:stateMachine:unknown'
|
||||||
|
client.delete_state_machine(stateMachineArn=unknown_account_id)
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('AccessDeniedException')
|
||||||
|
exc.exception.response['Error']['Message'].should.contain('is not authorized to access this resource')
|
||||||
|
exc.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_list_tags_for_created_machine():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
machine = client.create_state_machine(name='name1',
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role,
|
||||||
|
tags=[{'key': 'tag_key', 'value': 'tag_value'}])
|
||||||
|
response = client.list_tags_for_resource(resourceArn=machine['stateMachineArn'])
|
||||||
|
tags = response['tags']
|
||||||
|
tags.should.have.length_of(1)
|
||||||
|
tags[0].should.equal({'key': 'tag_key', 'value': 'tag_value'})
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_list_tags_for_machine_without_tags():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
machine = client.create_state_machine(name='name1',
|
||||||
|
definition=str(simple_definition),
|
||||||
|
roleArn=default_stepfunction_role)
|
||||||
|
response = client.list_tags_for_resource(resourceArn=machine['stateMachineArn'])
|
||||||
|
tags = response['tags']
|
||||||
|
tags.should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_stepfunctions
|
||||||
|
@mock_sts
|
||||||
|
def test_state_machine_list_tags_for_nonexisting_machine():
|
||||||
|
client = boto3.client('stepfunctions', region_name=region)
|
||||||
|
#
|
||||||
|
non_existing_state_machine = 'arn:aws:states:' + region + ':' + str(DEFAULT_ACCOUNT_ID) + ':stateMachine:unknown'
|
||||||
|
response = client.list_tags_for_resource(resourceArn=non_existing_state_machine)
|
||||||
|
tags = response['tags']
|
||||||
|
tags.should.have.length_of(0)
|
Loading…
Reference in New Issue
Block a user