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
232 changes: 207 additions & 25 deletions .github/workflows/add-to-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,21 @@ jobs:
if: github.event_name == 'issues' || github.event_name == 'pull_request' || github.event_name == 'workflow_call'
name: Add issue/PR to a project
runs-on: ubuntu-latest
permissions: write-all
permissions:
contents: read
pull-requests: read
issues: read
steps:
- uses: actions/create-github-app-token@v3
id: generate-token
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_SECRET }}
permission-issues: write
permission-pull-requests: write
permission-members: read
permission-organization-projects: write
permission-administration: write
- uses: actions/add-to-project@v1.0.2
id: add-project
with:
Expand All @@ -58,27 +66,177 @@ jobs:
project-url: ${{ inputs.project-url != '' && inputs.project-url || secrets.PLATFORM_PROJECT_URL }}
github-token: ${{ steps.generate-token.outputs.token }}
labeled: ${{ inputs.labeled }}
- if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == true && steps.add-project.outputs.itemId }}
uses: titoportas/update-project-fields@v0.1.0
- if: ${{ github.event_name == 'pull_request' && steps.add-project.outputs.itemId }}
uses: actions/github-script@v7
with:
project-url: ${{ inputs.project-url != '' && inputs.project-url || secrets.PLATFORM_PROJECT_URL }}
github-token: ${{ steps.generate-token.outputs.token }}
item-id: ${{ steps.add-project.outputs.itemId }} # Use the item-id output of the previous step
field-keys: Status
field-values: 🏗 In progress
- if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false && steps.add-project.outputs.itemId }}
uses: titoportas/update-project-fields@v0.1.0
with:
project-url: ${{ inputs.project-url != '' && inputs.project-url || secrets.PLATFORM_PROJECT_URL }}
github-token: ${{ steps.generate-token.outputs.token }}
item-id: ${{ steps.add-project.outputs.itemId }} # Use the item-id output of the previous step
field-keys: Status
field-values: 🔖 Ready
script: |
const projectUrl = '${{ inputs.project-url != '' && inputs.project-url || secrets.PLATFORM_PROJECT_URL }}';
const itemId = '${{ steps.add-project.outputs.itemId }}';
const isDraft = ${{ github.event.pull_request.draft }};
const statusValue = isDraft ? '🏗 In progress' : '🔖 Ready';

// Parse project URL, supports both:
// - https://github.com/orgs/<org>/projects/<number>
// - https://github.com/users/<user>/projects/<number>
const projectMatch = projectUrl.match(/github\.com\/(?:(orgs|users)\/)?([^/]+)\/projects\/(\d+)/i);
if (!projectMatch) {
core.setFailed(`Could not parse project URL: ${projectUrl}`);
return;
}
const projectOwnerType = (projectMatch[1] || '').toLowerCase();
const projectOwnerLogin = projectMatch[2];
const projectNumber = Number(projectMatch[3]);
const isUserProject = projectOwnerType === 'users';

// Get project fields
const fieldsQuery = isUserProject ? `
query($number: Int!, $first: Int) {
user(login: "${projectOwnerLogin}") {
projectV2(number: $number) {
id
fields(first: $first) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
` : `
query($number: Int!, $first: Int) {
organization(login: "${projectOwnerLogin}") {
projectV2(number: $number) {
id
fields(first: $first) {
nodes {
... on ProjectV2Field {
id
name
}
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
`;

try {
const fieldsResult = await github.graphql(fieldsQuery, {
number: projectNumber,
first: 20
});

const projectNode = isUserProject
? fieldsResult.user?.projectV2
: fieldsResult.organization?.projectV2;

const statusField = projectNode?.fields?.nodes?.find(
f => f.name === 'Status'
);

if (!statusField) {
core.setFailed('Status field not found in project');
return;
}

const normalize = (value) =>
String(value || '')
.replace(/^\s*[^A-Za-z0-9]+\s*/, '')
.trim()
.toLowerCase();

const statusOptions = statusField.options || [];
const isSingleSelect = Array.isArray(statusField.options);
const statusOption =
statusOptions.find((opt) => opt.name === statusValue) ||
statusOptions.find((opt) => normalize(opt.name) === normalize(statusValue));

if (isSingleSelect && !statusOption) {
core.setFailed(
`Status option not found for "${statusValue}". Available options: ${statusOptions
.map((opt) => opt.name)
.join(', ')}`
);
return;
}

const projectId = projectNode?.id;
if (!projectId) {
core.setFailed('Could not resolve project id from project URL and owner');
return;
}

// Update field value using the correct value type
const valueFragment = statusOption
? `singleSelectOptionId: "${statusOption.id}"`
: `text: "${statusValue.replace(/"/g, '\\"')}"`;
const updateMutation = `
mutation {
updateProjectV2ItemFieldValue(input: {
projectId: "${projectId}"
itemId: "${itemId}"
fieldId: "${statusField.id}"
value: {
${valueFragment}
}
}) {
projectV2Item {
id
}
}
}
`;

await github.graphql(updateMutation);
console.log(`Updated Status to: ${statusValue}`);
} catch (error) {
core.setFailed(`Failed to update project field: ${error.message}`);
}
- name: Assign PR to creator
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false && steps.add-project.outputs.itemId }}
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft && steps.add-project.outputs.itemId }}
uses: actions/github-script@v7
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ steps.generate-token.outputs.token }}
script: |
// Check if PR already has assignees
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number
});

// Only assign PR creator if no assignees exist
if (!pr.data.assignees || pr.data.assignees.length === 0) {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
assignees: [context.payload.pull_request.user.login]
});
console.log('Assigned PR creator as assignee');
} else {
console.log('PR already has assignees, skipping assignment');
}
- name: Add permissions
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false && steps.add-project.outputs.itemId }}
run: |
Expand All @@ -98,11 +256,35 @@ jobs:
REPO: ${{ github.event.repository.name }}
TEAMS: ${{ inputs.reviewers-team != '' && inputs.reviewers-team || 'backend-devs,ops' }}
- name: Add reviewers
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false && steps.add-project.outputs.itemId }}
uses: rowi1de/auto-assign-review-teams@v1.1.3
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false && steps.add-project.outputs.itemId }}
uses: actions/github-script@v7
with:
repo-token: ${{ steps.generate-token.outputs.token }}
teams: ${{ inputs.reviewers-team != '' && inputs.reviewers-team || 'backend-devs,ops' }} # only works for GitHub Organisation/Teams
persons: ${{ inputs.reviewers-individuals }} # add individual persons here
include-draft: false # Draft PRs will be skipped (default: false)
skip-with-manual-reviewers: 1 # Skip this action, if the number of reviwers was already assigned (default: 0)
github-token: ${{ steps.generate-token.outputs.token }}
script: |
const teams = '${{ inputs.reviewers-team != '' && inputs.reviewers-team || 'backend-devs,ops' }}'
Comment thread
chicco785 marked this conversation as resolved.
.split(',')
.map(t => t.trim())
.filter(t => t);

const persons = '${{ inputs.reviewers-individuals }}'
.split(',')
.map(p => p.trim())
.filter(p => p);

if (teams.length === 0 && persons.length === 0) {
console.log('No reviewers configured');
return;
}

try {
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers: persons,
team_reviewers: teams
});
console.log(`Requested reviewers: teams=[${teams}], persons=[${persons}]`);
} catch (error) {
core.warning(`Failed to request reviewers: ${error.message}`);
}
17 changes: 13 additions & 4 deletions .github/workflows/check-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,18 @@ jobs:
pr-label-check:
if: ${{ github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- uses: docker://agilepathway/pull-request-label-checker:latest
- uses: actions/github-script@v7
with:
github_enterprise_graphql_url: https://api.github.com/graphql
one_of: ${{ inputs.labels !='' && inputs.labels || 'feature,bug,ci,refactor,security,documentation,dependencies,customer,skip-changelog' }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
script: |
const labels = context.payload.pull_request.labels.map(l => l.name);
const required = '${{ inputs.labels != '' && inputs.labels || 'feature,bug,ci,refactor,security,documentation,dependencies,customer,skip-changelog' }}'
.split(',')
.map(s => s.trim())
.filter(Boolean);
const hasLabel = required.some(label => labels.includes(label));
if (!hasLabel) {
core.setFailed(`PR must have one of: ${required.join(', ')}`);
}
17 changes: 5 additions & 12 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,11 @@ on:
type: string
default: ""
description: Use caching mechanism for a given language
submodules:
required: false
type: string
default: ""
description: True or Recursive to initialize git submodules
repositories:
required: false
type: string
default: ""
description: Comma or newline-separated list of repositories to grant access to during the docker build.
description: Comma or newline-separated list of additional repositories needed for Docker build (e.g., private dependencies)

jobs:
check:
Expand All @@ -92,15 +87,15 @@ jobs:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_SECRET }}
repositories: ${{ github.event.repository.name }}${{ inputs.repositories && format(',{0}', inputs.repositories) || '' }}
permission-contents: read
- name: Checkout
uses: actions/checkout@v6
with:
submodules: ${{ inputs.submodules }}
token: ${{ inputs.submodules != '' && steps.generate-token.outputs.token || github.token }}
token: ${{ steps.generate-token.outputs.token }}
- name: Run pre-build
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
BUILD_SECRET: ${{ secrets.BUILD_SECRET }}
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
run: |
if test -f "Makefile"; then
make ci-pre-build
Expand Down Expand Up @@ -175,8 +170,6 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
secrets: |
"github_token=${{ steps.generate-token.outputs.token }}"
build-args: |
GIT_REV=${{fromJson(steps.meta.outputs.json).labels['org.opencontainers.image.revision']}}
GIT_VERSION=${{fromJson(steps.meta.outputs.json).labels['org.opencontainers.image.version']}}
Expand All @@ -192,7 +185,7 @@ jobs:
run: |
echo "tag=$(echo '${{ needs.build.outputs.tags }}' | head -n1)" >> "$GITHUB_OUTPUT"
- name: Trivy vulnerability scanner
# trivy-action v0.35.0 (safe version)
# trivy-action v0.35.0 (safe version) - pinned to full commit SHA
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
with:
scan-type: image
Expand Down
5 changes: 4 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# GitHub Workflows Release Notes

## 0.0.3-dev - 2026-03-24
## 0.0.3-dev - 2026-03-27

### Features

Expand Down Expand Up @@ -80,13 +80,16 @@

### Security

- Workflow hardening: check-pr / docker / add-to-project (PR #269 by @chicco785)
- Fix trivy action to a secure version (PR #266 by @chicco785)
- add-to-project wf: add job to sync priority in projects with labels for Vanta
(PR #229 by @chicco785)
- Pass github token to docker build as secret (PR #163 by @chicco785)

### Dependencies

- Bump dawidd6/action-download-artifact from 18 to 19 (PR #265 by
@dependabot[bot])
- Bump actions/create-github-app-token from 2 to 3 (PR #261 by @dependabot[bot])
- Bump dorny/paths-filter from 3 to 4 (PR #259 by @dependabot[bot])
- Bump docker/login-action from 3 to 4 (PR #262 by @dependabot[bot])
Expand Down
Loading