gradle-build-action/src/dependency-graph.ts
daz be413309fa
First cut at actions for dependency-graph
- Dependency graph init-script references published version of plugin jar.
- `dependency-graph-generate` action will:
     - Provision Gradle if required
     - Execute Gradle with dependency-graph plugin to generate graph JSON
     - Upload dependency-graph JSON file as workflow artifact
- `dependency-graph-submit` action will:
    - Download dependency-graph JSON artifact
    - Submit the graph via the GitHub dependency submission API
2023-06-28 11:24:57 -06:00

155 lines
5.6 KiB
TypeScript

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'
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph'
const DEPENDENCY_GRAPH_FILE = 'dependency-graph.json'
export async function generateDependencyGraph(executable: string | undefined): Promise<void> {
const workspaceDirectory = layout.workspaceDirectory()
const buildRootDirectory = layout.buildRootDirectory()
const buildPath = getRelativePathFromWorkspace(buildRootDirectory)
const initScript = path.resolve(
__dirname,
'..',
'..',
'src',
'resources',
'init-scripts',
'github-dependency-graph.init.gradle'
)
const args = [
`-Dorg.gradle.github.env.GRADLE_BUILD_PATH=${buildPath}`,
'--init-script',
initScript,
':GitHubDependencyGraphPlugin_generateDependencyGraph'
]
await execution.executeGradleBuild(executable, buildRootDirectory, args)
const dependencyGraphJson = copyDependencyGraphToBuildRoot(buildRootDirectory)
const artifactClient = artifact.create()
artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, [dependencyGraphJson], workspaceDirectory)
}
function copyDependencyGraphToBuildRoot(buildRootDirectory: string): string {
const sourceFile = path.resolve(
buildRootDirectory,
'build',
'reports',
'github-dependency-graph-plugin',
'github-dependency-snapshot.json'
)
const destFile = path.resolve(buildRootDirectory, DEPENDENCY_GRAPH_FILE)
fs.copyFileSync(sourceFile, destFile)
return destFile
}
export async function submitDependencyGraph(): Promise<void> {
const workspaceDirectory = layout.workspaceDirectory()
const octokit: Octokit = getOctokit()
for (const jsonFile of await retrieveDependencyGraphs(octokit, workspaceDirectory)) {
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.info(`Submitted ${relativeJsonFile}: ${JSON.stringify(response)}`)
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
}
}
async function findDependencyGraphFiles(dir: string): Promise<string[]> {
const globber = await glob.create(`${dir}/**/${DEPENDENCY_GRAPH_FILE}`)
const graphFiles = globber.glob()
core.info(`Found graph files in ${dir}: ${graphFiles}`)
return graphFiles
}
async function retrieveDependencyGraphs(octokit: Octokit, workspaceDirectory: string): Promise<string[]> {
if (github.context.payload.workflow_run) {
return await retrieveDependencyGraphsForWorkflowRun(
github.context.payload.workflow_run.id,
octokit,
workspaceDirectory
)
}
return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory)
}
async function retrieveDependencyGraphsForWorkflowRun(
runId: number,
octokit: Octokit,
workspaceDirectory: string
): Promise<string[]> {
// 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)
}
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)
}