Skip to content
Draft
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
104 changes: 95 additions & 9 deletions .github/workflows/require-nvskills-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ jobs:
HEAD_REF: ${{ github.event.pull_request.head.ref || '' }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.sha || github.sha }}
STATUS_CONTEXT: ${{ vars.NVSKILLS_STATUS_CONTEXT || 'NVSkills CI' }}
SIGNATURE_COMMIT_TITLE: ${{ vars.NVSKILLS_SIGNATURE_COMMIT_TITLE || 'Attach NVSkills validation signatures' }}
SIGNATURE_PUSH_ACTOR: ${{ vars.NVSKILLS_SIGNATURE_PUSH_ACTOR || 'nv-skills-ci[bot]' }}
SIGNATURE_SERVICE_ACCOUNT_LOGIN: ${{ vars.NVSKILLS_FORK_SERVICE_ACCOUNT_LOGIN || vars.NVSKILLS_SIGNATURE_COMMIT_LOGIN || 'svc-nvskills-signing' }}
STATUS_POLL_SECONDS: ${{ vars.NVSKILLS_STATUS_POLL_SECONDS || '30' }}
MISSING_STATUS_GRACE_SECONDS: ${{ vars.NVSKILLS_MISSING_STATUS_GRACE_SECONDS || '120' }}
MAX_STATUS_WAIT_SECONDS: ${{ vars.NVSKILLS_MAX_STATUS_WAIT_SECONDS || '3600' }}
MAX_STATUS_WAIT_SECONDS: ${{ vars.NVSKILLS_MAX_STATUS_WAIT_SECONDS || '3300' }}
run: |
set -euo pipefail

Expand All @@ -50,6 +53,25 @@ jobs:
esac
}

bounded_positive_integer() {
local value
local maximum="$3"

value="$(positive_integer "$1" "$2")"
while [ "${value#0}" != "${value}" ]; do
value="${value#0}"
done
if [ -z "${value}" ] || [ "${value}" = "0" ]; then
value="$2"
fi
if [ "${#value}" -gt "${#maximum}" ] ||
{ [ "${#value}" -eq "${#maximum}" ] && [[ "${value}" > "${maximum}" ]]; }; then
echo "::warning::Configured wait value ${value}s exceeds the safe maximum ${maximum}s; clamping it." >&2
value="${maximum}"
fi
printf '%s' "${value}"
}

case "${HEAD_REF}" in
automated/sync-skills|bot/regenerate-skill-metadata)
append_summary \
Expand Down Expand Up @@ -120,6 +142,47 @@ jobs:
'
}

is_generated_signature_artifact_commit() {
local commit_json="$1"
local commit_title

commit_title="$(printf '%s' "${commit_json}" | jq -r '.commit.message | split("\n")[0]')"
[ "${commit_title}" = "${SIGNATURE_COMMIT_TITLE}" ] || return 1

printf '%s' "${commit_json}" | jq -e '
def generated_artifact:
test(
"^(skills/[^/]+|team-skills/[^/]+/[^/]+|" +
"plugins/[^/]+|plugins/[^/]+/skills/[^/]+)/" +
"(BENCHMARK\\.md|skill-card\\.md|skill\\.oms\\.sig|" +
"skill-card-review-needed\\.md|[^/]+-review-needed\\.md)$"
);
any(.files[]?; .filename | endswith("/skill.oms.sig")) and
all(.files[]?;
((.previous_filename? // "") | length) == 0 and
(.filename | generated_artifact)
)
' >/dev/null
}

is_service_generated_signature_commit() {
local commit_json="$1"

is_generated_signature_artifact_commit "${commit_json}" || return 1
printf '%s' "${commit_json}" | jq -e \
--arg actor "${SIGNATURE_PUSH_ACTOR}" \
--arg service_login "${SIGNATURE_SERVICE_ACCOUNT_LOGIN}" '
((.author.login? // "") | ascii_downcase) == ($actor | ascii_downcase) or
((.committer.login? // "") | ascii_downcase) == ($actor | ascii_downcase) or
(
($service_login | length) > 0 and
(
((.author.login? // "") | ascii_downcase) == ($service_login | ascii_downcase) or
((.committer.login? // "") | ascii_downcase) == ($service_login | ascii_downcase)
)
)
' >/dev/null
}
if [ -z "${pr_number}" ] || [ -z "${head_sha}" ]; then
echo "Pull request metadata is incomplete."
exit 1
Expand Down Expand Up @@ -188,13 +251,20 @@ jobs:

latest_watched_sha=""
latest_watched_path=""
latest_generated_signature_sha=""
for ((idx=${#commit_shas[@]} - 1; idx >= 0; idx--)); do
commit_sha="${commit_shas[idx]}"
if [ "${commit_sha}" = "${head_sha}" ]; then
commit_json="${head_commit_json}"
else
commit_json="$(get_commit_json "${commit_sha}")"
fi
if is_service_generated_signature_commit "${commit_json}"; then
if [ -z "${latest_generated_signature_sha}" ]; then
latest_generated_signature_sha="${commit_sha}"
fi
continue
fi
watched_path="$(first_watched_path "${commit_json}")"
if [ -n "${watched_path}" ]; then
latest_watched_sha="${commit_sha}"
Expand All @@ -204,16 +274,28 @@ jobs:
done

if [ -z "${latest_watched_sha}" ]; then
if [ -n "${latest_generated_signature_sha}" ]; then
append_summary \
"## NVSkills CI required status" \
"" \
"Failed: generated signature commit \`${latest_generated_signature_sha}\` has no preceding watched-path content commit to validate."
exit 1
fi
append_summary \
"## NVSkills CI required status" \
"" \
"Skipped: PR #${pr_number} has no commits that changed watched NVSkills paths."
exit 0
fi

poll_seconds="$(positive_integer "${STATUS_POLL_SECONDS}" 30)"
missing_grace_seconds="$(positive_integer "${MISSING_STATUS_GRACE_SECONDS}" 120)"
max_wait_seconds="$(positive_integer "${MAX_STATUS_WAIT_SECONDS}" 3600)"
status_floor_sha="${latest_watched_sha}"
if [ -n "${latest_generated_signature_sha}" ]; then
status_floor_sha="${latest_generated_signature_sha}"
fi

poll_seconds="$(bounded_positive_integer "${STATUS_POLL_SECONDS}" 30 300)"
missing_grace_seconds="$(bounded_positive_integer "${MISSING_STATUS_GRACE_SECONDS}" 120 600)"
max_wait_seconds="$(bounded_positive_integer "${MAX_STATUS_WAIT_SECONDS}" 3300 3300)"
elapsed_seconds=0
status_sha=""
status_state="missing"
Expand All @@ -237,7 +319,7 @@ jobs:
'[.[] | select(.context == $context)][0].target_url // ""')"
break
fi
if [ "${commit_sha}" = "${latest_watched_sha}" ]; then
if [ "${commit_sha}" = "${status_floor_sha}" ]; then
break
fi
done
Expand All @@ -263,7 +345,7 @@ jobs:
;;
esac

echo "Waiting for ${STATUS_CONTEXT} at or after ${latest_watched_sha} (state: ${status_state}, elapsed: ${elapsed_seconds}s)."
echo "Waiting for ${STATUS_CONTEXT} at or after ${status_floor_sha} (state: ${status_state}, elapsed: ${elapsed_seconds}s)."
sleep "${poll_seconds}"
elapsed_seconds=$((elapsed_seconds + poll_seconds))
done
Expand All @@ -275,6 +357,11 @@ jobs:
"Passed: \`${STATUS_CONTEXT}\` succeeded for validation evidence commit \`${status_sha}\`." \
"" \
"Latest watched-path change: \`${latest_watched_path}\` at \`${latest_watched_sha}\`."
if [ -n "${latest_generated_signature_sha}" ]; then
append_summary \
"" \
"Latest generated signature commit: \`${latest_generated_signature_sha}\`."
fi
if [ "${status_sha}" != "${head_sha}" ]; then
append_summary \
"" \
Expand All @@ -286,15 +373,14 @@ jobs:
append_summary \
"## NVSkills CI required status" \
"" \
"Failed: no successful \`${STATUS_CONTEXT}\` status was found at or after latest watched commit \`${latest_watched_sha}\` (state: \`${status_state}\`)." \
"Failed: no successful \`${STATUS_CONTEXT}\` status was found at or after required evidence commit \`${status_floor_sha}\` (state: \`${status_state}\`)." \
"" \
"Latest watched-path change: \`${latest_watched_path}\`." \
"" \
"Comment \`/nvskills-ci\` on this PR after every commit that changes" \
"\`skills/\`, \`team-skills/\`, \`rules/team-rules/\`, or \`plugins/\`." \
"" \
"\`/nvskills-ci\` only works on branches in \`NVIDIA/skills\`, not forks." \
"If this PR is from a fork, move the changes to a branch in \`NVIDIA/skills\` first."
"For a fork pull request, add \`${SIGNATURE_SERVICE_ACCOUNT_LOGIN}\`, the configured NVSkills signing service account, as a collaborator with write access to the fork repository, then ask a maintainer of the base repository to comment \`/nvskills-ci\`."
if [ -n "${status_sha}" ]; then
append_summary "" "Latest NVSkills CI status checked: \`${status_state}\` on \`${status_sha}\`."
fi
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/team-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,31 @@ jobs:
group: nvskills-ci-request-${{ github.repository }}-${{ github.event.issue.number || github.sha }}
cancel-in-progress: true
steps:
- name: Check dispatch availability
id: dispatch
env:
DISPATCH_TOKEN: ${{ secrets.NVSKILLS_CI_DISPATCH_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
run: |
set -euo pipefail
if [ -n "${DISPATCH_TOKEN}" ]; then
echo "enabled=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
if [ "${EVENT_NAME}" = "push" ]; then
{
echo "## NVSkills CI request"
echo
echo "Skipped: this trusted signature push has no dispatch secret. The originating central run verifies the generated signature commit."
} >> "${GITHUB_STEP_SUMMARY}"
echo "enabled=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
echo "Missing NVSKILLS_CI_DISPATCH_TOKEN secret."
exit 1

- name: Validate requester permission
if: ${{ github.event_name == 'issue_comment' }}
if: ${{ steps.dispatch.outputs.enabled == 'true' && github.event_name == 'issue_comment' }}
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
Expand All @@ -53,6 +76,7 @@ jobs:

- name: Resolve request context
id: context
if: ${{ steps.dispatch.outputs.enabled == 'true' }}
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
Expand Down Expand Up @@ -153,7 +177,7 @@ jobs:
} >> "${GITHUB_OUTPUT}"

- name: Dispatch NVSkills CI
if: steps.context.outputs.should_dispatch == 'true'
if: steps.dispatch.outputs.enabled == 'true' && steps.context.outputs.should_dispatch == 'true'
env:
DISPATCH_TOKEN: ${{ secrets.NVSKILLS_CI_DISPATCH_TOKEN }}
REPO: ${{ github.repository }}
Expand Down
Loading