terraspace 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/lib/templates/base/git_hook/hook.sh +5 -0
  4. data/lib/templates/base/project/.gitignore +0 -1
  5. data/lib/templates/base/shim/terraspace +7 -0
  6. data/lib/templates/hcl/project/config/terraform/backend.tf.tt +3 -3
  7. data/lib/templates/plugin/lib/templates/hcl/project/config/terraform/backend.tf.tt +2 -2
  8. data/lib/terraspace/app.rb +6 -0
  9. data/lib/terraspace/builder.rb +2 -46
  10. data/lib/terraspace/cli.rb +34 -18
  11. data/lib/terraspace/cli/build/placeholder.rb +40 -0
  12. data/lib/terraspace/cli/cloud.rb +24 -0
  13. data/lib/terraspace/cli/commander.rb +8 -1
  14. data/lib/terraspace/cli/init.rb +67 -0
  15. data/lib/terraspace/cli/list.rb +13 -0
  16. data/lib/terraspace/cli/new.rb +8 -0
  17. data/lib/terraspace/cli/new/git_hook.rb +33 -0
  18. data/lib/terraspace/cli/new/shim.rb +58 -0
  19. data/lib/terraspace/cli/summary.rb +9 -12
  20. data/lib/terraspace/compiler/backend.rb +9 -37
  21. data/lib/terraspace/compiler/backend/parser.rb +42 -0
  22. data/lib/terraspace/compiler/builder.rb +6 -2
  23. data/lib/terraspace/compiler/cleaner.rb +19 -2
  24. data/lib/terraspace/compiler/cleaner/backend_change.rb +1 -1
  25. data/lib/terraspace/compiler/dsl/syntax/mod.rb +1 -0
  26. data/lib/terraspace/compiler/dsl/syntax/mod/backend.rb +16 -3
  27. data/lib/terraspace/compiler/expander.rb +28 -1
  28. data/lib/terraspace/compiler/writer.rb +1 -1
  29. data/lib/terraspace/core.rb +7 -1
  30. data/lib/terraspace/mod.rb +37 -12
  31. data/lib/terraspace/mod/remote.rb +1 -1
  32. data/lib/terraspace/plugin/expander/interface.rb +48 -5
  33. data/lib/terraspace/plugin/infer_provider.rb +15 -0
  34. data/lib/terraspace/plugin/layer/interface.rb +5 -0
  35. data/lib/terraspace/seeder.rb +4 -4
  36. data/lib/terraspace/terraform/api.rb +53 -0
  37. data/lib/terraspace/terraform/api/client.rb +10 -0
  38. data/lib/terraspace/terraform/api/http.rb +106 -0
  39. data/lib/terraspace/terraform/api/var.rb +72 -0
  40. data/lib/terraspace/terraform/api/vars.rb +38 -0
  41. data/lib/terraspace/terraform/api/vars/base.rb +7 -0
  42. data/lib/terraspace/terraform/api/vars/json.rb +14 -0
  43. data/lib/terraspace/terraform/api/vars/rb.rb +21 -0
  44. data/lib/terraspace/terraform/args/custom.rb +1 -1
  45. data/lib/terraspace/terraform/args/default.rb +16 -2
  46. data/lib/terraspace/terraform/cloud.rb +25 -0
  47. data/lib/terraspace/terraform/cloud/workspace.rb +95 -0
  48. data/lib/terraspace/terraform/runner.rb +1 -1
  49. data/lib/terraspace/util/sh.rb +1 -1
  50. data/lib/terraspace/version.rb +1 -1
  51. data/spec/fixtures/{cache_build_dir → cache_dir}/variables.tf +0 -0
  52. data/spec/fixtures/projects/hcl/aws/config/backend.tf +1 -1
  53. data/spec/fixtures/projects/hcl/google/config/backend.tf +1 -1
  54. data/spec/terraspace/seeder_spec.rb +1 -1
  55. data/spec/terraspace/terraform/hooks/builder_spec.rb +1 -1
  56. data/terraspace.gemspec +3 -3
  57. metadata +37 -17
  58. data/lib/terraspace/cli/build.rb +0 -7
@@ -0,0 +1,13 @@
1
+ class Terraspace::CLI
2
+ class List
3
+ def initialize(options={})
4
+ @options = options
5
+ end
6
+
7
+ def run
8
+ Dir.glob("{app,vendor}/{modules,stacks}/*").sort.each do |path|
9
+ puts path
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,13 @@
1
1
  class Terraspace::CLI
2
2
  class New < Terraspace::Command
3
+ long_desc Help.text(:git_hook)
4
+ GitHook.cli_options.each { |args| option(*args) }
5
+ register(GitHook, "git_hook", "git_hook", "Generates new git hook")
6
+
7
+ long_desc Help.text(:shim)
8
+ Shim.cli_options.each { |args| option(*args) }
9
+ register(Shim, "shim", "shim", "Generates terraspace shim")
10
+
3
11
  long_desc Help.text(:module)
4
12
  Module.base_options.each { |args| option(*args) }
5
13
  Module.component_options.each { |args| option(*args) }
@@ -0,0 +1,33 @@
1
+ class Terraspace::CLI::New
2
+ class GitHook < Thor::Group
3
+ include Thor::Actions
4
+
5
+ def self.cli_options
6
+ [
7
+ [:envs, type: :array, default: %w[dev prod], desc: "envs to build"],
8
+ [:type, aliases: %w[t], default: "pre-push", desc: "git hook type"],
9
+ ]
10
+ end
11
+ cli_options.each { |args| class_option(*args) }
12
+
13
+ def self.source_root
14
+ File.expand_path("../../../templates/base/git_hook", __dir__)
15
+ end
16
+
17
+ def create
18
+ return unless File.exist?(".git")
19
+ dest = ".git/hooks/#{options[:type]}"
20
+ template "hook.sh", dest
21
+ chmod dest, 0755
22
+ end
23
+
24
+ private
25
+ def terraspace_build_commands
26
+ code = []
27
+ @options[:envs].each do |env|
28
+ code << %Q|TS_ENV=#{env} terraspace build placeholder|
29
+ end
30
+ code.join("\n")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ class Terraspace::CLI::New
2
+ class Shim < Thor::Group
3
+ include Thor::Actions
4
+
5
+ def self.cli_options
6
+ [
7
+ [:path, aliases: %w[p], default: "/usr/local/bin/terraspace", desc: "path to save the shim script"],
8
+ ]
9
+ end
10
+ cli_options.each { |args| class_option(*args) }
11
+
12
+ def self.source_root
13
+ File.expand_path("../../../templates/base/shim", __dir__)
14
+ end
15
+
16
+ def set_vars
17
+ @path = @options[:path]
18
+ end
19
+
20
+ def create
21
+ return unless File.exist?(".git")
22
+ dest = @path
23
+ template "terraspace", dest
24
+ chmod dest, 0755
25
+ end
26
+
27
+ def message
28
+ dir = File.dirname(@path)
29
+ puts <<~EOL
30
+ A terraspace shim as been generated at #{@path}
31
+ Please make sure that it is found in the $PATH.
32
+
33
+ You can double check with:
34
+
35
+ which terraspace
36
+
37
+ You should see
38
+
39
+ $ which terraspace
40
+ #{@path}
41
+
42
+ If you do not, please add #{dir} to your PATH.
43
+ You can usually do so by adding this line to ~/.bash_profile and opening a new terminal to check.
44
+
45
+ export PATH=#{dir}:/$PATH
46
+
47
+ EOL
48
+ end
49
+
50
+ private
51
+ def switch_ruby_version_line
52
+ rbenv_installed = system("type rbenv 2>&1 > /dev/null")
53
+ if rbenv_installed
54
+ 'eval "$(rbenv init -)"'
55
+ end
56
+ end
57
+ end
58
+ end
@@ -11,26 +11,19 @@ class Terraspace::CLI
11
11
  end
12
12
 
13
13
  def run
14
+ Terraspace.check_project!
14
15
  build
15
16
  puts "Summary of resources based on backend storage statefiles"
16
17
  backend_expr = '.terraspace-cache/**/backend.*'
17
- backends = Dir.glob(backend_expr)
18
- backends.each do |backend|
19
- process(backend)
20
- end
18
+ # Currently summary assumes backend are within the same bucket and key prefix
19
+ backend = Dir.glob(backend_expr).first
20
+ process(backend) if backend
21
21
  end
22
22
 
23
23
  # Grab the last module and build that.
24
24
  # Assume the backend key has the same prefix
25
25
  def build
26
- return if ENV['TS_SUMMARY_BUILD'] == '0'
27
-
28
- mod = @options[:mod]
29
- unless mod
30
- mod_path = Dir.glob("{app,vendor}/{modules,stacks}/*").last
31
- mod = File.basename(mod_path)
32
- end
33
- Build.new(@options.merge(mod: mod)).run # generate and init
26
+ Build::Placeholder.new(@options).build
34
27
  end
35
28
 
36
29
  def process(path)
@@ -43,6 +36,10 @@ class Terraspace::CLI
43
36
 
44
37
  info = backend.values.first # structure within the s3 or gcs key
45
38
  klass = summary_class(name)
39
+ unless klass
40
+ logger.info "Summary is unavailable for this backend: #{name}"
41
+ exit
42
+ end
46
43
  summary = klass.new(info, @options)
47
44
  summary.call
48
45
  end
@@ -1,5 +1,3 @@
1
- require "hcl_parser"
2
-
3
1
  module Terraspace::Compiler
4
2
  class Backend
5
3
  extend Memoist
@@ -9,53 +7,27 @@ module Terraspace::Compiler
9
7
  end
10
8
 
11
9
  def create
12
- klass = backend_class(backend_name)
10
+ klass = backend_interface(backend_name)
13
11
  return unless klass # in case auto-creation is not supported for specific backend
14
12
 
15
- backend = klass.new(backend_info)
16
- backend.call
13
+ interface = klass.new(backend_info)
14
+ interface.call
17
15
  end
18
16
 
19
17
  def backend_name
20
- backend_raw.keys.first # IE: s3, gcs, etc
18
+ backend.keys.first # IE: s3, gcs, etc
21
19
  end
22
20
 
23
21
  def backend_info
24
- backend_raw.values.first # structure within the s3 or gcs key
25
- end
26
-
27
- def backend_raw
28
- return {} unless exist?(backend_path)
29
- if backend_path.include?('.json')
30
- json_backend
31
- else
32
- hcl_backend
33
- end
34
- end
35
-
36
- def json_backend
37
- data = JSON.load(IO.read(backend_path))
38
- data.dig("terraform", "backend") || {}
39
- end
40
-
41
- def hcl_backend
42
- return {} unless File.exist?(backend_path)
43
- backend_raw = HclParser.load(IO.read(backend_path))
44
- return {} unless backend_raw
45
- backend_raw.dig("terraform", "backend") || {}
46
- end
47
-
48
- def exist?(path)
49
- path && File.exist?(path)
22
+ backend.values.first # structure within the s3 or gcs key
50
23
  end
51
24
 
52
- def backend_path
53
- expr = "#{@mod.cache_build_dir}/backend.tf*"
54
- Dir.glob(expr).first
25
+ def backend
26
+ Parser.new(@mod).result
55
27
  end
56
- memoize :backend_path
28
+ memoize :backend
57
29
 
58
- def backend_class(name)
30
+ def backend_interface(name)
59
31
  return unless name
60
32
  # IE: TerraspacePluginAws::Interfaces::Backend
61
33
  klass_name = Terraspace::Plugin.klass("Backend", backend: name)
@@ -0,0 +1,42 @@
1
+ require "hcl_parser"
2
+
3
+ class Terraspace::Compiler::Backend
4
+ class Parser
5
+ extend Memoist
6
+
7
+ def initialize(mod)
8
+ @mod = mod
9
+ end
10
+
11
+ def result
12
+ return {} unless exist?(backend_path)
13
+ if backend_path.include?('.json')
14
+ json_backend
15
+ else
16
+ hcl_backend
17
+ end
18
+ end
19
+
20
+ def json_backend
21
+ data = JSON.load(IO.read(backend_path))
22
+ data.dig("terraform", "backend") || {}
23
+ end
24
+
25
+ def hcl_backend
26
+ return {} unless File.exist?(backend_path)
27
+ backend_raw = HclParser.load(IO.read(backend_path))
28
+ return {} unless backend_raw
29
+ backend_raw.dig("terraform", "backend") || {}
30
+ end
31
+
32
+ def exist?(path)
33
+ path && File.exist?(path)
34
+ end
35
+
36
+ def backend_path
37
+ expr = "#{@mod.cache_dir}/backend.tf*"
38
+ Dir.glob(expr).first
39
+ end
40
+ memoize :backend_path
41
+ end
42
+ end
@@ -14,7 +14,7 @@ module Terraspace::Compiler
14
14
 
15
15
  # build common config files: provider and backend for the root module
16
16
  def build_config
17
- return unless @mod.root_module?
17
+ return unless build?
18
18
  build_config_templates
19
19
  end
20
20
 
@@ -25,11 +25,15 @@ module Terraspace::Compiler
25
25
  end
26
26
 
27
27
  def build_tfvars
28
- return unless @mod.root_module?
28
+ return unless build?
29
29
  Strategy::Tfvar.new(@mod).run # writer within Strategy to control file ordering
30
30
  end
31
31
 
32
32
  private
33
+ def build?
34
+ @mod.type == "stack" || @mod.root_module?
35
+ end
36
+
33
37
  def build_config_templates
34
38
  expr = "#{Terraspace.root}/config/terraform/**/*"
35
39
  Dir.glob(expr).each do |path|
@@ -19,6 +19,7 @@ module Terraspace::Compiler
19
19
  # only remove .tf* files. leaving cache .terraform and terraform.state files
20
20
  def remove_materialized_artifacts
21
21
  Dir.glob("#{Terraspace.cache_root}/**/*").each do |path|
22
+ next unless within_env?(path)
22
23
  next if path.include?(".tfstate")
23
24
  FileUtils.rm_f(path) if File.file?(path)
24
25
  end
@@ -31,7 +32,7 @@ module Terraspace::Compiler
31
32
  # the file again. With verbose logging, it shows it twice so that's a little bit confusing though.
32
33
  #
33
34
  # def remove_materialized_artifacts_dot_terraform
34
- # expr = "#{@mod.cache_build_dir}/.terraform/**/*"
35
+ # expr = "#{@mod.cache_dir}/.terraform/**/*"
35
36
  #
36
37
  # Dir.glob(expr).each do |path|
37
38
  # logger.info "path #{path}"
@@ -40,7 +41,23 @@ module Terraspace::Compiler
40
41
 
41
42
  def remove_empty_directories
42
43
  return unless File.exist?(Terraspace.cache_root)
43
- Dir["#{Terraspace.cache_root}/**/"].reverse_each { |d| Dir.rmdir d if Dir.entries(d).size == 2 }
44
+ Dir["#{Terraspace.cache_root}/**/"].reverse_each do |d|
45
+ next unless within_env?(d)
46
+ Dir.rmdir(d) if Dir.entries(d).size == 2
47
+ end
48
+ end
49
+
50
+ # Only remove files within an env for the TFC VCS-Workflow.
51
+ # We dont want to run:
52
+ #
53
+ # TS_ENV=prod terraspace up demo
54
+ #
55
+ # And that to delete the .terraspace-cache/us-west-2/dev files
56
+ #
57
+ # May need to allow further customization to this if user project has a stack named the same as the env.
58
+ #
59
+ def within_env?(path)
60
+ path.include?("/#{Terraspace.env}/")
44
61
  end
45
62
  end
46
63
  end
@@ -27,7 +27,7 @@ class Terraspace::Compiler::Cleaner
27
27
  end
28
28
 
29
29
  def current_backend
30
- materialized_path = find_src_path("#{@mod.cache_build_dir}/backend*")
30
+ materialized_path = find_src_path("#{@mod.cache_dir}/backend*")
31
31
  IO.read(materialized_path) if materialized_path
32
32
  end
33
33
 
@@ -1,5 +1,6 @@
1
1
  module Terraspace::Compiler::Dsl::Syntax
2
2
  module Mod
3
+ include Terraspace::Util::Logging
3
4
  include_dir("mod")
4
5
  include_dir("helpers")
5
6
  end
@@ -3,16 +3,29 @@ module Terraspace::Compiler::Dsl::Syntax::Mod
3
3
  def backend(name, props={})
4
4
  terraform = @structure[:terraform] ||= {}
5
5
  backend = terraform[:backend] ||= {}
6
- backend_expand_all!(name, props)
6
+ expansion_all!(name, props)
7
7
  backend[name] = props
8
8
  end
9
9
 
10
- def backend_expand_all!(backend_name, props={})
10
+ def expansion_all!(backend_name, props={})
11
11
  Terraspace::Compiler::Expander.new(@mod, backend_name).expand(props)
12
12
  end
13
13
 
14
+ # Can set opts to explicitly use an specfic backend. Example:
15
+ #
16
+ # opts = {backend: s3}
17
+ #
18
+ # Else Terraspace autodetects the backend installed.
19
+ #
20
+ def expansion(string, opts={})
21
+ expander = Terraspace::Compiler::Expander.autodetect(@mod, opts)
22
+ expander.expansion(string)
23
+ end
24
+
25
+ # DEPRECATED: Will be removed in future release
14
26
  def backend_expand(backend_name, string)
15
- Terraspace::Compiler::Expander.new(@mod, backend_name).expand_string(string)
27
+ logger.info "DEPRECATED backend_expand: instead use expansion(string)"
28
+ Terraspace::Compiler::Expander.new(@mod, backend_name).expansion(string)
16
29
  end
17
30
  end
18
31
  end
@@ -1,6 +1,6 @@
1
1
  module Terraspace::Compiler
2
2
  class Expander
3
- delegate :expand, :expand_string, to: :expander
3
+ delegate :expand, :expansion, to: :expander
4
4
 
5
5
  attr_reader :expander
6
6
  def initialize(mod, name)
@@ -15,5 +15,32 @@ module Terraspace::Compiler
15
15
  rescue NameError
16
16
  Terraspace::Plugin::Expander::Generic
17
17
  end
18
+
19
+ class << self
20
+ extend Memoist
21
+
22
+ def autodetect(mod, opts={})
23
+ backend = opts[:backend]
24
+ unless backend
25
+ plugin = find_plugin
26
+ backend = plugin[:backend]
27
+ end
28
+ new(mod, backend)
29
+ end
30
+ memoize :autodetect
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
43
+ end
44
+ end
18
45
  end
19
46
  end
@@ -19,7 +19,7 @@ module Terraspace::Compiler
19
19
  if @mod.is_a?(Terraspace::Mod::Remote)
20
20
  File.dirname(@src_path) # for Mod::Remote src is dest
21
21
  else
22
- @mod.cache_build_dir
22
+ @mod.cache_dir
23
23
  end
24
24
  end
25
25