Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .github/scripts/ug-review/review.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Post a label-triggered, LLM-generated Unity Gateway pull-request review.
"""Post a comment-triggered, LLM-generated Unity Gateway pull-request review.

The script runs from the trusted default branch under ``pull_request_target``.
The script runs from the trusted default branch, triggered by a ``/ug-review`` comment.
It retrieves PR-controlled metadata and patches as untrusted text through the
GitHub API; it never checks out or executes code from the PR head.
"""
Expand Down
24 changes: 21 additions & 3 deletions .github/scripts/user-journey-required/check.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Require integration coverage when a PR changes a ug user journey.

This script is run only from the trusted default branch by a
``pull_request_target`` workflow. PR-controlled patches are untrusted text: they
are retrieved through the GitHub API and sent to the judge, never executed.
The workflow checks this script out from the default branch and runs it automatically
for in-repo PRs, or for a fork PR when an org member comments ``/user-journey-check``.
PR-controlled patches are untrusted text: they are retrieved through the
GitHub API and sent to the judge, never executed.
"""

from __future__ import annotations
Expand Down Expand Up @@ -296,6 +297,22 @@ def _sync_waiver_label() -> int:
return _pass("waiver label already matches the authorized comment state.")


def _record_evaluated_head(pr_path: str, pr: dict[str, Any]) -> None:
"""Pin the verdict to the head the diff was read at, for the fork-PR status report."""
sha = str(pr.get("head", {}).get("sha", ""))
current = _gh_json(pr_path)
current_sha = str(current.get("head", {}).get("sha", "")) if isinstance(current, dict) else ""
if not re.fullmatch(r"[0-9a-f]{40}", sha) or current_sha != sha:
raise GateError(
f"The PR head changed while its diff was read ({sha[:7] or 'unknown'} -> "
f"{current_sha[:7] or 'unknown'}). Re-run this check."
)
output = os.environ.get("GITHUB_OUTPUT")
if output:
with open(output, "a") as handle:
handle.write(f"sha={sha}\n")


def _pass(message: str) -> int:
print(f"PASS: {message}")
return 0
Expand All @@ -315,6 +332,7 @@ def main() -> int:
files = _gh_json(f"{pr_path}/files", paginate=True)
if not isinstance(pr, dict) or not isinstance(files, list):
raise GateError("GitHub returned an unexpected pull-request response.")
_record_evaluated_head(pr_path, pr)

if not any(_is_product_path(str(file.get("filename", ""))) for file in files):
return _pass("no ug product or packaging files changed; no journey test is required.")
Expand Down
127 changes: 127 additions & 0 deletions .github/workflows/fork-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Fork integration

# Fork PRs get no secrets on `pull_request`. After reviewing a PR, an org member
# comments `/integration-test` to run the Integration workflow with secrets on the PR
# head, or `/integration-test <sha>` to run only if the head is still the reviewed
# commit. Workflow definitions always come from the default branch.
on:
issue_comment:
types: [created]

permissions:
contents: read

jobs:
authorize:
name: Authorize fork integration
if: >-
${{
github.event.issue.pull_request &&
(github.event.comment.body == '/integration-test' ||
startsWith(github.event.comment.body, '/integration-test ')) &&
contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association)
}}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: write
statuses: write
outputs:
sha: ${{ steps.pr.outputs.sha }}
steps:
- name: Pin the reviewed commit
id: pr
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
COMMENT_BODY: ${{ github.event.comment.body }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
refuse() {
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body "Fork integration not started: $1"
echo "::error::$1"
exit 1
}
[[ "$COMMENT_BODY" =~ ^/integration-test([[:space:]]+([0-9a-f]{7,40}))?[[:space:]]*$ ]] \
|| refuse "use \`/integration-test\`, or \`/integration-test <sha>\` with the head commit you reviewed."
requested="${BASH_REMATCH[2]}"
pr=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")
sha=$(jq -r '.head.sha' <<<"$pr")
head_repo=$(jq -r '.head.repo.full_name // empty' <<<"$pr")
[[ "$sha" =~ ^[0-9a-f]{40}$ ]] || refuse "could not read the PR head commit."
[ -n "$head_repo" ] || refuse "the fork repository no longer exists."
# Naming the commit binds the run to what was reviewed, not to a later push.
[[ -z "$requested" || "$sha" == "$requested"* ]] \
|| refuse "the PR head is now ${sha:0:7}, not $requested. Review the new commits and comment again."
gh api -X POST "repos/$GITHUB_REPOSITORY/issues/comments/$COMMENT_ID/reactions" \
-f content=eyes >/dev/null
gh api -X POST "repos/$GITHUB_REPOSITORY/statuses/$sha" -f state=pending \
-f context="Fork integration" -f description="Running on ${sha:0:7}" \
-f target_url="$RUN_URL" >/dev/null
echo "sha=$sha" >> "$GITHUB_OUTPUT"

reject:
name: Reject non-member request
if: >-
${{
github.event.issue.pull_request &&
(github.event.comment.body == '/integration-test' ||
startsWith(github.event.comment.body, '/integration-test ')) &&
!contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association)
}}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: write
steps:
- name: Reply with the member-only notice
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMENTER: ${{ github.event.comment.user.login }}
run: |
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body \
"Sorry @$COMMENTER, only Databricks org members can run integration tests. A maintainer will run \`/integration-test\` after reviewing the changes."

integration:
name: Integration
needs: authorize
# Job-level so ordinary PR comments, which skip this job, never cancel a run.
concurrency:
group: fork-integration-${{ github.event.issue.number }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
uses: ./.github/workflows/integration.yml
with:
ref: ${{ needs.authorize.outputs.sha }}
secrets: inherit # zizmor: ignore[secrets-inherit] same-repo workflow that needs nearly every secret

report:
name: Report fork integration
needs: [authorize, integration]
if: ${{ always() && needs.authorize.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
statuses: write
pull-requests: write
steps:
- name: Post the result on the PR
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.issue.number }}
SHA: ${{ needs.authorize.outputs.sha }}
RESULT: ${{ needs.integration.result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
state=$([ "$RESULT" = success ] && echo success || echo failure)
gh api -X POST "repos/$GITHUB_REPOSITORY/statuses/$SHA" \
-f state="$state" -f context="Fork integration" \
-f description="Integration $RESULT on ${SHA:0:7}" -f target_url="$RUN_URL" >/dev/null
gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" \
--body "Fork integration $RESULT on ${SHA:0:7}: $RUN_URL"
34 changes: 27 additions & 7 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ name: Integration

on:
workflow_call:
inputs:
ref:
description: Commit to test; fork-integration.yml passes the PR head pinned by /integration-test
type: string
default: ''
workflow_dispatch:
inputs:
ug_version:
Expand Down Expand Up @@ -38,7 +43,7 @@ permissions:
contents: read

concurrency:
group: integration-${{ github.event.pull_request.number || github.ref }}-${{ github.event_name }}
group: integration-${{ github.event.pull_request.number || github.event.issue.number || github.ref }}-${{ github.event_name }}
cancel-in-progress: true

env:
Expand All @@ -61,14 +66,16 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- name: Test a fresh installed package without credentials
run: |
uv run --no-project --python 3.12 python scripts/run_integration.py \
Expand Down Expand Up @@ -103,14 +110,16 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- name: Authenticate to JFrog with GitHub OIDC
id: jfrog
uses: jfrog/setup-jfrog-cli@279b1f629f43dd5bc658d8361ac4802a7ef8d2d5 # v4.9.1
Expand Down Expand Up @@ -204,14 +213,16 @@ jobs:
steps: &live-steps
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
Expand Down Expand Up @@ -281,14 +292,16 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
Expand Down Expand Up @@ -424,14 +437,16 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
Expand Down Expand Up @@ -481,14 +496,16 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
fetch-depth: 0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
Expand Down Expand Up @@ -537,6 +554,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- name: Discover every E2E CUJ file
id: discover
Expand Down Expand Up @@ -574,13 +592,15 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22.19.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: 0.9.8
enable-cache: ${{ inputs.ref == '' }}
- uses: databricks/setup-cli@bdb89f81c11a5bd647fd55b585b7c396ec68a25a # v1.0.0
with:
version: 1.17.0
Expand Down
25 changes: 13 additions & 12 deletions .github/workflows/ug-review.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
name: UG Review

# The reviewer always runs trusted code from the default branch. PR-controlled
# metadata and patches are fetched as untrusted text and are never executed.
# An org member comments `/ug-review` on a PR. The reviewer always runs trusted code from
# the default branch. PR-controlled metadata and patches are fetched as untrusted text and
# are never executed.
on:
pull_request_target: # zizmor: ignore[dangerous-triggers]
types: [opened, synchronize, reopened, ready_for_review, labeled]
issue_comment:
types: [created]

permissions:
contents: read

concurrency:
group: ug-review-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
review:
name: Review with the UG rubric
if: >-
${{
!github.event.pull_request.draft &&
contains(github.event.pull_request.labels.*.name, 'ug-review') &&
(github.event.action != 'labeled' || github.event.label.name == 'ug-review')
github.event.issue.pull_request &&
github.event.comment.body == '/ug-review' &&
contains(fromJSON('["MEMBER", "OWNER"]'), github.event.comment.author_association)
}}
# Job-level so ordinary PR comments, which skip this job, never cancel a review.
concurrency:
group: ug-review-${{ github.event.issue.number }}
cancel-in-progress: true
permissions:
contents: read
issues: write
Expand All @@ -46,7 +47,7 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_NUMBER: ${{ github.event.issue.number }}
DATABRICKS_HOST: https://ai-oss-ecosystem-integration-testing.cloud.databricks.com
DATABRICKS_CLIENT_ID: 6476a5e7-7f32-4073-abc5-ee30bb7d0060
DATABRICKS_CLIENT_SECRET: ${{ secrets.UG_CLI_REVIEW_BOT_SP_CLIENT_SECRET }}
Expand Down
Loading
Loading