terraspace 0.6.23 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/README.md +2 -0
  3. data/.cody/azurerm/bin/az/configure.sh +5 -1
  4. data/.cody/azurerm/project.rb +4 -0
  5. data/.cody/azurerm/role.rb +0 -1
  6. data/.cody/google/bin/gcloud/configure.sh +2 -1
  7. data/.cody/google/project.rb +4 -0
  8. data/.cody/google/role.rb +0 -1
  9. data/.cody/none/bin/build.sh +15 -0
  10. data/.cody/none/buildspec.yml +10 -0
  11. data/.cody/none/project.rb +2 -0
  12. data/.cody/none/role.rb +4 -0
  13. data/.cody/shared/script/install/terraform.sh +14 -6
  14. data/.cody/unit/buildspec.yml +1 -0
  15. data/.pipedream/pipeline.rb +1 -0
  16. data/CHANGELOG.md +63 -0
  17. data/Gemfile +6 -0
  18. data/README.md +8 -4
  19. data/lib/templates/examples/hcl/module/main.tf +3 -0
  20. data/lib/templates/examples/hcl/module/outputs.tf +4 -0
  21. data/lib/templates/examples/hcl/module/variables.tf +5 -0
  22. data/lib/templates/examples/hcl/stack/main.tf +4 -0
  23. data/lib/templates/examples/hcl/stack/outputs.tf +4 -0
  24. data/lib/templates/examples/hcl/stack/variables.tf +5 -0
  25. data/lib/templates/examples/ruby/module/main.rb +3 -0
  26. data/lib/templates/examples/ruby/module/outputs.rb +4 -0
  27. data/lib/templates/examples/ruby/module/variables.rb +5 -0
  28. data/lib/templates/examples/ruby/stack/main.rb +4 -0
  29. data/lib/templates/examples/ruby/stack/outputs.rb +4 -0
  30. data/lib/templates/examples/ruby/stack/variables.rb +5 -0
  31. data/lib/templates/hcl/project/config/terraform/backend.tf.tt +5 -13
  32. data/lib/templates/hcl/project/config/terraform/provider.tf +4 -14
  33. data/lib/terraspace/autodetect.rb +15 -0
  34. data/lib/terraspace/autoloader.rb +6 -1
  35. data/lib/terraspace/builder.rb +1 -9
  36. data/lib/terraspace/bundle.rb +54 -0
  37. data/lib/terraspace/cli/concern.rb +113 -0
  38. data/lib/terraspace/cli/help/new/example.md +7 -1
  39. data/lib/terraspace/cli/help.rb +7 -8
  40. data/lib/terraspace/cli/new/example.rb +36 -0
  41. data/lib/terraspace/cli/new/helpers/plugin_gem.rb +2 -14
  42. data/lib/terraspace/cli/new/helpers.rb +1 -1
  43. data/lib/terraspace/cli/new/module.rb +1 -1
  44. data/lib/terraspace/cli/new/plugin/helper.rb +1 -1
  45. data/lib/terraspace/cli/new/plugin.rb +1 -1
  46. data/lib/terraspace/cli/new/project.rb +11 -17
  47. data/lib/terraspace/cli/new/sequence.rb +1 -1
  48. data/lib/terraspace/cli/new/source/core.rb +7 -6
  49. data/lib/terraspace/cli/new/source/plugin.rb +2 -2
  50. data/lib/terraspace/cli/new/stack.rb +3 -2
  51. data/lib/terraspace/cli/new/test.rb +12 -2
  52. data/lib/terraspace/cli/new.rb +4 -0
  53. data/lib/terraspace/cli/{check_setup.rb → setup/check.rb} +5 -3
  54. data/lib/terraspace/cli/setup.rb +9 -0
  55. data/lib/terraspace/cli/tfc_concern.rb +1 -1
  56. data/lib/terraspace/cli.rb +34 -27
  57. data/lib/terraspace/command.rb +3 -2
  58. data/lib/terraspace/compiler/commands_concern.rb +0 -8
  59. data/lib/terraspace/compiler/expander/backend.rb +44 -0
  60. data/lib/terraspace/compiler/expander.rb +11 -19
  61. data/lib/terraspace/core.rb +10 -0
  62. data/lib/terraspace/plugin/expander/friendly.rb +1 -0
  63. data/lib/terraspace/plugin/expander/interface.rb +19 -4
  64. data/lib/terraspace/terraform/api/client.rb +1 -1
  65. data/lib/terraspace/terraform/args/custom.rb +11 -6
  66. data/lib/terraspace/terraform/args/pass.rb +110 -0
  67. data/lib/terraspace/terraform/args/{default.rb → thor.rb} +1 -3
  68. data/lib/terraspace/{compiler → terraform/runner}/backend/parser.rb +1 -1
  69. data/lib/terraspace/{compiler → terraform/runner}/backend.rb +14 -1
  70. data/lib/terraspace/terraform/runner/retryer.rb +1 -1
  71. data/lib/terraspace/terraform/runner.rb +25 -15
  72. data/lib/terraspace/terraform/tfc/sync.rb +1 -1
  73. data/lib/terraspace/version.rb +1 -1
  74. data/spec/terraspace/terraform/args/custom_spec.rb +4 -4
  75. data/spec/terraspace/terraform/args/pass_spec.rb +101 -0
  76. data/terraspace.gemspec +2 -4
  77. metadata +46 -50
@@ -1,11 +1,10 @@
1
1
  class Terraspace::CLI
2
- class Help
3
- class << self
4
- def text(namespaced_command)
5
- path = namespaced_command.to_s.gsub(':','/')
6
- path = File.expand_path("../help/#{path}.md", __FILE__)
7
- IO.read(path) if File.exist?(path)
8
- end
2
+ module Help
3
+ def text(namespaced_command)
4
+ path = namespaced_command.to_s.gsub(':','/')
5
+ path = File.expand_path("../help/#{path}.md", __FILE__)
6
+ IO.read(path) if File.exist?(path)
9
7
  end
8
+ extend self
10
9
  end
11
- end
10
+ end
@@ -0,0 +1,36 @@
1
+ class Terraspace::CLI::New
2
+ class Example < Thor::Group
3
+ include Thor::Actions
4
+
5
+ # only stack name is configurable
6
+ argument :name, default: "demo"
7
+
8
+ def self.options
9
+ # default is nil for autodetection
10
+ [
11
+ [:force, aliases: %w[f], type: :boolean, desc: "Force overwrite"],
12
+ [:lang, default: "hcl", desc: "Language to use: HCL/ERB or Ruby DSL"],
13
+ [:plugin, aliases: %w[p], default: nil, type: :string],
14
+ ]
15
+ end
16
+ options.each { |args| class_option(*args) }
17
+
18
+ def create
19
+ Module.start(["example", "--examples"] + cli_args)
20
+ Stack.start([name, "--examples"] + cli_args)
21
+ end
22
+
23
+ private
24
+ def cli_args
25
+ plugin = @options[:plugin] || Terraspace::Autodetect.new.plugin
26
+ args = if plugin
27
+ ['--plugin', plugin]
28
+ else
29
+ ['--plugin', 'none'] # override default of aws
30
+ end
31
+ args << "--force" if @options[:force]
32
+ args += ["--lang", @options[:lang]] if @options[:lang]
33
+ args
34
+ end
35
+ end
36
+ end
@@ -5,20 +5,8 @@ module Terraspace::CLI::New::Helpers
5
5
  if @options[:plugin_gem]
6
6
  @options[:plugin_gem]
7
7
  else
8
- plugin = @options[:plugin] || autodetect_provider
9
- "terraspace_plugin_#{plugin}"
10
- end
11
- end
12
-
13
- def autodetect_provider
14
- providers = Terraspace::Plugin.meta.keys
15
- if providers.size == 1
16
- providers.first
17
- else
18
- precedence = %w[aws azurerm google]
19
- precedence.find do |p|
20
- providers.include?(p)
21
- end
8
+ plugin = @options[:plugin] || Terraspace::Autodetect.new.plugin
9
+ "terraspace_plugin_#{plugin}" if plugin and plugin != "none"
22
10
  end
23
11
  end
24
12
  end
@@ -5,7 +5,7 @@ class Terraspace::CLI::New
5
5
  private
6
6
  def build_gemfile(*list)
7
7
  lines = []
8
- list.each do |name|
8
+ list.compact.each do |name|
9
9
  lines << gem_line(name)
10
10
  end
11
11
  lines.join("\n")
@@ -5,7 +5,7 @@ class Terraspace::CLI::New
5
5
  argument :name
6
6
 
7
7
  def create_module
8
- puts "=> Creating test for new module: #{name}"
8
+ puts "=> Creating new module called #{name}"
9
9
  plugin_template_source(@options[:lang], "module") # IE: plugin_template_source("hcl", "module")
10
10
  dest = "app/modules/#{name}"
11
11
  dest = "#{@options[:project_name]}/#{dest}" if @options[:project_name]
@@ -1,5 +1,5 @@
1
1
  class Terraspace::CLI::New::Plugin
2
- module Helpers
2
+ module Helper
3
3
  private
4
4
  # helper
5
5
  def camel_name
@@ -1,6 +1,6 @@
1
1
  class Terraspace::CLI::New
2
2
  class Plugin < Sequence
3
- include Helpers
3
+ include Helper
4
4
 
5
5
  argument :name
6
6
 
@@ -6,7 +6,6 @@ class Terraspace::CLI::New
6
6
  [:config, type: :boolean, default: true, desc: "Whether or not to generate config files."],
7
7
  [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files."],
8
8
  [:quiet, type: :boolean, desc: "Quiet output."],
9
- [:test_structure, type: :boolean, desc: "Create project bootstrap test structure."],
10
9
  ]
11
10
  end
12
11
 
@@ -20,7 +19,7 @@ class Terraspace::CLI::New
20
19
 
21
20
  public
22
21
  def creating_messaging
23
- log "=> Creating new project called #{name}."
22
+ log "=> Creating new project called #{name}"
24
23
  end
25
24
 
26
25
  def create_base
@@ -28,6 +27,16 @@ class Terraspace::CLI::New
28
27
  directory ".", "#{name}"
29
28
  end
30
29
 
30
+ def bundle_install
31
+ return if @options[:bundle] == false
32
+ log "=> Installing dependencies with: bundle install"
33
+ Bundler.with_unbundled_env do
34
+ bundle = "BUNDLE_IGNORE_CONFIG=1 bundle install"
35
+ bundle << " > /dev/null 2>&1" if @options[:quiet]
36
+ system(bundle, chdir: name)
37
+ end
38
+ end
39
+
31
40
  # Will generate config folder from
32
41
  #
33
42
  # 1. terraspace code lang templates or
@@ -60,21 +69,6 @@ class Terraspace::CLI::New
60
69
  Stack.start(component_args("demo", name))
61
70
  end
62
71
 
63
- def create_test
64
- return unless @options[:test_structure]
65
- Test::Bootstrap.start(["--dir", name])
66
- end
67
-
68
- def bundle_install
69
- return if @options[:bundle] == false
70
- log "=> Installing dependencies with: bundle install"
71
- Bundler.with_unbundled_env do
72
- bundle = "BUNDLE_IGNORE_CONFIG=1 bundle install"
73
- bundle << " > /dev/null 2>&1" if @options[:quiet]
74
- system(bundle, chdir: name)
75
- end
76
- end
77
-
78
72
  def welcome_message_examples
79
73
  return unless options[:examples]
80
74
  log <<~EOL
@@ -28,7 +28,7 @@ class Terraspace::CLI::New
28
28
  argument :name
29
29
 
30
30
  private
31
- def component_args(component_name, project_name)
31
+ def component_args(component_name, project_name=nil)
32
32
  args = [
33
33
  component_name,
34
34
  "--project-name", project_name,
@@ -9,7 +9,8 @@ module Terraspace::CLI::New::Source
9
9
 
10
10
  def set_core_source(template, type=nil)
11
11
  template_name = template_name(template, type)
12
- template_path = File.expand_path("../../../../templates/#{template_name}", __dir__)
12
+ folder = @options[:examples] && type != "project" ? "examples/#{template_name}" : template_name
13
+ template_path = File.expand_path("../../../../templates/#{folder}", __dir__)
13
14
  override_source_paths(template_path)
14
15
  end
15
16
 
@@ -18,11 +19,14 @@ module Terraspace::CLI::New::Source
18
19
  end
19
20
 
20
21
  def require_gem(name)
22
+ return unless name # can be passed in name=nil with rspec test harness
21
23
  begin
24
+ # Need to clear gem paths since installing plugins like terraspace_plugin_aws as part of terraspace new project
25
+ Gem.clear_paths
22
26
  require name # require plugin for the templates, this registers the plugin
23
27
  rescue LoadError => e
24
28
  puts "#{e.class}: #{e.message}".color(:red)
25
- logger.error "ERROR: Unable to require plugin #{name}.".color(:red)
29
+ logger.error "ERROR: Unable to require plugin #{name}".color(:red)
26
30
  puts "Are you sure you the plugin exists and you specified the right plugin option."
27
31
  puts "You specified --plugin #{@options[:plugin]}"
28
32
  exit 1
@@ -32,10 +36,7 @@ module Terraspace::CLI::New::Source
32
36
  def set_plugin_gem_source(template, type)
33
37
  require_gem(plugin_gem_name)
34
38
  plugin = Terraspace::Plugin.find_with(plugin: @options[:plugin])
35
- unless plugin
36
- puts "ERROR: Unable to a find plugin for #{@options[:plugin]}. Are you sure the gem for the plugin is correct?".color(:red)
37
- exit 1
38
- end
39
+ return unless plugin # can be passed in name=nil with rspec test harness
39
40
  template_name = template_name(template, type)
40
41
  template_path = File.expand_path("#{plugin.root}/lib/templates/#{template_name}")
41
42
  override_source_paths(template_path)
@@ -9,12 +9,12 @@ module Terraspace::CLI::New::Source
9
9
  # project always uses the examples from the provider gem for configs
10
10
  # base always uses terraspace core templates
11
11
  # examples option always use examples from provider gems
12
- if (type == "project" || @options[:examples]) && template != "base"
12
+ if (type == "project" || @options[:examples]) && template != "base" &&
13
+ (@options[:plugin] != "none" && !@options[:plugin].nil?)
13
14
  set_plugin_gem_source(template, type) # provider gems has examples
14
15
  else
15
16
  set_core_source(template, type) # terraspace core has empty starter files
16
17
  end
17
18
  end
18
-
19
19
  end
20
20
  end
@@ -2,10 +2,11 @@ class Terraspace::CLI::New
2
2
  class Stack < Sequence
3
3
  component_options.each { |args| class_option(*args) }
4
4
 
5
- argument :name
5
+ # default so terraspace new example works without a Thor warning
6
+ argument :name, default: "demo"
6
7
 
7
8
  def create_stack
8
- puts "=> Creating new stack called #{name}."
9
+ puts "=> Creating new stack called #{name}"
9
10
  plugin_template_source(@options[:lang], "stack") # IE: plugin_template_source("hcl", "stack")
10
11
  dest = "app/stacks/#{name}"
11
12
  dest = "#{@options[:project_name]}/#{dest}" if @options[:project_name]
@@ -3,13 +3,13 @@ class Terraspace::CLI::New
3
3
  include Thor::Actions
4
4
  include Terraspace::CLI::New::Helpers
5
5
 
6
- argument :name
6
+ argument :name, required: false
7
7
 
8
8
  def self.options
9
9
  [
10
10
  [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
11
11
  [:test_name, desc: "Test name. Defaults to the project, module or stack name"],
12
- [:type, default: "project", desc: "project, stack or module"],
12
+ [:type, default: "stack", desc: "project, stack or module"],
13
13
  ]
14
14
  end
15
15
  options.each { |args| class_option(*args) }
@@ -42,9 +42,19 @@ class Terraspace::CLI::New
42
42
  public
43
43
 
44
44
  def create
45
+ if type != 'project' && name.nil?
46
+ puts "ERROR: require NAME for type stack and module".color(:red)
47
+ exit 1
48
+ end
45
49
  test_template_source(@options[:lang], type)
46
50
  puts "=> Creating #{type} test: #{name}"
47
51
  directory ".", dest
48
52
  end
53
+
54
+ # Used in ERB template
55
+ # rspec-terraspace/lib/templates/stack/test/spec/fixtures/config/terraform/provider.tf.tt
56
+ def autodetect_provider
57
+ Terraspace::Autodetect.new.plugin
58
+ end
49
59
  end
50
60
  end
@@ -42,5 +42,9 @@ class Terraspace::CLI
42
42
  long_desc Help.text("new/test")
43
43
  Test.options.each { |args| option(*args) }
44
44
  register(Test, "test", "test NAME", "Generates new test.")
45
+
46
+ long_desc Help.text("new/example")
47
+ Example.options.each { |args| option(*args) }
48
+ register(Example, "example", "example [NAME]", "Generates new example.")
45
49
  end
46
50
  end
@@ -1,5 +1,5 @@
1
- class Terraspace::CLI
2
- class CheckSetup
1
+ class Terraspace::CLI::Setup
2
+ class Check
3
3
  extend Memoist
4
4
 
5
5
  # Terraspace requires at least this version of terraform
@@ -83,7 +83,9 @@ class Terraspace::CLI
83
83
  #
84
84
  # Note: The -json option is only available in v0.13+
85
85
  def terraform_version_message
86
- `terraform --version`.split("\n").find { |l| l =~ /^Terraform / }.strip
86
+ out = `terraform --version`
87
+ version = out.split("\n").find { |l| l =~ /^Terraform / }
88
+ version.strip if version
87
89
  end
88
90
  memoize :terraform_version_message
89
91
 
@@ -0,0 +1,9 @@
1
+ class Terraspace::CLI
2
+ class Setup < Terraspace::Command
3
+ desc "check", "Check setup is ok"
4
+ long_desc Help.text("setup/check")
5
+ def check
6
+ Check.new(options).run
7
+ end
8
+ end
9
+ end
@@ -7,7 +7,7 @@ class Terraspace::CLI
7
7
  end
8
8
 
9
9
  def backend
10
- Terraspace::Compiler::Backend::Parser.new(@mod).result
10
+ Terraspace::Terraform::Runner::Backend::Parser.new(@mod).result
11
11
  end
12
12
  memoize :backend
13
13
  end
@@ -1,14 +1,13 @@
1
1
  module Terraspace
2
2
  class CLI < Command
3
+ include Concern
4
+
3
5
  class_option :verbose, type: :boolean
4
6
  class_option :noop, type: :boolean
5
7
 
6
8
  yes_option = Proc.new {
7
9
  option :yes, aliases: :y, type: :boolean, desc: "-auto-approve the terraform apply"
8
10
  }
9
- format_option = Proc.new {
10
- option :format, desc: "output formats: json, text"
11
- }
12
11
  out_option = Proc.new {
13
12
  option :out, aliases: :o, desc: "Write the output to path"
14
13
  }
@@ -43,6 +42,10 @@ module Terraspace
43
42
  long_desc Help.text(:new)
44
43
  subcommand "new", New
45
44
 
45
+ desc "setup SUBCOMMAND", "setup subcommands"
46
+ long_desc Help.text(:setup)
47
+ subcommand "setup", Setup
48
+
46
49
  desc "tfc SUBCOMMAND", "tfc subcommands"
47
50
  long_desc Help.text(:tfc)
48
51
  subcommand "tfc", Tfc
@@ -62,17 +65,23 @@ module Terraspace
62
65
  Bundle.new(options.merge(args: args)).run
63
66
  end
64
67
 
65
- desc "check_setup", "Check setup."
68
+ desc "check_setup", "Check setup.", hide: true
66
69
  long_desc Help.text(:check_setup)
67
70
  def check_setup
68
- CheckSetup.new(options).run
71
+ puts <<~EOL
72
+ DEPRECATED: The terraspace check_setup command is deprecated. Instead use:
73
+
74
+ terraspace setup check
75
+
76
+ EOL
77
+ Setup::Check.new(options).run
69
78
  end
70
79
 
71
80
  desc "console STACK", "Run console in built terraform project."
72
81
  long_desc Help.text(:console)
73
82
  instance_option.call
74
- def console(mod)
75
- Commander.new("console", options.merge(mod: mod, shell: "system")).run
83
+ def console(mod, *args)
84
+ Commander.new("console", options.merge(mod: mod, args: args, shell: "system")).run
76
85
  end
77
86
 
78
87
  desc "down STACK", "Destroy infrastructure stack."
@@ -81,8 +90,8 @@ module Terraspace
81
90
  yes_option.call
82
91
  reconfigure_option.call
83
92
  option :destroy_workspace, type: :boolean, desc: "Also destroy the Cloud workspace. Only applies when using Terraform Cloud remote backend."
84
- def down(mod)
85
- Down.new(options.merge(mod: mod)).run
93
+ def down(mod, *args)
94
+ Down.new(options.merge(mod: mod, args: args)).run
86
95
  end
87
96
 
88
97
  desc "force_unlock", "Calls terrform force-unlock"
@@ -111,8 +120,8 @@ module Terraspace
111
120
  desc "init STACK", "Run init in built terraform project."
112
121
  long_desc Help.text(:init)
113
122
  instance_option.call
114
- def init(mod)
115
- Commander.new("init", options.merge(mod: mod, quiet: false)).run
123
+ def init(mod, *args)
124
+ Commander.new("init", options.merge(mod: mod, args: args, quiet: false)).run
116
125
  end
117
126
 
118
127
  desc "list", "List stacks and modules."
@@ -141,22 +150,22 @@ module Terraspace
141
150
  out_option.call
142
151
  reconfigure_option.call
143
152
  option :copy_to_root, type: :boolean, default: true, desc: "Copy plan file generated in the cache folder back to project root"
144
- def plan(mod)
145
- Commander.new("plan", options.merge(mod: mod)).run
153
+ def plan(mod, *args)
154
+ Commander.new("plan", options.merge(mod: mod, args: args)).run
146
155
  end
147
156
 
148
157
  desc "providers STACK", "Show providers."
149
158
  long_desc Help.text(:providers)
150
159
  instance_option.call
151
- def providers(mod)
152
- Commander.new("providers", options.merge(mod: mod)).run
160
+ def providers(mod, *args)
161
+ Commander.new("providers", options.merge(mod: mod, args: args)).run
153
162
  end
154
163
 
155
164
  desc "refresh STACK", "Run refresh."
156
165
  long_desc Help.text(:refresh)
157
166
  instance_option.call
158
- def refresh(mod)
159
- Commander.new("refresh", options.merge(mod: mod)).run
167
+ def refresh(mod, *args)
168
+ Commander.new("refresh", options.merge(mod: mod, args: args)).run
160
169
  end
161
170
 
162
171
  desc "seed STACK", "Build starer seed tfvars file."
@@ -182,9 +191,8 @@ module Terraspace
182
191
  long_desc Help.text(:show)
183
192
  instance_option.call
184
193
  option :plan, desc: "path to created.plan"
185
- option :json, type: :boolean, desc: "show plan in json format"
186
- def show(mod)
187
- Commander.new("show", options.merge(mod: mod)).run
194
+ def show(mod, *args)
195
+ Commander.new("show", options.merge(mod: mod, args: args)).run
188
196
  end
189
197
 
190
198
  desc "state SUBCOMMAND STACK", "Run state."
@@ -201,11 +209,10 @@ module Terraspace
201
209
 
202
210
  desc "output STACK", "Run output."
203
211
  long_desc Help.text(:output)
204
- format_option.call
205
212
  instance_option.call
206
213
  out_option.call
207
- def output(mod)
208
- Commander.new("output", options.merge(mod: mod)).run
214
+ def output(mod, *args)
215
+ Commander.new("output", options.merge(mod: mod, args: args)).run
209
216
  end
210
217
 
211
218
  desc "up STACK", "Deploy infrastructure stack."
@@ -218,15 +225,15 @@ module Terraspace
218
225
  reconfigure_option.call
219
226
  option :plan, desc: "Execution plan that can be used to only execute a pre-determined set of actions."
220
227
  option :var_files, type: :array, desc: "list of var files"
221
- def up(mod)
222
- Up.new(options.merge(mod: mod)).run
228
+ def up(mod, *args)
229
+ Up.new(options.merge(mod: mod, args: args)).run
223
230
  end
224
231
 
225
232
  desc "validate STACK", "Validate stack."
226
233
  long_desc Help.text(:validate)
227
234
  instance_option.call
228
- def validate(mod)
229
- Commander.new("validate", options.merge(mod: mod)).run
235
+ def validate(mod, *args)
236
+ Commander.new("validate", options.merge(mod: mod, args: args)).run
230
237
  end
231
238
 
232
239
  desc "completion *PARAMS", "Prints words for auto-completion."
@@ -71,7 +71,7 @@ module Terraspace
71
71
  version_manager = "rvm" if rvm?
72
72
  version_manager = "rbenv" if rbenv?
73
73
  if rbenv? || rvm?
74
- puts <<~EOL.color(:yellow)
74
+ $stderr.puts <<~EOL.color(:yellow)
75
75
  WARN: It looks like a standalone Terraspace install and #{version_manager} is also in use.
76
76
  Different gems from the standalone Terraspace install and #{version_manager} can cause all kinds of trouble.
77
77
  Please install Terraspace as a gem instead and remove the standalone Terraspace /opt/terraspace installation.
@@ -102,8 +102,9 @@ module Terraspace
102
102
  return if subcommand?
103
103
  return if command_name.nil?
104
104
  return if help_flags.include?(Terraspace.argv.last) # IE: -h help
105
- return if %w[-h -v check_setup completion completion_script help new test version].include?(command_name)
105
+ return if %w[-h -v --version check_setup completion completion_script help new setup test version].include?(command_name)
106
106
  return if File.exist?("#{Terraspace.root}/config/app.rb")
107
+ return unless Terraspace.check_project
107
108
  logger.error "ERROR: It doesnt look like this is a terraspace project. Are you sure you are in a terraspace project?".color(:red)
108
109
  ENV['TS_TEST'] ? raise : exit(1)
109
110
  end
@@ -1,13 +1,5 @@
1
1
  module Terraspace::Compiler
2
2
  module CommandsConcern
3
- def requires_backend?
4
- command_is?(requires_backend_commands)
5
- end
6
-
7
- def requires_backend_commands
8
- %w[down init output plan providers refresh show up validate]
9
- end
10
-
11
3
  def command_is?(*commands)
12
4
  commands.flatten!
13
5
  commands.map!(&:to_s)
@@ -0,0 +1,44 @@
1
+ # Simpler than Terraspace::Terraform::Runner::Backend::Parser because
2
+ # Terraspace::Compiler::Expander autodetect backend super early on.
3
+ # It's so early that don't want helper methods like <%= expansion(...) %> to be called.
4
+ # Calling the expansion helper itself results in Terraspace autodetecting a concrete
5
+ # Terraspace Plugin Expander, which creates an infinite loop.
6
+ # This simple detection class avoids calling ERB and avoids the infinite loop.
7
+ class Terraspace::Compiler::Expander
8
+ class Backend
9
+ extend Memoist
10
+
11
+ def initialize(mod)
12
+ @mod = mod
13
+ end
14
+
15
+ COMMENT = /^\s+#/
16
+ # Works for both backend.rb DSL and backend.tf ERB
17
+ def detect
18
+ return nil unless src_path # no backend file. returning nil means a local backend
19
+ lines = IO.readlines(src_path)
20
+ backend_line = lines.find { |l| l.include?("backend") && l !~ COMMENT }
21
+ md = backend_line.match(/['"](.*)['"]/)
22
+ md[1] if md
23
+ end
24
+
25
+ private
26
+ # Use original unrendered source as wont know the
27
+ # @mod.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
28
+ # Until the concrete Terraspace Plugin Expander has been autodetected.
29
+ # Follow same precedence rules as rest of Terraspace.
30
+ def src_path
31
+ exprs = [
32
+ "app/stacks/#{@mod.build_dir}/backend.*",
33
+ "app/modules/#{@mod.build_dir}/backend.*",
34
+ "vendor/stacks/#{@mod.build_dir}/backend.*",
35
+ "vendor/modules/#{@mod.build_dir}/backend.*",
36
+ "config/terraform/backend.*",
37
+ ]
38
+ path = nil
39
+ exprs.find { |expr| path = Dir.glob(expr).first }
40
+ path
41
+ end
42
+ memoize :src_path
43
+ end
44
+ end
@@ -1,17 +1,21 @@
1
1
  module Terraspace::Compiler
2
2
  class Expander
3
+ extend Memoist
3
4
  delegate :expand, :expansion, to: :expander
4
5
 
5
- attr_reader :expander
6
6
  def initialize(mod, name)
7
7
  @mod, @name = mod, name
8
- @expander = expander_class.new(@mod)
9
8
  end
10
9
 
10
+ def expander
11
+ expander_class.new(@mod)
12
+ end
13
+ memoize :expander
14
+
11
15
  def expander_class
12
16
  # IE: TerraspacePluginAws::Interfaces::Expander
13
17
  klass_name = Terraspace::Plugin.klass("Expander", backend: @name)
14
- klass_name.constantize if klass_name
18
+ klass_name ? klass_name.constantize : Terraspace::Plugin::Expander::Generic
15
19
  rescue NameError
16
20
  Terraspace::Plugin::Expander::Generic
17
21
  end
@@ -20,27 +24,15 @@ module Terraspace::Compiler
20
24
  extend Memoist
21
25
 
22
26
  def autodetect(mod, opts={})
23
- backend = opts[:backend]
24
- unless backend
25
- plugin = find_plugin
26
- backend = plugin[:backend]
27
- end
27
+ backend = opts[:backend] || find_backend(mod)
28
28
  new(mod, backend)
29
29
  end
30
30
  memoize :autodetect
31
31
 
32
- def find_plugin
33
- plugins = Terraspace::Plugin.meta
34
- if plugins.size == 1
35
- plugins.first[1]
36
- else
37
- precedence = %w[aws azurerm google]
38
- plugin = precedence.find do |provider|
39
- plugins[provider]
40
- end
41
- plugins[plugin]
42
- end
32
+ def find_backend(mod)
33
+ Backend.new(mod).detect
43
34
  end
35
+ memoize :find_backend
44
36
  end
45
37
  end
46
38
  end
@@ -68,5 +68,15 @@ module Terraspace
68
68
  def argv
69
69
  @@argv
70
70
  end
71
+
72
+ @@check_project = true
73
+ def check_project
74
+ @@check_project
75
+ end
76
+
77
+ # allow testing frameworks to disable
78
+ def check_project=(v)
79
+ @@check_project = v
80
+ end
71
81
  end
72
82
  end
@@ -4,6 +4,7 @@ module Terraspace::Plugin::Expander
4
4
  # Terraspace::Compiler::Strategy::Tfvar::Layer
5
5
  # Terraspace::Plugin::Expander::Interface
6
6
  def friendly_name(name)
7
+ return '' if name.nil?
7
8
  Terraspace.config.layering.names[name.to_sym] || name
8
9
  end
9
10
  end