class GithubActions::JobRunner

Runs GitHub Actions job in a container locally

Attributes

image[R]
job[R]

Public Class Methods

new(job, image = nil) click to toggle source

constructor @param job [GithubActions::Job] the job to run @param image [String,nil] override the Docker image to use,

if `nil` it by default uses the image specified in the job
# File lib/tasks/github_actions/github_actions/job_runner.rb, line 32
def initialize(job, image = nil)
  @job = job
  @image = image
end

Public Instance Methods

run() click to toggle source

run the job in a container @return [Boolean] ‘true` if all steps were successfully executed,

`false` otherwise
# File lib/tasks/github_actions/github_actions/job_runner.rb, line 40
def run
  stage("Running \"#{job.name}\" job from file #{job.workflow.file}")
  start_container

  result = true
  job.steps.each do |step|
    result &= run_step(step)
  end

  print_result(result)
  container.stop
  result
end

Private Instance Methods

expand_name(image_name) click to toggle source

replace the ${{ matrix.<value> }} placeholders in the image name

@param image_name [String] name of the Docker image @return [String] name with replaced values

# File lib/tasks/github_actions/github_actions/job_runner.rb, line 88
def expand_name(image_name)
  image_name.gsub(/\$\{\{\s*matrix\.[^}]*\}\}/) do |subst|
    name = /\$\{\{\s*matrix\.([^}]*)\}\}/.match(subst)[1].strip
    value = job.matrix ? job.matrix[name] : subst

    # if the value is an Array use the first value by default
    replacement = value.is_a?(Array) ? value.first : value.to_s
    puts "Using ${{matrix.#{name}}} value: #{replacement.inspect}"
    replacement
  end
end
find_container() click to toggle source

Get the container configuration @return [Container] container which should run the job

# File lib/tasks/github_actions/github_actions/job_runner.rb, line 68
def find_container
  # prefer the custom image if requested
  image_name = if image
    image
  elsif job.container.is_a?(String)
    job.container
  elsif job.container.is_a?(Hash)
    options = job.container["options"]
    job.container["image"]
  else
    abort "Unsupported container definition: #{job.container.inspect}"
  end

  Container.new(expand_name(image_name), options.to_s)
end
print_result(success) click to toggle source

print the job result @param success [Boolean] status of the job

run_step(step) click to toggle source

run a job step @param step [GithubActions::Step] the step to run @return [Boolean] ‘true` if the step succeeded, `false` otherwise

# File lib/tasks/github_actions/github_actions/job_runner.rb, line 103
def run_step(step)
  info("Step \"#{step.name}\"")

  # run "uses" step if present
  run_uses_step(step.uses) if step.uses

  # run "run" step if present
  return true unless step.run

  container.run(step.run)
end
run_uses_step(uses) click to toggle source

workarounds for some special Javascript actions which are otherwise not supported in general @param uses [String] name of the “uses” action

# File lib/tasks/github_actions/github_actions/job_runner.rb, line 128
def run_uses_step(uses)
  case uses
  when CHECKOUT_ACTION
    # emulate the Git checkout action, just copy the current checkout
    # into the current directory in the running container
    container.copy_current_dir
  when COVERALLS_ACTION
    # skip the coveralls action, do not send the code coverage report
    # when running locally
    info("Skipping the Coveralls step")
  else
    # this should actually never happen, we already checked for
    # the unsupported steps before starting the job
    raise "Unsupported action \"#{uses}\""
  end
end
start_container() click to toggle source

pull the Docker image and start the container

# File lib/tasks/github_actions/github_actions/job_runner.rb, line 60
def start_container
  @container = find_container
  container.pull
  container.start
end