sparkle_formation 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ SparkleFormation.build do
2
+ set!('AWSTemplateFormatVersion', '2010-09-09')
3
+
4
+ resources.cfn_user do
5
+ type 'AWS::IAM::User'
6
+ properties.path '/'
7
+ properties.policies _array(
8
+ -> {
9
+ policy_name 'cfn_access'
10
+ policy_document.statement _array(
11
+ -> {
12
+ effect 'Allow'
13
+ action 'cloudformation:DescribeStackResource'
14
+ resource '*'
15
+ }
16
+ )
17
+ }
18
+ )
19
+ end
20
+
21
+ resources.cfn_keys do
22
+ type 'AWS::IAM::AccessKey'
23
+ properties.user_name ref!(:cfn_user)
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ SparkleFormation.dynamic(:elb) do |_name, _config={}|
2
+ resources("#{_name}_elb".to_sym) do
3
+ type 'AWS::ElasticLoadBalancing::LoadBalancer'
4
+ properties do
5
+ availability_zones._set('Fn::GetAZs', '')
6
+ listeners _array(
7
+ -> {
8
+ load_balancer_port _config[:load_balancer_port] || '80'
9
+ protocol _config[:protocol] || 'HTTP'
10
+ instance_port _config[:instance_port] || '80'
11
+ instance_protocol _config[:instance_protocol] || 'HTTP'
12
+ }
13
+ )
14
+ health_check do
15
+ target _config[:target] || 'HTTP:80/'
16
+ healthy_threshold _config[:healthy_threshold] || '3'
17
+ unhealthy_threshold _config[:unhealthy_threshold] || '3'
18
+ interval _config[:interval] || '5'
19
+ timeout _config[:timeout] || '15'
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ SparkleFormation.new(:website).load(:base).overrides do
2
+
3
+ description 'Supercool Website'
4
+
5
+ parameters.web_nodes do
6
+ type 'Number'
7
+ description 'Number of web nodes for ASG.'
8
+ default 2
9
+ end
10
+
11
+ resources.website_autoscale do
12
+ type 'AWS::AutoScaling::AutoScalingGroup'
13
+ properties do
14
+ availability_zones({'Fn::GetAZs' => ''})
15
+ launch_configuration_name ref!(:website_launch_config)
16
+ min_size ref!(:web_nodes)
17
+ max_size ref!(:web_nodes)
18
+ end
19
+ end
20
+
21
+ resources.website_launch_config do
22
+ type 'AWS::AutoScaling::LaunchConfiguration'
23
+ properties do
24
+ image_id 'ami-123456'
25
+ instance_type 'm3.medium'
26
+ end
27
+ end
28
+
29
+ dynamic!(:elb, 'website')
30
+ end
@@ -0,0 +1,88 @@
1
+ {
2
+ "AWSTemplateFormatVersion": "2010-09-09",
3
+ "Description": "Supercool Website",
4
+ "Resources": {
5
+ "CfnUser": {
6
+ "Type": "AWS::IAM::User",
7
+ "Properties": {
8
+ "Path": "/",
9
+ "Policies": [
10
+ {
11
+ "PolicyName": "cfn_access",
12
+ "PolicyDocument": {
13
+ "Statement": [
14
+ {
15
+ "Effect": "Allow",
16
+ "Action": "cloudformation:DescribeStackResource",
17
+ "Resource": "*"
18
+ }
19
+ ]
20
+ }
21
+ }
22
+ ]
23
+ }
24
+ },
25
+ "CfnKeys": {
26
+ "Type": "AWS::IAM::AccessKey",
27
+ "Properties": {
28
+ "UserName": {
29
+ "Ref": "CfnUser"
30
+ }
31
+ }
32
+ },
33
+ "WebsiteAutoscale": {
34
+ "Type": "AWS::AutoScaling::AutoScalingGroup",
35
+ "Properties": {
36
+ "AvailabilityZones": {
37
+ "Fn::GetAZs": ""
38
+ },
39
+ "LaunchConfigurationName": {
40
+ "Ref": "WebsiteLaunchConfig"
41
+ },
42
+ "MinSize": {
43
+ "Ref": "WebNodes"
44
+ },
45
+ "MaxSize": {
46
+ "Ref": "WebNodes"
47
+ }
48
+ }
49
+ },
50
+ "WebsiteLaunchConfig": {
51
+ "Type": "AWS::AutoScaling::LaunchConfiguration",
52
+ "Properties": {
53
+ "ImageId": "ami-123456",
54
+ "InstanceType": "m3.medium"
55
+ }
56
+ },
57
+ "WebsiteElb": {
58
+ "Type": "AWS::ElasticLoadBalancing::LoadBalancer",
59
+ "Properties": {
60
+ "AvailabilityZones": {
61
+ "Fn::GetAZs": ""
62
+ },
63
+ "Listeners": [
64
+ {
65
+ "LoadBalancerPort": "80",
66
+ "Protocol": "HTTP",
67
+ "InstancePort": "80",
68
+ "InstanceProtocol": "HTTP"
69
+ }
70
+ ],
71
+ "HealthCheck": {
72
+ "Target": "HTTP:80/",
73
+ "HealthyThreshold": "3",
74
+ "UnhealthyThreshold": "3",
75
+ "Interval": "5",
76
+ "Timeout": "15"
77
+ }
78
+ }
79
+ }
80
+ },
81
+ "Parameters": {
82
+ "WebNodes": {
83
+ "Type": "Number",
84
+ "Description": "Number of web nodes for ASG.",
85
+ "Default": "2"
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,74 @@
1
+ SparkleFormation.new('website') do
2
+
3
+ set!('AWSTemplateFormatVersion', '2010-09-09')
4
+
5
+ description 'Supercool Website'
6
+
7
+ resources.cfn_user do
8
+ type 'AWS::IAM::User'
9
+ properties.path '/'
10
+ properties.policies _array(
11
+ -> {
12
+ policy_name 'cfn_access'
13
+ policy_document.statement _array(
14
+ -> {
15
+ effect 'Allow'
16
+ action 'cloudformation:DescribeStackResource'
17
+ resource '*'
18
+ }
19
+ )
20
+ }
21
+ )
22
+ end
23
+
24
+ resources.cfn_keys do
25
+ type 'AWS::IAM::AccessKey'
26
+ properties.user_name ref!(:cfn_user)
27
+ end
28
+
29
+ parameters.web_nodes do
30
+ type 'Number'
31
+ description 'Number of web nodes for ASG.'
32
+ default 2
33
+ end
34
+
35
+ resources.website_autoscale do
36
+ type 'AWS::AutoScaling::AutoScalingGroup'
37
+ properties do
38
+ availability_zones({'Fn::GetAZs' => ''})
39
+ launch_configuration_name ref!(:website_launch_config)
40
+ min_size ref!(:web_nodes)
41
+ max_size ref!(:web_nodes)
42
+ end
43
+ end
44
+
45
+ resources.website_launch_config do
46
+ type 'AWS::AutoScaling::LaunchConfiguration'
47
+ properties do
48
+ image_id 'ami-123456'
49
+ instance_type 'm3.medium'
50
+ end
51
+ end
52
+
53
+ resources.website_elb do
54
+ type 'AWS::ElasticLoadBalancing::LoadBalancer'
55
+ properties do
56
+ availability_zones._set('Fn::GetAZs', '')
57
+ listeners _array(
58
+ -> {
59
+ load_balancer_port '80'
60
+ protocol 'HTTP'
61
+ instance_port '80'
62
+ instance_protocol 'HTTP'
63
+ }
64
+ )
65
+ health_check do
66
+ target 'HTTP:80/'
67
+ healthy_threshold '3'
68
+ unhealthy_threshold '3'
69
+ interval '5'
70
+ timeout '15'
71
+ end
72
+ end
73
+ end
74
+ end
data/docs/functions.md ADDED
@@ -0,0 +1,41 @@
1
+ ## Intrinsic Functions
2
+ The following are all intrinsic AWS Cloudformation functions that are
3
+ supported with special syntax in SparkleFormation. Note that these may
4
+ not be implemented for all providers.
5
+
6
+ ### Ref
7
+ Ref allows you to reference parameter and resource values. We did this
8
+ earlier with the autoscaling group size:
9
+ ```ruby
10
+ parameters.web_nodes do
11
+ type 'Number'
12
+ description 'Number of web nodes for ASG.'
13
+ default '2'
14
+ end
15
+
16
+ ...
17
+
18
+ min_size ref!(:web_nodes)
19
+ ```
20
+ It also works for resource names. The following returns the name of
21
+ the launch configuration so we can use it in the autoscaling group
22
+ properties.
23
+ ```ruby
24
+ ref!(:website_launch_config)
25
+ ```
26
+
27
+ ### Join
28
+ A Join combines strings. You can use Refs and Mappings within a Join.
29
+ ```ruby
30
+ join!(ref!(:environment), '-', map!(:region_map, ref!('AWS::Region'), :ami))
31
+ ```
32
+ Would return 'development-us-east-1', if we built a stack in the
33
+ AWS Virgnia region and provided 'development' for the environment
34
+ parameter.
35
+
36
+ ### Attr
37
+ Certain resources attributes can be retrieved directly. To access an
38
+ IAM user's (in this case, :cfn_user) secret key:
39
+ ```ruby
40
+ attr!(:cfn_user, :secret_access_key)
41
+ ```
@@ -0,0 +1,32 @@
1
+ ## Universal Properties
2
+
3
+ ### Tags
4
+ Tags can be applied to most resources. These make it easy to track
5
+ resource usage across stacks. They may be used for cost tracking as
6
+ well as configuration tools that are cloud-infrastructure aware. Tags
7
+ are provided as key/value pairs within an array. In this example we
8
+ provide the stack name and a contact email:
9
+
10
+ ```ruby
11
+ resources.website_autoscale do
12
+ type 'AWS::AutoScaling::AutoScalingGroup'
13
+ properties do
14
+ availability_zones({ 'Fn::GetAZs' => '' })
15
+ tags _array(
16
+ -> {
17
+ key 'StackName'
18
+ value ref!('AWS::StackName'))
19
+ propagate_at_launch true
20
+ },
21
+ -> {
22
+ key 'ContactEmail'
23
+ value 'support@hw-ops.com'
24
+ propagate_at_launch true
25
+ }
26
+ )
27
+ launch_configuration_name ref!(:website_launch_config)
28
+ end
29
+ ```
30
+
31
+ Please check the relevant reference documentation to confirm that tags
32
+ are available for a specific resource type.
@@ -0,0 +1,82 @@
1
+ ## Provisioning SparkleFormations
2
+
3
+ ### JSON Templates
4
+
5
+ Generating JSON from a SparkleFormation template is as simple as
6
+ calling `SparkleFormation.compile()` on the template file. Here's a simple
7
+ script to output a JSON template from a supplied
8
+ SparkeFormation template:
9
+
10
+ ```ruby
11
+ #!/usr/bin/env ruby
12
+ require 'sparkle_formation'
13
+ require 'json'
14
+
15
+ puts SparkleFormation.compile(ARGV[0])
16
+ ```
17
+
18
+ The output can be written to a file and uploaded to the provider using
19
+ the method of your choice.
20
+
21
+ For a more legible template:
22
+
23
+ ```ruby
24
+ puts JSON.pretty_generate(SparkleFormation.compile(ARGV[0]))
25
+ ```
26
+
27
+ Note: The output from this command may not be usable with cloud providers,
28
+ as the many spaces and newlines may exceed the cloudformation
29
+ character limit. However, it is much easier to read.
30
+
31
+ ### Knife Cloudformation
32
+ knife-cloudformation [knife-cloudformation plugin](https://rubygems.org/gems/knife-cloudformation) is a plugin for
33
+ knife that provisions cloudformation stacks. It can be used with
34
+ SparkleFormation to build stacks without the intermediary steps of
35
+ writing a JSON template to file and uploading the template to the provider.
36
+
37
+ #### Processing SparkleFormation Templates
38
+ To build a stack directly from a SparkleFormation template, use the
39
+ `create` command with the `--file` and `--processing` flags:
40
+
41
+ ```
42
+ knife cloudformation create my-web-stack --file templates/website.rb --processing
43
+ ```
44
+
45
+ `--file` directs knife to a file under the `cloudformation` directory,
46
+ and `--processing` tells knife to render JSON from the
47
+ SparkleFormation template before passing it to the provider.
48
+
49
+ #### Applying Stacks
50
+ You can also apply an existing stack's outputs to the stack you are
51
+ building. Using the `--apply-stack` flag sets parameters to the
52
+ values of any matching outputs.
53
+
54
+ Consider that you have built a database stack (`db-stack-01`) that includes an output for the
55
+ database endpoint:
56
+
57
+ ```ruby
58
+ outputs do
59
+ database_endpoint do
60
+ value attr!(:database_elb, 'DNSName')
61
+ description "Database ELB Endpoint for client connections"
62
+ end
63
+ end
64
+ ```
65
+
66
+ Next, you build a website stack (`web-stack-01`) that needs to connect to the
67
+ database. The SparkleFormation for this stack includes a parameter to
68
+ prompt for the database endpoint:
69
+
70
+ ```ruby
71
+ parameters.database_endpoint do
72
+ type 'String'
73
+ description 'Database endpoint to connect to'
74
+ default 'localhost'
75
+ end
76
+ ```
77
+
78
+ Using knife-cloudformation, you apply the database stack in order to
79
+ automatically provide the correct database endpoint:
80
+
81
+ `knife cloudformation create web-stack-01 --file templates/website.rb --processing --apply-stack db-stack-01`
82
+
@@ -0,0 +1,49 @@
1
+ ## Resource Reference
2
+
3
+ This is a best-effort list of commonly used cloudformation resources and their
4
+ availability/feature set in supported cloud providers. This list does
5
+ not cover everything that may be offered by a specific provider, and
6
+ is not intended as a comparison of cloud providers.
7
+
8
+ #### Auto Scaling Groups
9
+
10
+ |Provider |Available |Caveats & Limitations |
11
+ |------------|----------|-----------------------------------------------------------------------------|
12
+ |AWS |Yes |EC2 Classic and VPC use mutually exclusive properties. |
13
+ |Rackspace |Yes |Autoscaling will not replace lost instances, only does policy based scaling. |
14
+
15
+ #### Compute Instances
16
+
17
+ |Provider |Available |Caveats & Limitations |
18
+ |------------|----------|-----------------------------------------------------------------------------|
19
+ |AWS |Yes | |
20
+ |Rackspace |Yes | |
21
+
22
+ #### Load Balancers
23
+
24
+ |Provider |Available |Caveats & Limitations |
25
+ |------------|----------|-----------------------------------------------------------------------------|
26
+ |AWS |Yes |Security Group Ingress is not automatic. Must be defined separately. |
27
+ |Rackspace |Yes |Multiple ports generates new template resources. |
28
+
29
+ #### Security Groups
30
+
31
+ |Provider |Available |Caveats & Limitations |
32
+ |------------|----------|-----------------------------------------------------------------------------|
33
+ |AWS |Yes | |
34
+ |Rackspace |No | |
35
+
36
+ #### Storage
37
+
38
+ |Provider |Available |Caveats & Limitations |
39
+ |------------|----------|-----------------------------------------------------------------------------|
40
+ |AWS |Yes | |
41
+ |Rackspace |No | |
42
+
43
+
44
+ #### Stack Users
45
+
46
+ |Provider |Available |Caveats & Limitations |
47
+ |------------|----------|-----------------------------------------------------------------------------|
48
+ |AWS |Yes | |
49
+ |Rackspace |No | |
@@ -154,7 +154,7 @@ class SparkleFormation
154
154
  #
155
155
  # @param name [String, Symbol] name of dynamic
156
156
  # @param args [Hash] dynamic metadata
157
- # @param args [Hash] :parameters description of _config parameters
157
+ # @option args [Hash] :parameters description of _config parameters
158
158
  # @example
159
159
  # metadata describes dynamic parameters for _config hash:
160
160
  # :item_name => {:description => 'Defines item name', :type => 'String'}
@@ -3,6 +3,66 @@ class SparkleFormation
3
3
  # Translation for Heat (HOT)
4
4
  class Heat < Translation
5
5
 
6
+ # Translate stack definition
7
+ #
8
+ # @return [TrueClass]
9
+ # @note this is an override to return in proper HOT format
10
+ # @todo still needs replacements of functions and pseudo-params
11
+ def translate!
12
+ super
13
+ cache = MultiJson.load(MultiJson.dump(translated))
14
+ # top level
15
+ cache.each do |k,v|
16
+ translated.delete(k)
17
+ translated[snake(k).to_s] = v
18
+ end
19
+ # params
20
+ cache.fetch('Parameters', {}).each do |k,v|
21
+ translated['parameters'][k] = Hash[
22
+ v.map do |key, value|
23
+ if(key == 'Type')
24
+ [snake(key).to_s, value.downcase]
25
+ elsif(key == 'AllowedValues')
26
+ # @todo fix this up to properly build constraints
27
+ ['constraints', [{'allowed_values' => value}]]
28
+ else
29
+ [snake(key).to_s, value]
30
+ end
31
+ end
32
+ ]
33
+ end
34
+ # resources
35
+ cache.fetch('Resources', {}).each do |r_name, r_value|
36
+ translated['resources'][r_name] = Hash[
37
+ r_value.map do |k,v|
38
+ [snake(k).to_s, v]
39
+ end
40
+ ]
41
+ end
42
+ # outputs
43
+ cache.fetch('Outputs', {}).each do |o_name, o_value|
44
+ translated['outputs'][o_name] = Hash[
45
+ o_value.map do |k,v|
46
+ [snake(k).to_s, v]
47
+ end
48
+ ]
49
+ end
50
+ translated.delete('awstemplate_format_version')
51
+ translated['heat_template_version'] = '2013-05-23'
52
+ # no HOT support for mappings, so remove and clean pseudo
53
+ # params in refs
54
+ if(translated['resources'])
55
+ translated['resources'] = dereference_processor(translated['resources'], ['Fn::FindInMap', 'Ref'])
56
+ translated['resources'] = rename_processor(translated['resources'])
57
+ end
58
+ if(translated['outputs'])
59
+ translated['outputs'] = dereference_processor(translated['outputs'], ['Fn::FindInMap', 'Ref'])
60
+ translated['outputs'] = rename_processor(translated['outputs'])
61
+ end
62
+ translated.delete('mappings')
63
+ true
64
+ end
65
+
6
66
  # Custom mapping for block device
7
67
  #
8
68
  # @param value [Object] original property value
@@ -49,7 +109,19 @@ class SparkleFormation
49
109
  if(files = config['files'])
50
110
  files.each do |key, args|
51
111
  if(args['source'])
52
- args['source'].replace("\"#{args['source']}\"")
112
+ if(args['source'].is_a?(String))
113
+ args['source'].replace("\"#{args['source']}\"")
114
+ else
115
+ args['source'] = {
116
+ 'Fn::Join' => [
117
+ "", [
118
+ "\"",
119
+ args['source'],
120
+ "\""
121
+ ]
122
+ ]
123
+ }
124
+ end
53
125
  end
54
126
  end
55
127
  end
@@ -126,6 +198,17 @@ class SparkleFormation
126
198
  }
127
199
  }
128
200
 
201
+ REF_MAPPING = {
202
+ 'AWS::StackName' => 'OS::stack_name',
203
+ 'AWS::StackId' => 'OS::stack_id',
204
+ 'AWS::Region' => 'OS::stack_id' # @todo i see it set in source, but no function. wat
205
+ }
206
+
207
+ FN_MAPPING = {
208
+ 'Fn::GetAtt' => 'get_attr',
209
+ 'Fn::Join' => 'list_join' # @todo why is this not working?
210
+ }
211
+
129
212
  end
130
213
  end
131
214
  end