stacker-yaml 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 +9 -0
- data/lib/stacker.rb +4 -0
- data/lib/stacker/cli.rb +271 -0
- data/lib/stacker/differ.rb +31 -0
- data/lib/stacker/logging.rb +62 -0
- data/lib/stacker/region.rb +39 -0
- data/lib/stacker/resolvers/file_resolver.rb +15 -0
- data/lib/stacker/resolvers/resolver.rb +20 -0
- data/lib/stacker/resolvers/stack_output_resolver.rb +17 -0
- data/lib/stacker/stack.rb +275 -0
- data/lib/stacker/stack/capabilities.rb +19 -0
- data/lib/stacker/stack/component.rb +22 -0
- data/lib/stacker/stack/errors.rb +87 -0
- data/lib/stacker/stack/parameter.rb +102 -0
- data/lib/stacker/stack/parameters.rb +76 -0
- data/lib/stacker/stack/template.rb +126 -0
- data/lib/stacker/version.rb +3 -0
- metadata +273 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c0b70e11acb1afd956b95f53339a7c4a45e3a940
|
4
|
+
data.tar.gz: bef0a18196e611732827b74fa1b562b78cc8ab49
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fda992447c8032d5561281b5f1ca56ba077f440dbda399de53e01126c28831d2536071cb4b521cc7071d25491b3f3ee59407cd9c961be870289fe8288c7a40f8
|
7
|
+
data.tar.gz: b0466bc502fa74c1d4dd63c0bbaec0097e32a9874b0d1b9bf95dab12a70e63aecac260d3a5b4392a9816e9df2dd5e1f531b4bacabc4c76ea79b8e307361a40d2
|
data/bin/stacker
ADDED
data/lib/stacker.rb
ADDED
data/lib/stacker/cli.rb
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'active_support/core_ext/object/try'
|
2
|
+
require 'benchmark'
|
3
|
+
require 'stacker'
|
4
|
+
require 'thor'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Stacker
|
8
|
+
class Cli < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
default_path = ENV['STACKER_PATH'] || '.'
|
12
|
+
default_region = ENV['STACKER_REGION'] || 'us-east-1'
|
13
|
+
default_env = ENV['STACKER_ENVIRONMENT'] || 'development'
|
14
|
+
|
15
|
+
method_option :path, type: :string, default: default_path,
|
16
|
+
banner: 'project path'
|
17
|
+
|
18
|
+
method_option :region, type: :string, default: default_region,
|
19
|
+
banner: 'AWS region name'
|
20
|
+
|
21
|
+
method_option :environment, type: :string, default: default_env,
|
22
|
+
banner: 'Environment name (e.g. production, staging)'
|
23
|
+
|
24
|
+
method_option :allow_destructive, type: :boolean, default: false,
|
25
|
+
banner: 'allow destructive updates'
|
26
|
+
|
27
|
+
def initialize(*args); super(*args) end
|
28
|
+
|
29
|
+
desc "init [PATH]", "Create stacker project directories"
|
30
|
+
def init path = nil
|
31
|
+
init_project path || options['path']
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "list", "List stacks"
|
35
|
+
def list
|
36
|
+
Stacker.logger.inspect region.stacks.map(&:name)
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "show STACK_NAME", "Show details of a stack"
|
40
|
+
def show stack_name
|
41
|
+
with_one_or_all stack_name do |stack|
|
42
|
+
Stacker.logger.inspect(
|
43
|
+
'Description' => stack.description,
|
44
|
+
'Status' => stack.status,
|
45
|
+
'Updated' => stack.last_updated_time || stack.creation_time,
|
46
|
+
'Capabilities' => stack.capabilities.remote,
|
47
|
+
'Parameters' => stack.parameters.remote,
|
48
|
+
'Outputs' => stack.outputs
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "status [STACK_NAME]", "Show stack status"
|
54
|
+
def status stack_name = nil
|
55
|
+
with_one_or_all(stack_name) do |stack|
|
56
|
+
begin
|
57
|
+
Stacker.logger.debug stack.status.indent
|
58
|
+
rescue Aws::CloudFormation::Errors::ValidationError,
|
59
|
+
Stacker::Stack::Error => err
|
60
|
+
Stacker.logger.error err.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "diff [STACK_NAME]", "Show outstanding stack differences"
|
66
|
+
def diff stack_name = nil
|
67
|
+
with_one_or_all(stack_name) do |stack|
|
68
|
+
begin
|
69
|
+
resolve stack
|
70
|
+
next unless full_diff stack
|
71
|
+
rescue Aws::CloudFormation::Errors::ValidationError,
|
72
|
+
Stacker::Stack::Error => err
|
73
|
+
Stacker.logger.error err.message
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "update [STACK_NAME]", "Create or update stack"
|
79
|
+
def update stack_name = nil
|
80
|
+
with_one_or_all(stack_name) do |stack|
|
81
|
+
begin
|
82
|
+
resolve stack
|
83
|
+
|
84
|
+
if stack.exists?
|
85
|
+
next unless full_diff stack
|
86
|
+
|
87
|
+
if yes? "Update remote template with these changes (y/n)?"
|
88
|
+
time = Benchmark.realtime do
|
89
|
+
stack.update allow_destructive: options['allow_destructive']
|
90
|
+
end
|
91
|
+
Stacker.logger.info formatted_time stack_name, 'updated', time
|
92
|
+
else
|
93
|
+
Stacker.logger.warn 'Update skipped'
|
94
|
+
end
|
95
|
+
else
|
96
|
+
if yes? "#{stack.name} does not exist. Create it (y/n)?"
|
97
|
+
time = Benchmark.realtime do
|
98
|
+
stack.create
|
99
|
+
end
|
100
|
+
Stacker.logger.info formatted_time stack_name, 'created', time
|
101
|
+
else
|
102
|
+
Stacker.logger.warn 'Create skipped'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue Aws::CloudFormation::Errors::ValidationError,
|
106
|
+
Stacker::Stack::Error => err
|
107
|
+
Stacker.logger.error err.message
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
desc "dump [STACK_NAME]", "Download stack template"
|
113
|
+
def dump stack_name = nil
|
114
|
+
with_one_or_all(stack_name) do |stack|
|
115
|
+
if stack.exists?
|
116
|
+
diff = stack.template.diff :down, :color
|
117
|
+
next Stacker.logger.warn 'Stack up-to-date' if diff.length == 0
|
118
|
+
|
119
|
+
Stacker.logger.debug "\n" + diff.indent
|
120
|
+
if yes? "Update local template with these changes (y/n)?"
|
121
|
+
stack.template.dump
|
122
|
+
else
|
123
|
+
Stacker.logger.warn 'Pull skipped'
|
124
|
+
end
|
125
|
+
else
|
126
|
+
Stacker.logger.warn "#{stack.name} does not exist"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def init_project path
|
134
|
+
project_path = File.expand_path path
|
135
|
+
|
136
|
+
%w[ regions templates ].each do |dir|
|
137
|
+
directory_path = File.join project_path, dir
|
138
|
+
unless Dir.exists? directory_path
|
139
|
+
Stacker.logger.debug "Creating directory at #{directory_path}"
|
140
|
+
FileUtils.mkdir_p directory_path
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
region_path = File.join project_path, 'regions', 'us-east-1.yml'
|
145
|
+
unless File.exists? region_path
|
146
|
+
Stacker.logger.debug "Creating region file at #{region_path}"
|
147
|
+
File.open(region_path, 'w+') { |f| f.print <<-YAML }
|
148
|
+
defaults:
|
149
|
+
parameters:
|
150
|
+
CidrBlock: '10.0'
|
151
|
+
stacks:
|
152
|
+
- name: VPC
|
153
|
+
YAML
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def formatted_time stack, action, benchmark
|
158
|
+
"Stack #{stack} #{action} in: #{(benchmark / 60).floor} min and #{(benchmark % 60).round} seconds."
|
159
|
+
end
|
160
|
+
|
161
|
+
def full_diff stack
|
162
|
+
templ_diff = stack.template.diff :color
|
163
|
+
param_diff = stack.parameters.diff :color
|
164
|
+
|
165
|
+
if (templ_diff + param_diff).length == 0
|
166
|
+
Stacker.logger.warn 'Stack up-to-date'
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
|
170
|
+
Stacker.logger.info "\n#{templ_diff.indent}\n" if templ_diff.length > 0
|
171
|
+
Stacker.logger.info "\n#{param_diff.indent}\n" if param_diff.length > 0
|
172
|
+
|
173
|
+
Stacker.logger.info stack.pretty_change_set
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
def region
|
178
|
+
@region ||= begin
|
179
|
+
config_path = region_config_path
|
180
|
+
if File.exists? config_path
|
181
|
+
begin
|
182
|
+
config = YAML.load_file(config_path)
|
183
|
+
rescue Psych::SyntaxError => err
|
184
|
+
Stacker.logger.fatal err.message
|
185
|
+
exit 1
|
186
|
+
end
|
187
|
+
|
188
|
+
defaults = config.fetch 'defaults', {}
|
189
|
+
stacks = config.fetch 'stacks', {}
|
190
|
+
region_options = {
|
191
|
+
stack_prefix: environment_config.fetch('prefix', '')
|
192
|
+
}
|
193
|
+
|
194
|
+
Region.new options['region'], defaults, stacks, templates_path,
|
195
|
+
region_options
|
196
|
+
else
|
197
|
+
Stacker.logger.fatal "#{options['region']}.yml does not exist. Please configure or use stacker init"
|
198
|
+
exit 1
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def region_config_path
|
204
|
+
region_path = if environments?
|
205
|
+
File.join working_path, 'environments', options['environment']
|
206
|
+
else
|
207
|
+
File.join working_path, 'regions'
|
208
|
+
end
|
209
|
+
File.join region_path, "#{options['region']}.yml"
|
210
|
+
end
|
211
|
+
|
212
|
+
def environments_path
|
213
|
+
File.join working_path, 'environments'
|
214
|
+
end
|
215
|
+
|
216
|
+
def environments?
|
217
|
+
File.exists? environments_path
|
218
|
+
end
|
219
|
+
|
220
|
+
def environment_config
|
221
|
+
config_path = File.join environments_path, 'config.yml'
|
222
|
+
return {} unless File.exists? config_path
|
223
|
+
YAML.load_file(config_path).fetch('environments', {}).fetch(
|
224
|
+
options['environment'],
|
225
|
+
{}
|
226
|
+
)
|
227
|
+
end
|
228
|
+
|
229
|
+
def resolve stack
|
230
|
+
return {} if stack.parameters.dependencies.none?
|
231
|
+
|
232
|
+
Stacker.logger.debug 'Resolving dependencies...'
|
233
|
+
stack.parameters.resolved
|
234
|
+
end
|
235
|
+
|
236
|
+
def with_one_or_all stack_name = nil, &block
|
237
|
+
yield_with_stack = proc do |stack|
|
238
|
+
Stacker.logger.info "#{stack.name}:"
|
239
|
+
yield stack
|
240
|
+
Stacker.logger.info ''
|
241
|
+
end
|
242
|
+
|
243
|
+
if stack_name
|
244
|
+
yield_with_stack.call region.stack(stack_name)
|
245
|
+
else
|
246
|
+
region.stacks.each(&yield_with_stack)
|
247
|
+
end
|
248
|
+
|
249
|
+
rescue Stacker::Stack::StackPolicyError => err
|
250
|
+
if options['allow_destructive']
|
251
|
+
Stacker.logger.fatal err.message
|
252
|
+
else
|
253
|
+
Stacker.logger.fatal 'Stack update policy prevents replacing or destroying resources.'
|
254
|
+
Stacker.logger.warn 'Try running again with \'--allow-destructive\''
|
255
|
+
end
|
256
|
+
exit 1
|
257
|
+
rescue Stacker::Stack::Error => err
|
258
|
+
Stacker.logger.fatal err.message
|
259
|
+
exit 1
|
260
|
+
end
|
261
|
+
|
262
|
+
def templates_path
|
263
|
+
File.join working_path, 'templates'
|
264
|
+
end
|
265
|
+
|
266
|
+
def working_path
|
267
|
+
File.expand_path options['path']
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
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,62 @@
|
|
1
|
+
require 'coderay'
|
2
|
+
require 'delegate'
|
3
|
+
require 'indentation'
|
4
|
+
require 'logger'
|
5
|
+
require 'rainbow'
|
6
|
+
|
7
|
+
module Stacker
|
8
|
+
module_function
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
class PrettyLogger < SimpleDelegator
|
13
|
+
def initialize logger
|
14
|
+
super
|
15
|
+
|
16
|
+
old_formatter = logger.formatter
|
17
|
+
|
18
|
+
logger.formatter = proc do |level, time, prog, msg|
|
19
|
+
unless msg.start_with?("\e")
|
20
|
+
color = case level
|
21
|
+
when 'FATAL' then :red
|
22
|
+
when 'WARN' then :yellow
|
23
|
+
when 'INFO' then :blue
|
24
|
+
when 'DEBUG' then '333333'
|
25
|
+
else :default
|
26
|
+
end
|
27
|
+
msg = msg.color(color)
|
28
|
+
end
|
29
|
+
|
30
|
+
old_formatter.call level, time, prog, msg
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
%w[ debug info warn fatal ].each do |level|
|
35
|
+
define_method level do |msg, opts = {}|
|
36
|
+
if opts.include? :highlight
|
37
|
+
msg = CodeRay.scan(msg, opts[:highlight]).terminal
|
38
|
+
end
|
39
|
+
__getobj__.__send__ level, msg
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect object
|
44
|
+
info object.to_yaml[4..-1].strip.indent, highlight: :yaml
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger= logger
|
49
|
+
@logger = PrettyLogger.new logger
|
50
|
+
end
|
51
|
+
|
52
|
+
def logger
|
53
|
+
@logger ||= begin
|
54
|
+
logger = Logger.new STDOUT
|
55
|
+
logger.level = Logger::DEBUG
|
56
|
+
logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
|
57
|
+
PrettyLogger.new logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'stacker/stack'
|
3
|
+
require 'stacker/stack/errors'
|
4
|
+
|
5
|
+
module Stacker
|
6
|
+
class Region
|
7
|
+
|
8
|
+
attr_reader :name, :defaults, :stacks, :templates_path, :options
|
9
|
+
|
10
|
+
def initialize(name, defaults, stacks, templates_path, options={})
|
11
|
+
@name = name
|
12
|
+
@defaults = defaults
|
13
|
+
stack_prefix = options.fetch(:stack_prefix, '')
|
14
|
+
@stacks = stacks.map do |options|
|
15
|
+
begin
|
16
|
+
options['template_name'] ||= options['name']
|
17
|
+
options['name'] = stack_prefix + options['name']
|
18
|
+
Stack.new self, options.fetch('name'), options
|
19
|
+
rescue KeyError => err
|
20
|
+
Stacker.logger.fatal "Malformed YAML: #{err.message}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
@templates_path = templates_path
|
25
|
+
@options = options
|
26
|
+
end
|
27
|
+
|
28
|
+
def client
|
29
|
+
@client ||= Aws::CloudFormation::Client.new region: name
|
30
|
+
end
|
31
|
+
|
32
|
+
def stack name
|
33
|
+
stacks.find { |s| s.name == name }.tap do |stk|
|
34
|
+
raise Stack::StackUndeclared.new name unless stk
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|