mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-10-07 16:18:55 +00:00
fix(ci): load the isolation checker from this workflow's commit
The second checkout used the caller's SHA and the caller's token, so a private clone of this repo could not resolve the script. The checker is now a composite action referenced with $/.
This commit is contained in:
parent
e7712e6d0f
commit
39a6a91c9c
5 changed files with 97 additions and 50 deletions
6
.github/actionlint.yaml
vendored
Normal file
6
.github/actionlint.yaml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# actionlint 1.7.12 rejects `$/`, which GitHub accepts as a self-repository
|
||||||
|
# action reference (runner 2.336.0+). Drop this ignore when a release parses it.
|
||||||
|
paths:
|
||||||
|
.github/workflows/ci-terraform.yaml:
|
||||||
|
ignore:
|
||||||
|
- 'specifying action "\$/.github/actions/app-terraform-isolation" in invalid format because ref is missing'
|
||||||
64
.github/actions/app-terraform-isolation/action.yml
vendored
Normal file
64
.github/actions/app-terraform-isolation/action.yml
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
name: App and Terraform isolation
|
||||||
|
description: Fail when a change set mixes Terraform with deployable application files.
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
app-paths:
|
||||||
|
description: Newline-separated deployable paths. A trailing slash is a prefix. Any other entry is an exact file.
|
||||||
|
required: true
|
||||||
|
terraform-dir:
|
||||||
|
description: Directory containing Terraform sources.
|
||||||
|
required: true
|
||||||
|
default: terraform
|
||||||
|
event-name:
|
||||||
|
description: github.event_name from the calling workflow.
|
||||||
|
required: true
|
||||||
|
pr-base-sha:
|
||||||
|
description: pull_request base SHA. Empty outside pull_request.
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
merge-group-base-sha:
|
||||||
|
description: merge_group base SHA. Empty outside merge_group.
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Classify changed paths
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
env:
|
||||||
|
APP_PATHS: ${{ inputs.app-paths }}
|
||||||
|
TERRAFORM_DIR: ${{ inputs.terraform-dir }}
|
||||||
|
EVENT_NAME: ${{ inputs.event-name }}
|
||||||
|
PR_BASE_SHA: ${{ inputs.pr-base-sha }}
|
||||||
|
MERGE_GROUP_BASE_SHA: ${{ inputs.merge-group-base-sha }}
|
||||||
|
CHECKER: ${{ github.action_path }}/check_app_terraform_isolation.py
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
classify() {
|
||||||
|
python3 "${CHECKER}"
|
||||||
|
}
|
||||||
|
case "${EVENT_NAME}" in
|
||||||
|
pull_request)
|
||||||
|
if [[ -z "${PR_BASE_SHA}" ]]; then
|
||||||
|
echo "FAIL: pull_request base SHA is empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
merge_base="$(git merge-base "${PR_BASE_SHA}" HEAD)"
|
||||||
|
git diff --name-only --diff-filter=ACMRD "${merge_base}" HEAD | classify
|
||||||
|
;;
|
||||||
|
merge_group)
|
||||||
|
if [[ -z "${MERGE_GROUP_BASE_SHA}" ]]; then
|
||||||
|
echo "FAIL: merge_group base SHA is empty" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
while IFS= read -r sha; do
|
||||||
|
[[ -z "${sha}" ]] && continue
|
||||||
|
git diff --name-only --diff-filter=ACMRD "${sha}^" "${sha}" | classify
|
||||||
|
done < <(git rev-list --reverse --first-parent "${MERGE_GROUP_BASE_SHA}..HEAD")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "SKIP: live isolation runs on pull_request and merge_group (event: ${EVENT_NAME})"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
55
.github/workflows/ci-terraform.yaml
vendored
55
.github/workflows/ci-terraform.yaml
vendored
|
|
@ -21,6 +21,9 @@ name: CI — Terraform
|
||||||
# merge_group classifies each first-parent commit against its parent, so a
|
# merge_group classifies each first-parent commit against its parent, so a
|
||||||
# Terraform-only PR stacked with an app-only PR still passes. The classified
|
# Terraform-only PR stacked with an app-only PR still passes. The classified
|
||||||
# diff includes deletions. Terraform paths are those under working-directory.
|
# diff includes deletions. Terraform paths are those under working-directory.
|
||||||
|
# The checker is a composite action in this repository. `$/` resolves that
|
||||||
|
# action at this workflow's commit, so callers do not clone this private
|
||||||
|
# repository with their GITHUB_TOKEN.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
@ -72,50 +75,12 @@ jobs:
|
||||||
- name: Terraform validate
|
- name: Terraform validate
|
||||||
run: terraform validate
|
run: terraform validate
|
||||||
|
|
||||||
- name: Checkout isolation checker
|
|
||||||
if: inputs.app-paths != ''
|
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
||||||
with:
|
|
||||||
repository: Sea-Haven-Industries/.github
|
|
||||||
ref: ${{ github.workflow_sha }}
|
|
||||||
path: .ci-org-github
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: App and Terraform isolation
|
- name: App and Terraform isolation
|
||||||
if: inputs.app-paths != ''
|
if: inputs.app-paths != ''
|
||||||
working-directory: ${{ github.workspace }}
|
uses: $/.github/actions/app-terraform-isolation
|
||||||
env:
|
with:
|
||||||
APP_PATHS: ${{ inputs.app-paths }}
|
app-paths: ${{ inputs.app-paths }}
|
||||||
TERRAFORM_DIR: ${{ inputs.working-directory }}
|
terraform-dir: ${{ inputs.working-directory }}
|
||||||
EVENT_NAME: ${{ github.event_name }}
|
event-name: ${{ github.event_name }}
|
||||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
pr-base-sha: ${{ github.event.pull_request.base.sha }}
|
||||||
MERGE_GROUP_BASE_SHA: ${{ github.event.merge_group.base_sha }}
|
merge-group-base-sha: ${{ github.event.merge_group.base_sha }}
|
||||||
CHECKER: ${{ github.workspace }}/.ci-org-github/scripts/check_app_terraform_isolation.py
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
classify() {
|
|
||||||
python3 "${CHECKER}"
|
|
||||||
}
|
|
||||||
case "${EVENT_NAME}" in
|
|
||||||
pull_request)
|
|
||||||
if [[ -z "${PR_BASE_SHA}" ]]; then
|
|
||||||
echo "FAIL: pull_request base SHA is empty" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
merge_base="$(git merge-base "${PR_BASE_SHA}" HEAD)"
|
|
||||||
git diff --name-only --diff-filter=ACMRD "${merge_base}" HEAD | classify
|
|
||||||
;;
|
|
||||||
merge_group)
|
|
||||||
if [[ -z "${MERGE_GROUP_BASE_SHA}" ]]; then
|
|
||||||
echo "FAIL: merge_group base SHA is empty" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
while IFS= read -r sha; do
|
|
||||||
[[ -z "${sha}" ]] && continue
|
|
||||||
git diff --name-only --diff-filter=ACMRD "${sha}^" "${sha}" | classify
|
|
||||||
done < <(git rev-list --reverse --first-parent "${MERGE_GROUP_BASE_SHA}..HEAD")
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "SKIP: live isolation runs on pull_request and merge_group (event: ${EVENT_NAME})"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,19 @@ from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from check_app_terraform_isolation import first_isolation_violation, isolation_violation
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
ACTION = ROOT / ".github" / "actions" / "app-terraform-isolation"
|
||||||
|
WORKFLOW = ROOT / ".github" / "workflows" / "ci-terraform.yaml"
|
||||||
|
sys.path.insert(0, str(ACTION))
|
||||||
|
|
||||||
WORKFLOW = (
|
from check_app_terraform_isolation import ( # noqa: E402
|
||||||
Path(__file__).resolve().parents[1] / ".github" / "workflows" / "ci-terraform.yaml"
|
first_isolation_violation,
|
||||||
|
isolation_violation,
|
||||||
)
|
)
|
||||||
|
|
||||||
APP_PATHS = "src/\npackage.json\npackage-lock.json\n"
|
APP_PATHS = "src/\npackage.json\npackage-lock.json\n"
|
||||||
|
|
@ -133,10 +138,16 @@ class WorkflowDiffTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_workflow_passes_working_directory(self) -> None:
|
def test_workflow_passes_working_directory(self) -> None:
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"TERRAFORM_DIR: ${{ inputs.working-directory }}",
|
"terraform-dir: ${{ inputs.working-directory }}",
|
||||||
WORKFLOW.read_text(),
|
WORKFLOW.read_text(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_checker_is_this_repos_action_at_the_workflow_commit(self) -> None:
|
||||||
|
text = WORKFLOW.read_text()
|
||||||
|
self.assertIn("uses: $/.github/actions/app-terraform-isolation", text)
|
||||||
|
self.assertNotIn("github.workflow_sha", text)
|
||||||
|
self.assertNotIn("repository: Sea-Haven-Industries/.github", text)
|
||||||
|
|
||||||
def test_deleted_app_file_is_classified(self) -> None:
|
def test_deleted_app_file_is_classified(self) -> None:
|
||||||
diff_filter = _workflow_diff_filters()[0]
|
diff_filter = _workflow_diff_filters()[0]
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
|
@ -173,7 +184,8 @@ class WorkflowDiffTests(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
def _workflow_diff_filters() -> list[str]:
|
def _workflow_diff_filters() -> list[str]:
|
||||||
return re.findall(r"--diff-filter=([A-Z]+)", WORKFLOW.read_text())
|
action = (ACTION / "action.yml").read_text()
|
||||||
|
return re.findall(r"--diff-filter=([A-Z]+)", action)
|
||||||
|
|
||||||
|
|
||||||
def _git(repo: Path, *args: str) -> str:
|
def _git(repo: Path, *args: str) -> str:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue