sparkle_formation 0.2.0 → 0.2.2

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.
@@ -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