cloud_builder 0.0.6

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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+
9
+ script: bundle exec rspec spec/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cloud_builder.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Songkick, Optaros
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # CloudBuilder
2
+
3
+ Generates JSON config for AWS CloudFormation using a Ruby DSL. Manage CloudFormation stacks - create, update, diff.
4
+
5
+ Based on https://github.com/songkick/cloud_formatter
6
+
7
+ ## Installation
8
+
9
+ Clone the repository, then run:
10
+
11
+ gem build cloud_builder.gemspec
12
+ gem install cloud_builder-version.gem
13
+
14
+ ## Usage
15
+
16
+ ### CLI tool
17
+
18
+ $ stack --help
19
+ Usage:
20
+ stack [OPTIONS] STACK
21
+
22
+ Parameters:
23
+ STACK stack to build
24
+
25
+ Options:
26
+ -r, --region REGION AWS region to use (default: $EC2_REGION, or "us-east-1")
27
+ -v, --validate validate the stack file before doing anything else
28
+ -b, --bucket BUCKET upload template to BUCKET (default: $CLOUD_BUILDER_BUCKET, or nil)
29
+ -t, --diff-tool DIFF_TOOL tool to use for diff (default: $CLOUD_BUILDER_DIFF_TOOL)
30
+ -c, --create create the stack
31
+ -u, --update update the stack
32
+ -d, --diff do a diff between the existing template in BUCKET and the generated template
33
+ -e, --estimate estimate template cost
34
+ -h, --help print help
35
+
36
+ For most actions you will require an active AWS account, so make sure you export `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to `ENV`
37
+
38
+ ### Example stack
39
+
40
+ See stacks/example.rb for an example stack.
41
+
42
+ ## Contributing
43
+
44
+ 1. Fork it
45
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
46
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
47
+ 4. Push to the branch (`git push origin my-new-feature`)
48
+ 5. Create new Pull Request
49
+
50
+ ## Changelog
51
+
52
+ ### 0.0.6
53
+ - added support for template Outputs (see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)
54
+
55
+ ### 0.0.5
56
+ - cli can use the EC2_REGION, CLOUD_BUILDER_BUCKET and CLOUD_BUILDER_DIFF_TOOL environment variables as defaults for `--region`, `--bucket` and `--diff-tool`
57
+ - deprecated the `--upload` flag/action, if you specify a bucket the stack json is uploaded by default
58
+
59
+ ### 0.0.4
60
+ - support for specifying an AWS region
61
+
62
+ ### 0.0.3
63
+ - add the Changelog
64
+ - AWS resources can have a Version property
65
+
66
+ ### 0.0.2
67
+ - fix cloud_builder problem when using `--help`
68
+ - add DontFormatUnderscore class
69
+ - estimation should be done after uploading to s3 bucket
70
+ - add `--diff-tool` to specify a different tool to use for diffs
71
+
72
+ ### 0.0.1
73
+ - beta initial release
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/stack ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cloud_builder'
4
+ CloudBuilder::StackCommand.run
5
+
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cloud_builder/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "cloud_builder"
8
+ gem.version = CloudBuilder::GEM_VERSION
9
+ gem.authors = ["Sorin Stoiana"]
10
+ gem.email = ["sstoiana@optaros.com"]
11
+ gem.description = "Generate JSON config for AWS CloudFormation using a Ruby based DSL"
12
+ gem.summary = "Generate JSON config for AWS CloudFormation"
13
+ gem.homepage = "https://github.com/Optaros/cloud_builder"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "netaddr"
21
+ gem.add_development_dependency "rspec", "~> 2.6"
22
+
23
+ gem.add_dependency "clamp"
24
+ gem.add_dependency "json"
25
+ gem.add_dependency "aws-sdk"
26
+ gem.add_dependency "activesupport"
27
+ end
@@ -0,0 +1,45 @@
1
+ require 'cloud_builder/reference'
2
+
3
+ module CloudBuilder
4
+ class Brick
5
+ extend Forwardable
6
+ include ExposesRefs
7
+
8
+ def initialize(stack, type, hash, &block)
9
+ @stack = stack
10
+ @type = type
11
+ @factory = block
12
+ @block = block
13
+
14
+ @hash = DSLOpenStruct.new(hash)
15
+
16
+ filename = "%s/bricks/%s.rb" % [ @stack.dirname, @type.to_s ]
17
+ instance_eval File.read(filename), filename
18
+ end
19
+
20
+ def brick
21
+ @hash
22
+ end
23
+
24
+ def globals
25
+ @stack.globals
26
+ end
27
+
28
+ def include_brick(name, hash={}, &block)
29
+ @stack.include_brick(name, hash, &block)
30
+ end
31
+
32
+ def mappings(&block)
33
+ @stack.mappings(&block)
34
+ end
35
+
36
+ def parameter(name, &block)
37
+ @stack.parameter(name, &block)
38
+ end
39
+
40
+ def resource(name, &block)
41
+ @stack.resource(name, &block)
42
+ end
43
+ end
44
+ end
45
+
@@ -0,0 +1,79 @@
1
+ require 'clamp'
2
+ require 'aws-sdk'
3
+
4
+ require 'tempfile'
5
+
6
+ module CloudBuilder
7
+ class StackCommand < Clamp::Command
8
+ parameter "STACK", "stack to build"
9
+ option ["-r", "--region"], "REGION", "AWS region to use", :default => "us-east-1", :environment_variable => "EC2_REGION"
10
+ option ["-v", "--validate"], :flag, "validate the stack file before doing anything else"
11
+ option ["-b", "--bucket"], "BUCKET", "upload template to BUCKET", :attribute_name => :bucket, :default => nil, :environment_variable => "CLOUD_BUILDER_BUCKET"
12
+ option ["-t", "--diff-tool"], "DIFF_TOOL", "tool to use for diff", :environment_variable => "CLOUD_BUILDER_DIFF_TOOL"
13
+ option ["-c", "--create"], :flag, "create the stack"
14
+ option ["-u", "--update"], :flag, "update the stack"
15
+
16
+ option ["-d", "--diff"], :flag, "do a diff between the existing template in BUCKET and the generated template"
17
+
18
+ option ["-e", "--estimate"], :flag, "estimate template cost"
19
+
20
+ def execute
21
+ template = CloudBuilder::Stack.new(stack).to_json + "\n"
22
+
23
+ cf = AWS::CloudFormation.new(:cloud_formation_endpoint => 'cloudformation.%s.amazonaws.com' % region)
24
+
25
+ s3 = AWS::S3.new
26
+
27
+ key = File.basename(stack, '.*')
28
+
29
+ if validate?
30
+ result = cf.validate_template(template)
31
+ if result.has_key?(:code)
32
+ raise Exception, "Could not validate!\ncode=#{result[:code]}\nreason=#{result[:reason]}\n"
33
+ end
34
+ end
35
+
36
+ if diff?
37
+ remote_template = cf.stacks[key].template
38
+
39
+ # b = s3.buckets[bucket]
40
+ # o = b.objects[key]
41
+ # remote_template = o.read
42
+
43
+ t1 = Tempfile.new('stack')
44
+ t1.write(remote_template)
45
+ t1.close
46
+
47
+ t2 = Tempfile.new('stack')
48
+ t2.write(template)
49
+ t2.close
50
+
51
+ cmd = "%s %s %s" % [diff_tool ? diff_tool : "git diff --color", t1.path, t2.path]
52
+ # puts cmd
53
+ puts `#{cmd}`
54
+ return
55
+ end
56
+
57
+ if bucket
58
+ b = s3.buckets[bucket]
59
+ o = b.objects[key]
60
+ o.write template
61
+ template = o.public_url
62
+ else
63
+ puts template
64
+ end
65
+
66
+ if estimate?
67
+ puts cf.estimate_template_cost(template, :capabilities => ["CAPABILITY_IAM"] )
68
+ return
69
+ end
70
+
71
+ if create?
72
+ cf.stacks.create(key, template, :capabilities => ["CAPABILITY_IAM"] )
73
+ elsif update?
74
+ cf.stacks[key].update(:template => template, :capabilities => ["CAPABILITY_IAM"])
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_support'
2
+
3
+
4
+ module CloudBuilder
5
+ class DontFormatUnderscore
6
+ def initialize(*p)
7
+ @vars = ActiveSupport::OrderedHash.new()
8
+ if p.first
9
+ p.first.sort.map do |k,v|
10
+ self[k] = v
11
+ end
12
+ end
13
+ end
14
+
15
+ def []=(key, value)
16
+ @vars[key] = value
17
+ end
18
+
19
+ def to_json_data()
20
+ @vars
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,138 @@
1
+ require 'active_support'
2
+
3
+ module CloudBuilder
4
+
5
+ module DSL
6
+
7
+ attr_accessor :globals
8
+
9
+ def globals
10
+ @globals ||= DSLOpenStruct.new()
11
+ end
12
+
13
+ def self.format(key)
14
+ key.to_s.gsub(/(.)[_-](.)/) { $1 + $2.upcase }.gsub(/^(.)/) { $1.upcase }
15
+ end
16
+
17
+ def self.hashize(value, format_hash_keys = true)
18
+ case value
19
+ when Array then value.map { |v| jsonize v,format_hash_keys }
20
+ when Hash then
21
+ ret = {}
22
+ value.each do |k,v|
23
+ if format_hash_keys
24
+ ret[DSL.format(k)] = jsonize v,format_hash_keys
25
+ else
26
+ ret[k] = jsonize v,format_hash_keys
27
+ end
28
+ end
29
+ ret
30
+ else
31
+ value.respond_to?(:to_json_data) ? value.to_json_data : value
32
+ end
33
+ end
34
+
35
+ def self.jsonize(value, format_hash_keys = true)
36
+ ret = self.hashize(value, format_hash_keys)
37
+ case ret
38
+ when Hash then
39
+ sh = ActiveSupport::OrderedHash.new
40
+ ret.sort.map do |k,v|
41
+ sh[k] =v
42
+ end
43
+ sh
44
+ else
45
+ ret
46
+ end
47
+ end
48
+
49
+ def mappings(&block)
50
+ _mappings.instance_eval(&block)
51
+ end
52
+
53
+ def resource(name, &block)
54
+ _resources[name.to_s] = Resource.new(self, name, &block)
55
+ end
56
+
57
+ def include_brick(name, hash={}, &block)
58
+ _bricks[name.to_s] = Brick.new(self, name, hash, &block)
59
+ end
60
+
61
+ def parameter(name, &block)
62
+ _parameters[DSL.format(name.to_s)] = Parameter.new(self, &block)
63
+ end
64
+
65
+ def output(name, &block)
66
+ _outputs[DSL.format(name.to_s)] = Output.new(self, &block)
67
+ end
68
+
69
+ def to_json
70
+ JSON.pretty_generate(DSL.jsonize(generate_spec, false))
71
+ end
72
+
73
+ def reference_type(name)
74
+ if _parameters.has_key?(name)
75
+ :parameter
76
+ elsif _mappings.has_key?(name)
77
+ :map
78
+ elsif _resources.has_key?(name)
79
+ :resource
80
+ end
81
+ end
82
+
83
+ def dirname
84
+ @dirname
85
+ end
86
+
87
+ def method_missing(field, value)
88
+ instance_variable_set("@#{field}", value)
89
+ end
90
+
91
+ private
92
+
93
+ def generate_spec
94
+ spec = {
95
+ DESCRIPTION => @description,
96
+ VERSION => @template_version,
97
+ MAPPINGS => DSL.jsonize(_mappings),
98
+ RESOURCES => {},
99
+ PARAMETERS => {},
100
+ OUTPUTS => {},
101
+ }
102
+ _resources.each do |name, instance|
103
+ spec[RESOURCES][DSL.format(name)] = DSL.jsonize(instance)
104
+ end
105
+
106
+ _parameters.each do |name, parameter|
107
+ spec[PARAMETERS][name] = DSL.jsonize(parameter)
108
+ end
109
+
110
+ _outputs.each do |name, parameter|
111
+ spec[OUTPUTS][name] = DSL.jsonize(parameter)
112
+ end
113
+
114
+ spec
115
+ end
116
+
117
+ def _mappings
118
+ @mappings ||= Mappings.new
119
+ end
120
+
121
+ def _resources
122
+ @resources ||= {}
123
+ end
124
+
125
+ def _bricks
126
+ @bricks ||= {}
127
+ end
128
+
129
+ def _parameters
130
+ @parameters ||= {}
131
+ end
132
+
133
+ def _outputs
134
+ @outputs ||= {}
135
+ end
136
+ end
137
+ end
138
+
@@ -0,0 +1,33 @@
1
+ require 'ostruct'
2
+
3
+ module CloudBuilder
4
+ class DSLOpenStruct < OpenStruct
5
+
6
+ def ref(name)
7
+ Reference.new(name)
8
+ end
9
+
10
+ def get_att(name, att)
11
+ Reference::Attribute.new(name, att)
12
+ end
13
+
14
+ def method_missing(mid, *args)
15
+ mname = mid.id2name
16
+ len = args.length
17
+ if mname.chomp!('=')
18
+ if len != 1
19
+ raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
20
+ end
21
+ modifiable[new_ostruct_member(mname)] = args[0]
22
+ elsif len == 0
23
+ if @table.has_key? mid
24
+ @table[mid]
25
+ else
26
+ raise NameError, "undefined property #{mid} for #{self}", caller(1)
27
+ end
28
+ else
29
+ raise NoMethodError, "undefined method `#{mid}' for #{self}", caller(1)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module CloudBuilder
2
+ class JSONable
3
+ def to_hash
4
+ hash = {}
5
+
6
+ self.instance_variables.each do |var|
7
+ hash[var.to_s.delete("@")] = self.instance_variable_get var
8
+ end
9
+ hash
10
+ end
11
+
12
+ def to_json(*a)
13
+ self.to_hash.to_json(*a)
14
+ end
15
+
16
+ # def from_json! string
17
+ # JSON.load(string).each do |var, val|
18
+ # self.instance_variable_set var, val
19
+ # end
20
+ # end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ require 'forwardable'
2
+
3
+ module CloudBuilder
4
+ class Mappings
5
+ extend Forwardable
6
+ def_delegators :@maps, :has_key?
7
+
8
+ def initialize
9
+ @maps = {}
10
+ end
11
+
12
+ def to_json_data
13
+ json = {}
14
+ @maps.each do |key, value|
15
+ json[DSL.format(key)] = value
16
+ end
17
+ json
18
+ end
19
+
20
+ def method_missing(map_name, values)
21
+ @maps[map_name.to_s] = values
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,45 @@
1
+ module CloudBuilder
2
+ class Output < JSONable
3
+ include ExposesRefs
4
+
5
+ def initialize(stack, &block)
6
+ @stack = stack
7
+
8
+ @Description = ''
9
+ @block = block
10
+ instance_exec(&block)
11
+ end
12
+
13
+ def to_hash
14
+ hash = super.to_hash
15
+ hash.delete('block')
16
+ hash
17
+ end
18
+
19
+ def globals
20
+ @stack.globals
21
+ end
22
+
23
+ def to_json_data
24
+ { "Value" => @Value, "Description" => @Description }
25
+ end
26
+
27
+ def value v
28
+ @Value = v
29
+ end
30
+
31
+ def description value
32
+ @Description = value
33
+ end
34
+
35
+ def brick
36
+ @block.binding.eval('brick')
37
+ end
38
+ # def method_missing(field, *params)
39
+ # instance_variable_set("@#{field}", value)
40
+ # print @block.binding.eval('@hash')
41
+ # null
42
+ # end
43
+ end
44
+ end
45
+
@@ -0,0 +1,43 @@
1
+ module CloudBuilder
2
+ class Parameter < JSONable
3
+ def initialize(stack, &block)
4
+ @stack = stack
5
+
6
+ @Type = 'String'
7
+ @block = block
8
+ instance_exec(&block)
9
+ end
10
+
11
+ def to_hash
12
+ hash = super.to_hash
13
+ hash.delete('block')
14
+ hash
15
+ end
16
+
17
+ def globals
18
+ @stack.globals
19
+ end
20
+
21
+ def to_json_data
22
+ { "Default" => @Default, "Type" => @Type }
23
+ end
24
+
25
+ def default value
26
+ @Default = value
27
+ end
28
+
29
+ def type value
30
+ @Type = value
31
+ end
32
+
33
+ def brick
34
+ @block.binding.eval('brick')
35
+ end
36
+ # def method_missing(field, *params)
37
+ # instance_variable_set("@#{field}", value)
38
+ # print @block.binding.eval('@hash')
39
+ # null
40
+ # end
41
+ end
42
+ end
43
+
@@ -0,0 +1,88 @@
1
+ module CloudBuilder
2
+ module ExposesRefs
3
+ def ref(name)
4
+ Reference.new(name)
5
+ end
6
+
7
+ def base64(value)
8
+ Reference::Base64.new(value)
9
+ end
10
+
11
+ def get_att(name, att)
12
+ Reference::Attribute.new(name, att)
13
+ end
14
+
15
+ def join(sep, values)
16
+ Reference::Join.new(sep, values)
17
+ end
18
+ end
19
+
20
+ class Reference
21
+ def initialize(name)
22
+ @name = name
23
+ end
24
+
25
+ def to_json_data
26
+ {REF => DSL.format(@name)}
27
+ end
28
+
29
+ class Base64
30
+ def initialize(value)
31
+ @value = value
32
+ end
33
+
34
+ def to_json_data
35
+ { BASE64 => @value }
36
+ end
37
+ end
38
+
39
+ class Join
40
+ def initialize(sep, values)
41
+ @sep = sep
42
+ @values = values
43
+ end
44
+
45
+ def to_json_data
46
+ { JOIN => [ @sep, DSL.jsonize(@values) ]}
47
+ end
48
+ end
49
+
50
+ class Attribute
51
+ def initialize(name, att)
52
+ @name = name
53
+ @att = att
54
+ end
55
+
56
+ def to_json_data
57
+ {GET_ATT => [DSL.format(@name), DSL.format(@att)]}
58
+ end
59
+ end
60
+
61
+ class Map
62
+ def initialize(name)
63
+ @name = name.to_s
64
+ end
65
+
66
+ def [](key)
67
+ Key.new(@name, [key])
68
+ end
69
+ end
70
+
71
+ class Key
72
+ def initialize(name, key)
73
+ @map_name = name
74
+ @key = key
75
+ end
76
+
77
+ def [](subkey)
78
+ Key.new(@map_name, @key + [subkey])
79
+ end
80
+
81
+ def to_json_data
82
+ {FIND_IN_MAP => [DSL.format(@map_name)] + DSL.jsonize(@key)}
83
+ end
84
+ end
85
+
86
+ end
87
+ end
88
+
@@ -0,0 +1,82 @@
1
+ require 'cloud_builder/reference'
2
+
3
+ module CloudBuilder
4
+ class Resource
5
+ include ExposesRefs
6
+ def initialize(stack, name, &block)
7
+ @stack = stack
8
+ @type = nil
9
+ @properties = {}
10
+ @metadata = {}
11
+ @block = block
12
+
13
+ # add metadata describing the brick we were defined in
14
+ if @block.binding.eval('@type')
15
+ metadata do
16
+ brick_name @block.binding.eval('@type')
17
+ end
18
+ end
19
+
20
+ instance_eval(&block)
21
+ end
22
+
23
+ def type(value)
24
+ @type = value
25
+ end
26
+
27
+ def version(value)
28
+ @version = value
29
+ end
30
+
31
+ def properties
32
+ old_map = @current_map
33
+ @current_map = @properties
34
+ yield
35
+ @current_map = old_map
36
+ end
37
+
38
+ def metadata
39
+ old_map = @current_map
40
+ @current_map = @metadata
41
+ yield
42
+ @current_map = old_map
43
+ end
44
+
45
+ def brick
46
+ @block.binding.eval('brick')
47
+ end
48
+
49
+ def globals
50
+ @stack.globals
51
+ end
52
+
53
+ def tags(tag_map, propagate = nil)
54
+ list = @current_map[TAGS] ||= []
55
+ tag_map = Hash[tag_map.map { |key, value| [key.to_s, value] }]
56
+ tag_map.keys.sort.each do |key|
57
+ if propagate.nil?
58
+ list << {KEY => DSL.format(key), VALUE => DSL.jsonize(tag_map[key])}
59
+ else
60
+ list << {KEY => DSL.format(key), VALUE => DSL.jsonize(tag_map[key]), PROPAGATE_AT_LAUNCH => propagate }
61
+ end
62
+ end
63
+ end
64
+
65
+ def to_json_data
66
+ if @version
67
+ {TYPE => @type, "Version" => @version, METADATA => @metadata, PROPERTIES => @properties}
68
+ else
69
+ {TYPE => @type, METADATA => @metadata, PROPERTIES => @properties}
70
+ end
71
+ end
72
+
73
+ def method_missing(field, *params)
74
+ if @stack.reference_type(field.to_s) == :map
75
+ Reference::Map.new(field)
76
+ else
77
+ @current_map[DSL.format(field)] = DSL.jsonize(params.first)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,12 @@
1
+ module CloudBuilder
2
+ class Stack
3
+ include DSL
4
+ include ExposesRefs
5
+
6
+ def initialize filename
7
+ @dirname = File.dirname(File.expand_path(filename))
8
+ instance_eval File.read(filename), filename
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module CloudBuilder
2
+ GEM_VERSION = "0.0.6"
3
+ end
@@ -0,0 +1,45 @@
1
+ require "cloud_builder/version"
2
+ require "cloud_builder/cli"
3
+ require 'cloud_builder/dsl'
4
+ require 'cloud_builder/resource'
5
+ require 'cloud_builder/mappings'
6
+ require 'cloud_builder/reference'
7
+ require 'cloud_builder/stack'
8
+ require 'cloud_builder/brick'
9
+ require 'cloud_builder/jsonable'
10
+ require 'cloud_builder/parameter'
11
+ require 'cloud_builder/output'
12
+
13
+
14
+ require 'cloud_builder/dslostruct'
15
+ require 'cloud_builder/dontformatunderscore'
16
+
17
+ require 'json'
18
+ require 'forwardable'
19
+
20
+ module CloudBuilder
21
+ DESCRIPTION = 'Description'
22
+ GET_ATT = 'Fn::GetAtt'
23
+ FIND_IN_MAP = 'Fn::FindInMap'
24
+ BASE64 = 'Fn::Base64'
25
+ JOIN = 'Fn::Join'
26
+ KEY = 'Key'
27
+ MAPPINGS = 'Mappings'
28
+ PROPERTIES = 'Properties'
29
+ REF = 'Ref'
30
+ RESOURCES = 'Resources'
31
+ PARAMETERS = 'Parameters'
32
+ METADATA = 'Metadata'
33
+ OUTPUTS = 'Outputs'
34
+ TAGS = 'Tags'
35
+ TYPE = 'Type'
36
+ VALUE = 'Value'
37
+ PROPAGATE_AT_LAUNCH = 'PropagateAtLaunch'
38
+ VERSION = 'AWSTemplateFormatVersion'
39
+
40
+
41
+ class Spec
42
+ extend DSL
43
+ end
44
+ end
45
+
@@ -0,0 +1,14 @@
1
+ require 'cloud_builder'
2
+ require 'rubygems'
3
+ require 'aws-sdk'
4
+
5
+ stack = CloudBuilder::Stack.new(File.expand_path('../../stacks/example.rb', __FILE__))
6
+
7
+ describe stack, "#valid_cfn" do
8
+ it "returns valid cloudformation" do
9
+ cf = AWS::CloudFormation.new
10
+ template = stack.to_json
11
+ result = cf.validate_template(template)
12
+ result.has_key?(:code).should eq false
13
+ end
14
+ end
@@ -0,0 +1,42 @@
1
+ #
2
+ # Define the default *ingress* security rules. Your office IPs and VPN IPs need to be in here
3
+ #
4
+ def globals.example_security_group_ingress
5
+ # Define VPC vs. non-vpc security groups differently
6
+ if self.respond_to?('no_vpc') && self.no_vpc
7
+ protocols = [ "tcp", "udp", "icmp" ]
8
+ from_port = "-1"
9
+ to_port = "-1"
10
+ else
11
+ protocols = [ "-1" ]
12
+ from_port = "0"
13
+ to_port = "65535"
14
+ end
15
+
16
+ rules = []
17
+ protocols.each do |protocol|
18
+ case protocol
19
+ when "tcp"
20
+ from_port = "0"
21
+ to_port = "65535"
22
+ when "udp"
23
+ from_port = "0"
24
+ to_port = "65535"
25
+ when "icmp"
26
+ from_port = "-1"
27
+ to_port = "-1"
28
+ when "-1"
29
+ from_port = "0"
30
+ to_port = "65535"
31
+ else
32
+ # error ?
33
+ end
34
+
35
+ rules += [
36
+ # Main office IP
37
+ { :ip_protocol => protocol, :from_port => from_port, :to_port => to_port, :cidr_ip => "123.123.123.123/32", },
38
+ ]
39
+ end
40
+
41
+ rules
42
+ end
@@ -0,0 +1,38 @@
1
+ # Defines Example specific parameters and tags
2
+ # - client
3
+ # - projects
4
+ # - environments
5
+ #
6
+ # Also defines the globals.example_tags method
7
+
8
+ # Client name
9
+ parameter :client do
10
+ default globals.client
11
+ end
12
+
13
+ # Project name
14
+ parameter :project do
15
+ default globals.project
16
+ end
17
+
18
+ # Environment name
19
+ parameter :environment do
20
+ default globals.environment
21
+ end
22
+
23
+ # Output from this method can be passed directly to the tags parameter block
24
+ #
25
+ # Example:
26
+ # resource :name do
27
+ # type "AWS::EC2::Instance"
28
+ # properties do
29
+ # tags globals.example_tags
30
+ # end
31
+ # end
32
+ def globals.example_tags
33
+ {
34
+ :client => ref(:client),
35
+ :project => ref(:project),
36
+ :environment => ref(:environment),
37
+ }
38
+ end
@@ -0,0 +1,39 @@
1
+ # Ubuntu Precise (12.04 LTS) Release 20130624 EC2 AMIs for ebs Instance Types
2
+ # Source: http://cloud-images.ubuntu.com/locator/ec2/
3
+
4
+ ami_ids = {}
5
+ ami_ids["ap-northeast-1"] = {}
6
+ ami_ids["ap-northeast-1"]["64"] = "ami-51129850"
7
+ ami_ids["ap-northeast-1"]["32"] = "ami-4b12984a"
8
+
9
+ ami_ids["ap-southeast-1"] = {}
10
+ ami_ids["ap-southeast-1"]["64"] = "ami-a02f66f2"
11
+ ami_ids["ap-southeast-1"]["32"] = "ami-a22f66f0"
12
+
13
+ ami_ids["eu-west-1"] = {}
14
+ ami_ids["eu-west-1"]["64"] = "ami-89b1a3fd"
15
+ ami_ids["eu-west-1"]["32"] = "ami-8fb1a3fb"
16
+
17
+ ami_ids["sa-east-1"] = {}
18
+ ami_ids["sa-east-1"]["64"] = "ami-5c7edb41"
19
+ ami_ids["sa-east-1"]["32"] = "ami-227edb3f"
20
+
21
+ ami_ids["us-east-1"] = {}
22
+ ami_ids["us-east-1"]["64"] = "ami-23d9a94a"
23
+ ami_ids["us-east-1"]["32"] = "ami-21d9a948"
24
+
25
+ ami_ids["us-west-1"] = {}
26
+ ami_ids["us-west-1"]["64"] = "ami-c4072e81"
27
+ ami_ids["us-west-1"]["32"] = "ami-3a072e7f"
28
+
29
+ ami_ids["ap-southeast-2"] = {}
30
+ ami_ids["ap-southeast-2"]["64"] = "ami-974ddead"
31
+ ami_ids["ap-southeast-2"]["32"] = "ami-914ddeab"
32
+
33
+ ami_ids["us-west-2"] = {}
34
+ ami_ids["us-west-2"]["64"] = "ami-fb68f8cb"
35
+ ami_ids["us-west-2"]["32"] = "ami-f968f8c9"
36
+
37
+ mappings do
38
+ ami_ids_ubuntu_ebs_20130624 ami_ids
39
+ end
@@ -0,0 +1,40 @@
1
+ # Ubuntu Precise (12.04 LTS) Release 20130624 EC2 AMIs for instance-store Instance Types
2
+ # Source: http://cloud-images.ubuntu.com/locator/ec2/
3
+
4
+ ami_ids = {}
5
+ ami_ids["ap-northeast-1"] = {}
6
+ ami_ids["ap-northeast-1"]["64"] = "ami-7d1d977c"
7
+ ami_ids["ap-northeast-1"]["32"] = "ami-d31c96d2"
8
+
9
+ ami_ids["ap-southeast-1"] = {}
10
+ ami_ids["ap-southeast-1"]["64"] = "ami-b02f66e2"
11
+ ami_ids["ap-southeast-1"]["32"] = "ami-c22f6690"
12
+
13
+ ami_ids["eu-west-1"] = {}
14
+ ami_ids["eu-west-1"]["64"] = "ami-57b0a223"
15
+ ami_ids["eu-west-1"]["32"] = "ami-a5b2a0d1"
16
+
17
+ ami_ids["sa-east-1"] = {}
18
+ ami_ids["sa-east-1"]["64"] = "ami-027edb1f"
19
+ ami_ids["sa-east-1"]["32"] = "ami-ce7fdad3"
20
+
21
+ ami_ids["us-east-1"] = {}
22
+ ami_ids["us-east-1"]["64"] = "ami-d9d6a6b0"
23
+ ami_ids["us-east-1"]["32"] = "ami-bfd3a3d6"
24
+
25
+ ami_ids["us-west-1"] = {}
26
+ ami_ids["us-west-1"]["64"] = "ami-72072e37"
27
+ ami_ids["us-west-1"]["32"] = "ami-e6042da3"
28
+
29
+ ami_ids["ap-southeast-2"] = {}
30
+ ami_ids["ap-southeast-2"]["64"] = "ami-934ddea9"
31
+ ami_ids["ap-southeast-2"]["32"] = "ami-134dde29"
32
+
33
+ ami_ids["us-west-2"] = {}
34
+ ami_ids["us-west-2"]["64"] = "ami-5168f861"
35
+ ami_ids["us-west-2"]["32"] = "ami-ef67f7df"
36
+
37
+
38
+ mappings do
39
+ ami_ids_ubuntu_instancestore_20130624 ami_ids
40
+ end
@@ -0,0 +1,35 @@
1
+ mappings do
2
+ aws_instance_type_to_arch(
3
+ {
4
+ "t1.micro"=> {
5
+ "Arch"=> "64"
6
+ },
7
+ "m1.small"=> {
8
+ "Arch"=> "64"
9
+ },
10
+ "m1.medium"=> {
11
+ "Arch"=> "64"
12
+ },
13
+ "m1.large"=> {
14
+ "Arch"=> "64"
15
+ },
16
+ "m1.xlarge"=> {
17
+ "Arch"=> "64"
18
+ },
19
+ "m2.xlarge"=> {
20
+ "Arch"=> "64"
21
+ },
22
+ "m2.2xlarge"=> {
23
+ "Arch"=> "64"
24
+ },
25
+ "m2.4xlarge"=> {
26
+ "Arch"=> "64"
27
+ },
28
+ "c1.medium"=> {
29
+ "Arch"=> "64"
30
+ },
31
+ "c1.xlarge"=> {
32
+ "Arch"=> "64"
33
+ }
34
+ })
35
+ end
data/stacks/example.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'rubygems'
2
+ require 'netaddr'
3
+
4
+ #
5
+ # Define some global variables
6
+ #
7
+
8
+ # This variable isn't used anywhere, but provides the perfect example for
9
+ # using Ruby code in the DSL
10
+ globals.subnet_class_b = NetAddr::CIDR.create('10.200.0.0/16')
11
+
12
+ #
13
+ globals.client = 'example'
14
+ globals.project = 'cloud_builder'
15
+ globals.environment = 'testing'
16
+
17
+ #
18
+ # We will not be using a vpc;
19
+ #
20
+ globals.no_vpc = true
21
+
22
+ #
23
+ # Include bricks
24
+ #
25
+ include_brick :example_tags
26
+ include_brick :example_security_groups
27
+
28
+ include_brick "mappings/ami_ids_ubuntu_ebs_20130624"
29
+ include_brick "mappings/instance2arch"
30
+
31
+ #
32
+ # Begin the actual template descriptions
33
+ #
34
+ description "%s/%s/%s" % [globals.client, globals.project, globals.environment]
35
+ template_version "2010-09-09"
36
+
37
+
38
+ # ssh key name that will be used
39
+ parameter :key_name do
40
+ default :example
41
+ end
42
+
43
+ parameter :example_instance_type do
44
+ default "m1.small"
45
+ end
46
+
47
+ # Security group for the example instance
48
+ resource "example_instance_security_group" do
49
+ type "AWS::EC2::SecurityGroup"
50
+ properties do
51
+ group_description "example security group"
52
+ security_group_ingress globals.example_security_group_ingress + [
53
+ {
54
+ :ip_protocol => "tcp",
55
+ :from_port => "8080",
56
+ :to_port => "8080",
57
+ :cidr_ip => "0.0.0.0/0",
58
+ }
59
+ ]
60
+ end
61
+ end
62
+
63
+ # EC2 instance declaration
64
+ resource "example_instance" do
65
+ type "AWS::EC2::Instance"
66
+ properties do
67
+ key_name ref(:key_name)
68
+ image_id ami_ids_ubuntu_ebs_20130624[ref("AWS::Region")][ aws_instance_type_to_arch[ref(:example_instance_type)]["Arch"] ]
69
+
70
+
71
+ security_group_ids [ ref(:example_instance_security_group) ]
72
+
73
+ instance_type ref(:example_instance_type)
74
+
75
+ monitoring true
76
+ tags globals.example_tags
77
+ tags :function => "example"
78
+ tags :name => "%s-%s-%s-%s" % [globals.client, globals.project, globals.environment, "example"]
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloud_builder
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 6
9
+ version: 0.0.6
10
+ platform: ruby
11
+ authors:
12
+ - Sorin Stoiana
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-09-19 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: netaddr
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rspec
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 2
41
+ - 6
42
+ version: "2.6"
43
+ type: :development
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: clamp
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ type: :runtime
56
+ version_requirements: *id003
57
+ - !ruby/object:Gem::Dependency
58
+ name: json
59
+ prerelease: false
60
+ requirement: &id004 !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ type: :runtime
68
+ version_requirements: *id004
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-sdk
71
+ prerelease: false
72
+ requirement: &id005 !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ type: :runtime
80
+ version_requirements: *id005
81
+ - !ruby/object:Gem::Dependency
82
+ name: activesupport
83
+ prerelease: false
84
+ requirement: &id006 !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ type: :runtime
92
+ version_requirements: *id006
93
+ description: Generate JSON config for AWS CloudFormation using a Ruby based DSL
94
+ email:
95
+ - sstoiana@optaros.com
96
+ executables:
97
+ - stack
98
+ extensions: []
99
+
100
+ extra_rdoc_files: []
101
+
102
+ files:
103
+ - .gitignore
104
+ - .travis.yml
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - bin/stack
110
+ - cloud_builder.gemspec
111
+ - lib/cloud_builder.rb
112
+ - lib/cloud_builder/brick.rb
113
+ - lib/cloud_builder/cli.rb
114
+ - lib/cloud_builder/dontformatunderscore.rb
115
+ - lib/cloud_builder/dsl.rb
116
+ - lib/cloud_builder/dslostruct.rb
117
+ - lib/cloud_builder/jsonable.rb
118
+ - lib/cloud_builder/mappings.rb
119
+ - lib/cloud_builder/output.rb
120
+ - lib/cloud_builder/parameter.rb
121
+ - lib/cloud_builder/reference.rb
122
+ - lib/cloud_builder/resource.rb
123
+ - lib/cloud_builder/stack.rb
124
+ - lib/cloud_builder/version.rb
125
+ - spec/example_spec.rb
126
+ - stacks/bricks/example_security_groups.rb
127
+ - stacks/bricks/example_tags.rb
128
+ - stacks/bricks/mappings/ami_ids_ubuntu_ebs_20130624.rb
129
+ - stacks/bricks/mappings/ami_ids_ubuntu_instancestore_20130624.rb
130
+ - stacks/bricks/mappings/instance2arch.rb
131
+ - stacks/example.rb
132
+ has_rdoc: true
133
+ homepage: https://github.com/Optaros/cloud_builder
134
+ licenses: []
135
+
136
+ post_install_message:
137
+ rdoc_options: []
138
+
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ segments:
146
+ - 0
147
+ version: "0"
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ segments:
153
+ - 0
154
+ version: "0"
155
+ requirements: []
156
+
157
+ rubyforge_project:
158
+ rubygems_version: 1.3.6
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: Generate JSON config for AWS CloudFormation
162
+ test_files:
163
+ - spec/example_spec.rb