Skip to content

test pipeline v2 for js automation #3646

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 71 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
bf94daa
seed watch-lintdiff.yaml
danieljurek Apr 2, 2025
672c896
add watch-lintdiff.yaml
danieljurek Apr 2, 2025
66f2a83
Merge pull request #12 from danieljurek/djurek/watchdog-trb
danieljurek Apr 2, 2025
f532f21
checks: read
danieljurek Apr 2, 2025
60c3e55
All check_runs
danieljurek Apr 2, 2025
e2e35df
job permissions?
danieljurek Apr 2, 2025
f850114
hello world
danieljurek Apr 2, 2025
fb24b67
on: check_run
danieljurek Apr 2, 2025
78a893a
check_run, check_suite
danieljurek Apr 2, 2025
09414a3
write checks
danieljurek Apr 2, 2025
12c4ec6
check_suite
danieljurek Apr 2, 2025
57964cd
workflow_run
danieljurek Apr 3, 2025
7658bfd
workflow_run
danieljurek Apr 3, 2025
c6dc574
workflows
danieljurek Apr 3, 2025
8b768aa
is it a parsing problem?
danieljurek Apr 3, 2025
cd7e696
Remove brackets from name
danieljurek Apr 3, 2025
e15d77f
Add brackets back in
danieljurek Apr 3, 2025
1780c16
Escape attempt
danieljurek Apr 3, 2025
2b812cf
double slashes
danieljurek Apr 3, 2025
db492be
types: completed
danieljurek Apr 3, 2025
036762c
log workflow_run object
danieljurek Apr 3, 2025
f9aa86d
remove completed, more runs
danieljurek Apr 3, 2025
baf1949
context.payload
danieljurek Apr 3, 2025
0edb76b
More logging
danieljurek Apr 3, 2025
3c1d1e6
Formatting
danieljurek Apr 3, 2025
34e4f57
basic api calls to get more outputs
danieljurek Apr 3, 2025
75ec3d2
.rest
danieljurek Apr 3, 2025
0bd5f18
Merge branch 'main' into main
danieljurek Apr 3, 2025
fb6d09f
Merge pull request #3640 from danieljurek/main
danieljurek Apr 3, 2025
d9ed12b
Filter based on check run name
danieljurek Apr 3, 2025
69dd652
Merge pull request #3642 from test-repo-billy/djurek/filter-event-name
danieljurek Apr 3, 2025
0a7b02d
Output check_run.name
danieljurek Apr 3, 2025
0124dcb
Filter check_run.name
danieljurek Apr 3, 2025
af175ce
First attempt at JS
danieljurek Apr 3, 2025
82b20a7
check_suite
danieljurek Apr 3, 2025
067da1b
Types
danieljurek Apr 3, 2025
cf840bd
Parameter names
danieljurek Apr 3, 2025
01fdbd5
core.debug
danieljurek Apr 3, 2025
2d903f5
Use check_run, fix permission, reduce logging
danieljurek Apr 4, 2025
340bf6c
Refactoring and test coverage
danieljurek Apr 5, 2025
05faf7e
Output event info
danieljurek Apr 5, 2025
0d5705b
In the case of multiple matching workflow runs, use the latest.
danieljurek Apr 6, 2025
c5f44c8
Adjust flow to use fewer API calls
danieljurek Apr 7, 2025
d4b5296
Only run on "check_run" if name matches
danieljurek Apr 7, 2025
dcea1cc
Remove comment, already filtering on repo
danieljurek Apr 7, 2025
5ca2f71
Logging cleanup
danieljurek Apr 7, 2025
e6ee4e6
Formatting
danieljurek Apr 7, 2025
a98ef4b
Coverage and cleanup
danieljurek Apr 7, 2025
ff7cfc2
Workflow run URL
danieljurek Apr 7, 2025
e51599f
formatting
danieljurek Apr 7, 2025
2724b31
Refactor watch-lintdiff to use reusable workflow
danieljurek Apr 7, 2025
a11b5bf
Name, condition
danieljurek Apr 7, 2025
fb03a8b
Formatting inputs
danieljurek Apr 7, 2025
5fd733d
formatting
danieljurek Apr 7, 2025
0e37a9b
Remove if
danieljurek Apr 7, 2025
e9fe46d
set head_sha after checking the name
danieljurek Apr 7, 2025
2500bc2
Test after review feedback
danieljurek Apr 7, 2025
3a994cf
Escape
danieljurek Apr 7, 2025
3568072
Notices
danieljurek Apr 7, 2025
0cd37fe
DRY up calls to checks.listForRef and actions.listWorkflowRunsForRepo
danieljurek Apr 7, 2025
0a49c83
Revert up to current state
danieljurek Apr 7, 2025
28074fd
Attempt condition
danieljurek Apr 7, 2025
5a313bc
github.event_name == 'workflow_run'
danieljurek Apr 7, 2025
40eacb7
check_run
danieljurek Apr 7, 2025
4528e5b
Into template
danieljurek Apr 7, 2025
37c57ee
inputs
danieljurek Apr 7, 2025
992bcc9
inputs
danieljurek Apr 7, 2025
99a939d
Update to state of PR
danieljurek Apr 8, 2025
274e8e2
check_run
danieljurek Apr 8, 2025
2305565
Update specificationRepositoryConfiguration.json (only for test) (#3644)
msyyc Apr 9, 2025
e6343f4
test pipeline v2
kazrael2119 Apr 10, 2025
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
28 changes: 14 additions & 14 deletions .github/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 42 additions & 0 deletions .github/workflows/_reusable-verify-run-status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Prefix with "~" to sort last in Actions list
name: ~Templates - Verify Run Status

on:
workflow_call:
inputs:
check_run_name:
description: Name of the check run to verify
required: true
type: string
workflow_name:
description: Name of the workflow to verify
required: true
type: string

permissions:
checks: read
contents: read

jobs:
check-run-status:
if: |
(github.event_name == 'workflow_run') ||
(github.event_name == 'check_suite' && github.event.check_suite.app.name == 'openapi-pipeline-app') ||
(github.event_name == 'check_run' && github.event.check_run.name == inputs.check_run_name)
runs-on: ubuntu-24.04

steps:
- uses: actions/checkout@v4
with:
sparse-checkout: |
.github

- name: Verify matching status
uses: actions/github-script@v7
with:
script: |
const { verifyRunStatus } = await import('${{ github.workspace }}/.github/workflows/src/verify-run-status.js');
return await verifyRunStatus({ github, context, core });
env:
CHECK_RUN_NAME: ${{ inputs.check_run_name }}
WORKFLOW_NAME: ${{ inputs.workflow_name }}
25 changes: 25 additions & 0 deletions .github/workflows/src/checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { PER_PAGE_MAX } from "./github.js";

/**
* @param {import('github-script').AsyncFunctionArguments['github']} github
* @param {{
* owner: string;
* repo: string;
* ref: string;
* name?: string;
* status?: "queued" | "in_progress" | "completed";
* }} params
* @returns {Promise<import("@octokit/plugin-rest-endpoint-methods").RestEndpointMethodTypes["checks"]["listForRef"]["response"]["data"]["check_runs"]>}
*/
export async function listChecksForRef(github, { owner, repo, ref, name, status}) {
const options = {
owner,
repo,
ref,
...(name && { check_name: name }),
...(status && { status }),
per_page: PER_PAGE_MAX,
};

return await github.paginate(github.rest.checks.listForRef, options);
}
180 changes: 180 additions & 0 deletions .github/workflows/src/verify-run-status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { extractInputs } from "./context.js";
import { PER_PAGE_MAX } from "./github.js";

const SUPPORTED_EVENTS = ["workflow_run", "check_run", "check_suite"];

/**
* @typedef {import('@octokit/plugin-rest-endpoint-methods').RestEndpointMethodTypes} RestEndpointMethodTypes
* @typedef {RestEndpointMethodTypes["checks"]["listForRef"]["response"]["data"]["check_runs"]} CheckRuns
* @typedef {RestEndpointMethodTypes["actions"]["listWorkflowRunsForRepo"]["response"]["data"]["workflow_runs"]} WorkflowRuns
*/

/* v8 ignore start */
/**
* Given the name of a completed check run name and a completed workflow, verify
* that both have the same conclusion. If conclusions are different, fail the
* action.
* @param {import('github-script').AsyncFunctionArguments} AsyncFunctionArguments
*/
export async function verifyRunStatus({ github, context, core }) {
const checkRunName = process.env.CHECK_RUN_NAME;
if (!checkRunName) {
throw new Error("CHECK_RUN_NAME is not set");
}

const workflowName = process.env.WORKFLOW_NAME;
if (!workflowName) {
throw new Error("WORKFLOW_NAME is not set");
}

if (!SUPPORTED_EVENTS.some((e) => e === context.eventName)) {
throw new Error(
`Unsupported event: ${context.eventName}. Supported events: ${SUPPORTED_EVENTS.join(", ")}`,
);
}

if (context.eventName === "check_suite" && context.payload.check_suite.status !== "completed") {
core.setFailed(`Check suite ${context.payload.check_suite.app.name} is not completed. Cannot evaluate incomplete check suite.`);
return;
}

return await verifyRunStatusImpl({ github, context, core , checkRunName, workflowName});
}
/* v8 ignore stop */

/**
* @param {Object} params
* @param {import('github-script').AsyncFunctionArguments["github"]} params.github
* @param {import('github-script').AsyncFunctionArguments["context"]} params.context
* @param {import('github-script').AsyncFunctionArguments["core"]} params.core
* @param {string} params.checkRunName
* @param {string} params.workflowName
*/
export async function verifyRunStatusImpl({github, context, core, checkRunName, workflowName}) {
if (context.eventName == "check_run") {
const contextRunName = context.payload.check_run.name;
if (contextRunName !== checkRunName) {
core.setFailed(`Check run name (${contextRunName}) does not match input: ${checkRunName}. Ensure job is filtering by github.event.check_run.name.`);
return;
}
}

const { head_sha } = await extractInputs(github, context, core);

let checkRun;
if (context.eventName == "check_run") {
checkRun = context.payload.check_run;
} else {
const checkRuns = await getCheckRuns(github, context, checkRunName, head_sha);
if (checkRuns.length === 0) {
if (context.eventName === "check_suite") {
const message = `Could not locate check run ${checkRunName} in check suite ${context.payload.check_suite.app.name}. Ensure job is filtering by github.event.check_suite.app.name.`;
core.setFailed(message);
return;
}

core.notice(`No completed check run with name: ${checkRunName}. Not enough information to judge success or failure. Ending with success status.`);
return;
}

// Use the most recent check run
checkRun = checkRuns[0];
}

core.info(
`Check run name: ${checkRun.name}, conclusion: ${checkRun.conclusion}, URL: ${checkRun.html_url}`,
);
core.debug(`Check run: ${JSON.stringify(checkRun)}`);

let workflowRun;
if (context.eventName == "workflow_run") {
workflowRun = context.payload.workflow_run;
} else {
const workflowRuns = await getWorkflowRuns(github, context, workflowName, head_sha);
if (workflowRuns.length === 0) {
core.notice(`No completed workflow run with name: ${workflowName}. Not enough information to judge success or failure. Ending with success status.`);
return;
}

// Use the most recent workflow run
workflowRun = workflowRuns[0];
}

core.info(
`Workflow run name: ${workflowRun.name}, conclusion: ${workflowRun.conclusion}, URL: ${workflowRun.html_url}`,
);
core.debug(`Workflow run: ${JSON.stringify(workflowRun)}`);

if (checkRun.conclusion !== workflowRun.conclusion) {
core.setFailed(
`Check run conclusion (${checkRun.conclusion}) does not match workflow run conclusion (${workflowRun.conclusion})`,
);
return;
}

core.notice(`Conclusions match for check run ${checkRunName} and workflow run ${workflowName}`);
}

/**
* Returns the check with the given checkRunName for the given ref.
* @param {import('github-script').AsyncFunctionArguments['github']} github
* @param {import('github-script').AsyncFunctionArguments['context']} context
* @param {string} checkRunName
* @param {string} ref
* @returns {Promise<CheckRuns>}
*/
export async function getCheckRuns(
github,
context,
checkRunName,
ref,
) {
const result = await github.paginate(github.rest.checks.listForRef, {
...context.repo,
ref: ref,
check_name: checkRunName,
status: "completed",
per_page: PER_PAGE_MAX,
});

// a and b will never be null because status is "completed"
/* v8 ignore next */
return result.sort((a, b) => compareDatesDescending(a.completed_at || '', b.completed_at || ''));
}

/**
* Returns the workflow run with the given workflowName for the given ref.
* @param {import('github-script').AsyncFunctionArguments['github']} github
* @param {import('github-script').AsyncFunctionArguments['context']} context
* @param {string} workflowName
* @param {string} ref
* @returns {Promise<WorkflowRuns>}
*/
export async function getWorkflowRuns(
github,
context,
workflowName,
ref,
) {
const result = await github.paginate(
github.rest.actions.listWorkflowRunsForRepo,
{
...context.repo,
head_sha: ref,
status: "completed",
per_page: PER_PAGE_MAX,
},
);

return result.filter((run) => run.name === workflowName).sort((a, b) => compareDatesDescending(a.updated_at, b.updated_at));
}

/**
* Compares two date strings in descending order.
* @param {string} a date string of the form "YYYY-MM-DDTHH:mm:ssZ"
* @param {string} b date string of the form "YYYY-MM-DDTHH:mm:ssZ"
* @returns
*/
export function compareDatesDescending(a, b) {
return new Date(b).getTime() - new Date(a).getTime();
}
23 changes: 23 additions & 0 deletions .github/workflows/src/workflows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { PER_PAGE_MAX } from "./github.js";

/**
* @param {import('github-script').AsyncFunctionArguments['github']} github
* @param {{
* owner: string;
* repo: string;
* head_sha?: string;
* status?: "completed" | "in_progress" | "queued";
* event?: string;
* }} params
* @returns {Promise<import("@octokit/plugin-rest-endpoint-methods").RestEndpointMethodTypes["actions"]["listWorkflowRunsForRepo"]["response"]["data"]["workflow_runs"]>}
*/
export async function listWorkflowRunsForRepo(github, { owner, repo, head_sha, status, event}) {
return await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
owner,
repo,
...(head_sha && { head_sha }),
...(status && { status }),
...(event && { event }),
per_page: PER_PAGE_MAX,
});
}
2 changes: 2 additions & 0 deletions .github/workflows/test/mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ export function createMockCore() {
return {
debug: vi.fn(console.debug),
info: vi.fn(console.log),
notice: vi.fn(console.log),
error: vi.fn(console.error),
warning: vi.fn(console.warn),
isDebug: vi.fn().mockReturnValue(true),
setOutput: vi.fn((name, value) =>
console.log(`setOutput('${name}', '${value}')`),
),
setFailed: vi.fn((msg) => console.log(`setFailed('${msg}')`)),
};
}

Expand Down
Loading