Skip to content

Commit 6be56a4

Browse files
committed
Add support for using GitHub app with automatic token refresh
1 parent 3133c5d commit 6be56a4

File tree

8 files changed

+16371
-9831
lines changed

8 files changed

+16371
-9831
lines changed

action.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ inputs:
66
description: 'Name or ID of workflow to run'
77
required: true
88
token:
9-
description: 'GitHub token with repo write access, can NOT use secrets.GITHUB_TOKEN, see readme'
10-
required: true
9+
description: 'GitHub token with repo write access, can NOT use secrets.GITHUB_TOKEN, see README.md. If not provided, app_id and app_private_key must be provided. If token is set it takes precedence over app_id and app_private_key.'
10+
required: false
11+
app_id:
12+
description: 'GitHub App ID with access to Actions API.'
13+
required: false
14+
app_private_key:
15+
description: 'GitHub App Private Key of the app with access to Actions API.'
16+
required: false
1117
inputs:
1218
description: 'Inputs to pass to the workflow, must be a JSON string. All values must be strings (even if used as boolean or number)'
1319
required: false

dist/index.js

Lines changed: 16118 additions & 9813 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,9 @@
2424
"@vercel/ncc": "0.38.1",
2525
"eslint": "8.57.0",
2626
"typescript": "5.4.5"
27+
},
28+
"dependencies": {
29+
"@octokit/auth-app": "^7.1.4",
30+
"@octokit/rest": "^21.1.0"
2731
}
2832
}

src/main.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
// ----------------------------------------------------------------------------
77

88
import * as core from '@actions/core'
9+
import {createAppAuth} from '@octokit/auth-app'
10+
import {Octokit} from '@octokit/rest'
911
import { formatDuration, getArgs, isTimedOut, sleep } from './utils'
1012
import { WorkflowHandler, WorkflowRunConclusion, WorkflowRunResult, WorkflowRunStatus } from './workflow-handler'
1113
import { handleWorkflowLogsPerJob } from './workflow-logs-handler'
1214

1315

14-
1516
async function getFollowUrl(workflowHandler: WorkflowHandler, interval: number, timeout: number) {
1617
const start = Date.now()
1718
let url
@@ -60,22 +61,63 @@ function computeConclusion(start: number, waitForCompletionTimeout: number, resu
6061
if (conclusion === WorkflowRunConclusion.TIMED_OUT) throw new Error('Workflow run has failed due to timeout')
6162
}
6263

63-
async function handleLogs(args: any, workflowHandler: WorkflowHandler) {
64+
async function handleLogs(octokit: Octokit, args: any, workflowHandler: WorkflowHandler) {
6465
try {
6566
const workflowRunId = await workflowHandler.getWorkflowRunId()
66-
await handleWorkflowLogsPerJob(args, workflowRunId)
67+
await handleWorkflowLogsPerJob(octokit, args, workflowRunId)
6768
} catch(e: any) {
6869
core.error(`Failed to handle logs of triggered workflow. Cause: ${e}`)
6970
}
7071
}
7172

73+
async function setupOctokit(token: string, appId: string, appPrivateKey: string):Promise<Octokit> {
74+
if(token) {
75+
return new Octokit({
76+
auth: token
77+
})
78+
}
79+
80+
return setupGitHubAppOctokit(appId, appPrivateKey)
81+
}
82+
83+
async function setupGitHubAppOctokitClient(appId: string, privateKey: string, installationId?: any):Promise<Octokit> {
84+
const auth : any = {
85+
appId,
86+
privateKey,
87+
}
88+
89+
if(installationId) {
90+
auth.installationId = installationId
91+
}
92+
93+
return new Octokit({
94+
authStrategy: createAppAuth,
95+
auth,
96+
})
97+
}
98+
99+
async function setupGitHubAppOctokit(appId: string, privateKey: string):Promise<Octokit> {
100+
const octokit = await setupGitHubAppOctokitClient(appId, privateKey)
101+
102+
const installations = await octokit.apps.listInstallations()
103+
const installationId = installations.data[0].id
104+
105+
core.info(`Using installationId=${installationId}`)
106+
107+
// Recreate octokit with installationId which results in
108+
// auto refresh of the token.
109+
return setupGitHubAppOctokitClient(appId, privateKey, installationId)
110+
}
111+
72112
//
73113
// Main task function (async wrapper)
74114
//
75115
async function run(): Promise<void> {
76116
try {
77117
const args = getArgs()
78-
const workflowHandler = new WorkflowHandler(args.token, args.workflowRef, args.owner, args.repo, args.ref, args.runName)
118+
119+
const octokit = await setupOctokit(args.token, args.appId, args.appPrivateKey)
120+
const workflowHandler = new WorkflowHandler(octokit, args.workflowRef, args.owner, args.repo, args.ref, args.runName)
79121

80122
// Trigger workflow run
81123
await workflowHandler.triggerWorkflow(args.inputs)
@@ -94,7 +136,7 @@ async function run(): Promise<void> {
94136
core.info('Waiting for workflow completion')
95137
const { result, start } = await waitForCompletionOrTimeout(workflowHandler, args.checkStatusInterval, args.waitForCompletionTimeout)
96138

97-
await handleLogs(args, workflowHandler)
139+
await handleLogs(octokit, args, workflowHandler)
98140

99141
core.setOutput('workflow-id', result?.id)
100142
core.setOutput('workflow-url', result?.url)

src/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ function parse(inputsJson: string) {
3030
export function getArgs() {
3131
// Required inputs
3232
const token = core.getInput('token')
33+
const appId = core.getInput('app_id')
34+
const appPrivateKey = core.getInput('app_private_key')
35+
36+
if (!token && (!appId || !appPrivateKey)) {
37+
throw new Error('Either token or app_id and app_private_key must be provided')
38+
}
39+
3340
const workflowRef = core.getInput('workflow')
3441
// Optional inputs, with defaults
3542
const ref = core.getInput('ref') || github.context.ref
@@ -54,6 +61,8 @@ export function getArgs() {
5461

5562
return {
5663
token,
64+
appId,
65+
appPrivateKey,
5766
workflowRef,
5867
ref,
5968
owner,

src/workflow-handler.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
import * as core from '@actions/core'
3-
import * as github from '@actions/github'
43
import { debug } from './debug'
54

65
export enum WorkflowRunStatus {
@@ -44,19 +43,17 @@ export interface WorkflowRunResult {
4443

4544

4645
export class WorkflowHandler {
47-
private octokit: any
4846
private workflowId?: number | string
4947
private workflowRunId?: number
5048
private triggerDate = 0
5149

52-
constructor(token: string,
50+
constructor(
51+
private octokit: any,
5352
private workflowRef: string,
5453
private owner: string,
5554
private repo: string,
5655
private ref: string,
5756
private runName: string) {
58-
// Get octokit client for making API calls
59-
this.octokit = github.getOctokit(token)
6057
}
6158

6259
async triggerWorkflow(inputs: any) {
@@ -216,5 +213,4 @@ export class WorkflowHandler {
216213
})))
217214
}
218215

219-
}
220-
216+
}

src/workflow-logs-handler.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as core from '@actions/core'
2-
import * as github from '@actions/github'
2+
import {Octokit} from '@octokit/rest'
33
import { debug } from './debug'
44

55
interface JobInfo {
@@ -8,9 +8,8 @@ interface JobInfo {
88
}
99

1010

11-
export async function handleWorkflowLogsPerJob(args: any, workflowRunId: number): Promise<void> {
11+
export async function handleWorkflowLogsPerJob(octokit: Octokit, args: any, workflowRunId: number): Promise<void> {
1212
const mode = args.workflowLogMode
13-
const token = args.token
1413
const owner = args.owner
1514
const repo = args.repo
1615

@@ -19,7 +18,6 @@ export async function handleWorkflowLogsPerJob(args: any, workflowRunId: number)
1918
return
2019
}
2120

22-
const octokit = github.getOctokit(token)
2321
const runId = workflowRunId
2422
const response = await octokit.rest.actions.listJobsForWorkflowRun({
2523
owner: owner,

0 commit comments

Comments
 (0)