class Onceover::CLI::Run::Diff

Public Class Methods

command() click to toggle source
# File lib/onceover/octocatalog/diff/cli.rb, line 5
        def self.command
          @cmd ||= Cri::Command.define do
            name 'diff'
            usage 'diff'
            summary "Diff two versions of the controlrepo's compiled catalogs"
            description <<-DESCRIPTION
This uses octocatalog-diff to run diffs on all things in the test matrix
instead of actually testing them. Requires two branches, tags or
revisions to compare between.
            DESCRIPTION

            option :f,  :from, 'branch to compare from', argument: :required
            option :t,  :to,   'branch to compare to', argument: :required

            run do |opts, args, cmd|
              require 'facter'
              require 'colored'

              #TODO: Allow for custom arguments
              repo        = Onceover::Controlrepo.new(opts)
              test_config = Onceover::TestConfig.new(repo.onceover_yaml, opts)
              num_threads = (Facter.value('processors')['count'] / 2)
              tests = test_config.run_filters(Onceover::Test.deduplicate(test_config.spec_tests))

              @queue = tests.inject(Queue.new, :push)
              @results = []

              @threads = Array.new(num_threads) do
                Thread.new do
                  r10k_cache_dir = Dir.mktmpdir('r10k_cache')
                  r10k_config = {
                    'cachedir' => r10k_cache_dir,
                  }
                  logger.debug "Creating r10k cache for thread at #{r10k_cache_dir}"
                  File.write("#{r10k_cache_dir}/r10k.yaml",r10k_config.to_yaml)

                  until @queue.empty?
                    test = @queue.shift

                    logger.info "Preparing environment for #{test.classes[0].name} on #{test.nodes[0].name}"
                    logger.debug "Creating temp directory"
                    tempdir = Dir.mktmpdir(test.to_s)
                    logger.debug "Temp directory created at #{tempdir}"

                    logger.debug "Copying controlrepo to #{tempdir}"
                    FileUtils.copy_entry(repo.root,tempdir)

                    # Copy all of the factsets over in reverse order so that
                    # local ones override vendored ones
                    logger.debug "Deploying vendored factsets"
                    deduped_factsets = repo.facts_files.reverse.inject({}) do |hash, file|
                      hash[File.basename(file)] = file
                      hash
                    end

                    deduped_factsets.each do |basename,path|
                      facts = JSON.load(File.read(path))
                      File.open("#{tempdir}/spec/factsets/#{File.basename(path,'.*')}.yaml", 'w') { |f| f.write facts.to_yaml }
                    end

                    if File.directory?("#{r10k_cache_dir}/modules")
                      logger.debug "Copying modules from thread cache to #{tempdir}"
                      FileUtils.copy_entry("#{r10k_cache_dir}/modules","#{tempdir}/modules")
                    end

                    logger.info "Deploying Puppetfile for #{test.classes[0].name} on #{test.nodes[0].name}"
                    r10k_cmd = "r10k puppetfile install --verbose --color --puppetfile #{repo.puppetfile} --config #{r10k_cache_dir}/r10k.yaml"
                    Open3.popen3(r10k_cmd, :chdir => tempdir) do |stdin, stdout, stderr, wait_thr|
                      exit_status = wait_thr.value
                      if exit_status.exitstatus != 0
                        STDOUT.puts stdout.read
                        STDERR.puts stderr.read
                        abort "R10k encountered an error, see the logs for details"
                      end
                    end

                    # TODO: Improve the way this works so that it doesn't blat site.pp
                    logger.debug "Creating before script that overwrites site.pp"
                    class_name = test.classes[0].name
                    template_dir = File.expand_path('../../../../templates',File.dirname(__FILE__))
                    template = File.read(File.expand_path("./change_manifest.rb.erb",template_dir))
                    File.write("#{tempdir}/bootstrap_script.rb",ERB.new(template, nil, '-').result(binding))
                    FileUtils.chmod("u=rwx","#{tempdir}/bootstrap_script.rb")

                    logger.debug "Getting Puppet binary"
                    binary = `which puppet`.chomp

                    logger.debug "Running Octocatalog diff"
                    logger.info "Compiling catalogs for #{test.classes[0].name} on #{test.nodes[0].name}"

                    command_prefix = ENV['BUNDLE_GEMFILE'] ? 'bundle exec ' : ''

                    command_args = [
                      '--fact-file',
                      "#{tempdir}/spec/factsets/#{test.nodes[0].name}.yaml",
                      '--from',
                      opts[:from],
                      '--to',
                      opts[:to],
                      '--basedir',
                      tempdir,
                      '--puppet-binary',
                      binary,
                      '--bootstrap-script',
                      "'#{tempdir}/bootstrap_script.rb'",
                      '--hiera-config',
                      repo.hiera_config_file,
                      '--pass-env-vars',
                      ENV.keys.keep_if {|k| k =~ /^RUBY|^BUNDLE/ }.join(',')
                    ]

                    cmd = "#{command_prefix}octocatalog-diff #{command_args.join(' ')}"
                    logger.debug "Running: #{cmd}"
                    Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
                      exit_status = wait_thr.value
                      @results << {
                        stdout: stdout.read,
                        stderr: stderr.read,
                        exit_status: exit_status.exitstatus,
                        test: test
                      }
                    end
                    logger.info "Storing results for #{test.classes[0].name} on #{test.nodes[0].name}"

                    logger.debug "Backing up modules to thread cache #{tempdir}"
                    FileUtils.mv("#{tempdir}/modules","#{r10k_cache_dir}/modules",:force => true)

                    logger.debug "Removing temporary build cache"
                    FileUtils.rm_r(tempdir)
                  end

                  FileUtils.rm_r(r10k_cache_dir)
                end
              end

              @threads.each(&:join)
              @results.each do |result|
                puts "#{"Test:".bold} #{result[:test].classes[0].name} on #{result[:test].nodes[0].name}"
                puts "#{"Exit:".bold} #{result[:exit_status]}"
                puts "#{"Status:".bold} #{"changes".yellow}" if result[:exit_status] == 2
                puts "#{"Status:".bold} #{"no differences".green}" if result[:exit_status] == 0
                puts "#{"Status:".bold} #{"failed".red}" if result[:exit_status] == 1
                puts "#{"Results:".bold}\n#{result[:stdout]}\n" if result[:exit_status] == 2
                puts "#{"Errors:".bold}\n#{result[:stderr]}\n" if result[:exit_status] == 1
                puts ""
              end
            end
          end
        end