terraspace 0.2.4 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.cody/aws/bin/build.sh +2 -0
- data/.cody/azurerm/bin/build.sh +2 -0
- data/.cody/google/bin/build.sh +2 -0
- data/CHANGELOG.md +39 -1
- data/README.md +14 -1
- data/lib/templates/base/git_hook/hook.sh +1 -1
- data/lib/templates/base/project/.gitignore +1 -0
- data/lib/templates/base/project/README.md +17 -0
- data/lib/terraspace.rb +6 -0
- data/lib/terraspace/all/base.rb +8 -0
- data/lib/terraspace/all/grapher.rb +129 -0
- data/lib/terraspace/all/preview.rb +43 -0
- data/lib/terraspace/all/runner.rb +169 -0
- data/lib/terraspace/all/summary.rb +119 -0
- data/lib/terraspace/app.rb +31 -9
- data/lib/terraspace/booter.rb +9 -0
- data/lib/terraspace/builder.rb +59 -22
- data/lib/terraspace/cli.rb +60 -33
- data/lib/terraspace/cli/all.rb +63 -0
- data/lib/terraspace/cli/build/placeholder.rb +2 -5
- data/lib/terraspace/cli/bundle.rb +1 -1
- data/lib/terraspace/cli/check_setup.rb +5 -0
- data/lib/terraspace/cli/cloud.rb +16 -6
- data/lib/terraspace/cli/cloud/runs.rb +22 -0
- data/lib/terraspace/cli/commander.rb +1 -8
- data/lib/terraspace/cli/down.rb +20 -0
- data/lib/terraspace/cli/help/all/down.md +32 -0
- data/lib/terraspace/cli/help/all/graph.md +21 -0
- data/lib/terraspace/cli/help/all/output.md +22 -0
- data/lib/terraspace/cli/help/all/plan.md +25 -0
- data/lib/terraspace/cli/help/all/providers.md +21 -0
- data/lib/terraspace/cli/help/all/refresh.md +17 -0
- data/lib/terraspace/cli/help/all/show.md +21 -0
- data/lib/terraspace/cli/help/all/up.md +27 -0
- data/lib/terraspace/cli/help/all/validate.md +21 -0
- data/lib/terraspace/cli/help/build.md +6 -0
- data/lib/terraspace/cli/help/bundle.md +9 -5
- data/lib/terraspace/cli/help/check_setup.md +9 -0
- data/lib/terraspace/cli/help/clean.md +5 -0
- data/lib/terraspace/cli/help/cloud/destroy.md +16 -0
- data/lib/terraspace/cli/help/cloud/list.md +7 -0
- data/lib/terraspace/cli/help/cloud/runs/list.md +36 -0
- data/lib/terraspace/cli/help/cloud/runs/prune.md +25 -0
- data/lib/terraspace/cli/help/cloud/sync.md +43 -0
- data/lib/terraspace/cli/help/console.md +8 -0
- data/lib/terraspace/cli/help/down.md +26 -0
- data/lib/terraspace/cli/help/info.md +43 -0
- data/lib/terraspace/cli/help/init.md +37 -0
- data/lib/terraspace/cli/help/list.md +20 -0
- data/lib/terraspace/cli/help/log.md +48 -0
- data/lib/terraspace/cli/help/logs/remove.md +5 -0
- data/lib/terraspace/cli/help/logs/truncate.md +5 -0
- data/lib/terraspace/cli/help/new/bootstrap_test.md +8 -0
- data/lib/terraspace/cli/help/new/example.md +8 -0
- data/lib/terraspace/cli/help/new/git_hook.md +6 -0
- data/lib/terraspace/cli/help/new/module.md +9 -0
- data/lib/terraspace/cli/help/new/module_test.md +12 -0
- data/lib/terraspace/cli/help/new/plugin.md +49 -0
- data/lib/terraspace/cli/help/new/project.md +40 -0
- data/lib/terraspace/cli/help/new/project_test.md +8 -0
- data/lib/terraspace/cli/help/new/shim.md +21 -0
- data/lib/terraspace/cli/help/new/stack.md +9 -0
- data/lib/terraspace/cli/help/output.md +6 -0
- data/lib/terraspace/cli/help/plan.md +29 -0
- data/lib/terraspace/cli/help/providers.md +18 -0
- data/lib/terraspace/cli/help/refresh.md +11 -0
- data/lib/terraspace/cli/help/seed.md +7 -0
- data/lib/terraspace/cli/help/show.md +36 -0
- data/lib/terraspace/cli/help/summary.md +11 -0
- data/lib/terraspace/cli/help/test.md +35 -0
- data/lib/terraspace/cli/help/up.md +30 -0
- data/lib/terraspace/cli/help/validate.md +9 -0
- data/lib/terraspace/cli/info.rb +4 -16
- data/lib/terraspace/cli/init.rb +35 -7
- data/lib/terraspace/cli/list.rb +14 -1
- data/lib/terraspace/cli/log.rb +112 -0
- data/lib/terraspace/cli/log/concern.rb +24 -0
- data/lib/terraspace/cli/logs.rb +15 -0
- data/lib/terraspace/cli/logs/tasks.rb +32 -0
- data/lib/terraspace/cli/new.rb +18 -18
- data/lib/terraspace/cli/new/git_hook.rb +5 -2
- data/lib/terraspace/cli/new/project.rb +1 -1
- data/lib/terraspace/cli/summary.rb +2 -2
- data/lib/terraspace/cli/tfc_concern.rb +14 -0
- data/lib/terraspace/cli/up.rb +32 -0
- data/lib/terraspace/command.rb +1 -1
- data/lib/terraspace/compiler/backend.rb +10 -0
- data/lib/terraspace/compiler/builder.rb +5 -4
- data/lib/terraspace/compiler/cleaner.rb +1 -1
- data/lib/terraspace/compiler/cleaner/backend_change.rb +21 -7
- data/lib/terraspace/compiler/commands_concern.rb +18 -0
- data/lib/terraspace/compiler/dirs_concern.rb +47 -0
- data/lib/terraspace/compiler/dsl/syntax/helpers/common.rb +26 -1
- data/lib/terraspace/core.rb +11 -2
- data/lib/terraspace/dependency/graph.rb +140 -0
- data/lib/terraspace/dependency/node.rb +38 -0
- data/lib/terraspace/dependency/registry.rb +11 -0
- data/lib/terraspace/logger.rb +6 -18
- data/lib/terraspace/logger/formatter.rb +13 -0
- data/lib/terraspace/mod.rb +7 -1
- data/lib/terraspace/plugin/summary/interface.rb +1 -1
- data/lib/terraspace/seeder/where.rb +6 -2
- data/lib/terraspace/shell.rb +107 -0
- data/lib/terraspace/terraform/api.rb +7 -45
- data/lib/terraspace/terraform/api/base.rb +7 -0
- data/lib/terraspace/terraform/api/client.rb +23 -3
- data/lib/terraspace/terraform/api/http.rb +14 -34
- data/lib/terraspace/terraform/api/http/concern.rb +10 -0
- data/lib/terraspace/terraform/api/runs.rb +28 -0
- data/lib/terraspace/terraform/api/token.rb +65 -0
- data/lib/terraspace/terraform/api/var.rb +20 -6
- data/lib/terraspace/terraform/api/vars.rb +2 -1
- data/lib/terraspace/terraform/api/workspace.rb +98 -0
- data/lib/terraspace/terraform/args/default.rb +48 -21
- data/lib/terraspace/terraform/cloud/runs.rb +13 -0
- data/lib/terraspace/terraform/cloud/runs/base.rb +33 -0
- data/lib/terraspace/terraform/cloud/runs/item_presenter.rb +37 -0
- data/lib/terraspace/terraform/cloud/runs/lister.rb +20 -0
- data/lib/terraspace/terraform/cloud/runs/pruner.rb +109 -0
- data/lib/terraspace/terraform/cloud/sync.rb +41 -0
- data/lib/terraspace/terraform/cloud/syncer.rb +52 -0
- data/lib/terraspace/terraform/cloud/workspace.rb +10 -30
- data/lib/terraspace/terraform/hooks/builder.rb +1 -1
- data/lib/terraspace/terraform/remote_state/fetcher.rb +143 -0
- data/lib/terraspace/terraform/remote_state/marker/output.rb +39 -0
- data/lib/terraspace/terraform/remote_state/marker/pretty_tracer.rb +37 -0
- data/lib/terraspace/terraform/remote_state/output_proxy.rb +29 -0
- data/lib/terraspace/terraform/runner.rb +24 -14
- data/lib/terraspace/util.rb +1 -5
- data/lib/terraspace/util/pretty.rb +18 -0
- data/lib/terraspace/version.rb +1 -1
- data/spec/fixtures/fetcher/c1.json +37 -0
- data/spec/fixtures/parser/cache_dirs/all/01-test.auto.tfvars +5 -0
- data/spec/fixtures/parser/cache_dirs/depends_on/01-test.auto.tfvars +2 -0
- data/spec/fixtures/parser/cache_dirs/output/01-test.auto.tfvars +2 -0
- data/spec/fixtures/summary/down.log +12 -0
- data/spec/fixtures/summary/output.log +5 -0
- data/spec/fixtures/summary/plan/error.log +20 -0
- data/spec/fixtures/summary/plan/success.log +17 -0
- data/spec/fixtures/summary/show.log +22 -0
- data/spec/fixtures/summary/up/error.log +13 -0
- data/spec/fixtures/summary/up/success.log +63 -0
- data/spec/fixtures/summary/validate/error.log +13 -0
- data/spec/fixtures/summary/validate/success.log +5 -0
- data/spec/terraspace/all/grapher_spec.rb +38 -0
- data/spec/terraspace/all/runner_spec.rb +48 -0
- data/spec/terraspace/all/summary_spec.rb +93 -0
- data/spec/terraspace/dependency/graph_spec.rb +162 -0
- data/spec/terraspace/seeder_spec.rb +0 -1
- data/spec/terraspace/terraform/remote_state/fetcher_spec.rb +52 -0
- data/terraspace.gemspec +5 -1
- metadata +179 -6
- data/lib/terraspace/cli/help/update.md +0 -5
- data/lib/terraspace/terraform/cloud.rb +0 -25
- data/lib/terraspace/util/sh.rb +0 -19
@@ -0,0 +1,33 @@
|
|
1
|
+
class Terraspace::Terraform::Cloud::Runs
|
2
|
+
class Base
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Util::Logging
|
5
|
+
include Terraspace::Util::Sure
|
6
|
+
include Terraspace::Terraform::Api::Client
|
7
|
+
|
8
|
+
# Api::Client requires @mod to be set
|
9
|
+
def initialize(mod, options={})
|
10
|
+
@mod, @options = mod, options
|
11
|
+
end
|
12
|
+
|
13
|
+
def runs
|
14
|
+
runs = api.runs.list
|
15
|
+
runs.select! do |item|
|
16
|
+
@options[:status].nil? ||
|
17
|
+
@options[:status].include?("all") ||
|
18
|
+
@options[:status].include?(item['attributes']['status'])
|
19
|
+
end
|
20
|
+
runs
|
21
|
+
end
|
22
|
+
memoize :runs
|
23
|
+
|
24
|
+
def build_project
|
25
|
+
Terraspace::Builder.new(@options).run
|
26
|
+
|
27
|
+
unless remote && remote['organization']
|
28
|
+
logger.info "ERROR: There was no organization found. Are you sure you configured backend.tf with it?".color(:red)
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Terraspace::Terraform::Cloud::Runs
|
2
|
+
class ItemPresenter
|
3
|
+
attr_reader :id
|
4
|
+
def initialize(raw)
|
5
|
+
@raw = raw # raw item
|
6
|
+
@id = raw['id']
|
7
|
+
@attrs = raw['attributes']
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args, &block)
|
11
|
+
attrs = @attrs.transform_keys { |k| k.gsub('-','_').to_sym }
|
12
|
+
if attrs.key?(name)
|
13
|
+
attrs[name]
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def message
|
20
|
+
max = 25
|
21
|
+
message = @attrs['message']
|
22
|
+
if message.size >= max
|
23
|
+
message[0..max] + "..."
|
24
|
+
else
|
25
|
+
message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def created_at
|
30
|
+
pretty_time(@attrs['created-at'])
|
31
|
+
end
|
32
|
+
|
33
|
+
def pretty_time(text)
|
34
|
+
text.sub(/\..*/,'')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Terraspace::Terraform::Cloud::Runs
|
2
|
+
class Lister < Base
|
3
|
+
def run
|
4
|
+
build_project
|
5
|
+
if runs.empty?
|
6
|
+
logger.info "No runs found"
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
presenter = CliFormat::Presenter.new(@options)
|
11
|
+
presenter.header = ["Id", "Status", "Message", "Created At"]
|
12
|
+
runs.each do |item|
|
13
|
+
p = ItemPresenter.new(item)
|
14
|
+
row = [p.id, p.status, p.message, p.created_at]
|
15
|
+
presenter.rows << row
|
16
|
+
end
|
17
|
+
presenter.show
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class Terraspace::Terraform::Cloud::Runs
|
2
|
+
class Pruner < Base
|
3
|
+
include Terraspace::Terraform::Api::Client
|
4
|
+
|
5
|
+
# @mod required for Api::Client
|
6
|
+
def initialize(mod, options={})
|
7
|
+
super
|
8
|
+
@queue, @kept, @needs_pruning = [], nil, false
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
build_project
|
13
|
+
build_queue
|
14
|
+
are_you_sure?
|
15
|
+
process_queue
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def build_queue
|
20
|
+
runs.each do |item|
|
21
|
+
next unless actionable?(item)
|
22
|
+
unless @kept
|
23
|
+
@kept = item
|
24
|
+
next
|
25
|
+
end
|
26
|
+
queue(item)
|
27
|
+
@needs_pruning = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def are_you_sure?
|
32
|
+
unless @needs_pruning
|
33
|
+
logger.info "Nothing to prune"
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
keeping = item_message(@kept)
|
38
|
+
items = @queue.map { |i| item_message(i) }.join("\n")
|
39
|
+
message =<<~EOL
|
40
|
+
Will keep:
|
41
|
+
|
42
|
+
#{keeping}
|
43
|
+
|
44
|
+
Will prune:
|
45
|
+
|
46
|
+
#{items}
|
47
|
+
|
48
|
+
Are you sure?
|
49
|
+
EOL
|
50
|
+
sure?(message.chop)
|
51
|
+
end
|
52
|
+
|
53
|
+
def item_message(item)
|
54
|
+
p = ItemPresenter.new(item)
|
55
|
+
" #{p.id} #{p.status} #{p.message} #{p.created_at}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_queue
|
59
|
+
@queue.each do |item|
|
60
|
+
process(item)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def process(item)
|
65
|
+
id = item['id']
|
66
|
+
action = discardable?(item) ? "Discarded" : "Cancelled"
|
67
|
+
p = ItemPresenter.new(item)
|
68
|
+
msg = "#{action} #{p.id} #{p.message}" # note id is named run-xxx
|
69
|
+
logger.info("NOOP: #{msg}") && return if @options[:noop]
|
70
|
+
|
71
|
+
if discardable?(item)
|
72
|
+
api.runs.discard(id)
|
73
|
+
elsif cancelable?(item)
|
74
|
+
api.runs.cancel(id)
|
75
|
+
end
|
76
|
+
logger.info msg
|
77
|
+
end
|
78
|
+
|
79
|
+
def queue(item)
|
80
|
+
if discardable?(item)
|
81
|
+
@queue << item
|
82
|
+
elsif cancelable?(item)
|
83
|
+
@queue << item
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Docs seem to be off: https://www.terraform.io/docs/cloud/api/run.html#apply-a-run
|
88
|
+
#
|
89
|
+
# This includes runs in the "pending," "needs confirmation," "policy checked," and "policy override" states.
|
90
|
+
#
|
91
|
+
# Cant really discard a "pending" status, but can discard a "planned" status.
|
92
|
+
#
|
93
|
+
def actionable?(item)
|
94
|
+
discardable?(item) || cancelable?(item)
|
95
|
+
end
|
96
|
+
|
97
|
+
def discardable?(item)
|
98
|
+
%w[planned].include?(status(item))
|
99
|
+
end
|
100
|
+
|
101
|
+
def cancelable?(item)
|
102
|
+
%w[pending].include?(status(item))
|
103
|
+
end
|
104
|
+
|
105
|
+
def status(item)
|
106
|
+
item['attributes']['status']
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Terraspace::Terraform::Cloud
|
2
|
+
class Sync < Terraspace::CLI::Base
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Terraform::Api::Client
|
5
|
+
|
6
|
+
# Note about why workspace.create is called:
|
7
|
+
#
|
8
|
+
# CLI::Init#run
|
9
|
+
# init => runs `terraform init`
|
10
|
+
# build_remote_dependencies
|
11
|
+
# sync_cloud => leads to create_workspace
|
12
|
+
#
|
13
|
+
# The `terraform init` will auto-create the TFC workspace
|
14
|
+
# If there is a .terraform folder the config.init.mode == "auto" though,
|
15
|
+
# then the workspace won't be created.
|
16
|
+
# So we check and create the workspace if necessary.
|
17
|
+
def run
|
18
|
+
# Note: workspace still gets created by `terraform init` However, variables wont be sync if returns early
|
19
|
+
return unless Terraspace.config.cloud.auto_sync || @options[:override_auto_sync]
|
20
|
+
return unless workspaces_backend?
|
21
|
+
logger.info "Syncing to Terraform Cloud: #{@mod.name} => #{workspace_name}"
|
22
|
+
@api = Terraspace::Terraform::Api.new(@mod, remote)
|
23
|
+
workspace.create_or_update
|
24
|
+
workspace.set_working_dir
|
25
|
+
workspace.set_env_vars
|
26
|
+
end
|
27
|
+
|
28
|
+
def workspace
|
29
|
+
@api.workspace
|
30
|
+
end
|
31
|
+
|
32
|
+
def workspaces_backend?
|
33
|
+
remote && remote['workspaces']
|
34
|
+
end
|
35
|
+
|
36
|
+
# already memoized in Api::Client
|
37
|
+
def backend
|
38
|
+
Terraspace::Compiler::Backend::Parser.new(@mod).result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Terraspace::Terraform::Cloud
|
2
|
+
class Syncer < Terraspace::CLI::Base
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Compiler::DirsConcern
|
5
|
+
include Terraspace::Util::Sure
|
6
|
+
|
7
|
+
def run
|
8
|
+
are_you_sure?
|
9
|
+
mods.each do |mod|
|
10
|
+
run_sync(mod)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def mods
|
15
|
+
stacks = @options[:stacks]
|
16
|
+
stacks.empty? ? stack_names : stacks
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_sync(mod)
|
20
|
+
sync(mod).run
|
21
|
+
end
|
22
|
+
|
23
|
+
def sync(mod)
|
24
|
+
Sync.new(@options.merge(mod: mod))
|
25
|
+
end
|
26
|
+
memoize :sync
|
27
|
+
|
28
|
+
def are_you_sure?
|
29
|
+
message =<<~EOL
|
30
|
+
About to sync these project stacks with Terraform Cloud workspaces:
|
31
|
+
|
32
|
+
Stack => Workspace
|
33
|
+
EOL
|
34
|
+
|
35
|
+
mods.each do |mod|
|
36
|
+
sync = sync(mod)
|
37
|
+
message << " #{mod} => #{sync.workspace_name}\n"
|
38
|
+
end
|
39
|
+
message << <<~EOL
|
40
|
+
|
41
|
+
A sync does the following for each workspace:
|
42
|
+
|
43
|
+
1. Create or update workspace, including the VCS settings.
|
44
|
+
2. Set the working dir.
|
45
|
+
3. Set env and terraform variables.
|
46
|
+
|
47
|
+
Are you sure?
|
48
|
+
EOL
|
49
|
+
sure?(message.chop)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
|
1
|
+
module Terraspace::Terraform::Cloud
|
2
2
|
class Workspace < Terraspace::CLI::Base
|
3
3
|
extend Memoist
|
4
4
|
include Terraspace::Util::Logging
|
5
5
|
include Terraspace::Terraform::Api::Client
|
6
|
+
include Terraspace::Terraform::Api::Http::Concern
|
6
7
|
|
7
8
|
# List will not have @mod set.
|
8
9
|
def list
|
@@ -19,23 +20,19 @@ class Terraspace::Terraform::Cloud
|
|
19
20
|
logger.info names.join("\n")
|
20
21
|
end
|
21
22
|
|
22
|
-
def setup
|
23
|
-
build
|
24
|
-
unless backend.dig('remote','workspaces') # in case called by terraspace down demo -y --destroy-workspace with a non-remote backend
|
25
|
-
logger.info "ERROR: Workspace not configured in backend.tf"
|
26
|
-
exit 1
|
27
|
-
end
|
28
|
-
init
|
29
|
-
end
|
30
|
-
|
31
23
|
def init
|
32
24
|
Terraspace::CLI::Init.new(@options.merge(calling_command: "cloud-setup")).run
|
33
25
|
end
|
34
26
|
|
27
|
+
def create
|
28
|
+
build
|
29
|
+
return unless api
|
30
|
+
api.workspace.create
|
31
|
+
end
|
32
|
+
|
35
33
|
def destroy
|
36
34
|
build
|
37
|
-
return unless
|
38
|
-
api = Terraspace::Terraform::Api.new(@mod, remote)
|
35
|
+
return unless api
|
39
36
|
workspace = api.workspace(exit_on_fail: false)
|
40
37
|
unless workspace
|
41
38
|
logger.info "Workspace #{workspace_name} not found for #{@mod.type}: #{@mod.name}"
|
@@ -43,25 +40,8 @@ class Terraspace::Terraform::Cloud
|
|
43
40
|
end
|
44
41
|
sure?
|
45
42
|
logger.info "Destroying workspace #{workspace_name}"
|
46
|
-
api.
|
47
|
-
end
|
48
|
-
|
49
|
-
def build
|
50
|
-
Terraspace::Builder.new(@options).run
|
51
|
-
end
|
52
|
-
|
53
|
-
def workspace_name
|
54
|
-
remote['workspaces']['name']
|
55
|
-
end
|
56
|
-
|
57
|
-
def remote
|
58
|
-
backend["remote"]
|
59
|
-
end
|
60
|
-
|
61
|
-
def backend
|
62
|
-
Terraspace::Compiler::Backend::Parser.new(@mod).result
|
43
|
+
api.workspace.destroy
|
63
44
|
end
|
64
|
-
memoize :backend
|
65
45
|
|
66
46
|
def sure?
|
67
47
|
message = <<~EOL.chop + " " # chop to remove newline
|
@@ -34,7 +34,7 @@ module Terraspace::Terraform::Hooks
|
|
34
34
|
exit_on_fail = exit_on_fail.nil? ? true : exit_on_fail
|
35
35
|
|
36
36
|
logger.info "Running #{type} hook"
|
37
|
-
|
37
|
+
Terraspace::Shell.new(@mod, execute, exit_on_fail: exit_on_fail).run
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Terraspace::Terraform::RemoteState
|
2
|
+
class Fetcher
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Compiler::CommandsConcern
|
5
|
+
include Terraspace::Util::Logging
|
6
|
+
|
7
|
+
def initialize(parent, identifier, options={})
|
8
|
+
@parent, @identifier, @options = parent, identifier, options
|
9
|
+
child_name, @output_key = identifier.split('.')
|
10
|
+
@child = Terraspace::Mod.new(child_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
validate! # check child stack exists
|
15
|
+
pull
|
16
|
+
load
|
17
|
+
end
|
18
|
+
|
19
|
+
def output
|
20
|
+
run
|
21
|
+
if pull_success?
|
22
|
+
value = output_value
|
23
|
+
error = output_error(:key_not_found) unless @outputs.key?(@output_key)
|
24
|
+
OutputProxy.new(value, @options.merge(error: error))
|
25
|
+
else
|
26
|
+
@error_type ||= :state_not_found # could be set to :bucket_not_found by bucket_not_found_error
|
27
|
+
error = output_error(@error_type)
|
28
|
+
OutputProxy.new(nil, @options.merge(error: error))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def output_value
|
33
|
+
return unless @outputs.key?(@output_key)
|
34
|
+
result = @outputs.dig(@output_key)
|
35
|
+
result.dig("value") if result
|
36
|
+
end
|
37
|
+
|
38
|
+
def output_error(type)
|
39
|
+
msg = case type
|
40
|
+
when :key_not_found
|
41
|
+
"Output #{@output_key} was not found for the #{@parent.name} tfvars file. Either #{@child.name} stack has not been deployed yet or it does not have this output: #{@output_key}"
|
42
|
+
when :state_not_found
|
43
|
+
"Output #{@output_key} could not be looked up for the #{@parent.name} tfvars file. #{@child.name} stack needs to be deployed"
|
44
|
+
when :bucket_not_found
|
45
|
+
"The bucket for the backend could not be found"
|
46
|
+
end
|
47
|
+
msg = "(#{msg})"
|
48
|
+
log_message(msg)
|
49
|
+
msg
|
50
|
+
end
|
51
|
+
|
52
|
+
@@pull_successes = {}
|
53
|
+
@@download_shown = false
|
54
|
+
def pull
|
55
|
+
return if @@pull_successes[cache_key]
|
56
|
+
logger.info "Downloading tfstate files for dependencies defined in tfvars..." unless @@download_shown || @options[:quiet]
|
57
|
+
@@download_shown = true
|
58
|
+
logger.debug "Downloading tfstate for stack: #{@child.name}"
|
59
|
+
|
60
|
+
success = init # init not yet run. only run .init directly, not .run. init can completely error and early exit.
|
61
|
+
return unless success
|
62
|
+
|
63
|
+
FileUtils.mkdir_p(File.dirname(state_path))
|
64
|
+
command = "cd #{@child.cache_dir} && terraform state pull > #{state_path}"
|
65
|
+
logger.debug "=> #{command}"
|
66
|
+
success = system(command)
|
67
|
+
# Can error if using a old terraform version and the statefile was created with a newer version of terraform
|
68
|
+
# IE: Failed to refresh state: state snapshot was created by Terraform v0.13.2, which is newer than current v0.12.29;
|
69
|
+
# upgrade to Terraform v0.13.2 or greater to work with this state
|
70
|
+
unless success
|
71
|
+
logger.info "Error running: #{command}".color(:red)
|
72
|
+
logger.info "Please fix the error before continuing"
|
73
|
+
end
|
74
|
+
@@pull_successes[cache_key] = success
|
75
|
+
end
|
76
|
+
|
77
|
+
def init
|
78
|
+
Terraspace::CLI::Init.new(mod: @child.name, calling_command: "apply", quiet: true, suppress_error_color: true).init
|
79
|
+
true
|
80
|
+
rescue Terraspace::BucketNotFoundError # from Terraspace::Shell
|
81
|
+
bucket_not_found_error
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
# mimic pull error
|
86
|
+
def bucket_not_found_error
|
87
|
+
@@pull_successes[cache_key] = false
|
88
|
+
@error_type = :bucket_not_found
|
89
|
+
end
|
90
|
+
|
91
|
+
def load
|
92
|
+
return self unless pull_success?
|
93
|
+
|
94
|
+
# use or set cache
|
95
|
+
if @@cache[cache_key]
|
96
|
+
@outputs = @@cache[cache_key]
|
97
|
+
else
|
98
|
+
@outputs = @@cache[cache_key] = read_statefile_outputs
|
99
|
+
end
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
memoize :load
|
104
|
+
|
105
|
+
def cache_key
|
106
|
+
@child.name
|
107
|
+
end
|
108
|
+
|
109
|
+
def read_statefile_outputs
|
110
|
+
data = JSON.load(IO.read(state_path))
|
111
|
+
data ? data['outputs'] : {}
|
112
|
+
end
|
113
|
+
|
114
|
+
def pull_success?
|
115
|
+
@@pull_successes[cache_key]
|
116
|
+
end
|
117
|
+
|
118
|
+
def state_path
|
119
|
+
"#{Terraspace.tmp_root}/remote_state/#{@child.build_dir}/state.json"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Note we already validate mod exist at the terraform_output helper. This is just in case that logic changes.
|
123
|
+
def validate!
|
124
|
+
return if @child.exist?
|
125
|
+
logger.error "ERROR: stack #{@child.name} not found".color(:red)
|
126
|
+
exit 1
|
127
|
+
end
|
128
|
+
|
129
|
+
# Using debug level because all the tfvar files always get evaluated.
|
130
|
+
# So dont want these messages to show up and be noisy unless debugging.
|
131
|
+
def log_message(msg)
|
132
|
+
logger.debug "DEBUG: #{msg}".color(:yellow)
|
133
|
+
end
|
134
|
+
|
135
|
+
cattr_accessor :cache, default: {}
|
136
|
+
class << self
|
137
|
+
def flush!
|
138
|
+
@@pull_successes = {}
|
139
|
+
@@cache = {}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|