terraspace 0.4.3 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/aws/bin/build.sh +2 -2
  3. data/.cody/azurerm/bin/build.sh +2 -2
  4. data/.cody/google/bin/build.sh +2 -2
  5. data/.github/ISSUE_TEMPLATE.md +1 -1
  6. data/CHANGELOG.md +32 -0
  7. data/README.md +4 -3
  8. data/lib/templates/base/arg/terraform.rb.tt +3 -0
  9. data/lib/templates/base/helper/%name%_helper.rb.tt +2 -0
  10. data/lib/templates/base/hook/%kind%.rb.tt +7 -0
  11. data/lib/terraspace.rb +7 -4
  12. data/lib/terraspace/all/base.rb +0 -1
  13. data/lib/terraspace/app.rb +1 -0
  14. data/lib/terraspace/autoloader.rb +16 -1
  15. data/lib/terraspace/bundle.rb +6 -0
  16. data/lib/terraspace/cli/bundle.rb +0 -1
  17. data/lib/terraspace/cli/check_setup.rb +15 -2
  18. data/lib/terraspace/cli/clean/base.rb +0 -1
  19. data/lib/terraspace/cli/clean/cache.rb +0 -1
  20. data/lib/terraspace/cli/cloud.rb +2 -0
  21. data/lib/terraspace/cli/help/new/arg.md +19 -0
  22. data/lib/terraspace/cli/help/new/helper.md +39 -0
  23. data/lib/terraspace/cli/help/new/hook.md +25 -0
  24. data/lib/terraspace/cli/help/new/test.md +34 -0
  25. data/lib/terraspace/cli/new.rb +22 -16
  26. data/lib/terraspace/cli/new/arg.rb +62 -0
  27. data/lib/terraspace/cli/new/helper.rb +44 -12
  28. data/lib/terraspace/cli/new/helpers.rb +22 -0
  29. data/lib/terraspace/cli/new/helpers/plugin_gem.rb +25 -0
  30. data/lib/terraspace/cli/new/hook.rb +70 -0
  31. data/lib/terraspace/cli/new/module.rb +0 -11
  32. data/lib/terraspace/cli/new/plugin.rb +4 -4
  33. data/lib/terraspace/cli/new/plugin/helper.rb +1 -1
  34. data/lib/terraspace/cli/new/project.rb +16 -7
  35. data/lib/terraspace/cli/new/sequence.rb +3 -10
  36. data/lib/terraspace/cli/new/source/core.rb +1 -1
  37. data/lib/terraspace/cli/new/stack.rb +1 -12
  38. data/lib/terraspace/cli/new/test.rb +50 -0
  39. data/lib/terraspace/cli/summary.rb +0 -1
  40. data/lib/terraspace/command.rb +50 -0
  41. data/lib/terraspace/compiler/builder.rb +2 -0
  42. data/lib/terraspace/compiler/dsl/mod.rb +2 -0
  43. data/lib/terraspace/compiler/dsl/syntax/mod.rb +2 -0
  44. data/lib/terraspace/compiler/dsl/syntax/mod/backend.rb +1 -1
  45. data/lib/terraspace/compiler/erb/context.rb +2 -0
  46. data/lib/terraspace/compiler/helper_extender.rb +27 -0
  47. data/lib/terraspace/core.rb +0 -6
  48. data/lib/terraspace/ext/core/module.rb +16 -0
  49. data/lib/terraspace/hooks/builder.rb +6 -7
  50. data/lib/terraspace/hooks/concern.rb +2 -2
  51. data/lib/terraspace/logger.rb +6 -0
  52. data/lib/terraspace/mod.rb +0 -1
  53. data/lib/terraspace/plugin.rb +8 -4
  54. data/lib/terraspace/plugin/config/interface.rb +2 -2
  55. data/lib/terraspace/plugin/helper/interface.rb +31 -0
  56. data/lib/terraspace/shell.rb +5 -33
  57. data/lib/terraspace/shell/error.rb +46 -0
  58. data/lib/terraspace/terraform/args/custom.rb +2 -3
  59. data/lib/terraspace/terraform/runner.rb +5 -13
  60. data/lib/terraspace/terraform/runner/retryer.rb +69 -0
  61. data/lib/terraspace/version.rb +1 -1
  62. data/spec/terraspace/terraform/args/custom_spec.rb +6 -4
  63. data/terraspace.gemspec +4 -4
  64. metadata +29 -21
  65. data/lib/terraspace/cli/help/new/bootstrap_test.md +0 -8
  66. data/lib/terraspace/cli/help/new/module_test.md +0 -12
  67. data/lib/terraspace/cli/help/new/project_test.md +0 -8
  68. data/lib/terraspace/cli/new/helper/plugin_gem.rb +0 -12
  69. data/lib/terraspace/cli/new/test/base.rb +0 -17
  70. data/lib/terraspace/cli/new/test/bootstrap.rb +0 -18
  71. data/lib/terraspace/cli/new/test/module.rb +0 -15
  72. data/lib/terraspace/cli/new/test/project.rb +0 -15
@@ -11,7 +11,6 @@ class Terraspace::CLI
11
11
  end
12
12
 
13
13
  def run
14
- Terraspace.check_project!
15
14
  build_placeholder
16
15
  puts "Summary of resources based on backend storage statefiles"
17
16
  backend_expr = '.terraspace-cache/**/backend.*'
@@ -27,7 +27,12 @@ Thor::Util.singleton_class.prepend(ThorPrepend::Util)
27
27
  module Terraspace
28
28
  class Command < Thor
29
29
  class << self
30
+ include Terraspace::Util::Logging
31
+
30
32
  def dispatch(m, args, options, config)
33
+ check_standalone_install!
34
+ check_project!(args.first)
35
+
31
36
  # Allow calling for help via:
32
37
  # terraspace command help
33
38
  # terraspace command -h
@@ -54,6 +59,51 @@ module Terraspace
54
59
  super
55
60
  end
56
61
 
62
+ def check_standalone_install!
63
+ return unless opt?
64
+ version_manager = "rvm" if rvm?
65
+ version_manager = "rbenv" if rbenv?
66
+ if rbenv? || rvm?
67
+ puts <<~EOL.color(:yellow)
68
+ WARN: It looks like a standalone Terraspace install and #{version_manager} is also in use.
69
+ Different gems from the standalone Terraspace install and #{version_manager} can cause all kinds of trouble.
70
+ Please install Terraspace as a gem instead and remove the standalone Terraspace /opt/terraspace installation.
71
+ See: https://terraspace.cloud/docs/install/gem/
72
+ EOL
73
+ end
74
+ end
75
+
76
+ def opt?
77
+ paths = ENV['PATH'].split(':')
78
+ opt = paths.detect { |p| p.include?('/opt/terraspace') }
79
+ opt && File.exist?('/opt/terraspace')
80
+ end
81
+
82
+ def rvm?
83
+ paths = ENV['PATH'].split(':')
84
+ rvm = paths.detect { |p| p.include?('/rvm/') || p.include?('/.rvm/') }
85
+ rvm && system("type rvm > /dev/null 2>&1")
86
+ end
87
+
88
+ def rbenv?
89
+ paths = ENV['PATH'].split(':')
90
+ rbenv = paths.detect { |p| p.include?('/rbenv/') || p.include?('/.rbenv/') }
91
+ rbenv && system("type rbenv > /dev/null 2>&1")
92
+ end
93
+
94
+ def check_project!(command_name)
95
+ return if subcommand?
96
+ return if command_name.nil?
97
+ return if %w[-h -v check_setup completion completion_script help new test version].include?(command_name)
98
+ return if File.exist?("#{Terraspace.root}/config/app.rb")
99
+ logger.error "ERROR: It doesnt look like this is a terraspace project. Are you sure you are in a terraspace project?".color(:red)
100
+ ENV['TS_TEST'] ? raise : exit(1)
101
+ end
102
+
103
+ def subcommand?
104
+ !!caller.detect { |l| l.include?('in subcommand') }
105
+ end
106
+
57
107
  # Override command_help to include the description at the top of the
58
108
  # long_description.
59
109
  def command_help(shell, command_name)
@@ -73,6 +73,8 @@ module Terraspace::Compiler
73
73
  def skip?(src_path)
74
74
  return true unless File.file?(src_path)
75
75
  # certain folders will be skipped
76
+ src_path.include?("#{@mod.root}/config/args") ||
77
+ src_path.include?("#{@mod.root}/config/hooks") ||
76
78
  src_path.include?("#{@mod.root}/test")
77
79
  end
78
80
  end
@@ -1,8 +1,10 @@
1
1
  module Terraspace::Compiler::Dsl
2
2
  class Mod < Base
3
3
  include Syntax::Mod
4
+ include Terraspace::Compiler::HelperExtender
4
5
 
5
6
  def build
7
+ extend_module_level_helpers
6
8
  evaluate
7
9
  build_content
8
10
  end
@@ -3,5 +3,7 @@ module Terraspace::Compiler::Dsl::Syntax
3
3
  include Terraspace::Util::Logging
4
4
  include_dir("mod")
5
5
  include_dir("helpers")
6
+ include_plugin_helpers
7
+ include_project_level_helpers
6
8
  end
7
9
  end
@@ -11,7 +11,7 @@ module Terraspace::Compiler::Dsl::Syntax::Mod
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:
14
+ # Can set opts to explicitly use an specific backend. Example:
15
15
  #
16
16
  # opts = {backend: s3}
17
17
  #
@@ -1,11 +1,13 @@
1
1
  module Terraspace::Compiler::Erb
2
2
  class Context
3
3
  include Helpers
4
+ include Terraspace::Compiler::HelperExtender
4
5
 
5
6
  attr_reader :mod, :options
6
7
  def initialize(mod)
7
8
  @mod = mod
8
9
  @options = mod.options # so user has access to cli options
10
+ extend_module_level_helpers
9
11
  end
10
12
  end
11
13
  end
@@ -0,0 +1,27 @@
1
+ module Terraspace::Compiler
2
+ module HelperExtender
3
+ private
4
+ def extend_module_level_helpers
5
+ full_dir = "#{@mod.root}/config/helpers"
6
+ Dir.glob("#{full_dir}/**/*").each do |path|
7
+ regexp = Regexp.new(".*/helpers/")
8
+ klass = path.sub(regexp, '').sub('.rb','').camelize
9
+ klass = "#{mod_namespace}::#{klass}"
10
+ require path # able to use require instead of load since each helper has unique namespace
11
+ send :extend, klass.constantize
12
+ end
13
+ end
14
+
15
+ # IE: mod_namespace = Terraspace::Module::Demo
16
+ # Use separate namespaces scope with module name so custom helper methods from different modules are isolated.
17
+ def mod_namespace
18
+ mod_name = @mod.name.camelize
19
+ ns = "Terraspace::#{@mod.type.camelize}".constantize # IE: Terraspace::Module or Terraspace::Stack
20
+ if ns.const_defined?(mod_name.to_sym)
21
+ "#{ns}::#{mod_name}".constantize
22
+ else
23
+ ns.const_set(mod_name, Module.new)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -51,11 +51,5 @@ module Terraspace
51
51
  def logger=(v)
52
52
  @@logger = v
53
53
  end
54
-
55
- def check_project!
56
- return if File.exist?("#{Terraspace.root}/config/app.rb")
57
- logger.error "ERROR: It doesnt look like this is a terraspace project. Are you sure you are in a terraspace project?".color(:red)
58
- ENV['TS_TEST'] ? raise : exit(1)
59
- end
60
54
  end
61
55
  end
@@ -16,4 +16,20 @@ class Module
16
16
  include klass.constantize
17
17
  end
18
18
  end
19
+
20
+ def include_project_level_helpers
21
+ full_dir = "#{Terraspace.root}/config/helpers"
22
+ Dir.glob("#{full_dir}/**/*").each do |path|
23
+ regexp = Regexp.new(".*/config/helpers/")
24
+ klass = path.sub(regexp, '').sub('.rb','').camelize
25
+ klass = "Terraspace::Project::#{klass}"
26
+ include klass.constantize
27
+ end
28
+ end
29
+
30
+ def include_plugin_helpers
31
+ Terraspace::Plugin.helper_classes.each do |klass|
32
+ include klass # IE: TerraspacePluginAws::Interfaces::Helper
33
+ end
34
+ end
19
35
  end
@@ -7,14 +7,14 @@ module Terraspace::Hooks
7
7
 
8
8
  # IE: dsl_file: config/hooks/terraform.rb
9
9
  attr_accessor :name
10
- def initialize(mod, dsl_file, name)
11
- @mod, @dsl_file, @name = mod, dsl_file, name
10
+ def initialize(mod, file, name)
11
+ @mod, @file, @name = mod, file, name
12
12
  @hooks = {before: {}, after: {}}
13
13
  end
14
14
 
15
15
  def build
16
- return @hooks unless File.exist?(@dsl_file)
17
- evaluate_file(@dsl_file)
16
+ evaluate_file("#{Terraspace.root}/config/hooks/#{@file}")
17
+ evaluate_file("#{@mod.root}/config/hooks/#{@file}")
18
18
  @hooks.deep_stringify_keys!
19
19
  end
20
20
  memoize :build
@@ -37,11 +37,10 @@ module Terraspace::Hooks
37
37
  def run_hook(type, hook)
38
38
  return unless run?(hook)
39
39
 
40
- command = File.basename(@dsl_file).sub('.rb','') # IE: kubes, kubectl, docker
40
+ command = File.basename(@file).sub('.rb','') # IE: terraform or terraspace
41
41
  id = "#{command} #{type} #{@name}"
42
42
  label = " label: #{hook["label"]}" if hook["label"]
43
- logger.info "Running #{id} hook.#{label}"
44
- logger.debug "Hook options: #{hook}"
43
+ logger.info "Hook: Running #{id} hook.#{label}".color(:cyan)
45
44
  Runner.new(@mod, hook).run
46
45
  end
47
46
 
@@ -1,7 +1,7 @@
1
1
  module Terraspace::Hooks
2
2
  module Concern
3
- def run_hooks(dsl_file, name, &block)
4
- hooks = Builder.new(@mod, "#{Terraspace.root}/config/hooks/#{dsl_file}", name)
3
+ def run_hooks(file, name, &block)
4
+ hooks = Builder.new(@mod, file, name)
5
5
  hooks.build # build hooks
6
6
  hooks.run_hooks(&block)
7
7
  end
@@ -2,6 +2,12 @@ require 'logger'
2
2
 
3
3
  module Terraspace
4
4
  class Logger < ::Logger
5
+ def initialize(*args)
6
+ super
7
+ self.formatter = Formatter.new
8
+ self.level = :info
9
+ end
10
+
5
11
  def format_message(severity, datetime, progname, msg)
6
12
  line = if @logdev.dev == $stdout || @logdev.dev == $stderr
7
13
  msg # super simple format if stdout
@@ -31,7 +31,6 @@ module Terraspace
31
31
  end
32
32
 
33
33
  def check_exist!
34
- Terraspace.check_project!
35
34
  return if root
36
35
 
37
36
  pretty_paths = paths.map { |p| Terraspace::Util.pretty_path(p) }.join(", ")
@@ -16,14 +16,18 @@ module Terraspace
16
16
  @@meta
17
17
  end
18
18
 
19
- def layer_classes
20
- @@meta.map { |plugin, data| data[:layer_class] }.compact
21
- end
22
-
23
19
  def config_classes
24
20
  @@meta.map { |plugin, data| data[:config_class] }.compact
25
21
  end
26
22
 
23
+ def helper_classes
24
+ @@meta.map { |plugin, data| data[:helper_class] }.compact
25
+ end
26
+
27
+ def layer_classes
28
+ @@meta.map { |plugin, data| data[:layer_class] }.compact
29
+ end
30
+
27
31
  # The resource map can be used to customized the mapping from the resource "first word" to the plugin.
28
32
  #
29
33
  # resource map is in meta structure.
@@ -16,8 +16,8 @@ module Terraspace::Plugin::Config
16
16
  end
17
17
 
18
18
  def load_project_config
19
- project_config = "#{Terraspace.root}/config/plugins/#{provider}.rb"
20
- evaluate_file(project_config)
19
+ evaluate_file("#{Terraspace.root}/config/plugins/#{provider}.rb")
20
+ evaluate_file("#{Terraspace.root}/config/plugins/#{provider}/#{Terraspace.env}.rb")
21
21
  end
22
22
 
23
23
  def configure
@@ -0,0 +1,31 @@
1
+ module Terraspace::Plugin::Helper
2
+ module Interface
3
+ extend ActiveSupport::Concern
4
+
5
+ # Useful for plugin helpers. Can check this only run logic after dependency resolution.
6
+ def resolved?
7
+ !!@mod.resolved
8
+ end
9
+
10
+ class_methods do
11
+ @@helper_cache = {}
12
+ # This method is useful to avoid double call of heavy processing logic for tfvars,
13
+ # since the tfvars files get evaluated twice.
14
+ # Note: Not setting any cache or doing any logic unless resolved.
15
+ def cache_helper(meth)
16
+ uncached_meth = "uncached_#{meth}"
17
+ alias_method(uncached_meth, meth)
18
+ define_method(meth) do |*args|
19
+ return unless resolved? # return nil in first unresolved pass
20
+ id = Marshal.dump([meth] + args)
21
+ exist = @@helper_cache.key?(id)
22
+ if exist
23
+ @@helper_cache[id]
24
+ else
25
+ @@helper_cache[id] = send(uncached_meth, *args)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,8 +6,6 @@ module Terraspace
6
6
 
7
7
  def initialize(mod, command, options={})
8
8
  @mod, @command, @options = mod, command, options
9
- # error_messages holds aggregation of all error lines
10
- @known_error, @error_messages = nil, ''
11
9
  end
12
10
 
13
11
  # requires @mod to be set
@@ -33,9 +31,9 @@ module Terraspace
33
31
  Open3.popen3(env, @command, chdir: @mod.cache_dir) do |stdin, stdout, stderr, wait_thread|
34
32
  mimic_terraform_input(stdin, stdout)
35
33
  while err = stderr.gets
36
- @error_messages << err # aggregate all error lines
37
- @known_error ||= known_error_type(err)
38
- unless @known_error
34
+ @error ||= Error.new
35
+ @error.lines << err # aggregate all error lines
36
+ unless @error.known?
39
37
  # Sometimes may print a "\e[31m\n" which like during dependencies fetcher init
40
38
  # suppress it so dont get a bunch of annoying "newlines"
41
39
  next if err == "\e[31m\n" && @options[:suppress_error_color]
@@ -48,38 +46,12 @@ module Terraspace
48
46
  end
49
47
  end
50
48
 
51
- def known_error_type(err)
52
- if reinit_required?(err)
53
- :reinit_required
54
- elsif bucket_not_found?(err)
55
- :bucket_not_found
56
- end
57
- end
58
-
59
- def bucket_not_found?(err)
60
- # Message is included in aws, azurerm, and google. See: https://bit.ly/3iOKDri
61
- err.include?("Failed to get existing workspaces")
62
- end
63
-
64
- def reinit_required?(err)
65
- # Example error: https://gist.github.com/tongueroo/f7e0a44b64f0a2e533089b18f331c21e
66
- squeezed = @error_messages.gsub("\n", ' ').squeeze(' ') # remove double whitespaces and newlines
67
- general_check = squeezed.include?("terraform init") && squeezed.include?("Error:")
68
-
69
- general_check ||
70
- err.include?("reinitialization required") ||
71
- err.include?("terraform init") ||
72
- err.include?("require reinitialization")
73
- end
74
-
75
49
  def exit_status(status)
76
50
  return if status == 0
77
51
 
78
52
  exit_on_fail = @options[:exit_on_fail].nil? ? true : @options[:exit_on_fail]
79
- if @known_error == :reinit_required
80
- raise InitRequiredError.new(@error_messages)
81
- elsif @known_error == :bucket_not_found
82
- raise BucketNotFoundError.new(@error_messages)
53
+ if @error && @error.known?
54
+ raise @error.instance
83
55
  elsif exit_on_fail
84
56
  logger.error "Error running command: #{@command}".color(:red)
85
57
  exit status
@@ -0,0 +1,46 @@
1
+ class Terraspace::Shell
2
+ class Error
3
+ attr_accessor :lines
4
+ def initialize
5
+ @lines = '' # holds aggregation of all error lines
6
+ end
7
+
8
+ def known?
9
+ !!instance
10
+ end
11
+
12
+ def instance
13
+ if reinit_required?
14
+ Terraspace::InitRequiredError.new(@lines)
15
+ elsif bucket_not_found?
16
+ Terraspace::BucketNotFound.new(@lines)
17
+ elsif shared_cache_error?
18
+ Terraspace::SharedCacheError.new(@lines)
19
+ end
20
+ end
21
+
22
+ def bucket_not_found?
23
+ # Message is included in aws, azurerm, and google. See: https://bit.ly/3iOKDri
24
+ message.include?("Failed to get existing workspaces")
25
+ end
26
+
27
+ def reinit_required?
28
+ # Example error: https://gist.github.com/tongueroo/f7e0a44b64f0a2e533089b18f331c21e
29
+ general_check = message.include?("terraform init") && message.include?("Error:")
30
+ general_check ||
31
+ message.include?("reinitialization required") ||
32
+ message.include?("terraform init") ||
33
+ message.include?("require reinitialization")
34
+ end
35
+
36
+ def message
37
+ @lines.gsub("\n", ' ').squeeze(' ') # remove double whitespaces and newlines
38
+ end
39
+
40
+ def shared_cache_error?
41
+ # Example: https://gist.github.com/tongueroo/4f2c925709d21f5810229ce9ca482560
42
+ message.include?("Failed to install provider from shared cache") ||
43
+ message.include?("Failed to validate installed provider")
44
+ end
45
+ end
46
+ end
@@ -7,13 +7,12 @@ module Terraspace::Terraform::Args
7
7
  attr_accessor :name
8
8
  def initialize(mod, name)
9
9
  @mod, @name = mod, name
10
- @file = "#{Terraspace.root}/config/args/terraform.rb"
11
10
  @commands = {}
12
11
  end
13
12
 
14
13
  def build
15
- return @commands unless File.exist?(@file)
16
- evaluate_file(@file)
14
+ evaluate_file("#{Terraspace.root}/config/args/terraform.rb")
15
+ evaluate_file("#{@mod.root}/config/args/terraform.rb")
17
16
  @commands.deep_stringify_keys!
18
17
  end
19
18
  memoize :build