class Codecov::Uploader
Constants
- RECOGNIZED_CIS
CIs
Public Class Methods
build_params(ci)
click to toggle source
# File lib/codecov/uploader.rb, line 130 def self.build_params(ci) puts [red('x>'), 'No token specified or token is empty'].join(' ') if ENV['CODECOV_TOKEN'].nil? || ENV['CODECOV_TOKEN'].empty? params = { 'token' => ENV['CODECOV_TOKEN'], 'flags' => ENV['CODECOV_FLAG'] || ENV['CODECOV_FLAGS'], 'package' => "ruby-#{::Codecov::VERSION}" } case ci when APPVEYOR # http://www.appveyor.com/docs/environment-variables params[:service] = 'appveyor' params[:branch] = ENV['APPVEYOR_REPO_BRANCH'] params[:build] = ENV['APPVEYOR_JOB_ID'] params[:pr] = ENV['APPVEYOR_PULL_REQUEST_NUMBER'] params[:job] = ENV['APPVEYOR_ACCOUNT_NAME'] + '/' + ENV['APPVEYOR_PROJECT_SLUG'] + '/' + ENV['APPVEYOR_BUILD_VERSION'] params[:slug] = ENV['APPVEYOR_REPO_NAME'] params[:commit] = ENV['APPVEYOR_REPO_COMMIT'] when AZUREPIPELINES params[:service] = 'azure_pipelines' params[:branch] = ENV['BUILD_SOURCEBRANCH'] params[:pull_request] = ENV['SYSTEM_PULLREQUEST_PULLREQUESTNUMBER'] params[:job] = ENV['SYSTEM_JOBID'] params[:build] = ENV['BUILD_BUILDID'] params[:build_url] = "#{ENV['SYSTEM_TEAMFOUNDATIONSERVERURI']}/#{ENV['SYSTEM_TEAMPROJECT']}/_build/results?buildId=#{ENV['BUILD_BUILDID']}" params[:commit] = ENV['BUILD_SOURCEVERSION'] params[:slug] = ENV['BUILD_REPOSITORY_ID'] when BITBUCKET # https://confluence.atlassian.com/bitbucket/variables-in-pipelines-794502608.html params[:service] = 'bitbucket' params[:branch] = ENV['BITBUCKET_BRANCH'] # BITBUCKET_COMMIT does not always provide full commit sha due to a bug https://jira.atlassian.com/browse/BCLOUD-19393# params[:commit] = (ENV['BITBUCKET_COMMIT'].length < 40 ? nil : ENV['BITBUCKET_COMMIT']) params[:build] = ENV['BITBUCKET_BUILD_NUMBER'] when BITRISE # http://devcenter.bitrise.io/faq/available-environment-variables/ params[:service] = 'bitrise' params[:branch] = ENV['BITRISE_GIT_BRANCH'] params[:pr] = ENV['BITRISE_PULL_REQUEST'] params[:build] = ENV['BITRISE_BUILD_NUMBER'] params[:build_url] = ENV['BITRISE_BUILD_URL'] params[:commit] = ENV['BITRISE_GIT_COMMIT'] params[:slug] = ENV['BITRISEIO_GIT_REPOSITORY_OWNER'] + '/' + ENV['BITRISEIO_GIT_REPOSITORY_SLUG'] when BUILDKITE # https://buildkite.com/docs/guides/environment-variables params[:service] = 'buildkite' params[:branch] = ENV['BUILDKITE_BRANCH'] params[:build] = ENV['BUILDKITE_BUILD_NUMBER'] params[:job] = ENV['BUILDKITE_JOB_ID'] params[:build_url] = ENV['BUILDKITE_BUILD_URL'] params[:slug] = ENV['BUILDKITE_PROJECT_SLUG'] params[:commit] = ENV['BUILDKITE_COMMIT'] when CIRCLE # https://circleci.com/docs/environment-variables params[:service] = 'circleci' params[:build] = ENV['CIRCLE_BUILD_NUM'] params[:job] = ENV['CIRCLE_NODE_INDEX'] params[:slug] = if !ENV['CIRCLE_PROJECT_REPONAME'].nil? ENV['CIRCLE_PROJECT_USERNAME'] + '/' + ENV['CIRCLE_PROJECT_REPONAME'] else ENV['CIRCLE_REPOSITORY_URL'].gsub(/^.*:/, '').gsub(/\.git$/, '') end params[:pr] = ENV['CIRCLE_PR_NUMBER'] params[:branch] = ENV['CIRCLE_BRANCH'] params[:commit] = ENV['CIRCLE_SHA1'] when CIRRUS # https://cirrus-ci.org/guide/writing-tasks/#environment-variables params[:branch] = ENV['CIRRUS_BRANCH'] params[:build] = ENV['CIRRUS_BUILD_ID'] params[:build_url] = "https://cirrus-ci.com/tasks/#{ENV['CIRRUS_TASK_ID']}" params[:commit] = ENV['CIRRUS_CHANGE_IN_REPO'] params[:job] = ENV['CIRRUS_TASK_NAME'] params[:pr] = ENV['CIRRUS_PR'] params[:service] = 'cirrus-ci' params[:slug] = ENV['CIRRUS_REPO_FULL_NAME'] when CODEBUILD # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html # To use CodePipeline as CodeBuild source which sets no branch and slug variable: # # 1. Set up CodeStarSourceConnection as source action provider # https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodestarConnectionSource.html # 2. Add a Namespace to your source action. Example: "CodeStar". # https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-variables.html#reference-variables-concepts-namespaces # 3. Add these environment variables to your CodeBuild action: # - CODESTAR_BRANCH_NAME: #{CodeStar.BranchName} # - CODESTAR_FULL_REPOSITORY_NAME: #{CodeStar.FullRepositoryName} (optional) # https://docs.aws.amazon.com/codepipeline/latest/userguide/action-reference-CodeBuild.html#action-reference-CodeBuild-config # # PRs are not supported with CodePipeline. params[:service] = 'codebuild' params[:branch] = ENV['CODEBUILD_WEBHOOK_HEAD_REF']&.split('/')&.[](2) || ENV['CODESTAR_BRANCH_NAME'] params[:build] = ENV['CODEBUILD_BUILD_ID'] params[:commit] = ENV['CODEBUILD_RESOLVED_SOURCE_VERSION'] params[:job] = ENV['CODEBUILD_BUILD_ID'] params[:slug] = ENV['CODEBUILD_SOURCE_REPO_URL']&.match(/.*github.com\/(?<slug>.*).git/)&.[]('slug') || ENV['CODESTAR_FULL_REPOSITORY_NAME'] params[:pr] = if ENV['CODEBUILD_SOURCE_VERSION'] && !(ENV['CODEBUILD_INITIATOR'] =~ /codepipeline/) matched = ENV['CODEBUILD_SOURCE_VERSION'].match(%r{pr/(?<pr>.*)}) matched.nil? ? ENV['CODEBUILD_SOURCE_VERSION'] : matched['pr'] end params[:build_url] = ENV['CODEBUILD_BUILD_URL'] when CODESHIP # https://www.codeship.io/documentation/continuous-integration/set-environment-variables/ params[:service] = 'codeship' params[:branch] = ENV['CI_BRANCH'] params[:commit] = ENV['CI_COMMIT_ID'] params[:build] = ENV['CI_BUILD_NUMBER'] params[:build_url] = ENV['CI_BUILD_URL'] when DRONEIO # https://semaphoreapp.com/docs/available-environment-variables.html params[:service] = 'drone.io' params[:branch] = ENV['DRONE_BRANCH'] params[:commit] = ENV['DRONE_COMMIT_SHA'] params[:job] = ENV['DRONE_JOB_NUMBER'] params[:build] = ENV['DRONE_BUILD_NUMBER'] params[:build_url] = ENV['DRONE_BUILD_LINK'] || ENV['DRONE_BUILD_URL'] || ENV['CI_BUILD_URL'] params[:pr] = ENV['DRONE_PULL_REQUEST'] params[:tag] = ENV['DRONE_TAG'] when GITHUB # https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables#default-environment-variables params[:service] = 'github-actions' if (ENV['GITHUB_HEAD_REF'] || '').empty? params[:branch] = ENV['GITHUB_REF'].sub('refs/heads/', '') else params[:branch] = ENV['GITHUB_HEAD_REF'] # PR refs are in the format: refs/pull/7/merge for pull_request events params[:pr] = ENV['GITHUB_REF'].split('/')[2] end params[:slug] = ENV['GITHUB_REPOSITORY'] params[:build] = ENV['GITHUB_RUN_ID'] params[:commit] = ENV['GITHUB_SHA'] when GITLAB # http://doc.gitlab.com/ci/examples/README.html#environmental-variables # https://gitlab.com/gitlab-org/gitlab-ci-runner/blob/master/lib/build.rb#L96 # GitLab Runner v9 renamed some environment variables, so we check both old and new variable names. params[:service] = 'gitlab' params[:branch] = ENV['CI_BUILD_REF_NAME'] || ENV['CI_COMMIT_REF_NAME'] params[:build] = ENV['CI_BUILD_ID'] || ENV['CI_JOB_ID'] slug = ENV['CI_BUILD_REPO'] || ENV['CI_REPOSITORY_URL'] params[:slug] = slug.split('/', 4)[-1].sub('.git', '') if slug params[:commit] = ENV['CI_BUILD_REF'] || ENV['CI_COMMIT_SHA'] when HEROKU params[:service] = 'heroku' params[:branch] = ENV['HEROKU_TEST_RUN_BRANCH'] params[:build] = ENV['HEROKU_TEST_RUN_ID'] params[:commit] = ENV['HEROKU_TEST_RUN_COMMIT_VERSION'] when JENKINS # https://wiki.jenkins-ci.org/display/JENKINS/Building+a+software+project # https://wiki.jenkins-ci.org/display/JENKINS/GitHub+pull+request+builder+plugin#GitHubpullrequestbuilderplugin-EnvironmentVariables params[:service] = 'jenkins' params[:branch] = ENV['ghprbSourceBranch'] || ENV['GIT_BRANCH'] params[:commit] = ENV['ghprbActualCommit'] || ENV['GIT_COMMIT'] params[:pr] = ENV['ghprbPullId'] params[:build] = ENV['BUILD_NUMBER'] params[:root] = ENV['WORKSPACE'] params[:build_url] = ENV['BUILD_URL'] when SEMAPHORE # https://semaphoreapp.com/docs/available-environment-variables.html params[:service] = 'semaphore' params[:branch] = ENV['BRANCH_NAME'] params[:commit] = ENV['REVISION'] params[:build] = ENV['SEMAPHORE_BUILD_NUMBER'] params[:job] = ENV['SEMAPHORE_CURRENT_THREAD'] params[:slug] = ENV['SEMAPHORE_REPO_SLUG'] when SHIPPABLE # http://docs.shippable.com/en/latest/config.html#common-environment-variables params[:service] = 'shippable' params[:branch] = ENV['BRANCH'] params[:build] = ENV['BUILD_NUMBER'] params[:build_url] = ENV['BUILD_URL'] params[:pull_request] = ENV['PULL_REQUEST'] params[:slug] = ENV['REPO_NAME'] params[:commit] = ENV['COMMIT'] when SOLANO # http://docs.solanolabs.com/Setup/tddium-set-environment-variables/ params[:service] = 'solano' params[:branch] = ENV['TDDIUM_CURRENT_BRANCH'] params[:commit] = ENV['TDDIUM_CURRENT_COMMIT'] params[:build] = ENV['TDDIUM_TID'] params[:pr] = ENV['TDDIUM_PR_ID'] when TEAMCITY # https://confluence.jetbrains.com/display/TCD8/Predefined+Build+Parameters # Teamcity does not automatically make build parameters available as environment variables. # Add the following environment parameters to the build configuration # env.TEAMCITY_BUILD_BRANCH = %teamcity.build.branch% # env.TEAMCITY_BUILD_ID = %teamcity.build.id% # env.TEAMCITY_BUILD_URL = %teamcity.serverUrl%/viewLog.html?buildId=%teamcity.build.id% # env.TEAMCITY_BUILD_COMMIT = %system.build.vcs.number% # env.TEAMCITY_BUILD_REPOSITORY = %vcsroot.<YOUR TEAMCITY VCS NAME>.url% params[:service] = 'teamcity' params[:branch] = ENV['TEAMCITY_BUILD_BRANCH'] params[:build] = ENV['TEAMCITY_BUILD_ID'] params[:build_url] = ENV['TEAMCITY_BUILD_URL'] params[:commit] = ENV['TEAMCITY_BUILD_COMMIT'] params[:slug] = ENV['TEAMCITY_BUILD_REPOSITORY'].split('/', 4)[-1].sub('.git', '') when TRAVIS # http://docs.travis-ci.com/user/ci-environment/#Environment-variables params[:service] = 'travis' params[:branch] = ENV['TRAVIS_BRANCH'] params[:pull_request] = ENV['TRAVIS_PULL_REQUEST'] params[:job] = ENV['TRAVIS_JOB_ID'] params[:slug] = ENV['TRAVIS_REPO_SLUG'] params[:build] = ENV['TRAVIS_JOB_NUMBER'] params[:commit] = ENV['TRAVIS_COMMIT'] params[:env] = ENV['TRAVIS_RUBY_VERSION'] when WERCKER # http://devcenter.wercker.com/articles/steps/variables.html params[:service] = 'wercker' params[:branch] = ENV['WERCKER_GIT_BRANCH'] params[:build] = ENV['WERCKER_MAIN_PIPELINE_STARTED'] params[:slug] = ENV['WERCKER_GIT_OWNER'] + '/' + ENV['WERCKER_GIT_REPOSITORY'] params[:commit] = ENV['WERCKER_GIT_COMMIT'] end if params[:branch].nil? # find branch, commit, repo from git command branch = `git rev-parse --abbrev-ref HEAD`.strip params[:branch] = branch != 'HEAD' ? branch : 'master' end if !ENV['VCS_COMMIT_ID'].nil? params[:commit] = ENV['VCS_COMMIT_ID'] elsif params[:commit].nil? params[:commit] = `git rev-parse HEAD`.strip end slug = ENV['CODECOV_SLUG'] params[:slug] = slug unless slug.nil? params[:pr] = params[:pr].sub('#', '') unless params[:pr].nil? params end
detect_ci()
click to toggle source
# File lib/codecov/uploader.rb, line 78 def self.detect_ci ci = if (ENV['CI'] == 'True') && (ENV['APPVEYOR'] == 'True') APPVEYOR elsif !ENV['TF_BUILD'].nil? AZUREPIPELINES elsif (ENV['CI'] == 'true') && !ENV['BITBUCKET_BRANCH'].nil? BITBUCKET elsif (ENV['CI'] == 'true') && (ENV['BITRISE_IO'] == 'true') BITRISE elsif (ENV['CI'] == 'true') && (ENV['BUILDKITE'] == 'true') BUILDKITE elsif (ENV['CI'] == 'true') && (ENV['CIRCLECI'] == 'true') CIRCLE elsif !ENV['CIRRUS_CI'].nil? CIRRUS elsif ENV['CODEBUILD_CI'] == 'true' CODEBUILD elsif (ENV['CI'] == 'true') && (ENV['CI_NAME'] == 'codeship') CODESHIP elsif ((ENV['CI'] == 'true') || (ENV['CI'] == 'drone')) && (ENV['DRONE'] == 'true') DRONEIO elsif (ENV['CI'] == 'true') && (ENV['GITHUB_ACTIONS'] == 'true') GITHUB elsif !ENV['GITLAB_CI'].nil? GITLAB elsif ENV['HEROKU_TEST_RUN_ID'] HEROKU elsif !ENV['JENKINS_URL'].nil? JENKINS elsif (ENV['CI'] == 'true') && (ENV['SEMAPHORE'] == 'true') SEMAPHORE elsif (ENV['CI'] == 'true') && (ENV['SHIPPABLE'] == 'true') SHIPPABLE elsif ENV['TDDIUM'] == 'true' SOLANO elsif ENV['CI_SERVER_NAME'] == 'TeamCity' TEAMCITY elsif (ENV['CI'] == 'true') && (ENV['TRAVIS'] == 'true') TRAVIS elsif (ENV['CI'] == 'true') && !ENV['WERCKER_GIT_BRANCH'].nil? WERCKER end if !RECOGNIZED_CIS.include?(ci) puts [red('x>'), 'No CI provider detected.'].join(' ') else puts "==> #{ci} detected" end ci end
display_header()
click to toggle source
# File lib/codecov/uploader.rb, line 64 def self.display_header puts [ '', ' _____ _', ' / ____| | |', '| | ___ __| | ___ ___ _____ __', '| | / _ \ / _\`|/ _ \/ __/ _ \ \ / /', '| |___| (_) | (_| | __/ (_| (_) \ V /', ' \_____\___/ \__,_|\___|\___\___/ \_/', " Ruby-#{::Codecov::VERSION}", '' ].join("\n") end
gzip_report(report)
click to toggle source
# File lib/codecov/uploader.rb, line 393 def self.gzip_report(report) puts [green('==>'), 'Gzipping contents'].join(' ') io = StringIO.new gzip = Zlib::GzipWriter.new(io) gzip << report gzip.close io.string end
handle_report_response(report)
click to toggle source
# File lib/codecov/uploader.rb, line 516 def self.handle_report_response(report) if report['result']['uploaded'] puts " View reports at #{report['result']['url']}" else puts red(' X> Failed to upload coverage reports') end end
retry_request(req, https)
click to toggle source
# File lib/codecov/uploader.rb, line 366 def self.retry_request(req, https) retries = 3 begin response = https.request(req) rescue Timeout::Error, SocketError => e retries -= 1 if retries.zero? puts 'Timeout or connection error uploading coverage reports to Codecov. Out of retries.' puts e return response end puts 'Timeout or connection error uploading coverage reports to Codecov. Retrying...' puts e retry rescue StandardError => e puts 'Error uploading coverage reports to Codecov. Sorry' puts e.class.name puts e puts "Backtrace:\n\t#{e.backtrace}" return response end response end
upload(report, disable_net_blockers = true)
click to toggle source
# File lib/codecov/uploader.rb, line 36 def self.upload(report, disable_net_blockers = true) net_blockers(:off) if disable_net_blockers display_header ci = detect_ci begin response = upload_to_codecov(ci, report) rescue StandardError => e puts e.message puts e.backtrace.join("\n") raise e unless ::Codecov.pass_ci_if_error response = false end net_blockers(:on) if disable_net_blockers unless response report['result'] = { 'uploaded' => false } raise StandardError.new 'Could not upload reports to Codecov' unless ::Codecov.pass_ci_if_error return report end report['result'] = JSON.parse(response) handle_report_response(report) report end
upload_to_codecov(ci, report)
click to toggle source
# File lib/codecov/uploader.rb, line 404 def self.upload_to_codecov(ci, report) url = ENV['CODECOV_URL'] || 'https://codecov.io' is_enterprise = url != 'https://codecov.io' params = build_params(ci) params_secret_token = params.clone params_secret_token['token'] = 'secret' query = URI.encode_www_form(params) query_without_token = URI.encode_www_form(params_secret_token) gzipped_report = gzip_report(report['codecov']) report['params'] = params report['query'] = query puts [green('==>'), 'Uploading reports'].join(' ') puts " url: #{url}" puts " query: #{query_without_token}" response = false unless is_enterprise response = upload_to_v4(url, gzipped_report, query, query_without_token) return false if response == false end response || upload_to_v2(url, gzipped_report, query, query_without_token) end
upload_to_v2(url, report, query, query_without_token)
click to toggle source
# File lib/codecov/uploader.rb, line 494 def self.upload_to_v2(url, report, query, query_without_token) uri = URI.parse(url.chomp('/') + '/upload/v2') https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = !url.match(/^https/).nil? puts [green('-> '), 'Uploading to Codecov'].join(' ') puts "#{url}#{uri.path}?#{query_without_token}" req = Net::HTTP::Post.new( "#{uri.path}?#{query}", { 'Accept' => 'application/json', 'Content-Encoding' => 'gzip', 'Content-Type' => 'text/plain', 'X-Content-Encoding' => 'gzip' } ) req.body = report res = retry_request(req, https) res&.body end
upload_to_v4(url, report, query, query_without_token)
click to toggle source
# File lib/codecov/uploader.rb, line 433 def self.upload_to_v4(url, report, query, query_without_token) uri = URI.parse(url.chomp('/') + '/upload/v4') https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = !url.match(/^https/).nil? puts [green('-> '), 'Pinging Codecov'].join(' ') puts "#{url}#{uri.path}?#{query_without_token}" req = Net::HTTP::Post.new( "#{uri.path}?#{query}", { 'X-Reduced-Redundancy' => 'false', 'X-Content-Encoding' => 'application/x-gzip', 'Content-Type' => 'text/plain' } ) response = retry_request(req, https) if !response&.code || response.code == '400' puts red(response&.body) return false end reports_url = response.body.lines[0] s3target = response.body.lines[1] if s3target.nil? || s3target.empty? puts red(response.body) return false end puts [green('-> '), 'Uploading to'].join(' ') puts s3target uri = URI(s3target) https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = true req = Net::HTTP::Put.new( s3target, { 'Content-Encoding' => 'gzip', 'Content-Type' => 'text/plain' } ) req.body = report res = retry_request(req, https) if res&.body == '' { 'uploaded' => true, 'url' => reports_url, 'meta' => { 'status' => res.code }, 'message' => 'Coverage reports upload successfully' }.to_json else puts [black('-> '), 'Could not upload reports via v4 API, defaulting to v2'].join(' ') puts red(res&.body || 'nil') nil end end
Private Class Methods
black(str)
click to toggle source
Convenience color methods
# File lib/codecov/uploader.rb, line 557 def self.black(str) str.nil? ? '' : "\e[30m#{str}\e[0m" end
green(str)
click to toggle source
# File lib/codecov/uploader.rb, line 565 def self.green(str) str.nil? ? '' : "\e[32m#{str}\e[0m" end
net_blockers(switch)
click to toggle source
Toggle VCR and WebMock on or off
@param switch Toggle switch for Net Blockers. @return [Boolean]
# File lib/codecov/uploader.rb, line 530 def self.net_blockers(switch) throw 'Only :on or :off' unless %i[on off].include? switch if defined?(VCR) case switch when :on VCR.turn_on! when :off VCR.turn_off!(ignore_cassettes: true) end end if defined?(WebMock) # WebMock on by default # VCR depends on WebMock 1.8.11; no method to check whether enabled. case switch when :on WebMock.enable! when :off WebMock.disable! end end true end
red(str)
click to toggle source
# File lib/codecov/uploader.rb, line 561 def self.red(str) str.nil? ? '' : "\e[31m#{str}\e[0m" end