Add some support for Cloudformation Conditions. Closes #285.
This commit is contained in:
parent
1e4df18c42
commit
8da6437689
@ -95,6 +95,13 @@ def clean_json(resource_json, resources_map):
|
|||||||
join_list.append(cleaned_val if cleaned_val else '{0}'.format(val))
|
join_list.append(cleaned_val if cleaned_val else '{0}'.format(val))
|
||||||
return resource_json['Fn::Join'][0].join(join_list)
|
return resource_json['Fn::Join'][0].join(join_list)
|
||||||
|
|
||||||
|
if 'Fn::If' in resource_json:
|
||||||
|
condition_name, true_value, false_value = resource_json['Fn::If']
|
||||||
|
if resources_map[condition_name]:
|
||||||
|
return true_value
|
||||||
|
else:
|
||||||
|
return false_value
|
||||||
|
|
||||||
cleaned_json = {}
|
cleaned_json = {}
|
||||||
for key, value in resource_json.items():
|
for key, value in resource_json.items():
|
||||||
cleaned_json[key] = clean_json(value, resources_map)
|
cleaned_json[key] = clean_json(value, resources_map)
|
||||||
@ -126,6 +133,11 @@ def parse_resource(logical_id, resource_json, resources_map, region_name):
|
|||||||
if not resource_class:
|
if not resource_class:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
condition = resource_json.get('Condition')
|
||||||
|
if condition and not resources_map[condition]:
|
||||||
|
# If this has a False condition, don't create the resource
|
||||||
|
return None
|
||||||
|
|
||||||
resource_json = clean_json(resource_json, resources_map)
|
resource_json = clean_json(resource_json, resources_map)
|
||||||
resource_name_property = resource_name_property_from_type(resource_type)
|
resource_name_property = resource_name_property_from_type(resource_type)
|
||||||
if resource_name_property:
|
if resource_name_property:
|
||||||
@ -148,6 +160,36 @@ def parse_resource(logical_id, resource_json, resources_map, region_name):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
|
|
||||||
|
def parse_condition(condition, resources_map):
|
||||||
|
if isinstance(condition, bool):
|
||||||
|
return condition
|
||||||
|
|
||||||
|
condition_operator = condition.keys()[0]
|
||||||
|
|
||||||
|
condition_values = []
|
||||||
|
for value in condition.values()[0]:
|
||||||
|
# Check if we are referencing another Condition
|
||||||
|
if 'Condition' in value:
|
||||||
|
condition_values.append(resources_map[value['Condition']])
|
||||||
|
else:
|
||||||
|
condition_values.append(clean_json(value, resources_map))
|
||||||
|
|
||||||
|
if condition_operator == "Fn::Equals":
|
||||||
|
return condition_values[0] == condition_values[1]
|
||||||
|
elif condition_operator == "Fn::Not":
|
||||||
|
return not parse_condition(condition_values[0], resources_map)
|
||||||
|
elif condition_operator == "Fn::And":
|
||||||
|
return all([
|
||||||
|
parse_condition(condition_value, resources_map)
|
||||||
|
for condition_value
|
||||||
|
in condition_values])
|
||||||
|
elif condition_operator == "Fn::Or":
|
||||||
|
return any([
|
||||||
|
parse_condition(condition_value, resources_map)
|
||||||
|
for condition_value
|
||||||
|
in condition_values])
|
||||||
|
|
||||||
|
|
||||||
def parse_output(output_logical_id, output_json, resources_map):
|
def parse_output(output_logical_id, output_json, resources_map):
|
||||||
output_json = clean_json(output_json, resources_map)
|
output_json = clean_json(output_json, resources_map)
|
||||||
output = Output()
|
output = Output()
|
||||||
@ -218,8 +260,15 @@ class ResourceMap(collections.Mapping):
|
|||||||
|
|
||||||
self._parsed_resources.update(self.resolved_parameters)
|
self._parsed_resources.update(self.resolved_parameters)
|
||||||
|
|
||||||
|
def load_conditions(self):
|
||||||
|
conditions = self._template.get('Conditions', {})
|
||||||
|
|
||||||
|
for condition_name, condition in conditions.items():
|
||||||
|
self._parsed_resources[condition_name] = parse_condition(condition, self._parsed_resources)
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
self.load_parameters()
|
self.load_parameters()
|
||||||
|
self.load_conditions()
|
||||||
|
|
||||||
# Since this is a lazy map, to create every object we just need to
|
# Since this is a lazy map, to create every object we just need to
|
||||||
# iterate through self.
|
# iterate through self.
|
||||||
|
@ -8,6 +8,7 @@ import boto.ec2.autoscale
|
|||||||
import boto.ec2.elb
|
import boto.ec2.elb
|
||||||
from boto.exception import BotoServerError
|
from boto.exception import BotoServerError
|
||||||
import boto.iam
|
import boto.iam
|
||||||
|
import boto.sqs
|
||||||
import boto.vpc
|
import boto.vpc
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ from moto import (
|
|||||||
mock_ec2,
|
mock_ec2,
|
||||||
mock_elb,
|
mock_elb,
|
||||||
mock_iam,
|
mock_iam,
|
||||||
|
mock_sqs,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .fixtures import (
|
from .fixtures import (
|
||||||
@ -548,3 +550,105 @@ def test_fn_join():
|
|||||||
stack = conn.describe_stacks()[0]
|
stack = conn.describe_stacks()[0]
|
||||||
fn_join_output = stack.outputs[0]
|
fn_join_output = stack.outputs[0]
|
||||||
fn_join_output.value.should.equal('test eip:{0}'.format(eip.public_ip))
|
fn_join_output.value.should.equal('test eip:{0}'.format(eip.public_ip))
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation()
|
||||||
|
@mock_sqs()
|
||||||
|
def test_conditional_resources():
|
||||||
|
sqs_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Parameters": {
|
||||||
|
"EnvType": {
|
||||||
|
"Description": "Environment type.",
|
||||||
|
"Type": "String",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Conditions": {
|
||||||
|
"CreateQueue": {"Fn::Equals": [{"Ref": "EnvType"}, "prod"]}
|
||||||
|
},
|
||||||
|
"Resources": {
|
||||||
|
"QueueGroup": {
|
||||||
|
"Condition": "CreateQueue",
|
||||||
|
"Type": "AWS::SQS::Queue",
|
||||||
|
"Properties": {
|
||||||
|
"QueueName": "my-queue",
|
||||||
|
"VisibilityTimeout": 60,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sqs_template_json = json.dumps(sqs_template)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack(
|
||||||
|
"test_stack_without_queue",
|
||||||
|
template_body=sqs_template_json,
|
||||||
|
parameters=[("EnvType", "staging")],
|
||||||
|
)
|
||||||
|
sqs_conn = boto.sqs.connect_to_region("us-west-1")
|
||||||
|
list(sqs_conn.get_all_queues()).should.have.length_of(0)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack(
|
||||||
|
"test_stack_with_queue",
|
||||||
|
template_body=sqs_template_json,
|
||||||
|
parameters=[("EnvType", "prod")],
|
||||||
|
)
|
||||||
|
sqs_conn = boto.sqs.connect_to_region("us-west-1")
|
||||||
|
list(sqs_conn.get_all_queues()).should.have.length_of(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cloudformation()
|
||||||
|
@mock_ec2()
|
||||||
|
def test_conditional_if_handling():
|
||||||
|
dummy_template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Conditions": {
|
||||||
|
"EnvEqualsPrd": {
|
||||||
|
"Fn::Equals": [
|
||||||
|
{
|
||||||
|
"Ref": "ENV"
|
||||||
|
},
|
||||||
|
"prd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Parameters": {
|
||||||
|
"ENV": {
|
||||||
|
"Default": "dev",
|
||||||
|
"Description": "Deployment environment for the stack (dev/prd)",
|
||||||
|
"Type": "String"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Description": "Stack 1",
|
||||||
|
"Resources": {
|
||||||
|
"App1": {
|
||||||
|
"Properties": {
|
||||||
|
"ImageId": {
|
||||||
|
"Fn::If": [
|
||||||
|
"EnvEqualsPrd",
|
||||||
|
"ami-00000000",
|
||||||
|
"ami-ffffffff"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Type": "AWS::EC2::Instance"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dummy_template_json = json.dumps(dummy_template)
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-1")
|
||||||
|
conn.create_stack('test_stack1', template_body=dummy_template_json)
|
||||||
|
ec2_conn = boto.ec2.connect_to_region("us-west-1")
|
||||||
|
reservation = ec2_conn.get_all_instances()[0]
|
||||||
|
ec2_instance = reservation.instances[0]
|
||||||
|
ec2_instance.image_id.should.equal("ami-ffffffff")
|
||||||
|
ec2_instance.terminate()
|
||||||
|
|
||||||
|
conn = boto.cloudformation.connect_to_region("us-west-2")
|
||||||
|
conn.create_stack('test_stack1', template_body=dummy_template_json, parameters=[("ENV", "prd")])
|
||||||
|
ec2_conn = boto.ec2.connect_to_region("us-west-2")
|
||||||
|
reservation = ec2_conn.get_all_instances()[0]
|
||||||
|
ec2_instance = reservation.instances[0]
|
||||||
|
ec2_instance.image_id.should.equal("ami-00000000")
|
||||||
|
@ -5,7 +5,7 @@ from mock import patch
|
|||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
from moto.cloudformation.models import FakeStack
|
from moto.cloudformation.models import FakeStack
|
||||||
from moto.cloudformation.parsing import resource_class_from_type
|
from moto.cloudformation.parsing import resource_class_from_type, parse_condition
|
||||||
from moto.sqs.models import Queue
|
from moto.sqs.models import Queue
|
||||||
from boto.cloudformation.stack import Output
|
from boto.cloudformation.stack import Output
|
||||||
from boto.exception import BotoServerError
|
from boto.exception import BotoServerError
|
||||||
@ -145,3 +145,86 @@ def test_parse_stack_with_get_attribute_outputs():
|
|||||||
def test_parse_stack_with_bad_get_attribute_outputs():
|
def test_parse_stack_with_bad_get_attribute_outputs():
|
||||||
FakeStack.when.called_with(
|
FakeStack.when.called_with(
|
||||||
"test_id", "test_stack", bad_output_template_json, {}, "us-west-1").should.throw(BotoServerError)
|
"test_id", "test_stack", bad_output_template_json, {}, "us-west-1").should.throw(BotoServerError)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_equals_condition():
|
||||||
|
parse_condition(
|
||||||
|
condition={"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(True)
|
||||||
|
|
||||||
|
parse_condition(
|
||||||
|
condition={"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
resources_map={"EnvType": "staging"},
|
||||||
|
).should.equal(False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_not_condition():
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::Not": [{
|
||||||
|
"Fn::Equals": [{"Ref": "EnvType"}, "prod"]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(False)
|
||||||
|
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::Not": [{
|
||||||
|
"Fn::Equals": [{"Ref": "EnvType"}, "prod"]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "staging"},
|
||||||
|
).should.equal(True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_and_condition():
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::And": [
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "staging"]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(False)
|
||||||
|
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::And": [
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_or_condition():
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::Or": [
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "prod"]},
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "staging"]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(True)
|
||||||
|
|
||||||
|
parse_condition(
|
||||||
|
condition={
|
||||||
|
"Fn::Or": [
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "staging"]},
|
||||||
|
{"Fn::Equals": [{"Ref": "EnvType"}, "staging"]},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resources_map={"EnvType": "prod"},
|
||||||
|
).should.equal(False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reference_other_conditions():
|
||||||
|
parse_condition(
|
||||||
|
condition={"Fn::Not": [{"Condition": "OtherCondition"}]},
|
||||||
|
resources_map={"OtherCondition": True},
|
||||||
|
).should.equal(False)
|
||||||
|
Loading…
Reference in New Issue
Block a user