2023-06-05 15:14:31 -06:00
|
|
|
import * as core from '@actions/core'
|
|
|
|
import * as artifact from '@actions/artifact'
|
|
|
|
import * as github from '@actions/github'
|
|
|
|
import * as glob from '@actions/glob'
|
|
|
|
import * as toolCache from '@actions/tool-cache'
|
|
|
|
import {Octokit} from '@octokit/rest'
|
|
|
|
|
|
|
|
import * as path from 'path'
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
|
|
import * as execution from './execution'
|
|
|
|
import * as layout from './repository-layout'
|
2023-07-05 12:33:47 -06:00
|
|
|
import {DependencyGraphOption, getJobMatrix} from './input-params'
|
2023-06-05 15:14:31 -06:00
|
|
|
|
|
|
|
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph'
|
2023-07-01 19:00:28 -06:00
|
|
|
|
2023-07-05 12:33:47 -06:00
|
|
|
export function setup(option: DependencyGraphOption): void {
|
|
|
|
if (option === DependencyGraphOption.Disabled || option === DependencyGraphOption.DownloadAndSubmit) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
core.info('Enabling dependency graph generation')
|
2023-07-01 19:00:28 -06:00
|
|
|
const jobCorrelator = getJobCorrelator()
|
|
|
|
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_ENABLED', 'true')
|
|
|
|
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_CORRELATOR', jobCorrelator)
|
|
|
|
core.exportVariable('GITHUB_DEPENDENCY_GRAPH_JOB_ID', github.context.runId)
|
|
|
|
core.exportVariable(
|
|
|
|
'GITHUB_DEPENDENCY_GRAPH_REPORT_DIR',
|
|
|
|
path.resolve(layout.workspaceDirectory(), 'dependency-graph-reports')
|
|
|
|
)
|
|
|
|
}
|
2023-06-05 15:14:31 -06:00
|
|
|
|
2023-07-05 12:33:47 -06:00
|
|
|
export async function complete(option: DependencyGraphOption): Promise<void> {
|
|
|
|
switch (option) {
|
|
|
|
case DependencyGraphOption.Disabled:
|
|
|
|
return
|
|
|
|
case DependencyGraphOption.Generate:
|
|
|
|
await uploadDependencyGraphs()
|
|
|
|
return
|
|
|
|
case DependencyGraphOption.GenerateAndSubmit:
|
|
|
|
await submitDependencyGraphs(await uploadDependencyGraphs())
|
|
|
|
return
|
|
|
|
case DependencyGraphOption.DownloadAndSubmit:
|
|
|
|
await downloadAndSubmitDependencyGraphs()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-05 15:14:31 -06:00
|
|
|
export async function generateDependencyGraph(executable: string | undefined): Promise<void> {
|
|
|
|
const buildRootDirectory = layout.buildRootDirectory()
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
const args = [':GitHubDependencyGraphPlugin_generateDependencyGraph']
|
2023-06-05 15:14:31 -06:00
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
await execution.executeGradleBuild(executable, buildRootDirectory, args)
|
2023-06-05 15:14:31 -06:00
|
|
|
}
|
|
|
|
|
2023-07-05 12:33:47 -06:00
|
|
|
export async function uploadDependencyGraphs(): Promise<string[]> {
|
2023-07-01 19:00:28 -06:00
|
|
|
const workspaceDirectory = layout.workspaceDirectory()
|
|
|
|
const graphFiles = await findDependencyGraphFiles(workspaceDirectory)
|
|
|
|
|
|
|
|
const relativeGraphFiles = graphFiles.map(x => getRelativePathFromWorkspace(x))
|
|
|
|
core.info(`Uploading dependency graph files: ${relativeGraphFiles}`)
|
2023-06-05 15:14:31 -06:00
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
const artifactClient = artifact.create()
|
|
|
|
artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, graphFiles, workspaceDirectory)
|
2023-07-05 12:33:47 -06:00
|
|
|
|
|
|
|
return graphFiles
|
2023-06-05 15:14:31 -06:00
|
|
|
}
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
export async function downloadAndSubmitDependencyGraphs(): Promise<void> {
|
2023-06-05 15:14:31 -06:00
|
|
|
const workspaceDirectory = layout.workspaceDirectory()
|
2023-07-01 19:00:28 -06:00
|
|
|
submitDependencyGraphs(await retrieveDependencyGraphs(workspaceDirectory))
|
|
|
|
}
|
|
|
|
|
|
|
|
async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
|
2023-06-05 15:14:31 -06:00
|
|
|
const octokit: Octokit = getOctokit()
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
for (const jsonFile of dependencyGraphFiles) {
|
2023-06-05 15:14:31 -06:00
|
|
|
const jsonContent = fs.readFileSync(jsonFile, 'utf8')
|
|
|
|
|
|
|
|
const jsonObject = JSON.parse(jsonContent)
|
|
|
|
jsonObject.owner = github.context.repo.owner
|
|
|
|
jsonObject.repo = github.context.repo.repo
|
|
|
|
const response = await octokit.request('POST /repos/{owner}/{repo}/dependency-graph/snapshots', jsonObject)
|
|
|
|
|
|
|
|
const relativeJsonFile = getRelativePathFromWorkspace(jsonFile)
|
|
|
|
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
async function retrieveDependencyGraphs(workspaceDirectory: string): Promise<string[]> {
|
2023-06-05 15:14:31 -06:00
|
|
|
if (github.context.payload.workflow_run) {
|
2023-07-01 19:00:28 -06:00
|
|
|
return await retrieveDependencyGraphsForWorkflowRun(github.context.payload.workflow_run.id, workspaceDirectory)
|
2023-06-05 15:14:31 -06:00
|
|
|
}
|
|
|
|
return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory)
|
|
|
|
}
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
async function retrieveDependencyGraphsForWorkflowRun(runId: number, workspaceDirectory: string): Promise<string[]> {
|
|
|
|
const octokit: Octokit = getOctokit()
|
|
|
|
|
2023-06-05 15:14:31 -06:00
|
|
|
// Find the workflow run artifacts named "dependency-graph"
|
|
|
|
const artifacts = await octokit.rest.actions.listWorkflowRunArtifacts({
|
|
|
|
owner: github.context.repo.owner,
|
|
|
|
repo: github.context.repo.repo,
|
|
|
|
run_id: runId
|
|
|
|
})
|
|
|
|
|
|
|
|
const matchArtifact = artifacts.data.artifacts.find(candidate => {
|
|
|
|
return candidate.name === DEPENDENCY_GRAPH_ARTIFACT
|
|
|
|
})
|
|
|
|
|
|
|
|
if (matchArtifact === undefined) {
|
|
|
|
throw new Error(`Dependency graph artifact not found. Has it been generated by workflow run '${runId}'?`)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Download the dependency-graph artifact
|
|
|
|
const download = await octokit.rest.actions.downloadArtifact({
|
|
|
|
owner: github.context.repo.owner,
|
|
|
|
repo: github.context.repo.repo,
|
|
|
|
artifact_id: matchArtifact.id,
|
|
|
|
archive_format: 'zip'
|
|
|
|
})
|
|
|
|
|
|
|
|
const downloadBuffer = download.data as ArrayBuffer
|
|
|
|
const downloadZip = path.resolve(workspaceDirectory, 'dependency-graph.zip')
|
|
|
|
fs.writeFileSync(downloadZip, Buffer.from(downloadBuffer))
|
|
|
|
|
|
|
|
// Expance the dependency-graph zip and locate each dependency-graph JSON file
|
|
|
|
const extractDir = path.resolve(workspaceDirectory, 'dependency-graph')
|
|
|
|
const extracted = await toolCache.extractZip(downloadZip, extractDir)
|
|
|
|
core.info(`Extracted dependency graph artifacts to ${extracted}: ${fs.readdirSync(extracted)}`)
|
|
|
|
|
|
|
|
return findDependencyGraphFiles(extracted)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory: string): Promise<string[]> {
|
|
|
|
const artifactClient = artifact.create()
|
|
|
|
const downloadPath = path.resolve(workspaceDirectory, 'dependency-graph')
|
|
|
|
await artifactClient.downloadArtifact(DEPENDENCY_GRAPH_ARTIFACT, downloadPath)
|
|
|
|
return await findDependencyGraphFiles(downloadPath)
|
|
|
|
}
|
|
|
|
|
2023-07-01 19:00:28 -06:00
|
|
|
async function findDependencyGraphFiles(dir: string): Promise<string[]> {
|
|
|
|
const globber = await glob.create(`${dir}/dependency-graph-reports/*.json`)
|
|
|
|
const graphFiles = globber.glob()
|
|
|
|
return graphFiles
|
|
|
|
}
|
|
|
|
|
2023-06-05 15:14:31 -06:00
|
|
|
function getOctokit(): Octokit {
|
|
|
|
return new Octokit({
|
|
|
|
auth: getGithubToken()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function getGithubToken(): string {
|
|
|
|
return core.getInput('github-token', {required: true})
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRelativePathFromWorkspace(file: string): string {
|
|
|
|
const workspaceDirectory = layout.workspaceDirectory()
|
|
|
|
return path.relative(workspaceDirectory, file)
|
|
|
|
}
|
2023-07-01 19:00:28 -06:00
|
|
|
|
|
|
|
export function getJobCorrelator(): string {
|
2023-07-05 12:33:47 -06:00
|
|
|
return constructJobCorrelator(github.context.workflow, github.context.job, getJobMatrix())
|
2023-07-01 19:00:28 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
export function constructJobCorrelator(workflow: string, jobId: string, matrixJson: string): string {
|
|
|
|
const matrixString = describeMatrix(matrixJson)
|
|
|
|
const label = matrixString ? `${workflow}-${jobId}-${matrixString}` : `${workflow}-${jobId}`
|
|
|
|
return sanitize(label)
|
|
|
|
}
|
|
|
|
|
|
|
|
function describeMatrix(matrixJson: string): string {
|
2023-07-07 13:49:10 -06:00
|
|
|
core.debug(`Got matrix json: ${matrixJson}`)
|
2023-07-01 19:00:28 -06:00
|
|
|
const matrix = JSON.parse(matrixJson)
|
|
|
|
if (matrix) {
|
|
|
|
return Object.values(matrix).join('-')
|
|
|
|
}
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
function sanitize(value: string): string {
|
|
|
|
return value.replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase()
|
|
|
|
}
|