From 3e10123fb014a76093ff2fb5440ea20f3ecf9885 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Thu, 16 Jan 2025 16:07:53 -0600 Subject: [PATCH] Initial implementation (#1) --- .editorconfig | 12 ++++++ .github/workflows/gha.yaml | 27 ++++++++++++ .github/workflows/integration-tests.yaml | 37 +++++++++++++++++ .github/workflows/shell.yaml | 36 ++++++++++++++++ .github/workflows/yaml.yaml | 18 ++++++++ .yamllint.yaml | 8 ++++ LICENSE | 21 ++++++++++ README.md | 53 +++++++++++++++++++++++- action.yaml | 38 +++++++++++++++++ get-workflow-run.sh | 38 +++++++++++++++++ 10 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/gha.yaml create mode 100644 .github/workflows/integration-tests.yaml create mode 100644 .github/workflows/shell.yaml create mode 100644 .github/workflows/yaml.yaml create mode 100644 .yamllint.yaml create mode 100644 LICENSE create mode 100644 action.yaml create mode 100755 get-workflow-run.sh diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..28491b7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# https://editorconfig.org/ + +# https://manpages.debian.org/testing/shfmt/shfmt.1.en.html#EXAMPLES +[*.sh] +indent_style = space +indent_size = 4 +shell_variant = bash # --language-variant +binary_next_line = false +switch_case_indent = true # --case-indent +space_redirects = false +keep_padding = false +function_next_line = false # --func-next-line diff --git a/.github/workflows/gha.yaml b/.github/workflows/gha.yaml new file mode 100644 index 0000000..86d7dd3 --- /dev/null +++ b/.github/workflows/gha.yaml @@ -0,0 +1,27 @@ +--- +name: GitHub Actions +on: + pull_request: + paths: + - ".github/workflows/*" + +jobs: + lint: + name: Lint + # These permissions are needed to: + # - Checkout the Git repo (`contents: read`) + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # https://github.com/rhysd/actionlint/blob/v1.7.6/docs/usage.md#use-actionlint-on-github-actions + # https://github.com/rhysd/actionlint/blob/v1.7.6/docs/usage.md#reviewdog + # https://github.com/reviewdog/reviewdog#filter-mode + # No support for non-workflows yet: https://github.com/rhysd/actionlint/issues/46 + - uses: reviewdog/action-actionlint@a1b7ce56be870acfe94b83ce5f6da076aecc6d8c # v1.62.0 + with: + fail_level: error + filter_mode: nofilter # Post results on all results and not just changed files + env: + SHELLCHECK_OPTS: -e SC2309 diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml new file mode 100644 index 0000000..0018e4d --- /dev/null +++ b/.github/workflows/integration-tests.yaml @@ -0,0 +1,37 @@ +--- +name: Integration Tests +on: + pull_request: + paths: + - "action.yaml" + - ".github/workflows/integration-tests.yaml" + +jobs: + test: + name: Test + # These permissions are needed to: + # - Checkout the repo + permissions: + actions: read + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Wait a little bit to ensure the GitHub API query can find the workflow. + - name: Delay + run: sleep 5 + - uses: ./ + id: workflow + with: + workflow-file: integration-tests.yaml + commit-sha: ${{ github.event.pull_request.head.sha || github.sha }} # Run is associated with HEAD commit + status: in_progress + - name: Show run details + run: | + echo "${{ toJSON(steps.workflow.outputs) }}" + - name: Test + run: | + set -x + [[ "${{ steps.workflow.outputs.workflow-id }}" -eq 137922876 ]] || exit 1 + [[ "${{ steps.workflow.outputs.run-id }}" -eq "${{ github.run_id }}" ]] || exit 1 + [[ "${{ steps.workflow.outputs.run-attempt }}" -eq "${{ github.run_attempt }}" ]] || exit 1 diff --git a/.github/workflows/shell.yaml b/.github/workflows/shell.yaml new file mode 100644 index 0000000..5694c6b --- /dev/null +++ b/.github/workflows/shell.yaml @@ -0,0 +1,36 @@ +--- +name: Shell +on: + pull_request: + paths: + - "**.sh" + - ".github/workflows/*" + +jobs: + lint-format: + name: Lint & Format + # These permissions are needed to: + # - Checkout the Git repo (`contents: read`) + # - Post a comments on PRs: https://github.com/luizm/action-sh-checker#secrets + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Extract workflow shell scripts + id: extract + uses: beacon-biosignals/gha-extract-shell-scripts@v1 + with: + shellcheck-disable: SC2309 + - uses: luizm/action-sh-checker@c6edb3de93e904488b413636d96c6a56e3ad671a # v0.8.0 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + sh_checker_comment: true + # Support investigating linting/formatting errors + - uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: workflow-scripts + path: ${{ steps.extract.outputs.output-dir }} diff --git a/.github/workflows/yaml.yaml b/.github/workflows/yaml.yaml new file mode 100644 index 0000000..91d390c --- /dev/null +++ b/.github/workflows/yaml.yaml @@ -0,0 +1,18 @@ +--- +# https://yamllint.readthedocs.io/en/stable/integration.html#integration-with-github-actions +name: YAML +on: + pull_request: + paths: + - "**/*.yaml" + - "**/*.yml" +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install yamllint + run: pip install yamllint + - name: Lint YAML files + run: yamllint . --format=github diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..da01502 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,8 @@ +--- +rules: + indentation: + spaces: 2 + indent-sequences: true + document-start: + present: true + new-line-at-end-of-file: enable diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..df26dd2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Beacon Biosignals + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index a12820a..872e226 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# get-workflow-run \ No newline at end of file +# Get Workflow Run + +Determine the latest workflow run for a given workflow file and commit SHA. + +## Example + +```yaml +--- +jobs: + test: + # These permissions are needed to: + # - Get the workflow run: https://github.com/beacon-biosignals/get-workflow-run#permissions + permissions: + actions: read + runs-on: ubuntu-latest + steps: + - uses: beacon-biosignals/get-workflow-run@v1 + id: workflow + with: + workflow-file: ci.yaml + commit: ${{ github.event.pull_request.head.sha || github.sha }} + - name: Show run details + run: | + echo "${{ toJSON(steps.workflow.outputs) }}" +``` + +## Inputs + +| Name | Description | Required | Example | +|:---------------------|:------------|:---------|:--------| +| `workflow-file` | The GitHub Actions workflow file name to target. | Yes | `ci.yaml` | +| `commit-sha` | The Git commit SHA used in the workflow run. | Yes | `23202e68ee664c345e985910770b7b9b873acfac` | +| `status` | Filter by the workflow run status. Possible status values can be [anything allowed by this API call](https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository) and `any`. Defaults to `any`. | No | `any`, `completed`, `skipped` | +| `repository` | The repository containing the workflow run. | No | `${{ github.token }}` | +| `token` | The GitHub token used to authenticate with the GitHub API. Need when attempting to access other repositories. | No | `${{ github.token }}` | + +## Outputs + +| Name | Description | Example | +|:--------------|:------------|:--------| +| `workflow-id` | The GitHub API ID of the targeted workflow. | `96660010` | +| `run-id` | The workflow run ID as used by the GitHub API. | `11958836222` | +| `run-attempt` | The workflow run attempt. | `1` | + +## Permissions + +The following [job permissions](https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs) are required to run this action: + +```yaml +permissions: + actions: read +``` diff --git a/action.yaml b/action.yaml new file mode 100644 index 0000000..c3045bc --- /dev/null +++ b/action.yaml @@ -0,0 +1,38 @@ +--- +name: Get Workflow Run +description: Determine the latest workflow run for a given workflow file and commit SHA. +branding: + color: yellow + icon: at-sign +inputs: + workflow-file: + required: true + commit-sha: + required: true + status: + required: true + default: "any" + repository: + default: ${{ github.repository }} + token: + default: ${{ github.token }} +outputs: + workflow-id: + value: ${{ steps.get-workflow-run.outputs.workflow-id }} + run-id: + value: ${{ steps.get-workflow-run.outputs.run-id }} + run-attempt: + value: ${{ steps.get-workflow-run.outputs.run-attempt }} +runs: + using: composite + steps: + - name: Get workflow run + id: get-workflow-run + shell: bash + run: ${{ github.action_path }}/get-workflow-run.sh + env: + GH_REPO: ${{ inputs.repository }} + GH_TOKEN: ${{ inputs.token }} + workflow_file: ${{ inputs.workflow-file }} + commit_sha: ${{ inputs.commit-sha }} + status: ${{ inputs.status }} diff --git a/get-workflow-run.sh b/get-workflow-run.sh new file mode 100755 index 0000000..2512842 --- /dev/null +++ b/get-workflow-run.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Determine the latest workflow run. +# +# Environment variable inputs: +# - `workflow_file`: The workflow file name used with the workflow run. +# - `commit_sha`: The Git commit SHA associated with the workflow run. +# - `status`: Return workflow runs with this status (e.g. "success", "completed"). Defaults +# to "any" for any status. + +set -eo pipefail + +status="${status:-any}" + +workflow_id="$(gh api -X GET --paginate "/repos/{owner}/{repo}/actions/workflows" --jq ".workflows[] | select(.path == \".github/workflows/${workflow_file:?}\").id")" +echo "workflow-id=${workflow_id:?}" | tee -a "$GITHUB_OUTPUT" + +flags=() +valid_statuses=(completed action_required cancelled failure neutral skipped stale success timed_out in_progress queued requested waiting pending) +if printf '%s\n' "${valid_statuses[@]}" | grep -qw "${status}"; then + flags+=(-f status="${status:?}") +elif [[ "${status}" != "any" ]]; then + echo "Invalid status \"$status\". Valid status input include: $(printf '"%s", ' "${valid_statuses[@]}")or \"any\"." >&2 + exit 1 +fi + +# Determine the latest workflow run associated with the `commit_sha` +# https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository +run="$(gh api -X GET --paginate "/repos/{owner}/{repo}/actions/runs" -f head_sha="${commit_sha:?}" "${flags[@]}" --jq ".workflow_runs | map(select(.workflow_id == ${workflow_id:?})) | sort_by(.run_number, .run_attempt) | .[-1]")" +if [[ -z "${run}" ]]; then + echo "Unable to locate any workflow runs for commit SHA ${commit_sha} and status \"${status}\"." >&2 + exit 1 +fi + +run_id="$(jq -er '.id' <<<"${run}")" +run_attempt="$(jq -er '.run_attempt' <<<"${run}")" +echo "run-id=${run_id:?}" | tee -a "$GITHUB_OUTPUT" +echo "run-attempt=${run_attempt:?}" | tee -a "$GITHUB_OUTPUT"