chemtrail 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/examples/lib/templates/config/load_balancer.yml +23 -0
  3. data/examples/lib/templates/config/nat_device.yml +49 -0
  4. data/examples/lib/templates/config/opsworks.yml +5 -0
  5. data/examples/lib/templates/config/private_network.yml +34 -0
  6. data/examples/lib/templates/config/{resources.yml → public_network.yml} +4 -14
  7. data/examples/lib/templates/config/public_network_acl.yml +54 -0
  8. data/examples/lib/templates/config/{mappings.yml → stack.yml} +24 -17
  9. data/examples/lib/templates/opsworks_vpc/load_balancer.rb +36 -0
  10. data/examples/lib/templates/opsworks_vpc/nat_device.rb +58 -0
  11. data/examples/lib/templates/opsworks_vpc/opsworks.rb +27 -0
  12. data/examples/lib/templates/opsworks_vpc/private_network.rb +89 -0
  13. data/examples/lib/templates/opsworks_vpc/public_network.rb +79 -0
  14. data/examples/lib/templates/opsworks_vpc/public_network_acl.rb +72 -0
  15. data/examples/lib/templates/opsworks_vpc_template.rb +71 -67
  16. data/examples/spec/lib/templates/opsworks_vpc/load_balancer_spec.rb +22 -0
  17. data/examples/spec/lib/templates/opsworks_vpc/nat_device_spec.rb +45 -0
  18. data/examples/spec/lib/templates/opsworks_vpc/opsworks_spec.rb +21 -0
  19. data/examples/spec/lib/templates/opsworks_vpc/private_network_spec.rb +62 -0
  20. data/examples/spec/lib/templates/opsworks_vpc/public_network_acl_spec.rb +63 -0
  21. data/examples/spec/lib/templates/opsworks_vpc/public_network_spec.rb +40 -0
  22. data/examples/spec/lib/templates/opsworks_vpc_template_spec.rb +37 -39
  23. data/lib/chemtrail/matchers/be_reference_to.rb +11 -0
  24. data/lib/chemtrail/matchers/have_entry.rb +36 -0
  25. data/lib/chemtrail/matchers/have_field.rb +7 -20
  26. data/lib/chemtrail/matchers/have_mapping.rb +4 -2
  27. data/lib/chemtrail/matchers/have_output.rb +46 -0
  28. data/lib/chemtrail/matchers/have_parameter.rb +9 -8
  29. data/lib/chemtrail/matchers/have_property.rb +35 -20
  30. data/lib/chemtrail/matchers/have_resource.rb +9 -8
  31. data/lib/chemtrail/matchers/have_tag.rb +7 -20
  32. data/lib/chemtrail/reference_presenter.rb +8 -6
  33. data/lib/chemtrail/rspec.rb +3 -1
  34. data/lib/chemtrail/version.rb +1 -1
  35. metadata +24 -6
  36. data/examples/lib/templates/config/parameters.yml +0 -18
  37. data/lib/chemtrail/matchers/have_mapping_key.rb +0 -49
@@ -0,0 +1,79 @@
1
+ require_relative "public_network_acl"
2
+
3
+ module OpsworksVpc
4
+ class PublicNetwork
5
+ attr_reader :vpc, :subnet_config
6
+
7
+ def initialize(vpc, subnet_config)
8
+ @vpc = vpc
9
+ @subnet_config = subnet_config
10
+ end
11
+
12
+ def resources
13
+ [
14
+ subnet,
15
+ internet_gateway,
16
+ gateway_to_internet,
17
+ route_table,
18
+ route,
19
+ route_table_association
20
+ ] + network_acl.resources
21
+ end
22
+
23
+ def subnet
24
+ @subnet ||= Chemtrail::Resource.new("PublicSubnet", "AWS::EC2::Subnet", resources_config["PublicSubnet"]).tap do |config|
25
+ config.properties["VpcId"] = vpc
26
+ config.properties["CidrBlock"] = subnet_config.find("Public", "CIDR")
27
+ config.properties["Tags"] << stack_name.as_tag("Application")
28
+ end
29
+ end
30
+
31
+ def internet_gateway
32
+ @internet_gateway ||= Chemtrail::Resource.new("InternetGateway", "AWS::EC2::InternetGateway", resources_config["InternetGateway"]).tap do |config|
33
+ config.properties["Tags"] << stack_name.as_tag("Application")
34
+ end
35
+ end
36
+
37
+ def gateway_to_internet
38
+ @gateway_to_internet ||= Chemtrail::Resource.new("GatewayToInternet", "AWS::EC2::VPCGatewayAttachment", resources_config["GatewayToInternet"]).tap do |config|
39
+ config.properties["VpcId"] = vpc
40
+ config.properties["InternetGatewayId"] = internet_gateway
41
+ end
42
+ end
43
+
44
+ def route_table
45
+ @route_table ||= Chemtrail::Resource.new("PublicRouteTable", "AWS::EC2::RouteTable", resources_config["PublicRouteTable"]).tap do |config|
46
+ config.properties["VpcId"] = vpc
47
+ config.properties["Tags"] << stack_name.as_tag("Application")
48
+ end
49
+ end
50
+
51
+ def route
52
+ @route ||= Chemtrail::Resource.new("PublicRoute", "AWS::EC2::Route", resources_config["PublicRoute"]).tap do |config|
53
+ config.properties["RouteTableId"] = route_table
54
+ config.properties["GatewayId"] = gateway_to_internet
55
+ end
56
+ end
57
+
58
+ def route_table_association
59
+ @route_table_association ||= Chemtrail::Resource.new("PublicSubnetRouteTableAssociation", "AWS::EC2::SubnetRouteTableAssociation", resources_config["PublicSubnetRouteTableAssociation"]).tap do |config|
60
+ config.properties["RouteTableId"] = route_table
61
+ config.properties["SubnetId"] = subnet
62
+ end
63
+ end
64
+
65
+ def network_acl
66
+ @network_acl ||= OpsworksVpc::PublicNetworkAcl.new(vpc, subnet)
67
+ end
68
+
69
+ protected
70
+
71
+ def stack_name
72
+ @stack_name ||= Chemtrail::Intrinsic.new("AWS::StackName")
73
+ end
74
+
75
+ def resources_config
76
+ @resources_config ||= YAML.load_file(File.expand_path("../../config/public_network.yml", __FILE__))
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ module OpsworksVpc
2
+ class PublicNetworkAcl
3
+ attr_reader :vpc, :subnet
4
+
5
+ def initialize(vpc, subnet)
6
+ @vpc = vpc
7
+ @subnet = subnet
8
+ end
9
+
10
+ def resources
11
+ [
12
+ network_acl,
13
+ network_acl_association,
14
+ http_entry,
15
+ https_entry,
16
+ ssh_entry,
17
+ ephemeral_entry,
18
+ outbound_entry
19
+ ]
20
+ end
21
+
22
+ def network_acl
23
+ @network_acl ||= Chemtrail::Resource.new("PublicNetworkAcl", "AWS::EC2::NetworkAcl", resources_config["PublicNetworkAcl"]).tap do |config|
24
+ config.properties["VpcId"] = vpc
25
+ config.properties["Tags"] << stack_name.as_tag("Application")
26
+ end
27
+ end
28
+
29
+ def network_acl_association
30
+ @network_acl_association ||= Chemtrail::Resource.new("PublicSubnetNetworkAclAssociation", "AWS::EC2::SubnetNetworkAclAssociation", resources_config["PublicSubnetNetworkAclAssociation"]).tap do |config|
31
+ config.properties["SubnetId"] = subnet
32
+ config.properties["NetworkAclId"] = network_acl
33
+ end
34
+ end
35
+
36
+ def http_entry
37
+ @http_entry ||= acl_entry("InboundHTTPPublicNetworkAclEntry")
38
+ end
39
+
40
+ def https_entry
41
+ @https_entry ||= acl_entry("InboundHTTPSPublicNetworkAclEntry")
42
+ end
43
+
44
+ def ssh_entry
45
+ @ssh_entry ||= acl_entry("InboundSSHPublicNetworkAclEntry")
46
+ end
47
+
48
+ def ephemeral_entry
49
+ @ephemeral_entry ||= acl_entry("InboundEmphemeralPublicNetworkAclEntry")
50
+ end
51
+
52
+ def outbound_entry
53
+ @outbound_entry ||= acl_entry("OutboundPublicNetworkAclEntry")
54
+ end
55
+
56
+ protected
57
+
58
+ def acl_entry(id)
59
+ config = resources_config[id]
60
+ config.merge!("NetworkAclId" => network_acl)
61
+ Chemtrail::Resource.new(id, "AWS::EC2::NetworkAclEntry", config)
62
+ end
63
+
64
+ def stack_name
65
+ @stack_name ||= Chemtrail::Intrinsic.new("AWS::StackName")
66
+ end
67
+
68
+ def resources_config
69
+ @resources_config ||= YAML.load_file(File.expand_path("../../config/public_network_acl.yml", __FILE__))
70
+ end
71
+ end
72
+ end
@@ -1,89 +1,93 @@
1
1
  require "chemtrail"
2
2
  require "yaml"
3
+ require_relative "opsworks_vpc/opsworks"
4
+ require_relative "opsworks_vpc/nat_device"
5
+ require_relative "opsworks_vpc/public_network"
6
+ require_relative "opsworks_vpc/load_balancer"
7
+ require_relative "opsworks_vpc/private_network"
3
8
 
4
- class OpsworksVpc < Chemtrail::Template
5
- def description
6
- <<-DESCRIPTION.strip.gsub!(/\s+/, ' ')
7
- Sample template showing how to create a VPC environment for AWS OpsWorks.
8
- The stack contains 2 subnets: the first subnet is public and contains the
9
- load balancer, a NAT device for internet access from the private subnet.
10
- The second subnet is private.
11
-
12
- You will be billed for the AWS resources used if you create a stack from
13
- this template.
14
- DESCRIPTION
15
- end
9
+ module OpsworksVpc
10
+ class Stack < Chemtrail::Template
11
+ def description
12
+ <<-DESCRIPTION.strip.gsub!(/\s+/, ' ')
13
+ Sample template showing how to create a VPC environment for AWS OpsWorks.
14
+ The stack contains 2 subnets: the first subnet is public and contains the
15
+ load balancer, a NAT device for internet access from the private subnet.
16
+ The second subnet is private.
16
17
 
17
- def parameters
18
- [
19
- Chemtrail::Parameter.new("NATInstanceType", "String", parameters_config["NATInstanceType"])
20
- ]
21
- end
18
+ You will be billed for the AWS resources used if you create a stack from
19
+ this template.
20
+ DESCRIPTION
21
+ end
22
22
 
23
- def mappings
24
- [
25
- Chemtrail::Mapping.new("AWSNATAMI", mappings_config["AWSNATAMI"]),
26
- Chemtrail::Mapping.new("AWSInstanceType2Arch", mappings_config["AWSInstanceType2Arch"]),
27
- subnet_config
28
- ]
29
- end
23
+ def parameters
24
+ [nat_instance_type]
25
+ end
30
26
 
31
- def resources
32
- [
33
- vpc,
34
- public_subnet,
35
- internet_gateway,
36
- gateway_to_internet
37
- ]
38
- end
27
+ def mappings
28
+ [instance_type_arch, subnet_config] + nat.mappings
29
+ end
39
30
 
40
- def subnet_config
41
- @subnet_config ||= Chemtrail::Mapping.new("SubnetConfig", mappings_config["SubnetConfig"])
42
- end
31
+ def resources
32
+ [vpc] + public_network.resources + private_network.resources + load_balancer.resources + opsworks.resources + nat.resources
33
+ end
43
34
 
44
- def vpc
45
- @vpc ||= Chemtrail::Resource.new("VPC", "AWS::EC2::VPC", resources_config["VPC"]).tap do |config|
46
- config.properties["CidrBlock"] = subnet_config.find("VPC", "CIDR")
47
- config.properties["Tags"] << stack_name.as_tag("Application")
35
+ def outputs
36
+ [
37
+ Chemtrail::Output.new("VPC", vpc),
38
+ Chemtrail::Output.new("PrivateSubnets", private_network.subnet),
39
+ Chemtrail::Output.new("PublicSubnets", public_network.subnet),
40
+ Chemtrail::Output.new("LoadBalancer", load_balancer.elb),
41
+ ]
48
42
  end
49
- end
50
43
 
51
- def public_subnet
52
- @public_subnet ||= Chemtrail::Resource.new("PublicSubnet", "AWS::EC2::Subnet", resources_config["PublicSubnet"]).tap do |config|
53
- config.properties["VpcId"] = vpc
54
- config.properties["CidrBlock"] = subnet_config.find("VPC", "CIDR")
55
- config.properties["Tags"] << stack_name.as_tag("Application")
44
+ def nat_instance_type
45
+ @nat_instance_type ||= Chemtrail::Parameter.new("NATInstanceType", "String", stack_config["NATInstanceType"])
56
46
  end
57
- end
58
47
 
59
- def internet_gateway
60
- @internet_gateway ||= Chemtrail::Resource.new("InternetGateway", "AWS::EC2::InternetGateway", resources_config["InternetGateway"]).tap do |config|
61
- config.properties["Tags"] << stack_name.as_tag("Application")
48
+ def instance_type_arch
49
+ @instance_type_arch ||= Chemtrail::Mapping.new("AWSInstanceType2Arch", stack_config["AWSInstanceType2Arch"])
62
50
  end
63
- end
64
51
 
65
- def gateway_to_internet
66
- @gateway_to_internet ||= Chemtrail::Resource.new("GatewayToInternet", "AWS::EC2::VPCGatewayAttachment", resources_config["GatewayToInternet"]).tap do |config|
67
- config.properties["VpcId"] = vpc
68
- config.properties["InternetGatewayId"] = internet_gateway
52
+ def subnet_config
53
+ @subnet_config ||= Chemtrail::Mapping.new("SubnetConfig", stack_config["SubnetConfig"])
69
54
  end
70
- end
71
55
 
72
- def stack_name
73
- @stack_name ||= Chemtrail::Intrinsic.new("AWS::StackName")
74
- end
56
+ def vpc
57
+ @vpc ||= Chemtrail::Resource.new("VPC", "AWS::EC2::VPC", stack_config["VPC"]).tap do |config|
58
+ config.properties["CidrBlock"] = subnet_config.find("VPC", "CIDR")
59
+ config.properties["Tags"] << stack_name.as_tag("Application")
60
+ end
61
+ end
75
62
 
76
- protected
63
+ protected
77
64
 
78
- def parameters_config
79
- @parameters_config ||= YAML.load_file(File.expand_path("../config/parameters.yml", __FILE__))
80
- end
65
+ def nat
66
+ @nat ||= OpsworksVpc::NatDevice.new(vpc, public_network.subnet, opsworks.security_group, nat_instance_type)
67
+ end
81
68
 
82
- def mappings_config
83
- @mappings_config ||= YAML.load_file(File.expand_path("../config/mappings.yml", __FILE__))
84
- end
69
+ def opsworks
70
+ @opsworks ||= OpsworksVpc::Opsworks.new(vpc, load_balancer.security_group)
71
+ end
72
+
73
+ def load_balancer
74
+ @load_balancer ||= OpsworksVpc::LoadBalancer.new(vpc, public_network.subnet)
75
+ end
85
76
 
86
- def resources_config
87
- @resources_config ||= YAML.load_file(File.expand_path("../config/resources.yml", __FILE__))
77
+ def public_network
78
+ @public_network ||= OpsworksVpc::PublicNetwork.new(vpc, subnet_config)
79
+ end
80
+
81
+ def private_network
82
+ @private_network ||= OpsworksVpc::PrivateNetwork.new(vpc, subnet_config, nat.device)
83
+ end
84
+
85
+ def stack_name
86
+ @stack_name ||= Chemtrail::Intrinsic.new("AWS::StackName")
87
+ end
88
+
89
+ def stack_config
90
+ @stack_config ||= YAML.load_file(File.expand_path("../config/stack.yml", __FILE__))
91
+ end
88
92
  end
89
93
  end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe OpsworksVpc::LoadBalancer do
4
+ let(:vpc) { double(:vpc, id: "VPC") }
5
+ let(:public_subnet) { double(:public_subnet, id: "PublicSubnet") }
6
+
7
+ subject(:elb) { OpsworksVpc::LoadBalancer.new(vpc, public_subnet) }
8
+
9
+ describe "#resources" do
10
+ its(:resources) { should have_resource("LoadBalancerSecurityGroup").with_type("AWS::EC2::SecurityGroup") }
11
+ its(:resources) { should have_resource("ElasticLoadBalancer").with_type("AWS::ElasticLoadBalancing::LoadBalancer") }
12
+
13
+ describe "LoadBalancerSecurityGroup" do
14
+ its(:security_group) { should have_property("VpcId").with_reference("VPC") }
15
+ end
16
+
17
+ describe "ElasticLoadBalancer" do
18
+ its(:elb) { should have_property("SecurityGroups").including_reference("LoadBalancerSecurityGroup") }
19
+ its(:elb) { should have_property("Subnets").including_reference("PublicSubnet") }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe OpsworksVpc::NatDevice do
4
+ let(:vpc) { double(:vpc, id: "VPC") }
5
+ let(:nat_instance_type) { double(:nat_instance_type, id: "NATInstanceType") }
6
+ let(:public_subnet) { double(:public_subnet, id: "PublicSubnet") }
7
+ let(:opsworks_security_group) { double(:opsworks_security_group, id: "OpsWorksSecurityGroup") }
8
+
9
+ subject(:nat_device) { OpsworksVpc::NatDevice.new(vpc, public_subnet, opsworks_security_group, nat_instance_type) }
10
+
11
+ describe "#mappings" do
12
+ its(:mappings) { should have_mapping "AWSNATAMI" }
13
+
14
+ describe "AWSNATAMI" do
15
+ its(:nat_ami) { should have_entry("us-east-1").including("AMI" =>"ami-c6699baf") }
16
+ end
17
+ end
18
+
19
+ describe "#resources" do
20
+ its(:resources) { should have_resource("NATSecurityGroup").with_type("AWS::EC2::SecurityGroup") }
21
+ its(:resources) { should have_resource("NATDevice").with_type("AWS::EC2::Instance") }
22
+ its(:resources) { should have_resource("NATIPAddress").with_type("AWS::EC2::EIP") }
23
+
24
+ describe "NATSecurityGroup" do
25
+ its(:security_group) { should have_property("VpcId").with_reference("VPC") }
26
+
27
+ it "references the opsworks security group on all ingress rules" do
28
+ nat_device.security_group.properties["SecurityGroupIngress"].each do |rule|
29
+ rule["SourceSecurityGroupId"].should be_reference_to("OpsWorksSecurityGroup")
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "NATDevice" do
35
+ its(:device) { should have_property("InstanceType").with_reference("NATInstanceType") }
36
+ its(:device) { should have_property("SubnetId").with_reference("PublicSubnet") }
37
+ its(:device) { should have_property("ImageId") }
38
+ its(:device) { should have_property("SecurityGroupIds").including_reference("NATSecurityGroup") }
39
+ end
40
+
41
+ describe "NATIPAddress" do
42
+ its(:ip) { should have_property("VpcId").with_reference("VPC") }
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+
3
+ describe OpsworksVpc::Opsworks do
4
+ let(:vpc) { double(:vpc, id: "VPC") }
5
+ let(:elb_security_group) { double(:elb_security_group, id: "LoadBalancerSecurityGroup") }
6
+
7
+ subject(:opsworks) { OpsworksVpc::Opsworks.new(vpc, elb_security_group) }
8
+
9
+ describe "#resources" do
10
+ its(:resources) { should have_resource("OpsWorksSecurityGroup").with_type("AWS::EC2::SecurityGroup") }
11
+
12
+ describe "OpsWorksSecurityGroup" do
13
+ let(:ingress) { opsworks.security_group.properties["SecurityGroupIngress"].first }
14
+
15
+ its(:security_group) { should have_property("VpcId").with_reference("VPC") }
16
+ its(:security_group) { should have_property("SecurityGroupIngress") }
17
+
18
+ specify { ingress["SourceSecurityGroupId"].should be_reference_to("LoadBalancerSecurityGroup") }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ require "spec_helper"
2
+
3
+ describe OpsworksVpc::PrivateNetwork do
4
+ let(:vpc) { double(:vpc, id: "VPC") }
5
+ let(:nat_device) { double(:nat_device, id: "NATDevice") }
6
+ let(:subnet) { double(:subnet, find: "found") }
7
+
8
+ subject(:network) { OpsworksVpc::PrivateNetwork.new(vpc, subnet, nat_device) }
9
+
10
+ describe "#resources" do
11
+ its(:resources) { should have_resource("PrivateSubnet").with_type("AWS::EC2::Subnet") }
12
+ its(:resources) { should have_resource("PrivateRouteTable").with_type("AWS::EC2::RouteTable") }
13
+ its(:resources) { should have_resource("PrivateRoute").with_type("AWS::EC2::Route") }
14
+ its(:resources) { should have_resource("PrivateSubnetRouteTableAssociation").with_type("AWS::EC2::SubnetRouteTableAssociation") }
15
+ its(:resources) { should have_resource("PrivateNetworkAcl").with_type("AWS::EC2::NetworkAcl") }
16
+ its(:resources) { should have_resource("PrivateSubnetNetworkAclAssociation").with_type("AWS::EC2::SubnetNetworkAclAssociation") }
17
+ its(:resources) { should have_resource("InboundPrivateNetworkAclEntry").with_type("AWS::EC2::NetworkAclEntry") }
18
+ its(:resources) { should have_resource("OutBoundPrivateNetworkAclEntry").with_type("AWS::EC2::NetworkAclEntry") }
19
+
20
+ describe "PrivateSubnet" do
21
+ its(:subnet) { should have_property("CidrBlock") }
22
+ its(:subnet) { should have_tag("Application").with_reference("AWS::StackName") }
23
+ end
24
+
25
+ describe "PrivateRouteTable" do
26
+ its(:route_table) { should have_property("VpcId").with_reference("VPC") }
27
+ its(:route_table) { should have_tag("Application").with_reference("AWS::StackName") }
28
+ end
29
+
30
+ describe "PrivateRoute" do
31
+ its(:route) { should have_property("RouteTableId").with_reference("PrivateRouteTable") }
32
+ its(:route) { should have_property("InstanceId").with_reference("NATDevice") }
33
+ end
34
+
35
+ describe "PrivateSubnetRouteTableAssociation" do
36
+ its(:subnet_route_table_association) { should have_property("RouteTableId").with_reference("PrivateRouteTable") }
37
+ its(:subnet_route_table_association) { should have_property("SubnetId").with_reference("PrivateSubnet") }
38
+ end
39
+
40
+ describe "PrivateNetworkAcl" do
41
+ its(:network_acl) { should have_property("VpcId").with_reference("VPC") }
42
+ its(:network_acl) { should have_tag("Application").with_reference("AWS::StackName") }
43
+ end
44
+
45
+ describe "PrivateSubnetNetworkAclAssociation" do
46
+ its(:network_acl_association) { should have_property("SubnetId").with_reference("PrivateSubnet") }
47
+ its(:network_acl_association) { should have_property("NetworkAclId").with_reference("PrivateNetworkAcl") }
48
+ end
49
+
50
+ describe "InboundPrivateNetworkAclEntry" do
51
+ its(:inbound_entry) { should have_property("NetworkAclId").with_reference("PrivateNetworkAcl") }
52
+ its(:inbound_entry) { should have_property("PortRange").with_value("From" => "0", "To" => "65535") }
53
+ its(:inbound_entry) { should have_property("Egress").with_value("false") }
54
+ end
55
+
56
+ describe "OutBoundPrivateNetworkAclEntry" do
57
+ its(:outbound_entry) { should have_property("NetworkAclId").with_reference("PrivateNetworkAcl") }
58
+ its(:outbound_entry) { should have_property("PortRange").with_value("From" => "0", "To" => "65535") }
59
+ its(:outbound_entry) { should have_property("Egress").with_value("true") }
60
+ end
61
+ end
62
+ end