terraspace 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/lib/templates/base/arg/terraform.rb.tt +3 -0
  4. data/lib/templates/base/helper/%name%_helper.rb.tt +2 -0
  5. data/lib/templates/base/hook/%kind%.rb.tt +7 -0
  6. data/lib/terraspace.rb +4 -3
  7. data/lib/terraspace/all/base.rb +0 -1
  8. data/lib/terraspace/app.rb +1 -0
  9. data/lib/terraspace/autoloader.rb +20 -3
  10. data/lib/terraspace/cli/bundle.rb +0 -1
  11. data/lib/terraspace/cli/clean/base.rb +0 -1
  12. data/lib/terraspace/cli/clean/cache.rb +0 -1
  13. data/lib/terraspace/cli/help/new/arg.md +19 -0
  14. data/lib/terraspace/cli/help/new/helper.md +39 -0
  15. data/lib/terraspace/cli/help/new/hook.md +25 -0
  16. data/lib/terraspace/cli/help/new/test.md +34 -0
  17. data/lib/terraspace/cli/new.rb +22 -16
  18. data/lib/terraspace/cli/new/arg.rb +62 -0
  19. data/lib/terraspace/cli/new/helper.rb +44 -12
  20. data/lib/terraspace/cli/new/helpers.rb +22 -0
  21. data/lib/terraspace/cli/new/helpers/plugin_gem.rb +25 -0
  22. data/lib/terraspace/cli/new/hook.rb +70 -0
  23. data/lib/terraspace/cli/new/module.rb +0 -11
  24. data/lib/terraspace/cli/new/plugin.rb +4 -4
  25. data/lib/terraspace/cli/new/plugin/helper.rb +1 -1
  26. data/lib/terraspace/cli/new/project.rb +16 -7
  27. data/lib/terraspace/cli/new/sequence.rb +3 -10
  28. data/lib/terraspace/cli/new/source/core.rb +1 -1
  29. data/lib/terraspace/cli/new/stack.rb +1 -12
  30. data/lib/terraspace/cli/new/test.rb +50 -0
  31. data/lib/terraspace/cli/summary.rb +0 -1
  32. data/lib/terraspace/command.rb +16 -0
  33. data/lib/terraspace/compiler/builder.rb +2 -0
  34. data/lib/terraspace/compiler/dsl/mod.rb +2 -0
  35. data/lib/terraspace/compiler/dsl/syntax/mod.rb +2 -0
  36. data/lib/terraspace/compiler/dsl/syntax/mod/backend.rb +1 -1
  37. data/lib/terraspace/compiler/erb/context.rb +2 -0
  38. data/lib/terraspace/compiler/helper_extender.rb +27 -0
  39. data/lib/terraspace/core.rb +0 -6
  40. data/lib/terraspace/ext/core/module.rb +16 -0
  41. data/lib/terraspace/hooks/builder.rb +6 -7
  42. data/lib/terraspace/hooks/concern.rb +2 -2
  43. data/lib/terraspace/logger.rb +6 -0
  44. data/lib/terraspace/mod.rb +0 -1
  45. data/lib/terraspace/plugin.rb +8 -4
  46. data/lib/terraspace/plugin/config/interface.rb +2 -2
  47. data/lib/terraspace/plugin/helper/interface.rb +31 -0
  48. data/lib/terraspace/terraform/args/custom.rb +2 -3
  49. data/lib/terraspace/terraform/runner/retryer.rb +7 -3
  50. data/lib/terraspace/version.rb +1 -1
  51. data/spec/terraspace/terraform/args/custom_spec.rb +6 -4
  52. data/terraspace.gemspec +4 -4
  53. metadata +27 -21
  54. data/lib/terraspace/cli/help/new/bootstrap_test.md +0 -8
  55. data/lib/terraspace/cli/help/new/module_test.md +0 -12
  56. data/lib/terraspace/cli/help/new/project_test.md +0 -8
  57. data/lib/terraspace/cli/new/helper/plugin_gem.rb +0 -12
  58. data/lib/terraspace/cli/new/test/base.rb +0 -17
  59. data/lib/terraspace/cli/new/test/bootstrap.rb +0 -18
  60. data/lib/terraspace/cli/new/test/module.rb +0 -15
  61. data/lib/terraspace/cli/new/test/project.rb +0 -15
@@ -0,0 +1,22 @@
1
+ class Terraspace::CLI::New
2
+ module Helpers
3
+ include Helpers::PluginGem
4
+
5
+ private
6
+ def build_gemfile(*list)
7
+ lines = []
8
+ list.each do |name|
9
+ lines << gem_line(name)
10
+ end
11
+ lines.join("\n")
12
+ end
13
+
14
+ def gem_line(name)
15
+ if name == "terraspace"
16
+ %Q|gem "#{name}", '~> #{Terraspace::VERSION}'|
17
+ else
18
+ %Q|gem "#{name}"|
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module Terraspace::CLI::New::Helpers
2
+ module PluginGem
3
+ private
4
+ def plugin_gem_name
5
+ if @options[:plugin_gem]
6
+ @options[:plugin_gem]
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
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,70 @@
1
+ class Terraspace::CLI::New
2
+ class Hook < Thor::Group
3
+ include Thor::Actions
4
+
5
+ argument :stack, required: false
6
+
7
+ def self.options
8
+ [
9
+ [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
10
+ [:kind, default: "terraform", desc: "terraform or terraspace"],
11
+ [:name, desc: "Command name. Defaults to apply for terraform kind and build for terraspace kind"],
12
+ [:type, default: "project", desc: "project, stack or module"],
13
+ ]
14
+ end
15
+ options.each { |args| class_option(*args) }
16
+
17
+ def self.source_root
18
+ File.expand_path("../../../templates/base/hook", __dir__)
19
+ end
20
+
21
+ private
22
+ def kind
23
+ valid_kinds = %w[terraform terraspace]
24
+ kind = @options[:kind]
25
+ valid_kinds.include?(kind) ? kind : "terraform" # fallback to terraform if user provides invalid type
26
+ end
27
+
28
+ def type
29
+ valid_types = %w[project stack module]
30
+ type = @options[:type]
31
+ valid_types.include?(type) ? type : "project" # fallback to project if user provides invalid type
32
+ end
33
+
34
+ def name
35
+ return options[:name] if options[:name]
36
+ kind == "terraform" ? "apply" : "build"
37
+ end
38
+
39
+ def dest
40
+ map = {
41
+ project: "config/hooks",
42
+ stack: "app/stacks/#{stack}/config/hooks",
43
+ module: "app/modules/#{stack}/config/hooks",
44
+ }
45
+ map[type.to_sym]
46
+ end
47
+
48
+ def hook_path
49
+ "#{dest}/#{kind}.rb"
50
+ end
51
+
52
+ public
53
+
54
+ def check_stack_arg
55
+ return if type == "project"
56
+ return unless stack.nil?
57
+ # Else check for STACK argument for type module or stack
58
+ puts <<~EOL
59
+ Required STACK argument, either the module or stack name. Usage:
60
+
61
+ terraspace new hook STACK --type #{type}
62
+ EOL
63
+ exit 1
64
+ end
65
+
66
+ def create
67
+ directory ".", dest
68
+ end
69
+ end
70
+ end
@@ -11,16 +11,5 @@ class Terraspace::CLI::New
11
11
  dest = "#{@options[:project_name]}/#{dest}" if @options[:project_name]
12
12
  directory ".", dest
13
13
  end
14
-
15
- def create_test
16
- args = component_args(name, @options[:project_name])
17
- Test::Module.start(args)
18
- end
19
-
20
- def run_generator_hook_script
21
- script = ENV['TS_GENERATOR_MODULE']
22
- return unless script
23
- run_script(script, "app/modules/#{name}")
24
- end
25
14
  end
26
15
  end
@@ -1,16 +1,16 @@
1
1
  class Terraspace::CLI::New
2
2
  class Plugin < Sequence
3
- include Helper
3
+ include Helpers
4
+
5
+ argument :name
4
6
 
5
7
  def self.options
6
8
  [
7
- [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
9
+ [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
8
10
  ]
9
11
  end
10
12
  options.each { |args| class_option(*args) }
11
13
 
12
- argument :name
13
-
14
14
  def create_plugin
15
15
  puts "=> Creating new plugin: #{name}"
16
16
  core_template_source("plugin")
@@ -1,5 +1,5 @@
1
1
  class Terraspace::CLI::New::Plugin
2
- module Helper
2
+ module Helpers
3
3
  private
4
4
  # helper
5
5
  def camel_name
@@ -4,16 +4,23 @@ class Terraspace::CLI::New
4
4
  [
5
5
  [:bundle, type: :boolean, default: true, desc: "Runs bundle install on the project"],
6
6
  [:config, type: :boolean, default: true, desc: "Whether or not to generate config files."],
7
- [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files."],
8
- [:test_structure, type: :boolean, desc: "Create project bootstrap test structure."]
7
+ [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files."],
8
+ [:quiet, type: :boolean, desc: "Quiet output."],
9
+ [:test_structure, type: :boolean, desc: "Create project bootstrap test structure."],
9
10
  ]
10
11
  end
11
12
 
12
13
  base_options.each { |args| class_option(*args) }
13
14
  project_options.each { |args| class_option(*args) }
14
15
 
16
+ private
17
+ def log(msg)
18
+ logger.info(msg) unless @options[:quiet]
19
+ end
20
+
21
+ public
15
22
  def creating_messaging
16
- puts "=> Creating new project called #{name}."
23
+ log "=> Creating new project called #{name}."
17
24
  end
18
25
 
19
26
  def create_base
@@ -60,15 +67,17 @@ class Terraspace::CLI::New
60
67
 
61
68
  def bundle_install
62
69
  return if @options[:bundle] == false
63
- puts "=> Installing dependencies with: bundle install"
70
+ log "=> Installing dependencies with: bundle install"
64
71
  Bundler.with_unbundled_env do
65
- system("BUNDLE_IGNORE_CONFIG=1 bundle install", chdir: name)
72
+ bundle = "BUNDLE_IGNORE_CONFIG=1 bundle install"
73
+ bundle << " > /dev/null 2>&1" if @options[:quiet]
74
+ system(bundle, chdir: name)
66
75
  end
67
76
  end
68
77
 
69
78
  def welcome_message_examples
70
79
  return unless options[:examples]
71
- puts <<~EOL
80
+ log <<~EOL
72
81
  #{"="*64}
73
82
  Congrats! You have successfully created a terraspace project.
74
83
  Check out the created files. Adjust to the examples and then deploy with:
@@ -83,7 +92,7 @@ class Terraspace::CLI::New
83
92
 
84
93
  def welcome_message_no_examples
85
94
  return if options[:examples]
86
- puts <<~EOL
95
+ log <<~EOL
87
96
  #{"="*64}
88
97
  Congrats! You have successfully created a terraspace project.
89
98
  Check out the created files.
@@ -3,12 +3,13 @@ require 'thor'
3
3
  class Terraspace::CLI::New
4
4
  class Sequence < Thor::Group
5
5
  include Thor::Actions
6
- include Helper
6
+ include Terraspace::Util::Logging
7
+ include Helpers
7
8
 
8
9
  def self.base_options
9
10
  [
10
11
  [:examples, type: :boolean, default: false, desc: "Also generate examples"],
11
- [:force, type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
12
+ [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
12
13
  [:lang, default: "hcl", desc: "Language to use: HCL/ERB or Ruby DSL"],
13
14
  [:plugin, aliases: %w[p], default: "aws", desc: "Cloud Plugin. Supports: aws, google"],
14
15
  [:test, type: :boolean, desc: "Whether or not to generate tests"],
@@ -47,13 +48,5 @@ class Terraspace::CLI::New
47
48
  source = Source::Plugin.new(self, @options)
48
49
  source.set_source_paths(template, type)
49
50
  end
50
-
51
- # A generator script hook to allow for further customizations
52
- # The dest folder like app/modules/demo is provided as a first argument to the command.
53
- def run_script(script, dest)
54
- command = "#{script} #{dest}"
55
- puts "Running: #{command}" unless ENV['TS_GENERATOR_MUTE']
56
- system(command)
57
- end
58
51
  end
59
52
  end
@@ -1,6 +1,6 @@
1
1
  module Terraspace::CLI::New::Source
2
2
  class Core
3
- include Terraspace::CLI::New::Helper::PluginGem
3
+ include Terraspace::CLI::New::Helpers::PluginGem
4
4
  include Terraspace::Util
5
5
 
6
6
  def initialize(sequence, options)
@@ -5,22 +5,11 @@ class Terraspace::CLI::New
5
5
  argument :name
6
6
 
7
7
  def create_stack
8
- plugin_template_source(@options[:lang], "stack") # IE: plugin_template_source("hcl", "stack")
9
-
10
8
  puts "=> Creating new stack called #{name}."
9
+ plugin_template_source(@options[:lang], "stack") # IE: plugin_template_source("hcl", "stack")
11
10
  dest = "app/stacks/#{name}"
12
11
  dest = "#{@options[:project_name]}/#{dest}" if @options[:project_name]
13
12
  directory ".", dest
14
13
  end
15
-
16
- def create_test
17
- Test::Project.start(component_args(name, @options[:project_name]))
18
- end
19
-
20
- def run_generator_hook_script
21
- script = ENV['TS_GENERATOR_STACK']
22
- return unless script
23
- run_script(script, "app/stacks/#{name}")
24
- end
25
14
  end
26
15
  end
@@ -0,0 +1,50 @@
1
+ class Terraspace::CLI::New
2
+ class Test < Thor::Group
3
+ include Thor::Actions
4
+ include Terraspace::CLI::New::Helpers
5
+
6
+ argument :name
7
+
8
+ def self.options
9
+ [
10
+ [:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
11
+ [:test_name, desc: "Test name. Defaults to the project, module or stack name"],
12
+ [:type, default: "project", desc: "project, stack or module"],
13
+ ]
14
+ end
15
+ options.each { |args| class_option(*args) }
16
+
17
+ private
18
+ def type
19
+ valid_types = %w[project stack module]
20
+ type = @options[:type]
21
+ valid_types.include?(type) ? type : "project" # fallback to project if user provides invalid type
22
+ end
23
+
24
+ def test_name
25
+ options[:test_name] || name
26
+ end
27
+
28
+ def dest
29
+ map = {
30
+ project: ".", # Terraspace.root
31
+ stack: "app/stacks/#{name}",
32
+ module: "app/modules/#{name}",
33
+ }
34
+ map[type.to_sym]
35
+ end
36
+
37
+ def test_template_source(template, type)
38
+ source = Terraspace::CLI::New::Source::Test.new(self, @options)
39
+ source.set_source_paths(template, type)
40
+ end
41
+
42
+ public
43
+
44
+ def create
45
+ test_template_source(@options[:lang], type)
46
+ puts "=> Creating #{type} test: #{name}"
47
+ directory ".", dest
48
+ end
49
+ end
50
+ end
@@ -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,11 @@ 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_project!(args.first)
34
+
31
35
  # Allow calling for help via:
32
36
  # terraspace command help
33
37
  # terraspace command -h
@@ -54,6 +58,18 @@ module Terraspace
54
58
  super
55
59
  end
56
60
 
61
+ def check_project!(command_name)
62
+ return if subcommand?
63
+ return if %w[-h -v completion completion_script help new test version].include?(command_name)
64
+ return if File.exist?("#{Terraspace.root}/config/app.rb")
65
+ logger.error "ERROR: It doesnt look like this is a terraspace project. Are you sure you are in a terraspace project?".color(:red)
66
+ ENV['TS_TEST'] ? raise : exit(1)
67
+ end
68
+
69
+ def subcommand?
70
+ !!caller.detect { |l| l.include?('in subcommand') }
71
+ end
72
+
57
73
  # Override command_help to include the description at the top of the
58
74
  # long_description.
59
75
  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