terraspace 1.0.4 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/lib/templates/base/project/.gitignore +1 -0
  4. data/lib/templates/base/project/config/app.rb +1 -1
  5. data/lib/terraspace/all/grapher.rb +11 -3
  6. data/lib/terraspace/all/runner.rb +17 -10
  7. data/lib/terraspace/app/callable_option/concern.rb +12 -0
  8. data/lib/terraspace/app/callable_option.rb +58 -0
  9. data/lib/terraspace/app.rb +16 -3
  10. data/lib/terraspace/autodetect.rb +3 -7
  11. data/lib/terraspace/builder/allow/base.rb +59 -0
  12. data/lib/terraspace/builder/allow/env.rb +17 -0
  13. data/lib/terraspace/builder/allow/region.rb +31 -0
  14. data/lib/terraspace/builder/allow/stack.rb +17 -0
  15. data/lib/terraspace/builder/allow.rb +3 -33
  16. data/lib/terraspace/builder/children.rb +42 -0
  17. data/lib/terraspace/builder.rb +34 -32
  18. data/lib/terraspace/cli/all.rb +2 -0
  19. data/lib/terraspace/cli/build/placeholder.rb +3 -1
  20. data/lib/terraspace/cli/help/all/plan.md +11 -1
  21. data/lib/terraspace/cli/help/all/up.md +10 -0
  22. data/lib/terraspace/cli/help/plan.md +9 -1
  23. data/lib/terraspace/cli/help/up.md +9 -1
  24. data/lib/terraspace/cli/init.rb +1 -1
  25. data/lib/terraspace/cli.rb +3 -2
  26. data/lib/terraspace/compiler/dirs_concern.rb +6 -1
  27. data/lib/terraspace/compiler/expander/backend.rb +1 -1
  28. data/lib/terraspace/compiler/expander.rb +38 -14
  29. data/lib/terraspace/compiler/{builder → perform}/skip.rb +1 -1
  30. data/lib/terraspace/compiler/{builder.rb → perform.rb} +21 -20
  31. data/lib/terraspace/compiler/select.rb +32 -42
  32. data/lib/terraspace/compiler/strategy/tfvar.rb +2 -1
  33. data/lib/terraspace/dependency/resolver.rb +20 -0
  34. data/lib/terraspace/mod.rb +1 -0
  35. data/lib/terraspace/plugin/summary/interface.rb +1 -1
  36. data/lib/terraspace/terraform/args/expand.rb +25 -0
  37. data/lib/terraspace/terraform/args/thor.rb +14 -11
  38. data/lib/terraspace/terraform/ihooks/after/plan.rb +2 -2
  39. data/lib/terraspace/terraform/ihooks/base.rb +5 -0
  40. data/lib/terraspace/terraform/ihooks/before/plan.rb +3 -5
  41. data/lib/terraspace/terraform/remote_state/fetcher.rb +12 -5
  42. data/lib/terraspace/terraform/remote_state/marker/output.rb +1 -1
  43. data/lib/terraspace/version.rb +1 -1
  44. data/spec/terraspace/all/runner_spec.rb +1 -0
  45. data/spec/terraspace/compiler/erb/render_spec.rb +5 -1
  46. data/terraspace.gemspec +1 -1
  47. metadata +14 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f265061c3973d32dcbea50e12b291ee22041dc2e5bc2cf2521907dd4d74b0703
4
- data.tar.gz: 97e6064b47fe37f11660fe05512e1750b23f96cd7bc7ea1bef0aa512884922de
3
+ metadata.gz: c07e843df9e61e62be834feba56f4d5fa058f3dbb130ed7e15edca66ef23e22d
4
+ data.tar.gz: a7d7c3946e2485ccb369a5d76d413118aed14592750880a48990ca83cdca5bca
5
5
  SHA512:
6
- metadata.gz: bb0ccae4efe36fae839a755561e9b6829b998c4ec653f7a9a24c8a67a6e9da9ff9e5b81ad1134018edceed75b5859e12cce280819e2792a203da7a65f4997087
7
- data.tar.gz: '0269f857a01274f4c9b64b051c724fff030582e2d62b9222da04ae0828e7770160645a4dee01a0a0c791921197d08ce87f420e5fa24678e961e76fb267c9377b'
6
+ metadata.gz: 026aa5128ce2bea7deb3e5b0fdb6e99c7cf50297c20f5292bbe6896dabb0ba995e62ecd14526ea2ea7e8e1a4be3895e29021620a89b26df49ad34ee0b95ffd1d
7
+ data.tar.gz: c2df058d2fe9848834fc4e166c1382ccb0e782170fb52f8fd264e4e948a1032916cc9d77662eac1969f4d2101e66b3655e07d944b217e5749581ac0d3ec04916
data/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
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
+ ## [1.1.1] - 2022-02-02
7
+ - [#199](https://github.com/boltops-tools/terraspace/pull/199) build required dependent stacks as part of terraspace up
8
+
9
+ ## [1.1.0] - 2022-01-30
10
+ - [#196](https://github.com/boltops-tools/terraspace/pull/196) terraspace all: build modules in batches and only each specific stack
11
+ - [#197](https://github.com/boltops-tools/terraspace/pull/197) all plan --output and all up --plan options
12
+ - simplify starter config/app.rb
13
+
14
+ ## [1.0.6] - 2022-01-24
15
+ - [#195](https://github.com/boltops-tools/terraspace/pull/195) improve autodetection for plugin expander for backend like remote
16
+
17
+ ## [1.0.5] - 2022-01-23
18
+ - [#194](https://github.com/boltops-tools/terraspace/pull/194) ability to allow and deny envs, regions, and stacks
19
+
6
20
  ## [1.0.4] - 2022-01-21
7
21
  - [#193](https://github.com/boltops-tools/terraspace/pull/193) improve all include_stacks and exclude_stacks option
8
22
 
@@ -5,6 +5,7 @@ terraform.tfvars
5
5
  *.tfstate*
6
6
 
7
7
  .terraspace-cache
8
+ *.plan
8
9
 
9
10
  # OS X files
10
11
  .history
@@ -1,4 +1,4 @@
1
+ # Docs: https://terraspace.cloud/docs/config/reference/
1
2
  Terraspace.configure do |config|
2
3
  config.logger.level = :info
3
- config.test_framework = "rspec"
4
4
  end
@@ -3,14 +3,13 @@ require "tty-tree"
3
3
 
4
4
  module Terraspace::All
5
5
  class Grapher < Base
6
+ include Terraspace::Compiler::DirsConcern
6
7
  include Terraspace::Util::Logging
7
8
 
8
9
  def run
9
10
  check_graphviz!
10
11
  logger.info "Building graph..."
11
- builder = Terraspace::Builder.new(@options.merge(mod: "placeholder", quiet: true, draw_full_graph: draw_full_graph))
12
- builder.run
13
- graph = builder.graph
12
+ graph = build_graph
14
13
  if @options[:format] == "text"
15
14
  text(graph.top_nodes)
16
15
  else
@@ -18,6 +17,15 @@ module Terraspace::All
18
17
  end
19
18
  end
20
19
 
20
+ def build_graph
21
+ resolver = Terraspace::Dependency::Resolver.new(@options.merge(quiet: true, draw_full_graph: draw_full_graph))
22
+ resolver.resolve
23
+ dependencies = Terraspace::Dependency::Registry.data # populated after build_unresolved
24
+ graph = Terraspace::Dependency::Graph.new(stack_names, dependencies, @options)
25
+ graph.build
26
+ graph
27
+ end
28
+
21
29
  def text(nodes)
22
30
  Rainbow.enabled = false unless @options[:full]
23
31
  data = build_tree_data(nodes)
@@ -1,6 +1,7 @@
1
1
  module Terraspace::All
2
2
  class Runner < Base
3
3
  include Terraspace::Util
4
+ extend Memoist
4
5
 
5
6
  def initialize(command, options={})
6
7
  @command, @options = command, options
@@ -21,32 +22,27 @@ module Terraspace::All
21
22
  end
22
23
 
23
24
  def build_batches
24
- @batches = run_builder(quiet: false)
25
+ @batches = Terraspace::Dependency::Resolver.new(@options).resolve
25
26
  @batches.reverse! if @command == "down"
26
27
  @batches
27
28
  end
28
29
 
29
30
  def deploy_batches
30
31
  truncate_logs if ENV['TS_TRUNCATE_LOGS']
32
+ build_modules
31
33
  @batches.each_with_index do |batch,i|
32
34
  logger.info "Batch Run #{i+1}:"
33
- run_builder unless i == 0 # already handled by build_batches the first time
34
35
  deploy_batch(batch)
35
36
  end
36
37
  end
37
38
 
38
- # Should run after each batch run. run_builder also calls replace_outputs.
39
- # Important: rebuild from source so placeholders are in place.
40
- def run_builder(quiet: true)
41
- Terraspace::Builder.new(@options.merge(mod: "placeholder", quiet: quiet)).run
42
- end
43
-
44
39
  def deploy_batch(batch)
45
40
  @pids = {} # stores child processes pids. map of pid to mod_name, reset this list on each batch run
46
41
  concurrency = Terraspace.config.all.concurrency
47
42
  batch.sort_by(&:name).each_slice(concurrency) do |slice|
48
43
  slice.each do |node|
49
44
  pid = fork do
45
+ build_stack(node.name)
50
46
  run_terraspace(node.name)
51
47
  end
52
48
  @pids[pid] = node.name # store mod_name mapping
@@ -57,6 +53,16 @@ module Terraspace::All
57
53
  report_errors # reports finall errors and possibly exit
58
54
  end
59
55
 
56
+ def build_modules
57
+ builder = Terraspace::Builder.new(@options.merge(mod: "placeholder", clean: true, quiet: true, include_stacks: :none))
58
+ builder.build(modules: true)
59
+ end
60
+
61
+ def build_stack(name)
62
+ builder = Terraspace::Builder.new(@options.merge(mod: name, clean: false, quiet: true, include_stacks: :root_only))
63
+ builder.build(modules: false)
64
+ end
65
+
60
66
  def wait_for_child_proccess
61
67
  @errors = [] # stores child processes pids that errored
62
68
  @pids.each do |pid, _|
@@ -70,7 +76,7 @@ module Terraspace::All
70
76
  @errors.each do |pid|
71
77
  mod_name = @pids[pid]
72
78
  terraspace_command = terraspace_command(mod_name)
73
- logger.error "Error running: #{terraspace_command}. Check logs and fix the error.".color(:red)
79
+ logger.error "Error running: #{terraspace_command}. Fix the error above or check logs for the error.".color(:red)
74
80
  end
75
81
  unless @errors.empty?
76
82
  exit 2 if exit_on_fail?
@@ -97,7 +103,8 @@ module Terraspace::All
97
103
  log_path: log_path(mod_name),
98
104
  terraspace_command: terraspace_command(mod_name),
99
105
  }
100
- Summary.new(data).run
106
+ # Its possible for log file to not get created if RemoteState::Fetcher#validate! fails
107
+ Summary.new(data).run if File.exist?(data[:log_path])
101
108
  end
102
109
  end
103
110
 
@@ -0,0 +1,12 @@
1
+ class Terraspace::App::CallableOption
2
+ module Concern
3
+ def callable_option(options={})
4
+ callable_option = Terraspace::App::CallableOption.new(
5
+ config_name: options[:config_name],
6
+ config_value: options[:config_value],
7
+ passed_args: options[:passed_args],
8
+ )
9
+ callable_option.object
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,58 @@
1
+ # Class represents a terraspace option that is possibly callable. Examples:
2
+ #
3
+ # config.allow.envs
4
+ # config.allow.regions
5
+ # config.deny.envs
6
+ # config.deny.regions
7
+ # config.all.include_stacks
8
+ # config.all.exclude_stacks
9
+ #
10
+ # Abstraction is definitely obtuse. Using it to get rid of duplication.
11
+ #
12
+ class Terraspace::App
13
+ class CallableOption
14
+ include Terraspace::Util::Logging
15
+
16
+ def initialize(options={})
17
+ @options = options
18
+ # Example:
19
+ # config_name: config.allow.envs
20
+ # config_value: ["dev"]
21
+ # args: [@stack_name] # passed to object.call
22
+ @config_name = options[:config_name]
23
+ @config_value = options[:config_value]
24
+ @passed_args = options[:passed_args]
25
+ end
26
+
27
+ # Returns either an Array or nil
28
+ def object
29
+ case @config_value
30
+ when nil
31
+ return nil
32
+ when Array
33
+ return @config_value
34
+ when -> (c) { c.respond_to?(:public_instance_methods) && c.public_instance_methods.include?(:call) }
35
+ object= @config_value.new
36
+ when -> (c) { c.respond_to?(:call) }
37
+ object = @config_value
38
+ else
39
+ raise "Invalid option for #{@config_name}"
40
+ end
41
+
42
+ if object
43
+ result = @passed_args.empty? ? object.call : object.call(*@passed_args)
44
+ unless result.is_a?(Array) || result.is_a?(NilClass)
45
+ message = "ERROR: The #{@config_name} needs to return an Array or nil"
46
+ logger.info message.color(:yellow)
47
+ logger.info <<~EOL
48
+ The #{@config_name} when assigned a class, object, or proc must implement
49
+ the call method and return an Array or nil.
50
+ The current return value is a #{result.class}
51
+ EOL
52
+ raise message
53
+ end
54
+ end
55
+ result
56
+ end
57
+ end
58
+ end
@@ -12,17 +12,30 @@ module Terraspace
12
12
 
13
13
  def defaults
14
14
  config = ActiveSupport::OrderedOptions.new
15
+
15
16
  config.all = ActiveSupport::OrderedOptions.new
16
17
  config.all.concurrency = 5
17
18
  config.all.exit_on_fail = ActiveSupport::OrderedOptions.new
18
- config.all.exit_on_fail.down = true
19
+ config.all.exit_on_fail.down = false
20
+ config.all.exit_on_fail.plan = true
19
21
  config.all.exit_on_fail.up = true
20
- config.all.ignore_stacks = nil
21
- config.all.include_stacks = nil
22
+
22
23
  config.allow = ActiveSupport::OrderedOptions.new
23
24
  config.allow.envs = nil
24
25
  config.allow.regions = nil
26
+ config.allow.stacks = nil
27
+ config.deny = ActiveSupport::OrderedOptions.new
28
+ config.deny.envs = nil
29
+ config.deny.regions = nil
30
+ config.deny.stacks = nil
31
+
32
+ config.all.exclude_stacks = nil
33
+ config.all.include_stacks = nil
34
+ config.all.consider_allow_deny_stacks = true
35
+
25
36
  config.auto_create_backend = true
37
+ config.autodetect = ActiveSupport::OrderedOptions.new
38
+ config.autodetect.expander = nil
26
39
  config.build = ActiveSupport::OrderedOptions.new
27
40
  config.build.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
28
41
  config.build.cache_root = nil # defaults to /full/path/to/.terraspace-cache
@@ -2,13 +2,9 @@ module Terraspace
2
2
  class Autodetect
3
3
  def plugin
4
4
  plugins = Terraspace::Plugin.meta.keys
5
- if plugins.size == 1
6
- plugins.first
7
- else
8
- precedence = %w[aws azurerm google]
9
- precedence.find do |p|
10
- plugins.include?(p)
11
- end
5
+ precedence = %w[aws azurerm google]
6
+ precedence.find do |p|
7
+ plugins.include?(p)
12
8
  end
13
9
  end
14
10
  end
@@ -0,0 +1,59 @@
1
+ class Terraspace::Builder::Allow
2
+ class Base
3
+ include Terraspace::App::CallableOption::Concern
4
+
5
+ def initialize(mod)
6
+ @mod = mod # Only Region subclass uses @mod but keeping interface same for Env for simplicity
7
+ @stack_name = mod.name
8
+ end
9
+
10
+ def check!
11
+ messages = []
12
+ unless allowed?
13
+ messages << message # message is interface method
14
+ end
15
+ unless messages.empty?
16
+ puts "ERROR: The configs do not allow this.".color(:red)
17
+ puts messages
18
+ exit 1
19
+ end
20
+ end
21
+
22
+ def allowed?
23
+ if allows.nil? && denys.nil?
24
+ true
25
+ elsif denys.nil?
26
+ allows.include?(check_value)
27
+ elsif allows.nil?
28
+ !denys.include?(check_value)
29
+ else
30
+ allows.include?(check_value) && !denys.include?(check_value)
31
+ end
32
+ end
33
+
34
+ def allows
35
+ callable_option(
36
+ config_name: "config.allow.#{config_name}",
37
+ config_value: config.dig(:allow, config_name),
38
+ passed_args: [@stack_name],
39
+ )
40
+ end
41
+
42
+ def denys
43
+ callable_option(
44
+ config_name: "config.deny.#{config_name}",
45
+ config_value: config.dig(:deny, config_name),
46
+ passed_args: [@stack_name],
47
+ )
48
+ end
49
+
50
+ private
51
+ def config
52
+ Terraspace.config
53
+ end
54
+
55
+ def config_name
56
+ self.class.to_s.split('::').last.underscore.pluralize.to_sym # ActiveSuport::HashWithIndifferentAccess#dig requires symbol
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ class Terraspace::Builder::Allow
2
+ class Env < Base
3
+ # interface method
4
+ def message
5
+ messages = []
6
+ messages << "This env is not allowed to be used: TS_ENV=#{Terraspace.env}"
7
+ messages << "Allow envs: #{allows.join(', ')}" if allows
8
+ messages << "Deny envs: #{denys.join(', ')}" if denys
9
+ messages.join("\n")
10
+ end
11
+
12
+ # interface method
13
+ def check_value
14
+ Terraspace.env
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ class Terraspace::Builder::Allow
2
+ class Region < Base
3
+ # interface method
4
+ def message
5
+ messages = []
6
+ word = config_name.to_s # IE: regions or locations
7
+ messages << "This #{word.singularize} is not allowed to be used: Detected current #{word.singularize}=#{current_region}"
8
+ messages << "Allow #{word}: #{allows.join(', ')}" if allows
9
+ messages << "Deny #{word}: #{denys.join(', ')}" if denys
10
+ messages.join("\n")
11
+ end
12
+
13
+ # interface method
14
+ def check_value
15
+ current_region
16
+ end
17
+
18
+ def current_region
19
+ expander = Terraspace::Compiler::Expander.autodetect(@mod).expander
20
+ expander.region
21
+ end
22
+
23
+ def config_name
24
+ if config.allow.locations || config.deny.locations
25
+ :locations # ActiveSuport::HashWithIndifferentAccess#dig requires symbol
26
+ else
27
+ super # :regions
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ class Terraspace::Builder::Allow
2
+ class Stack < Base
3
+ # interface method
4
+ def message
5
+ messages = []
6
+ messages << "This stack is not allowed to be used for TS_ENV=#{Terraspace.env}"
7
+ messages << "Allow stacks: #{allows.join(', ')}" if allows
8
+ messages << "Deny stacks: #{denys.join(', ')}" if denys
9
+ messages.join("\n")
10
+ end
11
+
12
+ # interface method
13
+ def check_value
14
+ @mod.name
15
+ end
16
+ end
17
+ end
@@ -5,39 +5,9 @@ class Terraspace::Builder
5
5
  end
6
6
 
7
7
  def check!
8
- messages = []
9
- unless env_allowed?
10
- messages << "This env is not allowed to be used: TS_ENV=#{Terraspace.env}"
11
- messages << "Allowed envs: #{config.allow.envs.join(', ')}"
12
- end
13
- unless region_allowed?
14
- messages << "This region is not allowed to be used: Detected current region=#{current_region}"
15
- messages << "Allowed regions: #{config.allow.regions.join(', ')}"
16
- end
17
- unless messages.empty?
18
- puts "ERROR: The configs do not allow this.".color(:red)
19
- puts messages
20
- exit 1
21
- end
22
- end
23
-
24
- def env_allowed?
25
- return true unless config.allow.envs
26
- config.allow.envs.include?(Terraspace.env)
27
- end
28
-
29
- def region_allowed?
30
- return true unless config.allow.regions
31
- config.allow.regions.include?(current_region)
32
- end
33
-
34
- def current_region
35
- expander = Terraspace::Compiler::Expander.autodetect(@mod).expander
36
- expander.region
37
- end
38
-
39
- def config
40
- Terraspace.config
8
+ Env.new(@mod).check!
9
+ Stack.new(@mod).check!
10
+ Region.new(@mod).check!
41
11
  end
42
12
  end
43
13
  end
@@ -0,0 +1,42 @@
1
+ class Terraspace::Builder
2
+ class Children
3
+ include Terraspace::Util::Logging
4
+
5
+ def initialize(mod, options={})
6
+ @mod, @options = mod, options
7
+ @queue = []
8
+ end
9
+
10
+ def build
11
+ dependencies = Terraspace::Dependency::Registry.data
12
+ # Find out if current deploy stack contains dependency
13
+ found = dependencies.find do |parent_child|
14
+ parent, _ = parent_child.split(':')
15
+ parent == @mod.name
16
+ end
17
+ return unless found
18
+
19
+ # Go down graph children, which are the dependencies to build a queue
20
+ parent, _ = found.split(':')
21
+ node = Terraspace::Dependency::Node.find_by(name: parent)
22
+ build_queue(node)
23
+
24
+ logger.debug "Terraspace::Builder::Children @queue #{@queue}"
25
+
26
+ # Process queue in reverse order to build leaf nodes first
27
+ @queue.reverse.each do |node|
28
+ mod = Terraspace::Mod.new(node.name, @options)
29
+ Terraspace::Compiler::Perform.new(mod).compile
30
+ end
31
+ end
32
+
33
+ # Use depth first traversal to build queue
34
+ def build_queue(node)
35
+ node.children.each do |child|
36
+ @queue << child
37
+ build_queue(child)
38
+ end
39
+ @queue
40
+ end
41
+ end
42
+ end
@@ -4,64 +4,66 @@ module Terraspace
4
4
  include Compiler::DirsConcern
5
5
  include Hooks::Concern
6
6
 
7
- attr_reader :graph
7
+ # @include_stacks can be 3 values: root_with_children, none, root_only
8
+ #
9
+ # none: dont build any stacks at all. used by `terraspace all up`
10
+ # root_only: only build root stack. used by `terraspace all up`
11
+ # root_with_children: build all parent stacks as well as the root stack. normal `terraspace up`
12
+ #
13
+ def initialize(options={})
14
+ super
15
+ @include_stacks = @options[:include_stacks] || :root_with_children
16
+ end
8
17
 
9
18
  def run
10
19
  return if @options[:build] == false
11
20
  Terraspace::CLI::Setup::Check.check!
21
+ check_allow!
12
22
  @mod.root_module = true
13
23
  clean
24
+ resolve_dependencies if @include_stacks == :root_with_children
25
+ build
26
+ end
27
+
28
+ def resolve_dependencies
29
+ resolver = Terraspace::Dependency::Resolver.new(@options.merge(quiet: true))
30
+ resolver.resolve # returns batches
31
+ end
32
+
33
+ def build(modules: true)
14
34
  build_dir = Util.pretty_path(@mod.cache_dir)
15
35
  placeholder_stack_message
16
36
  logger.info "Building #{build_dir}" unless @options[:quiet] # from terraspace all
17
-
18
- batches = nil
19
37
  FileUtils.mkdir_p(@mod.cache_dir) # so terraspace before build hooks work
20
38
  run_hooks("terraspace.rb", "build") do
21
- check_allow!
22
- build_unresolved
23
- batches = build_batches
24
- build_all
39
+ build_dir("modules") if modules
40
+ build_stacks
25
41
  logger.info "Built in #{build_dir}" unless @options[:quiet] # from terraspace all
26
42
  end
27
- batches
28
43
  end
29
44
 
30
45
  def check_allow!
31
46
  Allow.new(@mod).check!
32
47
  end
33
48
 
34
- # Builds dependency graph and returns the batches to run
35
- def build_batches
36
- dependencies = Terraspace::Dependency::Registry.data # populated after build_unresolved
37
- @graph = Terraspace::Dependency::Graph.new(stack_names, dependencies, @options)
38
- @graph.build
39
- end
40
-
41
- def build_all
42
- # At this point dependencies have been resolved.
43
- Terraspace::Terraform::RemoteState::Fetcher.flush!
44
- @resolved = true
45
- build_unresolved
46
- end
47
-
48
- def build_unresolved
49
- build_dir("modules")
50
- build_dir("stacks")
51
- build_root_module
49
+ def build_stacks
50
+ return if @include_stacks == :none
51
+ build_children_stacks if @include_stacks == :root_with_children
52
+ Compiler::Perform.new(@mod).compile # @include_stacks :root or :root_with_children
52
53
  end
53
54
 
54
- def build_root_module
55
- @mod.resolved = @resolved
56
- Compiler::Builder.new(@mod).build
55
+ # Build stacks that are part of the dependency graph. Because .terraspace-cache folders
56
+ # need to exist so `terraform state pull` works to get the state info.
57
+ def build_children_stacks
58
+ children = Children.new(@mod, @options)
59
+ children.build
57
60
  end
58
61
 
59
62
  def build_dir(type_dir)
60
63
  with_each_mod(type_dir) do |mod|
61
- mod.resolved = @resolved
62
64
  is_root_module = mod.cache_dir == @mod.cache_dir
63
- next if is_root_module # handled by build_root_module
64
- Compiler::Builder.new(mod).build
65
+ next if is_root_module # handled by build_stacks
66
+ Compiler::Perform.new(mod).compile
65
67
  end
66
68
  end
67
69
 
@@ -38,6 +38,7 @@ class Terraspace::CLI
38
38
 
39
39
  desc "plan", "Run plan for all or multiple stacks."
40
40
  long_desc Help.text("all/plan")
41
+ option :out, aliases: :o, desc: "Output path. Can be a pattern like :MOD_NAME.plan"
41
42
  def plan(*stacks)
42
43
  Terraspace::All::Runner.new("plan", @options.merge(stacks: stacks)).run
43
44
  end
@@ -56,6 +57,7 @@ class Terraspace::CLI
56
57
 
57
58
  desc "up", "Deploy all or multiple stacks."
58
59
  long_desc Help.text("all/up")
60
+ option :plan, desc: "Plan path. Can be a pattern like :MOD_NAME.plan"
59
61
  def up(*stacks)
60
62
  Terraspace::All::Runner.new("up", @options.merge(stacks: stacks)).run
61
63
  end
@@ -3,6 +3,7 @@
3
3
  # It's useful for the summary command.
4
4
  module Terraspace::CLI::Build
5
5
  class Placeholder
6
+ include Terraspace::Compiler::DirsConcern
6
7
  include Terraspace::Util::Logging
7
8
 
8
9
  def initialize(options={})
@@ -28,7 +29,8 @@ module Terraspace::CLI::Build
28
29
  def find_stack
29
30
  stack_paths = Dir.glob("{app,vendor}/stacks/*")
30
31
  stack_paths.select! do |path|
31
- select = Terraspace::Compiler::Select.new(path)
32
+ stack_name = extract_stack_name(path)
33
+ select = Terraspace::Compiler::Select.new(stack_name)
32
34
  select.selected?
33
35
  end
34
36
  mod_path = stack_paths.last
@@ -22,4 +22,14 @@
22
22
  terraspace plan a1: Plan: 2 to add, 0 to change, 0 to destroy.
23
23
  terraspace plan a1: Changes to Outputs:
24
24
  Time took: 11s
25
- $
25
+ $
26
+
27
+ ## Using Plan Outputs
28
+
29
+ Using plan output path. You can specify an output path for the plan that contains pattern for expansion. Example:
30
+
31
+ $ terraspace all plan --out ":MOD_NAME.plan"
32
+
33
+ You can then use this later in terraspace up:
34
+
35
+ $ terraspace all up --plan ":MOD_NAME.plan"
@@ -25,3 +25,13 @@ Once you confirm, Terraspace deploys the batches in parallel. Essentially, Terra
25
25
  Time took: 25s
26
26
 
27
27
  Terraspace provides a reduced-noise summary of the runs. The full logs are also written for further inspection and debugging. The [terraspace log](https://terraspace.cloud/reference/terraspace-log/) command is useful for viewing the logs.
28
+
29
+ ## Using Plans
30
+
31
+ Using plan output path. You can specify an output path for the plan that contains pattern for expansion. Example:
32
+
33
+ $ terraspace all plan --out ":MOD_NAME.plan"
34
+
35
+ You can then use this later in terraspace up:
36
+
37
+ $ terraspace all up --plan ":MOD_NAME.plan"