From 4a90761fd443ec6c7b590a4f6f3b319e53ff8e63 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Mon, 27 Dec 2021 03:47:00 +0000 Subject: [PATCH] support non-cloud providers and backends * decouple backend detection from plugins * expander backend detection that doesn't evaluate ERB * adjust default max retries for backend re-init to 1 --- lib/terraspace/compiler/backend.rb | 1 + lib/terraspace/compiler/expander.rb | 30 ++++++-------- lib/terraspace/compiler/expander/backend.rb | 43 +++++++++++++++++++++ lib/terraspace/plugin/expander/interface.rb | 13 ++++++- lib/terraspace/terraform/runner/retryer.rb | 2 +- 5 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 lib/terraspace/compiler/expander/backend.rb diff --git a/lib/terraspace/compiler/backend.rb b/lib/terraspace/compiler/backend.rb index c65a67d6..b991dd58 100644 --- a/lib/terraspace/compiler/backend.rb +++ b/lib/terraspace/compiler/backend.rb @@ -16,6 +16,7 @@ def create klass = backend_interface(backend_name) return unless klass # in case auto-creation is not supported for specific backend + # IE: TerraspacePluginAws::Interfaces::Backend.new interface = klass.new(backend_info) interface.call end diff --git a/lib/terraspace/compiler/expander.rb b/lib/terraspace/compiler/expander.rb index b44f4b13..d78f120a 100644 --- a/lib/terraspace/compiler/expander.rb +++ b/lib/terraspace/compiler/expander.rb @@ -1,17 +1,21 @@ module Terraspace::Compiler class Expander + extend Memoist delegate :expand, :expansion, to: :expander - attr_reader :expander def initialize(mod, name) @mod, @name = mod, name - @expander = expander_class.new(@mod) end + def expander + expander_class.new(@mod) + end + memoize :expander + def expander_class # IE: TerraspacePluginAws::Interfaces::Expander klass_name = Terraspace::Plugin.klass("Expander", backend: @name) - klass_name.constantize if klass_name + klass_name ? klass_name.constantize : Terraspace::Plugin::Expander::Generic rescue NameError Terraspace::Plugin::Expander::Generic end @@ -20,27 +24,15 @@ class << self extend Memoist def autodetect(mod, opts={}) - backend = opts[:backend] - unless backend - plugin = find_plugin - backend = plugin[:backend] - end + backend = opts[:backend] || find_backend(mod) new(mod, backend) end memoize :autodetect - def find_plugin - plugins = Terraspace::Plugin.meta - if plugins.size == 1 - plugins.first[1] - else - precedence = %w[aws azurerm google] - plugin = precedence.find do |provider| - plugins[provider] - end - plugins[plugin] - end + def find_backend(mod) + Backend.new(mod).detect end + memoize :find_backend end end end diff --git a/lib/terraspace/compiler/expander/backend.rb b/lib/terraspace/compiler/expander/backend.rb new file mode 100644 index 00000000..e1ea7a50 --- /dev/null +++ b/lib/terraspace/compiler/expander/backend.rb @@ -0,0 +1,43 @@ +# Simpler than Terraspace::Compiler::Backend::Parser because +# Terraspace::Compiler::Expander autodetect backend super early on. +# It's so early that don't want helper methods like <%= expansion(...) %> to be called. +# Calling the expansion helper itself results in Terraspace autodetecting a concrete +# Terraspace Plugin Expander, which creates an infinite loop. +# This simple detection class avoids calling ERB and avoids the infinite loop. +class Terraspace::Compiler::Expander + class Backend + extend Memoist + + def initialize(mod) + @mod = mod + end + + COMMENT = /^\s+#/ + # Works for both backend.rb DSL and backend.tf ERB + def detect + lines = IO.readlines(src_path) + backend_line = lines.find { |l| l.include?("backend") && l !~ COMMENT } + md = backend_line.match(/['"](.*)['"]/) + md[1] if md + end + + private + # Use original unrendered source as wont know the + # @mod.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR" + # Until the concrete Terraspace Plugin Expander has been autodetected. + # Follow same precedence rules as rest of Terraspace. + def src_path + exprs = [ + "app/stacks/#{@mod.build_dir}/backend.*", + "app/modules/#{@mod.build_dir}/backend.*", + "vendor/stacks/#{@mod.build_dir}/backend.*", + "vendor/modules/#{@mod.build_dir}/backend.*", + "config/terraform/backend.*", + ] + path = nil + exprs.find { |expr| path = Dir.glob(expr).first } + path + end + memoize :src_path + end +end diff --git a/lib/terraspace/plugin/expander/interface.rb b/lib/terraspace/plugin/expander/interface.rb index d91ddf7b..3d18c83a 100644 --- a/lib/terraspace/plugin/expander/interface.rb +++ b/lib/terraspace/plugin/expander/interface.rb @@ -70,7 +70,10 @@ def expand_string?(string) # def strip(string) string.sub(/^-+/,'').sub(/-+$/,'') # remove leading and trailing - - .sub(%r{/+$},'') # only remove trailing / or else /home/ec2-user => home/ec2-user + .sub(%r{/+$},'') # only remove trailing / or else /home/ec2-user => home/ec2-user + .sub(/:\/\//, 'TMP_KEEP_HTTP') # so we can keep ://. IE: https:// or http:// + .gsub(%r{/+},'/') # remove double slashes are more. IE: // -> / Useful of region is '' in generic expander + .sub('TMP_KEEP_HTTP', '://') # restore :// IE: https:// or http:// end def var_value(name) @@ -102,5 +105,13 @@ def instance def cache_root Terraspace.cache_root end + + # So default config works: + # config.cache_dir = ":CACHE_ROOT/:REGION/:ENV/:BUILD_DIR" + # For when folks configure it with the http backend for non-cloud providers + # The double slash // will be replace with a single slash in expander/interface.rb + def region + '' + end end end diff --git a/lib/terraspace/terraform/runner/retryer.rb b/lib/terraspace/terraform/runner/retryer.rb index 9dd5d61d..744384c5 100644 --- a/lib/terraspace/terraform/runner/retryer.rb +++ b/lib/terraspace/terraform/runner/retryer.rb @@ -9,7 +9,7 @@ def initialize(mod, options, command_name, exception) end def retry? - max_retries = ENV['TS_MAX_RETRIES'] ? ENV['TS_MAX_RETRIES'].to_i : 3 + max_retries = ENV['TS_MAX_RETRIES'] ? ENV['TS_MAX_RETRIES'].to_i : 1 if @retries <= max_retries && !@stop_retrying true # will retry else