Skip to content

Commit

Permalink
Merge 2e68872 into 71ce30e
Browse files Browse the repository at this point in the history
  • Loading branch information
CasperWA authored Oct 31, 2023
2 parents 71ce30e + 2e68872 commit f2fdd2a
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 11 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ jobs:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
branch: protected
unprotect_reviews: true
acceptable_conclusions: success,skipped

- name: Pushing to a protected branch without any changes
uses: ./
with:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
ref: refs/heads/protected
unprotect_reviews: true
acceptable_conclusions: success,skipped

force-pushing:
needs: [protected]
Expand Down Expand Up @@ -138,6 +140,7 @@ jobs:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
branch: protected
unprotect_reviews: true
acceptable_conclusions: success,skipped

- name: This runs ONLY if the previous step doesn't fail
if: steps.push_no_force.outcome != 'failure' || steps.push_no_force.conclusion != 'success'
Expand All @@ -152,6 +155,7 @@ jobs:
branch: protected
unprotect_reviews: true
force: yes
acceptable_conclusions: success,skipped

branch_and_ref:
needs: [force-pushing]
Expand Down Expand Up @@ -220,6 +224,35 @@ jobs:
force: yes
debug: true

acceptable_conclusions:
needs: [path]
runs-on: ubuntu-latest
name: Testing - Default for `acceptable_conclusions` with skipped checks
steps:
- name: Use local action (checkout)
uses: actions/checkout@v4

- name: Perform changes
run: ./ci.sh
working-directory: .github/utils

- name: Push to protected branch without 'skipped' in 'acceptable_conclusions'
id: push_skipped
continue-on-error: true
uses: ./
with:
token: '${{ secrets.CI_PUSH_TO_PROTECTED_BRANCH }}'
branch: protected
unprotect_reviews: true
# Default value - set here explicitly for clarity
acceptable_conclusions: success

- name: This runs ONLY if the previous step doesn't fail
if: steps.push_skipped.outcome != 'failure' || steps.push_skipped.conclusion != 'success'
run: |
echo "Outcome: ${{ steps.push_skipped.outcome }} (not 'failure'), Conclusion: ${{ steps.push_skipped.conclusion }} (not 'success')"
exit 1
pre-commit:
runs-on: ubuntu-latest
steps:
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/test_status_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ jobs:
steps:
- name: Important status check
run: echo "Very important status check - SUCCESS!"

skipped_mock_status_check:
runs-on: ubuntu-latest
name: Skipped Mock Status Check
if: ${{ !always() }}
steps:
- name: Skipped status check
run: echo "Very important skipped status check - SKIPPED!"
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ All input names in **bold** are _required_.
| `debug` | Set `set -x` in `entrypoint.sh` when running the action. This is for debugging the action. | `False` |
| `path` | A path to the working directory of the action. This should be relative to the `$GITHUB_WORKSPACE`. | `.` |
| `gh_rest_api_base_url` | The base URL for the GitHub REST API. This is useful for GitHub Enterprise users.</br>Note, `/api/v3` will be appended to this value if it does not already exist. See the note [here](https://docs.github.com/en/[email protected]/rest/quickstart?apiVersion=2022-11-28&tool=curl#using-curl-commands-in-github-actions). | `https://api.github.com` |
| `acceptable_conclusions` | A comma-separated list of acceptable statuses. If any of these statuses are present, the action will not fail.</br></br>See the [GitHub REST API documentation](https://docs.github.com/en/rest/actions/workflow-jobs#get-a-job-for-a-workflow-run), specifically, the Response schema's "conclusion" property's `enum` values, for a complete list of supported values (excluding `null`). | `success` |

## License

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ inputs:
description: 'The base URL for the GitHub REST API. This is useful for GitHub Enterprise users. Note, `/api/v3` will be appended to this value if it does not already exist. See the note here: https://docs.github.com/en/[email protected]/rest/quickstart?apiVersion=2022-11-28&tool=curl#using-curl-commands-in-github-actions.'
required: false
default: 'https://api.github.com'
acceptable_conclusions:
description: 'A comma-separated list of acceptable conclusions. If any of these conclusions are present, the action will not fail.'
required: false
default: 'success'
runs:
using: 'docker'
image: 'Dockerfile'
44 changes: 40 additions & 4 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ unprotect () {
y | Y | yes | Yes | YES | true | True | TRUE | on | On | ON)
if [ -n "${PUSH_PROTECTED_CHANGED_BRANCH}" ] && [ -n "${PUSH_PROTECTED_PROTECTED_BRANCH}" ]; then
echo -e "\nRemove '${INPUT_BRANCH}' pull request review protection ..."
push-action --token "${INPUT_TOKEN}" --ref "${INPUT_BRANCH}" --temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" -- unprotect_reviews

push-action \
--token "${INPUT_TOKEN}" \
--ref "${INPUT_BRANCH}" \
--temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" \
-- unprotect_reviews

echo "Remove '${INPUT_BRANCH}' pull request review protection ... DONE!"
fi
;;
Expand All @@ -36,7 +42,13 @@ protect () {
y | Y | yes | Yes | YES | true | True | TRUE | on | On | ON)
if [ -n "${PUSH_PROTECTED_CHANGED_BRANCH}" ] && [ -n "${PUSH_PROTECTED_PROTECTED_BRANCH}" ]; then
echo -e "\nRe-add '${INPUT_BRANCH}' pull request review protection ..."
push-action --token "${INPUT_TOKEN}" --ref "${INPUT_BRANCH}" --temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" -- protect_reviews

push-action \
--token "${INPUT_TOKEN}" \
--ref "${INPUT_BRANCH}" \
--temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" \
-- protect_reviews

echo "Re-add '${INPUT_BRANCH}' pull request review protection ... DONE!"
fi
;;
Expand All @@ -52,14 +64,38 @@ wait_for_checks() {
echo -e "\nWaiting for status checks to finish for '${PUSH_PROTECTED_TEMPORARY_BRANCH}' ..."
# Sleep for 5 seconds to let the workflows start
sleep ${INPUT_SLEEP}
push-action --token "${INPUT_TOKEN}" --ref "${INPUT_BRANCH}" --temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" --wait-timeout "${INPUT_TIMEOUT}" --wait-interval "${INPUT_INTERVAL}" -- wait_for_checks

ACCEPTABLE_CONCLUSIONS=()
if [ -n "${INPUT_ACCEPTABLE_CONCLUSIONS}" ]; then
while IFS="," read -ra CONCLUSIONS; do
for CONCLUSION in "${CONCLUSIONS[@]}"; do
ACCEPTABLE_CONCLUSIONS+=(--acceptable-conclusion="${CONCLUSION}")
done
done <<< "${INPUT_ACCEPTABLE_CONCLUSIONS}"
fi

push-action \
--token "${INPUT_TOKEN}" \
--ref "${INPUT_BRANCH}" \
--temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" \
--wait-timeout "${INPUT_TIMEOUT}" \
--wait-interval "${INPUT_INTERVAL}"\
"${ACCEPTABLE_CONCLUSIONS[@]}" \
-- wait_for_checks

echo "Waiting for status checks to finish for '${PUSH_PROTECTED_TEMPORARY_BRANCH}' ... DONE!"
fi
}
remove_remote_temp_branch() {
if [ -n "${PUSH_PROTECTED_CHANGED_BRANCH}" ] && [ -n "${PUSH_PROTECTED_PROTECTED_BRANCH}" ]; then
echo -e "\nRemoving temporary branch '${PUSH_PROTECTED_TEMPORARY_BRANCH}' ..."
push-action --token "${INPUT_TOKEN}" --ref "${INPUT_BRANCH}" --temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" -- remove_temp_branch

push-action \
--token "${INPUT_TOKEN}" \
--ref "${INPUT_BRANCH}" \
--temp-branch "${PUSH_PROTECTED_TEMPORARY_BRANCH}" \
-- remove_temp_branch

echo "Removing temporary branch '${PUSH_PROTECTED_TEMPORARY_BRANCH}' ... DONE!"
fi
}
Expand Down
32 changes: 25 additions & 7 deletions push_action/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
Match found required GitHub Actions runs found in 1)
4) Wait and do 3) again until required GitHub Actions jobs have "status": "completed"
If "conclusion": "success" YAY
If "conclusion" != "success" FAIL this action
If "conclusion" in inputs provided through `--acceptable-conclusion`
(default: "success") YAY
Otherwise, FAIL this action
"""
import argparse
Expand All @@ -41,6 +42,7 @@
get_workflow_run_jobs,
remove_branch,
)
from push_action.validate import validate_conclusions

if TYPE_CHECKING: # pragma: no cover
from typing import Any, Dict
Expand Down Expand Up @@ -80,7 +82,10 @@ def wait() -> None:
# All jobs are completed
print("All required GitHub Actions jobs complete!", flush=True)
unsuccessful_jobs = [
_ for _ in actions_required if _.get("conclusion", "") != "success"
job_run
for job_run in actions_required
if job_run.get("conclusion", "")
not in IN_MEMORY_CACHE["acceptable_conclusions"]
]
break

Expand All @@ -100,6 +105,7 @@ def wait() -> None:
if _["name"] in required_statuses and _["status"] != "completed"
]
)

if actions_required:
print(
f"{len(actions_required)} required GitHub Actions jobs have not yet "
Expand Down Expand Up @@ -281,6 +287,16 @@ def main() -> None:
),
default=30,
)
parser.add_argument(
"--acceptable-conclusion",
type=str,
help=(
"Acceptable conclusion for the wait_for_checks run to be considered "
"successful"
),
action="append",
default=["success"],
)
parser.add_argument(
"ACTION",
type=str,
Expand All @@ -301,6 +317,10 @@ def main() -> None:

fail = ""
try:
IN_MEMORY_CACHE["acceptable_conclusions"] = validate_conclusions(
IN_MEMORY_CACHE["args"].acceptable_conclusion
)

if IN_MEMORY_CACHE["args"].ACTION == "wait_for_checks":
wait()
elif IN_MEMORY_CACHE["args"].ACTION == "remove_temp_branch":
Expand All @@ -315,10 +335,8 @@ def main() -> None:
print(compile_origin_url(), end="", flush=True)
else:
raise RuntimeError(f"Unknown ACTIONS {IN_MEMORY_CACHE['args'].ACTION!r}")

except Exception as exc: # pylint: disable=broad-except
fail = f"{exc.__class__.__name__}: {exc}"

if fail:
sys.exit(fail)
else:
sys.exit()
sys.exit(fail or None)
45 changes: 45 additions & 0 deletions push_action/validate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
"""Validate inputs."""
from __future__ import annotations

from urllib.parse import urlsplit


VALID_CONCLUSIONS = [
"action_required",
"cancelled",
"failure",
"neutral",
"skipped",
"success",
"timed_out",
]
"""List of valid GitHub Actions workflow job run conclusions.
This is taken from
https://docs.github.com/en/rest/actions/workflow-jobs?apiVersion=2022-11-28#get-a-job-for-a-workflow-run
as of 30.10.2023.
"""

GITHUB_FREE_REST_API_BASE_URL = "https://api.github.com"
"""Base URL for GitHub Free API."""

Expand All @@ -11,6 +29,33 @@
"""


def validate_conclusions(conclusions: list[str]) -> list[str]:
"""Validate the conclusions.
I.e., ensure they are valid GitHub Actions workflow job run conclusions.
"""
if not conclusions:
raise ValueError(
"No conclusions supplied - at least one is required (default: 'success')."
)

# Remove redundant entries and conform to lowercase
conclusions = list(set(conclusion.lower() for conclusion in conclusions))

for conclusion in conclusions:
invalid_conclusions: list[str] = []
if conclusion not in VALID_CONCLUSIONS:
invalid_conclusions.append(conclusion)

if invalid_conclusions:
return [
f"Invalid supplied conclusions: {invalid_conclusions}. "
f"Valid conclusions are: {VALID_CONCLUSIONS}"
]

return conclusions


def validate_rest_api_base_url(base_url: str) -> str:
"""Validate and parse the `gh_rest_api_base_url` input."""
split_base_url = urlsplit(base_url)
Expand Down

0 comments on commit f2fdd2a

Please sign in to comment.