poise/poise-boiler

View on GitHub
lib/poise_boiler/helpers/rake/travis.rb

Summary

Maintainability
A
3 hrs
Test Coverage
#
# Copyright 2015-2016, Noah Kantrowitz
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'halite/helper_base'


module PoiseBoiler
  module Helpers
    class Rake
      # Helper for a Rakefile to install a task for testing on CI.
      #
      # @since 1.0.0
      # @example Installing tasks
      #   require 'poise_boiler/helpers/rake/travis'
      #   PoiseBoiler::Helpers::Rake::Travis.install
      # @example Running CI suite
      #   $ rake travis
      class Travis < Halite::HelperBase
        # Install the rake tasks.
        #
        # @return [void]
        def install
          # Delayed so that Rake doesn't need to be loaded to run this file.
          extend ::Rake::DSL

          file 'test/docker/docker.key' do
            sh(*%w{openssl rsa -in test/docker/docker.pem -passin env:KITCHEN_DOCKER_PASS -out test/docker/docker.key})
          end

          file './docker' do
            begin
              sh(*%w{wget https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz -O docker.tgz})
            rescue RuntimeError
              sh(*%w{curl https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz -o docker.tgz})
            end
            sh(*%w{tar --strip-components=1 -xzvf docker.tgz})
            File.chmod(0755, *%w{./docker ./docker-containerd ./docker-containerd-ctr ./docker-containerd-shim ./docker-runc})
          end

          file '.ssh/id_rsa' do
            # Add a zero-byte passphrase field.
            cmd = %w{ssh-keygen -f} + [File.expand_path('~/.ssh/id_rsa')] +  %w{-b 1024 -P} + ['']
            sh(*cmd)
          end

          task 'travis:integration:rackspace' do
            if integration_rackspace?
              shell.say('Configuring Rackspace test dependencies')
              task('.ssh/id_rsa').invoke
            end
          end

          task 'travis:integration:docker' do
            if integration_docker?
              shell.say('Configuring Docker test dependencies')
              task('test/docker/docker.key').invoke
              task('./docker').invoke
            end
          end

          task 'travis:integration:ec2' do
            if ENV['KITCHEN_EC2_PASS']
              shell.say('Configuring EC2 test dependencies')
              sh(*%w{openssl rsa -in test/ec2/ssh.pem -passin env:KITCHEN_EC2_PASS -out test/ec2/ssh.key})
            end
          end

          desc 'Run Test-Kitchen integration tests.'
          task 'travis:integration' => %w{travis:integration:rackspace travis:integration:docker travis:integration:ec2 chef:kitchen}

          desc 'Run CI tests'
          task 'travis' do
            ENV['POISE_MASTER_BUILD'] = 'true' if master_build?
            run_subtask('spec')
            run_subtask('chef:foodcritic')
            run_subtask('travis:integration') if integration_tests?
            if @failed && !@failed.empty?
              raise "Subtasks #{@failed.join(', ')} failed"
            end
          end
        end

        private

        # Is this a master build?
        #
        # @return [Boolean]
        def master_build?
          ENV['BUNDLE_GEMFILE'].to_s.include?('master.gemfile')
        end

        # Should we run integration tests?
        #
        # @return [Boolean]
        def integration_tests?
          ENV['TRAVIS_SECURE_ENV_VARS'] == 'true' && File.exist?('.kitchen.yml')
        end

        # Should we set things up for Docker integration tests?
        #
        # @return [Boolean]
        def integration_docker?
          File.exist?('test/docker')
        end

        # Should we set things up for Rackspace integration tests?
        #
        # @return [Boolean]
        def integration_rackspace?
          File.exist?('.kitchen.yml') && IO.read('.kitchen.yml') =~ /(driver|name): (['"]?)rackspace\2/
        end

        # Convert a Time object to nanoseconds since the epoch.
        #
        # @param t [Time] Time object to convert.
        # @return [Integer]
        def time_nanoseconds(t)
          (t.tv_sec * 1000000000) + t.nsec
        end

        # Wrap a block in Travis-CI timer tags. These are read by the web UI
        # to nicely format timing information.
        #
        # @param block [Proc] Block to time.
        # @return [void]
        def travis_timer(&block)
          begin
            start_time = time_nanoseconds(Time.now)
            timer_id = '%08x' % Random.rand(0xFFFFFFFF)
            shell.say("travis_time:start:#{timer_id}")
            block.call
          ensure
            end_time = time_nanoseconds(Time.now)
            shell.say("travis_time:end:#{timer_id}:start=#{start_time},finish=#{end_time},duration=#{end_time - start_time}")
          end
        end

        # Wrap a block in Travis-CI fold tags. These are read bu the web UI to
        # allow hiding sections of output.
        #
        # @param name [String] Name of the fold block.
        # @param block [Proc] Block to fold.
        # @return [void]
        def travis_fold(name, &block)
          begin
            shell.say("travis_fold:start:#{name}")
            block.call
          ensure
            shell.say("travis_fold:end:#{name}")
          end
        end

        # Decorate a Rake subtask for Travis.
        #
        # @param name [String] Task to run.
        # @return [void]
        def run_subtask(name)
          travis_timer do
            begin
              shell.say("Running task #{name}")
              task(name).invoke
              shell.say("Task #{name} succeeded.", :green)
            rescue StandardError => ex
              (@failed ||= []) << name
              shell.say("Task #{name} failed with #{ex}:", :red)
              travis_fold "#{name}.backtrace" do
                shell.say(ex.backtrace.map{|line| '  '+line }.join("\n"), :red)
              end
            end
          end
        end

      end
    end
  end
end