terraspace 0.6.4 → 0.6.9
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 +17 -0
- data/lib/terraspace/all/runner.rb +1 -1
- data/lib/terraspace/app.rb +2 -0
- data/lib/terraspace/cli.rb +14 -3
- data/lib/terraspace/cli/fmt.rb +20 -1
- data/lib/terraspace/cli/help/fmt.md +13 -1
- data/lib/terraspace/cli/help/force_unlock.md +7 -0
- data/lib/terraspace/compiler/strategy/mod.rb +4 -3
- data/lib/terraspace/compiler/writer.rb +7 -1
- data/lib/terraspace/core.rb +7 -0
- data/lib/terraspace/hooks/runner.rb +25 -2
- data/lib/terraspace/shell.rb +70 -42
- data/lib/terraspace/terraform/args/default.rb +8 -4
- data/lib/terraspace/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d88e968da03e187d230fad5e1785995ffa95415146a9a2b7cfe500c0e54473a
|
|
4
|
+
data.tar.gz: 49535538ad977e0d7b36620037a70478e6bfdf9c9bc607feff4576724fc351fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d0a9476e10f46a504c555212aa538968045d27845eabcec4c43306a008422dce9b19c28b4d0ce16defce89bc78674c01098c08291af6e321f85f2385e6390f0
|
|
7
|
+
data.tar.gz: 27563820dafa65f85df07ce9e28430a75b7ffb6330b2440444b6a88803efa3f851d9524ae5c0417039a3d698a62896b2071fb84b8de30e93a42c03ad64941dbd
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
|
5
5
|
|
|
6
|
+
## [0.6.9] - 2021-05-07
|
|
7
|
+
- [#112](https://github.com/boltops-tools/terraspace/pull/112) fix smart auto retry
|
|
8
|
+
|
|
9
|
+
## [0.6.8] - 2021-05-07
|
|
10
|
+
- [#110](https://github.com/boltops-tools/terraspace/pull/110) fix popen deadlock with large amounts of output [#97](https://github.com/boltops-tools/terraspace/pull/97) Terraspace hangs when TF_LOG=TRACE environment variable exists #97
|
|
11
|
+
|
|
12
|
+
## [0.6.7] - 2021-05-05
|
|
13
|
+
- [#108](https://github.com/boltops-tools/terraspace/pull/108) provide runner context to terraspace hook
|
|
14
|
+
|
|
15
|
+
## [0.6.6] - 2021-04-15
|
|
16
|
+
- [#101](https://github.com/boltops-tools/terraspace/pull/101) terraspace force-unlock command
|
|
17
|
+
- [#102](https://github.com/boltops-tools/terraspace/pull/102) fix terraspace all summarized logging
|
|
18
|
+
- [#103](https://github.com/boltops-tools/terraspace/pull/103) config.build.pass_files with default files use pass strategy
|
|
19
|
+
|
|
20
|
+
## [0.6.5] - 2021-03-24
|
|
21
|
+
- [#96](https://github.com/boltops-tools/terraspace/pull/96) terraspace fmt: ability to specific module or stack
|
|
22
|
+
|
|
6
23
|
## [0.6.4] - 2021-03-22
|
|
7
24
|
- [#94](https://github.com/boltops-tools/terraspace/pull/94) terraspace fmt command
|
|
8
25
|
|
|
@@ -104,7 +104,7 @@ module Terraspace::All
|
|
|
104
104
|
def run_terraspace(mod_name)
|
|
105
105
|
set_log_path!(mod_name)
|
|
106
106
|
name = command_map(@command)
|
|
107
|
-
o = @options.merge(mod: mod_name, yes: true, build: false, input: false)
|
|
107
|
+
o = @options.merge(mod: mod_name, yes: true, build: false, input: false, log_to_stderr: true)
|
|
108
108
|
o.merge!(quiet: false) if @command == "init" # noisy so can filter and summarize output
|
|
109
109
|
case @command
|
|
110
110
|
when "up"
|
data/lib/terraspace/app.rb
CHANGED
|
@@ -27,6 +27,8 @@ module Terraspace
|
|
|
27
27
|
config.build.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
|
|
28
28
|
config.build.cache_root = nil # defaults to /full/path/to/.terraspace-cache
|
|
29
29
|
config.build.clean_cache = nil # defaults to /full/path/to/.terraspace-cache
|
|
30
|
+
config.build.default_pass_files = ["/files/"]
|
|
31
|
+
config.build.pass_files = []
|
|
30
32
|
config.bundle = ActiveSupport::OrderedOptions.new
|
|
31
33
|
config.bundle.logger = ts_logger
|
|
32
34
|
config.init = ActiveSupport::OrderedOptions.new
|
data/lib/terraspace/cli.rb
CHANGED
|
@@ -27,6 +27,9 @@ module Terraspace
|
|
|
27
27
|
reconfigure_option = Proc.new {
|
|
28
28
|
option :reconfigure, type: :boolean, desc: "Add terraform -reconfigure option"
|
|
29
29
|
}
|
|
30
|
+
type_option = Proc.new {
|
|
31
|
+
option :type, default: "stack", aliases: %w[t], desc: "Type: stack, module, or all"
|
|
32
|
+
}
|
|
30
33
|
|
|
31
34
|
desc "all SUBCOMMAND", "all subcommands"
|
|
32
35
|
long_desc Help.text(:all)
|
|
@@ -82,10 +85,18 @@ module Terraspace
|
|
|
82
85
|
Down.new(options.merge(mod: mod)).run
|
|
83
86
|
end
|
|
84
87
|
|
|
88
|
+
desc "force_unlock", "Calls terrform force-unlock"
|
|
89
|
+
long_desc Help.text(:force_unlock)
|
|
90
|
+
instance_option.call
|
|
91
|
+
def force_unlock(mod, lock_id)
|
|
92
|
+
Commander.new("force-unlock", options.merge(mod: mod, lock_id: lock_id)).run
|
|
93
|
+
end
|
|
94
|
+
|
|
85
95
|
desc "fmt", "Run terraform fmt"
|
|
86
96
|
long_desc Help.text(:fmt)
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
type_option.call
|
|
98
|
+
def fmt(mod=nil)
|
|
99
|
+
Fmt.new(options.merge(mod: mod)).run
|
|
89
100
|
end
|
|
90
101
|
|
|
91
102
|
desc "info STACK", "Get info about stack."
|
|
@@ -106,7 +117,7 @@ module Terraspace
|
|
|
106
117
|
|
|
107
118
|
desc "list", "List stacks and modules."
|
|
108
119
|
long_desc Help.text(:list)
|
|
109
|
-
|
|
120
|
+
type_option.call
|
|
110
121
|
def list
|
|
111
122
|
List.new(options).run
|
|
112
123
|
end
|
data/lib/terraspace/cli/fmt.rb
CHANGED
|
@@ -5,11 +5,12 @@ class Terraspace::CLI
|
|
|
5
5
|
|
|
6
6
|
def initialize(options={})
|
|
7
7
|
@options = options
|
|
8
|
+
@mod_name = options[:mod]
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
def run
|
|
11
12
|
logger.info "Formating terraform files"
|
|
12
|
-
|
|
13
|
+
dirs.each do |dir|
|
|
13
14
|
format(dir)
|
|
14
15
|
end
|
|
15
16
|
end
|
|
@@ -17,5 +18,23 @@ class Terraspace::CLI
|
|
|
17
18
|
def format(dir)
|
|
18
19
|
Runner.new(dir).format!
|
|
19
20
|
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def dirs
|
|
24
|
+
if @mod_name
|
|
25
|
+
type_dirs.select { |p| p.include?(@mod_name) }
|
|
26
|
+
else
|
|
27
|
+
type_dirs
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def type_dirs
|
|
32
|
+
type = @options[:type]
|
|
33
|
+
if type
|
|
34
|
+
app_source_dirs.select { |p| p.include?("/#{type.pluralize}/") }
|
|
35
|
+
else
|
|
36
|
+
app_source_dirs
|
|
37
|
+
end
|
|
38
|
+
end
|
|
20
39
|
end
|
|
21
40
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
## Example
|
|
2
2
|
|
|
3
|
+
Format all source files.
|
|
4
|
+
|
|
3
5
|
$ terraspace fmt
|
|
4
6
|
Formating terraform files
|
|
5
7
|
app/modules/example
|
|
@@ -7,4 +9,14 @@
|
|
|
7
9
|
outputs.tf
|
|
8
10
|
variables.tf
|
|
9
11
|
app/stacks/demo
|
|
10
|
-
main.tf
|
|
12
|
+
main.tf
|
|
13
|
+
|
|
14
|
+
Format specific module or stack.
|
|
15
|
+
|
|
16
|
+
$ terraspace fmt stack1
|
|
17
|
+
$ terraspace fmt module1
|
|
18
|
+
|
|
19
|
+
Format scoping to module or stack types. In case there's a module and stack with the same name.
|
|
20
|
+
|
|
21
|
+
$ terraspace fmt example -t module
|
|
22
|
+
$ terraspace fmt demo -t stacke
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
module Terraspace::Compiler::Strategy
|
|
2
2
|
class Mod < AbstractBase
|
|
3
3
|
def run
|
|
4
|
-
|
|
5
|
-
klass = strategy_class(ext)
|
|
4
|
+
klass = strategy_class(@src_path)
|
|
6
5
|
strategy = klass.new(@mod, @src_path) # IE: Terraspace::Compiler::Strategy::Mod::Rb.new
|
|
7
6
|
strategy.run
|
|
8
7
|
end
|
|
9
8
|
|
|
10
|
-
def strategy_class(
|
|
9
|
+
def strategy_class(path)
|
|
10
|
+
ext = File.extname(path).sub('.','')
|
|
11
11
|
return Mod::Pass if ext.empty? # infinite loop without this
|
|
12
|
+
return Mod::Pass if Terraspace.pass_file?(path)
|
|
12
13
|
"Terraspace::Compiler::Strategy::Mod::#{ext.camelize}".constantize rescue Mod::Pass
|
|
13
14
|
end
|
|
14
15
|
end
|
|
@@ -10,11 +10,17 @@ module Terraspace::Compiler
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def dest_path
|
|
13
|
-
name =
|
|
13
|
+
name = get_name
|
|
14
14
|
name = basename(name)
|
|
15
15
|
"#{dest_dir}/#{name}"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def get_name
|
|
19
|
+
return @dest_name if @dest_name
|
|
20
|
+
return @src_path if Terraspace.pass_file?(@src_path)
|
|
21
|
+
@src_path.sub('.rb','.tf.json')
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
def dest_dir
|
|
19
25
|
if @mod.is_a?(Terraspace::Mod::Remote)
|
|
20
26
|
File.dirname(@src_path) # for Mod::Remote src is dest
|
data/lib/terraspace/core.rb
CHANGED
|
@@ -51,5 +51,12 @@ module Terraspace
|
|
|
51
51
|
def logger=(v)
|
|
52
52
|
@@logger = v
|
|
53
53
|
end
|
|
54
|
+
|
|
55
|
+
def pass_file?(path)
|
|
56
|
+
pass_files = config.build.pass_files + config.build.default_pass_files
|
|
57
|
+
pass_files.uniq.detect do |i|
|
|
58
|
+
i.is_a?(Regexp) ? path =~ i : path.include?(i)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
54
61
|
end
|
|
55
62
|
end
|
|
@@ -2,6 +2,17 @@ module Terraspace::Hooks
|
|
|
2
2
|
class Runner
|
|
3
3
|
include Terraspace::Util
|
|
4
4
|
|
|
5
|
+
# exposing mod and hook so terraspace hooks have access to them via runner context. IE:
|
|
6
|
+
#
|
|
7
|
+
# class EnvExporter
|
|
8
|
+
# def call(runner)
|
|
9
|
+
# puts "runner.hook #{runner.hook}"
|
|
10
|
+
# end
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# Docs: http://terraspace.cloud/docs/config/hooks/ruby/#method-argument
|
|
14
|
+
#
|
|
15
|
+
attr_reader :mod, :hook
|
|
5
16
|
def initialize(mod, hook)
|
|
6
17
|
@mod, @hook = mod, hook
|
|
7
18
|
@execute = @hook["execute"]
|
|
@@ -12,12 +23,24 @@ module Terraspace::Hooks
|
|
|
12
23
|
when String
|
|
13
24
|
Terraspace::Shell.new(@mod, @execute, exit_on_fail: @hook["exit_on_fail"]).run
|
|
14
25
|
when -> (e) { e.respond_to?(:public_instance_methods) && e.public_instance_methods.include?(:call) }
|
|
15
|
-
@execute.new
|
|
26
|
+
executor = @execute.new
|
|
16
27
|
when -> (e) { e.respond_to?(:call) }
|
|
17
|
-
@execute
|
|
28
|
+
executor = @execute
|
|
18
29
|
else
|
|
19
30
|
logger.warn "WARN: execute option not set for hook: #{@hook.inspect}"
|
|
20
31
|
end
|
|
32
|
+
|
|
33
|
+
return unless executor
|
|
34
|
+
|
|
35
|
+
meth = executor.method(:call)
|
|
36
|
+
case meth.arity
|
|
37
|
+
when 0
|
|
38
|
+
executor.call # backwards compatibility
|
|
39
|
+
when 1
|
|
40
|
+
executor.call(self)
|
|
41
|
+
else
|
|
42
|
+
raise "The #{executor} call method definition has been more than 1 arguments and is not supported"
|
|
43
|
+
end
|
|
21
44
|
end
|
|
22
45
|
end
|
|
23
46
|
end
|
data/lib/terraspace/shell.rb
CHANGED
|
@@ -29,70 +29,98 @@ module Terraspace
|
|
|
29
29
|
|
|
30
30
|
def popen3(env)
|
|
31
31
|
Open3.popen3(env, @command, chdir: @mod.cache_dir) do |stdin, stdout, stderr, wait_thread|
|
|
32
|
-
|
|
33
|
-
while out = stdout.gets
|
|
34
|
-
terraform_to_stdout(out)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
while err = stderr.gets
|
|
38
|
-
@error ||= Error.new
|
|
39
|
-
@error.lines << err # aggregate all error lines
|
|
40
|
-
unless @error.known?
|
|
41
|
-
# Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
|
|
42
|
-
# suppress it so dont get a bunch of annoying "newlines"
|
|
43
|
-
next if err == "\e[31m\n" && @options[:suppress_error_color]
|
|
44
|
-
logger.error(err)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
32
|
+
handle_streams(stdin, stdout, stderr)
|
|
48
33
|
status = wait_thread.value.exitstatus
|
|
49
34
|
exit_status(status)
|
|
50
35
|
end
|
|
51
36
|
end
|
|
52
37
|
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
BLOCK_SIZE = Integer(ENV['TS_BUFFER_BLOCK_SIZE'] || 102400)
|
|
39
|
+
BUFFER_TIMEOUT = Integer(ENV['TS_BUFFER_TIMEOUT'] || 3600) # 3600s = 1h
|
|
40
|
+
def handle_streams(stdin, stdout, stderr)
|
|
41
|
+
files = [stdout, stderr]
|
|
42
|
+
# note: t=0 and t=nil means no timeout. See: https://bit.ly/2PURlCX
|
|
43
|
+
t = BUFFER_TIMEOUT.to_i unless BUFFER_TIMEOUT.nil?
|
|
44
|
+
Timeout::timeout(t) do
|
|
45
|
+
until all_eof?(files) do
|
|
46
|
+
ready = IO.select(files, nil, nil, 0.1)
|
|
47
|
+
next unless ready
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
readable = ready[0]
|
|
50
|
+
readable.each do |f|
|
|
51
|
+
buffer = f.read_nonblock(BLOCK_SIZE, exception: false)
|
|
52
|
+
next unless buffer
|
|
53
|
+
|
|
54
|
+
lines = buffer.split("\n")
|
|
55
|
+
lines.each do |line|
|
|
56
|
+
if f.fileno == stdout.fileno
|
|
57
|
+
handle_stdout(line)
|
|
58
|
+
handle_input(stdin, line)
|
|
59
|
+
else
|
|
60
|
+
handle_stderr(line)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
62
65
|
end
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
def handle_stderr(line)
|
|
69
|
+
@error ||= Error.new
|
|
70
|
+
@error.lines << line # aggregate all error lines
|
|
71
|
+
|
|
72
|
+
return if @error.known?
|
|
73
|
+
# Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
|
|
74
|
+
# suppress it so dont get a bunch of annoying "newlines"
|
|
75
|
+
return if line == "\e[31m\n" && @options[:suppress_error_color]
|
|
76
|
+
|
|
77
|
+
logger.error(line)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def all_eof?(files)
|
|
81
|
+
files.find { |f| !f.eof }.nil?
|
|
82
|
+
end
|
|
83
|
+
|
|
65
84
|
# Terraform doesnt seem to stream the line that prompts with "Enter a value:" when using Open3.popen3
|
|
66
85
|
# Hack around it by mimicking the "Enter a value:" prompt
|
|
67
86
|
#
|
|
68
87
|
# Note: system does stream the prompt but using Open3.popen3 so we can capture output to save to logs.
|
|
69
|
-
def
|
|
70
|
-
|
|
88
|
+
def handle_input(stdin, line)
|
|
89
|
+
# stdout doesnt seem to flush and show "Enter a value: " look for earlier output
|
|
71
90
|
patterns = [
|
|
72
91
|
"Only 'yes' will be accepted", # prompt for apply. can happen on apply
|
|
73
92
|
"\e[0m\e[1mvar.", # prompts for variable input. can happen on plan or apply. looking for bold marker also in case "var." shows up somewhere else
|
|
74
93
|
]
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
if patterns.any? { |pattern| line.include?(pattern) }
|
|
95
|
+
print "\n Enter a value: ".bright
|
|
96
|
+
stdin.write_nonblock($stdin.gets)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def exit_status(status)
|
|
101
|
+
return if status == 0
|
|
102
|
+
|
|
103
|
+
exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
|
|
104
|
+
if @error && @error.known?
|
|
105
|
+
raise @error.instance
|
|
106
|
+
elsif exit_on_fail
|
|
107
|
+
logger.error "Error running command: #{@command}".color(:red)
|
|
108
|
+
exit status
|
|
85
109
|
end
|
|
86
110
|
end
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
def handle_stdout(line)
|
|
113
|
+
prompted = line.include?('Enter a value')
|
|
114
|
+
@prompt_shown ||= prompted
|
|
115
|
+
return if @prompt_shown && prompted
|
|
116
|
+
|
|
117
|
+
# Terraspace logger has special stdout method so original terraform output
|
|
118
|
+
# can be piped to jq. IE:
|
|
119
|
+
# terraspace show demo --json | jq
|
|
92
120
|
if logger.respond_to?(:stdout) && !@options[:log_to_stderr]
|
|
93
|
-
logger.stdout(
|
|
121
|
+
logger.stdout(line)
|
|
94
122
|
else
|
|
95
|
-
logger.info(
|
|
123
|
+
logger.info(line)
|
|
96
124
|
end
|
|
97
125
|
end
|
|
98
126
|
end
|
|
@@ -3,7 +3,7 @@ require "tempfile"
|
|
|
3
3
|
module Terraspace::Terraform::Args
|
|
4
4
|
class Default
|
|
5
5
|
def initialize(mod, name, options={})
|
|
6
|
-
@mod, @name, @options = mod, name, options
|
|
6
|
+
@mod, @name, @options = mod, name.underscore, options
|
|
7
7
|
@quiet = @options[:quiet].nil? ? true : @options[:quiet]
|
|
8
8
|
end
|
|
9
9
|
|
|
@@ -11,14 +11,18 @@ module Terraspace::Terraform::Args
|
|
|
11
11
|
# https://terraspace.cloud/docs/ci-automation/
|
|
12
12
|
ENV['TF_IN_AUTOMATION'] = '1' if @options[:auto]
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
send(
|
|
14
|
+
args_meth = "#{@name}_args"
|
|
15
|
+
if respond_to?(args_meth)
|
|
16
|
+
send(args_meth)
|
|
17
17
|
else
|
|
18
18
|
[]
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def force_unlock_args
|
|
23
|
+
[" -force #{@options[:lock_id]}"]
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
def apply_args
|
|
23
27
|
args = auto_approve_arg
|
|
24
28
|
var_files = @options[:var_files]
|
data/lib/terraspace/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: terraspace
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tung Nguyen
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-05-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -508,6 +508,7 @@ files:
|
|
|
508
508
|
- lib/terraspace/cli/help/console.md
|
|
509
509
|
- lib/terraspace/cli/help/down.md
|
|
510
510
|
- lib/terraspace/cli/help/fmt.md
|
|
511
|
+
- lib/terraspace/cli/help/force_unlock.md
|
|
511
512
|
- lib/terraspace/cli/help/info.md
|
|
512
513
|
- lib/terraspace/cli/help/init.md
|
|
513
514
|
- lib/terraspace/cli/help/list.md
|