Skip to content

KFLUXINFRA-4167: Create a staging ArgoCD instance for infra-deployments #215

KFLUXINFRA-4167: Create a staging ArgoCD instance for infra-deployments

KFLUXINFRA-4167: Create a staging ArgoCD instance for infra-deployments #215

Workflow file for this run

---
name: Assign PR Assignees
on:
pull_request_target:
types: [opened, ready_for_review, synchronize, reopened]
permissions:
contents: read
pull-requests: write # required to list PR files and to assign on PRs via the Issues API
issues: write
jobs:
assign-assignees:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Assign internal-services assignees from CODEOWNERS
uses: actions/github-script@v8
with:
script: |
const fs = require("fs");
const RELEASE_SERVICE_BOT = "release-service-bot";
const pr = context.payload.pull_request;
if (!pr) {
core.info("No pull request in event payload. Skipping.");
return;
}
if (context.payload.action === "opened" && pr.draft) {
core.info("Draft PR opened. Skipping assignment until ready_for_review.");
return;
}
const changedFiles = await github.paginate(
github.rest.pulls.listFiles,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
}
);
const isInternalServicesPR = changedFiles.some((file) =>
file.filename.startsWith("components/internal-services/")
);
if (!isInternalServicesPR) {
core.info("PR does not touch internal-services component. Skipping assignment.");
return;
}
const codeownersPath = ".github/CODEOWNERS";
const codeowners = fs.readFileSync(codeownersPath, "utf8");
const internalServicesLine = codeowners
.split("\n")
.map((line) => line.trim())
.find((line) => line.startsWith("/components/internal-services/ "));
if (!internalServicesLine) {
core.setFailed("Could not find /components/internal-services/ entry in .github/CODEOWNERS.");
return;
}
const owners = internalServicesLine
.split(/\s+/)
.slice(1)
.map((owner) => owner.replace(/^@/, "").trim())
.filter(Boolean);
const excluded = new Set([
RELEASE_SERVICE_BOT,
pr.user.login,
]);
const existingAssignees = (pr.assignees || []).map((assignee) => assignee.login);
for (const assignee of existingAssignees) {
excluded.add(assignee);
}
const candidates = owners.filter((owner) => !excluded.has(owner));
const neededAssignees = Math.max(0, 2 - existingAssignees.length);
if (neededAssignees === 0) {
core.info("PR already has 2 or more assignees.");
return;
}
if (candidates.length === 0) {
core.info("No eligible internal-services CODEOWNERS assignees available.");
return;
}
const collaboratorChecks = await Promise.all(
candidates.map(async (candidate) => {
try {
await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: candidate,
});
return candidate;
} catch (error) {
if (error.status === 404) {
core.info(`Skipping non-collaborator assignee candidate: ${candidate}`);
return null;
}
throw error;
}
})
);
const collaboratorCandidates = collaboratorChecks.filter(Boolean);
if (collaboratorCandidates.length === 0) {
core.info("No collaborator candidates available for assignment.");
return;
}
const shuffled = [...collaboratorCandidates].sort(() => Math.random() - 0.5);
const selected = shuffled.slice(0, neededAssignees);
if (selected.length === 0) {
core.info("No assignees selected.");
return;
}
try {
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
assignees: selected,
});
} catch (error) {
if (error.status === 422) {
core.warning(`Unable to assign one or more assignees: ${error.message}`);
return;
}
throw error;
}
core.info(`Assigned assignees: ${selected.join(", ")}`);