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
@@ -70,12 +70,19 @@ 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
- def var_value(name)
77
- name = name.sub(':','').downcase
78
- value = send(name)
79
+ def var_value(unexpanded)
80
+ name = unexpanded.sub(':','').downcase
81
+ if respond_to?(name)
82
+ value = send(name).to_s
83
+ else
84
+ return unexpanded
85
+ end
79
86
  if name == "namespace" && Terraspace.config.layering.enable_names.expansion
80
87
  value = friendly_name(value)
81
88
  end
@@ -102,5 +109,13 @@ module Terraspace::Plugin::Expander
102
109
  def cache_root
103
110
  Terraspace.cache_root
104
111
  end
112
+
113
+ # So default config works:
114
+ # config.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR"
115
+ # For when folks configure it with the http backend for non-cloud providers
116
+ # The double slash // will be replace with a single slash in expander/interface.rb
117
+ def region
118
+ ''
119
+ end
105
120
  end
106
121
  end
@@ -16,7 +16,7 @@ class Terraspace::Terraform::Api
16
16
 
17
17
  # backend may be overridden in classes including this Concern
18
18
  def backend
19
- Terraspace::Compiler::Backend::Parser.new(@mod).result
19
+ Terraspace::Terraform::Runner::Backend::Parser.new(@mod).result
20
20
  end
21
21
  memoize :backend
22
22
 
@@ -18,23 +18,28 @@ module Terraspace::Terraform::Args
18
18
  memoize :build
19
19
 
20
20
  def args
21
+ terraform_args + var_file_args
22
+ end
23
+
24
+ def env_vars
25
+ build
26
+ dig("env", {})
27
+ end
28
+
29
+ private
30
+ def terraform_args
21
31
  build
22
32
  args = dig("args")
23
33
  args.compact.flatten
24
34
  end
25
35
 
26
- def var_files
36
+ def var_file_args
27
37
  build
28
38
  var_files = dig("var_files")
29
39
  var_files.select! { |f| var_file_exist?(f) }
30
40
  var_files.map { |f| "-var-file=#{f}" }
31
41
  end
32
42
 
33
- def env_vars
34
- build
35
- dig("env", {})
36
- end
37
-
38
43
  def var_file_exist?(var_file)
39
44
  File.exist?("#{@mod.cache_dir}/#{var_file}")
40
45
  end
@@ -0,0 +1,110 @@
1
+ module Terraspace::Terraform::Args
2
+ class Pass
3
+ def initialize(mod, name, options={})
4
+ @mod, @name, @options = mod, name.underscore, options
5
+ end
6
+
7
+ # map to terraform options and only allow valid terraform options
8
+ # Arg Examples:
9
+ # -refresh-only
10
+ # -refresh=false
11
+ # -var 'foo=bar'
12
+ def args
13
+ args = pass_args.select do |arg|
14
+ arg_name = get_arg_name(arg)
15
+ terraform_arg_types.include?(arg_name)
16
+ end
17
+
18
+ args.map { |arg| "-#{arg}" } # add back in the leading single dash (-)
19
+ end
20
+
21
+ private
22
+ def pass_args
23
+ args = (@options[:args] || []).map do |o|
24
+ o.sub(/^-{1,2}/,'') # strips the - or --
25
+ end
26
+ reconstruct_hash_args(args)
27
+ end
28
+
29
+ # Thor parses CLI args -var 'foo=bar'
30
+ # as:
31
+ # ["-var", "foo=bar"]
32
+ # instead of:
33
+ # ["-var 'foo=bar'"]
34
+ #
35
+ # So reconstruct it to what works with terraform CLI args
36
+ def reconstruct_hash_args(args)
37
+ result = []
38
+ flatten = []
39
+ skip = false
40
+ args.each do |arg|
41
+ arg_type = terraform_arg_types[arg]
42
+ if arg_type == :hash || skip
43
+ skip = true
44
+ if flatten.size == 1
45
+ flatten << "'#{arg}'" # surround hash value with single quotes
46
+ else
47
+ flatten << arg
48
+ end
49
+ if flatten.size == 2 # time to grab value and end skipping
50
+ result << flatten.join(' ')
51
+ # reset flags
52
+ skip = false
53
+ flatten = []
54
+ end
55
+ next if skip
56
+ else
57
+ result << arg # boolean (-no-color) or assignment (-refresh=false)
58
+ end
59
+ end
60
+ result
61
+ end
62
+
63
+ # Parses terraform COMMAND -help output for arg types.
64
+ # Return Example:
65
+ #
66
+ # {
67
+ # destroy: :boolean,
68
+ # refresh: :assignment,
69
+ # var: :hash,
70
+ # }
71
+ #
72
+ def terraform_arg_types
73
+ out = terraform_help(@name)
74
+ lines = out.split("\n")
75
+ lines.select! do |line|
76
+ line =~ /^ -/
77
+ end
78
+ lines.inject({}) do |result, line|
79
+ # in: " -replace=resource Force replacement of a particular resource instance using",
80
+ # out: "-replace=resource Force replacement of a particular resource instance using",
81
+ line.sub!(' -', '') # remove leading whitespace and -
82
+
83
+ # in: " -var 'foo=bar' Set a value for one of the input variables in the root",
84
+ # out: "var"
85
+ # in: " -refresh=false Skip checking for external changes to remote objects",
86
+ # out: "refresh"
87
+ arg_name = get_arg_name(line)
88
+
89
+ if line.match(/'\w+=\w+'/) # hash. IE: -var 'foo=bar'
90
+ result[arg_name] = :hash
91
+ elsif line.match(/\w+=/) # value IE: -refresh=false
92
+ result[arg_name] = :assignment # can include string and numeric values
93
+ else # boolean IE: -refresh-only
94
+ result[arg_name] = :boolean
95
+ end
96
+ result
97
+ end
98
+ end
99
+
100
+ def get_arg_name(line)
101
+ line.sub(/\s+.*/,'').split('=').first # strips everything except arg name only
102
+ end
103
+
104
+ @@terraform_help = {}
105
+ def terraform_help(name)
106
+ @@terraform_help[name] ||= `terraform #{name} -help`
107
+ end
108
+ end
109
+ end
110
+
@@ -1,7 +1,7 @@
1
1
  require "tempfile"
2
2
 
3
3
  module Terraspace::Terraform::Args
4
- class Default
4
+ class Thor
5
5
  def initialize(mod, name, options={})
6
6
  @mod, @name, @options = mod, name.underscore, options
7
7
  @quiet = @options[:quiet].nil? ? true : @options[:quiet]
@@ -81,7 +81,6 @@ module Terraspace::Terraform::Args
81
81
 
82
82
  def output_args
83
83
  args = []
84
- args << "-json" if @options[:format] == "json"
85
84
  args << "> #{expanded_out}" if @options[:out]
86
85
  args
87
86
  end
@@ -98,7 +97,6 @@ module Terraspace::Terraform::Args
98
97
 
99
98
  def show_args
100
99
  args = []
101
- args << " -json" if @options[:json]
102
100
  plan = @options[:plan]
103
101
  if plan
104
102
  copy_to_cache(@options[:plan])
@@ -1,6 +1,6 @@
1
1
  require "hcl_parser"
2
2
 
3
- class Terraspace::Compiler::Backend
3
+ class Terraspace::Terraform::Runner::Backend
4
4
  class Parser
5
5
  extend Memoist
6
6
 
@@ -1,6 +1,7 @@
1
- module Terraspace::Compiler
1
+ class Terraspace::Terraform::Runner
2
2
  class Backend
3
3
  extend Memoist
4
+ include Terraspace::Compiler::CommandsConcern
4
5
 
5
6
  def initialize(mod)
6
7
  @mod = mod
@@ -9,6 +10,9 @@ module Terraspace::Compiler
9
10
  @@created = {}
10
11
  def create
11
12
  return if @@created[cache_key]
13
+ return if Terraspace.config.auto_create_backend == false
14
+ return unless requires_backend?
15
+
12
16
  # set immediately, since local storage wont reach bottom.
13
17
  # if fail for other backends, there will be an exception anyway
14
18
  @@created[cache_key] = true
@@ -16,6 +20,7 @@ module Terraspace::Compiler
16
20
  klass = backend_interface(backend_name)
17
21
  return unless klass # in case auto-creation is not supported for specific backend
18
22
 
23
+ # IE: TerraspacePluginAws::Interfaces::Backend.new
19
24
  interface = klass.new(backend_info)
20
25
  interface.call
21
26
  end
@@ -44,5 +49,13 @@ module Terraspace::Compiler
44
49
  klass_name.constantize if klass_name
45
50
  rescue NameError
46
51
  end
52
+
53
+ def requires_backend?
54
+ command_is?(requires_backend_commands)
55
+ end
56
+
57
+ def requires_backend_commands
58
+ %w[down init output plan providers refresh show up validate]
59
+ end
47
60
  end
48
61
  end
@@ -9,7 +9,7 @@ class Terraspace::Terraform::Runner
9
9
  end
10
10
 
11
11
  def retry?
12
- max_retries = ENV['TS_MAX_RETRIES'] ? ENV['TS_MAX_RETRIES'].to_i : 3
12
+ max_retries = ENV['TS_MAX_RETRIES'] ? ENV['TS_MAX_RETRIES'].to_i : 1
13
13
  if @retries <= max_retries && !@stop_retrying
14
14
  true # will retry
15
15
  else
@@ -16,12 +16,37 @@ module Terraspace::Terraform
16
16
  end
17
17
  end
18
18
 
19
+ # default at end in case of redirection. IE: terraform output > /path
20
+ def args
21
+ args = custom.args + pass.args + thor.args
22
+ args.uniq
23
+ end
24
+
25
+ # From config/args/terraform.rb https://terraspace.cloud/docs/config/args/terraform/
26
+ def custom
27
+ Args::Custom.new(@mod, @name)
28
+ end
29
+ memoize :custom
30
+
31
+ # From Thor passthrough cli @options[:args]
32
+ def pass
33
+ Args::Pass.new(@mod, @name, @options)
34
+ end
35
+ memoize :pass
36
+
37
+ # From Thor defined/managed cli @options
38
+ def thor
39
+ Args::Thor.new(@mod, @name, @options)
40
+ end
41
+ memoize :thor
42
+
19
43
  def terraform(name, *args)
20
44
  current_dir_message # only show once
21
45
 
22
46
  params = args.flatten.join(' ')
23
47
  command = "terraform #{name} #{params}".squish
24
48
  run_hooks("terraform.rb", name) do
49
+ Backend.new(@mod).create
25
50
  run_internal_hook(:before, name)
26
51
  Terraspace::Shell.new(@mod, command, @options.merge(env: custom.env_vars)).run
27
52
  run_internal_hook(:after, name)
@@ -58,21 +83,6 @@ module Terraspace::Terraform
58
83
  @options[:quiet] ? logger.debug(msg) : logger.info(msg)
59
84
  end
60
85
 
61
- def args
62
- # base at end in case of redirection. IE: terraform output > /path
63
- custom.args + custom.var_files + default.args
64
- end
65
-
66
- def custom
67
- Args::Custom.new(@mod, @name)
68
- end
69
- memoize :custom
70
-
71
- def default
72
- Args::Default.new(@mod, @name, @options)
73
- end
74
- memoize :default
75
-
76
86
  private
77
87
  def time_took
78
88
  t1 = Time.now
@@ -35,7 +35,7 @@ module Terraspace::Terraform::Tfc
35
35
 
36
36
  # already memoized in Api::Client
37
37
  def backend
38
- Terraspace::Compiler::Backend::Parser.new(@mod).result
38
+ Terraspace::Terraform::Runner::Backend::Parser.new(@mod).result
39
39
  end
40
40
  end
41
41
  end
@@ -1,3 +1,3 @@
1
1
  module Terraspace
2
- VERSION = "0.6.23"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -20,10 +20,10 @@ describe Terraspace::Terraform::Args::Custom do
20
20
  expect(custom.args).to eq(["-lock-timeout=20m"])
21
21
  end
22
22
 
23
- it "var_files" do
23
+ it "var_file_args" do
24
24
  custom.evaluate_file(file)
25
25
  allow(custom).to receive(:var_file_exist?).and_return(true)
26
- expect(custom.var_files).to eq(["-var-file=a.tfvars", "-var-file=b.tfvars"])
26
+ expect(custom.send(:var_file_args)).to eq(["-var-file=a.tfvars", "-var-file=b.tfvars"])
27
27
  end
28
28
  end
29
29
 
@@ -41,10 +41,10 @@ describe Terraspace::Terraform::Args::Custom do
41
41
  expect(custom.args).to eq(["-lock-timeout=20m"])
42
42
  end
43
43
 
44
- it "var_files" do
44
+ it "var_file_args" do
45
45
  custom.evaluate_file(file)
46
46
  allow(custom).to receive(:var_file_exist?).and_return(true)
47
- expect(custom.var_files).to eq([])
47
+ expect(custom.send(:var_file_args)).to eq([])
48
48
  end
49
49
  end
50
50
  end
@@ -0,0 +1,101 @@
1
+ describe Terraspace::Terraform::Args::Pass do
2
+ let(:pass) do
3
+ described_class.new(mod, name, options)
4
+ end
5
+ let(:mod) { double(:mod).as_null_object }
6
+ let(:options) { {args: args} }
7
+ # defaults
8
+ let(:name) { "plan" }
9
+ let(:args) { [] }
10
+
11
+ context "boolean flag" do
12
+ let(:args) { ["-refresh-only"] }
13
+ it "pass through args" do
14
+ args = pass.args
15
+ expect(args).to eq ["-refresh-only"]
16
+ end
17
+ end
18
+
19
+ context "assignment arg" do
20
+ context "-refresh=false" do
21
+ let(:args) { ["-refresh=false"] }
22
+ it "pass through args" do
23
+ args = pass.args
24
+ expect(args).to eq ["-refresh=false"]
25
+ end
26
+ end
27
+
28
+ context "-lock-timeout=5s" do
29
+ let(:args) { ["-lock-timeout=5s"] }
30
+ it "pass through args" do
31
+ args = pass.args
32
+ expect(args).to eq ["-lock-timeout=5s"]
33
+ end
34
+ end
35
+
36
+ context "-var-file=filename -lock-timeout=5s" do
37
+ let(:args) { ["-var-file=filename', '-lock-timeout=5s"] }
38
+ it "pass through args" do
39
+ args = pass.args
40
+ expect(args).to eq ["-var-file=filename', '-lock-timeout=5s"]
41
+ end
42
+ end
43
+
44
+ # normalizes -- to -
45
+ context "--var-file=filename --lock-timeout=5s" do
46
+ let(:args) { ["--var-file=filename", "--lock-timeout=5s"] }
47
+ it "pass through args" do
48
+ args = pass.args
49
+ expect(args).to eq ["-var-file=filename", "-lock-timeout=5s"]
50
+ end
51
+ end
52
+ end
53
+
54
+ context "hash arg" do
55
+ context "-var 'foo=bar'" do
56
+ let(:args) { ["-var 'foo=bar'"] }
57
+ it "pass through args" do
58
+ args = pass.args
59
+ expect(args).to eq ["-var 'foo=bar'"]
60
+ end
61
+ end
62
+
63
+ context "['-var', 'foo=bar'] improperly passed due to Thor CLI parsing" do
64
+ let(:args) { ["-var", "foo=bar"] }
65
+ it "reconstruct to work with terraform cli" do
66
+ args = pass.send(:pass_args)
67
+ expect(args).to eq ["var 'foo=bar'"]
68
+ end
69
+ end
70
+ end
71
+
72
+ # Works at the parsing level. Also works at Thor CLI parsing level. To test:
73
+ #
74
+ # terraspace plan demo -refresh=false -no-color -var 'foo=bar' -var 'foo2=bar2'
75
+ #
76
+ # Results in:
77
+ #
78
+ # terraform plan -input=false -refresh=false -no-color -var 'foo=bar' -var 'foo2=bar2'
79
+ #
80
+ context "multiple hash arg" do
81
+ context "-var 'foo=bar' -var 'foo2=bar2'" do
82
+ let(:args) { ["-var 'foo=bar'", "-var 'foo2=bar2'"] }
83
+ it "pass through args" do
84
+ args = pass.args
85
+ expect(args).to eq ["-var 'foo=bar'", "-var 'foo2=bar2'"]
86
+ end
87
+ end
88
+ end
89
+
90
+ # Could mock out Pass#terraform_help with fixtures,
91
+ # but will call out to terraform in specs so to see breakage more quickly.
92
+ context "terraform_arg_types" do
93
+ it "parses help output to determine arg types" do
94
+ args = pass.send(:terraform_arg_types)
95
+ # checking specific keys so spec is more robust to terraform cli changes
96
+ expect(args['destroy']).to eq(:boolean)
97
+ expect(args['refresh']).to eq(:assignment)
98
+ expect(args['var']).to eq(:hash)
99
+ end
100
+ end
101
+ end
data/terraspace.gemspec CHANGED
@@ -29,15 +29,13 @@ Gem::Specification.new do |spec|
29
29
  spec.add_dependency "memoist"
30
30
  spec.add_dependency "rainbow"
31
31
  spec.add_dependency "render_me_pretty"
32
- spec.add_dependency "terraspace-bundler", "~> 0.4.0"
32
+ spec.add_dependency "rexml"
33
+ spec.add_dependency "terraspace-bundler", "~> 0.4.4"
33
34
  spec.add_dependency "thor"
34
35
  spec.add_dependency "tty-tree"
35
36
  spec.add_dependency "zeitwerk"
36
37
 
37
38
  # core baseline plugins
38
- spec.add_dependency "terraspace_plugin_aws", "~> 0.3.0"
39
- spec.add_dependency "terraspace_plugin_azurerm", "~> 0.3.0"
40
- spec.add_dependency "terraspace_plugin_google", "~> 0.3.0"
41
39
  spec.add_dependency "rspec-terraspace", "~> 0.3.0"
42
40
 
43
41
  spec.add_development_dependency "bundler"