lib/build.rb (155 lines of code) (raw):

require_relative 'encode' require_relative 'config' require 'childprocess' require 'tempfile' require 'fileutils' require 'bundler' require 'shellwords' module GitlabCi class Build TIMEOUT = 7200 attr_accessor :id, :commands, :ref, :tmp_file_path, :output, :before_sha, :run_at, :post_message def initialize(data) @output = "" @post_message = "" @commands = data[:commands].to_a @ref = data[:ref] @ref_name = data[:ref_name] @id = data[:id] @project_id = data[:project_id] @repo_url = data[:repo_url] @before_sha = data[:before_sha] @timeout = data[:timeout] || TIMEOUT @allow_git_fetch = data[:allow_git_fetch] end def run @run_file = Tempfile.new("executor") @run_file.chmod(0700) @commands.unshift(checkout_cmd) if repo_exists? && @allow_git_fetch @commands.unshift(fetch_cmd) else FileUtils.rm_rf(project_dir) FileUtils.mkdir_p(project_dir) @commands.unshift(clone_cmd) end @run_file.puts %|#!/bin/bash| @run_file.puts %|set -e| @run_file.puts %|trap 'kill -s INT 0' EXIT| @commands.each do |command| command.strip! @run_file.puts %|echo #{command.shellescape}| @run_file.puts(command) end @run_file.close @run_at = Time.now Bundler.with_clean_env { execute("setsid #{@run_file.path}") } end def state return :success if success? return :failed if failed? :running end def completed? @process.exited? end def success? return nil unless completed? @process.exit_code == 0 end def failed? return nil unless completed? @process.exit_code != 0 end def running? @process.alive? end def abort @process.stop end def trace output + tmp_file_output + post_message rescue '' end def tmp_file_output tmp_file_output = GitlabCi::Encode.encode!(File.binread(tmp_file_path)) if tmp_file_path && File.readable?(tmp_file_path) tmp_file_output ||= '' end def cleanup @tmp_file.rewind @output << GitlabCi::Encode.encode!(@tmp_file.read) @tmp_file.close @tmp_file.unlink @run_file.unlink end # Check if build execution is longer # than allowed by timeout def running_too_long? if @run_at && @timeout @run_at + @timeout < Time.now else false end end def timeout_abort self.abort @post_message = "\nCI Timeout. Execution took longer then #{@timeout} seconds" end private def execute(cmd) cmd = cmd.strip @process = ChildProcess.build('bash', '--login', '-c', cmd) @tmp_file = Tempfile.new("child-output", binmode: true) @process.io.stdout = @tmp_file @process.io.stderr = @tmp_file @process.cwd = project_dir # ENV # Bundler.with_clean_env now handles PATH, GEM_HOME, RUBYOPT & BUNDLE_*. @process.environment['CI_SERVER'] = 'yes' @process.environment['CI_SERVER_NAME'] = 'GitLab CI' @process.environment['CI_SERVER_VERSION'] = nil# GitlabCi::Version @process.environment['CI_SERVER_REVISION'] = nil# GitlabCi::Revision @process.environment['CI_BUILD_REF'] = @ref @process.environment['CI_BUILD_BEFORE_SHA'] = @before_sha @process.environment['CI_BUILD_REF_NAME'] = @ref_name @process.environment['CI_BUILD_ID'] = @id @process.environment['CI_BUILD_REPO'] = @repo_url @process.environment['CI_PROJECT_ID'] = @project_id @process.environment['CI_PROJECT_DIR'] = project_dir @process.start @tmp_file_path = @tmp_file.path rescue => e @output << e.message end def checkout_cmd cmd = [] cmd << "cd #{project_dir}" cmd << "git reset --hard" cmd << "git checkout #{@ref}" cmd.join(" && ") end def clone_cmd cmd = [] cmd << "cd #{config.builds_dir}" cmd << "git clone #{@repo_url} project-#{@project_id}" cmd << "cd project-#{@project_id}" cmd << "git checkout #{@ref}" cmd.join(" && ") end def fetch_cmd cmd = [] cmd << "cd #{project_dir}" cmd << "git reset --hard" cmd << "git clean -fdx" cmd << "git remote set-url origin #{@repo_url}" cmd << "git fetch origin" cmd.join(" && ") end def repo_exists? File.exists?(File.join(project_dir, '.git')) end def config @config ||= Config.new end def project_dir File.join(config.builds_dir, "project-#{@project_id}") end end end