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
@@ -27,6 +27,10 @@ module Terraspace
27
27
  end
28
28
  memoize :tmp_root
29
29
 
30
+ def log_root
31
+ "#{root}/log"
32
+ end
33
+
30
34
  def configure(&block)
31
35
  App.instance.configure(&block)
32
36
  end
@@ -38,10 +42,15 @@ module Terraspace
38
42
  end
39
43
  memoize :config
40
44
 
45
+ @@logger = nil
41
46
  def logger
42
- config.logger
47
+ @@logger ||= config.logger
48
+ end
49
+
50
+ # allow different logger when running up all
51
+ def logger=(v)
52
+ @@logger = v
43
53
  end
44
- memoize :logger
45
54
 
46
55
  def check_project!
47
56
  return if File.exist?("#{Terraspace.root}/config/app.rb")
@@ -0,0 +1,140 @@
1
+ module Terraspace::Dependency
2
+ class Graph
3
+ include Terraspace::Util::Logging
4
+
5
+ attr_reader :nodes
6
+ def initialize(stack_names, dependencies, options={})
7
+ @stack_names, @dependencies, @options = stack_names, dependencies, options
8
+ @nodes = []
9
+ @batches = []
10
+ end
11
+
12
+ def build
13
+ precreate_all_nodes
14
+ build_nodes_with_dependencies # @nodes has dependency graph info afterwards
15
+ check_circular_dependencies!
16
+ @nodes = filter_nodes
17
+ check_empty_nodes!
18
+ build_batches
19
+ clean_batches
20
+ @batches
21
+ end
22
+
23
+ # Only check when stacks option is pass. Edge case: There can be app/modules but no app/stacks yet
24
+ def check_empty_nodes!
25
+ return unless @nodes.empty? && @options[:stacks]
26
+ logger.error "ERROR: No stacks were found that match: #{@options[:stacks].join(' ')}".color(:red)
27
+ exit 1
28
+ end
29
+
30
+ def check_circular_dependencies!
31
+ @nodes.each do |node|
32
+ check_cycle(node)
33
+ end
34
+ end
35
+
36
+ MAX_CYCLE_DEPTH = Integer(ENV['TS_MAX_CYCLE_DEPTH'] || 100)
37
+ def check_cycle(node, depth=0, list=[])
38
+ if depth > MAX_CYCLE_DEPTH
39
+ logger.error "ERROR: It seems like there is a circular dependency! Stacks involved: #{list.uniq}".color(:red)
40
+ exit 1
41
+ end
42
+ node.parents.each do |parent|
43
+ check_cycle(parent, depth+1, list += [parent])
44
+ end
45
+ end
46
+
47
+ def precreate_all_nodes
48
+ @stack_names.each do |name|
49
+ node = Node.find_or_create_by(name: name)
50
+ save_node(node)
51
+ end
52
+ end
53
+
54
+ def build_nodes_with_dependencies
55
+ @dependencies.each do |item|
56
+ parent_name, child_name = item.split(':')
57
+ save_node_parent(parent_name, child_name)
58
+ end
59
+ end
60
+
61
+ def save_node_parent(parent_name, child_name)
62
+ parent = Node.find_by(name: parent_name)
63
+ child = Node.find_by(name: child_name)
64
+ child.parent!(parent)
65
+ save_node(parent)
66
+ save_node(child)
67
+ end
68
+
69
+ def build_batches
70
+ @batches[0] = Set.new(leaves)
71
+ leaves.each do |leaf|
72
+ leaf.parents.each do |parent|
73
+ build_batch(parent)
74
+ end
75
+ end
76
+ end
77
+
78
+ # So stack nodes dont get deployed more than once and too early
79
+ def clean_batches
80
+ all = Set.new
81
+ # batch is a set
82
+ @batches.reverse.each do |batch|
83
+ batch.each do |node|
84
+ batch.delete(node) if all.include?(node)
85
+ end
86
+ all += batch
87
+ end
88
+ @batches.reject! { |batch| batch.empty? }
89
+ @batches
90
+ end
91
+
92
+ def build_batch(leaf, depth=1)
93
+ @batches[depth] ||= Set.new
94
+ @batches[depth] << leaf
95
+ leaf.parents.each do |parent|
96
+ build_batch(parent, depth+1)
97
+ end
98
+ end
99
+
100
+ def filter_nodes
101
+ @filtered = []
102
+ top_nodes.each { |node| apply_filter(node) }
103
+ # draw_full_graph option is only used internally by All::Grapher
104
+ update_parents! unless @options[:draw_full_graph]
105
+ @options[:draw_full_graph] ? @nodes : @filtered
106
+ end
107
+
108
+ # remove missing parents references since they will be filtered out
109
+ def update_parents!
110
+ @filtered.each do |node|
111
+ new_parents = node.parents & @filtered
112
+ node.parents = new_parents
113
+ end
114
+ end
115
+
116
+ def apply_filter(parent, keep=false)
117
+ keep ||= @options[:stacks].blank?
118
+ keep ||= @options[:stacks].include?(parent.name) # apply filter
119
+ if keep
120
+ parent.filtered = true
121
+ @filtered << parent
122
+ end
123
+ parent.children.sort_by(&:name).each do |child|
124
+ apply_filter(child, keep)
125
+ end
126
+ end
127
+
128
+ def leaves
129
+ @nodes.select { |n| n.children.empty? }.sort_by(&:name)
130
+ end
131
+
132
+ def top_nodes
133
+ @nodes.select { |n| n.parents.empty? }.sort_by(&:name)
134
+ end
135
+
136
+ def save_node(node)
137
+ @nodes << node unless @nodes.detect { |n| n.name == node.name }
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,38 @@
1
+ module Terraspace::Dependency
2
+ class Node
3
+ attr_reader :name
4
+ attr_accessor :children, :parents, :filtered
5
+ def initialize(name)
6
+ @name = name
7
+ @children, @parents = Set.new, Set.new
8
+ end
9
+
10
+ def highlighted?
11
+ @filtered
12
+ end
13
+
14
+ def inspect
15
+ @name
16
+ end
17
+
18
+ def parent!(parent)
19
+ @parents << parent
20
+ parent.children << self
21
+ end
22
+
23
+ class << self
24
+ @@nodes = []
25
+ def find_or_create_by(name:)
26
+ node = find_by(name: name)
27
+ return node if node
28
+ node = Node.new(name)
29
+ @@nodes << node
30
+ node
31
+ end
32
+
33
+ def find_by(name:)
34
+ @@nodes.find { |n| n.name == name }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ module Terraspace::Dependency
2
+ class Registry
3
+ cattr_accessor :data, default: Set.new
4
+
5
+ class << self
6
+ def register(parent_name, child_name)
7
+ @@data << "#{parent_name}:#{child_name}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,25 +2,13 @@ require 'logger'
2
2
 
3
3
  module Terraspace
4
4
  class Logger < ::Logger
5
- # Only need to override the add method as the other calls all lead to it.
6
- def add(severity, message = nil, progname = nil)
7
- # Taken from Logger#add source
8
- # https://ruby-doc.org/stdlib-2.5.1/libdoc/logger/rdoc/Logger.html#method-i-add
9
- if message.nil?
10
- if block_given?
11
- message = yield
12
- else
13
- message = progname
14
- progname = @progname
15
- end
5
+ def format_message(severity, datetime, progname, msg)
6
+ line = if @logdev.dev == $stdout || @logdev.dev == $stderr
7
+ msg # super simple format if stdout
8
+ else
9
+ super # use the configured formatter
16
10
  end
17
-
18
- super # original logic
19
- end
20
-
21
- # plain formatting
22
- def format_message(severity, timestamp, progname, msg)
23
- "#{msg}\n"
11
+ line =~ /\n$/ ? line : "#{line}\n"
24
12
  end
25
13
  end
26
14
  end
@@ -0,0 +1,13 @@
1
+ class Terraspace::Logger
2
+ class Formatter < ::Logger::Formatter
3
+ def call(severity, time, progname, msg)
4
+ # careful changing the format. All::Summary uses a regexp on this format to remove the timestamp
5
+ "[#{format_datetime(time)} ##{Process.pid} #{progname}]: #{msg}"
6
+ end
7
+
8
+ private
9
+ def format_datetime(time)
10
+ time.strftime("%Y-%m-%dT%H:%M:%S")
11
+ end
12
+ end
13
+ end
@@ -10,6 +10,7 @@ module Terraspace
10
10
  include Terraspace::Util
11
11
 
12
12
  attr_reader :name, :consider_stacks, :instance, :options
13
+ attr_accessor :resolved # dependencies resolved
13
14
  def initialize(name, options={})
14
15
  @name, @options = placeholder(name), options
15
16
  @consider_stacks = options[:consider_stacks].nil? ? true : options[:consider_stacks]
@@ -74,6 +75,10 @@ module Terraspace
74
75
  end
75
76
  end
76
77
 
78
+ def exist?
79
+ !!root
80
+ end
81
+
77
82
  # Relative folder path without app or vendor. For example, the actual location can be found in a couple of places
78
83
  #
79
84
  # app/modules/vpc
@@ -86,8 +91,9 @@ module Terraspace
86
91
  # modules/vpc
87
92
  #
88
93
  def build_dir(disable_instance: false)
89
- if !disable_instance && !@instance.nil?
94
+ if !@instance.nil? && type_dir == "stacks" && !disable_instance
90
95
  # add _ in front so instance doesnt collide with other default stacks
96
+ # never add for app/modules sources
91
97
  instance_name = [name, @instance].compact.join('.')
92
98
  else
93
99
  instance_name = name
@@ -18,7 +18,7 @@ module Terraspace::Plugin::Summary
18
18
  # Note: will not change any of these instance variables unless we note breaking changes
19
19
  @bucket = @info[bucket_field]
20
20
  @key = @info[key_field] # key_field is INTERFACE METHOD IE: aws: key , google: prefix
21
- @folder = folder(@key)
21
+ @folder = folder(@key) # useful as prefix for performance when listing objects in buckets
22
22
  @dest = dest(@bucket)
23
23
  # May change any of these instance variables that follow
24
24
  @dest_folder = "#{@dest}/#{@folder}"
@@ -20,11 +20,15 @@ class Terraspace::Seeder
20
20
  end
21
21
 
22
22
  def app_path
23
- "#{Terraspace.root}/app/#{@mod.build_dir}/tfvars/#{Terraspace.env}.tfvars"
23
+ "#{Terraspace.root}/app/#{@mod.build_dir}/tfvars/#{seed_file}.tfvars"
24
24
  end
25
25
 
26
26
  def seed_path
27
- "#{Terraspace.root}/seed/tfvars/#{@mod.build_dir}/#{Terraspace.env}.tfvars"
27
+ "#{Terraspace.root}/seed/tfvars/#{@mod.build_dir}/#{seed_file}.tfvars"
28
+ end
29
+
30
+ def seed_file
31
+ @options[:instance] || Terraspace.env
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,107 @@
1
+ require 'open3'
2
+
3
+ module Terraspace
4
+ class Shell
5
+ include Util::Logging
6
+
7
+ def initialize(mod, command, options={})
8
+ @mod, @command, @options = mod, command, options
9
+ @error_type, @error_messages = nil, ''
10
+ end
11
+
12
+ # requires @mod to be set
13
+ # quiet useful for RemoteState::Fetcher
14
+ def run
15
+ msg = "=> #{@command}"
16
+ @options[:quiet] ? logger.debug(msg) : logger.info(msg)
17
+ return if ENV['TS_TEST']
18
+ shell
19
+ end
20
+
21
+ def shell
22
+ env = @options[:env] || {}
23
+ env.stringify_keys!
24
+ if @options[:shell] == "system" # terraspace console
25
+ system(env, @command, chdir: @mod.cache_dir)
26
+ else
27
+ popen3(env)
28
+ end
29
+ end
30
+
31
+ def popen3(env)
32
+ Open3.popen3(env, @command, chdir: @mod.cache_dir) do |stdin, stdout, stderr, wait_thread|
33
+ mimic_terraform_input(stdin, stdout)
34
+ while err = stderr.gets
35
+ @error_type ||= known_error_type(err)
36
+ if @error_type
37
+ @error_messages << err
38
+ else
39
+ # Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
40
+ # suppress it so dont get a bunch of annoying "newlines"
41
+ next if err == "\e[31m\n" && @options[:suppress_error_color]
42
+ logger.error(err)
43
+ end
44
+ end
45
+
46
+ status = wait_thread.value.exitstatus
47
+ exit_status(status)
48
+ end
49
+ end
50
+
51
+ def known_error_type(err)
52
+ if reinitialization_required?(err)
53
+ :reinitialization_required
54
+ elsif bucket_not_found?(err)
55
+ :bucket_not_found
56
+ end
57
+ end
58
+
59
+ def bucket_not_found?(err)
60
+ # Message is included in aws, azurerm, and google. See: https://bit.ly/3iOKDri
61
+ err.include?("Failed to get existing workspaces")
62
+ end
63
+
64
+ def reinitialization_required?(err)
65
+ err.include?("reinitialization required") ||
66
+ err.include?("terraform init") ||
67
+ err.include?("require reinitialization")
68
+ end
69
+
70
+ def exit_status(status)
71
+ return if status == 0
72
+
73
+ exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
74
+ if @error_type == :reinitialization_required
75
+ raise InitRequiredError.new(@error_messages)
76
+ elsif @error_type == :bucket_not_found
77
+ raise BucketNotFoundError.new(@error_messages)
78
+ elsif exit_on_fail
79
+ logger.error "Error running command: #{@command}".color(:red)
80
+ exit status
81
+ end
82
+ end
83
+
84
+ # Terraform doesnt seem to stream the line that prompts with "Enter a value:" when using Open3.popen3
85
+ # Hack around it by mimicking the "Enter a value:" prompt
86
+ #
87
+ # Note: system does stream the prompt but using Open3.popen3 so we can capture output to save to logs.
88
+ def mimic_terraform_input(stdin, stdout)
89
+ shown = false
90
+ patterns = [
91
+ "Only 'yes' will be accepted", # prompt for apply. can happen on apply
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
93
+ ]
94
+ while out = stdout.gets
95
+ logger.info(out) unless shown && out.include?("Enter a value:")
96
+ shown = false if out.include?("Enter a value:") # reset shown in case of multiple input prompts
97
+
98
+ # Sometimes stdout doesnt flush and show "Enter a value: ", so mimic it
99
+ if patterns.any? { |pattern| out.include?(pattern) }
100
+ print " Enter a value: ".bright
101
+ shown = true
102
+ stdin.write_nonblock($stdin.gets)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,58 +1,20 @@
1
1
  module Terraspace::Terraform
2
2
  class Api
3
3
  extend Memoist
4
- include Client
5
- include Terraspace::Util::Logging
6
4
 
7
5
  def initialize(mod, remote)
8
- @mod = mod
9
- @organization = remote['organization']
10
- @workspace_name = remote['workspaces']['name']
6
+ @mod, @remote = mod, remote
11
7
  end
12
8
 
13
- # Docs: https://www.terraform.io/docs/cloud/api/workspaces.html
14
- def set_working_dir
15
- return if working_directory == workspace['attributes']['working-directory']
16
-
17
- payload = {
18
- data: {
19
- attributes: {
20
- "working-directory": working_directory
21
- },
22
- type: "workspaces"
23
- }
24
- }
25
- http.patch("organizations/#{@organization}/workspaces/#{@workspace_name}", payload)
26
- end
27
-
28
- def working_directory
29
- cache_dir = @mod.cache_dir.sub("#{Terraspace.root}/", '')
30
- relative_root = Terraspace.config.cloud.relative_root # prepended to TFC Working Directory
31
- relative_root ? "#{relative_root}/#{cache_dir}" : cache_dir
32
- end
33
-
34
- def set_env_vars
35
- Vars.new(@mod, workspace).run
36
- end
37
-
38
- def workspace(options={})
39
- payload = http.get("organizations/#{@organization}/workspaces/#{@workspace_name}")
40
- # Note only way to get here is to bypass init. Example:
41
- #
42
- # terraspace up demo --no-init
43
- #
44
- unless payload || options[:exit_on_fail] == false
45
- logger.error "ERROR: Unable to find the workspace. The workspace may not exist. Or the Terraform token may be invalid. Please double check your Terraform token.".color(:red)
46
- exit 1
47
- end
48
- return unless payload
49
- payload['data']
9
+ def workspace
10
+ Workspace.new(@mod, @remote['organization'], @remote['workspaces']['name'])
50
11
  end
51
12
  memoize :workspace
52
13
 
53
- def destroy_workspace
54
- # resp payload from delete operation is nil
55
- http.delete("/organizations/#{@organization}/workspaces/#{@workspace_name}")
14
+ def runs
15
+ workspace_id = workspace.details['id']
16
+ Runs.new(workspace_id)
56
17
  end
18
+ memoize :runs
57
19
  end
58
20
  end