terradactyl 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ require 'bundler'
2
+ require 'open3'
3
+ require 'uri'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ RSpec::Core::RakeTask.new(:doc) do |t|
10
+ t.rspec_opts = "--format doc"
11
+ end
12
+
13
+ task :default => :spec
14
+
15
+ BUILD_DIR = 'pkg'
16
+
17
+ def bundler
18
+ @bundler ||= Bundler::GemHelper.new
19
+ end
20
+
21
+ def execute(cmd)
22
+ Open3.popen2e(ENV, cmd) do |stdin, stdout_err, wait_thru|
23
+ puts $_ while stdout_err.gets
24
+ wait_thru.value.exitstatus
25
+ end
26
+ end
27
+
28
+ def name
29
+ bundler.gemspec.name
30
+ end
31
+
32
+ def version
33
+ bundler.gemspec.version
34
+ end
35
+
36
+ def allowed_push_host
37
+ bundler.gemspec.metadata['allowed_push_host'] || String.new
38
+ end
39
+
40
+ def gem_server
41
+ URI.parse(allowed_push_host).host
42
+ end
43
+
44
+ def resultant_gem
45
+ "#{BUILD_DIR}/#{name}-#{version}.gem"
46
+ end
47
+
48
+ desc "Lint gem"
49
+ task :lint do
50
+ exit(execute('bundle exec rubocop lib'))
51
+ end
52
+
53
+ desc "Build gem"
54
+ task :build do
55
+ bundler.build_gem
56
+ end
57
+
58
+ desc "Clean all builds"
59
+ task :clean do
60
+ FileUtils.rm_rf BUILD_DIR if File.exist? BUILD_DIR
61
+ end
@@ -0,0 +1 @@
1
+ resource "null_resource" "foo" {}
@@ -0,0 +1,3 @@
1
+ terradactyl:
2
+ terraform:
3
+ version: '0.11.14' # an explicit version
@@ -0,0 +1 @@
1
+ resource "null_resource" "bar" {}
@@ -0,0 +1,3 @@
1
+ terradactyl:
2
+ terraform:
3
+ version: 0.12.29
@@ -0,0 +1 @@
1
+ resource "null_resource" "baz" {}
@@ -0,0 +1,3 @@
1
+ terradactyl:
2
+ terraform:
3
+ version: '~> 0.13.5' # >= 0.13.5 && < 0.14.0
@@ -0,0 +1,3 @@
1
+ terradactyl:
2
+ terraform:
3
+ version: 0.12.29
@@ -0,0 +1 @@
1
+ resource "null_resource" "demo" {}
@@ -0,0 +1 @@
1
+ terradactyl:
data/exe/td ADDED
@@ -0,0 +1 @@
1
+ exe/terradactyl
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'terradactyl'
4
+
5
+ trap 'SIGINT' do
6
+ puts
7
+ puts 'Caught Interrupt. Exiting ...'
8
+ exit 1
9
+ end
10
+
11
+ Terradactyl::CLI.start
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'rake'
5
+ require 'open3'
6
+ require 'yaml'
7
+ require 'json'
8
+ require 'ostruct'
9
+ require 'digest'
10
+ require 'singleton'
11
+ require 'colorize'
12
+ require 'deepsort'
13
+ require 'deep_merge'
14
+ require 'terradactyl/terraform'
15
+
16
+ require_relative 'terradactyl/version'
17
+ require_relative 'terradactyl/config'
18
+ require_relative 'terradactyl/commands'
19
+ require_relative 'terradactyl/common'
20
+ require_relative 'terradactyl/stack'
21
+ require_relative 'terradactyl/stacks'
22
+ require_relative 'terradactyl/filters'
23
+ require_relative 'terradactyl/cli'
@@ -0,0 +1,335 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Terradactyl
4
+ # rubocop:disable Metrics/ClassLength
5
+ class CLI < Thor
6
+ include Common
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ def initialize(*args)
12
+ # Hook ensures abort on stack errors
13
+ at_exit { abort if Stacks.error? }
14
+ super
15
+ end
16
+
17
+ no_commands do
18
+ # Monkey-patch Thor internal method to break out of nested calls
19
+ def invoke_command(command, *args)
20
+ catch(:error) { super }
21
+ end
22
+
23
+ def validate_smartplan(stacks)
24
+ if stacks.empty?
25
+ print_message 'No Stacks Modified ...'
26
+ print_line 'Did you forget to `git add` your selected changes?'
27
+ end
28
+ stacks
29
+ end
30
+
31
+ def validate_planpr(stacks)
32
+ if stacks.empty?
33
+ print_message 'No Stacks Modified ...'
34
+ print_line 'Skipping plan ...'
35
+ end
36
+ stacks
37
+ end
38
+
39
+ def generate_report(report)
40
+ data_file = "#{config.base_folder}.audit.json"
41
+ print_warning "Writing Report: #{data_file} ..."
42
+ report[:error] = Stacks.error.map { |s| "#{config.base_folder}/#{s.name}" }.sort
43
+ File.write data_file, JSON.pretty_generate(report)
44
+ print_ok 'Done!'
45
+ end
46
+ end
47
+
48
+ #################################################################
49
+ # GENERIC TASKS
50
+ # * These tasks are used regularly against stacks, by name.
51
+ #################################################################
52
+
53
+ desc 'defaults', 'Print the compiled configuration'
54
+ def defaults
55
+ puts config.to_h.to_yaml
56
+ end
57
+
58
+ desc 'stacks', 'List the stacks'
59
+ def stacks
60
+ print_ok 'Stacks:'
61
+ Stacks.load.each do |name|
62
+ print_dot name.to_s
63
+ end
64
+ end
65
+
66
+ desc 'version', 'Print version'
67
+ def version
68
+ print_message format('version: %<semver>s', semver: Terradactyl::VERSION)
69
+ end
70
+
71
+ #################################################################
72
+ # SPECIAL TASKS
73
+ # * These tasks are related to Git state and PR planning ops.
74
+ # * Some are useful only in pipelines. These are hidden.
75
+ #################################################################
76
+
77
+ desc 'planpr', 'Plan stacks against origin/HEAD (used for PRs)', hide: true
78
+ def planpr
79
+ print_header 'SmartPlanning PR ...'
80
+ stacks = Stacks.load(filter: StacksPlanFilterGitDiffOriginBranch.new)
81
+ validate_planpr(stacks).each do |name|
82
+ clean(name)
83
+ init(name)
84
+ plan(name)
85
+ @stack = nil
86
+ end
87
+ end
88
+
89
+ desc 'smartplan', 'Plan any stacks that differ from Git HEAD'
90
+ def smartplan
91
+ print_header 'SmartPlanning Stacks ...'
92
+ stacks = Stacks.load(filter: StacksPlanFilterGitDiffHead.new)
93
+ validate_smartplan(stacks).each do |name|
94
+ clean(name)
95
+ init(name)
96
+ plan(name)
97
+ @stack = nil
98
+ end
99
+ end
100
+
101
+ desc 'smartapply', 'Apply any stacks that contain plan files', hide: true
102
+ def smartapply
103
+ print_header 'SmartApplying Stacks ...'
104
+ stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new)
105
+ print_warning 'No stacks contain plan files ...' unless stacks.any?
106
+ stacks.each do |name|
107
+ apply(name)
108
+ @stack = nil
109
+ end
110
+ print_message "Total Stacks Modified: #{stacks.size}"
111
+ end
112
+
113
+ desc 'smartrefresh', 'Refresh any stacks that contain plan files', hide: true
114
+ def smartrefresh
115
+ print_header 'SmartRefreshing Stacks ...'
116
+ stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new)
117
+ print_warning 'No stacks contain plan files ...' unless stacks.any?
118
+ stacks.each do |name|
119
+ refresh(name)
120
+ @stack = nil
121
+ end
122
+ print_message "Total Stacks Refreshed: #{stacks.size}"
123
+ end
124
+
125
+ #################################################################
126
+ # META-STACK TASKS
127
+ # * These tasks are used regularly against groups of stacks, but
128
+ # the `quickplan` task is an exception to this rule.
129
+ #################################################################
130
+
131
+ desc 'quickplan NAME', 'Clean, init and plan a stack, by name'
132
+ def quickplan(name)
133
+ print_header "Quick planning #{name} ..."
134
+ clean(name)
135
+ init(name)
136
+ plan(name)
137
+ end
138
+
139
+ desc 'clean-all', 'Clean all stacks'
140
+ def clean_all
141
+ print_header 'Cleaning ALL Stacks ...'
142
+ Stacks.load.each do |name|
143
+ clean(name)
144
+ @stack = nil
145
+ end
146
+ end
147
+
148
+ desc 'plan-all', 'Plan all stacks'
149
+ def plan_all
150
+ print_header 'Planning ALL Stacks ...'
151
+ Stacks.load.each do |name|
152
+ catch(:error) do
153
+ clean(name)
154
+ init(name)
155
+ plan(name)
156
+ end
157
+ @stack = nil
158
+ end
159
+ end
160
+
161
+ desc 'audit-all', 'Audit all stacks'
162
+ options report: :optional
163
+ method_option :report, type: :boolean
164
+ # rubocop:disable Metrics/AbcSize
165
+ def audit_all
166
+ report = { start: Time.now.to_json }
167
+ print_header 'Auditing ALL Stacks ...'
168
+ Stacks.load.each do |name|
169
+ catch(:error) do
170
+ clean(name)
171
+ init(name)
172
+ audit(name)
173
+ end
174
+ @stack = nil
175
+ end
176
+ report[:finish] = Time.now.to_json
177
+ if options[:report]
178
+ print_header 'Audit Report ...'
179
+ generate_report(report)
180
+ end
181
+ end
182
+ # rubocop:enable Metrics/AbcSize
183
+
184
+ desc 'validate-all', 'Validate all stacks'
185
+ def validate_all
186
+ print_header 'Validating ALL Stacks ...'
187
+ Stacks.load.each do |name|
188
+ catch(:error) do
189
+ clean(name)
190
+ init(name)
191
+ validate(name)
192
+ end
193
+ @stack = nil
194
+ end
195
+ end
196
+
197
+ #################################################################
198
+ # TARGETED STACK TASKS
199
+ # * These tasks are used regularly against stacks, by name.
200
+ #################################################################
201
+
202
+ desc 'lint NAME', 'Lint an individual stack, by name'
203
+ def lint(name)
204
+ @stack ||= Stack.new(name)
205
+ print_ok "Linting: #{@stack.name}"
206
+ if @stack.lint.zero?
207
+ print_ok "Formatting OK: #{@stack.name}"
208
+ else
209
+ Stacks.error!(@stack)
210
+ print_warning "Bad Formatting: #{@stack.name}"
211
+ end
212
+ end
213
+
214
+ desc 'fmt NAME', 'Format an individual stack, by name'
215
+ def fmt(name)
216
+ @stack ||= Stack.new(name)
217
+ print_warning "Formatting: #{@stack.name}"
218
+ if @stack.fmt.zero?
219
+ print_ok "Formatted: #{@stack.name}"
220
+ else
221
+ Stacks.error!(@stack)
222
+ print_crit "Formatting failed: #{@stack.name}"
223
+ end
224
+ end
225
+
226
+ desc 'init NAME', 'Init an individual stack, by name'
227
+ def init(name)
228
+ @stack ||= Stack.new(name)
229
+ print_ok "Initializing: #{@stack.name}"
230
+ if @stack.init.zero?
231
+ print_ok "Initialized: #{@stack.name}"
232
+ else
233
+ Stacks.error!(@stack)
234
+ print_crit "Initialization failed: #{@stack.name}"
235
+ throw :error
236
+ end
237
+ end
238
+
239
+ desc 'plan NAME', 'Plan an individual stack, by name'
240
+ # rubocop:disable Metrics/AbcSize
241
+ def plan(name)
242
+ @stack ||= Stack.new(name)
243
+ print_ok "Planning: #{@stack.name}"
244
+ case @stack.plan
245
+ when 0
246
+ print_ok "No changes: #{@stack.name}"
247
+ when 1
248
+ Stacks.error!(@stack)
249
+ print_crit "Plan failed: #{@stack.name}"
250
+ @stack.print_plan
251
+ throw :error
252
+ when 2
253
+ Stacks.dirty!(@stack)
254
+ print_warning "Changes detected: #{@stack.name}"
255
+ @stack.print_plan
256
+ else
257
+ raise
258
+ end
259
+ end
260
+ # rubocop:enable Metrics/AbcSize
261
+
262
+ desc 'audit NAME', 'Audit an individual stack, by name'
263
+ def audit(name)
264
+ plan(name)
265
+ if (@stack = Stacks.dirty?(name))
266
+ Stacks.error!(@stack)
267
+ print_crit "Dirty stack: #{@stack.name}"
268
+ end
269
+ end
270
+
271
+ desc 'validate NAME', 'Validate an individual stack, by name'
272
+ def validate(name)
273
+ @stack ||= Stack.new(name)
274
+ print_ok "Validating: #{@stack.name}"
275
+ if @stack.validate.zero?
276
+ print_ok "Validated: #{@stack.name}"
277
+ else
278
+ Stacks.error!(@stack)
279
+ print_crit "Validation failed: #{@stack.name}"
280
+ throw :error
281
+ end
282
+ end
283
+
284
+ desc 'clean NAME', 'Clean an individual stack, by name'
285
+ def clean(name)
286
+ @stack ||= Stack.new(name)
287
+ print_warning "Cleaning: #{@stack.name}"
288
+ @stack.clean
289
+ print_ok "Cleaned: #{@stack.name}"
290
+ end
291
+
292
+ #################################################################
293
+ # HIDDEN TARGETED STACK TASKS
294
+ # * These tasks are destructive in nature and do not require
295
+ # regular use.
296
+ #################################################################
297
+
298
+ desc 'apply NAME', 'Apply an individual stack, by name', hide: true
299
+ def apply(name)
300
+ @stack ||= Stack.new(name)
301
+ print_warning "Applying: #{@stack.name}"
302
+ if @stack.apply.zero?
303
+ print_ok "Applied: #{@stack.name}"
304
+ else
305
+ Stacks.error!(@stack)
306
+ print_crit "Failed to apply changes: #{@stack.name}"
307
+ end
308
+ end
309
+
310
+ desc 'refresh NAME', 'Refresh state on an individual stack, by name', hide: true
311
+ def refresh(name)
312
+ @stack ||= Stack.new(name)
313
+ print_crit "Refreshing: #{@stack.name}"
314
+ if @stack.refresh.zero?
315
+ print_warning "Refreshed: #{@stack.name}"
316
+ else
317
+ Stacks.error!(@stack)
318
+ print_crit "Failed to refresh stack: #{@stack.name}"
319
+ end
320
+ end
321
+
322
+ desc 'destroy NAME', 'Destroy an individual stack, by name', hide: true
323
+ def destroy(name)
324
+ @stack ||= Stack.new(name)
325
+ print_crit "Destroying: #{@stack.name}"
326
+ if @stack.destroy.zero?
327
+ print_warning "Destroyed: #{@stack.name}"
328
+ else
329
+ Stacks.error!(@stack)
330
+ print_crit "Failed to apply changes: #{@stack.name}"
331
+ end
332
+ end
333
+ end
334
+ # rubocop:enable Metrics/ClassLength
335
+ end