lib/cfer/core/stack.rb in cfer-0.3.0 vs lib/cfer/core/stack.rb in cfer-0.4.0

- old
+ new

@@ -11,34 +11,49 @@ # The fully resolved parameters, including defaults and parameters fetched from an existing stack during an update attr_reader :parameters attr_reader :options - def converge! - if @options[:client] - @options[:client].converge self - end + attr_reader :git_version + + def client + @options[:client] || raise('No client set on this stack') end - def tail!(&block) - if @options[:client] - @options[:client].tail self, &block - end + def converge!(options = {}) + client.converge self, options end + def tail!(options = {}, &block) + client.tail self, options, &block + end + def initialize(options = {}) self[:AWSTemplateFormatVersion] = '2010-09-09' self[:Description] = '' @options = options + self[:Metadata] = { + :Cfer => { + :Version => Cfer::SEMANTIC_VERSION.to_h.delete_if { |k, v| v === nil } + } + } + self[:Parameters] = {} self[:Mappings] = {} self[:Conditions] = {} self[:Resources] = {} self[:Outputs] = {} + if options[:client] && git = options[:client].git && @git_version = (git.object('HEAD^').sha rescue nil) + self[:Metadata][:Cfer][:Git] = { + Rev: @git_version, + Clean: git.status.changed.empty? + } + end + @parameters = HashWithIndifferentAccess.new @input_parameters = HashWithIndifferentAccess.new if options[:client] begin @@ -82,10 +97,12 @@ # # ```Malformed input-Parameter MyParameter must only contain upper and lower case letters and numbers``` def parameter(name, options = {}) param = {} options.each do |key, v| + next if v === nil + k = key.to_s.camelize.to_sym param[k] = case k when :AllowedPattern if v.class == Regexp @@ -117,13 +134,11 @@ # @param type [String] The type of CloudFormation resource to create. # @param options [Hash] Additional attributes to add to the resource block (such as the `UpdatePolicy` for an `AWS::AutoScaling::AutoScalingGroup`) def resource(name, type, options = {}, &block) Preconditions.check_argument(/[[:alnum:]]+/ =~ name, "Resource name must be alphanumeric") - clazz = "CferExt::#{type}".split('::').inject(Object) { |o, c| o.const_get c if o && o.const_defined?(c) } || Cfer::Cfn::Resource - Preconditions.check_argument clazz <= Cfer::Cfn::Resource, "#{type} is not a valid resource type because CferExt::#{type} does not inherit from `Cfer::Cfn::Resource`" - + clazz = Cfer::Core::Resource.resource_class(type) rc = clazz.new(name, type, options, &block) self[:Resources][name] = rc rc end @@ -148,20 +163,76 @@ end # Includes template code from one or more files, and evals it in the context of this stack. # Filenames are relative to the file containing the invocation of this method. def include_template(*files) - calling_file = caller.first.split(/:\d/,2).first - dirname = File.dirname(calling_file) + include_base = options[:include_base] || File.dirname(caller.first.split(/:\d/,2).first) files.each do |file| - path = File.join(dirname, file) + path = File.join(include_base, file) instance_eval(File.read(path), path) end end def lookup_output(stack, out) client = @options[:client] || raise(Cfer::Util::CferError, "Can not fetch stack outputs without a client") client.fetch_output(stack, out) + end + + def lookup_outputs(stack) + client = @options[:client] || raise(Cfer::Util::CferError, "Can not fetch stack outputs without a client") + client.fetch_outputs(stack) + end + + private + + def post_block + begin + validate_stack!(self) + rescue Cfer::Util::CferValidationError => e + Cfer::LOGGER.error "Cfer detected #{e.errors.size > 1 ? 'errors' : 'an error'} when generating the stack:" + e.errors.each do |err| + Cfer::LOGGER.error "* #{err[:error]} in Stack#{validation_contextualize(err[:context])}" + end + raise e + end + end + + def validate_stack!(hash) + errors = [] + context = [] + _inner_validate_stack!(hash, errors, context) + + raise Cfer::Util::CferValidationError, errors unless errors.empty? + end + + def _inner_validate_stack!(hash, errors = [], context = []) + case hash + when Hash + hash.each do |k, v| + _inner_validate_stack!(v, errors, context + [k]) + end + when Array + hash.each_index do |i| + _inner_validate_stack!(hash[i], errors, context + [i]) + end + when nil + errors << { + error: "CloudFormation does not allow nulls in templates", + context: context + } + end + end + + def validation_contextualize(err_ctx) + err_ctx.inject("") do |err_str, ctx| + err_str << + case ctx + when String + ".#{ctx}" + when Numeric + "[#{ctx}]" + end + end end end end