stacker 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/stacker +4 -1
- data/lib/stacker.rb +4 -5
- data/lib/stacker/cli.rb +180 -83
- data/lib/stacker/differ.rb +31 -0
- data/lib/stacker/logging.rb +61 -0
- data/lib/stacker/region.rb +32 -0
- data/lib/stacker/resolver.rb +29 -0
- data/lib/stacker/stack.rb +109 -25
- data/lib/stacker/stack/capabilities.rb +17 -0
- data/lib/stacker/stack/component.rb +22 -0
- data/lib/stacker/stack/parameters.rb +68 -0
- data/lib/stacker/stack/template.rb +86 -0
- data/lib/stacker/version.rb +1 -1
- metadata +141 -60
- data/.gitignore +0 -5
- data/.rvmrc +0 -1
- data/Gemfile +0 -4
- data/README.md +0 -21
- data/Rakefile +0 -1
- data/lib/stacker/aws.rb +0 -47
- data/lib/stacker/repo.rb +0 -22
- data/lib/stacker/template.rb +0 -77
- data/stacker.gemspec +0 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bc3ba2bbeac3d470e4d355cf4605df35e6640720
|
4
|
+
data.tar.gz: c6e53ace12414864ae41bbfe9a3ed32135daab50
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 669c65102b76e13165b7668e56631f6b97d02ee5643f703ed2f0cdffa41c8d8844ae320e6ccfc218582a41f2e27f1f5079ecbe1ca10dd736bc42b768caae6662
|
7
|
+
data.tar.gz: 25f33fa2551709a54a55cd5a3633374c183078f8dbb7508f4ff986d180e9470d0ce1910bedf3cc13a507ad7a936ab3b7c99aa20832b4ddd5e163d9ab89e01096
|
data/bin/stacker
CHANGED
data/lib/stacker.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require "stacker/template"
|
1
|
+
require 'stacker/region'
|
2
|
+
require 'stacker/stack'
|
3
|
+
require 'stacker/logging'
|
4
|
+
require 'stacker/version'
|
data/lib/stacker/cli.rb
CHANGED
@@ -1,119 +1,216 @@
|
|
1
|
-
require '
|
1
|
+
require 'benchmark'
|
2
|
+
require 'stacker'
|
3
|
+
require 'thor'
|
2
4
|
require 'yaml'
|
3
5
|
|
4
6
|
module Stacker
|
5
|
-
|
6
|
-
|
7
|
-
read_variables
|
7
|
+
class Cli < Thor
|
8
|
+
include Thor::Actions
|
8
9
|
|
9
|
-
|
10
|
+
default_path = ENV['STACKER_PATH'] || '.'
|
11
|
+
default_region = ENV['STACKER_REGION'] || 'us-east-1'
|
10
12
|
|
11
|
-
|
13
|
+
method_option :path, default: default_path, banner: 'project path'
|
14
|
+
method_option :region, default: default_region, banner: 'AWS region name'
|
15
|
+
def initialize(*args); super(*args) end
|
12
16
|
|
13
|
-
|
17
|
+
desc "init [PATH]", "Create stacker project directories"
|
18
|
+
def init path = nil
|
19
|
+
init_project path || options['path']
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
describe
|
22
|
+
desc "list", "List stacks"
|
23
|
+
def list
|
24
|
+
Stacker.logger.inspect region.stacks.map(&:name)
|
20
25
|
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
desc "show STACK_NAME", "Show details of a stack"
|
28
|
+
def show stack_name
|
29
|
+
with_one_or_all stack_name do |stack|
|
30
|
+
Stacker.logger.inspect(
|
31
|
+
'Description' => stack.description,
|
32
|
+
'Status' => stack.status,
|
33
|
+
'Updated' => stack.last_updated_time || stack.creation_time,
|
34
|
+
'Capabilities' => stack.capabilities.remote,
|
35
|
+
'Parameters' => stack.parameters.remote,
|
36
|
+
'Outputs' => stack.outputs
|
37
|
+
)
|
38
|
+
end
|
25
39
|
end
|
26
40
|
|
27
|
-
|
28
|
-
|
41
|
+
desc "status [STACK_NAME]", "Show stack status"
|
42
|
+
def status stack_name = nil
|
43
|
+
with_one_or_all(stack_name) do |stack|
|
44
|
+
Stacker.logger.debug stack.status.indent
|
45
|
+
end
|
29
46
|
end
|
30
47
|
|
31
|
-
|
32
|
-
|
48
|
+
desc "diff [STACK_NAME]", "Show outstanding stack differences"
|
49
|
+
def diff stack_name = nil
|
50
|
+
with_one_or_all(stack_name) do |stack|
|
51
|
+
resolve stack
|
52
|
+
next unless full_diff stack
|
53
|
+
end
|
33
54
|
end
|
34
55
|
|
35
|
-
|
36
|
-
|
56
|
+
desc "update [STACK_NAME]", "Create or update stack"
|
57
|
+
def update stack_name = nil
|
58
|
+
with_one_or_all(stack_name) do |stack|
|
59
|
+
resolve stack
|
60
|
+
|
61
|
+
if stack.exists?
|
62
|
+
next unless full_diff stack
|
63
|
+
|
64
|
+
if yes? "Update remote template with these changes (y/n)?"
|
65
|
+
time = Benchmark.realtime do
|
66
|
+
stack.update
|
67
|
+
end
|
68
|
+
Stacker.logger.info time stack_name, 'updated', time
|
69
|
+
else
|
70
|
+
Stacker.logger.warn 'Update skipped'
|
71
|
+
end
|
72
|
+
else
|
73
|
+
if yes? "#{stack.name} does not exist. Create it (y/n)?"
|
74
|
+
time = Benchmark.realtime do
|
75
|
+
stack.create
|
76
|
+
end
|
77
|
+
Stacker.logger.info time stack_name, 'created', time
|
78
|
+
else
|
79
|
+
Stacker.logger.warn 'Create skipped'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
37
83
|
end
|
38
84
|
|
39
|
-
|
40
|
-
|
41
|
-
|
85
|
+
desc "dump [STACK_NAME]", "Download stack template"
|
86
|
+
def dump stack_name = nil
|
87
|
+
with_one_or_all(stack_name) do |stack|
|
88
|
+
if stack.exists?
|
89
|
+
diff = stack.template.diff :down, :color
|
90
|
+
next Stacker.logger.warn 'Stack up-to-date' if diff.length == 0
|
91
|
+
|
92
|
+
Stacker.logger.debug "\n" + diff.indent
|
93
|
+
if yes? "Update local template with these changes (y/n)?"
|
94
|
+
stack.template.dump
|
95
|
+
else
|
96
|
+
Stacker.logger.warn 'Pull skipped'
|
97
|
+
end
|
98
|
+
else
|
99
|
+
Stacker.logger.warn "#{stack.name} does not exist"
|
100
|
+
end
|
42
101
|
end
|
43
102
|
end
|
44
103
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def self.read_variables
|
55
|
-
@cmd = ARGV.shift
|
56
|
-
|
57
|
-
@opts = case @cmd
|
58
|
-
when "create"
|
59
|
-
Trollop::options do
|
60
|
-
opt :attributes, "Attributes definition file", :default => 'attributes.json.erb'
|
61
|
-
opt :git_repo, "Git repository to export.", :type => String #:default => 'git@github.com:brettweavnet/stacker-repo.git'
|
62
|
-
opt :path, "Path to configuraiton repo. If a -g or --git_repo is specified, it will override this setting.", :type => String
|
63
|
-
opt :name, "Name of stack to create.", :type => String
|
64
|
-
opt :output, "Template output file.", :default => 'stack.json'
|
65
|
-
opt :resources, "Resource definition file", :default => 'resources.json.erb'
|
66
|
-
opt :silent, "Silent mode.", :default => false
|
67
|
-
opt :temp_dir, "Directory to export configuration git repo.", :default => File.join(Dir.home, "stack-repo")
|
68
|
-
opt :verbose, "Verbose mode.", :default => false
|
69
|
-
end
|
70
|
-
when "delete"
|
71
|
-
Trollop::options do
|
72
|
-
opt :name, "Name of stack to delete.", :type => String
|
73
|
-
opt :silent, "Silent mode.", :default => false
|
74
|
-
opt :verbose, "Verbose mode.", :default => false
|
75
|
-
end
|
76
|
-
when "describe"
|
77
|
-
Trollop::options do
|
78
|
-
opt :name, "Name of stack to describe.", :type => String
|
79
|
-
opt :verbose, "Verbose mode.", :default => false
|
80
|
-
end
|
81
|
-
when "events"
|
82
|
-
Trollop::options do
|
83
|
-
opt :name, "Name of stack who's events to display.", :type => String
|
84
|
-
opt :verbose, "Verbose mode.", :default => false
|
104
|
+
desc "fmt [STACK_NAME]", "Re-format template JSON"
|
105
|
+
def fmt stack_name = nil
|
106
|
+
with_one_or_all(stack_name) do |stack|
|
107
|
+
if stack.template.exists?
|
108
|
+
Stacker.logger.warn 'Formatting...'
|
109
|
+
stack.template.write
|
110
|
+
else
|
111
|
+
Stacker.logger.warn "#{stack.name} does not exist"
|
85
112
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def init_project path
|
119
|
+
project_path = File.expand_path path
|
120
|
+
|
121
|
+
%w[ regions templates ].each do |dir|
|
122
|
+
directory_path = File.join project_path, dir
|
123
|
+
unless Dir.exists? directory_path
|
124
|
+
Stacker.logger.debug "Creating directory at #{directory_path}"
|
125
|
+
FileUtils.mkdir_p directory_path
|
89
126
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
127
|
+
end
|
128
|
+
|
129
|
+
region_path = File.join project_path, 'regions', 'us-east-1.yml'
|
130
|
+
unless File.exists? region_path
|
131
|
+
Stacker.logger.debug "Creating region file at #{region_path}"
|
132
|
+
File.open(region_path, 'w+') { |f| f.print <<-YAML }
|
133
|
+
defaults:
|
134
|
+
parameters:
|
135
|
+
CidrBlock: '10.0'
|
136
|
+
stacks:
|
137
|
+
- name: VPC
|
138
|
+
YAML
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def full_diff stack
|
143
|
+
templ_diff = stack.template.diff :color
|
144
|
+
param_diff = stack.parameters.diff :color
|
145
|
+
|
146
|
+
if (templ_diff + param_diff).length == 0
|
147
|
+
Stacker.logger.warn 'Stack up-to-date'
|
148
|
+
return false
|
149
|
+
end
|
150
|
+
|
151
|
+
Stacker.logger.info "\n#{templ_diff.indent}\n" if templ_diff.length > 0
|
152
|
+
Stacker.logger.info "\n#{param_diff.indent}\n" if param_diff.length > 0
|
153
|
+
|
154
|
+
true
|
155
|
+
end
|
156
|
+
|
157
|
+
def region
|
158
|
+
@region ||= begin
|
159
|
+
config_path = File.join working_path, 'regions', "#{options['region']}.yml"
|
160
|
+
if File.exists? config_path
|
161
|
+
begin
|
162
|
+
config = YAML.load_file(config_path)
|
163
|
+
rescue Psych::SyntaxError => err
|
164
|
+
Stacker.logger.fatal err.message
|
165
|
+
exit 1
|
166
|
+
end
|
167
|
+
|
168
|
+
defaults = config.fetch 'defaults', {}
|
169
|
+
stacks = config.fetch 'stacks', {}
|
170
|
+
|
171
|
+
Region.new options['region'], defaults, stacks, templates_path
|
172
|
+
else
|
173
|
+
Stacker.logger.fatal "#{options['region']}.yml does not exist. Please configure or use stacker init"
|
174
|
+
exit 1
|
94
175
|
end
|
95
|
-
else
|
96
|
-
status "\nUnknown command: '#{@cmd}'.\n"
|
97
|
-
status "\nstacker [create|describe|delete|list] --help\n"
|
98
|
-
exit 1
|
99
176
|
end
|
100
177
|
end
|
101
178
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
179
|
+
def resolve stack
|
180
|
+
return {} if stack.parameters.resolver.dependencies.none?
|
181
|
+
Stacker.logger.debug 'Resolving dependencies...'
|
182
|
+
stack.parameters.resolved
|
105
183
|
end
|
106
184
|
|
107
|
-
def
|
108
|
-
|
185
|
+
def time (stack, action, benchmark)
|
186
|
+
return "Stack #{stack} #{action} in: #{(benchmark / 60).floor} min and #{(benchmark % 60).round} seconds."
|
109
187
|
end
|
110
188
|
|
111
|
-
def
|
112
|
-
|
189
|
+
def with_one_or_all stack_name = nil, &block
|
190
|
+
yield_with_stack = proc do |stack|
|
191
|
+
Stacker.logger.info "#{stack.name}:"
|
192
|
+
yield stack
|
193
|
+
Stacker.logger.info ''
|
194
|
+
end
|
195
|
+
|
196
|
+
if stack_name
|
197
|
+
yield_with_stack.call region.stack(stack_name)
|
198
|
+
else
|
199
|
+
region.stacks.each(&yield_with_stack)
|
200
|
+
end
|
201
|
+
|
202
|
+
rescue Stacker::Stack::Error => err
|
203
|
+
Stacker.logger.fatal err.message
|
204
|
+
exit 1
|
205
|
+
end
|
206
|
+
|
207
|
+
def templates_path
|
208
|
+
File.join working_path, 'templates'
|
113
209
|
end
|
114
210
|
|
115
|
-
def
|
116
|
-
|
211
|
+
def working_path
|
212
|
+
File.expand_path options['path']
|
117
213
|
end
|
214
|
+
|
118
215
|
end
|
119
216
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Stacker
|
5
|
+
module Differ
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def diff one, two, *args
|
10
|
+
down = args.include? :down
|
11
|
+
|
12
|
+
diff = Diffy::Diff.new(
|
13
|
+
(down ? one : two) + "\n",
|
14
|
+
(down ? two : one) + "\n",
|
15
|
+
context: 3,
|
16
|
+
include_diff_info: true
|
17
|
+
).to_s(*args.select { |arg| arg == :color })
|
18
|
+
|
19
|
+
diff.gsub(/^(\x1B.+)?(\-{3}|\+{3}).+\n/, '').strip
|
20
|
+
end
|
21
|
+
|
22
|
+
def json_diff one, two, *args
|
23
|
+
diff JSON.pretty_generate(one), JSON.pretty_generate(two), *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def yaml_diff one, two, *args
|
27
|
+
diff YAML.dump(one), YAML.dump(two), *args
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'coderay'
|
2
|
+
require 'delegate'
|
3
|
+
require 'indentation'
|
4
|
+
require 'rainbow'
|
5
|
+
|
6
|
+
module Stacker
|
7
|
+
module_function
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
class PrettyLogger < SimpleDelegator
|
12
|
+
def initialize logger
|
13
|
+
super
|
14
|
+
|
15
|
+
old_formatter = logger.formatter
|
16
|
+
|
17
|
+
logger.formatter = proc do |level, time, prog, msg|
|
18
|
+
unless msg.start_with?("\e")
|
19
|
+
color = case level
|
20
|
+
when 'FATAL' then :red
|
21
|
+
when 'WARN' then :yellow
|
22
|
+
when 'INFO' then :blue
|
23
|
+
when 'DEBUG' then '333333'
|
24
|
+
else :default
|
25
|
+
end
|
26
|
+
msg = msg.color(color)
|
27
|
+
end
|
28
|
+
|
29
|
+
old_formatter.call level, time, prog, msg
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
%w[ debug info warn fatal ].each do |level|
|
34
|
+
define_method level do |msg, opts = {}|
|
35
|
+
if opts.include? :highlight
|
36
|
+
msg = CodeRay.scan(msg, opts[:highlight]).terminal
|
37
|
+
end
|
38
|
+
__getobj__.__send__ level, msg
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspect object
|
43
|
+
info object.to_yaml[4..-1].strip.indent, highlight: :yaml
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def logger= logger
|
48
|
+
@logger = PrettyLogger.new logger
|
49
|
+
end
|
50
|
+
|
51
|
+
def logger
|
52
|
+
@logger ||= begin
|
53
|
+
logger = Logger.new STDOUT
|
54
|
+
logger.level = Logger::DEBUG
|
55
|
+
logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
|
56
|
+
PrettyLogger.new logger
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'stacker/stack'
|
3
|
+
|
4
|
+
module Stacker
|
5
|
+
class Region
|
6
|
+
|
7
|
+
attr_reader :name, :defaults, :stacks, :templates_path
|
8
|
+
|
9
|
+
def initialize(name, defaults, stacks, templates_path)
|
10
|
+
@name = name
|
11
|
+
@defaults = defaults
|
12
|
+
@stacks = stacks.map do |options|
|
13
|
+
begin
|
14
|
+
Stack.new self, options.fetch('name'), options
|
15
|
+
rescue KeyError => err
|
16
|
+
Stacker.logger.fatal "Malformed YAML: #{err.message}"
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@templates_path = templates_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def client
|
24
|
+
@client ||= AWS::CloudFormation.new region: name
|
25
|
+
end
|
26
|
+
|
27
|
+
def stack name
|
28
|
+
stacks.find { |s| s.name == name } || Stack.new(self, name)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Stacker
|
2
|
+
class Resolver
|
3
|
+
|
4
|
+
attr_reader :region, :parameters
|
5
|
+
|
6
|
+
def initialize region, parameters
|
7
|
+
@region, @parameters = region, parameters
|
8
|
+
end
|
9
|
+
|
10
|
+
def dependencies
|
11
|
+
@dependencies ||= parameters.select { |_, value|
|
12
|
+
value.is_a?(Hash)
|
13
|
+
}.map { |_, value|
|
14
|
+
"#{value.fetch('Stack')}.#{value.fetch('Output')}"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def resolved
|
19
|
+
@resolved ||= Hash[parameters.map do |name, value|
|
20
|
+
if value.is_a? Hash
|
21
|
+
stack = region.stack value.fetch('Stack')
|
22
|
+
value = stack.outputs.fetch value.fetch('Output')
|
23
|
+
end
|
24
|
+
[ name, value ]
|
25
|
+
end]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/stacker/stack.rb
CHANGED
@@ -1,45 +1,129 @@
|
|
1
|
-
|
1
|
+
require 'active_support/core_ext/hash/slice'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 'memoist'
|
5
|
+
require 'stacker/stack/capabilities'
|
6
|
+
require 'stacker/stack/parameters'
|
7
|
+
require 'stacker/stack/template'
|
2
8
|
|
9
|
+
module Stacker
|
3
10
|
class Stack
|
4
|
-
attr_accessor :name
|
5
11
|
|
6
|
-
|
7
|
-
|
12
|
+
class Error < StandardError; end
|
13
|
+
class DoesNotExistError < Error; end
|
14
|
+
class MissingParameters < Error; end
|
15
|
+
class UpToDateError < Error; end
|
8
16
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
17
|
+
extend Memoist
|
18
|
+
|
19
|
+
CLIENT_METHODS = %w[
|
20
|
+
creation_time
|
21
|
+
description
|
22
|
+
exists?
|
23
|
+
last_updated_time
|
24
|
+
status
|
25
|
+
status_reason
|
26
|
+
]
|
13
27
|
|
14
|
-
|
28
|
+
attr_reader :region, :name, :options
|
29
|
+
|
30
|
+
def initialize region, name, options = {}
|
31
|
+
@region, @name, @options = region, name, options
|
15
32
|
end
|
16
33
|
|
17
|
-
def
|
18
|
-
|
19
|
-
@stack = AWS::CloudFormation::Stack.new
|
34
|
+
def client
|
35
|
+
@client ||= region.client.stacks[name]
|
20
36
|
end
|
21
37
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
38
|
+
delegate *CLIENT_METHODS, to: :client
|
39
|
+
memoize *CLIENT_METHODS
|
40
|
+
|
41
|
+
%w[complete failed in_progress].each do |stage|
|
42
|
+
define_method(:"#{stage}?") { status =~ /#{stage.upcase}/ }
|
26
43
|
end
|
27
44
|
|
28
|
-
def
|
29
|
-
@
|
45
|
+
def template
|
46
|
+
@template ||= Template.new self
|
30
47
|
end
|
31
48
|
|
32
|
-
def
|
33
|
-
@
|
49
|
+
def parameters
|
50
|
+
@parameters ||= Parameters.new self
|
34
51
|
end
|
35
52
|
|
36
|
-
def
|
37
|
-
@
|
53
|
+
def capabilities
|
54
|
+
@capabilities ||= Capabilities.new self
|
38
55
|
end
|
39
56
|
|
40
|
-
def
|
41
|
-
@
|
57
|
+
def outputs
|
58
|
+
@outputs ||= begin
|
59
|
+
return {} unless complete?
|
60
|
+
Hash[client.outputs.map { |output| [ output.key, output.value ] }]
|
61
|
+
end
|
42
62
|
end
|
43
|
-
end
|
44
63
|
|
64
|
+
def create blocking = true
|
65
|
+
if exists?
|
66
|
+
Stacker.logger.warn 'Stack already exists'
|
67
|
+
return
|
68
|
+
end
|
69
|
+
|
70
|
+
if parameters.missing.any?
|
71
|
+
raise MissingParameters.new(
|
72
|
+
"Required parameters missing: #{parameters.missing.join ', '}"
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
Stacker.logger.info 'Creating stack'
|
77
|
+
|
78
|
+
region.client.stacks.create(
|
79
|
+
name,
|
80
|
+
template.local,
|
81
|
+
parameters: parameters.resolved,
|
82
|
+
capabilities: capabilities.local
|
83
|
+
)
|
84
|
+
|
85
|
+
wait_while_status 'CREATE_IN_PROGRESS' if blocking
|
86
|
+
rescue AWS::CloudFormation::Errors::ValidationError => err
|
87
|
+
raise Error.new err.message
|
88
|
+
end
|
89
|
+
|
90
|
+
def update blocking = true
|
91
|
+
if parameters.missing.any?
|
92
|
+
raise MissingParameters.new(
|
93
|
+
"Required parameters missing: #{parameters.missing.join ', '}"
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
Stacker.logger.info 'Updating stack'
|
98
|
+
|
99
|
+
client.update(
|
100
|
+
template: template.local,
|
101
|
+
parameters: parameters.resolved,
|
102
|
+
capabilities: capabilities.local
|
103
|
+
)
|
104
|
+
|
105
|
+
wait_while_status 'UPDATE_IN_PROGRESS' if blocking
|
106
|
+
rescue AWS::CloudFormation::Errors::ValidationError => err
|
107
|
+
case err.message
|
108
|
+
when /does not exist/
|
109
|
+
raise DoesNotExistError.new err.message
|
110
|
+
when /No updates/
|
111
|
+
raise UpToDateError.new err.message
|
112
|
+
else
|
113
|
+
raise Error.new err.message
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def wait_while_status wait_status
|
120
|
+
while flush_cache(:status) && status == wait_status
|
121
|
+
Stacker.logger.debug "#{name} Status => #{status}"
|
122
|
+
sleep 5
|
123
|
+
end
|
124
|
+
Stacker.logger.info "#{name} Status => #{status}"
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
45
128
|
end
|
129
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'stacker/stack/component'
|
2
|
+
|
3
|
+
module Stacker
|
4
|
+
class Stack
|
5
|
+
class Capabilities < Component
|
6
|
+
|
7
|
+
def local
|
8
|
+
@local ||= Array(stack.options.fetch 'capabilities', [])
|
9
|
+
end
|
10
|
+
|
11
|
+
def remote
|
12
|
+
@remote ||= client.capabilities
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'stacker/stack'
|
2
|
+
|
3
|
+
module Stacker
|
4
|
+
class Stack
|
5
|
+
# an abstract base class for stack components (template, parameters)
|
6
|
+
class Component
|
7
|
+
|
8
|
+
attr_reader :stack
|
9
|
+
|
10
|
+
def initialize stack
|
11
|
+
@stack = stack
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def client
|
17
|
+
stack.client
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'memoist'
|
2
|
+
require 'stacker/differ'
|
3
|
+
require 'stacker/resolver'
|
4
|
+
require 'stacker/stack/component'
|
5
|
+
|
6
|
+
module Stacker
|
7
|
+
class Stack
|
8
|
+
class Parameters < Component
|
9
|
+
|
10
|
+
extend Memoist
|
11
|
+
|
12
|
+
# everything required by the template
|
13
|
+
def template_definitions
|
14
|
+
stack.template.local.fetch 'Parameters', {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def region_defaults
|
18
|
+
stack.region.defaults.fetch 'parameters', {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# template defaults merged with region and stack-specific overrides
|
22
|
+
def local
|
23
|
+
region_defaults = stack.region.defaults.fetch 'parameters', {}
|
24
|
+
|
25
|
+
template_defaults = Hash[
|
26
|
+
template_definitions.select { |_, opts|
|
27
|
+
opts.key?('Default')
|
28
|
+
}.map { |name, opts|
|
29
|
+
[name, opts['Default']]
|
30
|
+
}
|
31
|
+
]
|
32
|
+
|
33
|
+
available = template_defaults.merge(
|
34
|
+
region_defaults.merge(
|
35
|
+
stack.options.fetch 'parameters', {}
|
36
|
+
)
|
37
|
+
)
|
38
|
+
|
39
|
+
available.slice(*template_definitions.keys)
|
40
|
+
end
|
41
|
+
|
42
|
+
def missing
|
43
|
+
template_definitions.keys - local.keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def remote
|
47
|
+
client.parameters
|
48
|
+
end
|
49
|
+
memoize :remote
|
50
|
+
|
51
|
+
def resolved
|
52
|
+
resolver.resolved
|
53
|
+
end
|
54
|
+
memoize :resolved
|
55
|
+
|
56
|
+
def resolver
|
57
|
+
Resolver.new stack.region, local
|
58
|
+
end
|
59
|
+
memoize :resolver
|
60
|
+
|
61
|
+
def diff *args
|
62
|
+
Differ.yaml_diff Hash[resolved.sort], Hash[remote.sort], *args
|
63
|
+
end
|
64
|
+
memoize :diff
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
require 'json'
|
3
|
+
require 'memoist'
|
4
|
+
require 'stacker/differ'
|
5
|
+
require 'stacker/stack/component'
|
6
|
+
|
7
|
+
module Stacker
|
8
|
+
class Stack
|
9
|
+
class Template < Component
|
10
|
+
|
11
|
+
FORMAT_VERSION = '2010-09-09'
|
12
|
+
|
13
|
+
extend Memoist
|
14
|
+
|
15
|
+
def exists?
|
16
|
+
File.exists? path
|
17
|
+
end
|
18
|
+
|
19
|
+
def local
|
20
|
+
@local ||= begin
|
21
|
+
if exists?
|
22
|
+
template = JSON.parse File.read path
|
23
|
+
template['AWSTemplateFormatVersion'] ||= FORMAT_VERSION
|
24
|
+
template
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def remote
|
32
|
+
@remote ||= JSON.parse client.template
|
33
|
+
rescue AWS::CloudFormation::Errors::ValidationError => err
|
34
|
+
if err.message =~ /does not exist/
|
35
|
+
raise DoesNotExistError.new err.message
|
36
|
+
else
|
37
|
+
raise Error.new err.message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def diff *args
|
42
|
+
Differ.json_diff local, remote, *args
|
43
|
+
end
|
44
|
+
memoize :diff
|
45
|
+
|
46
|
+
def write value = local
|
47
|
+
File.write path, JSONFormatter.format(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
def dump
|
51
|
+
write remote
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def path
|
57
|
+
@path ||= File.join(
|
58
|
+
stack.region.templates_path,
|
59
|
+
"#{stack.options.fetch('template_name', stack.name)}.json"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
class JSONFormatter
|
64
|
+
STR = '\"[^\"]+\"'
|
65
|
+
|
66
|
+
def self.format object
|
67
|
+
formatted = JSON.pretty_generate object
|
68
|
+
|
69
|
+
# put empty arrays on a single line
|
70
|
+
formatted.gsub! /: \[\s*\]/m, ': []'
|
71
|
+
|
72
|
+
# put { "Ref": ... } on a single line
|
73
|
+
formatted.gsub! /\{\s+\"Ref\"\:\s+(?<ref>#{STR})\s+\}/m,
|
74
|
+
'{ "Ref": \\k<ref> }'
|
75
|
+
|
76
|
+
# put { "Fn::GetAtt": ... } on a single line
|
77
|
+
formatted.gsub! /\{\s+\"Fn::GetAtt\"\: \[\s+(?<key>#{STR}),\s+(?<val>#{STR})\s+\]\s+\}/m,
|
78
|
+
'{ "Fn::GetAtt": [ \\k<key>, \\k<val> ] }'
|
79
|
+
|
80
|
+
formatted + "\n"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/stacker/version.rb
CHANGED
metadata
CHANGED
@@ -1,118 +1,199 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stacker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- Cotap, Inc.
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
default_executable:
|
11
|
+
date: 2014-11-25 00:00:00.000000000 Z
|
14
12
|
dependencies:
|
15
13
|
- !ruby/object:Gem::Dependency
|
16
|
-
name:
|
17
|
-
requirement:
|
18
|
-
none: false
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
19
16
|
requirements:
|
20
|
-
- -
|
17
|
+
- - ~>
|
21
18
|
- !ruby/object:Gem::Version
|
22
|
-
version: '0'
|
23
|
-
type: :
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
24
21
|
prerelease: false
|
25
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: coderay
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: diffy
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
26
69
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
28
|
-
requirement:
|
29
|
-
none: false
|
70
|
+
name: indentation
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
30
72
|
requirements:
|
31
|
-
- -
|
73
|
+
- - ~>
|
32
74
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
75
|
+
version: '0.0'
|
34
76
|
type: :runtime
|
35
77
|
prerelease: false
|
36
|
-
version_requirements:
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.0'
|
37
83
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
-
requirement:
|
40
|
-
none: false
|
84
|
+
name: memoist
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
41
86
|
requirements:
|
42
|
-
- -
|
87
|
+
- - ~>
|
43
88
|
- !ruby/object:Gem::Version
|
44
|
-
version: '0'
|
89
|
+
version: '0.9'
|
45
90
|
type: :runtime
|
46
91
|
prerelease: false
|
47
|
-
version_requirements:
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.9'
|
48
97
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
50
|
-
requirement:
|
51
|
-
none: false
|
98
|
+
name: rainbow
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
52
100
|
requirements:
|
53
|
-
- -
|
101
|
+
- - ~>
|
54
102
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
103
|
+
version: '1.1'
|
56
104
|
type: :runtime
|
57
105
|
prerelease: false
|
58
|
-
version_requirements:
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.1'
|
59
111
|
- !ruby/object:Gem::Dependency
|
60
|
-
name:
|
61
|
-
requirement:
|
62
|
-
none: false
|
112
|
+
name: thor
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
63
114
|
requirements:
|
64
|
-
- -
|
115
|
+
- - ~>
|
65
116
|
- !ruby/object:Gem::Version
|
66
|
-
version: '0'
|
117
|
+
version: '0.18'
|
67
118
|
type: :runtime
|
68
119
|
prerelease: false
|
69
|
-
version_requirements:
|
70
|
-
|
71
|
-
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.18'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - '='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 10.1.0
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 10.1.0
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - '='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.14.1
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - '='
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 2.14.1
|
153
|
+
description: Stacker helps you assemple and manage CloudFormation stacks and dependencies.
|
72
154
|
email:
|
73
|
-
-
|
155
|
+
- martin@cotap.com
|
156
|
+
- evan@cotap.com
|
74
157
|
executables:
|
75
158
|
- stacker
|
76
159
|
extensions: []
|
77
160
|
extra_rdoc_files: []
|
78
161
|
files:
|
79
|
-
- .gitignore
|
80
|
-
- .rvmrc
|
81
|
-
- Gemfile
|
82
|
-
- README.md
|
83
|
-
- Rakefile
|
84
162
|
- bin/stacker
|
85
163
|
- lib/stacker.rb
|
86
|
-
- lib/stacker/aws.rb
|
87
164
|
- lib/stacker/cli.rb
|
88
|
-
- lib/stacker/
|
165
|
+
- lib/stacker/differ.rb
|
166
|
+
- lib/stacker/logging.rb
|
167
|
+
- lib/stacker/region.rb
|
168
|
+
- lib/stacker/resolver.rb
|
89
169
|
- lib/stacker/stack.rb
|
90
|
-
- lib/stacker/
|
170
|
+
- lib/stacker/stack/capabilities.rb
|
171
|
+
- lib/stacker/stack/component.rb
|
172
|
+
- lib/stacker/stack/parameters.rb
|
173
|
+
- lib/stacker/stack/template.rb
|
91
174
|
- lib/stacker/version.rb
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
175
|
+
homepage: https://github.com/cotap/stacker
|
176
|
+
licenses:
|
177
|
+
- MIT
|
178
|
+
metadata: {}
|
96
179
|
post_install_message:
|
97
180
|
rdoc_options: []
|
98
181
|
require_paths:
|
99
182
|
- lib
|
100
183
|
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
-
none: false
|
102
184
|
requirements:
|
103
|
-
- -
|
185
|
+
- - '>='
|
104
186
|
- !ruby/object:Gem::Version
|
105
|
-
version:
|
187
|
+
version: 1.9.3
|
106
188
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
-
none: false
|
108
189
|
requirements:
|
109
|
-
- -
|
190
|
+
- - '>='
|
110
191
|
- !ruby/object:Gem::Version
|
111
192
|
version: '0'
|
112
193
|
requirements: []
|
113
|
-
rubyforge_project:
|
114
|
-
rubygems_version:
|
194
|
+
rubyforge_project:
|
195
|
+
rubygems_version: 2.2.2
|
115
196
|
signing_key:
|
116
|
-
specification_version:
|
117
|
-
summary:
|
197
|
+
specification_version: 4
|
198
|
+
summary: Easily assemble CloudFormation stacks.
|
118
199
|
test_files: []
|
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm use ruby-1.9.2-p180@stacker --create
|
data/Gemfile
DELETED
data/README.md
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
Stacker like to build full application stacks. Creates cloud formation templates from a set of attributes, resource templates and data files.
|
2
|
-
|
3
|
-
Getting started, first get the gem
|
4
|
-
|
5
|
-
gem install stacker
|
6
|
-
|
7
|
-
Then set the following environment variables to your AWS credentials:
|
8
|
-
|
9
|
-
export AWS_SECRET_ACCESS_KEY='my aws secret key'
|
10
|
-
|
11
|
-
export AWS_ACCESS_KEY_ID='my aws access key'
|
12
|
-
|
13
|
-
export STACKER_DEFAULT_KEY='the name of my ssh key in AWS'
|
14
|
-
|
15
|
-
Create your first stack from our repository
|
16
|
-
|
17
|
-
stacker create -g git@github.com:brettweavnet/stacker-repo.git -n my-new-app
|
18
|
-
|
19
|
-
You can then see the status on the stack build
|
20
|
-
|
21
|
-
stacker describe -n my_new_app
|
data/Rakefile
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
data/lib/stacker/aws.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'fog'
|
2
|
-
|
3
|
-
module Stacker
|
4
|
-
module AWS
|
5
|
-
module CloudFormation
|
6
|
-
class Stack
|
7
|
-
def initialize
|
8
|
-
@connect = Stack.connect
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.list
|
12
|
-
Stack.connect.describe_stacks
|
13
|
-
end
|
14
|
-
|
15
|
-
def create(args)
|
16
|
-
@connect.create_stack(args[:name], 'TemplateBody' => args[:body] )
|
17
|
-
end
|
18
|
-
|
19
|
-
def delete(args)
|
20
|
-
@connect.delete_stack(args[:name])
|
21
|
-
end
|
22
|
-
|
23
|
-
def describe(args)
|
24
|
-
@connect.describe_stacks('StackName' => args[:name])
|
25
|
-
end
|
26
|
-
|
27
|
-
def events(args)
|
28
|
-
@connect.describe_stack_events(args[:name])
|
29
|
-
end
|
30
|
-
|
31
|
-
def resources(args)
|
32
|
-
@connect.describe_stack_resources('StackName' => args[:name])
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def self.connect
|
38
|
-
Fog::AWS::CloudFormation.new(
|
39
|
-
:aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
|
40
|
-
:aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
data/lib/stacker/repo.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require "git"
|
2
|
-
|
3
|
-
module Stacker
|
4
|
-
class Repo
|
5
|
-
|
6
|
-
attr_accessor :git_repo, :temp_repo_export_dir
|
7
|
-
|
8
|
-
def initialize(args)
|
9
|
-
self.git_repo = args[:git_repo]
|
10
|
-
self.temp_repo_export_dir = "/tmp/repo-#{(0...8).map{65.+(rand(25)).chr}.join}"
|
11
|
-
end
|
12
|
-
|
13
|
-
def export
|
14
|
-
Dir.mkdir(temp_repo_export_dir, 0700)
|
15
|
-
Git.export(git_repo, temp_repo_export_dir)
|
16
|
-
end
|
17
|
-
|
18
|
-
def remove
|
19
|
-
FileUtils.remove_dir(temp_repo_export_dir)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/stacker/template.rb
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
require "erb"
|
2
|
-
require "json"
|
3
|
-
|
4
|
-
module Stacker
|
5
|
-
|
6
|
-
class Template
|
7
|
-
|
8
|
-
attr_accessor :attributes_file, :git_repo, :output_file,
|
9
|
-
:repo_path, :resources_file
|
10
|
-
|
11
|
-
def initialize(args)
|
12
|
-
self.attributes_file = args[:attributes]
|
13
|
-
self.git_repo = args[:git_repo]
|
14
|
-
self.output_file = args[:output]
|
15
|
-
self.repo_path = args[:path]
|
16
|
-
self.resources_file = args[:resources]
|
17
|
-
|
18
|
-
@repo = Repo.new(:git_repo => git_repo)
|
19
|
-
end
|
20
|
-
|
21
|
-
def build
|
22
|
-
begin
|
23
|
-
export_git_repo if git_repo
|
24
|
-
load_attributes
|
25
|
-
load_data
|
26
|
-
load_resources
|
27
|
-
create_template
|
28
|
-
ensure
|
29
|
-
remove_git_repo if git_repo
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def load_attributes
|
36
|
-
template = IO.read("#{repo_path}/attributes/#{attributes_file}")
|
37
|
-
attributes_json = ERB.new(template).result(binding).to_s
|
38
|
-
parsed_attributes = JSON.parse(attributes_json)
|
39
|
-
@data = parsed_attributes['data']
|
40
|
-
@resources = parsed_attributes['resources']
|
41
|
-
end
|
42
|
-
|
43
|
-
def load_data
|
44
|
-
data_dir = "#{repo_path}/data"
|
45
|
-
|
46
|
-
Dir.entries(data_dir).each do |data_file|
|
47
|
-
if data_file =~ /.erb$/
|
48
|
-
template = IO.read("#{data_dir}/#{data_file}")
|
49
|
-
data = ERB.new(template).result(binding).to_s
|
50
|
-
instance_variable_set("@#{data_file.gsub(/.erb$/, '')}", data)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def load_resources
|
56
|
-
resource_template = IO.read("#{repo_path}/resources/#{resources_file}")
|
57
|
-
ERB.new(resource_template).result(binding)
|
58
|
-
end
|
59
|
-
|
60
|
-
def create_template
|
61
|
-
output_file_handle = File.new(output_file,'w')
|
62
|
-
output_file_handle.puts load_resources
|
63
|
-
output_file_handle.close
|
64
|
-
end
|
65
|
-
|
66
|
-
def export_git_repo
|
67
|
-
@repo.export
|
68
|
-
self.repo_path = @repo.temp_repo_export_dir
|
69
|
-
end
|
70
|
-
|
71
|
-
def remove_git_repo
|
72
|
-
@repo.remove
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
data/stacker.gemspec
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "stacker/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |s|
|
6
|
-
s.name = "stacker"
|
7
|
-
s.version = Stacker::VERSION
|
8
|
-
s.authors = ["Brett Weaver"]
|
9
|
-
s.email = ["brett@weaver.io"]
|
10
|
-
s.homepage = "http://brettweavnet.github.com/stacker"
|
11
|
-
s.summary = %q{stacker helps build cloud stacks}
|
12
|
-
s.description = %q{stacker integrates with git and AWS cloud formation to rapidley build full application stacks from dynamic templates}
|
13
|
-
|
14
|
-
s.rubyforge_project = "stacker"
|
15
|
-
|
16
|
-
s.files = `git ls-files`.split("\n")
|
17
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
s.require_paths = ["lib"]
|
20
|
-
|
21
|
-
s.add_development_dependency "rspec"
|
22
|
-
s.add_runtime_dependency "fog"
|
23
|
-
s.add_runtime_dependency "git"
|
24
|
-
s.add_runtime_dependency "json"
|
25
|
-
s.add_runtime_dependency "trollop"
|
26
|
-
end
|