#!/usr/bin/env ruby require 'rubygems' require 'bundler' Bundler.setup(:default, :development) require 'pathname' require 'json' require 'rest-client' require 'pry' GITHUB_API_OPENPROJECT_PREFIX = "https://api.github.com/repos/opf/openproject" RAILS_ROOT = Pathname.new(__dir__).dirname # current branch branch_name = `git rev-parse --abbrev-ref HEAD`.strip if !ENV['GITHUB_USERNAME'] raise "Missing GITHUB_USERNAME env" elsif !ENV['GITHUB_TOKEN'] raise "Missing GITHUB_TOKEN env, go to https://github.com/settings/tokens and create one with 'repo' access" end def get_http(path) url = if path.start_with?('http') path else "#{GITHUB_API_OPENPROJECT_PREFIX}/#{path}" end response = RestClient::Request.new( method: :get, url:, user: ENV.fetch('GITHUB_USERNAME'), password: ENV.fetch('GITHUB_TOKEN') ).execute response.to_str rescue StandardError => e warn "Failed to perform API request #{url}: #{e} #{e.message}" exit 1 end def get_json(path) JSON.parse(get_http(path)) end def get_cached_json(path) unique_name = path .gsub(/\?.*$/, '') # remove query parameter .gsub(/^#{GITHUB_API_OPENPROJECT_PREFIX}\/?/, '') # remove https://.../ .gsub(/\W/, '_') # transform non alphanum chars cached(unique_name) { get_json(path) } end def commit_message(workflow_run) get_cached_json("commits/#{workflow_run['head_sha']}?per_page=1") .then { |commit_response| commit_response["commit"]["message"] } .then { |message| message.split("\n", 2).first } end def get_log(job) cached("job_#{job['id']}.log") do get_http("actions/jobs/#{job['id']}/logs") end end def cached(unique_name) cached_file = RAILS_ROOT.join("tmp/github_pr_errors/#{unique_name}") if cached_file.file? content = cached_file.read content.start_with?("---") ? YAML::load(content) : content else content = yield cached_file.dirname.mkpath cached_file.write(content.is_a?(String) ? content : YAML::dump(content)) content end end warn "Looking for the last 'Test suite' workflow run in branch #{branch_name}" response = get_json "actions/runs?branch=#{CGI.escape(branch_name)}" last_test_action = response['workflow_runs'] .select { |entry| entry['name'] == 'Test suite' } .reject { |entry| entry['status'] == 'in_progress' } .max_by { |entry| entry['run_number'] } raise "No action run found for branch #{branch_name}" unless last_test_action warn " Commit SHA: #{last_test_action['head_sha']}" warn " Commit message: #{commit_message(last_test_action)}" errors = [] get_cached_json(last_test_action['jobs_url']) .then { |jobs_response| jobs_response['jobs'] } .select { _1['conclusion'] == 'failure' } .sort_by { _1['name'] } .each { warn " #{_1['name']}: #{_1['conclusion']}" } .each do |job| get_log(job).scan(/^\S+ rspec (\S+) #.+$/) do |match| errors << match end end if errors.empty? warn "No rspec errors found :-/" else puts errors.flatten.uniq.map { "'#{_1}'" }.join(" ") end