stacker 0.0.2 → 0.1.0
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.
- 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
|