cloud_builder 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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