terradactyl 0.13.0 → 0.15.3
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 +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
|