terradactyl 1.3.0 → 1.4.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 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