We generate each templates CloudFormation .json file and check them in to spot subtle diffs when refactoring. We also validate all configs on every PR. Doing this serially takes a lot of time, but it can be parallelized easily, while also avoiding ruby boot overhead.
This took us from ~5 minutes runtime to 30s, enjoy!
desc "Validate templates via cloudformation api" task :validate do each_template do |template| execute_sfn_command :validate, template end end desc "Generates cloudformation json files" task :generate, [:pattern] do |_t, args| pattern = /#{args[:pattern]}/ if args[:pattern] previous = Dir["generated/*.json"] used = each_template do |template| next if pattern && template !~ pattern output = execute_sfn_command :print, template generated = "generated/#{File.basename(template.sub('.rb', '.json'))}" File.write generated, output generated end (previous - used).each { |f| File.unlink(f) } unless pattern end ... require 'parallel' require 'timeout' def each_template(&block) # preload slow requires require 'bogo-cli' require 'sfn' # run in parallel, but isolated to avoid caches from being reused options = {in_processes: 10, progress: "Progress", isolation: true} Parallel.map(Dir["templates/**/*.rb"], options, &block) end private def execute_sfn_command(command, template, *args) Timeout.timeout(20) do capture_stdout do Sfn::Command.const_get(command.capitalize).new({ defaults: true, # do not ask questions about parameters file: template, retry: {type: :flat, interval: 1} # we will run into rate limits, ignore them quickly }, args).execute! end end rescue StandardError # give users context when something failed warn "bundle exec sfn #{command} #{args.join(" ")} --defaults -f #{template}" raise end def capture_stdout old = $stdout $stdout = StringIO.new yield $stdout.string ensure $stdout = old end