terradactyl 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 790c643382c73bae3dc70e56e4f6cfe872454fb9908638b2c8c6543787971c0c
4
- data.tar.gz: 636d761cf9091f7f7e8aa34c680aa30a4ec9a9e7f87e07b46b955ebcd706afc8
3
+ metadata.gz: 572d97af0265aeffbfc6144ffa01767365af13a5c927c2faeb075ab1a9c40abb
4
+ data.tar.gz: 9551f11afa4b79a621457e7a3b14891667deb90012164590141a958b345ef730
5
5
  SHA512:
6
- metadata.gz: 25b3da672dfed59180ded6fd45cab05acaeb4abd1bed8493f5bb61febebde091db6259e40584ef0672c87ecd885cb664292379b07ca9c1edc2e85e9a18873ce0
7
- data.tar.gz: 1b4901be7074aaa8240a1c6b94a326bb1914683ff4f915b7c2b4e0288f4c34cff95c3038b69535324fa44978b37ef03d52f4f74d63d994ff45dabcea3a0998e4
6
+ metadata.gz: 5a915631b4d0abaab3a850192e9e2201eccbe78d8abb87906458077f0565d14d86ad4b7af234e73ae0ec409a9f89051013c2d37879084ce11dac3abf585f0aba
7
+ data.tar.gz: fae2314dcc7db1189d60216d1ff3590570cc18714cf023442de44e3746e255d32f4c3d6f69cfc1b74302136c1588d9b9eace300842da141f7544296d01271b9a
@@ -23,4 +23,4 @@ jobs:
23
23
  bundler-cache: true
24
24
  - name: Run tests
25
25
  id: test
26
- run: bundle exec rake spec
26
+ run: bundle exec rake doc
@@ -26,4 +26,4 @@ jobs:
26
26
  run: bundle exec rake lint
27
27
  - name: Run tests
28
28
  id: test
29
- run: bundle exec rake spec
29
+ run: bundle exec rake doc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.4.0 (2022-09-21)
4
+
5
+ NEW FEATURES:
6
+
7
+ * add support for multiple stack subdirectories
8
+ * add optional BASE_FOLDER input for most Terradactyl commands
9
+
10
+ BUG FIXES:
11
+
12
+ * fix StacksApplyFilterPrePlanned filtering behaviour
13
+
3
14
  ## 1.3.0 (2022-09-21)
4
15
 
5
16
  NEW FEATURES:
data/README.md CHANGED
@@ -19,7 +19,7 @@ Terradactyl simplifies managing large heterogeneous Terraform monorepos by intro
19
19
 
20
20
  Requires Ruby 2.5 or greater.
21
21
 
22
- NOTE: Terraform sub-command operations are only supported between stable versions `>= 0.11.x` to `~> 0.15.x`.
22
+ NOTE: Terraform sub-command operations are only supported between stable versions `>= 0.11.x` to `~> 1.3.x`.
23
23
 
24
24
  ## Installation
25
25
 
@@ -43,8 +43,8 @@ And then execute:
43
43
 
44
44
  Terradactyl repos rely on two simple organizational conventions:
45
45
 
46
- * a single project-level `terradactly.yaml`
47
- * a single subdirectory for your stacks
46
+ * a single project-level `terradactyl.yaml`
47
+ * subdirectories for your stacks
48
48
 
49
49
  ```sh
50
50
  .
@@ -140,6 +140,20 @@ NOTE: `*.tfstate*` files are not cleaned up by default for obvious reasons, so c
140
140
 
141
141
  See the [Configuration](#configuration) section for more info on how to control which files get removed during a `clean <stack>` or `clean-all` operation.
142
142
 
143
+ #### quickplan a single stack in a non-default subdirectory
144
+
145
+ If you have a setup with multiple stack subdirectories, you have one stack subdirectory that's referenced by default in the root-level terradactyl.yaml.
146
+
147
+ $ cd examples/multi-stack-subdirectories
148
+
149
+ Notice that `preprod-stacks` is the base_folder defined in the root terradactyl.yaml. By default, Terradactyl commands will run in this subdirectory.
150
+
151
+ All subdirectories define their own terradactyl.yaml with customized `base_folder` and `misc.base_folder_name` settings. To run commands on stacks in non-default subdirectories from root, you must pass in the optional BASE_FOLDER input to your Terradactyl command.
152
+
153
+ $ terradactyl quickplan stack1 # this command will run quickplan on preprod-stacks/stack1
154
+ $ terradactyl quickplan stack1 test-stacks # this command will run quickplan on test-stacks/stack1
155
+
156
+
143
157
  ## Operation
144
158
 
145
159
  NOTE: `terradactyl` (symlinked as `td`) ONLY operates in the root of your monorepo. In order to execute any subcommands, your working directory must contain your project-level configuration file, otherwise you will receive this:
@@ -176,7 +190,7 @@ Terradactyl add some unique utility commands that permit you to more readily man
176
190
  Installs supporting components, namely Terraform itself...
177
191
 
178
192
  # Install the latest terraform binary
179
- terradactly install terraform
193
+ terradactyl install terraform
180
194
 
181
195
  # Install pessimistic version
182
196
  terradactyl install terraform --version="~> 0.13.0"
@@ -201,14 +215,14 @@ Terradactyl provides a few useful meta-commands that can help you avoid repetiti
201
215
 
202
216
  Clean, initialize and plan a single stack in one operation.
203
217
 
204
- terradactly quickplan <stack>
218
+ terradactyl quickplan <stack>
205
219
 
206
220
  #### smartapply/smartrefresh
207
221
 
208
222
  Apply or Refresh _ANY_ stack containing a plan file.
209
223
 
210
- terradactly smartapply <stack>
211
- terradactly smartrefresh <stack>
224
+ terradactyl smartapply <stack>
225
+ terradactyl smartrefresh <stack>
212
226
 
213
227
  ### Getting Help
214
228
 
@@ -225,6 +239,7 @@ For help on any individual sub-command do:
225
239
  As previously mentioned, configuration is hierarchical. This means you may specify:
226
240
 
227
241
  * one project-level configuration for ALL stacks
242
+ * an overriding subdirectory configuration for all stacks within the subdirectory
228
243
  * an overriding stack-level configuration for each independent stack
229
244
 
230
245
  See [examples](examples) for different setups.
@@ -265,6 +280,7 @@ terradactyl: <Object, Terradactyl config>
265
280
  environment: <Object, shell environment variables>
266
281
  TF_PLUGIN_CACHE_DIR: <String, path to common Terraform plugin directory, default=$HOME/.terraform.d/plugins>
267
282
  misc: <Object, misc Terradactyl settings>
283
+ base_folder_name: <String, name of the stack folder. Required for multiple stack subdirectories, default=nil>
268
284
  utf8: <Bool, use utf8 in stdout, default=true>
269
285
  disable_color: <Bool, disable color in stdout, default=false>
270
286
  cleanup: <Object, Terradactyl cleanup settings>
@@ -0,0 +1 @@
1
+ resource "null_resource" "baz" {}
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ null = {
4
+ source = "hashicorp/null"
5
+ }
6
+ }
7
+ required_version = "~> 1.0.0"
8
+ }
@@ -0,0 +1 @@
1
+ resource "null_resource" "baz" {}
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ null = {
4
+ source = "hashicorp/null"
5
+ }
6
+ }
7
+ required_version = "~> 1.0.0"
8
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ terradactyl:
3
+ base_folder: .
4
+ terraform:
5
+ version: 1.0.0
6
+ misc:
7
+ base_folder_name: preprod-stacks
@@ -0,0 +1,4 @@
1
+ terradactyl:
2
+ base_folder: test-stacks
3
+ terraform:
4
+ version: 0.15.1
@@ -0,0 +1 @@
1
+ resource "null_resource" "bar" {}
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ null = {
4
+ source = "hashicorp/null"
5
+ }
6
+ }
7
+ required_version = "~> 1.0.0"
8
+ }
@@ -0,0 +1 @@
1
+ resource "null_resource" "foo" {}
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ null = {
4
+ source = "hashicorp/null"
5
+ }
6
+ }
7
+ required_version = "~> 1.0.0"
8
+ }
@@ -0,0 +1,7 @@
1
+ ---
2
+ terradactyl:
3
+ base_folder: .
4
+ terraform:
5
+ version: 0.15.1
6
+ misc:
7
+ base_folder_name: test-stacks
@@ -68,9 +68,15 @@ module Terradactyl
68
68
  Terradactyl::Terraform::VersionManager.latest
69
69
  end
70
70
 
71
- def upgrade_stack(name)
72
- @stack ||= Stack.new(name)
73
- print_warning "Upgrading: #{@stack.name}"
71
+ # get name of base folder for printing logs
72
+ def base_folder_name(base_folder)
73
+ base_folder || config.misc.base_folder_name || config.base_folder
74
+ end
75
+
76
+ # rubocop:disable Metrics/AbcSize
77
+ def upgrade_stack(name, base_override = nil)
78
+ @stack ||= Stack.new(name, base_override)
79
+ print_warning "Upgrading in #{config.base_folder}: #{@stack.name}"
74
80
  if @stack.upgrade.zero?
75
81
  print_ok "Upgraded: #{@stack.name}"
76
82
  else
@@ -95,10 +101,10 @@ module Terradactyl
95
101
  puts config.to_h.to_yaml
96
102
  end
97
103
 
98
- desc 'stacks', 'List the stacks'
99
- def stacks
104
+ desc 'stacks [BASE_FOLDER]', 'List the stacks, with optional base folder override'
105
+ def stacks(base_override = nil)
100
106
  print_ok 'Stacks:'
101
- Stacks.load.each do |name|
107
+ Stacks.load(base_override: base_override).each do |name|
102
108
  print_dot name.to_s
103
109
  end
104
110
  end
@@ -114,49 +120,59 @@ module Terradactyl
114
120
  # * Some are useful only in pipelines. These are hidden.
115
121
  #################################################################
116
122
 
117
- desc 'planpr', 'Plan stacks against origin/HEAD (used for PRs)', hide: true
118
- def planpr
119
- print_header 'SmartPlanning PR ...'
120
- stacks = Stacks.load(filter: StacksPlanFilterGitDiffOriginBranch.new)
123
+ desc 'planpr [BASE_FOLDER]',
124
+ 'Plan stacks against origin/HEAD (used for PRs), with optional base folder override',
125
+ hide: true
126
+ def planpr(base_override = nil)
127
+ print_header "SmartPlanning PR in #{base_folder_name(base_override)} ..."
128
+ stacks = Stacks.load(
129
+ filter: StacksPlanFilterGitDiffOriginBranch.new,
130
+ base_override: base_override
131
+ )
121
132
  validate_planpr(stacks).each do |name|
122
- clean(name)
123
- init(name)
124
- plan(name)
133
+ clean(name, base_override)
134
+ init(name, base_override)
135
+ plan(name, base_override)
125
136
  @stack = nil
126
137
  end
127
138
  end
128
139
 
129
- desc 'smartplan', 'Plan any stacks that differ from Git HEAD'
130
- def smartplan
131
- print_header 'SmartPlanning Stacks ...'
132
- stacks = Stacks.load(filter: StacksPlanFilterGitDiffHead.new)
140
+ desc 'smartplan [BASE_FOLDER]',
141
+ 'Plan any stacks that differ from Git HEAD, with optional base folder override'
142
+ def smartplan(base_override = nil)
143
+ print_header "SmartPlanning Stacks in #{base_folder_name(base_override)} ..."
144
+ stacks = Stacks.load(filter: StacksPlanFilterGitDiffHead.new, base_override: base_override)
133
145
  validate_smartplan(stacks).each do |name|
134
- clean(name)
135
- init(name)
136
- plan(name)
146
+ clean(name, base_override)
147
+ init(name, base_override)
148
+ plan(name, base_override)
137
149
  @stack = nil
138
150
  end
139
151
  end
140
152
 
141
- desc 'smartapply', 'Apply any stacks that contain plan files', hide: true
142
- def smartapply
143
- print_header 'SmartApplying Stacks ...'
144
- stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new)
153
+ desc 'smartapply',
154
+ 'Apply any stacks that contain plan files, with optional base folder override',
155
+ hide: true
156
+ def smartapply(base_override = nil)
157
+ print_header "SmartApplying Stacks in #{base_folder_name(base_override)} ..."
158
+ stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new, base_override: base_override)
145
159
  print_warning 'No stacks contain plan files ...' unless stacks.any?
146
160
  stacks.each do |name|
147
- apply(name)
161
+ apply(name, base_override)
148
162
  @stack = nil
149
163
  end
150
164
  print_message "Total Stacks Modified: #{stacks.size}"
151
165
  end
152
166
 
153
- desc 'smartrefresh', 'Refresh any stacks that contain plan files', hide: true
154
- def smartrefresh
155
- print_header 'SmartRefreshing Stacks ...'
156
- stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new)
167
+ desc 'smartrefresh [BASE_FOLDER]',
168
+ 'Refresh any stacks that contain plan files, with optional base folder override',
169
+ hide: true
170
+ def smartrefresh(base_override = nil)
171
+ print_header "SmartRefreshing Stacks in #{base_folder_name(base_override)} ..."
172
+ stacks = Stacks.load(filter: StacksApplyFilterPrePlanned.new, base_override: base_override)
157
173
  print_warning 'No stacks contain plan files ...' unless stacks.any?
158
174
  stacks.each do |name|
159
- refresh(name)
175
+ refresh(name, base_override)
160
176
  @stack = nil
161
177
  end
162
178
  print_message "Total Stacks Refreshed: #{stacks.size}"
@@ -168,56 +184,57 @@ module Terradactyl
168
184
  # the `quickplan` task is an exception to this rule.
169
185
  #################################################################
170
186
 
171
- desc 'upgrade NAME', 'Cleans, inits, upgrades and formats an individual stack, by name'
172
- def upgrade(name)
173
- clean(name)
174
- init(name, backend: false)
175
- upgrade_stack(name)
176
- fmt(name)
187
+ desc 'upgrade NAME [BASE_FOLDER]',
188
+ 'Cleans, inits, upgrades and fmts a given stack, by name & optional base folder override'
189
+ def upgrade(name, base_override = nil)
190
+ clean(name, base_override)
191
+ init(name, base_override, backend: false)
192
+ upgrade_stack(name, base_override)
193
+ fmt(name, base_override)
177
194
  end
178
195
 
179
- desc 'quickplan NAME', 'Clean, init and plan a stack, by name'
180
- def quickplan(name)
196
+ desc 'quickplan NAME [BASE_FOLDER]',
197
+ 'Clean, init and plan a stack, by name & optional base folder override'
198
+ def quickplan(name, base_override = nil)
181
199
  print_header "Quick planning #{name} ..."
182
- clean(name)
183
- init(name)
184
- plan(name)
200
+ clean(name, base_override)
201
+ init(name, base_override)
202
+ plan(name, base_override)
185
203
  end
186
204
 
187
- desc 'clean-all', 'Clean all stacks'
188
- def clean_all
189
- print_header 'Cleaning ALL Stacks ...'
190
- Stacks.load.each do |name|
191
- clean(name)
205
+ desc 'clean-all [BASE_FOLDER]', 'Clean all stacks, by optional base folder override'
206
+ def clean_all(base_override = nil)
207
+ print_header "Cleaning ALL Stacks in #{base_folder_name(base_override)} ..."
208
+ Stacks.load(base_override: base_override).each do |name|
209
+ clean(name, base_override)
192
210
  @stack = nil
193
211
  end
194
212
  end
195
213
 
196
- desc 'plan-all', 'Plan all stacks'
197
- def plan_all
198
- print_header 'Planning ALL Stacks ...'
199
- Stacks.load.each do |name|
214
+ desc 'plan-all [BASE_FOLDER]', 'Plan all stacks, by optional base folder override'
215
+ def plan_all(base_override = nil)
216
+ print_header "Planning ALL Stacks in #{base_folder_name(base_override)} ..."
217
+ Stacks.load(base_override: base_override).each do |name|
200
218
  catch(:error) do
201
- clean(name)
202
- init(name)
203
- plan(name)
219
+ clean(name, base_override)
220
+ init(name, base_override)
221
+ plan(name, base_override)
204
222
  end
205
223
  @stack = nil
206
224
  end
207
225
  end
208
226
 
209
- desc 'audit-all', 'Audit all stacks'
227
+ desc 'audit-all [BASE_FOLDER]', 'Audit all stacks, by optional base folder override'
210
228
  options report: :optional
211
229
  method_option :report, type: :boolean
212
- # rubocop:disable Metrics/AbcSize
213
- def audit_all
230
+ def audit_all(base_override = nil)
214
231
  report = { start: Time.now.to_json }
215
- print_header 'Auditing ALL Stacks ...'
216
- Stacks.load.each do |name|
232
+ print_header "Auditing ALL Stacks in #{base_folder_name(base_override)} ..."
233
+ Stacks.load(base_override: base_override).each do |name|
217
234
  catch(:error) do
218
- clean(name)
219
- init(name)
220
- audit(name)
235
+ clean(name, base_override)
236
+ init(name, base_override)
237
+ audit(name, base_override)
221
238
  end
222
239
  @stack = nil
223
240
  end
@@ -229,14 +246,14 @@ module Terradactyl
229
246
  end
230
247
  # rubocop:enable Metrics/AbcSize
231
248
 
232
- desc 'validate-all', 'Validate all stacks'
233
- def validate_all
234
- print_header 'Validating ALL Stacks ...'
235
- Stacks.load.each do |name|
249
+ desc 'validate-all [BASE_FOLDER]', 'Validate all stacks, by optional base folder override'
250
+ def validate_all(base_override = nil)
251
+ print_header "Validating ALL Stacks in #{base_folder_name(base_override)} ..."
252
+ Stacks.load(base_override: base_override).each do |name|
236
253
  catch(:error) do
237
- clean(name)
238
- init(name)
239
- validate(name)
254
+ clean(name, base_override)
255
+ init(name, base_override)
256
+ validate(name, base_override)
240
257
  end
241
258
  @stack = nil
242
259
  end
@@ -247,9 +264,10 @@ module Terradactyl
247
264
  # * These tasks are used regularly against stacks, by name.
248
265
  #################################################################
249
266
 
250
- desc 'lint NAME', 'Lint an individual stack, by name'
251
- def lint(name)
252
- @stack ||= Stack.new(name)
267
+ desc 'lint NAME [BASE_FOLDER]',
268
+ 'Lint an individual stack, by name & optional base folder override'
269
+ def lint(name, base_override = nil)
270
+ @stack ||= Stack.new(name, base_override)
253
271
  print_ok "Linting: #{@stack.name}"
254
272
  if @stack.lint.zero?
255
273
  print_ok "Formatting OK: #{@stack.name}"
@@ -259,9 +277,10 @@ module Terradactyl
259
277
  end
260
278
  end
261
279
 
262
- desc 'fmt NAME', 'Format an individual stack, by name'
263
- def fmt(name)
264
- @stack ||= Stack.new(name)
280
+ desc 'fmt NAME [BASE_FOLDER]',
281
+ 'Format an individual stack, by name & optional base folder override'
282
+ def fmt(name, base_override = nil)
283
+ @stack ||= Stack.new(name, base_override)
265
284
  print_warning "Formatting: #{@stack.name}"
266
285
  if @stack.fmt.zero?
267
286
  print_ok "Formatted: #{@stack.name}"
@@ -271,12 +290,14 @@ module Terradactyl
271
290
  end
272
291
  end
273
292
 
274
- desc 'init NAME', 'Init an individual stack, by name'
275
- def init(name, backend: true)
276
- @stack ||= Stack.new(name)
293
+ desc 'init NAME [BASE_FOLDER]',
294
+ 'Init an individual stack, by name & optional base folder override'
295
+ # rubocop:disable Metrics/AbcSize
296
+ def init(name, base_override = nil, backend: true)
297
+ @stack ||= Stack.new(name, base_override)
277
298
  @stack.config.terraform.init.backend = backend
278
299
 
279
- print_ok "Initializing: #{@stack.name}"
300
+ print_ok "Initializing in #{base_folder_name(base_override)}: #{@stack.name}"
280
301
  if @stack.init.zero?
281
302
  print_ok "Initialized: #{@stack.name}"
282
303
  else
@@ -286,11 +307,11 @@ module Terradactyl
286
307
  end
287
308
  end
288
309
 
289
- desc 'plan NAME', 'Plan an individual stack, by name'
290
- # rubocop:disable Metrics/AbcSize
291
- def plan(name)
292
- @stack ||= Stack.new(name)
293
- print_ok "Planning: #{@stack.name}"
310
+ desc 'plan NAME [BASE_FOLDER]',
311
+ 'Plan an individual stack, by name & optional base folder override'
312
+ def plan(name, base_override = nil)
313
+ @stack ||= Stack.new(name, base_override)
314
+ print_ok "Planning in #{base_folder_name(base_override)}: #{@stack.name}"
294
315
  case @stack.plan
295
316
  when 0
296
317
  print_ok "No changes: #{@stack.name}"
@@ -309,19 +330,21 @@ module Terradactyl
309
330
  end
310
331
  # rubocop:enable Metrics/AbcSize
311
332
 
312
- desc 'audit NAME', 'Audit an individual stack, by name'
313
- def audit(name)
314
- plan(name)
333
+ desc 'audit NAME [BASE_FOLDER]',
334
+ 'Audit an individual stack, by name & optional base folder override'
335
+ def audit(name, base_override = nil)
336
+ plan(name, base_override)
315
337
  if (@stack = Stacks.dirty?(name))
316
338
  Stacks.error!(@stack)
317
339
  print_crit "Dirty stack: #{@stack.name}"
318
340
  end
319
341
  end
320
342
 
321
- desc 'validate NAME', 'Validate an individual stack, by name'
322
- def validate(name)
323
- @stack ||= Stack.new(name)
324
- print_ok "Validating: #{@stack.name}"
343
+ desc 'validate NAME [BASE_FOLDER]',
344
+ 'Validate an individual stack, by name & optional base folder override'
345
+ def validate(name, base_override = nil)
346
+ @stack ||= Stack.new(name, base_override)
347
+ print_ok "Validating in #{base_folder_name(base_override)}: #{@stack.name}"
325
348
  if @stack.validate.zero?
326
349
  print_ok "Validated: #{@stack.name}"
327
350
  else
@@ -331,18 +354,20 @@ module Terradactyl
331
354
  end
332
355
  end
333
356
 
334
- desc 'clean NAME', 'Clean an individual stack, by name'
335
- def clean(name)
336
- @stack ||= Stack.new(name)
337
- print_warning "Cleaning: #{@stack.name}"
357
+ desc 'clean NAME [BASE_FOLDER]',
358
+ 'Clean an individual stack, by name & optional base folder override'
359
+ def clean(name, base_override = nil)
360
+ @stack ||= Stack.new(name, base_override)
361
+ print_warning "Cleaning in #{config.base_folder}: #{@stack.name}"
338
362
  @stack.clean
339
363
  print_ok "Cleaned: #{@stack.name}"
340
364
  end
341
365
 
342
- desc 'apply NAME', 'Apply an individual stack, by name'
343
- def apply(name)
344
- @stack ||= Stack.new(name)
345
- print_warning "Applying: #{@stack.name}"
366
+ desc 'apply NAME [BASE_FOLDER]',
367
+ 'Apply an individual stack, by name & optional base folder override'
368
+ def apply(name, base_override = nil)
369
+ @stack ||= Stack.new(name, base_override)
370
+ print_warning "Applying in #{base_folder_name(base_override)}: #{@stack.name}"
346
371
  if @stack.apply.zero?
347
372
  print_ok "Applied: #{@stack.name}"
348
373
  else
@@ -351,10 +376,11 @@ module Terradactyl
351
376
  end
352
377
  end
353
378
 
354
- desc 'refresh NAME', 'Refresh state on an individual stack, by name'
355
- def refresh(name)
356
- @stack ||= Stack.new(name)
357
- print_crit "Refreshing: #{@stack.name}"
379
+ desc 'refresh NAME [BASE_FOLDER]',
380
+ 'Refresh state on an individual stack, by name & optional base folder override'
381
+ def refresh(name, base_override = nil)
382
+ @stack ||= Stack.new(name, base_override)
383
+ print_crit "Refreshing in #{base_folder_name(base_override)}: #{@stack.name}"
358
384
  if @stack.refresh.zero?
359
385
  print_warning "Refreshed: #{@stack.name}"
360
386
  else
@@ -363,10 +389,11 @@ module Terradactyl
363
389
  end
364
390
  end
365
391
 
366
- desc 'destroy NAME', 'Destroy an individual stack, by name'
367
- def destroy(name)
368
- @stack ||= Stack.new(name)
369
- print_crit "Destroying: #{@stack.name}"
392
+ desc 'destroy NAME [BASE_FOLDER]',
393
+ 'Destroy an individual stack, by name & optional base folder override'
394
+ def destroy(name, base_override = nil)
395
+ @stack ||= Stack.new(name, base_override)
396
+ print_crit "Destroying in #{base_folder_name(base_override)}: #{@stack.name}"
370
397
  if @stack.destroy.zero?
371
398
  print_warning "Destroyed: #{@stack.name}"
372
399
  else
@@ -61,15 +61,16 @@ module Terradactyl
61
61
 
62
62
  private
63
63
 
64
- def load_config
64
+ def load_config(defaults_override: nil, overlay_override: nil)
65
65
  @config = [
66
- @defaults,
67
- @overlay
66
+ defaults_override || @defaults,
67
+ overlay_override || @overlay
68
68
  ].inject({}) do |memo, obj|
69
69
  memo.deep_merge!(obj, overwrite_arrays: true)
70
70
  Marshal.load(Marshal.dump(memo))
71
71
  end
72
72
  @terradactyl = structify(@config).terradactyl
73
+
73
74
  configure_colorization
74
75
  @terradactyl
75
76
  end
@@ -120,8 +121,10 @@ module Terradactyl
120
121
 
121
122
  private_class_method :new
122
123
 
123
- def load_overlay(_overload)
124
- YAML.load_file(config_file)
124
+ def load_overlay(overload)
125
+ config_file_path = overload ? "./#{overload}/#{config_file}" : config_file
126
+
127
+ YAML.load_file(config_file_path)
125
128
  rescue Errno::ENOENT => e
126
129
  abort "FATAL: Could not load project file: `#{config_file}`, #{e.message}"
127
130
  end
@@ -129,6 +132,21 @@ module Terradactyl
129
132
  def config_file
130
133
  @config_file = CONFIG_PROJECT_FILE
131
134
  end
135
+
136
+ def merge_overlay(overlay_path)
137
+ config_file_path = overlay_path ? "./#{overlay_path}/#{config_file}" : config_file
138
+
139
+ config_to_merge = YAML.load_file(config_file_path)
140
+
141
+ # set base_folder name if it's '.'
142
+ if config_to_merge['terradactyl']['base_folder'] == '.'
143
+ config_to_merge['terradactyl']['base_folder'] = overlay_path
144
+ end
145
+
146
+ load_config(overlay_override: config_to_merge)
147
+ rescue Errno::ENOENT => e
148
+ abort "FATAL: Could not load project file: `#{config_file}`, #{e.message}"
149
+ end
132
150
  end
133
151
 
134
152
  class ConfigStack < ConfigApplication
@@ -140,14 +158,15 @@ module Terradactyl
140
158
 
141
159
  attr_reader :stack_name, :stack_path, :base_folder
142
160
 
143
- def initialize(stack_name)
161
+ def initialize(stack_name, base_override = nil)
144
162
  @stack_name = stack_name
145
163
  @project_config = ConfigProject.instance
146
- @base_folder = @project_config.base_folder
164
+ @base_folder = base_override || @project_config.base_folder
147
165
  @stack_path = "#{@base_folder}/#{@stack_name}"
148
166
  @config_file = "#{@stack_path}/#{ConfigProject::CONFIG_PROJECT_FILE}"
149
167
  @defaults = load_defaults(@project_config.to_h)
150
168
  @overlay = load_overlay(@config_file)
169
+
151
170
  load_config
152
171
  end
153
172
 
@@ -16,15 +16,11 @@ module Terradactyl
16
16
  `git ls-files .`
17
17
  end
18
18
 
19
- def base_dir
20
- config.base_folder
21
- end
22
-
23
19
  def stack_name(path)
24
20
  path.split('/')[1]
25
21
  end
26
22
 
27
- def sift(stacks)
23
+ def sift(stacks, _base_dir)
28
24
  stacks
29
25
  end
30
26
  end
@@ -42,7 +38,7 @@ module Terradactyl
42
38
  `git --no-pager diff --name-only HEAD .`
43
39
  end
44
40
 
45
- def sift(stacks)
41
+ def sift(stacks, base_dir)
46
42
  modified = git_cmd.split.each_with_object([]) do |path, memo|
47
43
  memo << stack_name(path) if path =~ /#{base_dir}/
48
44
  end
@@ -90,8 +86,8 @@ module Terradactyl
90
86
  end
91
87
 
92
88
  class StacksApplyFilterPrePlanned < StacksApplyFilterDefault
93
- def sift(stacks)
94
- targets = Dir.glob('**/*.tfout').each_with_object([]) do |path, memo|
89
+ def sift(stacks, base_dir)
90
+ targets = Dir.glob("#{base_dir}/**/*.tfout").each_with_object([]) do |path, memo|
95
91
  memo << path.split('/')[1]
96
92
  end
97
93
  stacks & targets
@@ -10,10 +10,18 @@ module Terradactyl
10
10
  new(stack_name)
11
11
  end
12
12
 
13
- def initialize(stack_name)
14
- @stack_name = validate_stack_name(stack_name)
15
- @stack_config = ConfigStack.new(@stack_name)
16
- @tf_version = tf_version
13
+ def initialize(stack_name, base_override = nil)
14
+ if base_override
15
+ # merge the terradactyl.yaml inside the provided base folder
16
+ config.merge_overlay(base_override)
17
+ else
18
+ config.reload
19
+ end
20
+
21
+ @stack_name = validate_stack_name(stack_name)
22
+ @base_override = base_override
23
+ @stack_config = ConfigStack.new(@stack_name, @base_override)
24
+ @tf_version = tf_version
17
25
  Commands.extend_by_revision(@tf_version, self)
18
26
  print_message "Terraform version: #{@tf_version}"
19
27
  inject_env_vars
@@ -50,10 +50,12 @@ module Terradactyl
50
50
 
51
51
  attr_reader :filter
52
52
 
53
- def initialize(filter: StacksPlanFilterDefault.new)
53
+ def initialize(filter: StacksPlanFilterDefault.new, base_override: nil)
54
+ base_folder = base_override || config.base_folder
55
+
54
56
  @filter = filter
55
- @base_dir = "#{Rake.original_dir}/#{config.base_folder}"
56
- @stacks = @filter.sift(stacks_all)
57
+ @base_dir = "#{Rake.original_dir}/#{base_folder}"
58
+ @stacks = @filter.sift(stacks_all, base_folder)
57
59
  end
58
60
 
59
61
  def list
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Terradactyl
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terradactyl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Warsing
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-09-21 00:00:00.000000000 Z
12
+ date: 2022-09-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -185,6 +185,17 @@ files:
185
185
  - LICENSE.txt
186
186
  - README.md
187
187
  - Rakefile
188
+ - examples/multi-stack-subdirectories/preprod-stacks/stack1/example.tf
189
+ - examples/multi-stack-subdirectories/preprod-stacks/stack1/versions.tf
190
+ - examples/multi-stack-subdirectories/preprod-stacks/stack2/example.tf
191
+ - examples/multi-stack-subdirectories/preprod-stacks/stack2/versions.tf
192
+ - examples/multi-stack-subdirectories/preprod-stacks/terradactyl.yaml
193
+ - examples/multi-stack-subdirectories/terradactyl.yaml
194
+ - examples/multi-stack-subdirectories/test-stacks/stack1/example.tf
195
+ - examples/multi-stack-subdirectories/test-stacks/stack1/versions.tf
196
+ - examples/multi-stack-subdirectories/test-stacks/stack2/example.tf
197
+ - examples/multi-stack-subdirectories/test-stacks/stack2/versions.tf
198
+ - examples/multi-stack-subdirectories/test-stacks/terradactyl.yaml
188
199
  - examples/multi-tf-version/stacks/tfv11/example.tf
189
200
  - examples/multi-tf-version/stacks/tfv11/terradactyl.yaml
190
201
  - examples/multi-tf-version/stacks/tfv12/example.tf