Skip to content
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

Add support for using GitHub app with automatic token refresh #24

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
10 changes: 8 additions & 2 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ inputs:
description: 'Name or ID of workflow to run'
required: true
token:
description: 'GitHub token with repo write access, can NOT use secrets.GITHUB_TOKEN, see readme'
required: true
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.'
required: false
app_id:
description: 'GitHub App ID with access to Actions API.'
required: false
app_private_key:
description: 'GitHub App Private Key of the app with access to Actions API.'
required: false
inputs:
description: 'Inputs to pass to the workflow, must be a JSON string. All values must be strings (even if used as boolean or number)'
required: false
Expand Down
25,931 changes: 16,118 additions & 9,813 deletions dist/index.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@
"@vercel/ncc": "0.38.1",
"eslint": "8.57.0",
"typescript": "5.4.5"
},
"dependencies": {
"@octokit/auth-app": "^7.1.4",
"@octokit/rest": "^21.1.0"
}
}
52 changes: 47 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
// ----------------------------------------------------------------------------

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



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

async function handleLogs(args: any, workflowHandler: WorkflowHandler) {
async function handleLogs(octokit: Octokit, args: any, workflowHandler: WorkflowHandler) {
try {
const workflowRunId = await workflowHandler.getWorkflowRunId()
await handleWorkflowLogsPerJob(args, workflowRunId)
await handleWorkflowLogsPerJob(octokit, args, workflowRunId)
} catch(e: any) {
core.error(`Failed to handle logs of triggered workflow. Cause: ${e}`)
}
}

async function setupOctokit(token: string, appId: string, appPrivateKey: string):Promise<Octokit> {
if(token) {
return new Octokit({
auth: token
})
}

return setupGitHubAppOctokit(appId, appPrivateKey)
}

async function setupGitHubAppOctokitClient(appId: string, privateKey: string, installationId?: any):Promise<Octokit> {
const auth : any = {
appId,
privateKey,
}

if(installationId) {
auth.installationId = installationId
}

return new Octokit({
authStrategy: createAppAuth,
auth,
})
}

async function setupGitHubAppOctokit(appId: string, privateKey: string):Promise<Octokit> {
const octokit = await setupGitHubAppOctokitClient(appId, privateKey)

const installations = await octokit.apps.listInstallations()
const installationId = installations.data[0].id

core.info(`Using installationId=${installationId}`)

// Recreate octokit with installationId which results in
// auto refresh of the token.
return setupGitHubAppOctokitClient(appId, privateKey, installationId)
}

//
// Main task function (async wrapper)
//
async function run(): Promise<void> {
try {
const args = getArgs()
const workflowHandler = new WorkflowHandler(args.token, args.workflowRef, args.owner, args.repo, args.ref, args.runName)

const octokit = await setupOctokit(args.token, args.appId, args.appPrivateKey)
const workflowHandler = new WorkflowHandler(octokit, args.workflowRef, args.owner, args.repo, args.ref, args.runName)

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

await handleLogs(args, workflowHandler)
await handleLogs(octokit, args, workflowHandler)

core.setOutput('workflow-id', result?.id)
core.setOutput('workflow-url', result?.url)
Expand Down
9 changes: 9 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ function parse(inputsJson: string) {
export function getArgs() {
// Required inputs
const token = core.getInput('token')
const appId = core.getInput('app_id')
const appPrivateKey = core.getInput('app_private_key')

if (!token && (!appId || !appPrivateKey)) {
throw new Error('Either token or app_id and app_private_key must be provided')
}

const workflowRef = core.getInput('workflow')
// Optional inputs, with defaults
const ref = core.getInput('ref') || github.context.ref
Expand All @@ -54,6 +61,8 @@ export function getArgs() {

return {
token,
appId,
appPrivateKey,
workflowRef,
ref,
owner,
Expand Down
10 changes: 3 additions & 7 deletions src/workflow-handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@

import * as core from '@actions/core'
import * as github from '@actions/github'
import { debug } from './debug'

export enum WorkflowRunStatus {
Expand Down Expand Up @@ -44,19 +43,17 @@ export interface WorkflowRunResult {


export class WorkflowHandler {
private octokit: any
private workflowId?: number | string
private workflowRunId?: number
private triggerDate = 0

constructor(token: string,
constructor(
private octokit: any,
private workflowRef: string,
private owner: string,
private repo: string,
private ref: string,
private runName: string) {
// Get octokit client for making API calls
this.octokit = github.getOctokit(token)
}

async triggerWorkflow(inputs: any) {
Expand Down Expand Up @@ -216,5 +213,4 @@ export class WorkflowHandler {
})))
}

}

}
6 changes: 2 additions & 4 deletions src/workflow-logs-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import {Octokit} from '@octokit/rest'
import { debug } from './debug'

interface JobInfo {
Expand All @@ -8,9 +8,8 @@ interface JobInfo {
}


export async function handleWorkflowLogsPerJob(args: any, workflowRunId: number): Promise<void> {
export async function handleWorkflowLogsPerJob(octokit: Octokit, args: any, workflowRunId: number): Promise<void> {
const mode = args.workflowLogMode
const token = args.token
const owner = args.owner
const repo = args.repo

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

const octokit = github.getOctokit(token)
const runId = workflowRunId
const response = await octokit.rest.actions.listJobsForWorkflowRun({
owner: owner,
Expand Down
Loading