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

[ci] improve experience with optional GitHub workflows #3740

Merged
merged 19 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .ci/append_comment.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/bash
#
# [description]
# Update comment appending a given body to the specified original comment.
#
# [usage]
# append_comment.sh <COMMENT_ID> <BODY>
#
# COMMENT_ID: ID of comment that should be modified.
#
# BODY: Text that will be appended to the original comment body.

set -e

if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit -1
fi

if [ $# -ne 2 ]; then
echo "Usage: $0 <COMMENT_ID> <BODY>"
exit -1
fi

comment_id=$1
body=$2

old_comment_body=$(curl -sL \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/issues/comments/$comment_id" | \
jq '.body')
StrikerRUS marked this conversation as resolved.
Show resolved Hide resolved
body=${body/failure/failure ❌}
body=${body/error/failure ❌}
body=${body/cancelled/failure ❌}
body=${body/timed_out/failure ❌}
body=${body/success/success ✔️}
data=$(jq -n \
--argjson body "${old_comment_body%?}\r\n\r\n$body\"" \
'{"body":$body}')
StrikerRUS marked this conversation as resolved.
Show resolved Hide resolved
curl -sL \
-X PATCH \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
-d "$data" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/issues/comments/$comment_id"
82 changes: 82 additions & 0 deletions .ci/get_workflow_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# coding: utf-8
"""Get the most recent status of workflow for the current PR."""
import json
from os import environ
from sys import argv, exit
from time import sleep
try:
from urllib import request
except ImportError:
import urllib2 as request
Comment on lines +7 to +10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this necessary? I have a lot more experience with requests than urllib, so I haven't seen this pattern

Copy link
Collaborator Author

@StrikerRUS StrikerRUS Jan 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for Python 2/3 compatibility. For the same reason I'm not using with ... statement but manually close connection. I know we dropped Python 2 support some time ago. But default installed Python version in ubuntu-latest container we run our actions in is 2. I really don't want to install conda/new Python version/additional packages because I want to keep Optional checks workflow where this script is used as lightweight as possible with the aim of the fastest execution time. So this Python-agnostic and dependency-free solution will work fine on any default Python version that can be installed in container.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok I see, thanks. Fine with me



def get_runs(workflow_name):
"""Get all triggering workflow comments in the current PR.

Parameters
----------
workflow_name : string
Name of the workflow.

Returns
-------
pr_runs : list
List of comment objects sorted by the time of creation in decreasing order.
"""
pr_runs = []
if environ.get("GITHUB_EVENT_NAME", "") == "pull_request":
pr_number = int(environ.get("GITHUB_REF").split('/')[-2])
req = request.Request(url="{}/repos/microsoft/LightGBM/issues/{}/comments".format(environ.get("GITHUB_API_URL"),
pr_number),
headers={"Accept": "application/vnd.github.v3+json"})
url = request.urlopen(req)
data = json.loads(url.read().decode('utf-8'))
url.close()
pr_runs = [i for i in data
if i['author_association'].lower() in {'owner', 'member', 'collaborator'}
and i['body'].startswith('/gha run')
and 'Workflow **{}** has been triggered!'.format(workflow_name) in i['body']]
return pr_runs[::-1]


def get_status(runs):
"""Get the most recent status of workflow for the current PR.

Parameters
----------
runs : list
List of comment objects sorted by the time of creation in decreasing order.

Returns
-------
status : string
The most recent status of workflow.
Can be 'success', 'failure' or 'in-progress'.
"""
status = 'success'
for run in runs:
body = run['body']
if "Status: " in body:
if "Status: skipped" in body:
continue
if "Status: failure" in body:
status = 'failure'
break
if "Status: success" in body:
status = 'success'
break
else:
status = 'in-progress'
break
return status


if __name__ == "__main__":
workflow_name = argv[1]
while True:
status = get_status(get_runs(workflow_name))
if status != 'in-progress':
break
sleep(60)
if status == 'failure':
exit(1)
45 changes: 45 additions & 0 deletions .ci/rerun_workflow.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
#
# [description]
# Rerun specified workflow for given pull request.
#
# [usage]
# rerun_workflow.sh <WORKFLOW_ID> <PR_NUMBER> <PR_BRANCH>
#
# WORKFLOW_ID: Identifier (config name of ID) of a workflow to be rerun.
#
# PR_NUMBER: Number of pull request for which workflow should be rerun.
#
# PR_BRANCH: Name of pull request's branch.

set -e

if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit -1
fi

if [ $# -ne 3 ]; then
echo "Usage: $0 <WORKFLOW_ID> <PR_NUMBER> <PR_BRANCH>"
exit -1
fi

workflow_id=$1
pr_number=$2
pr_branch=$3

runs=$(curl -sL \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/actions/workflows/${workflow_id}/runs?event=pull_request&branch=${pr_branch}" | \
jq '.workflow_runs')
runs=$(echo $runs | jq --arg pr_number "$pr_number" --arg pr_branch "$pr_branch" 'map(select(.event == "pull_request" and ((.pull_requests | length) != 0 and (.pull_requests[0].number | tostring) == $pr_number or .head_branch == $pr_branch)))')
runs=$(echo $runs | jq 'sort_by(.run_number) | reverse')

if [[ $(echo $runs | jq 'length') -gt 0 ]]; then
curl -sL \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/actions/runs/$(echo $runs | jq '.[0].id')/rerun"
fi
51 changes: 51 additions & 0 deletions .ci/set_commit_status.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/bash
#
# [description]
# Set a status with a given name to the specified commit.
#
# [usage]
# set_commit_status.sh <NAME> <STATUS> <SHA>
#
# NAME: Name of status.
# Status with existing name overwrites a previous one.
#
# STATUS: Status to be set.
# Can be "error", "failure", "pending" or "success".
#
# SHA: SHA of a commit to set a status on.

set -e

if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit -1
fi

if [ $# -ne 3 ]; then
echo "Usage: $0 <NAME> <STATUS> <SHA>"
exit -1
fi

name=$1

status=$2
status=${status/error/failure}
status=${status/cancelled/failure}
status=${status/timed_out/failure}
status=${status/in_progress/pending}
status=${status/queued/pending}

sha=$3

data=$(jq -n \
--arg state $status \
--arg url "${GITHUB_SERVER_URL}/microsoft/LightGBM/actions/runs/${GITHUB_RUN_ID}" \
--arg name "$name" \
'{"state":$state,"target_url":$url,"context":$name}')

curl -sL \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
-d "$data" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/statuses/$sha"
47 changes: 47 additions & 0 deletions .ci/trigger_dispatch_run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/bin/bash
#
# [description]
# Trigger manual workflow run by a dispatch event.
#
# [usage]
# trigger_dispatch_run.sh <PR_URL> <COMMENT_ID> <DISPATCH_NAME>
#
# PR_URL: URL of pull request from which dispatch is triggering.
#
# COMMENT_ID: ID of comment that is triggering a dispatch.
#
# DISPATCH_NAME: Name of a dispatch to be triggered.

set -e

if [ -z "$GITHUB_ACTIONS" ]; then
echo "Must be run inside GitHub Actions CI"
exit -1
fi

if [ $# -ne 3 ]; then
echo "Usage: $0 <PR_URL> <COMMENT_ID> <DISPATCH_NAME>"
exit -1
fi

pr_url=$1
comment_id=$2
dispatch_name=$3

pr=$(curl -sL \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
"$pr_url")
data=$(jq -n \
--arg event_type "$dispatch_name" \
--arg pr_number "$(echo $pr | jq '.number')" \
--arg pr_sha "$(echo $pr | jq '.head.sha')" \
--arg pr_branch "$(echo $pr | jq '.head.ref')" \
--arg comment_number "$comment_id" \
'{"event_type":$event_type,"client_payload":{"pr_number":$pr_number,"pr_sha":$pr_sha,"pr_branch":$pr_branch,"comment_number":$comment_number}}')
curl -sL \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token $SECRETS_WORKFLOW" \
-d "$data" \
"${GITHUB_API_URL}/repos/microsoft/LightGBM/dispatches"
25 changes: 25 additions & 0 deletions .github/workflows/optional_checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Optional checks

on:
pull_request:
branches:
- master

jobs:
all-successful:
timeout-minutes: 120
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2.3.4
with:
fetch-depth: 5
submodules: false
- name: Check that all tests succeeded
run: |
workflows=("R valgrind tests")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we should add optional workflows which failures should block PR merging.

for i in "${workflows[@]}"; do
python "$GITHUB_WORKSPACE/.ci/get_workflow_status.py" "$i" \
|| { echo "The last reported status from workflow \"$i\" is failure. Commit fixes and rerun the workflow."; \
exit -1; }
done
28 changes: 23 additions & 5 deletions .github/workflows/r_artifacts.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
name: R artifact builds

on:
pull_request_review_comment:
types: [created]
repository_dispatch:
types: [gha_run_build_r_artifacts]

jobs:
cran-package:
name: cran-package
if: github.event.comment.body == '/gha build r-artifacts' && contains('OWNER,MEMBER,COLLABORATOR', github.event.comment.author_association)
timeout-minutes: 60
runs-on: ubuntu-latest
container: rocker/r-base
env:
SECRETS_WORKFLOW: ${{ secrets.WORKFLOW }}
steps:
- name: Install Git before checkout
- name: Install essential software before checkout
shell: bash
run: |
apt-get update
apt-get install --no-install-recommends -y git
apt-get install --no-install-recommends -y \
curl \
git \
jq
- name: Checkout repository
uses: actions/checkout@v2.3.4
with:
fetch-depth: 5
submodules: true
repository: microsoft/LightGBM
ref: "refs/pull/${{ github.event.client_payload.pr_number }}/merge"
- name: Send init status
if: ${{ always() }}
run: |
$GITHUB_WORKSPACE/.ci/append_comment.sh \
"${{ github.event.client_payload.comment_number }}" \
"Workflow **${{ github.workflow }}** has been triggered! 🚀\r\n${GITHUB_SERVER_URL}/microsoft/LightGBM/actions/runs/${GITHUB_RUN_ID}"
- name: Build package
shell: bash
id: build_package
Expand All @@ -37,3 +49,9 @@ jobs:
with:
name: ${{ steps.build_package.outputs.artifact_name }}
path: ${{ steps.build_package.outputs.artifact_path }}
- name: Send final status
if: ${{ always() }}
run: |
$GITHUB_WORKSPACE/.ci/append_comment.sh \
"${{ github.event.client_payload.comment_number }}" \
"Status: ${{ job.status }}."
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know that direct link for artifact downloading will be more useful. But unfortunately, it is impossible:

Gets a redirect URL to download an archive for a repository. This URL expires after 1 minute.
https://docs.github.com/en/free-pro-team@latest/rest/reference/actions#download-an-artifact

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha it's ok, we don't use this job that often and it's only one or two more clicks to get to the artifact

Loading