Skip to content
Open
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
14 changes: 12 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import * as core from '@actions/core'
import {context, getCurrentDeployment, getCurrentJob, getInput, PermissionError, run} from './lib/actions.js'
import {context, getCurrentDeployment, getInput, PermissionError, run} from './lib/actions.js'
import { getCurrentJob } from 'lib/jobs.js'
// see https://github.com/actions/toolkit for more GitHub actions libraries
import {fileURLToPath} from 'url'
import * as process from 'node:process'
import {sleep} from "./lib/common.js";
import * as github from "@actions/github";

if(!process.env.GITHUB_API_URL && process.env.GITHUB_SERVER_URL) {
process.env.GITHUB_API_URL = `${process.env.GITHUB_SERVER_URL.replace(/\/$/, '')}/api/v3`

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not an expert of js/ts, once the process.env is modified here, will it stay up-to-date for the entire process life?
if not, the EnhancedContext::isGithubEnterprise() method impl is wrong, else is ok

}

export const action = run(async () => {

const inputs = {
Expand All @@ -26,7 +31,12 @@ export const action = run(async () => {
core.setOutput('run_html_url', runHtmlUrl)
core.exportVariable('GITHUB_RUN_HTML_URL', runHtmlUrl)

await getCurrentJob(octokit).then((job) => {
await getCurrentJob(octokit, context).then((job) => {
if (job === undefined) {
core.warning('Current job not found for runner: ' + context.runnerName)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly raise an error? but should never happen

return null
}

if (core.isDebug()) {
core.debug(JSON.stringify(job));
}
Expand Down
64 changes: 1 addition & 63 deletions lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import process from 'node:process';
import {_throw} from './common.js';
import YAML from "yaml";
import fs from "node:fs";
import {EnhancedContext} from './common.js';

// cache of getCurrentJob result
let _currentJob: Awaited<ReturnType<typeof getCurrentJob>>
// cache of getCurrentJob result
let _currentDeployment: Awaited<ReturnType<typeof getCurrentDeployment>>

Expand Down Expand Up @@ -187,69 +186,8 @@ export class PermissionError extends Error {
}
}

// --- Enhanced GitHub Action Context --------------------------------------------------

class EnhancedContext extends Context {

get repository() {
return `${this.repo.owner}/${this.repo.repo}`;
}

get jobCheckRunId() {
return parseInt(process.env.JOB_CHECK_RUN_ID
// WORKAROUND until https://github.com/actions/runner/pull/4053 is merged and released
?? process.env.INPUT__JOB_CHECK_RUN_ID
?? _throw(new Error('Missing environment variable: JOB_CHECK_RUN_ID')));
}

get workflowRef() {
return process.env.GITHUB_WORKFLOW_REF
?? _throw(new Error('Missing environment variable: GITHUB_WORKFLOW_REF'));
}

get workflowSha() {
return process.env.GITHUB_WORKFLOW_SHA
?? _throw(new Error('Missing environment variable: GITHUB_WORKFLOW_SHA'));
}

get runHtmlUrl() {
return process.env.GITHUB_RUN_HTML_URL ?? `${this.serverUrl}/${this.repository}` + `/actions/runs/${this.runId}` +
(this.runAttempt ? `/attempts/${this.runAttempt}` : '');
}

get runnerName() {
return process.env.RUNNER_NAME
?? _throw(new Error('Missing environment variable: RUNNER_NAME'));
}

get runnerTempDir() {
return process.env.RUNNER_TEMP
?? _throw(new Error('Missing environment variable: RUNNER_TEMP'));
}
}

export const context = new EnhancedContext();

/**
* Get the current job from the workflow run
* @returns the current job
*/
export async function getCurrentJob(octokit: InstanceType<typeof GitHub>): Promise<typeof currentJob> {
if (_currentJob) return _currentJob

const currentJob = await octokit.rest.actions.getJobForWorkflowRun({
...context.repo,
job_id: context.jobCheckRunId,
}).catch((error) => {
if (error.status === 403) {
throwPermissionError({scope: 'actions', permission: 'read'}, error)
}
throw error
}).then((res) => res.data)

return _currentJob = currentJob
}

/**
* Get the current deployment from the workflow run
* @returns the current deployment or undefined
Expand Down
46 changes: 46 additions & 0 deletions lib/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {z} from "zod";
import YAML from "yaml";
import {Context} from '@actions/github/lib/context';

export type JsonLiteral = string | number | boolean | null
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
Expand Down Expand Up @@ -59,3 +60,48 @@ export function getFlatValues(values: unknown): unknown[] {
export function _throw(error: unknown): never {
throw error
}

// --- Enhanced GitHub Action Context --------------------------------------------------

export class EnhancedContext extends Context {

get repository() {
return `${this.repo.owner}/${this.repo.repo}`;
}

get isGithubEnterprise() {
return process.env.GITHUB_API_URL != 'https://api.github.com'
}
Comment on lines +72 to +74

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only addition to the class


get jobCheckRunId() {
return parseInt(process.env.JOB_CHECK_RUN_ID
// WORKAROUND until https://github.com/actions/runner/pull/4053 is merged and released
?? process.env.INPUT__JOB_CHECK_RUN_ID
?? _throw(new Error('Missing environment variable: JOB_CHECK_RUN_ID')));
}

get workflowRef() {
return process.env.GITHUB_WORKFLOW_REF
?? _throw(new Error('Missing environment variable: GITHUB_WORKFLOW_REF'));
}

get workflowSha() {
return process.env.GITHUB_WORKFLOW_SHA
?? _throw(new Error('Missing environment variable: GITHUB_WORKFLOW_SHA'));
}

get runHtmlUrl() {
return process.env.GITHUB_RUN_HTML_URL ?? `${this.serverUrl}/${this.repository}` + `/actions/runs/${this.runId}` +
(this.runAttempt ? `/attempts/${this.runAttempt}` : '');
}

get runnerName() {
return process.env.RUNNER_NAME
?? _throw(new Error('Missing environment variable: RUNNER_NAME'));
}

get runnerTempDir() {
return process.env.RUNNER_TEMP
?? _throw(new Error('Missing environment variable: RUNNER_TEMP'));
}
}
71 changes: 71 additions & 0 deletions lib/jobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { GitHub } from "@actions/github/lib/utils";
import { context, throwPermissionError } from "./actions";
import { EnhancedContext } from "./common";
import * as core from '@actions/core'
import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-methods";

// type alias for job object returned by octokit.rest.actions.getJobForWorkflowRun
type Job = RestEndpointMethodTypes["actions"]["getJobForWorkflowRun"]["response"]["data"];

// cache of getCurrentJob result
let _currentJob: Job | undefined = undefined

/**
* Get the job for the current runner from the workflow run using Octokit REST API.
* Equivalent to:
* jobs=`curl .../actions/runs/${run_id}/jobs`
* job=$(echo $jobs | jq -r '.jobs[] | select(.runner_name=="${{ runner.name }}")')
* @param octokit - An authenticated Octokit instance
* @param context - The enhanced GitHub Actions context
* @returns The job for the current runner, or undefined if not found
*/
export async function getCurrentJobByRunner(
octokit: InstanceType<typeof GitHub>,
context: EnhancedContext
): Promise<Job | undefined> {
const runId = context.runId;
const runnerName = context.runnerName;
const { owner, repo } = context.repo;
const jobsResponse = await octokit.rest.actions.listJobsForWorkflowRun({
owner: owner,
repo: repo,
run_id: runId,
per_page: 100,
});
const job: Job | undefined = jobsResponse.data.jobs.find(j => j.runner_name === runnerName);
return job;
}

/**
* Get the job object for the current runner from the workflow run using Octokit REST API.
* @param octokit - An authenticated Octokit instance
* @param context - The enhanced GitHub Actions context
* @returns The job object for the current runner
*/
export async function getCurrentJob(octokit: InstanceType<typeof GitHub>, context: EnhancedContext
) : Promise<Job | undefined>{
// Return cached job if available
if (_currentJob != undefined) return _currentJob

// For GitHub Enterprise we need a workaround
if (context.isGithubEnterprise) {
core.warning(
'Running in GitHub Enterprise environment, we currently need to use a workaround until ' +
'this functionality is natively supported: https://github.blog/changelog/2025-11-13-github-actions-oidc-token-claims-now-include-check_run_id/'
)
const currentJob = await getCurrentJobByRunner(octokit, context)
return _currentJob = currentJob
}

// For GitHub.com we can directly get the job by using the check_run_id
const currentJob = await octokit.rest.actions.getJobForWorkflowRun({
...context.repo,
job_id: context.jobCheckRunId,
}).catch((error) => {
if (error.status === 403) {
throwPermissionError({scope: 'actions', permission: 'read'}, error)
}
throw error
}).then((res) => res.data)
return _currentJob = currentJob
}
2 changes: 1 addition & 1 deletion package-lock.json

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