terradactyl 0.13.0 → 0.15.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +59 -0
- data/README.md +84 -12
- data/examples/multi-tf-version/stacks/tfv11/terradactyl.yaml +1 -1
- data/examples/multi-tf-version/stacks/tfv12/example.tf +3 -1
- data/examples/multi-tf-version/stacks/tfv12/versions.tf +3 -0
- data/examples/multi-tf-version/stacks/tfv13/versions.tf +8 -0
- data/examples/multi-tf-version/stacks/tfv14/example.tf +1 -0
- data/examples/multi-tf-version/stacks/tfv14/versions.tf +8 -0
- data/examples/multi-tf-version/stacks/tfv15/example.tf +1 -0
- data/examples/multi-tf-version/stacks/tfv15/versions.tf +8 -0
- data/examples/multi-tf-version/terradactyl.yaml +1 -1
- data/exe/td +11 -0
- data/lib/terradactyl/cli.rb +102 -11
- data/lib/terradactyl/commands.rb +248 -17
- data/lib/terradactyl/common.rb +8 -0
- data/lib/terradactyl/config.rb +46 -1
- data/lib/terradactyl/stack.rb +4 -0
- data/lib/terradactyl/version.rb +1 -1
- data/terradactyl.gemspec +1 -2
- metadata +11 -7
- data/examples/multi-tf-version/stacks/tfv12/terradactyl.yaml +0 -3
- data/examples/multi-tf-version/stacks/tfv13/terradactyl.yaml +0 -3
- data/exe/td +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1878fa2dfe8685f8e0328afda2940e4a5662d9295bb3369a2fd8f2bf4c9ab88
|
4
|
+
data.tar.gz: 3f2412d3d987a3fc8881294676f8a435d09f00088f31c826719aa31a66872168
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 291474a3c77936a5245f7603ff2f4667f86e15485b7f26632c3b4e8d4c0ca5c92248597cc4a0a8d272c0ac817a2f3d7e8a5356ffd7a4be5492c9901cd12cf628
|
7
|
+
data.tar.gz: 5627523418cca7084a30a03f757dcf49a0d628b94c013c87b46279eca181b80856b4e5a064d0aa38c2d57cb97692f6d140878a04662de933e71cd87ab644675d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,64 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.15.3 (2021-05-14)
|
4
|
+
|
5
|
+
BUG FIXES:
|
6
|
+
|
7
|
+
* fix `auto-approve` on `destroy` subcommand for Terraform version 0.15
|
8
|
+
* update all tests to use latest minor rev
|
9
|
+
|
10
|
+
## 0.15.2 (2021-05-02)
|
11
|
+
|
12
|
+
NEW FEATURES:
|
13
|
+
|
14
|
+
* make all stacks upgradeable, regardless of binary version
|
15
|
+
* add warning after upgrading to Terrafrom version 0.13
|
16
|
+
* expanded testing
|
17
|
+
|
18
|
+
BUG FIXES:
|
19
|
+
|
20
|
+
* do not init backend during upgrade
|
21
|
+
* fix edge case on stacks with no `versions.tf` file
|
22
|
+
|
23
|
+
## 0.15.1 (2021-04-28)
|
24
|
+
|
25
|
+
BUG FIXES:
|
26
|
+
|
27
|
+
* repair broken `upgrade` subcommand
|
28
|
+
* fix malformed HCL substitution
|
29
|
+
* fix regex match order of operations bug
|
30
|
+
* make the feature more robust
|
31
|
+
* add better feedback
|
32
|
+
|
33
|
+
## 0.15.0 (2021-04-27)
|
34
|
+
|
35
|
+
NEW FEATURES:
|
36
|
+
|
37
|
+
* add support for Terraform version `0.14.x`
|
38
|
+
* add support for Terraform version `0.15.x`
|
39
|
+
* add new subcommand `install`
|
40
|
+
* generic component installation; presently only supports `terraform`
|
41
|
+
- permits on-demand installation of any available Terraform binary
|
42
|
+
* add new subcommand `upgrade`
|
43
|
+
* performs a Terraform upgrade of the target stack
|
44
|
+
* add support for native HCL Terraform contraints
|
45
|
+
* terradactyl will now search for Terraform version contraints in the following files: `settings.tf`, `versions.tf` and `backend.tf`
|
46
|
+
* update version expression parsing to match Terraform's own
|
47
|
+
* terradactyl version expression parsing should now operate the same way Terraform's own does, including support for version ranges
|
48
|
+
|
49
|
+
## 0.13.2 (2020-12-09)
|
50
|
+
|
51
|
+
BUG FIXES:
|
52
|
+
|
53
|
+
* replace symlink to executable with identical copy
|
54
|
+
- `gem build` produces broken symlinks; unsupported
|
55
|
+
|
56
|
+
## 0.13.1 (2020-12-09)
|
57
|
+
|
58
|
+
BUG FIXES:
|
59
|
+
|
60
|
+
* update README to include missing acknowledgments
|
61
|
+
|
3
62
|
## 0.13.0 (2020-11-26)
|
4
63
|
|
5
64
|
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
|
22
|
+
NOTE: Terraform sub-command operations are only supported between stable versions `>= 0.11.x` to `~> 0.15.x`.
|
23
23
|
|
24
24
|
## Installation
|
25
25
|
|
@@ -83,6 +83,14 @@ The Terradactyl CLI is installed with a symlink so it may be called by its full
|
|
83
83
|
$ terradactyl help
|
84
84
|
$ td help
|
85
85
|
|
86
|
+
#### install terraform
|
87
|
+
|
88
|
+
##### NOTE: You do not need to explicitly install Terraform, it will be downloaded and installed automatically, if your stack is configured to do so -- this is just to demonstrate on-demand installs ...
|
89
|
+
|
90
|
+
This is optional!
|
91
|
+
|
92
|
+
$ terradactyl install terraform --version=0.15.1
|
93
|
+
|
86
94
|
#### quickplan a single stack
|
87
95
|
|
88
96
|
You can specify the relative path to the stack OR the just the stack name. These two commands are equivalent:
|
@@ -110,6 +118,18 @@ When complete, you should have a JSON report that you can pass to other processe
|
|
110
118
|
|
111
119
|
$ terradactyl smartapply
|
112
120
|
|
121
|
+
#### upgrade a legacy stack
|
122
|
+
|
123
|
+
Running this one time will upgrade it to next minor revision of Terraform ...
|
124
|
+
|
125
|
+
# Take me to Terraform v12
|
126
|
+
$ terradactyl upgrade stacks/tfv11
|
127
|
+
|
128
|
+
Running it again will bump it again!
|
129
|
+
|
130
|
+
# Take me to Terraform v13
|
131
|
+
$ terradactyl upgrade stacks/tfv11
|
132
|
+
|
113
133
|
#### clean all the stacks
|
114
134
|
|
115
135
|
$ terradactyl clean-all
|
@@ -122,7 +142,7 @@ See the [Configuration](#configuration) section for more info on how to control
|
|
122
142
|
|
123
143
|
## Operation
|
124
144
|
|
125
|
-
NOTE: `terradactyl` (symlinked as `td`) ONLY operates in the root of your monorepo. In order to execute any
|
145
|
+
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:
|
126
146
|
|
127
147
|
FATAL: Could not load project file: `terradactyl.yaml`, No such file or directory @ rb_sysopen - terradactyl.yaml
|
128
148
|
|
@@ -135,9 +155,9 @@ Generally speaking, Terradactyl operates on the principle of **plan file** (`*.t
|
|
135
155
|
|
136
156
|
In some cases, this might seem onerous, but it pays dividends in team workflow and CI/CD contexts.
|
137
157
|
|
138
|
-
### Supported
|
158
|
+
### Supported subcommands
|
139
159
|
|
140
|
-
Terradactyl was created to facilitate the using Terraform in a CI environment. As such, some of the more exotic ad hoc user-focused
|
160
|
+
Terradactyl was created to facilitate the using Terraform in a CI environment. As such, some of the more exotic ad hoc user-focused subcommands have not received any effort in integration. The following is a list of the supported Terraform subcommands:
|
141
161
|
|
142
162
|
* apply
|
143
163
|
* destroy
|
@@ -147,6 +167,32 @@ Terradactyl was created to facilitate the using Terraform in a CI environment. A
|
|
147
167
|
* refresh
|
148
168
|
* validate
|
149
169
|
|
170
|
+
### Special utility subcommands
|
171
|
+
|
172
|
+
Terradactyl add some unique utility commands that permit you to more readily manage your Terraform stacks.
|
173
|
+
|
174
|
+
#### install
|
175
|
+
|
176
|
+
Installs supporting components, namely Terraform itself...
|
177
|
+
|
178
|
+
# Install the latest terraform binary
|
179
|
+
terradactly install terraform
|
180
|
+
|
181
|
+
# Install pessimistic version
|
182
|
+
terradactyl install terraform --version="~> 0.13.0"
|
183
|
+
|
184
|
+
# Install ranged version
|
185
|
+
terradactyl install terraform --version=">= 0.14.5, <= 0.14.7"
|
186
|
+
|
187
|
+
# Install explicit version
|
188
|
+
terradactyl install terraform --version=0.15.0-beta2
|
189
|
+
|
190
|
+
#### upgrade
|
191
|
+
|
192
|
+
Upgrade abstracts the various Terraform subcommands related to upgrading individual stacks.
|
193
|
+
|
194
|
+
terradactyl upgrade <stack>
|
195
|
+
|
150
196
|
### Meta-commands
|
151
197
|
|
152
198
|
Terradactyl provides a few useful meta-commands that can help you avoid repetitive multi-phase Terraform operations. Here are a few ...
|
@@ -166,7 +212,7 @@ Apply or Refresh _ANY_ stack containing a plan file.
|
|
166
212
|
|
167
213
|
### Getting Help
|
168
214
|
|
169
|
-
For a list of available
|
215
|
+
For a list of available subcommands do:
|
170
216
|
|
171
217
|
$ terradactyl help
|
172
218
|
|
@@ -195,7 +241,7 @@ You can dump the compiled configuration for your project using the `defaults` su
|
|
195
241
|
```yaml
|
196
242
|
terradactyl: <Object, Terradactyl config>
|
197
243
|
base_folder: <String, the sub-directory for all your Terraform stacks, default=stacks>
|
198
|
-
terraform: <Object, configuration to Terraform
|
244
|
+
terraform: <Object, configuration to Terraform subcommands and binaries>
|
199
245
|
binary: <String, path to the Terraform binary you wish to use, default=nil>
|
200
246
|
version: <String, explicit or implict Terraform version, default=nil>
|
201
247
|
autoinstall: <Bool, perform automatic Terraform installations, default=true>
|
@@ -228,7 +274,7 @@ terradactyl: <Object, Terradactyl config>
|
|
228
274
|
|
229
275
|
### Terraform sub-command arguments
|
230
276
|
|
231
|
-
Note that the config above contains config for Terraform
|
277
|
+
Note that the config above contains config for Terraform subcommands. for example:
|
232
278
|
|
233
279
|
```yaml
|
234
280
|
terradactyl:
|
@@ -243,12 +289,12 @@ Each of the keys in the `plan` object correspond to an argument passed to the `t
|
|
243
289
|
|
244
290
|
terraform -lock=false -parallelism=5 -detailed-exitcode
|
245
291
|
|
246
|
-
There are two conventions to keep in mind when configuring
|
292
|
+
There are two conventions to keep in mind when configuring subcommands:
|
247
293
|
|
248
294
|
1. any sub-command option which toggles behaviour (i.e. `-detailed-exitcode`) requires a specific Boolean value of `true` OR `false`
|
249
295
|
2. any sub-command option that is hyphenated (i.e. `-detailed-exitcode`) is set in the config using an **underscore** (i.e `detailed_exitcode`)
|
250
296
|
|
251
|
-
If you need to tweak or augment any of the default arguments passed to any of the supported Terraform
|
297
|
+
If you need to tweak or augment any of the default arguments passed to any of the supported Terraform subcommands, you can do so by adding them to the config.
|
252
298
|
|
253
299
|
Example:
|
254
300
|
|
@@ -260,7 +306,7 @@ terradactyl:
|
|
260
306
|
backup: /tmp/tfbackup
|
261
307
|
```
|
262
308
|
|
263
|
-
In addition, you can override the `echo` and `quiet` settings for any of the Terraform
|
309
|
+
In addition, you can override the `echo` and `quiet` settings for any of the Terraform subcommands:
|
264
310
|
|
265
311
|
```yaml
|
266
312
|
terradactyl:
|
@@ -279,9 +325,29 @@ This can assist in debugging.
|
|
279
325
|
|
280
326
|
### Terraform version management
|
281
327
|
|
328
|
+
Terradactyl gives you some powerful ways to manage which versions of Terraform you support and where.
|
329
|
+
|
330
|
+
You may set **project-wide** OR **stack-explicit** versions, by using a config file (`terradactyl.yaml`, see [Configuration](#configuration)).
|
331
|
+
|
332
|
+
In addition, Terradactyl will also, search for a stack's desired Terraform version from one of **your HCL files**.
|
333
|
+
|
334
|
+
terraform {
|
335
|
+
required_version = "~> 0.13.0"
|
336
|
+
}
|
337
|
+
|
338
|
+
If a configuration like the one above is discovered in your stack's ...
|
339
|
+
|
340
|
+
* `settings.tf`
|
341
|
+
* `versions.tf`
|
342
|
+
* `backend.tf`
|
343
|
+
|
344
|
+
... file, Terradactyl will download and use that version as required.
|
345
|
+
|
346
|
+
NOTE: These files are searched in the order you see above. If you specify `required_version` multiple times, the last one discovered is used.
|
347
|
+
|
282
348
|
#### Explicit versions
|
283
349
|
|
284
|
-
By default, Terradactyl will always use the **latest** stable version of Terraform.
|
350
|
+
By default, Terradactyl will always use the **latest** stable version of Terraform. So, if you don't specify a version, you will always get the latest stable version of Terraform available.
|
285
351
|
|
286
352
|
But, as part of Terradactyl's configuration, you can specify a **project** Terraform version, making it the default for _your_ monorepo:
|
287
353
|
|
@@ -291,7 +357,9 @@ terradactyl:
|
|
291
357
|
version: 0.12.29
|
292
358
|
```
|
293
359
|
|
294
|
-
Still, because Terradactyl's configuration is hierarchic,
|
360
|
+
Still, because Terradactyl's configuration is hierarchic, you can also specify a version at the project level ...
|
361
|
+
|
362
|
+
Yes! **Each stack may use a different version of Terradactyl independent of any other.**
|
295
363
|
|
296
364
|
See [examples/multi-tf-version](examples/multi-tf-version) for this setup.
|
297
365
|
|
@@ -323,3 +391,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/vcilab
|
|
323
391
|
## License
|
324
392
|
|
325
393
|
This code is released under the MIT License. See [LICENSE.txt](LICENSE.txt).
|
394
|
+
|
395
|
+
## Acknowledgments
|
396
|
+
|
397
|
+
Special thanks to [Riley Shott](https://github.com/Ginja) upon whose original design and work I based this Gem.
|
@@ -0,0 +1 @@
|
|
1
|
+
resource "null_resource" "baz" {}
|
@@ -0,0 +1 @@
|
|
1
|
+
resource "null_resource" "baz" {}
|
data/exe/td
ADDED
data/lib/terradactyl/cli.rb
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Fix for https://github.com/erikhuda/thor/issues/398
|
4
|
+
class Thor
|
5
|
+
module Shell
|
6
|
+
class Basic
|
7
|
+
def print_wrapped(message, options = {})
|
8
|
+
indent = (options[:indent] || 0).to_i
|
9
|
+
if indent.zero?
|
10
|
+
stdout.puts message
|
11
|
+
else
|
12
|
+
message.each_line do |message_line|
|
13
|
+
stdout.print ' ' * indent
|
14
|
+
stdout.puts message_line.chomp
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
3
22
|
module Terradactyl
|
4
23
|
# rubocop:disable Metrics/ClassLength
|
5
24
|
class CLI < Thor
|
@@ -14,6 +33,7 @@ module Terradactyl
|
|
14
33
|
super
|
15
34
|
end
|
16
35
|
|
36
|
+
# rubocop:disable Metrics/BlockLength
|
17
37
|
no_commands do
|
18
38
|
# Monkey-patch Thor internal method to break out of nested calls
|
19
39
|
def invoke_command(command, *args)
|
@@ -43,7 +63,27 @@ module Terradactyl
|
|
43
63
|
File.write data_file, JSON.pretty_generate(report)
|
44
64
|
print_ok 'Done!'
|
45
65
|
end
|
66
|
+
|
67
|
+
def terraform_latest
|
68
|
+
Terradactyl::Terraform::VersionManager.latest
|
69
|
+
end
|
70
|
+
|
71
|
+
def upgrade_stack(name)
|
72
|
+
@stack ||= Stack.new(name)
|
73
|
+
print_warning "Upgrading: #{@stack.name}"
|
74
|
+
if @stack.upgrade.zero?
|
75
|
+
print_ok "Upgraded: #{@stack.name}"
|
76
|
+
else
|
77
|
+
Stacks.error!(@stack)
|
78
|
+
print_crit "Failed to upgrade: #{@stack.name}"
|
79
|
+
throw :error
|
80
|
+
end
|
81
|
+
rescue Terradactyl::Terraform::VersionManager::VersionManagerError => e
|
82
|
+
print_crit "Error: #{e.message}"
|
83
|
+
exit 1
|
84
|
+
end
|
46
85
|
end
|
86
|
+
# rubocop:enable Metrics/BlockLength
|
47
87
|
|
48
88
|
#################################################################
|
49
89
|
# GENERIC TASKS
|
@@ -128,6 +168,14 @@ module Terradactyl
|
|
128
168
|
# the `quickplan` task is an exception to this rule.
|
129
169
|
#################################################################
|
130
170
|
|
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)
|
177
|
+
end
|
178
|
+
|
131
179
|
desc 'quickplan NAME', 'Clean, init and plan a stack, by name'
|
132
180
|
def quickplan(name)
|
133
181
|
print_header "Quick planning #{name} ..."
|
@@ -224,8 +272,10 @@ module Terradactyl
|
|
224
272
|
end
|
225
273
|
|
226
274
|
desc 'init NAME', 'Init an individual stack, by name'
|
227
|
-
def init(name)
|
275
|
+
def init(name, backend: true)
|
228
276
|
@stack ||= Stack.new(name)
|
277
|
+
@stack.config.terraform.init.backend = backend
|
278
|
+
|
229
279
|
print_ok "Initializing: #{@stack.name}"
|
230
280
|
if @stack.init.zero?
|
231
281
|
print_ok "Initialized: #{@stack.name}"
|
@@ -247,7 +297,7 @@ module Terradactyl
|
|
247
297
|
when 1
|
248
298
|
Stacks.error!(@stack)
|
249
299
|
print_crit "Plan failed: #{@stack.name}"
|
250
|
-
@stack.
|
300
|
+
@stack.print_error
|
251
301
|
throw :error
|
252
302
|
when 2
|
253
303
|
Stacks.dirty!(@stack)
|
@@ -289,13 +339,7 @@ module Terradactyl
|
|
289
339
|
print_ok "Cleaned: #{@stack.name}"
|
290
340
|
end
|
291
341
|
|
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
|
342
|
+
desc 'apply NAME', 'Apply an individual stack, by name'
|
299
343
|
def apply(name)
|
300
344
|
@stack ||= Stack.new(name)
|
301
345
|
print_warning "Applying: #{@stack.name}"
|
@@ -307,7 +351,7 @@ module Terradactyl
|
|
307
351
|
end
|
308
352
|
end
|
309
353
|
|
310
|
-
desc 'refresh NAME', 'Refresh state on an individual stack, by name'
|
354
|
+
desc 'refresh NAME', 'Refresh state on an individual stack, by name'
|
311
355
|
def refresh(name)
|
312
356
|
@stack ||= Stack.new(name)
|
313
357
|
print_crit "Refreshing: #{@stack.name}"
|
@@ -319,7 +363,7 @@ module Terradactyl
|
|
319
363
|
end
|
320
364
|
end
|
321
365
|
|
322
|
-
desc 'destroy NAME', 'Destroy an individual stack, by name'
|
366
|
+
desc 'destroy NAME', 'Destroy an individual stack, by name'
|
323
367
|
def destroy(name)
|
324
368
|
@stack ||= Stack.new(name)
|
325
369
|
print_crit "Destroying: #{@stack.name}"
|
@@ -330,6 +374,53 @@ module Terradactyl
|
|
330
374
|
print_crit "Failed to apply changes: #{@stack.name}"
|
331
375
|
end
|
332
376
|
end
|
377
|
+
|
378
|
+
#################################################################
|
379
|
+
# PROJECT-LEVEL UTILITY TASKS
|
380
|
+
# * These tasks are managing project-wide characteristics or
|
381
|
+
# invoking useful commands.
|
382
|
+
#################################################################
|
383
|
+
|
384
|
+
desc 'install COMPONENT', 'Installs specified component'
|
385
|
+
long_desc <<~LONGDESC
|
386
|
+
The `terradactyl install COMPONENT` subcommand perfoms installations of
|
387
|
+
prerequisties. At present, only Terraform binaries are supported.
|
388
|
+
|
389
|
+
Here are a few examples:
|
390
|
+
|
391
|
+
# Install latest
|
392
|
+
`terradactyl install terraform`
|
393
|
+
|
394
|
+
# Install pessimistic version
|
395
|
+
`terradactyl install terraform --version="~> 0.13.0"`
|
396
|
+
|
397
|
+
# Install ranged version
|
398
|
+
`terradactyl install terraform --version=">= 0.14.5, <= 0.14.7"`
|
399
|
+
|
400
|
+
# Install explicit version
|
401
|
+
`terradactyl install terraform --version=0.15.0-beta2`
|
402
|
+
|
403
|
+
LONGDESC
|
404
|
+
option :version, type: :string, default: 'latest'
|
405
|
+
# rubocop:disable Metrics/AbcSize
|
406
|
+
def install(component)
|
407
|
+
case component.to_sym
|
408
|
+
when :terraform
|
409
|
+
print_warning "Installing: #{component}, version: #{options[:version]}"
|
410
|
+
version = options[:version] == 'latest' ? terraform_latest : options[:version]
|
411
|
+
Terradactyl::Terraform::VersionManager.reset!
|
412
|
+
Terradactyl::Terraform::VersionManager.version = version
|
413
|
+
Terradactyl::Terraform::VersionManager.install
|
414
|
+
if Terradactyl::Terraform::VersionManager.binary
|
415
|
+
print_ok "Installed: #{Terradactyl::Terraform::VersionManager.binary}"
|
416
|
+
end
|
417
|
+
else
|
418
|
+
msg = %(Operation not supported -- I don't know how to install: #{component})
|
419
|
+
print_crit msg
|
420
|
+
exit 1
|
421
|
+
end
|
422
|
+
end
|
423
|
+
# rubocop:enable Metrics/AbcSize
|
333
424
|
end
|
334
425
|
# rubocop:enable Metrics/ClassLength
|
335
426
|
end
|
data/lib/terradactyl/commands.rb
CHANGED
@@ -1,13 +1,70 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Terradactyl
|
4
|
+
module Terraform
|
5
|
+
module Subcommands
|
6
|
+
module Upgrade
|
7
|
+
def defaults
|
8
|
+
{
|
9
|
+
'yes' => false
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def switches
|
14
|
+
%w[
|
15
|
+
yes
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Commands
|
22
|
+
class Upgrade < Base
|
23
|
+
def execute
|
24
|
+
VersionManager.install
|
25
|
+
return 0 unless revision.upgradeable?
|
26
|
+
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_version
|
31
|
+
@next_version ||= compute_upgrade
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def revision
|
37
|
+
Terradactyl::Stack.revision
|
38
|
+
end
|
39
|
+
|
40
|
+
def compute_upgrade
|
41
|
+
maj, min, _rev = version.split('.')
|
42
|
+
resolution = VersionManager.resolve("~> #{maj}.#{min.to_i + 1}.0")
|
43
|
+
VersionManager.version = resolution
|
44
|
+
VersionManager.version
|
45
|
+
end
|
46
|
+
|
47
|
+
def subcmd
|
48
|
+
pre = version.slice(/\d+\.\d+/)
|
49
|
+
sig = self.class.name.split('::').last.downcase
|
50
|
+
sig == 'base' ? '' : "#{pre}#{sig}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# rubocop:disable Metrics/ModuleLength
|
4
57
|
module Commands
|
5
58
|
class << self
|
6
59
|
def extend_by_revision(tf_version, object)
|
7
60
|
anon_module = revision_module
|
61
|
+
revision = revision_constant(tf_version)
|
8
62
|
|
9
63
|
anon_module.include(self)
|
10
|
-
anon_module.prepend(
|
64
|
+
anon_module.prepend(revision)
|
65
|
+
|
66
|
+
object.class.define_singleton_method(:revision) { revision }
|
67
|
+
object.define_singleton_method(:revision) { revision }
|
11
68
|
|
12
69
|
object.extend(anon_module)
|
13
70
|
end
|
@@ -63,18 +120,17 @@ module Terradactyl
|
|
63
120
|
options: options,
|
64
121
|
capture: true)
|
65
122
|
|
66
|
-
|
67
|
-
when 0
|
68
|
-
'No changes. Infrastructure is up-to-date.'
|
69
|
-
when 1
|
70
|
-
captured.stderr
|
71
|
-
when 2
|
72
|
-
captured.stdout
|
73
|
-
end
|
123
|
+
@plan_file_obj = load_plan_file
|
74
124
|
|
75
|
-
|
76
|
-
|
77
|
-
|
125
|
+
case captured.exitstatus
|
126
|
+
when 0
|
127
|
+
'No changes. Infrastructure is up-to-date.'
|
128
|
+
when 1
|
129
|
+
@plan_file_obj.error_output = captured.stderr
|
130
|
+
when 2
|
131
|
+
@plan_file_obj.plan_output = captured.stdout
|
132
|
+
@plan_file_obj.save
|
133
|
+
end
|
78
134
|
|
79
135
|
captured.exitstatus
|
80
136
|
end
|
@@ -118,19 +174,149 @@ module Terradactyl
|
|
118
174
|
end
|
119
175
|
# rubocop:enable Metrics/AbcSize
|
120
176
|
|
177
|
+
def upgrade
|
178
|
+
perform_upgrade
|
179
|
+
end
|
180
|
+
|
121
181
|
private
|
122
182
|
|
183
|
+
def versions_file
|
184
|
+
'versions.tf'
|
185
|
+
end
|
186
|
+
|
187
|
+
def settings_files
|
188
|
+
Dir.glob('*.tf').each_with_object([]) do |file, memo|
|
189
|
+
File.open(file, 'r').each_line do |line|
|
190
|
+
if line.match(Common.required_versions_re)
|
191
|
+
memo << file
|
192
|
+
break
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def sanitize_terraform_settings
|
199
|
+
settings_files.each do |file|
|
200
|
+
next if file == versions_file
|
201
|
+
|
202
|
+
write_stream = Tempfile.new(Common.tag)
|
203
|
+
File.open(file, 'r').each_line do |line|
|
204
|
+
write_stream.puts line unless line.match(Common.required_versions_re)
|
205
|
+
end
|
206
|
+
write_stream.close
|
207
|
+
FileUtils.mv(write_stream.path, file)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def update_required_version(upgrade_version)
|
212
|
+
if File.exist?(versions_file)
|
213
|
+
settings = File.read(versions_file)
|
214
|
+
if (req_version = settings.match(Common.required_versions_re))
|
215
|
+
substitution = %(#{req_version[:assignment]}"~> #{upgrade_version}")
|
216
|
+
settings.sub!(Common.required_versions_re, substitution)
|
217
|
+
end
|
218
|
+
else
|
219
|
+
# This is ugly, so let's explain ...
|
220
|
+
#
|
221
|
+
# When the versions.tf is present, but the stack is ~> 0.11.0, the
|
222
|
+
# `terraform 0.12upgrade` subcommand will FAIL because it uses the
|
223
|
+
# presence of this file as the sole gauge as to whether or not
|
224
|
+
# the stack can be upgraded. So, why not just use `-force`? Haha yes ...
|
225
|
+
#
|
226
|
+
# When the `versions.tf` file exists and the `-force` flag is passed,
|
227
|
+
# it will create a `versions-1.tf` file ... FML :facepalm:
|
228
|
+
#
|
229
|
+
# So, make the creation of a de facto versions.tf contingent upon the
|
230
|
+
# Terraform upgrade_version. Yay.
|
231
|
+
unless upgrade_version =~ /0\.12/
|
232
|
+
settings = <<~VERSIONS
|
233
|
+
terraform {
|
234
|
+
required_version = "~> #{upgrade_version}"
|
235
|
+
}
|
236
|
+
VERSIONS
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
File.write(versions_file, settings) if settings
|
241
|
+
end
|
242
|
+
|
243
|
+
def upgrade_notice
|
244
|
+
output = File.read('versions.tf')
|
245
|
+
insert = output.strip.split("\n").map { |l| " #{l}" }.join($INPUT_RECORD_SEPARATOR)
|
246
|
+
|
247
|
+
<<~NOTICE
|
248
|
+
This stack has been upgraded to version the described below and its
|
249
|
+
Terradactly config file (if it existed) has been removed.
|
250
|
+
|
251
|
+
#{insert}
|
252
|
+
|
253
|
+
NOTES:
|
254
|
+
|
255
|
+
• ALL Terraform version constraints are now specified in `versions.tf` using
|
256
|
+
the `required_version` directive.
|
257
|
+
|
258
|
+
• If your stack already contained one or more `required_version` directives,
|
259
|
+
they have been consolidated into a single directive in `versions.tf`.
|
260
|
+
|
261
|
+
• Terraform provider version contraints ARE NOT upgraded automatically. You
|
262
|
+
will need to edit these MANUALLY.
|
263
|
+
|
264
|
+
• Before proceeding. please perform a `terradactyl quickplan` on your stack
|
265
|
+
to ensure the upgraded stack functions as intended.
|
266
|
+
NOTICE
|
267
|
+
end
|
268
|
+
|
269
|
+
def upgrade_notice_rev013
|
270
|
+
<<~NOTICE
|
271
|
+
STOP UPGRADING!
|
272
|
+
|
273
|
+
Upgrading from Terraform 0.12 to 0.13 requires an apply to be performed
|
274
|
+
before continuing ...
|
275
|
+
|
276
|
+
DO NOT attempt to upgrade any further without first committing the existing
|
277
|
+
changes and seeing they are applied.
|
278
|
+
|
279
|
+
See the documentation here if you require more infomation ...
|
280
|
+
|
281
|
+
https://www.terraform.io/upgrade-guides/0-13.html
|
282
|
+
NOTICE
|
283
|
+
end
|
284
|
+
|
285
|
+
# rubocop:disable Metrics/AbcSize
|
286
|
+
def perform_upgrade
|
287
|
+
options = command_options.tap { |dat| dat.yes = true }
|
288
|
+
upgrade = Upgrade.new(dir_or_plan: nil, options: options)
|
289
|
+
|
290
|
+
sanitize_terraform_settings
|
291
|
+
|
292
|
+
update_required_version(upgrade.next_version)
|
293
|
+
|
294
|
+
if (result = upgrade.execute).zero?
|
295
|
+
update_required_version(upgrade.next_version)
|
296
|
+
FileUtils.rm_rf('terradactyl.yaml') if File.exist?('terradactyl.yaml')
|
297
|
+
end
|
298
|
+
|
299
|
+
print_content(upgrade_notice) if result.zero?
|
300
|
+
|
301
|
+
print_crit(upgrade_notice_rev013) if upgrade.next_version =~ /0\.13/
|
302
|
+
|
303
|
+
result
|
304
|
+
end
|
305
|
+
# rubocop:enable Metrics/AbcSize
|
306
|
+
|
123
307
|
def load_plan_file
|
124
308
|
Terraform::PlanFile.new(plan_path: plan_file, parser: parser)
|
125
309
|
end
|
126
310
|
|
127
311
|
module Rev011
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
312
|
+
class << self
|
313
|
+
def upgradeable?
|
314
|
+
true
|
315
|
+
end
|
132
316
|
end
|
133
317
|
|
318
|
+
include Terraform::Commands
|
319
|
+
|
134
320
|
private
|
135
321
|
|
136
322
|
def parser
|
@@ -139,6 +325,12 @@ module Terradactyl
|
|
139
325
|
end
|
140
326
|
|
141
327
|
module Rev012
|
328
|
+
class << self
|
329
|
+
def upgradeable?
|
330
|
+
true
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
142
334
|
include Terraform::Commands
|
143
335
|
|
144
336
|
private
|
@@ -149,13 +341,52 @@ module Terradactyl
|
|
149
341
|
end
|
150
342
|
|
151
343
|
module Rev013
|
344
|
+
class << self
|
345
|
+
def upgradeable?
|
346
|
+
false
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
152
350
|
include Terraform::Commands
|
153
351
|
|
154
352
|
private
|
155
353
|
|
156
354
|
def parser
|
157
|
-
Terraform::
|
355
|
+
Terraform::Rev013::PlanFileParser
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
module Rev014
|
360
|
+
class << self
|
361
|
+
def upgradeable?
|
362
|
+
false
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
include Terraform::Commands
|
367
|
+
|
368
|
+
private
|
369
|
+
|
370
|
+
def parser
|
371
|
+
Terraform::Rev014::PlanFileParser
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
module Rev015
|
376
|
+
class << self
|
377
|
+
def upgradeable?
|
378
|
+
false
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
include Terraform::Commands
|
383
|
+
|
384
|
+
private
|
385
|
+
|
386
|
+
def parser
|
387
|
+
Terraform::Rev015::PlanFileParser
|
158
388
|
end
|
159
389
|
end
|
160
390
|
end
|
391
|
+
# rubocop:enable Metrics/ModuleLength
|
161
392
|
end
|
data/lib/terradactyl/common.rb
CHANGED
@@ -7,6 +7,14 @@ module Terradactyl
|
|
7
7
|
|
8
8
|
module_function
|
9
9
|
|
10
|
+
def required_versions_re
|
11
|
+
/(?<assignment>(?:\n\s)*required_version\s+=\s+)(?<value>".*?")/m
|
12
|
+
end
|
13
|
+
|
14
|
+
def supported_revisions
|
15
|
+
Terradactyl::Commands.constants.select { |c| c =~ /Rev/ }.sort
|
16
|
+
end
|
17
|
+
|
10
18
|
def config
|
11
19
|
@config ||= ConfigProject.instance
|
12
20
|
end
|
data/lib/terradactyl/config.rb
CHANGED
@@ -26,7 +26,7 @@ module Terradactyl
|
|
26
26
|
input: false
|
27
27
|
destroy:
|
28
28
|
parallelism: 5
|
29
|
-
|
29
|
+
auto_approve: true
|
30
30
|
environment:
|
31
31
|
TF_PLUGIN_CACHE_DIR: ~/.terraform.d/plugins
|
32
32
|
misc:
|
@@ -132,6 +132,12 @@ module Terradactyl
|
|
132
132
|
end
|
133
133
|
|
134
134
|
class ConfigStack < ConfigApplication
|
135
|
+
TERRAFORM_SETTINGS_FILES = %w[
|
136
|
+
settings.tf
|
137
|
+
versions.tf
|
138
|
+
backend.tf
|
139
|
+
].freeze
|
140
|
+
|
135
141
|
attr_reader :stack_name, :stack_path, :base_folder
|
136
142
|
|
137
143
|
def initialize(stack_name)
|
@@ -163,5 +169,44 @@ module Terradactyl
|
|
163
169
|
def plan_path
|
164
170
|
"#{stack_path}/#{plan_file}"
|
165
171
|
end
|
172
|
+
|
173
|
+
def versions_file
|
174
|
+
"#{stack_path}/versions.tf"
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def terraform_required_version
|
180
|
+
matches = TERRAFORM_SETTINGS_FILES.map do |file|
|
181
|
+
path = File.join(stack_path, file)
|
182
|
+
if File.exist?(path)
|
183
|
+
File.read(path).match(Common.required_versions_re)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
return {} unless matches.compact!.any?
|
188
|
+
|
189
|
+
{
|
190
|
+
'terradactyl' => {
|
191
|
+
'terraform' => {
|
192
|
+
'version' => matches.last[:value].delete('"')
|
193
|
+
}
|
194
|
+
}
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def load_overlay(config_file)
|
199
|
+
overlay = super(config_file)
|
200
|
+
|
201
|
+
unless overlay_specifies_version?(overlay)
|
202
|
+
overlay.merge!(terraform_required_version)
|
203
|
+
end
|
204
|
+
|
205
|
+
overlay
|
206
|
+
end
|
207
|
+
|
208
|
+
def overlay_specifies_version?(overlay)
|
209
|
+
overlay['terradactyl']&.fetch('terraform', {})&.fetch('version', nil)
|
210
|
+
end
|
166
211
|
end
|
167
212
|
end
|
data/lib/terradactyl/stack.rb
CHANGED
data/lib/terradactyl/version.rb
CHANGED
data/terradactyl.gemspec
CHANGED
@@ -33,11 +33,10 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_dependency 'deep_merge', '~> 1.2'
|
34
34
|
spec.add_dependency 'bundler', '>= 1.16'
|
35
35
|
spec.add_dependency 'rake', '>= 10.0'
|
36
|
-
spec.add_dependency 'terradactyl-terraform', '>= 0.
|
36
|
+
spec.add_dependency 'terradactyl-terraform', '>= 0.15.0'
|
37
37
|
|
38
38
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
39
39
|
spec.add_development_dependency 'pry', '~> 0.12'
|
40
40
|
spec.add_development_dependency 'pry-remote', '~> 0.1.8'
|
41
41
|
spec.add_development_dependency 'rubocop', '~> 0.71.0'
|
42
42
|
end
|
43
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: terradactyl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Warsing
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 0.
|
103
|
+
version: 0.15.0
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 0.
|
110
|
+
version: 0.15.0
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: rspec
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,9 +186,13 @@ files:
|
|
186
186
|
- examples/multi-tf-version/stacks/tfv11/example.tf
|
187
187
|
- examples/multi-tf-version/stacks/tfv11/terradactyl.yaml
|
188
188
|
- examples/multi-tf-version/stacks/tfv12/example.tf
|
189
|
-
- examples/multi-tf-version/stacks/tfv12/
|
189
|
+
- examples/multi-tf-version/stacks/tfv12/versions.tf
|
190
190
|
- examples/multi-tf-version/stacks/tfv13/example.tf
|
191
|
-
- examples/multi-tf-version/stacks/tfv13/
|
191
|
+
- examples/multi-tf-version/stacks/tfv13/versions.tf
|
192
|
+
- examples/multi-tf-version/stacks/tfv14/example.tf
|
193
|
+
- examples/multi-tf-version/stacks/tfv14/versions.tf
|
194
|
+
- examples/multi-tf-version/stacks/tfv15/example.tf
|
195
|
+
- examples/multi-tf-version/stacks/tfv15/versions.tf
|
192
196
|
- examples/multi-tf-version/terradactyl.yaml
|
193
197
|
- examples/simple/stacks/demo/example.tf
|
194
198
|
- examples/simple/terradactyl.yaml
|
@@ -227,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
231
|
- !ruby/object:Gem::Version
|
228
232
|
version: '0'
|
229
233
|
requirements: []
|
230
|
-
rubygems_version: 3.1.
|
234
|
+
rubygems_version: 3.1.6
|
231
235
|
signing_key:
|
232
236
|
specification_version: 4
|
233
237
|
summary: Manage a Terraform monorepo
|
data/exe/td
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
exe/terradactyl
|