terraspace 0.6.23 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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 +28 -0
  17. data/Gemfile +6 -0
  18. data/README.md +1 -1
  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 +13 -2
  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
@@ -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
@@ -70,12 +70,15 @@ module Terraspace::Plugin::Expander
70
70
  #
71
71
  def strip(string)
72
72
  string.sub(/^-+/,'').sub(/-+$/,'') # remove leading and trailing -
73
- .sub(%r{/+$},'') # only remove trailing / or else /home/ec2-user => home/ec2-user
73
+ .sub(%r{/+$},'') # only remove trailing / or else /home/ec2-user => home/ec2-user
74
+ .sub(/:\/\//, 'TMP_KEEP_HTTP') # so we can keep ://. IE: https:// or http://
75
+ .gsub(%r{/+},'/') # remove double slashes are more. IE: // -> / Useful of region is '' in generic expander
76
+ .sub('TMP_KEEP_HTTP', '://') # restore :// IE: https:// or http://
74
77
  end
75
78
 
76
79
  def var_value(name)
77
80
  name = name.sub(':','').downcase
78
- value = send(name)
81
+ value = send(name).to_s
79
82
  if name == "namespace" && Terraspace.config.layering.enable_names.expansion
80
83
  value = friendly_name(value)
81
84
  end
@@ -102,5 +105,13 @@ module Terraspace::Plugin::Expander
102
105
  def cache_root
103
106
  Terraspace.cache_root
104
107
  end
108
+
109
+ # So default config works:
110
+ # config.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
111
+ # For when folks configure it with the http backend for non-cloud providers
112
+ # The double slash // will be replace with a single slash in expander/interface.rb
113
+ def region
114
+ ''
115
+ end
105
116
  end
106
117
  end