terraspace 0.2.4 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/aws/bin/build.sh +2 -0
  3. data/.cody/azurerm/bin/build.sh +2 -0
  4. data/.cody/google/bin/build.sh +2 -0
  5. data/CHANGELOG.md +39 -1
  6. data/README.md +14 -1
  7. data/lib/templates/base/git_hook/hook.sh +1 -1
  8. data/lib/templates/base/project/.gitignore +1 -0
  9. data/lib/templates/base/project/README.md +17 -0
  10. data/lib/terraspace.rb +6 -0
  11. data/lib/terraspace/all/base.rb +8 -0
  12. data/lib/terraspace/all/grapher.rb +129 -0
  13. data/lib/terraspace/all/preview.rb +43 -0
  14. data/lib/terraspace/all/runner.rb +169 -0
  15. data/lib/terraspace/all/summary.rb +119 -0
  16. data/lib/terraspace/app.rb +31 -9
  17. data/lib/terraspace/booter.rb +9 -0
  18. data/lib/terraspace/builder.rb +59 -22
  19. data/lib/terraspace/cli.rb +60 -33
  20. data/lib/terraspace/cli/all.rb +63 -0
  21. data/lib/terraspace/cli/build/placeholder.rb +2 -5
  22. data/lib/terraspace/cli/bundle.rb +1 -1
  23. data/lib/terraspace/cli/check_setup.rb +5 -0
  24. data/lib/terraspace/cli/cloud.rb +16 -6
  25. data/lib/terraspace/cli/cloud/runs.rb +22 -0
  26. data/lib/terraspace/cli/commander.rb +1 -8
  27. data/lib/terraspace/cli/down.rb +20 -0
  28. data/lib/terraspace/cli/help/all/down.md +32 -0
  29. data/lib/terraspace/cli/help/all/graph.md +21 -0
  30. data/lib/terraspace/cli/help/all/output.md +22 -0
  31. data/lib/terraspace/cli/help/all/plan.md +25 -0
  32. data/lib/terraspace/cli/help/all/providers.md +21 -0
  33. data/lib/terraspace/cli/help/all/refresh.md +17 -0
  34. data/lib/terraspace/cli/help/all/show.md +21 -0
  35. data/lib/terraspace/cli/help/all/up.md +27 -0
  36. data/lib/terraspace/cli/help/all/validate.md +21 -0
  37. data/lib/terraspace/cli/help/build.md +6 -0
  38. data/lib/terraspace/cli/help/bundle.md +9 -5
  39. data/lib/terraspace/cli/help/check_setup.md +9 -0
  40. data/lib/terraspace/cli/help/clean.md +5 -0
  41. data/lib/terraspace/cli/help/cloud/destroy.md +16 -0
  42. data/lib/terraspace/cli/help/cloud/list.md +7 -0
  43. data/lib/terraspace/cli/help/cloud/runs/list.md +36 -0
  44. data/lib/terraspace/cli/help/cloud/runs/prune.md +25 -0
  45. data/lib/terraspace/cli/help/cloud/sync.md +43 -0
  46. data/lib/terraspace/cli/help/console.md +8 -0
  47. data/lib/terraspace/cli/help/down.md +26 -0
  48. data/lib/terraspace/cli/help/info.md +43 -0
  49. data/lib/terraspace/cli/help/init.md +37 -0
  50. data/lib/terraspace/cli/help/list.md +20 -0
  51. data/lib/terraspace/cli/help/log.md +48 -0
  52. data/lib/terraspace/cli/help/logs/remove.md +5 -0
  53. data/lib/terraspace/cli/help/logs/truncate.md +5 -0
  54. data/lib/terraspace/cli/help/new/bootstrap_test.md +8 -0
  55. data/lib/terraspace/cli/help/new/example.md +8 -0
  56. data/lib/terraspace/cli/help/new/git_hook.md +6 -0
  57. data/lib/terraspace/cli/help/new/module.md +9 -0
  58. data/lib/terraspace/cli/help/new/module_test.md +12 -0
  59. data/lib/terraspace/cli/help/new/plugin.md +49 -0
  60. data/lib/terraspace/cli/help/new/project.md +40 -0
  61. data/lib/terraspace/cli/help/new/project_test.md +8 -0
  62. data/lib/terraspace/cli/help/new/shim.md +21 -0
  63. data/lib/terraspace/cli/help/new/stack.md +9 -0
  64. data/lib/terraspace/cli/help/output.md +6 -0
  65. data/lib/terraspace/cli/help/plan.md +29 -0
  66. data/lib/terraspace/cli/help/providers.md +18 -0
  67. data/lib/terraspace/cli/help/refresh.md +11 -0
  68. data/lib/terraspace/cli/help/seed.md +7 -0
  69. data/lib/terraspace/cli/help/show.md +36 -0
  70. data/lib/terraspace/cli/help/summary.md +11 -0
  71. data/lib/terraspace/cli/help/test.md +35 -0
  72. data/lib/terraspace/cli/help/up.md +30 -0
  73. data/lib/terraspace/cli/help/validate.md +9 -0
  74. data/lib/terraspace/cli/info.rb +4 -16
  75. data/lib/terraspace/cli/init.rb +35 -7
  76. data/lib/terraspace/cli/list.rb +14 -1
  77. data/lib/terraspace/cli/log.rb +112 -0
  78. data/lib/terraspace/cli/log/concern.rb +24 -0
  79. data/lib/terraspace/cli/logs.rb +15 -0
  80. data/lib/terraspace/cli/logs/tasks.rb +32 -0
  81. data/lib/terraspace/cli/new.rb +18 -18
  82. data/lib/terraspace/cli/new/git_hook.rb +5 -2
  83. data/lib/terraspace/cli/new/project.rb +1 -1
  84. data/lib/terraspace/cli/summary.rb +2 -2
  85. data/lib/terraspace/cli/tfc_concern.rb +14 -0
  86. data/lib/terraspace/cli/up.rb +32 -0
  87. data/lib/terraspace/command.rb +1 -1
  88. data/lib/terraspace/compiler/backend.rb +10 -0
  89. data/lib/terraspace/compiler/builder.rb +5 -4
  90. data/lib/terraspace/compiler/cleaner.rb +1 -1
  91. data/lib/terraspace/compiler/cleaner/backend_change.rb +21 -7
  92. data/lib/terraspace/compiler/commands_concern.rb +18 -0
  93. data/lib/terraspace/compiler/dirs_concern.rb +47 -0
  94. data/lib/terraspace/compiler/dsl/syntax/helpers/common.rb +26 -1
  95. data/lib/terraspace/core.rb +11 -2
  96. data/lib/terraspace/dependency/graph.rb +140 -0
  97. data/lib/terraspace/dependency/node.rb +38 -0
  98. data/lib/terraspace/dependency/registry.rb +11 -0
  99. data/lib/terraspace/logger.rb +6 -18
  100. data/lib/terraspace/logger/formatter.rb +13 -0
  101. data/lib/terraspace/mod.rb +7 -1
  102. data/lib/terraspace/plugin/summary/interface.rb +1 -1
  103. data/lib/terraspace/seeder/where.rb +6 -2
  104. data/lib/terraspace/shell.rb +107 -0
  105. data/lib/terraspace/terraform/api.rb +7 -45
  106. data/lib/terraspace/terraform/api/base.rb +7 -0
  107. data/lib/terraspace/terraform/api/client.rb +23 -3
  108. data/lib/terraspace/terraform/api/http.rb +14 -34
  109. data/lib/terraspace/terraform/api/http/concern.rb +10 -0
  110. data/lib/terraspace/terraform/api/runs.rb +28 -0
  111. data/lib/terraspace/terraform/api/token.rb +65 -0
  112. data/lib/terraspace/terraform/api/var.rb +20 -6
  113. data/lib/terraspace/terraform/api/vars.rb +2 -1
  114. data/lib/terraspace/terraform/api/workspace.rb +98 -0
  115. data/lib/terraspace/terraform/args/default.rb +48 -21
  116. data/lib/terraspace/terraform/cloud/runs.rb +13 -0
  117. data/lib/terraspace/terraform/cloud/runs/base.rb +33 -0
  118. data/lib/terraspace/terraform/cloud/runs/item_presenter.rb +37 -0
  119. data/lib/terraspace/terraform/cloud/runs/lister.rb +20 -0
  120. data/lib/terraspace/terraform/cloud/runs/pruner.rb +109 -0
  121. data/lib/terraspace/terraform/cloud/sync.rb +41 -0
  122. data/lib/terraspace/terraform/cloud/syncer.rb +52 -0
  123. data/lib/terraspace/terraform/cloud/workspace.rb +10 -30
  124. data/lib/terraspace/terraform/hooks/builder.rb +1 -1
  125. data/lib/terraspace/terraform/remote_state/fetcher.rb +143 -0
  126. data/lib/terraspace/terraform/remote_state/marker/output.rb +39 -0
  127. data/lib/terraspace/terraform/remote_state/marker/pretty_tracer.rb +37 -0
  128. data/lib/terraspace/terraform/remote_state/output_proxy.rb +29 -0
  129. data/lib/terraspace/terraform/runner.rb +24 -14
  130. data/lib/terraspace/util.rb +1 -5
  131. data/lib/terraspace/util/pretty.rb +18 -0
  132. data/lib/terraspace/version.rb +1 -1
  133. data/spec/fixtures/fetcher/c1.json +37 -0
  134. data/spec/fixtures/parser/cache_dirs/all/01-test.auto.tfvars +5 -0
  135. data/spec/fixtures/parser/cache_dirs/depends_on/01-test.auto.tfvars +2 -0
  136. data/spec/fixtures/parser/cache_dirs/output/01-test.auto.tfvars +2 -0
  137. data/spec/fixtures/summary/down.log +12 -0
  138. data/spec/fixtures/summary/output.log +5 -0
  139. data/spec/fixtures/summary/plan/error.log +20 -0
  140. data/spec/fixtures/summary/plan/success.log +17 -0
  141. data/spec/fixtures/summary/show.log +22 -0
  142. data/spec/fixtures/summary/up/error.log +13 -0
  143. data/spec/fixtures/summary/up/success.log +63 -0
  144. data/spec/fixtures/summary/validate/error.log +13 -0
  145. data/spec/fixtures/summary/validate/success.log +5 -0
  146. data/spec/terraspace/all/grapher_spec.rb +38 -0
  147. data/spec/terraspace/all/runner_spec.rb +48 -0
  148. data/spec/terraspace/all/summary_spec.rb +93 -0
  149. data/spec/terraspace/dependency/graph_spec.rb +162 -0
  150. data/spec/terraspace/seeder_spec.rb +0 -1
  151. data/spec/terraspace/terraform/remote_state/fetcher_spec.rb +52 -0
  152. data/terraspace.gemspec +5 -1
  153. metadata +179 -6
  154. data/lib/terraspace/cli/help/update.md +0 -5
  155. data/lib/terraspace/terraform/cloud.rb +0 -25
  156. data/lib/terraspace/util/sh.rb +0 -19
@@ -0,0 +1,13 @@
1
+ module Terraspace::Terraform::Cloud
2
+ class Runs < Terraspace::CLI::Base
3
+ def list
4
+ lister = Lister.new(@mod, @options)
5
+ lister.run
6
+ end
7
+
8
+ def prune
9
+ pruner = Pruner.new(@mod, @options)
10
+ pruner.run
11
+ end
12
+ end
13
+ end
@@ -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
- class Terraspace::Terraform::Cloud
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 backend.dig('remote','workspaces') # in case called by terraspace down demo -y --destroy-workspace with a non-remote backend
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.destroy_workspace
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
- sh(execute, exit_on_fail: exit_on_fail)
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