Skip to content
Merged
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
82 changes: 82 additions & 0 deletions .github/actions/utils/check-permissions/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Verify users rights
description: "Fails the workflow run if the triggering user lacks write permission or is not in the given organisation team."

inputs:
message:
description: "Message that will be shown as comment in case of access denial"
default: "⚠️ You don’t have permission to run this workflow. Please ask a maintainer to trigger it for you."
team:
description: "Name of the team that contains trusted contributors"
default: "contributors"

runs:
using: composite
steps:
- name: Check repository permission / team membership
uses: actions/github-script@v8
env:
TEAM: ${{ inputs.team }}
MESSAGE: ${{ inputs.message }}
with:
script: |
const {owner, repo} = context.repo;
// Get actor from event payload first, fallback to GITHUB_ACTOR
const actor = context.payload.actor ||
context.actor ||
process.env.GITHUB_ACTOR;
const team = process.env.TEAM
const denialMessage = process.env.MESSAGE

//------------------------------------------------------------------
// 1) Check collaborator permission (works for user *and* org repos)
//------------------------------------------------------------------
const perm = (await github.rest.repos.getCollaboratorPermissionLevel({
owner, repo, username: actor
})).data.permission; // admin | maintain | write | triage | read | none

if (['admin', 'maintain', 'write'].includes(perm)) {
core.info(`${actor} has ${perm} permission → authorised ✅`);
return;
}

//------------------------------------------------------------------
// 2) Repo is under an organisation? Then allow specific team members
//------------------------------------------------------------------
if (context.payload.repository.owner.type === 'Organization') {
try {
await github.rest.teams.getMembershipForUserInOrg({
org: owner,
team_slug: team,
username: actor
});
core.info(`${actor} is in org team “${team}” → authorised ✅`);
return;
} catch (_) {
core.info(`${actor} is not in team “${team}”.`);
}
}

let orgMember = true;
try {
await github.rest.orgs.getMembershipForUser({org: owner, username: actor});
core.info(`${actor} is in org” → authorised ✅`);
return;
} catch (_) {
core.info(`${actor} is not in org “${owner}”.`);
}

//------------------------------------------------------------------
// 3) Post PR comment if applicable, then fail
//------------------------------------------------------------------
if (context.payload.pull_request || context.payload.issue?.pull_request) {
const issueNumber = (context.payload.pull_request?.number)
?? (context.payload.issue.number);
await github.rest.issues.createComment({
owner, repo,
issue_number: issueNumber,
body: denialMessage
});
core.info(`Refusal comment posted to PR #${issueNumber}`);
}

core.setFailed(`${actor} is not authorised ❌`);
55 changes: 55 additions & 0 deletions .github/actions/utils/determine-ref/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: "Determine checkout ref"
description: "Determines the correct ref to checkout based on event context"

outputs:
ref:
description: "The ref to checkout"
value: ${{ steps.determine.outputs.ref }}
sha:
description: "The SHA to checkout"
value: ${{ steps.determine.outputs.sha }}

runs:
using: "composite"
steps:
- name: Determine ref and SHA
id: determine
uses: actions/github-script@v8
with:
script: |
const {owner, repo} = context.repo;
let ref, sha;

if (context.eventName === 'pull_request') {
// For PR events, use merge ref to test the merged state
const prNumber = context.payload.pull_request.number;
ref = `refs/pull/${prNumber}/merge`;
sha = context.payload.pull_request.head.sha;
core.info(`PR event detected: Using merge ref for PR #${prNumber}`);
core.info(`Head SHA: ${sha}`);
} else if (context.eventName === 'issue_comment' && context.payload.issue?.pull_request) {
// For PR comments, fetch the PR to get head SHA
const prNumber = context.payload.issue.number;
ref = `refs/pull/${prNumber}/merge`;

const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: prNumber
});
sha = pr.data.head.sha;

core.info(`PR comment detected: Using merge ref for PR #${prNumber}`);
core.info(`Head SHA: ${sha}`);
} else {
// For workflow_dispatch and other events, use the current branch
ref = context.ref;
sha = context.sha;
core.info(`Standard event: Using current ref ${ref}`);
core.info(`SHA: ${sha}`);
}

core.setOutput('ref', ref);
core.setOutput('sha', sha);
core.exportVariable('REF', ref);
core.exportVariable('SHA', sha);
38 changes: 38 additions & 0 deletions .github/actions/utils/should-run/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: "Should Run"
description: "Determines whether the workflow should proceed based on event context"

outputs:
shouldRun:
description: "Flag if tests were triggered via comment or UI which leads to running the following jobs"
value: ${{ steps.should_run.outputs.shouldRun }}

runs:
using: "composite"
steps:
- name: Should Run
id: should_run
uses: actions/github-script@v8
with:
script: |
const ev = context.eventName;
const body = context.payload?.comment?.body ?? '';
let shouldRun = false;

if (ev === 'workflow_dispatch') {
shouldRun = true;
} else if (ev === 'pull_request') {
// Auto-run smoke tests on PR workflow changes
shouldRun = true;
} else if (ev === 'issue_comment' && context.payload?.issue?.pull_request) {
// For issue comments on PRs, check if PR is closed first
const issueState = context.payload?.issue?.state;
if (issueState === 'closed') {
core.info(`PR #${context.payload.issue.number} is closed, skipping execution`);
shouldRun = false;
} else {
shouldRun = /^\s*\/gha\s+run\b/i.test(body || '');
}
}

core.setOutput('shouldRun', String(shouldRun));
core.info(`Parsed shouldRun: ${shouldRun}`);
24 changes: 24 additions & 0 deletions .github/tests/events/check-permissions/permissions_authorized.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"event_name": "pull_request",
"action": "opened",
"actor": "frawless",
"pull_request": {
"number": 123,
"title": "Test authorized user",
"head": {
"ref": "test-branch",
"sha": "abc123456789",
"repo": {
"name": "ci-playground",
"owner": { "login": "frawless" }
}
}
},
"repository": {
"name": "ci-playground",
"owner": {
"login": "frawless",
"type": "User"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"event_name": "pull_request",
"action": "opened",
"actor": "pepa",
"pull_request": {
"number": 66666666666,
"title": "Test unauthorized user",
"head": {
"ref": "test-branch",
"sha": "def987654321",
"repo": {
"name": "ci-playground",
"owner": { "login": "pepa" }
}
}
},
"repository": {
"name": "ci-playground",
"owner": {
"login": "frawless",
"type": "User"
}
}
}
13 changes: 13 additions & 0 deletions .github/tests/events/should-run/dispatch_default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"action": "workflow_dispatch",
"event_name": "workflow_dispatch",
"inputs": {},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
},
"ref": "refs/heads/main",
"head_commit": {
"id": "3333333333333333333333333333333333333333"
}
}
15 changes: 15 additions & 0 deletions .github/tests/events/should-run/issue_comment_closed_issue.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"event_name": "issue_comment",
"action": "created",
"comment": {
"body": "/gha run profile=operators,operands"
},
"issue": {
"number": 123,
"state": "closed"
},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
25 changes: 25 additions & 0 deletions .github/tests/events/should-run/issue_comment_closed_pr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"event_name": "issue_comment",
"action": "created",
"comment": {
"body": "/gha run pipeline=regression,upgrade"
},
"issue": {
"number": 42,
"state": "closed",
"pull_request": {
"head": {
"ref": "feature-123",
"sha": "1111111111111111111111111111111111111111",
"repo": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
}
},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
24 changes: 24 additions & 0 deletions .github/tests/events/should-run/issue_comment_no_trigger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"event_name": "issue_comment",
"action": "created",
"comment": {
"body": "This comment does not contain the trigger phrase"
},
"issue": {
"number": 42,
"pull_request": {
"head": {
"ref": "feature-123",
"sha": "1111111111111111111111111111111111111111",
"repo": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
}
},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
24 changes: 24 additions & 0 deletions .github/tests/events/should-run/issue_comment_trigger.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"event_name": "issue_comment",
"action": "created",
"comment": {
"body": "/gha run"
},
"issue": {
"number": 42,
"pull_request": {
"head": {
"ref": "feature-123",
"sha": "1111111111111111111111111111111111111111",
"repo": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
}
},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
20 changes: 20 additions & 0 deletions .github/tests/events/should-run/pull_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"event_name": "pull_request",
"action": "opened",
"pull_request": {
"number": 42,
"title": "Test PR",
"head": {
"ref": "feature-123",
"sha": "1111111111111111111111111111111111111111",
"repo": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
},
"repository": {
"name": "test-repo",
"owner": { "login": "batman" }
}
}
14 changes: 14 additions & 0 deletions .github/tests/scenarios/check-permissions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
scenarios:
- id: authorized-user
description: "Real authorized user (repository owner/collaborator)"
event: pull_request
fixture: .github/tests/events/check-permissions/permissions_authorized.json
expectations:
should_pass: "true"

- id: unauthorized-user
description: "Real unauthorized user (external contributor)"
event: pull_request
fixture: .github/tests/events/check-permissions/permissions_unauthorized.json
expectations:
should_pass: "false"
Loading
Loading