Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): support officially documented generic CI env vars (#299) #300

Merged
merged 3 commits into from Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 32 additions & 1 deletion coveralls/api.py
Expand Up @@ -16,6 +16,8 @@

log = logging.getLogger('coveralls.api')

NUMBER_REGEX = re.compile(r'(\d+)$', re.IGNORECASE)


class Coveralls:
# pylint: disable=too-many-public-methods
Expand Down Expand Up @@ -144,7 +146,36 @@ def load_config_from_semaphore():
def load_config_from_unknown():
return 'coveralls-python', None, None, None

def load_config_from_generic_ci_environment(self):
# Inspired by the official client:
# coveralls-ruby in lib/coveralls/configuration.rb
# (set_standard_service_params_for_generic_ci)

# The meaning of each env var is clarified in:
# https://github.com/lemurheavy/coveralls-public/issues/1558

config = {
'service_name': os.environ.get('CI_NAME'),
'service_number': os.environ.get('CI_BUILD_NUMBER'),
'service_build_url': os.environ.get('CI_BUILD_URL'),
'service_job_id': os.environ.get('CI_JOB_ID'),
'service_branch': os.environ.get('CI_BRANCH'),
}

pr_match = NUMBER_REGEX.findall(os.environ.get('CI_PULL_REQUEST', ''))
if pr_match:
config['service_pull_request'] = pr_match[-1]
TheKevJames marked this conversation as resolved.
Show resolved Hide resolved

non_empty = {key: value for key, value in config.items() if value}
self.config.update(non_empty)

def load_config_from_ci_environment(self):
# As defined at the bottom of
# https://docs.coveralls.io/supported-ci-services
# there are a few env vars that should support any arbitrary CI.
# We load them first and allow more specific vars to overwrite
self.load_config_from_generic_ci_environment()

if os.environ.get('APPVEYOR'):
name, job, number, pr = self.load_config_from_appveyor()
elif os.environ.get('BUILDKITE'):
Expand All @@ -166,7 +197,7 @@ def load_config_from_ci_environment(self):
else:
name, job, number, pr = self.load_config_from_unknown()

self.config['service_name'] = name
self.config.setdefault('service_name', name)
if job:
self.config['service_job_id'] = job
if number:
Expand Down
32 changes: 32 additions & 0 deletions docs/usage/configuration.rst
Expand Up @@ -156,3 +156,35 @@ you can copy from the coveralls.io website).
As per `#245 <https://github.com/TheKevJames/coveralls-python/issues/245>`_,
our users suggest leaving "keep this value secret" unchecked -- this may be
secure enough as-is, in that a user making a PR cannot access this variable.

Other CI systems
----------------

As specified in the Coveralls `official docs
<https://docs.coveralls.io/supported-ci-services>`
other CI systems can be supported if the following environment variables are
defined::

CI_NAME
# Name of the CI service being used.
CI_BUILD_NUMBER
# The number assigned to the build by your CI service.
CI_BUILD_URL
# URL to a webpage showing the build information/logs.
CI_BRANCH
# For pull requests this is the name of the branch being targeted,
# otherwise it corresponds to the name of the current branch or tag.
CI_JOB_ID (optional)
# For parallel builds, the number assigned to each job comprising the build.
# When missing, Coveralls will assign an incrementing integer (1, 2, 3 ...).
# This value should not change between multiple runs of the build.
CI_PULL_REQUEST (optional)
# If given, corresponds to the number of the pull request, as specified
# in the supported repository hosting service (GitHub, GitLab, etc).
# This variable expects a value defined as an integer, e.g.:
# CI_PULL_REQUEST=42 (recommended)
# However, for flexibility, any single line string ending with the same
# integer value can also be used (such as the pull request URL or
# relative path), e.g.:
# CI_PULL_REQUEST='myuser/myrepo/pull/42'
# CI_PULL_REQUEST='https://github.com/myuser/myrepo/pull/42'
34 changes: 34 additions & 0 deletions tests/api/configuration_test.py
Expand Up @@ -198,6 +198,40 @@ def test_semaphore_20_no_config(self):
assert cover.config['service_number'] == 'b86b3adf'
assert cover.config['service_pull_request'] == '9999'

@mock.patch.dict(
os.environ,
{'CI_NAME': 'generic-ci',
'CI_PULL_REQUEST': 'pull/1234',
'CI_JOB_ID': 'bb0e00166',
'CI_BUILD_NUMBER': '3',
'CI_BUILD_URL': 'https://generic-ci.local/build/123456789',
'CI_BRANCH': 'fixup-branch',
'COVERALLS_REPO_TOKEN': 'xxx'},
clear=True)
def test_generic_no_config(self):
cover = Coveralls()
assert cover.config['service_name'] == 'generic-ci'
assert cover.config['service_job_id'] == 'bb0e00166'
assert cover.config['service_branch'] == 'fixup-branch'
assert cover.config['service_pull_request'] == '1234'

@mock.patch.dict(
os.environ,
{'CI_NAME': 'generic-ci',
'CI_PULL_REQUEST': '',
'CI_JOB_ID': 'bb0e00166',
'CI_BUILD_NUMBER': '3',
'CI_BUILD_URL': 'https://generic-ci.local/build/123456789',
'CI_BRANCH': 'fixup-branch',
'COVERALLS_REPO_TOKEN': 'xxx'},
clear=True)
def test_generic_no_config_no_pr(self):
cover = Coveralls()
assert cover.config['service_name'] == 'generic-ci'
assert cover.config['service_job_id'] == 'bb0e00166'
assert cover.config['service_branch'] == 'fixup-branch'
assert 'service_pull_request' not in cover.config

@mock.patch.dict(os.environ, {'COVERALLS_SERVICE_NAME': 'xxx'}, clear=True)
def test_service_name_from_env(self):
cover = Coveralls(repo_token='yyy')
Expand Down