Fix merge conflicts. Add basic cloudformation support. Closes #111.

This commit is contained in:
Steve Pulec 2014-03-27 19:12:53 -04:00
commit ef876dd27e
28 changed files with 2473 additions and 11 deletions

View file

View file

@ -0,0 +1,343 @@
template = {
"Description": "AWS CloudFormation Sample Template Gollum_Single_Instance_With_EBS_Volume: Gollum is a simple wiki system built on top of Git that powers GitHub Wikis. This template installs a Gollum Wiki stack on a single EC2 instance with an EBS volume for storage and demonstrates using the AWS CloudFormation bootstrap scripts to install the packages and files necessary at instance launch time. **WARNING** This template creates an Amazon EC2 instance and an EBS volume. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters": {
"SSHLocation": {
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
"Description": "The IP address range that can be used to SSH to the EC2 instances",
"Default": "0.0.0.0/0",
"MinLength": "9",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"MaxLength": "18",
"Type": "String"
},
"KeyName": {
"Type": "String",
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances",
"MinLength": "1",
"AllowedPattern": "[\\x20-\\x7E]*",
"MaxLength": "255",
"ConstraintDescription": "can contain only ASCII characters."
},
"InstanceType": {
"Default": "m1.small",
"ConstraintDescription": "must be a valid EC2 instance type.",
"Type": "String",
"Description": "WebServer EC2 instance type",
"AllowedValues": [
"t1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"m3.xlarge",
"m3.2xlarge",
"c1.medium",
"c1.xlarge",
"cc1.4xlarge",
"cc2.8xlarge",
"cg1.4xlarge"
]
},
"VolumeSize": {
"Description": "WebServer EC2 instance type",
"Default": "5",
"Type": "Number",
"MaxValue": "1024",
"MinValue": "5",
"ConstraintDescription": "must be between 5 and 1024 Gb."
}
},
"AWSTemplateFormatVersion": "2010-09-09",
"Outputs": {
"WebsiteURL": {
"Description": "URL for Gollum wiki",
"Value": {
"Fn::Join": [
"",
[
"http://",
{
"Fn::GetAtt": [
"WebServer",
"PublicDnsName"
]
}
]
]
}
}
},
"Resources": {
"WebServerSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"SecurityGroupIngress": [
{
"ToPort": "80",
"IpProtocol": "tcp",
"CidrIp": "0.0.0.0/0",
"FromPort": "80"
},
{
"ToPort": "22",
"IpProtocol": "tcp",
"CidrIp": {
"Ref": "SSHLocation"
},
"FromPort": "22"
}
],
"GroupDescription": "Enable SSH access and HTTP access on the inbound port"
}
},
"WebServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash -v\n",
"yum update -y aws-cfn-bootstrap\n",
"# Helper function\n",
"function error_exit\n",
"{\n",
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
{
"Ref": "WaitHandle"
},
"'\n",
" exit 1\n",
"}\n",
"# Install Rails packages\n",
"/opt/aws/bin/cfn-init -s ",
{
"Ref": "AWS::StackId"
},
" -r WebServer ",
" --region ",
{
"Ref": "AWS::Region"
},
" || error_exit 'Failed to run cfn-init'\n",
"# Wait for the EBS volume to show up\n",
"while [ ! -e /dev/sdh ]; do echo Waiting for EBS volume to attach; sleep 5; done\n",
"# Format the EBS volume and mount it\n",
"mkdir /var/wikidata\n",
"/sbin/mkfs -t ext3 /dev/sdh1\n",
"mount /dev/sdh1 /var/wikidata\n",
"# Initialize the wiki and fire up the server\n",
"cd /var/wikidata\n",
"git init\n",
"gollum --port 80 --host 0.0.0.0 &\n",
"# If all is well so signal success\n",
"/opt/aws/bin/cfn-signal -e $? -r \"Rails application setup complete\" '",
{
"Ref": "WaitHandle"
},
"'\n"
]
]
}
},
"KeyName": {
"Ref": "KeyName"
},
"SecurityGroups": [
{
"Ref": "WebServerSecurityGroup"
}
],
"InstanceType": {
"Ref": "InstanceType"
},
"ImageId": {
"Fn::FindInMap": [
"AWSRegionArch2AMI",
{
"Ref": "AWS::Region"
},
{
"Fn::FindInMap": [
"AWSInstanceType2Arch",
{
"Ref": "InstanceType"
},
"Arch"
]
}
]
}
},
"Metadata": {
"AWS::CloudFormation::Init": {
"config": {
"packages": {
"rubygems": {
"nokogiri": [
"1.5.10"
],
"rdiscount": [],
"gollum": [
"1.1.1"
]
},
"yum": {
"libxslt-devel": [],
"gcc": [],
"git": [],
"rubygems": [],
"ruby-devel": [],
"ruby-rdoc": [],
"make": [],
"libxml2-devel": []
}
}
}
}
}
},
"DataVolume": {
"Type": "AWS::EC2::Volume",
"Properties": {
"Tags": [
{
"Value": "Gollum Data Volume",
"Key": "Usage"
}
],
"AvailabilityZone": {
"Fn::GetAtt": [
"WebServer",
"AvailabilityZone"
]
},
"Size": "100",
}
},
"MountPoint": {
"Type": "AWS::EC2::VolumeAttachment",
"Properties": {
"InstanceId": {
"Ref": "WebServer"
},
"Device": "/dev/sdh",
"VolumeId": {
"Ref": "DataVolume"
}
}
},
"WaitCondition": {
"DependsOn": "MountPoint",
"Type": "AWS::CloudFormation::WaitCondition",
"Properties": {
"Handle": {
"Ref": "WaitHandle"
},
"Timeout": "300"
},
"Metadata": {
"Comment1": "Note that the WaitCondition is dependent on the volume mount point allowing the volume to be created and attached to the EC2 instance",
"Comment2": "The instance bootstrap script waits for the volume to be attached to the instance prior to installing Gollum and signalling completion"
}
},
"WaitHandle": {
"Type": "AWS::CloudFormation::WaitConditionHandle"
}
},
"Mappings": {
"AWSInstanceType2Arch": {
"m3.2xlarge": {
"Arch": "64"
},
"m2.2xlarge": {
"Arch": "64"
},
"m1.small": {
"Arch": "64"
},
"c1.medium": {
"Arch": "64"
},
"cg1.4xlarge": {
"Arch": "64HVM"
},
"m2.xlarge": {
"Arch": "64"
},
"t1.micro": {
"Arch": "64"
},
"cc1.4xlarge": {
"Arch": "64HVM"
},
"m1.medium": {
"Arch": "64"
},
"cc2.8xlarge": {
"Arch": "64HVM"
},
"m1.large": {
"Arch": "64"
},
"m1.xlarge": {
"Arch": "64"
},
"m2.4xlarge": {
"Arch": "64"
},
"c1.xlarge": {
"Arch": "64"
},
"m3.xlarge": {
"Arch": "64"
}
},
"AWSRegionArch2AMI": {
"ap-southeast-1": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-b4b0cae6",
"64": "ami-beb0caec"
},
"ap-southeast-2": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-b3990e89",
"64": "ami-bd990e87"
},
"us-west-2": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-38fe7308",
"64": "ami-30fe7300"
},
"us-east-1": {
"64HVM": "ami-0da96764",
"32": "ami-31814f58",
"64": "ami-1b814f72"
},
"ap-northeast-1": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-0644f007",
"64": "ami-0a44f00b"
},
"us-west-1": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-11d68a54",
"64": "ami-1bd68a5e"
},
"eu-west-1": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-973b06e3",
"64": "ami-953b06e1"
},
"sa-east-1": {
"64HVM": "NOT_YET_SUPPORTED",
"32": "ami-3e3be423",
"64": "ami-3c3be421"
}
}
}
}

View file

@ -0,0 +1,402 @@
template = {
"Description": "AWS CloudFormation Sample Template vpc_single_instance_in_subnet.template: Sample template showing how to create a VPC and add an EC2 instance with an Elastic IP address and a security group. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters": {
"SSHLocation": {
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
"Description": " The IP address range that can be used to SSH to the EC2 instances",
"Default": "0.0.0.0/0",
"MinLength": "9",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"MaxLength": "18",
"Type": "String"
},
"KeyName": {
"Type": "String",
"Description": "Name of an existing EC2 KeyPair to enable SSH access to the instance",
"MinLength": "1",
"AllowedPattern": "[\\x20-\\x7E]*",
"MaxLength": "255",
"ConstraintDescription": "can contain only ASCII characters."
},
"InstanceType": {
"Default": "m1.small",
"ConstraintDescription": "must be a valid EC2 instance type.",
"Type": "String",
"Description": "WebServer EC2 instance type",
"AllowedValues": [
"t1.micro",
"m1.small",
"m1.medium",
"m1.large",
"m1.xlarge",
"m2.xlarge",
"m2.2xlarge",
"m2.4xlarge",
"m3.xlarge",
"m3.2xlarge",
"c1.medium",
"c1.xlarge",
"cc1.4xlarge",
"cc2.8xlarge",
"cg1.4xlarge"
]
}
},
"AWSTemplateFormatVersion": "2010-09-09",
"Outputs": {
"URL": {
"Description": "Newly created application URL",
"Value": {
"Fn::Join": [
"",
[
"http://",
{
"Fn::GetAtt": [
"WebServerInstance",
"PublicIp"
]
}
]
]
}
}
},
"Resources": {
"Subnet": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"VpcId": {
"Ref": "VPC"
},
"CidrBlock": "10.0.0.0/24",
"Tags": [
{
"Value": {
"Ref": "AWS::StackId"
},
"Key": "Application"
}
]
}
},
"WebServerWaitHandle": {
"Type": "AWS::CloudFormation::WaitConditionHandle"
},
"Route": {
"Type": "AWS::EC2::Route",
"Properties": {
"GatewayId": {
"Ref": "InternetGateway"
},
"DestinationCidrBlock": "0.0.0.0/0",
"RouteTableId": {
"Ref": "RouteTable"
}
},
"DependsOn": "AttachGateway"
},
"SubnetRouteTableAssociation": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"SubnetId": {
"Ref": "Subnet"
},
"RouteTableId": {
"Ref": "RouteTable"
}
}
},
"InternetGateway": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
"Tags": [
{
"Value": {
"Ref": "AWS::StackId"
},
"Key": "Application"
}
]
}
},
"RouteTable": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "VPC"
},
"Tags": [
{
"Value": {
"Ref": "AWS::StackId"
},
"Key": "Application"
}
]
}
},
"WebServerWaitCondition": {
"Type": "AWS::CloudFormation::WaitCondition",
"Properties": {
"Handle": {
"Ref": "WebServerWaitHandle"
},
"Timeout": "300"
},
"DependsOn": "WebServerInstance"
},
"VPC": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/16",
"Tags": [
{
"Value": {
"Ref": "AWS::StackId"
},
"Key": "Application"
}
]
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"SecurityGroupIngress": [
{
"ToPort": "22",
"IpProtocol": "tcp",
"CidrIp": {
"Ref": "SSHLocation"
},
"FromPort": "22"
},
{
"ToPort": "80",
"IpProtocol": "tcp",
"CidrIp": "0.0.0.0/0",
"FromPort": "80"
}
],
"VpcId": {
"Ref": "VPC"
},
"GroupDescription": "Enable SSH access via port 22"
}
},
"WebServerInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\n",
"yum update -y aws-cfn-bootstrap\n",
"# Helper function\n",
"function error_exit\n",
"{\n",
" /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '",
{
"Ref": "WebServerWaitHandle"
},
"'\n",
" exit 1\n",
"}\n",
"# Install the simple web page\n",
"/opt/aws/bin/cfn-init -s ",
{
"Ref": "AWS::StackId"
},
" -r WebServerInstance ",
" --region ",
{
"Ref": "AWS::Region"
},
" || error_exit 'Failed to run cfn-init'\n",
"# Start up the cfn-hup daemon to listen for changes to the Web Server metadata\n",
"/opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'\n",
"# All done so signal success\n",
"/opt/aws/bin/cfn-signal -e 0 -r \"WebServer setup complete\" '",
{
"Ref": "WebServerWaitHandle"
},
"'\n"
]
]
}
},
"Tags": [
{
"Value": {
"Ref": "AWS::StackId"
},
"Key": "Application"
}
],
"SecurityGroupIds": [
{
"Ref": "InstanceSecurityGroup"
}
],
"KeyName": {
"Ref": "KeyName"
},
"SubnetId": {
"Ref": "Subnet"
},
"ImageId": {
"Fn::FindInMap": [
"RegionMap",
{
"Ref": "AWS::Region"
},
"AMI"
]
},
"InstanceType": {
"Ref": "InstanceType"
}
},
"Metadata": {
"Comment": "Install a simple PHP application",
"AWS::CloudFormation::Init": {
"config": {
"files": {
"/etc/cfn/cfn-hup.conf": {
"content": {
"Fn::Join": [
"",
[
"[main]\n",
"stack=",
{
"Ref": "AWS::StackId"
},
"\n",
"region=",
{
"Ref": "AWS::Region"
},
"\n"
]
]
},
"owner": "root",
"group": "root",
"mode": "000400"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
"content": {
"Fn::Join": [
"",
[
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -s ",
{
"Ref": "AWS::StackId"
},
" -r WebServerInstance ",
" --region ",
{
"Ref": "AWS::Region"
},
"\n",
"runas=root\n"
]
]
}
},
"/var/www/html/index.php": {
"content": {
"Fn::Join": [
"",
[
"<?php\n",
"echo '<h1>AWS CloudFormation sample PHP application</h1>';\n",
"?>\n"
]
]
},
"owner": "apache",
"group": "apache",
"mode": "000644"
}
},
"services": {
"sysvinit": {
"httpd": {
"ensureRunning": "true",
"enabled": "true"
},
"sendmail": {
"ensureRunning": "false",
"enabled": "false"
}
}
},
"packages": {
"yum": {
"httpd": [],
"php": []
}
}
}
}
}
},
"IPAddress": {
"Type": "AWS::EC2::EIP",
"Properties": {
"InstanceId": {
"Ref": "WebServerInstance"
},
"Domain": "vpc"
},
"DependsOn": "AttachGateway"
},
"AttachGateway": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"VpcId": {
"Ref": "VPC"
},
"InternetGatewayId": {
"Ref": "InternetGateway"
}
}
}
},
"Mappings": {
"RegionMap": {
"ap-southeast-1": {
"AMI": "ami-74dda626"
},
"ap-southeast-2": {
"AMI": "ami-b3990e89"
},
"us-west-2": {
"AMI": "ami-16fd7026"
},
"us-east-1": {
"AMI": "ami-7f418316"
},
"ap-northeast-1": {
"AMI": "ami-dcfa4edd"
},
"us-west-1": {
"AMI": "ami-951945d0"
},
"eu-west-1": {
"AMI": "ami-24506250"
},
"sa-east-1": {
"AMI": "ami-3e3be423"
}
}
}
}

View file

@ -0,0 +1,115 @@
import json
import boto
import sure # noqa
from moto import mock_cloudformation
dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Stack 1",
"Resources": {},
}
dummy_template2 = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Stack 2",
"Resources": {},
}
dummy_template_json = json.dumps(dummy_template)
dummy_template_json2 = json.dumps(dummy_template2)
@mock_cloudformation
def test_create_stack():
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
stack = conn.describe_stacks()[0]
stack.stack_name.should.equal('test_stack')
stack.get_template().should.equal(dummy_template)
@mock_cloudformation
def test_describe_stack_by_name():
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
stack = conn.describe_stacks("test_stack")[0]
stack.stack_name.should.equal('test_stack')
@mock_cloudformation
def test_get_template_by_name():
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
template = conn.get_template("test_stack")
template.should.equal(dummy_template)
@mock_cloudformation
def test_list_stacks():
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
conn.create_stack(
"test_stack2",
template_body=dummy_template_json,
)
stacks = conn.list_stacks()
stacks.should.have.length_of(2)
stacks[0].template_description.should.equal("Stack 1")
@mock_cloudformation
def test_delete_stack_by_name():
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
conn.list_stacks().should.have.length_of(1)
conn.delete_stack("test_stack")
conn.list_stacks().should.have.length_of(0)
@mock_cloudformation
def test_delete_stack_by_id():
conn = boto.connect_cloudformation()
stack_id = conn.create_stack(
"test_stack",
template_body=dummy_template_json,
)
conn.list_stacks().should.have.length_of(1)
conn.delete_stack(stack_id)
conn.list_stacks().should.have.length_of(0)
# @mock_cloudformation
# def test_update_stack():
# conn = boto.connect_cloudformation()
# conn.create_stack(
# "test_stack",
# template_body=dummy_template_json,
# )
# conn.update_stack("test_stack", dummy_template_json2)
# stack = conn.describe_stacks()[0]
# stack.get_template().should.equal(dummy_template2)

View file

@ -0,0 +1,473 @@
import json
import boto
import sure # noqa
from moto import (
mock_autoscaling,
mock_cloudformation,
mock_ec2,
mock_elb,
mock_iam,
)
from .fixtures import single_instance_with_ebs_volume, vpc_single_instance_in_subnet
@mock_cloudformation()
def test_stack_sqs_integration():
sqs_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"QueueGroup": {
"Type": "AWS::SQS::Queue",
"Properties": {
"QueueName": "my-queue",
"VisibilityTimeout": 60,
}
},
},
}
sqs_template_json = json.dumps(sqs_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=sqs_template_json,
)
stack = conn.describe_stacks()[0]
queue = stack.describe_resources()[0]
queue.resource_type.should.equal('AWS::SQS::Queue')
queue.logical_resource_id.should.equal("QueueGroup")
queue.physical_resource_id.should.equal("my-queue")
@mock_ec2()
@mock_cloudformation()
def test_stack_ec2_integration():
ec2_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"WebServerGroup": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-1234abcd",
"UserData": "some user data",
}
},
},
}
ec2_template_json = json.dumps(ec2_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"ec2_stack",
template_body=ec2_template_json,
)
ec2_conn = boto.connect_ec2()
reservation = ec2_conn.get_all_instances()[0]
ec2_instance = reservation.instances[0]
stack = conn.describe_stacks()[0]
instance = stack.describe_resources()[0]
instance.resource_type.should.equal('AWS::EC2::Instance')
instance.logical_resource_id.should.equal("WebServerGroup")
instance.physical_resource_id.should.equal(ec2_instance.id)
@mock_ec2()
@mock_elb()
@mock_cloudformation()
def test_stack_elb_integration_with_attached_ec2_instances():
elb_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"MyELB": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Instances": [{"Ref": "Ec2Instance1"}],
"Properties": {
"LoadBalancerName": "test-elb",
"AvailabilityZones": ['us-east1'],
}
},
"Ec2Instance1": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-1234abcd",
"UserData": "some user data",
}
},
},
}
elb_template_json = json.dumps(elb_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"elb_stack",
template_body=elb_template_json,
)
elb_conn = boto.connect_elb()
load_balancer = elb_conn.get_all_load_balancers()[0]
ec2_conn = boto.connect_ec2()
reservation = ec2_conn.get_all_instances()[0]
ec2_instance = reservation.instances[0]
instance_id = ec2_instance.id
load_balancer.instances[0].id.should.equal(ec2_instance.id)
list(load_balancer.availability_zones).should.equal(['us-east1'])
load_balancer_name = load_balancer.name
stack = conn.describe_stacks()[0]
stack_resources = stack.describe_resources()
stack_resources.should.have.length_of(2)
for resource in stack_resources:
if resource.resource_type == 'AWS::ElasticLoadBalancing::LoadBalancer':
load_balancer = resource
else:
ec2_instance = resource
load_balancer.logical_resource_id.should.equal("MyELB")
load_balancer.physical_resource_id.should.equal(load_balancer_name)
ec2_instance.physical_resource_id.should.equal(instance_id)
@mock_ec2()
@mock_cloudformation()
def test_stack_security_groups():
security_group_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"my-security-group": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "My other group",
},
},
"Ec2Instance2": {
"Type": "AWS::EC2::Instance",
"Properties": {
"SecurityGroups": [{"Ref": "InstanceSecurityGroup"}],
"ImageId": "ami-1234abcd",
}
},
"InstanceSecurityGroup": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "My security group",
"SecurityGroupIngress": [{
"IpProtocol": "tcp",
"FromPort": "22",
"ToPort": "22",
"CidrIp": "123.123.123.123/32",
}, {
"IpProtocol": "tcp",
"FromPort": "80",
"ToPort": "8000",
"SourceSecurityGroupId": {"Ref": "my-security-group"},
}]
}
}
},
}
security_group_template_json = json.dumps(security_group_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"security_group_stack",
template_body=security_group_template_json,
)
ec2_conn = boto.connect_ec2()
security_groups = ec2_conn.get_all_security_groups()
for group in security_groups:
if group.name == "InstanceSecurityGroup":
instance_group = group
else:
other_group = group
reservation = ec2_conn.get_all_instances()[0]
ec2_instance = reservation.instances[0]
ec2_instance.groups[0].id.should.equal(instance_group.id)
instance_group.description.should.equal("My security group")
rule1, rule2 = instance_group.rules
int(rule1.to_port).should.equal(22)
int(rule1.from_port).should.equal(22)
rule1.grants[0].cidr_ip.should.equal("123.123.123.123/32")
rule1.ip_protocol.should.equal('tcp')
int(rule2.to_port).should.equal(8000)
int(rule2.from_port).should.equal(80)
rule2.ip_protocol.should.equal('tcp')
rule2.grants[0].group_id.should.equal(other_group.id)
@mock_autoscaling()
@mock_elb()
@mock_cloudformation()
def test_autoscaling_group_with_elb():
web_setup_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"my-as-group": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
"AvailabilityZones": ['us-east1'],
"LaunchConfigurationName": {"Ref": "my-launch-config"},
"MinSize": "2",
"MaxSize": "2",
"LoadBalancerNames": [{"Ref": "my-elb"}]
},
},
"my-launch-config": {
"Type": "AWS::AutoScaling::LaunchConfiguration",
"Properties": {
"ImageId": "ami-1234abcd",
"UserData": "some user data",
}
},
"my-elb": {
"Type": "AWS::ElasticLoadBalancing::LoadBalancer",
"Properties": {
"AvailabilityZones": ['us-east1'],
"Listeners": [{
"LoadBalancerPort": "80",
"InstancePort": "80",
"Protocol": "HTTP"
}],
"HealthCheck": {
"Target": "80",
"HealthyThreshold": "3",
"UnhealthyThreshold": "5",
"Interval": "30",
"Timeout": "5",
},
},
},
}
}
web_setup_template_json = json.dumps(web_setup_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"web_stack",
template_body=web_setup_template_json,
)
autoscale_conn = boto.connect_autoscale()
autoscale_group = autoscale_conn.get_all_groups()[0]
autoscale_group.launch_config_name.should.equal("my-launch-config")
autoscale_group.load_balancers[0].should.equal('my-elb')
# Confirm the Launch config was actually created
autoscale_conn.get_all_launch_configurations().should.have.length_of(1)
# Confirm the ELB was actually created
elb_conn = boto.connect_elb()
elb_conn.get_all_load_balancers().should.have.length_of(1)
stack = conn.describe_stacks()[0]
resources = stack.describe_resources()
as_group_resource = [resource for resource in resources if resource.resource_type == 'AWS::AutoScaling::AutoScalingGroup'][0]
as_group_resource.physical_resource_id.should.equal("my-as-group")
launch_config_resource = [resource for resource in resources if resource.resource_type == 'AWS::AutoScaling::LaunchConfiguration'][0]
launch_config_resource.physical_resource_id.should.equal("my-launch-config")
elb_resource = [resource for resource in resources if resource.resource_type == 'AWS::ElasticLoadBalancing::LoadBalancer'][0]
elb_resource.physical_resource_id.should.equal("my-elb")
@mock_ec2()
@mock_cloudformation()
def test_vpc_single_instance_in_subnet():
template_json = json.dumps(vpc_single_instance_in_subnet.template)
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=template_json,
)
vpc_conn = boto.connect_vpc()
vpc = vpc_conn.get_all_vpcs()[0]
vpc.cidr_block.should.equal("10.0.0.0/16")
# Add this once we implement the endpoint
# vpc_conn.get_all_internet_gateways().should.have.length_of(1)
subnet = vpc_conn.get_all_subnets()[0]
subnet.vpc_id.should.equal(vpc.id)
ec2_conn = boto.connect_ec2()
reservation = ec2_conn.get_all_instances()[0]
instance = reservation.instances[0]
# Check that the EIP is attached the the EC2 instance
eip = ec2_conn.get_all_addresses()[0]
eip.domain.should.equal('vpc')
eip.instance_id.should.equal(instance.id)
security_group = ec2_conn.get_all_security_groups()[0]
security_group.vpc_id.should.equal(vpc.id)
stack = conn.describe_stacks()[0]
resources = stack.describe_resources()
vpc_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::VPC'][0]
vpc_resource.physical_resource_id.should.equal(vpc.id)
subnet_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::Subnet'][0]
subnet_resource.physical_resource_id.should.equal(subnet.id)
eip_resource = [resource for resource in resources if resource.resource_type == 'AWS::EC2::EIP'][0]
eip_resource.physical_resource_id.should.equal(eip.allocation_id)
@mock_autoscaling()
@mock_iam()
@mock_cloudformation()
def test_iam_roles():
iam_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"my-launch-config": {
"Properties": {
"IamInstanceProfile": {"Ref": "my-instance-profile"},
"ImageId": "ami-1234abcd",
},
"Type": "AWS::AutoScaling::LaunchConfiguration"
},
"my-instance-profile": {
"Properties": {
"Path": "my-path",
"Roles": [{"Ref": "my-role"}],
},
"Type": "AWS::IAM::InstanceProfile"
},
"my-role": {
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": [
"sts:AssumeRole"
],
"Effect": "Allow",
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
}
}
]
},
"Path": "my-path",
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": [
"ec2:CreateTags",
"ec2:DescribeInstances",
"ec2:DescribeTags"
],
"Effect": "Allow",
"Resource": [
"*"
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "EC2_Tags"
},
{
"PolicyDocument": {
"Statement": [
{
"Action": [
"sqs:*"
],
"Effect": "Allow",
"Resource": [
"*"
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "SQS"
},
]
},
"Type": "AWS::IAM::Role"
}
}
}
iam_template_json = json.dumps(iam_template)
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=iam_template_json,
)
iam_conn = boto.connect_iam()
role = iam_conn.get_role("my-role")
role.role_name.should.equal("my-role")
role.path.should.equal("my-path")
instance_profile = iam_conn.get_instance_profile("my-instance-profile")
instance_profile.instance_profile_name.should.equal("my-instance-profile")
instance_profile.path.should.equal("my-path")
instance_profile.role_id.should.equal(role.role_id)
autoscale_conn = boto.connect_autoscale()
launch_config = autoscale_conn.get_all_launch_configurations()[0]
launch_config.instance_profile_name.should.equal("my-instance-profile")
stack = conn.describe_stacks()[0]
resources = stack.describe_resources()
instance_profile_resource = [resource for resource in resources if resource.resource_type == 'AWS::IAM::InstanceProfile'][0]
instance_profile_resource.physical_resource_id.should.equal(instance_profile.instance_profile_name)
role_resource = [resource for resource in resources if resource.resource_type == 'AWS::IAM::Role'][0]
role_resource.physical_resource_id.should.equal(role.role_id)
@mock_ec2()
@mock_cloudformation()
def test_single_instance_with_ebs_volume():
template_json = json.dumps(single_instance_with_ebs_volume.template)
conn = boto.connect_cloudformation()
conn.create_stack(
"test_stack",
template_body=template_json,
)
ec2_conn = boto.connect_ec2()
reservation = ec2_conn.get_all_instances()[0]
ec2_instance = reservation.instances[0]
volume = ec2_conn.get_all_volumes()[0]
volume.volume_state().should.equal('in-use')
volume.attach_data.instance_id.should.equal(ec2_instance.id)
stack = conn.describe_stacks()[0]
resources = stack.describe_resources()
ebs_volume = [resource for resource in resources if resource.resource_type == 'AWS::EC2::Volume'][0]
ebs_volume.physical_resource_id.should.equal(volume.id)

View file

View file

@ -0,0 +1,47 @@
import json
from mock import patch
import sure # noqa
from moto.cloudformation.models import FakeStack
from moto.cloudformation.parsing import resource_class_from_type
from moto.sqs.models import Queue
dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create a multi-az, load balanced, Auto Scaled sample web site. The Auto Scaling trigger is based on the CPU utilization of the web servers. The AMI is chosen based on the region in which the stack is run. This example creates a web service running across all availability zones in a region. The instances are load balanced with a simple health check. The web site is available on port 80, however, the instances can be configured to listen on any port (8888 by default). **WARNING** This template creates one or more Amazon EC2 instances. You will be billed for the AWS resources used if you create a stack from this template.",
"Resources": {
"WebServerGroup": {
"Type": "AWS::SQS::Queue",
"Properties": {
"QueueName": "my-queue",
"VisibilityTimeout": 60,
}
},
},
}
dummy_template_json = json.dumps(dummy_template)
def test_parse_stack_resources():
stack = FakeStack(
stack_id="test_id",
name="test_stack",
template=dummy_template_json,
)
stack.resource_map.should.have.length_of(1)
stack.resource_map.keys()[0].should.equal('WebServerGroup')
queue = stack.resource_map.values()[0]
queue.should.be.a(Queue)
queue.name.should.equal("my-queue")
@patch("moto.cloudformation.parsing.logger")
def test_missing_resource_logs(logger):
resource_class_from_type("foobar")
logger.warning.assert_called_with('No Moto CloudFormation support for %s', 'foobar')

View file

@ -0,0 +1,27 @@
import boto
import sure # noqa
from moto import mock_iam
@mock_iam()
def test_create_role_and_instance_profile():
conn = boto.connect_iam()
conn.create_instance_profile("my-profile", path="my-path")
conn.create_role("my-role", assume_role_policy_document="some policy", path="my-path")
conn.add_role_to_instance_profile("my-profile", "my-role")
role = conn.get_role("my-role")
role.path.should.equal("my-path")
role.assume_role_policy_document.should.equal("some policy")
profile = conn.get_instance_profile("my-profile")
profile.path.should.equal("my-path")
role_from_profile = profile.roles.values()[0]
role_from_profile['role_id'].should.equal(role.role_id)
role_from_profile['role_name'].should.equal("my-role")
conn.list_roles().roles[0].role_name.should.equal('my-role')
conn.list_instance_profiles().instance_profiles[0].instance_profile_name.should.equal("my-profile")