mkstack 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - checksums.yaml.gz.sig +0 -0
 - data.tar.gz.sig +0 -0
 - data/bin/mkstack +106 -0
 - data/lib/mkstack.rb +81 -0
 - data/lib/mkstack/section.rb +32 -0
 - data/lib/mkstack/template.rb +178 -0
 - data/mkstack.gemspec +18 -0
 - metadata +94 -0
 - metadata.gz.sig +0 -0
 
    
        checksums.yaml
    ADDED
    
    | 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ---
         
     | 
| 
      
 2 
     | 
    
         
            +
            SHA256:
         
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: aa22c1f1c57234678eefa866bc219909dc00334d91d6d4daf70be9762233d51c
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: c5a1f8095d6b34d467fafd443fbc9605b206a42ef70e65f47ec9290ed8783ca3
         
     | 
| 
      
 5 
     | 
    
         
            +
            SHA512:
         
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: b0a0588f47fbc38f7efddb0175afbfacb7d40604a41094a9bc705a26fc6edc154c00bc9f2146e1a19d3326d1d7bfdbb2a6f2de9918a5f8a67ca11e06e202d286
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: b23350d9abe01bb969d9dbf617d7025f5572481e526b37a7632a6ec8fb46c7a64aebb92266166105bfd55ffd73f5187b642620d04bdf4ede746ceeb99bf696ee
         
     | 
    
        checksums.yaml.gz.sig
    ADDED
    
    | 
         Binary file 
     | 
    
        data.tar.gz.sig
    ADDED
    
    | 
         Binary file 
     | 
    
        data/bin/mkstack
    ADDED
    
    | 
         @@ -0,0 +1,106 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/local/bin/ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "logger"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "optparse"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "mkstack"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            ##################################################
         
     | 
| 
      
 9 
     | 
    
         
            +
            # Main
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            # Define logger
         
     | 
| 
      
 12 
     | 
    
         
            +
            $logger = Logger.new(STDERR)
         
     | 
| 
      
 13 
     | 
    
         
            +
            $logger.level = Logger::WARN
         
     | 
| 
      
 14 
     | 
    
         
            +
            $logger.formatter = proc { |s, d, p, m|
         
     | 
| 
      
 15 
     | 
    
         
            +
              "[%s] %5s [%5s] - %s\n" % [ d.strftime("%Y-%m-%d %H:%M:%S"), s, caller(4, 1).first.split(":")[1], m ]
         
     | 
| 
      
 16 
     | 
    
         
            +
            }
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            # Default options
         
     | 
| 
      
 20 
     | 
    
         
            +
            options = {}
         
     | 
| 
      
 21 
     | 
    
         
            +
            options[:erb] = true
         
     | 
| 
      
 22 
     | 
    
         
            +
            options[:format] = "json"
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            # Command line options
         
     | 
| 
      
 26 
     | 
    
         
            +
            opts = OptionParser.new do |p|
         
     | 
| 
      
 27 
     | 
    
         
            +
              desc_padding = " " * (p.summary_indent.length + p.summary_width)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              p.banner = "Usage: #{p.program_name} [ options ] file1 [ file2... ]"
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              # Help
         
     | 
| 
      
 32 
     | 
    
         
            +
              p.on("-h", "--help", "Display this message") { puts opts ; exit }
         
     | 
| 
      
 33 
     | 
    
         
            +
              p.separator(" ")
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              # Verbosity
         
     | 
| 
      
 36 
     | 
    
         
            +
              p.on("-d", "--debug",   "Show debug messages")         { $logger.level = Logger::DEBUG }
         
     | 
| 
      
 37 
     | 
    
         
            +
              p.on("-v", "--verbose", "Be verbose")                  { $logger.level = Logger::INFO }
         
     | 
| 
      
 38 
     | 
    
         
            +
              p.on("-q", "--quiet",   "Only show errors")            { $logger.level = Logger::ERROR }
         
     | 
| 
      
 39 
     | 
    
         
            +
              p.on("-s", "--silient", "Don't show any log messages") { $logger.level = Logger::FATAL }
         
     | 
| 
      
 40 
     | 
    
         
            +
              p.separator(" ")
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              # Output
         
     | 
| 
      
 43 
     | 
    
         
            +
              p.on("-o", "--output=FILE",   "Print final template to FILE") { |x| options[:save] = x }
         
     | 
| 
      
 44 
     | 
    
         
            +
              p.on("%s Use '-' for stdout" % [ desc_padding ])
         
     | 
| 
      
 45 
     | 
    
         
            +
              p.on("-f", "--format=FORMAT", [ "json", "yaml" ], "Print as FORMAT") { |x| options[:format] = x }
         
     | 
| 
      
 46 
     | 
    
         
            +
              p.on("%s Supported formats: json (default), yaml" % [ desc_padding ])
         
     | 
| 
      
 47 
     | 
    
         
            +
              p.separator(" ")
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              # Operations
         
     | 
| 
      
 50 
     | 
    
         
            +
              p.on("--erb", "--[no-]erb", "Perform ERB processing (default is true)") { |x| options[:erb] = x }
         
     | 
| 
      
 51 
     | 
    
         
            +
              p.on("--validate", "Call ValidateTemplate after merging") { options[:validate] = true }
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
            files = opts.parse!.uniq
         
     | 
| 
      
 55 
     | 
    
         
            +
            opts.parse "--help" if files.count == 0
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            template = MkStack::Template.new(options[:format])
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            # Merge files
         
     | 
| 
      
 61 
     | 
    
         
            +
            files.each do |file|
         
     | 
| 
      
 62 
     | 
    
         
            +
              begin
         
     | 
| 
      
 63 
     | 
    
         
            +
                Dir.chdir(File.dirname(file)) { template.merge(File.basename(file), options[:erb]) }
         
     | 
| 
      
 64 
     | 
    
         
            +
              rescue KeyError => e
         
     | 
| 
      
 65 
     | 
    
         
            +
                $logger.warn { "#{file}: already parsed" }
         
     | 
| 
      
 66 
     | 
    
         
            +
              rescue Exception => e
         
     | 
| 
      
 67 
     | 
    
         
            +
                $logger.error { e.message }
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            # Check limits
         
     | 
| 
      
 73 
     | 
    
         
            +
            $logger.debug { "Checking limits" }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
            template.sections.each do |name, section|
         
     | 
| 
      
 76 
     | 
    
         
            +
              $logger.warn { "#{name} limit exceeded: (#{section.length} > #{section.limit})" } if section.exceeds_limit?
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            $logger.warn { "At least one Resources member must be defined" } if template["Resources"].length == 0
         
     | 
| 
      
 80 
     | 
    
         
            +
            $logger.warn { "Template too large (#{template.length} > #{template.limit})" } if template.exceeds_limit?
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
            # Validate template
         
     | 
| 
      
 84 
     | 
    
         
            +
            if options[:validate] then
         
     | 
| 
      
 85 
     | 
    
         
            +
              begin
         
     | 
| 
      
 86 
     | 
    
         
            +
                $logger.info { "Validating #{template.format} template" }
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                $logger.debug { template.validate }
         
     | 
| 
      
 89 
     | 
    
         
            +
              rescue Exception => e
         
     | 
| 
      
 90 
     | 
    
         
            +
                $logger.error { e.message }
         
     | 
| 
      
 91 
     | 
    
         
            +
              end
         
     | 
| 
      
 92 
     | 
    
         
            +
            end
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
            # Save template
         
     | 
| 
      
 96 
     | 
    
         
            +
            if options[:save] then
         
     | 
| 
      
 97 
     | 
    
         
            +
              begin
         
     | 
| 
      
 98 
     | 
    
         
            +
                $stdout = File.new(options[:save], File::CREAT | File::WRONLY) unless options[:save] == "-"
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                $logger.info { "Saving #{template.format} to #{$stdout.path}" } unless $stdout == STDOUT
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                puts template.pp
         
     | 
| 
      
 103 
     | 
    
         
            +
              rescue Exception => e
         
     | 
| 
      
 104 
     | 
    
         
            +
                $logger.error { e.message }
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/mkstack.rb
    ADDED
    
    | 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "mkstack/template"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            =begin rdoc
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Merge multiple CloudFormation template files into a single template.
         
     | 
| 
      
 6 
     | 
    
         
            +
            Each file may be in either JSON or YAML format.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            Get started with <i>template = MkStack::Template.new</i>
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            == ERB
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            By default all files are run through an ERB (Embedded RuBy) processor.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
         
     | 
| 
      
 14 
     | 
    
         
            +
              <% desc = "awesome" %>
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              AWSTemplateFormatVersion: "2010-09-09"
         
     | 
| 
      
 17 
     | 
    
         
            +
              Description: My <%= desc %> CloudFormation template
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            It is safe to leave this enabled.  If a file doesn't have any ERB tags
         
     | 
| 
      
 20 
     | 
    
         
            +
            it is passed through untouched.
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            == Include
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            MkStack searches each file for a section named <b>Include</b>, which should
         
     | 
| 
      
 25 
     | 
    
         
            +
            be a list of filenames.  These function the same as adding the listed
         
     | 
| 
      
 26 
     | 
    
         
            +
            files on the command line.
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            === JSON
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              "Include" : [
         
     | 
| 
      
 31 
     | 
    
         
            +
                "foo.yaml",
         
     | 
| 
      
 32 
     | 
    
         
            +
                "bar.json"
         
     | 
| 
      
 33 
     | 
    
         
            +
              ]
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            === YAML
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              Include:
         
     | 
| 
      
 38 
     | 
    
         
            +
                - foo.yaml
         
     | 
| 
      
 39 
     | 
    
         
            +
                - bar.json
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
            == ERB and Include working together
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
            MkStack uses a single <i>binding</i> for all files.  This allows ERB
         
     | 
| 
      
 44 
     | 
    
         
            +
            tags defined in one file to be referenced in subsequent files.
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            === foo.yaml
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              Include:
         
     | 
| 
      
 49 
     | 
    
         
            +
                - bar.json
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              <% tags_json = %q{
         
     | 
| 
      
 52 
     | 
    
         
            +
                   "Tags": [
         
     | 
| 
      
 53 
     | 
    
         
            +
                     { "Key" : "application", "Value" : "mkstack" }
         
     | 
| 
      
 54 
     | 
    
         
            +
                   ]
         
     | 
| 
      
 55 
     | 
    
         
            +
                 }
         
     | 
| 
      
 56 
     | 
    
         
            +
              %>
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            === bar.json
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              {
         
     | 
| 
      
 61 
     | 
    
         
            +
                "Resources" : {
         
     | 
| 
      
 62 
     | 
    
         
            +
                  "sg": {
         
     | 
| 
      
 63 
     | 
    
         
            +
                    "Type" : "AWS::EC2::SecurityGroup",
         
     | 
| 
      
 64 
     | 
    
         
            +
                    "Properties" : {
         
     | 
| 
      
 65 
     | 
    
         
            +
                      "GroupDescription" : { "Fn::Sub" : "Security Group for ${application}" }
         
     | 
| 
      
 66 
     | 
    
         
            +
                      <%= tags_json %>
         
     | 
| 
      
 67 
     | 
    
         
            +
                    }
         
     | 
| 
      
 68 
     | 
    
         
            +
                  }
         
     | 
| 
      
 69 
     | 
    
         
            +
                }
         
     | 
| 
      
 70 
     | 
    
         
            +
              }
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
            Note that foo.yaml is processed <i>before</i> bar.json.
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
            == See Also
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              MkStack::Template
         
     | 
| 
      
 77 
     | 
    
         
            +
              MkStack::Section
         
     | 
| 
      
 78 
     | 
    
         
            +
            =end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
            module MkStack
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module MkStack
         
     | 
| 
      
 2 
     | 
    
         
            +
              ##################################################
         
     | 
| 
      
 3 
     | 
    
         
            +
              # A CloudFormation template section
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Section
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :name, :limit
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :contents
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # * name: The section's name (Resources, Outputs, etc.)
         
     | 
| 
      
 9 
     | 
    
         
            +
                # * type: The section's type (Hash or String)
         
     | 
| 
      
 10 
     | 
    
         
            +
                # * limit: The AWS limit for this section, if any
         
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize(name, type, limit = nil)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @limit = limit
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  @contents = type.new
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Merge or override a section snippet
         
     | 
| 
      
 19 
     | 
    
         
            +
                def merge(contents)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  raise TypeError.new("#{contents.class} != #{@contents.class}") if contents.class != @contents.class
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  return @contents.merge!(contents) if @contents.respond_to?(:merge!)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @contents = contents
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # Return the length of the section's contents
         
     | 
| 
      
 27 
     | 
    
         
            +
                def length; @contents.length; end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                # Check if the section exceeds the AWS limit
         
     | 
| 
      
 30 
     | 
    
         
            +
                def exceeds_limit?; @limit && length > @limit; end
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,178 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative "section"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "erb"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "json"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module MkStack
         
     | 
| 
      
 8 
     | 
    
         
            +
              ##################################################
         
     | 
| 
      
 9 
     | 
    
         
            +
              # A CloudFormation template
         
     | 
| 
      
 10 
     | 
    
         
            +
              class Template
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :sections, :limit, :format
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def initialize(format = "json")
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @format = format
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  @sections = {
         
     | 
| 
      
 17 
     | 
    
         
            +
                    "AWSTemplateFormatVersion" => Section.new("AWSTemplateFormatVersion", String, nil),
         
     | 
| 
      
 18 
     | 
    
         
            +
                    "Description" => Section.new("Description", String, 1024),
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    "Conditions"  => Section.new("Conditions",  Hash, nil),
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "Mappings"    => Section.new("Mappings",    Hash, 100),
         
     | 
| 
      
 22 
     | 
    
         
            +
                    "Metadata"    => Section.new("Metadata",    Hash, nil),
         
     | 
| 
      
 23 
     | 
    
         
            +
                    "Outputs"     => Section.new("Outputs",     Hash, 60),
         
     | 
| 
      
 24 
     | 
    
         
            +
                    "Parameters"  => Section.new("Parameters",  Hash, 60),
         
     | 
| 
      
 25 
     | 
    
         
            +
                    "Resources"   => Section.new("Resources",   Hash, nil),
         
     | 
| 
      
 26 
     | 
    
         
            +
                    "Transforms"  => Section.new("Transforms",  Hash, nil),
         
     | 
| 
      
 27 
     | 
    
         
            +
                  }
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @limit = 51200
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  # Keep track of parsed files to avoid loops
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @parsed = {}
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # Save a binding so ERB can reuse it instead of creating a new one
         
     | 
| 
      
 34 
     | 
    
         
            +
                  # every time we load a file.  This allows ERB code in one file to
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # be referenced in another.
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @binding = binding
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  # See add_domain_types
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @yaml_domain = "mlfs.org,2019"
         
     | 
| 
      
 40 
     | 
    
         
            +
                  @global_tag = "tag:#{@yaml_domain}:"
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Shorthand accessor for template sections
         
     | 
| 
      
 44 
     | 
    
         
            +
                def [](section); @sections[section]; end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                # Return the length of the entire template
         
     | 
| 
      
 47 
     | 
    
         
            +
                def length; to_json.to_s.length; end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # Check if the template exceeds the AWS limit
         
     | 
| 
      
 50 
     | 
    
         
            +
                def exceeds_limit?; limit && length > limit; end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 54 
     | 
    
         
            +
                # Merge contents of a file
         
     | 
| 
      
 55 
     | 
    
         
            +
                def merge(file, erb)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  contents = load(file, erb)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # Try JSON
         
     | 
| 
      
 60 
     | 
    
         
            +
                    cfn = JSON.load(contents)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  rescue Exception => e
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # Try YAML
         
     | 
| 
      
 63 
     | 
    
         
            +
                    add_domain_types
         
     | 
| 
      
 64 
     | 
    
         
            +
                    cfn = YAML.load(contents)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  # Merge sections that are present in the file
         
     | 
| 
      
 68 
     | 
    
         
            +
                  @sections.each { |name, section| section.merge(cfn[name]) if cfn[name] }
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  # Look for Includes and merge them
         
     | 
| 
      
 71 
     | 
    
         
            +
                  # Files are Included relative to the file with the Include directive
         
     | 
| 
      
 72 
     | 
    
         
            +
                  cfn["Include"].each do |file|
         
     | 
| 
      
 73 
     | 
    
         
            +
                    Dir.chdir(File.dirname(file)) { self.merge(File.basename(file), erb) }
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end if cfn["Include"]
         
     | 
| 
      
 75 
     | 
    
         
            +
                end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 79 
     | 
    
         
            +
                # Call ValidateTemplate[https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_ValidateTemplate.html]
         
     | 
| 
      
 80 
     | 
    
         
            +
                def validate
         
     | 
| 
      
 81 
     | 
    
         
            +
                  require "aws-sdk-cloudformation"
         
     | 
| 
      
 82 
     | 
    
         
            +
                  Aws::CloudFormation::Client.new.validate_template({ template_body: pp })
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 87 
     | 
    
         
            +
                # Format contents
         
     | 
| 
      
 88 
     | 
    
         
            +
                def pp
         
     | 
| 
      
 89 
     | 
    
         
            +
                  case @format
         
     | 
| 
      
 90 
     | 
    
         
            +
                  when "json"
         
     | 
| 
      
 91 
     | 
    
         
            +
                    to_hash.to_json
         
     | 
| 
      
 92 
     | 
    
         
            +
                  when "yaml"
         
     | 
| 
      
 93 
     | 
    
         
            +
                    # Strip enclosing quotes around tags and revert tags to their short form
         
     | 
| 
      
 94 
     | 
    
         
            +
                    # And keep Psych from splitting "long" lines
         
     | 
| 
      
 95 
     | 
    
         
            +
                    to_hash.to_yaml({ line_width: -1 }).gsub(/"(#{@global_tag}[^"]+?)"/, '\1').gsub("#{@global_tag}", "!")
         
     | 
| 
      
 96 
     | 
    
         
            +
                  else
         
     | 
| 
      
 97 
     | 
    
         
            +
                    to_hash
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                private
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 104 
     | 
    
         
            +
                # Create a hash of each populated section's contents
         
     | 
| 
      
 105 
     | 
    
         
            +
                def to_hash
         
     | 
| 
      
 106 
     | 
    
         
            +
                  h = Hash.new
         
     | 
| 
      
 107 
     | 
    
         
            +
                  @sections.each { |k, v| h[k] = v.contents if v.length > 0 }
         
     | 
| 
      
 108 
     | 
    
         
            +
                  h
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 113 
     | 
    
         
            +
                # Read file and (optionally) perform ERB processing on it
         
     | 
| 
      
 114 
     | 
    
         
            +
                def load(file, erb = true)
         
     | 
| 
      
 115 
     | 
    
         
            +
                  path = File.expand_path(file)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  raise KeyError if @parsed.has_key?(path)
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  $logger.info { "Loading #{file}" } if $logger
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                  contents = File.read(file)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  contents = ERB.new(contents).result(@binding) if erb
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                  @parsed[path] = true
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  return contents
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                #########################
         
     | 
| 
      
 130 
     | 
    
         
            +
                # Define YAML domains to handle CloudFormation intrinsic
         
     | 
| 
      
 131 
     | 
    
         
            +
                # functions.
         
     | 
| 
      
 132 
     | 
    
         
            +
                #
         
     | 
| 
      
 133 
     | 
    
         
            +
                # CloudFormation uses <b>!</b> to denote the YAML short form of
         
     | 
| 
      
 134 
     | 
    
         
            +
                # intrinsic functions, which is the same prefix YAML uses for
         
     | 
| 
      
 135 
     | 
    
         
            +
                # local tags.  The default handler strips undefined local tags,
         
     | 
| 
      
 136 
     | 
    
         
            +
                # leaving just the value.
         
     | 
| 
      
 137 
     | 
    
         
            +
                #
         
     | 
| 
      
 138 
     | 
    
         
            +
                # This puts the tags back, but in global tag format.  The global
         
     | 
| 
      
 139 
     | 
    
         
            +
                # tag prefix is converted back to <b>!</b> on output.
         
     | 
| 
      
 140 
     | 
    
         
            +
                #
         
     | 
| 
      
 141 
     | 
    
         
            +
                # Using the short form will force the output to be in YAML format.
         
     | 
| 
      
 142 
     | 
    
         
            +
                def add_domain_types
         
     | 
| 
      
 143 
     | 
    
         
            +
                  functions = [
         
     | 
| 
      
 144 
     | 
    
         
            +
                    "Base64",
         
     | 
| 
      
 145 
     | 
    
         
            +
                    "Cidr",
         
     | 
| 
      
 146 
     | 
    
         
            +
                    "FindInMap",
         
     | 
| 
      
 147 
     | 
    
         
            +
                    "GetAtt",
         
     | 
| 
      
 148 
     | 
    
         
            +
                    "GetAZs",
         
     | 
| 
      
 149 
     | 
    
         
            +
                    "ImportValue",
         
     | 
| 
      
 150 
     | 
    
         
            +
                    "Join",
         
     | 
| 
      
 151 
     | 
    
         
            +
                    "Ref",
         
     | 
| 
      
 152 
     | 
    
         
            +
                    "Select",
         
     | 
| 
      
 153 
     | 
    
         
            +
                    "Split",
         
     | 
| 
      
 154 
     | 
    
         
            +
                    "Transform",
         
     | 
| 
      
 155 
     | 
    
         
            +
                    "And",
         
     | 
| 
      
 156 
     | 
    
         
            +
                    "Equals",
         
     | 
| 
      
 157 
     | 
    
         
            +
                    "If",
         
     | 
| 
      
 158 
     | 
    
         
            +
                    "Not",
         
     | 
| 
      
 159 
     | 
    
         
            +
                    "Or",
         
     | 
| 
      
 160 
     | 
    
         
            +
                  ].each do |function|
         
     | 
| 
      
 161 
     | 
    
         
            +
                    YAML::add_domain_type(@yaml_domain, function) do |type, val|
         
     | 
| 
      
 162 
     | 
    
         
            +
                      @format = "yaml"
         
     | 
| 
      
 163 
     | 
    
         
            +
                      "#{type} #{val}"
         
     | 
| 
      
 164 
     | 
    
         
            +
                    end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                  # The syntax for !Sub requires double-quotes around Strings
         
     | 
| 
      
 168 
     | 
    
         
            +
                  functions = [
         
     | 
| 
      
 169 
     | 
    
         
            +
                    "Sub",
         
     | 
| 
      
 170 
     | 
    
         
            +
                  ].each do |function|
         
     | 
| 
      
 171 
     | 
    
         
            +
                    YAML::add_domain_type(@yaml_domain, function) do |type, val|
         
     | 
| 
      
 172 
     | 
    
         
            +
                      @format = "yaml"
         
     | 
| 
      
 173 
     | 
    
         
            +
                      val.is_a?(String)? "#{type} \"#{val}\"" : "#{type} #{val}"
         
     | 
| 
      
 174 
     | 
    
         
            +
                    end
         
     | 
| 
      
 175 
     | 
    
         
            +
                  end
         
     | 
| 
      
 176 
     | 
    
         
            +
                end
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
            end
         
     | 
    
        data/mkstack.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,18 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 2 
     | 
    
         
            +
              s.name	= "mkstack"
         
     | 
| 
      
 3 
     | 
    
         
            +
              s.version	= "1.0.0"
         
     | 
| 
      
 4 
     | 
    
         
            +
              s.summary	= "Merge multiple CloudFormation template files into a single template"
         
     | 
| 
      
 5 
     | 
    
         
            +
              s.description	= <<-EOF
         
     | 
| 
      
 6 
     | 
    
         
            +
            Merge multiple CloudFormation template files into a single template.  Each file may be in either JSON or YAML format.  By default all files are run through an ERB (Embedded RuBy) processor.
         
     | 
| 
      
 7 
     | 
    
         
            +
            EOF
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              s.authors	= [ "Andy Rosen" ]
         
     | 
| 
      
 10 
     | 
    
         
            +
              s.email	= [ "ajr@corp.mlfs.org" ]
         
     | 
| 
      
 11 
     | 
    
         
            +
              s.homepage	= "https://github.com/ajrosen/AWS/tree/master/mkstack"
         
     | 
| 
      
 12 
     | 
    
         
            +
              s.licenses	= [ "GPL-3.0+" ]
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              s.files	= Dir[ "mkstack.gemspec", "bin/*", "lib/**/*.rb" ]
         
     | 
| 
      
 15 
     | 
    
         
            +
              s.executables	= [ "mkstack" ]
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              s.add_runtime_dependency "aws-sdk-cloudformation", "~> 1"
         
     | 
| 
      
 18 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,94 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: mkstack
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.0.0
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Andy Rosen
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain:
         
     | 
| 
      
 11 
     | 
    
         
            +
            - |
         
     | 
| 
      
 12 
     | 
    
         
            +
              -----BEGIN CERTIFICATE-----
         
     | 
| 
      
 13 
     | 
    
         
            +
              MIIEOjCCAqKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAlMSMwIQYDVQQDDBphanIv
         
     | 
| 
      
 14 
     | 
    
         
            +
              REM9Y29ycC9EQz1tbGZzL0RDPW9yZzAeFw0xOTA1MDEwMjIwNTBaFw0yMDA0MzAw
         
     | 
| 
      
 15 
     | 
    
         
            +
              MjIwNTBaMCUxIzAhBgNVBAMMGmFqci9EQz1jb3JwL0RDPW1sZnMvREM9b3JnMIIB
         
     | 
| 
      
 16 
     | 
    
         
            +
              ojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAqQ2xCUJ4wY8WujSzYd3OGTbj
         
     | 
| 
      
 17 
     | 
    
         
            +
              JDMeU44pXOTLc49Rs8ydukGfd0YBvYMzifmiVRj6depGx2+Ln28Y2mT6IB+zHq8X
         
     | 
| 
      
 18 
     | 
    
         
            +
              s1lrMdFCReztJjQ7OYS16YcZ6pmLkYClnHN3VNqayk1lQEJGCr8aawMeroSB01om
         
     | 
| 
      
 19 
     | 
    
         
            +
              d5wqDATnKEG3x4bnlxFJb3LHzUG1CgNuVCuNREi8zN/uYdm2MGe1fTJguy4/vzBQ
         
     | 
| 
      
 20 
     | 
    
         
            +
              /FnAMt1mr3LtM6YZRGaitIlOKBV/08v7fjH31KRmSMMHPq6A+WyWKRNKnK3tHpSN
         
     | 
| 
      
 21 
     | 
    
         
            +
              JbnFW7mFQGtBpfh8zY8OCQ76Aw8cb5bEIPTI+Hd4FoJLPKnexTI28endSLigOUCF
         
     | 
| 
      
 22 
     | 
    
         
            +
              76kLOiVOVOjdqZ8vEdSgWVugEAzIC30xW5b8yD6N7GdT7n3ktgoZu7jZMUW3D6PQ
         
     | 
| 
      
 23 
     | 
    
         
            +
              wS6BaABVsXbeVtFrzK2tQ65EwLfRluRLTfbnQ7qvMIYCwC3Ib9DTMLavCiOot1vc
         
     | 
| 
      
 24 
     | 
    
         
            +
              RBWXIrDex0tt3EN0dDIxP9O+7ciDgM4zPe6BvaF/AgMBAAGjdTBzMAkGA1UdEwQC
         
     | 
| 
      
 25 
     | 
    
         
            +
              MAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRDowzw+kmcX6FV7T9BkQsUgmzLZjAc
         
     | 
| 
      
 26 
     | 
    
         
            +
              BgNVHREEFTATgRFhanJAY29ycC5tbGZzLm9yZzAcBgNVHRIEFTATgRFhanJAY29y
         
     | 
| 
      
 27 
     | 
    
         
            +
              cC5tbGZzLm9yZzANBgkqhkiG9w0BAQsFAAOCAYEAbbgfVpRCtujGFRHNYLWnq/iQ
         
     | 
| 
      
 28 
     | 
    
         
            +
              vGtNs265jQKdq3SZ5HIsPP4RdiknOk2Q0BP4GDkXOhadtuuqeCVlJUczcrCKiKuP
         
     | 
| 
      
 29 
     | 
    
         
            +
              Vu7iOQKpOq9bafhjvTpRPZL7uXDu0lwBrDyL9PGouBBsijTtGCc/A8cu/2HVoX+Z
         
     | 
| 
      
 30 
     | 
    
         
            +
              X1pqmuJqVlgXp9ktbzPBeIdaFFT+9WzIzWCJ73oBYNZ1EP/4CRKIlmRCWzBGZtu6
         
     | 
| 
      
 31 
     | 
    
         
            +
              QqNypgW/OCQVRcJGZ99myNQO5/EImQp1py6iHymkdCx2RMsdTUpcm3gUl4XaFd4J
         
     | 
| 
      
 32 
     | 
    
         
            +
              pQ1HNcDdD3UCSDqlPpv2nVpOV4u1s3yojIHt0RcVXhSzqGHgmXNjKDIP0zokFBKx
         
     | 
| 
      
 33 
     | 
    
         
            +
              NigI/5A3vv9PplxXMatOeirqPqDY5F4Aim4t2Dn+d+9OiPADkIBwuIuwwgRmcWhE
         
     | 
| 
      
 34 
     | 
    
         
            +
              E2DV+ftSSpAT1zeHEZXx6BsvJVwtXq1kMDYibgB4lTU4lq1oHrx2x+PO2cukHlom
         
     | 
| 
      
 35 
     | 
    
         
            +
              4cVT/heohjMDtNBaDMVsTjTxtNq6oi/pvyLqBGmQ
         
     | 
| 
      
 36 
     | 
    
         
            +
              -----END CERTIFICATE-----
         
     | 
| 
      
 37 
     | 
    
         
            +
            date: 2019-05-02 00:00:00.000000000 Z
         
     | 
| 
      
 38 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 39 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 40 
     | 
    
         
            +
              name: aws-sdk-cloudformation
         
     | 
| 
      
 41 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 42 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 43 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 45 
     | 
    
         
            +
                    version: '1'
         
     | 
| 
      
 46 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 47 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 48 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 49 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 50 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 51 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 52 
     | 
    
         
            +
                    version: '1'
         
     | 
| 
      
 53 
     | 
    
         
            +
            description: 'Merge multiple CloudFormation template files into a single template.  Each
         
     | 
| 
      
 54 
     | 
    
         
            +
              file may be in either JSON or YAML format.  By default all files are run through
         
     | 
| 
      
 55 
     | 
    
         
            +
              an ERB (Embedded RuBy) processor.
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
            '
         
     | 
| 
      
 58 
     | 
    
         
            +
            email:
         
     | 
| 
      
 59 
     | 
    
         
            +
            - ajr@corp.mlfs.org
         
     | 
| 
      
 60 
     | 
    
         
            +
            executables:
         
     | 
| 
      
 61 
     | 
    
         
            +
            - mkstack
         
     | 
| 
      
 62 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 63 
     | 
    
         
            +
            extra_rdoc_files: []
         
     | 
| 
      
 64 
     | 
    
         
            +
            files:
         
     | 
| 
      
 65 
     | 
    
         
            +
            - bin/mkstack
         
     | 
| 
      
 66 
     | 
    
         
            +
            - lib/mkstack.rb
         
     | 
| 
      
 67 
     | 
    
         
            +
            - lib/mkstack/section.rb
         
     | 
| 
      
 68 
     | 
    
         
            +
            - lib/mkstack/template.rb
         
     | 
| 
      
 69 
     | 
    
         
            +
            - mkstack.gemspec
         
     | 
| 
      
 70 
     | 
    
         
            +
            homepage: https://github.com/ajrosen/AWS/tree/master/mkstack
         
     | 
| 
      
 71 
     | 
    
         
            +
            licenses:
         
     | 
| 
      
 72 
     | 
    
         
            +
            - GPL-3.0+
         
     | 
| 
      
 73 
     | 
    
         
            +
            metadata: {}
         
     | 
| 
      
 74 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 75 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 76 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 77 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 78 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 79 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 80 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 81 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 82 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 83 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 84 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 85 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 86 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 87 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 88 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 89 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 90 
     | 
    
         
            +
            rubygems_version: 2.7.6
         
     | 
| 
      
 91 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 92 
     | 
    
         
            +
            specification_version: 4
         
     | 
| 
      
 93 
     | 
    
         
            +
            summary: Merge multiple CloudFormation template files into a single template
         
     | 
| 
      
 94 
     | 
    
         
            +
            test_files: []
         
     | 
    
        metadata.gz.sig
    ADDED
    
    | 
         Binary file 
     |