Skip to content

Commit

Permalink
Merge branch 'release/v20200412'
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreMiras committed Apr 12, 2020
2 parents 02ae3ac + 8328d94 commit b6cda81
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 54 deletions.
39 changes: 29 additions & 10 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,37 @@ jobs:
- name: Unit tests
run: make test

- name: With GITHUB_TOKEN
- name: Default arguments
uses: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: With COVERALLS_REPO_TOKEN
- name: With --github-token GITHUB_TOKEN
uses: ./
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: With GITHUB_TOKEN and COVERALLS_REPO_TOKEN
- name: With --github-token COVERALLS_REPO_TOKEN
uses: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
with:
github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}

- name: With --debug
uses: ./
with:
debug: true

- name: With --parallel
uses: ./
with:
parallel: true

coveralls_finish:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Coveralls Finished
uses: ./
with:
github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}
parallel-finished: true
debug: true
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Change Log

## [v20200412]

- Leverages `with` keyword to configure the action
- Adds parallel support
- Adds webhook support
- Increases test coverage to 100%


## [v20200411]

- Initial release
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ COVERAGE=$(VIRTUAL_ENV)/bin/coverage
BLACK=$(VIRTUAL_ENV)/bin/black
SOURCES=src/ tests/
DOCKER_IMAGE_LINUX=andremiras/coveralls-python-action
DOCKER_WORKDIR=/github/workspace
DOCKER_WORKDIR_FLAG=--workdir $(DOCKER_WORKDIR)
DOCKER_VOLUME=$(CURDIR):$(DOCKER_WORKDIR)
DOCKER_VOLUME_FLAG=--volume $(DOCKER_VOLUME)



$(VIRTUAL_ENV):
Expand Down Expand Up @@ -57,7 +62,7 @@ docker/build:
docker build --tag=$(DOCKER_IMAGE_LINUX) .

docker/run:
docker run -it --rm --env-file .env $(DOCKER_IMAGE_LINUX)
docker run -it --rm --env-file .env $(DOCKER_WORKDIR_FLAG) $(DOCKER_VOLUME_FLAG) $(DOCKER_IMAGE_LINUX)

docker/run/shell:
docker run -it --rm --env-file .env --entrypoint /bin/sh $(DOCKER_IMAGE_LINUX)
docker run -it --rm --env-file .env $(DOCKER_WORKDIR_FLAG) $(DOCKER_VOLUME_FLAG) --entrypoint /bin/sh $(DOCKER_IMAGE_LINUX)
49 changes: 38 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
# coveralls-python-action

[![push](https://github.com/AndreMiras/coveralls-python-action/workflows/push/badge.svg?branch=develop)](https://github.com/AndreMiras/coveralls-python-action/actions?query=workflow%3Apush)
[![Coverage Status](https://coveralls.io/repos/github/AndreMiras/coveralls-python-action/badge.svg?branch=develop)](https://coveralls.io/github/AndreMiras/coveralls-python-action?branch=develop)

GitHub Action for Python Coveralls.io
GitHub Action for Python [Coveralls.io](https://coveralls.io/)

## Usage
You simply need to set one of the following two environment variables:
- `GITHUB_TOKEN`
- `COVERALLS_REPO_TOKEN`

## Example usage
Assuming you have a `make test` that runs coverage testing.
The following will upload it to coveralls.io.
```yml
First make sure your `coverage.py` is configured with [`relative_files = True`](https://coverage.readthedocs.io/en/coverage-5.0.4/config.html#config-run-relative-files).

Then assuming you have a `make test` that runs coverage testing.
The following workflow will upload it to coveralls.io.
```yaml
name: push
on: [push, pull_request]

Expand All @@ -28,6 +26,35 @@ jobs:

- name: Coveralls
uses: AndreMiras/coveralls-python-action@develop
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
parallel: true

coveralls_finish:
needs: test
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: AndreMiras/coveralls-python-action@develop
with:
parallel-finished: true
github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}
```
## Configuration
```yaml
- uses: AndreMiras/coveralls-python-action@develop
with:
# The `GITHUB_TOKEN` or `COVERALLS_REPO_TOKEN`.
# Default: ${{ github.token }}
github-token: ''
# Set to `true` if you are using parallel jobs, then use `parallel-finished: true` for the last action.
# Default: false
parallel: ''
# Set to `true` for the last action when using `parallel: true`.
# Note this phase requires `github-token: ${{ secrets.COVERALLS_REPO_TOKEN }}`.
# Default: false
parallel-finished: ''
# Set to true to increase logger verbosity.
# Default: false
debug: ''
```
30 changes: 28 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
name: 'Coveralls'
description: 'Reports to coveralls.io'
name: 'Coveralls Python'
author: 'Andre Miras'
description: 'Python coverage reports via coveralls.io'
branding:
color: 'green'
icon: 'percent'
inputs:
github-token:
description: 'The `GITHUB_TOKEN` or `COVERALLS_REPO_TOKEN`.'
default: ${{ github.token }}
parallel:
description: 'Set to true if you are using parallel jobs, then use `parallel-finished: true` for the last action.'
default: false
parallel-finished:
description: 'Set to true for the last action when using `parallel: true`.'
default: false
debug:
description: 'Set to `true` to increase logger verbosity.'
default: false
runs:
using: 'docker'
image: 'Dockerfile'
args:
- --github-token
- ${{ inputs.github-token }}
- --parallel
- ${{ inputs.parallel }}
- --parallel-finished
- ${{ inputs.parallel-finished }}
- --debug
- ${{ inputs.debug }}
149 changes: 122 additions & 27 deletions src/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import os
import sys
from enum import Enum
from unittest import mock

import requests
from coveralls.api import Coveralls, CoverallsException

log = logging.getLogger(__name__)
Expand All @@ -21,57 +23,150 @@ def set_failed(message):
sys.exit(ExitCode.FAILURE)


def run_coveralls():
"""Submits to coveralls using either GITHUB_TOKEN or COVERALLS_REPO_TOKEN."""
def patch_os_environ(repo_token, parallel):
"""
Temporarily updates the environment variable to satisfy coveralls Python API.
That is because the coveralls package API consumes mostly environment variables.
"""
# https://github.com/coveralls-clients/coveralls-python/blob/2.0.0/coveralls/api.py#L146
parallel = "true" if parallel else ""
environ = {"COVERALLS_REPO_TOKEN": repo_token, "COVERALLS_PARALLEL": parallel}
log.debug(f"Patching os.environ with: {environ}")
return mock.patch.dict("os.environ", environ)


def run_coveralls(repo_token, parallel=False):
"""Submits job to coveralls."""
# note that coveralls.io "service_name" can either be:
# - "github-actions" (local development?)
# - "github" (from GitHub jobs?)
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
COVERALLS_REPO_TOKEN = os.environ.get("COVERALLS_REPO_TOKEN")
repo_token = GITHUB_TOKEN or COVERALLS_REPO_TOKEN
if GITHUB_TOKEN and COVERALLS_REPO_TOKEN:
log.warning("Both GITHUB_TOKEN and COVERALLS_REPO_TOKEN defined.")
assert repo_token, "Either GITHUB_TOKEN or COVERALLS_REPO_TOKEN must be set."
# for some reasons the "service_name" can be one or the other
# (depending on where it's ran from?)
service_names = ("github", "github-actions")
kwargs = {"repo_token": repo_token}
result = None
for service_name in service_names:
log.info(f"Trying submitting coverage with service_name: {service_name}...")
coveralls = Coveralls(service_name=service_name, **kwargs)
try:
result = coveralls.wear()
break
except CoverallsException as e:
log.warning(f"Failed submitting coverage with service_name: {service_name}")
log.warning(e)
with patch_os_environ(repo_token, parallel):
coveralls = Coveralls(service_name=service_name)
try:
result = coveralls.wear()
break
except CoverallsException as e:
log.warning(
f"Failed submitting coverage with service_name: {service_name}"
)
log.warning(e)
if result is None:
set_failed("Failed to submit coverage")
log.info(result)
log.debug(result)
log.info(result["url"])


def get_github_sha():
"""e.g. ffac537e6cbbf934b08745a378932722df287a53"""
return os.environ.get("GITHUB_SHA")


def get_github_ref():
"""
The branch or tag ref that triggered the workflow.
For example, refs/heads/feature-branch-1.
If neither a branch or tag is available for the variable will not exist.
- for pull_request events: refs/pull/<pull_request_number>/merge
- for push event: refs/heads/<branch>
https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables
"""
return os.environ.get("GITHUB_REF")


def get_pull_request_number(github_ref):
"""
>>> get_pull_request_number("refs/pull/<pull_request_number>/merge")
"<pull_request_number>"
"""
return github_ref.split("/")[2]


def is_pull_request(github_ref):
return github_ref and github_ref.startswith("refs/pull/")


def get_build_number(github_sha, github_ref):
build_number = github_sha
if is_pull_request(github_ref):
pull_request_number = get_pull_request_number(github_ref)
build_number = f"{github_sha}-PR-{pull_request_number}"
return build_number


def post_webhook(repo_token):
""""
Note for this call, the repo token is always `COVERALLS_REPO_TOKEN`.
It cannot be the `GITHUB_TOKEN`.
https://docs.coveralls.io/parallel-build-webhook
"""
url = "https://coveralls.io/webhook"
build_num = get_build_number(get_github_sha(), get_github_ref())
params = {"repo_token": repo_token}
json = {"payload": {"build_num": build_num, "status": "done"}}
log.debug(f'requests.post("{url}", params={params}, json={json})')
response = requests.post(url, params=params, json=json)
response.raise_for_status()
log.debug(f"response.json(): {response.json()}")
assert response.json() == {"done": True}, response.json()


def str_to_bool(value):
if isinstance(value, bool):
return value
if value.lower() in {"false", "f", "0", "no", "n"}:
return False
elif value.lower() in {"true", "t", "1", "yes", "y"}:
return True
raise ValueError(f"{value} is not a valid boolean value")


def parse_args():
parser = argparse.ArgumentParser(description="Greetings")
parser.add_argument("who_to_greet", help="Who to greet", nargs="?", default="World")
parser.add_argument("--verbose", action="store_true")
parser.add_argument("--github-token", nargs=1, required=True)
parser.add_argument(
"--parallel", type=str_to_bool, nargs="?", const=True, default=False
)
parser.add_argument(
"--parallel-finished", type=str_to_bool, nargs="?", const=True, default=False
)
parser.add_argument(
"--debug", type=str_to_bool, nargs="?", const=True, default=False
)
return parser.parse_args()


def set_log_level(verbose):
level = logging.DEBUG if verbose else logging.INFO
def set_log_level(debug):
level = logging.DEBUG if debug else logging.INFO
log.addHandler(logging.StreamHandler())
log.setLevel(level)


def main():
args = parse_args()
verbose = args.verbose
set_log_level(verbose)
run_coveralls()


if __name__ == "__main__":
debug = args.debug
repo_token = args.github_token[0]
parallel = args.parallel
parallel_finished = args.parallel_finished
set_log_level(debug)
log.debug(f"args: {args}")
if parallel_finished:
post_webhook(repo_token)
else:
run_coveralls(repo_token, parallel)


def try_main():
try:
main()
except Exception as e:
set_failed(e)


if __name__ == "__main__":
try_main() # pragma: no cover
Loading

0 comments on commit b6cda81

Please sign in to comment.