diff --git a/scripts/int_test.sh b/scripts/int_test.sh index f57bb157f..27fad910a 100755 --- a/scripts/int_test.sh +++ b/scripts/int_test.sh @@ -4,6 +4,9 @@ # Runs a test to verify whether each service has the correct dependencies listed in setup.py # +# Tests that depend on multiple services are assumed to be located in dedicated testfiles (and ignored during this test) +# (test_*_integration.py/test_*_cloudformation.py) +# # ::Algorithm:: # For each valid service: # - Create a virtual environment @@ -48,13 +51,14 @@ test_service() { # Can't just install requirements-file, as it points to all dependencies pip install -r requirements-tests.txt > /dev/null pip install .[$service] > /dev/null 2>&1 + pip install boto > /dev/null 2>&1 # Restart venv - ensure these deps are loaded deactivate source ${venv_path}/bin/activate > /dev/null # Run tests for this service test_result_filename="test_results_${service}.log" touch $test_result_filename - nosetests -qxs --ignore-files="test_server\.py" --ignore-files="test_${service}_cloudformation\.py" --ignore-files="test_integration\.py" $path_to_test_file >$test_result_filename 2>&1 + pytest -sv --ignore-glob="**/test_server.py" --ignore-glob="**/test_*_cloudformation.py" --ignore-glob="**/test_*_integration.py" $path_to_test_file >$test_result_filename 2>&1 RESULT=$? if [[ $RESULT != 0 ]]; then echo -e "Tests for ${service} have failed!\n" diff --git a/tests/test_ec2/test_flow_logs.py b/tests/test_ec2/test_flow_logs.py index 9eff3674b..23812be11 100644 --- a/tests/test_ec2/test_flow_logs.py +++ b/tests/test_ec2/test_flow_logs.py @@ -6,14 +6,10 @@ import boto3 from botocore.exceptions import ClientError from botocore.parsers import ResponseParserError -import json import sure # noqa -import random -import sys from moto import ( settings, - mock_cloudformation, mock_ec2, mock_s3, mock_logs, @@ -630,48 +626,3 @@ def test_flow_logs_by_ids(): flow_logs = client.delete_flow_logs(FlowLogIds=[fl2]) flow_logs = client.describe_flow_logs()["FlowLogs"] flow_logs.should.have.length_of(0) - - -@mock_cloudformation -@mock_ec2 -@mock_s3 -def test_flow_logs_by_cloudformation(): - s3 = boto3.resource("s3", region_name="us-west-1") - client = boto3.client("ec2", region_name="us-west-1") - cf_client = boto3.client("cloudformation", "us-west-1") - - vpc = client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] - - bucket = s3.create_bucket( - Bucket="test-flow-logs", - CreateBucketConfiguration={"LocationConstraint": "us-west-1"}, - ) - - flow_log_template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Template for VPC Flow Logs creation.", - "Resources": { - "TestFlowLogs": { - "Type": "AWS::EC2::FlowLog", - "Properties": { - "ResourceType": "VPC", - "ResourceId": vpc["VpcId"], - "TrafficType": "ALL", - "LogDestinationType": "s3", - "LogDestination": "arn:aws:s3:::" + bucket.name, - "MaxAggregationInterval": "60", - "Tags": [{"Key": "foo", "Value": "bar"}], - }, - } - }, - } - flow_log_template_json = json.dumps(flow_log_template) - stack_id = cf_client.create_stack( - StackName="test_stack", TemplateBody=flow_log_template_json - )["StackId"] - - flow_logs = client.describe_flow_logs()["FlowLogs"] - flow_logs.should.have.length_of(1) - flow_logs[0]["ResourceId"].should.equal(vpc["VpcId"]) - flow_logs[0]["LogDestination"].should.equal("arn:aws:s3:::" + bucket.name) - flow_logs[0]["MaxAggregationInterval"].should.equal(60) diff --git a/tests/test_ec2/test_flow_logs_cloudformation.py b/tests/test_ec2/test_flow_logs_cloudformation.py new file mode 100644 index 000000000..baa86cd1d --- /dev/null +++ b/tests/test_ec2/test_flow_logs_cloudformation.py @@ -0,0 +1,95 @@ +from __future__ import unicode_literals + +import boto3 + +import json +import sure # noqa + +from moto import ( + mock_cloudformation, + mock_ec2, + mock_s3, +) +from tests import EXAMPLE_AMI_ID + + +@mock_cloudformation +@mock_ec2 +@mock_s3 +def test_flow_logs_by_cloudformation(): + s3 = boto3.resource("s3", region_name="us-west-1") + client = boto3.client("ec2", region_name="us-west-1") + cf_client = boto3.client("cloudformation", "us-west-1") + + vpc = client.create_vpc(CidrBlock="10.0.0.0/16")["Vpc"] + + bucket = s3.create_bucket( + Bucket="test-flow-logs", + CreateBucketConfiguration={"LocationConstraint": "us-west-1"}, + ) + + flow_log_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Template for VPC Flow Logs creation.", + "Resources": { + "TestFlowLogs": { + "Type": "AWS::EC2::FlowLog", + "Properties": { + "ResourceType": "VPC", + "ResourceId": vpc["VpcId"], + "TrafficType": "ALL", + "LogDestinationType": "s3", + "LogDestination": "arn:aws:s3:::" + bucket.name, + "MaxAggregationInterval": "60", + "Tags": [{"Key": "foo", "Value": "bar"}], + }, + } + }, + } + flow_log_template_json = json.dumps(flow_log_template) + stack_id = cf_client.create_stack( + StackName="test_stack", TemplateBody=flow_log_template_json + )["StackId"] + + flow_logs = client.describe_flow_logs()["FlowLogs"] + flow_logs.should.have.length_of(1) + flow_logs[0]["ResourceId"].should.equal(vpc["VpcId"]) + flow_logs[0]["LogDestination"].should.equal("arn:aws:s3:::" + bucket.name) + flow_logs[0]["MaxAggregationInterval"].should.equal(60) + + +@mock_ec2 +@mock_cloudformation +def test_cloudformation(): + dummy_template_json = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "InstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": {"Path": "/", "Roles": []}, + }, + "Ec2Instance": { + "Type": "AWS::EC2::Instance", + "Properties": { + "IamInstanceProfile": {"Ref": "InstanceProfile"}, + "KeyName": "mykey1", + "ImageId": EXAMPLE_AMI_ID, + }, + }, + }, + } + + client = boto3.client("ec2", region_name="us-east-1") + cf_conn = boto3.client("cloudformation", region_name="us-east-1") + cf_conn.create_stack( + StackName="test_stack", TemplateBody=json.dumps(dummy_template_json) + ) + associations = client.describe_iam_instance_profile_associations() + associations["IamInstanceProfileAssociations"].should.have.length_of(1) + associations["IamInstanceProfileAssociations"][0]["IamInstanceProfile"][ + "Arn" + ].should.contain("test_stack") + + cf_conn.delete_stack(StackName="test_stack") + associations = client.describe_iam_instance_profile_associations() + associations["IamInstanceProfileAssociations"].should.have.length_of(0) diff --git a/tests/test_ec2/test_iam_instance_profile_associations.py b/tests/test_ec2/test_iam_integration.py similarity index 88% rename from tests/test_ec2/test_iam_instance_profile_associations.py rename to tests/test_ec2/test_iam_integration.py index 379f614f3..0e7b0f142 100644 --- a/tests/test_ec2/test_iam_instance_profile_associations.py +++ b/tests/test_ec2/test_iam_integration.py @@ -307,40 +307,3 @@ def test_invalid_disassociate(): client.disassociate_iam_instance_profile(AssociationId="fake",) ex.value.response["Error"]["Code"].should.equal("InvalidAssociationID.NotFound") ex.value.response["Error"]["Message"].should.contain("An invalid association-id of") - - -@mock_ec2 -@mock_cloudformation -def test_cloudformation(): - dummy_template_json = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "InstanceProfile": { - "Type": "AWS::IAM::InstanceProfile", - "Properties": {"Path": "/", "Roles": []}, - }, - "Ec2Instance": { - "Type": "AWS::EC2::Instance", - "Properties": { - "IamInstanceProfile": {"Ref": "InstanceProfile"}, - "KeyName": "mykey1", - "ImageId": EXAMPLE_AMI_ID, - }, - }, - }, - } - - client = boto3.client("ec2", region_name="us-east-1") - cf_conn = boto3.client("cloudformation", region_name="us-east-1") - cf_conn.create_stack( - StackName="test_stack", TemplateBody=json.dumps(dummy_template_json) - ) - associations = client.describe_iam_instance_profile_associations() - associations["IamInstanceProfileAssociations"].should.have.length_of(1) - associations["IamInstanceProfileAssociations"][0]["IamInstanceProfile"][ - "Arn" - ].should.contain("test_stack") - - cf_conn.delete_stack(StackName="test_stack") - associations = client.describe_iam_instance_profile_associations() - associations["IamInstanceProfileAssociations"].should.have.length_of(0) diff --git a/tests/test_stepfunctions/test_stepfunctions.py b/tests/test_stepfunctions/test_stepfunctions.py index b0fad76fc..e2148df61 100644 --- a/tests/test_stepfunctions/test_stepfunctions.py +++ b/tests/test_stepfunctions/test_stepfunctions.py @@ -983,198 +983,6 @@ def test_state_machine_get_execution_history_contains_expected_failure_events_wh execution_history["events"].should.equal(expected_events) -@mock_stepfunctions -@mock_cloudformation -def test_state_machine_cloudformation(): - sf = boto3.client("stepfunctions", region_name="us-east-1") - cf = boto3.resource("cloudformation", region_name="us-east-1") - definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' - role_arn = ( - "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1;" - ) - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "An example template for a Step Functions state machine.", - "Resources": { - "MyStateMachine": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "StateMachineName": "HelloWorld-StateMachine", - "StateMachineType": "STANDARD", - "DefinitionString": definition, - "RoleArn": role_arn, - "Tags": [ - {"Key": "key1", "Value": "value1"}, - {"Key": "key2", "Value": "value2"}, - ], - }, - } - }, - "Outputs": { - "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, - "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, - }, - } - cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) - outputs_list = cf.Stack("test_stack").outputs - output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} - state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) - state_machine["stateMachineArn"].should.equal(output["StateMachineArn"]) - state_machine["name"].should.equal(output["StateMachineName"]) - state_machine["roleArn"].should.equal(role_arn) - state_machine["definition"].should.equal(definition) - tags = sf.list_tags_for_resource(resourceArn=output["StateMachineArn"]).get("tags") - for i, tag in enumerate(tags, 1): - tag["key"].should.equal("key{}".format(i)) - tag["value"].should.equal("value{}".format(i)) - - cf.Stack("test_stack").delete() - with pytest.raises(ClientError) as ex: - sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) - ex.value.response["Error"]["Code"].should.equal("StateMachineDoesNotExist") - ex.value.response["Error"]["Message"].should.contain("Does Not Exist") - - -@mock_stepfunctions -@mock_cloudformation -def test_state_machine_cloudformation_update_with_replacement(): - sf = boto3.client("stepfunctions", region_name="us-east-1") - cf = boto3.resource("cloudformation", region_name="us-east-1") - definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' - role_arn = ( - "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1" - ) - properties = { - "StateMachineName": "HelloWorld-StateMachine", - "DefinitionString": definition, - "RoleArn": role_arn, - "Tags": [ - {"Key": "key1", "Value": "value1"}, - {"Key": "key2", "Value": "value2"}, - ], - } - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "An example template for a Step Functions state machine.", - "Resources": { - "MyStateMachine": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": {}, - } - }, - "Outputs": { - "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, - "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, - }, - } - template["Resources"]["MyStateMachine"]["Properties"] = properties - cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) - outputs_list = cf.Stack("test_stack").outputs - output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} - state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) - original_machine_arn = state_machine["stateMachineArn"] - original_creation_date = state_machine["creationDate"] - - # Update State Machine, with replacement. - updated_role = role_arn + "-updated" - updated_definition = definition.replace("HelloWorld", "HelloWorld2") - updated_properties = { - "StateMachineName": "New-StateMachine-Name", - "DefinitionString": updated_definition, - "RoleArn": updated_role, - "Tags": [ - {"Key": "key3", "Value": "value3"}, - {"Key": "key1", "Value": "updated_value"}, - ], - } - template["Resources"]["MyStateMachine"]["Properties"] = updated_properties - cf.Stack("test_stack").update(TemplateBody=json.dumps(template)) - outputs_list = cf.Stack("test_stack").outputs - output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} - state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) - state_machine["stateMachineArn"].should_not.equal(original_machine_arn) - state_machine["name"].should.equal("New-StateMachine-Name") - state_machine["creationDate"].should.be.greater_than(original_creation_date) - state_machine["roleArn"].should.equal(updated_role) - state_machine["definition"].should.equal(updated_definition) - tags = sf.list_tags_for_resource(resourceArn=output["StateMachineArn"]).get("tags") - tags.should.have.length_of(3) - for tag in tags: - if tag["key"] == "key1": - tag["value"].should.equal("updated_value") - - with pytest.raises(ClientError) as ex: - sf.describe_state_machine(stateMachineArn=original_machine_arn) - ex.value.response["Error"]["Code"].should.equal("StateMachineDoesNotExist") - ex.value.response["Error"]["Message"].should.contain("State Machine Does Not Exist") - - -@mock_stepfunctions -@mock_cloudformation -def test_state_machine_cloudformation_update_with_no_interruption(): - sf = boto3.client("stepfunctions", region_name="us-east-1") - cf = boto3.resource("cloudformation", region_name="us-east-1") - definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' - role_arn = ( - "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1" - ) - properties = { - "StateMachineName": "HelloWorld-StateMachine", - "DefinitionString": definition, - "RoleArn": role_arn, - "Tags": [ - {"Key": "key1", "Value": "value1"}, - {"Key": "key2", "Value": "value2"}, - ], - } - template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "An example template for a Step Functions state machine.", - "Resources": { - "MyStateMachine": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": {}, - } - }, - "Outputs": { - "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, - "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, - }, - } - template["Resources"]["MyStateMachine"]["Properties"] = properties - cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) - outputs_list = cf.Stack("test_stack").outputs - output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} - state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) - machine_arn = state_machine["stateMachineArn"] - creation_date = state_machine["creationDate"] - - # Update State Machine in-place, no replacement. - updated_role = role_arn + "-updated" - updated_definition = definition.replace("HelloWorld", "HelloWorldUpdated") - updated_properties = { - "DefinitionString": updated_definition, - "RoleArn": updated_role, - "Tags": [ - {"Key": "key3", "Value": "value3"}, - {"Key": "key1", "Value": "updated_value"}, - ], - } - template["Resources"]["MyStateMachine"]["Properties"] = updated_properties - cf.Stack("test_stack").update(TemplateBody=json.dumps(template)) - - state_machine = sf.describe_state_machine(stateMachineArn=machine_arn) - state_machine["name"].should.equal("HelloWorld-StateMachine") - state_machine["creationDate"].should.equal(creation_date) - state_machine["roleArn"].should.equal(updated_role) - state_machine["definition"].should.equal(updated_definition) - tags = sf.list_tags_for_resource(resourceArn=machine_arn).get("tags") - tags.should.have.length_of(3) - for tag in tags: - if tag["key"] == "key1": - tag["value"].should.equal("updated_value") - - def _get_account_id(): global account_id if account_id: diff --git a/tests/test_stepfunctions/test_stepfunctions_cloudformation.py b/tests/test_stepfunctions/test_stepfunctions_cloudformation.py new file mode 100644 index 000000000..8a7d8f3db --- /dev/null +++ b/tests/test_stepfunctions/test_stepfunctions_cloudformation.py @@ -0,0 +1,201 @@ +from __future__ import unicode_literals + +import boto3 +import json +import sure # noqa +from botocore.exceptions import ClientError +import pytest + +from moto import mock_cloudformation, mock_stepfunctions + + +@mock_stepfunctions +@mock_cloudformation +def test_state_machine_cloudformation(): + sf = boto3.client("stepfunctions", region_name="us-east-1") + cf = boto3.resource("cloudformation", region_name="us-east-1") + definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' + role_arn = ( + "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1;" + ) + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "An example template for a Step Functions state machine.", + "Resources": { + "MyStateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "StateMachineName": "HelloWorld-StateMachine", + "StateMachineType": "STANDARD", + "DefinitionString": definition, + "RoleArn": role_arn, + "Tags": [ + {"Key": "key1", "Value": "value1"}, + {"Key": "key2", "Value": "value2"}, + ], + }, + } + }, + "Outputs": { + "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, + "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, + }, + } + cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) + outputs_list = cf.Stack("test_stack").outputs + output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} + state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) + state_machine["stateMachineArn"].should.equal(output["StateMachineArn"]) + state_machine["name"].should.equal(output["StateMachineName"]) + state_machine["roleArn"].should.equal(role_arn) + state_machine["definition"].should.equal(definition) + tags = sf.list_tags_for_resource(resourceArn=output["StateMachineArn"]).get("tags") + for i, tag in enumerate(tags, 1): + tag["key"].should.equal("key{}".format(i)) + tag["value"].should.equal("value{}".format(i)) + + cf.Stack("test_stack").delete() + with pytest.raises(ClientError) as ex: + sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) + ex.value.response["Error"]["Code"].should.equal("StateMachineDoesNotExist") + ex.value.response["Error"]["Message"].should.contain("Does Not Exist") + + +@mock_stepfunctions +@mock_cloudformation +def test_state_machine_cloudformation_update_with_replacement(): + sf = boto3.client("stepfunctions", region_name="us-east-1") + cf = boto3.resource("cloudformation", region_name="us-east-1") + definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' + role_arn = ( + "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1" + ) + properties = { + "StateMachineName": "HelloWorld-StateMachine", + "DefinitionString": definition, + "RoleArn": role_arn, + "Tags": [ + {"Key": "key1", "Value": "value1"}, + {"Key": "key2", "Value": "value2"}, + ], + } + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "An example template for a Step Functions state machine.", + "Resources": { + "MyStateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": {}, + } + }, + "Outputs": { + "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, + "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, + }, + } + template["Resources"]["MyStateMachine"]["Properties"] = properties + cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) + outputs_list = cf.Stack("test_stack").outputs + output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} + state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) + original_machine_arn = state_machine["stateMachineArn"] + original_creation_date = state_machine["creationDate"] + + # Update State Machine, with replacement. + updated_role = role_arn + "-updated" + updated_definition = definition.replace("HelloWorld", "HelloWorld2") + updated_properties = { + "StateMachineName": "New-StateMachine-Name", + "DefinitionString": updated_definition, + "RoleArn": updated_role, + "Tags": [ + {"Key": "key3", "Value": "value3"}, + {"Key": "key1", "Value": "updated_value"}, + ], + } + template["Resources"]["MyStateMachine"]["Properties"] = updated_properties + cf.Stack("test_stack").update(TemplateBody=json.dumps(template)) + outputs_list = cf.Stack("test_stack").outputs + output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} + state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) + state_machine["stateMachineArn"].should_not.equal(original_machine_arn) + state_machine["name"].should.equal("New-StateMachine-Name") + state_machine["creationDate"].should.be.greater_than(original_creation_date) + state_machine["roleArn"].should.equal(updated_role) + state_machine["definition"].should.equal(updated_definition) + tags = sf.list_tags_for_resource(resourceArn=output["StateMachineArn"]).get("tags") + tags.should.have.length_of(3) + for tag in tags: + if tag["key"] == "key1": + tag["value"].should.equal("updated_value") + + with pytest.raises(ClientError) as ex: + sf.describe_state_machine(stateMachineArn=original_machine_arn) + ex.value.response["Error"]["Code"].should.equal("StateMachineDoesNotExist") + ex.value.response["Error"]["Message"].should.contain("State Machine Does Not Exist") + + +@mock_stepfunctions +@mock_cloudformation +def test_state_machine_cloudformation_update_with_no_interruption(): + sf = boto3.client("stepfunctions", region_name="us-east-1") + cf = boto3.resource("cloudformation", region_name="us-east-1") + definition = '{"StartAt": "HelloWorld", "States": {"HelloWorld": {"Type": "Task", "Resource": "arn:aws:lambda:us-east-1:111122223333;:function:HelloFunction", "End": true}}}' + role_arn = ( + "arn:aws:iam::111122223333:role/service-role/StatesExecutionRole-us-east-1" + ) + properties = { + "StateMachineName": "HelloWorld-StateMachine", + "DefinitionString": definition, + "RoleArn": role_arn, + "Tags": [ + {"Key": "key1", "Value": "value1"}, + {"Key": "key2", "Value": "value2"}, + ], + } + template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "An example template for a Step Functions state machine.", + "Resources": { + "MyStateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": {}, + } + }, + "Outputs": { + "StateMachineArn": {"Value": {"Ref": "MyStateMachine"}}, + "StateMachineName": {"Value": {"Fn::GetAtt": ["MyStateMachine", "Name"]}}, + }, + } + template["Resources"]["MyStateMachine"]["Properties"] = properties + cf.create_stack(StackName="test_stack", TemplateBody=json.dumps(template)) + outputs_list = cf.Stack("test_stack").outputs + output = {item["OutputKey"]: item["OutputValue"] for item in outputs_list} + state_machine = sf.describe_state_machine(stateMachineArn=output["StateMachineArn"]) + machine_arn = state_machine["stateMachineArn"] + creation_date = state_machine["creationDate"] + + # Update State Machine in-place, no replacement. + updated_role = role_arn + "-updated" + updated_definition = definition.replace("HelloWorld", "HelloWorldUpdated") + updated_properties = { + "DefinitionString": updated_definition, + "RoleArn": updated_role, + "Tags": [ + {"Key": "key3", "Value": "value3"}, + {"Key": "key1", "Value": "updated_value"}, + ], + } + template["Resources"]["MyStateMachine"]["Properties"] = updated_properties + cf.Stack("test_stack").update(TemplateBody=json.dumps(template)) + + state_machine = sf.describe_state_machine(stateMachineArn=machine_arn) + state_machine["name"].should.equal("HelloWorld-StateMachine") + state_machine["creationDate"].should.equal(creation_date) + state_machine["roleArn"].should.equal(updated_role) + state_machine["definition"].should.equal(updated_definition) + tags = sf.list_tags_for_resource(resourceArn=machine_arn).get("tags") + tags.should.have.length_of(3) + for tag in tags: + if tag["key"] == "key1": + tag["value"].should.equal("updated_value")