lib/bora/parameter_resolver.rb in bora-1.6.0 vs lib/bora/parameter_resolver.rb in bora-1.7.0

- old
+ new

@@ -3,11 +3,14 @@ class Bora class ParameterResolver UnresolvedSubstitutionError = Class.new(StandardError) - PLACEHOLDER_REGEX = /\${[^}]+}/ + # Regular expression that can match placeholders nested to two levels. + # For example it will match: "${foo-${bar}}". + # See https://stackoverflow.com/questions/17759004/how-to-match-string-within-parentheses-nested-in-java + PLACEHOLDER_REGEX = /\${([^{}]*|{[^{}]*})*}/ def initialize(stack) @stack = stack @loader = ParameterResolverLoader.new @resolver_cache = {} @@ -18,28 +21,33 @@ while unresolved_placeholders_still_remain unresolved_placeholders_still_remain = false placeholders_were_substituted = false params.each do |k, v| resolved_value = process_param_substitutions(v, params) - unresolved_placeholders_still_remain ||= has_unresolved_placeholder?(resolved_value) + unresolved_placeholders_still_remain ||= unresolved_placeholder?(resolved_value) placeholders_were_substituted ||= resolved_value != v params[k] = resolved_value end if unresolved_placeholders_still_remain && !placeholders_were_substituted raise UnresolvedSubstitutionError, "Parameter substitutions could not be resolved:\n#{unresolved_placeholders_as_string(params)}" end end params end - private def process_param_substitutions(val, params) result = val if val.is_a? String result = val.gsub(PLACEHOLDER_REGEX) do |placeholder| + # Handle nested substitutions, like "${foo-${bar}} + if unresolved_placeholder?(placeholder) + placeholder_contents = placeholder[2..-2] + placeholder = "${#{process_param_substitutions(placeholder_contents, params)}}" + end + process_placeholder(placeholder, params) end elsif val.is_a? Array result = val.map { |i| process_param_substitutions(i, params) } elsif val.is_a? Hash @@ -51,43 +59,42 @@ def process_placeholder(placeholder, params) uri = parse_uri(placeholder[2..-2]) if !uri.scheme # This token refers to another parameter, rather than a resolver value_to_substitute = params[uri.path] - return !value_to_substitute || has_unresolved_placeholder?(value_to_substitute) ? placeholder : value_to_substitute + return !value_to_substitute || unresolved_placeholder?(value_to_substitute) ? placeholder : value_to_substitute else # This token needs to be resolved by a resolver resolver_name = uri.scheme resolver = @resolver_cache[resolver_name] || @loader.load_resolver(resolver_name).new(@stack) return resolver.resolve(uri) end end - def has_unresolved_placeholder?(val) + def unresolved_placeholder?(val) result = false if val.is_a? String result = val =~ PLACEHOLDER_REGEX elsif val.is_a? Array - result = val.find { |i| has_unresolved_placeholder?(i) } + result = val.find { |i| unresolved_placeholder?(i) } elsif val.is_a? Hash - result = val.find { |_, v| has_unresolved_placeholder?(v) } + result = val.find { |_, v| unresolved_placeholder?(v) } end result end def parse_uri(s) uri = URI(s) # Support for legacy CFN substitutions without a scheme, eg: ${stack/outputs/foo}. # Will be removed in next breaking version. - if !uri.scheme && uri.path && uri.path.count("/") == 2 + if !uri.scheme && uri.path && uri.path.count('/') == 2 uri = URI("cfn://#{s}") end uri end def unresolved_placeholders_as_string(params) - params.select { |k, v| has_unresolved_placeholder?(v) }.to_a.map { |k, v| "#{k}: #{v}" }.join("\n") + params.select { |_k, v| unresolved_placeholder?(v) }.to_a.map { |k, v| "#{k}: #{v}" }.join("\n") end - end end