terraspace 0.3.6 → 0.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/templates/base/project/README.md +1 -1
- data/lib/terraspace/all/runner.rb +1 -0
- data/lib/terraspace/all/summary.rb +8 -1
- data/lib/terraspace/app.rb +9 -5
- data/lib/terraspace/builder.rb +10 -6
- data/lib/terraspace/cli.rb +10 -16
- data/lib/terraspace/cli/all.rb +6 -0
- data/lib/terraspace/cli/bundle.rb +2 -1
- data/lib/terraspace/cli/clean.rb +18 -6
- data/lib/terraspace/cli/clean/all.rb +18 -0
- data/lib/terraspace/cli/clean/base.rb +15 -0
- data/lib/terraspace/cli/clean/cache.rb +25 -0
- data/lib/terraspace/cli/{logs/tasks.rb → clean/logs.rb} +8 -9
- data/lib/terraspace/cli/help/clean/all.md +10 -0
- data/lib/terraspace/cli/help/clean/cache.md +12 -0
- data/lib/terraspace/cli/help/clean/logs.md +17 -0
- data/lib/terraspace/cli/help/{log.md → logs.md} +14 -14
- data/lib/terraspace/cli/init.rb +3 -7
- data/lib/terraspace/cli/logs.rb +105 -10
- data/lib/terraspace/cli/{log → logs}/concern.rb +1 -1
- data/lib/terraspace/cli/new/helper.rb +9 -2
- data/lib/terraspace/dependency/helper/output.rb +1 -1
- data/lib/terraspace/hooks/builder.rb +52 -0
- data/lib/terraspace/hooks/concern.rb +9 -0
- data/lib/terraspace/{terraform/hooks → hooks}/dsl.rb +3 -2
- data/lib/terraspace/hooks/runner.rb +23 -0
- data/lib/terraspace/mod.rb +11 -2
- data/lib/terraspace/plugin/summary/interface.rb +3 -1
- data/lib/terraspace/shell.rb +15 -10
- data/lib/terraspace/terraform/args/custom.rb +1 -1
- data/lib/terraspace/terraform/remote_state/output_proxy.rb +3 -3
- data/lib/terraspace/terraform/remote_state/{null_object.rb → unresolved.rb} +1 -1
- data/lib/terraspace/terraform/runner.rb +2 -7
- data/lib/terraspace/version.rb +1 -1
- data/spec/terraspace/{terraform/hooks → hooks}/builder_spec.rb +4 -5
- data/spec/terraspace/terraform/remote_state/output_proxy_spec.rb +3 -3
- data/terraspace.gemspec +1 -1
- metadata +20 -14
- data/lib/terraspace/cli/help/clean.md +0 -5
- data/lib/terraspace/cli/log.rb +0 -112
- data/lib/terraspace/terraform/hooks/builder.rb +0 -40
data/lib/terraspace/cli/init.rb
CHANGED
@@ -67,7 +67,7 @@ class Terraspace::CLI
|
|
67
67
|
mode = ENV['TS_INIT_MODE'] || Terraspace.config.init.mode
|
68
68
|
case mode.to_sym
|
69
69
|
when :auto
|
70
|
-
!
|
70
|
+
!already_init?
|
71
71
|
when :always
|
72
72
|
true
|
73
73
|
when :never
|
@@ -80,16 +80,12 @@ class Terraspace::CLI
|
|
80
80
|
# Traverse symlink dirs also: linux_amd64 is a symlink
|
81
81
|
# plugins/registry.terraform.io/hashicorp/google/3.39.0/linux_amd64/terraform-provider-google_v3.39.0_x5
|
82
82
|
#
|
83
|
-
|
84
|
-
# So init happens again during the second pass.
|
85
|
-
#
|
86
|
-
def already_initialized?
|
83
|
+
def already_init?
|
87
84
|
terraform = "#{@mod.cache_dir}/.terraform"
|
88
85
|
provider = Dir.glob("#{terraform}/**{,/*/**}/*").find do |path|
|
89
86
|
path.include?("terraform-provider-")
|
90
87
|
end
|
91
|
-
|
92
|
-
!!(provider && modules)
|
88
|
+
!!provider
|
93
89
|
end
|
94
90
|
end
|
95
91
|
end
|
data/lib/terraspace/cli/logs.rb
CHANGED
@@ -1,17 +1,112 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "eventmachine-tail"
|
3
|
+
|
1
4
|
class Terraspace::CLI
|
2
|
-
class Logs <
|
3
|
-
|
5
|
+
class Logs < Base
|
6
|
+
include Concern
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
super
|
10
|
+
@action, @stack = options[:action], options[:stack]
|
11
|
+
@action ||= '**'
|
12
|
+
@stack ||= '*'
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
check_logs!
|
17
|
+
if @options[:follow]
|
18
|
+
follow_logs
|
19
|
+
else
|
20
|
+
all_log_paths.each { |path| show_log(path) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def follow_logs
|
25
|
+
glob_path = "#{Terraspace.log_root}/#{@action}/#{@stack}.log"
|
26
|
+
Dir.glob(glob_path).each do |path|
|
27
|
+
puts "Following #{pretty(path)}".color(:purple)
|
28
|
+
end
|
29
|
+
EventMachine.run do
|
30
|
+
interval = Integer(ENV['TS_LOG_GLOB_INTERNAL'] || 1)
|
31
|
+
EventMachine::FileGlobWatchTail.new(glob_path, nil, interval) do |filetail, line|
|
32
|
+
puts line # always show timestamp in follow mode
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def show_log(path)
|
38
|
+
report_log(path)
|
39
|
+
lines = readlines(path)
|
40
|
+
lines = apply_limit(lines)
|
41
|
+
lines.each do |line|
|
42
|
+
puts format(line)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def report_log(path)
|
47
|
+
pretty_path = pretty(path)
|
48
|
+
if File.exist?(path)
|
49
|
+
puts "Showing: #{pretty_path}".color(:purple)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def format(line)
|
54
|
+
if timestamps
|
55
|
+
line
|
56
|
+
else
|
57
|
+
line.sub(/.*\]: /,'')
|
58
|
+
end
|
59
|
+
end
|
4
60
|
|
5
|
-
|
6
|
-
|
7
|
-
def truncate
|
8
|
-
Tasks.new(options).truncate
|
61
|
+
def all_log_paths
|
62
|
+
Dir.glob("#{Terraspace.log_root}/#{@action}/#{@stack}.log")
|
9
63
|
end
|
10
64
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
65
|
+
def check_logs!
|
66
|
+
return unless all_log_paths.empty?
|
67
|
+
puts "WARN: No logs found".color(:yellow)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Only need to check if both action and stack are provided. Otherwise the Dir.globs are used to discover the files
|
71
|
+
def check_log!
|
72
|
+
return unless single_log?
|
73
|
+
path = "#{Terraspace.log_root}/#{@action}/#{@stack}.log"
|
74
|
+
return if File.exist?(path)
|
75
|
+
puts "ERROR: Log file was not found: #{pretty(path)}".color(:red)
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
|
79
|
+
def single_log?
|
80
|
+
@action != '**' && @stack != '*'
|
81
|
+
end
|
82
|
+
|
83
|
+
def apply_limit(lines)
|
84
|
+
return lines if all
|
85
|
+
left = limit * -1
|
86
|
+
lines[left..-1] || []
|
87
|
+
end
|
88
|
+
|
89
|
+
def all
|
90
|
+
if single_log?
|
91
|
+
@options[:all].nil? ? true : @options[:all]
|
92
|
+
else # multiple
|
93
|
+
@options[:all].nil? ? false : @options[:all]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def limit
|
98
|
+
@options[:limit].nil? ? 10 : @options[:limit]
|
99
|
+
end
|
100
|
+
|
101
|
+
def timestamps
|
102
|
+
if single_log?
|
103
|
+
@options[:timestamps].nil? ? false : @options[:timestamps]
|
104
|
+
else
|
105
|
+
@options[:timestamps].nil? ? true : @options[:timestamps]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
def pretty(path)
|
109
|
+
Terraspace::Util.pretty_path(path)
|
15
110
|
end
|
16
111
|
end
|
17
112
|
end
|
@@ -6,10 +6,17 @@ class Terraspace::CLI::New
|
|
6
6
|
def build_gemfile(*list)
|
7
7
|
lines = []
|
8
8
|
list.each do |name|
|
9
|
-
|
10
|
-
lines << line
|
9
|
+
lines << gem_line(name)
|
11
10
|
end
|
12
11
|
lines.join("\n")
|
13
12
|
end
|
13
|
+
|
14
|
+
def gem_line(name)
|
15
|
+
if name == "terraspace"
|
16
|
+
%Q|gem "#{name}", '~> #{Terraspace::VERSION}'|
|
17
|
+
else
|
18
|
+
%Q|gem "#{name}"|
|
19
|
+
end
|
20
|
+
end
|
14
21
|
end
|
15
22
|
end
|
@@ -4,7 +4,7 @@ module Terraspace::Dependency::Helper
|
|
4
4
|
if @mod.resolved # dependencies have been resolved
|
5
5
|
Terraspace::Terraform::RemoteState::Fetcher.new(@mod, @identifier, @options).output # Returns OutputProxy which defaults to json
|
6
6
|
else
|
7
|
-
Terraspace::Terraform::RemoteState::Marker::Output.new(@mod, @identifier, @options).build # Returns OutputProxy
|
7
|
+
Terraspace::Terraform::RemoteState::Marker::Output.new(@mod, @identifier, @options).build # Returns OutputProxy => Unresolved
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Terraspace::Hooks
|
2
|
+
class Builder
|
3
|
+
extend Memoist
|
4
|
+
include Dsl
|
5
|
+
include DslEvaluator
|
6
|
+
include Terraspace::Util
|
7
|
+
|
8
|
+
# IE: dsl_file: config/hooks/terraform.rb
|
9
|
+
attr_accessor :name
|
10
|
+
def initialize(mod, dsl_file, name)
|
11
|
+
@mod, @dsl_file, @name = mod, dsl_file, name
|
12
|
+
@hooks = {before: {}, after: {}}
|
13
|
+
end
|
14
|
+
|
15
|
+
def build
|
16
|
+
return @hooks unless File.exist?(@dsl_file)
|
17
|
+
evaluate_file(@dsl_file)
|
18
|
+
@hooks.deep_stringify_keys!
|
19
|
+
end
|
20
|
+
memoize :build
|
21
|
+
|
22
|
+
def run_hooks
|
23
|
+
build
|
24
|
+
run_each_hook("before")
|
25
|
+
out = yield if block_given?
|
26
|
+
run_each_hook("after")
|
27
|
+
out
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_each_hook(type)
|
31
|
+
hooks = @hooks.dig(type, @name) || []
|
32
|
+
hooks.each do |hook|
|
33
|
+
run_hook(type, hook)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run_hook(type, hook)
|
38
|
+
return unless run?(hook)
|
39
|
+
|
40
|
+
command = File.basename(@dsl_file).sub('.rb','') # IE: kubes, kubectl, docker
|
41
|
+
id = "#{command} #{type} #{@name}"
|
42
|
+
label = " label: #{hook["label"]}" if hook["label"]
|
43
|
+
logger.info "Running #{id} hook.#{label}"
|
44
|
+
logger.debug "Hook options: #{hook}"
|
45
|
+
Runner.new(@mod, hook).run
|
46
|
+
end
|
47
|
+
|
48
|
+
def run?(hook)
|
49
|
+
!!hook["execute"]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Terraspace::
|
1
|
+
module Terraspace::Hooks
|
2
2
|
module Dsl
|
3
3
|
def before(*commands, **props)
|
4
4
|
commands.each do |name|
|
@@ -13,7 +13,8 @@ module Terraspace::Terraform::Hooks
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def each_hook(type, name, props={})
|
16
|
-
@hooks[type][name]
|
16
|
+
@hooks[type][name] ||= []
|
17
|
+
@hooks[type][name] << props
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Terraspace::Hooks
|
2
|
+
class Runner
|
3
|
+
include Terraspace::Util
|
4
|
+
|
5
|
+
def initialize(mod, hook)
|
6
|
+
@mod, @hook = mod, hook
|
7
|
+
@execute = @hook["execute"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
case @execute
|
12
|
+
when String
|
13
|
+
Terraspace::Shell.new(@mod, @execute, exit_on_fail: @hook["exit_on_fail"]).run
|
14
|
+
when -> (e) { e.respond_to?(:public_instance_methods) && e.public_instance_methods.include?(:call) }
|
15
|
+
@execute.new.call
|
16
|
+
when -> (e) { e.respond_to?(:call) }
|
17
|
+
@execute.call
|
18
|
+
else
|
19
|
+
logger.warn "WARN: execute option not set for hook: #{@hook.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/terraspace/mod.rb
CHANGED
@@ -34,8 +34,17 @@ module Terraspace
|
|
34
34
|
Terraspace.check_project!
|
35
35
|
return if root
|
36
36
|
|
37
|
-
pretty_paths = paths.map { |p| Terraspace::Util.pretty_path(p) }
|
38
|
-
logger.error
|
37
|
+
pretty_paths = paths.map { |p| Terraspace::Util.pretty_path(p) }.join(", ")
|
38
|
+
logger.error <<~EOL
|
39
|
+
ERROR: Unable to find #{@name.color(:green)}. Searched paths:
|
40
|
+
|
41
|
+
#{pretty_paths}
|
42
|
+
|
43
|
+
To see available stacks, try running:
|
44
|
+
|
45
|
+
terraspace list
|
46
|
+
|
47
|
+
EOL
|
39
48
|
ENV['TS_TEST'] ? raise : exit(1)
|
40
49
|
end
|
41
50
|
|
@@ -64,7 +64,9 @@ module Terraspace::Plugin::Summary
|
|
64
64
|
|
65
65
|
def show_each(path)
|
66
66
|
data = JSON.load(IO.read(path))
|
67
|
+
return unless data # edge case: blank file
|
67
68
|
resources = data['resources']
|
69
|
+
return unless resources
|
68
70
|
remove_statefile(path) if resources && resources.size == 0 && !ENV['TS_KEEP_EMPTY_STATEFILES']
|
69
71
|
return unless resources && resources.size > 0
|
70
72
|
|
@@ -75,7 +77,7 @@ module Terraspace::Plugin::Summary
|
|
75
77
|
identifier = r['instances'].map do |i|
|
76
78
|
i['attributes']['name'] || i['attributes']['id']
|
77
79
|
end.join(',')
|
78
|
-
return
|
80
|
+
return unless @options[:details]
|
79
81
|
logger.info " #{r['type']} #{r['name']}: #{identifier}"
|
80
82
|
end
|
81
83
|
end
|
data/lib/terraspace/shell.rb
CHANGED
@@ -6,7 +6,8 @@ module Terraspace
|
|
6
6
|
|
7
7
|
def initialize(mod, command, options={})
|
8
8
|
@mod, @command, @options = mod, command, options
|
9
|
-
|
9
|
+
# error_messages holds aggregation of all error lines
|
10
|
+
@known_error, @error_messages = nil, ''
|
10
11
|
end
|
11
12
|
|
12
13
|
# requires @mod to be set
|
@@ -32,10 +33,9 @@ module Terraspace
|
|
32
33
|
Open3.popen3(env, @command, chdir: @mod.cache_dir) do |stdin, stdout, stderr, wait_thread|
|
33
34
|
mimic_terraform_input(stdin, stdout)
|
34
35
|
while err = stderr.gets
|
35
|
-
@
|
36
|
-
|
37
|
-
|
38
|
-
else
|
36
|
+
@error_messages << err # aggregate all error lines
|
37
|
+
@known_error ||= known_error_type(err)
|
38
|
+
unless @known_error
|
39
39
|
# Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
|
40
40
|
# suppress it so dont get a bunch of annoying "newlines"
|
41
41
|
next if err == "\e[31m\n" && @options[:suppress_error_color]
|
@@ -49,8 +49,8 @@ module Terraspace
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def known_error_type(err)
|
52
|
-
if
|
53
|
-
:
|
52
|
+
if reinit_required?(err)
|
53
|
+
:reinit_required
|
54
54
|
elsif bucket_not_found?(err)
|
55
55
|
:bucket_not_found
|
56
56
|
end
|
@@ -61,7 +61,12 @@ module Terraspace
|
|
61
61
|
err.include?("Failed to get existing workspaces")
|
62
62
|
end
|
63
63
|
|
64
|
-
def
|
64
|
+
def reinit_required?(err)
|
65
|
+
# Example error: https://gist.github.com/tongueroo/f7e0a44b64f0a2e533089b18f331c21e
|
66
|
+
squeezed = @error_messages.gsub("\n", ' ').squeeze(' ') # remove double whitespaces and newlines
|
67
|
+
general_check = squeezed.include?("terraform init") && squeezed.include?("Error:")
|
68
|
+
|
69
|
+
general_check ||
|
65
70
|
err.include?("reinitialization required") ||
|
66
71
|
err.include?("terraform init") ||
|
67
72
|
err.include?("require reinitialization")
|
@@ -71,9 +76,9 @@ module Terraspace
|
|
71
76
|
return if status == 0
|
72
77
|
|
73
78
|
exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
|
74
|
-
if @
|
79
|
+
if @known_error == :reinit_required
|
75
80
|
raise InitRequiredError.new(@error_messages)
|
76
|
-
elsif @
|
81
|
+
elsif @known_error == :bucket_not_found
|
77
82
|
raise BucketNotFoundError.new(@error_messages)
|
78
83
|
elsif exit_on_fail
|
79
84
|
logger.error "Error running command: #{@command}".color(:red)
|
@@ -11,18 +11,18 @@ module Terraspace::Terraform::RemoteState
|
|
11
11
|
# Should always return a String
|
12
12
|
def to_s
|
13
13
|
if @mod.resolved
|
14
|
-
# Dont use
|
14
|
+
# Dont use Unresolved wrapper because Integer get changed to Strings.
|
15
15
|
# Want raw value to be used for the to_json call
|
16
16
|
value = @raw.nil? ? mock_or_error : @raw
|
17
17
|
value.to_json
|
18
18
|
else
|
19
|
-
|
19
|
+
Unresolved.new
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
def to_ruby
|
24
24
|
data = @raw.nil? ? mock_or_error : @raw
|
25
|
-
@mod.resolved ? data :
|
25
|
+
@mod.resolved ? data : Unresolved.new
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Terraspace::Terraform
|
2
2
|
class Runner < Terraspace::CLI::Base
|
3
3
|
extend Memoist
|
4
|
+
include Terraspace::Hooks::Concern
|
4
5
|
include Terraspace::Util
|
5
6
|
|
6
7
|
attr_reader :name
|
@@ -21,7 +22,7 @@ module Terraspace::Terraform
|
|
21
22
|
|
22
23
|
params = args.flatten.join(' ')
|
23
24
|
command = "terraform #{name} #{params}"
|
24
|
-
run_hooks(name) do
|
25
|
+
run_hooks("terraform.rb", name) do
|
25
26
|
Terraspace::Shell.new(@mod, command, @options.merge(env: custom.env_vars)).run
|
26
27
|
end
|
27
28
|
rescue Terraspace::InitRequiredError => e
|
@@ -53,12 +54,6 @@ module Terraspace::Terraform
|
|
53
54
|
@options[:quiet] ? logger.debug(msg) : logger.info(msg)
|
54
55
|
end
|
55
56
|
|
56
|
-
def run_hooks(name, &block)
|
57
|
-
hooks = Hooks::Builder.new(@mod, name)
|
58
|
-
hooks.build # build hooks
|
59
|
-
hooks.run_hooks(&block)
|
60
|
-
end
|
61
|
-
|
62
57
|
def args
|
63
58
|
# base at end in case of redirection. IE: terraform output > /path
|
64
59
|
custom.args + custom.var_files + default.args
|