Adapt dependency-graph support for new artifact API

- Don't upload artifacts when using 'generate-and-submit'
- New option 'generate-and-upload' to be used with 'download-and-submit'
- Use Artifact API for downloading in the same and different workflow
This commit is contained in:
daz 2023-12-23 18:39:37 -07:00
parent 51b7a82e8e
commit a4dabb3a70
No known key found for this signature in database
5 changed files with 75 additions and 82 deletions

View file

@ -31,7 +31,7 @@ jobs:
- name: Setup Gradle for dependency-graph generate - name: Setup Gradle for dependency-graph generate
uses: ./ uses: ./
with: with:
dependency-graph: generate dependency-graph: generate-and-upload
- name: Run gradle build - name: Run gradle build
run: ./gradlew build run: ./gradlew build
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl
@ -55,7 +55,7 @@ jobs:
working-directory: .github/workflow-samples/kotlin-dsl working-directory: .github/workflow-samples/kotlin-dsl
submit: submit:
needs: [groovy-generate, kotlin-generate] needs: [groovy-generate]
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Checkout sources - name: Checkout sources
@ -80,7 +80,7 @@ jobs:
- name: Setup Gradle for dependency-graph generate - name: Setup Gradle for dependency-graph generate
uses: ./ uses: ./
with: with:
dependency-graph: generate dependency-graph: generate-and-submit
- id: gradle-assemble - id: gradle-assemble
run: ./gradlew assemble run: ./gradlew assemble
working-directory: .github/workflow-samples/groovy-dsl working-directory: .github/workflow-samples/groovy-dsl

View file

@ -64,7 +64,7 @@ inputs:
default: true default: true
dependency-graph: dependency-graph:
description: Specifies if a GitHub dependency snapshot should be generated for each Gradle build, and if so, how. Valid values are 'disabled' (default), 'generate', 'generate-and-submit' and 'download-and-submit'. description: Specifies if a GitHub dependency snapshot should be generated for each Gradle build, and if so, how. Valid values are 'disabled' (default), 'generate', 'generate-and-submit', 'generate-and-upload' and 'download-and-submit'.
required: false required: false
default: 'disabled' default: 'disabled'

View file

@ -1,8 +1,7 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as artifact from '@actions/artifact'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as toolCache from '@actions/tool-cache' import {DefaultArtifactClient} from '@actions/artifact'
import {GitHub} from '@actions/github/lib/utils' import {GitHub} from '@actions/github/lib/utils'
import {RequestError} from '@octokit/request-error' import {RequestError} from '@octokit/request-error'
import type {PullRequestEvent} from '@octokit/webhooks-types' import type {PullRequestEvent} from '@octokit/webhooks-types'
@ -13,7 +12,7 @@ import fs from 'fs'
import * as layout from './repository-layout' import * as layout from './repository-layout'
import {DependencyGraphOption, getJobMatrix, getArtifactRetentionDays} from './input-params' import {DependencyGraphOption, getJobMatrix, getArtifactRetentionDays} from './input-params'
const DEPENDENCY_GRAPH_ARTIFACT = 'dependency-graph' const DEPENDENCY_GRAPH_PREFIX = 'dependency-graph_'
export async function setup(option: DependencyGraphOption): Promise<void> { export async function setup(option: DependencyGraphOption): Promise<void> {
if (option === DependencyGraphOption.Disabled) { if (option === DependencyGraphOption.Disabled) {
@ -39,37 +38,48 @@ export async function setup(option: DependencyGraphOption): Promise<void> {
} }
export async function complete(option: DependencyGraphOption): Promise<void> { export async function complete(option: DependencyGraphOption): Promise<void> {
try {
switch (option) { switch (option) {
case DependencyGraphOption.Disabled: case DependencyGraphOption.Disabled:
case DependencyGraphOption.Generate: // Performed via init-script: nothing to do here
case DependencyGraphOption.DownloadAndSubmit: // Performed in setup case DependencyGraphOption.DownloadAndSubmit: // Performed in setup
return return
case DependencyGraphOption.Generate:
await uploadDependencyGraphs()
return
case DependencyGraphOption.GenerateAndSubmit: case DependencyGraphOption.GenerateAndSubmit:
await submitDependencyGraphs(await uploadDependencyGraphs()) await submitDependencyGraphs(await findGeneratedDependencyGraphFiles())
return return
case DependencyGraphOption.GenerateAndUpload:
await uploadDependencyGraphs(await findGeneratedDependencyGraphFiles())
}
} catch (e) {
core.warning(`Failed to ${option} dependency graph. Will continue. ${String(e)}`)
} }
} }
async function uploadDependencyGraphs(): Promise<string[]> { async function findGeneratedDependencyGraphFiles(): Promise<string[]> {
const workspaceDirectory = layout.workspaceDirectory() const workspaceDirectory = layout.workspaceDirectory()
const graphFiles = await findDependencyGraphFiles(workspaceDirectory) return await findDependencyGraphFiles(workspaceDirectory)
}
const relativeGraphFiles = graphFiles.map(x => getRelativePathFromWorkspace(x)) async function uploadDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
core.info(`Uploading dependency graph files: ${relativeGraphFiles}`) const workspaceDirectory = layout.workspaceDirectory()
const artifactClient = artifact.create() const artifactClient = new DefaultArtifactClient()
artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, graphFiles, workspaceDirectory, { for (const dependencyGraphFile of dependencyGraphFiles) {
const relativePath = getRelativePathFromWorkspace(dependencyGraphFile)
core.info(`Uploading dependency graph file: ${relativePath}`)
const artifactName = `${DEPENDENCY_GRAPH_PREFIX}${path.basename(dependencyGraphFile)}`
await artifactClient.uploadArtifact(artifactName, [dependencyGraphFile], workspaceDirectory, {
retentionDays: getArtifactRetentionDays() retentionDays: getArtifactRetentionDays()
}) })
}
return graphFiles
} }
async function downloadAndSubmitDependencyGraphs(): Promise<void> { async function downloadAndSubmitDependencyGraphs(): Promise<void> {
const workspaceDirectory = layout.workspaceDirectory() try {
submitDependencyGraphs(await retrieveDependencyGraphs(workspaceDirectory)) await submitDependencyGraphs(await downloadDependencyGraphs())
} catch (e) {
core.warning(`Download and submit dependency graph failed. Will continue. ${String(e)}`)
}
} }
async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> { async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise<void> {
@ -111,56 +121,37 @@ async function submitDependencyGraphFile(jsonFile: string): Promise<void> {
core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`) core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`)
} }
async function retrieveDependencyGraphs(workspaceDirectory: string): Promise<string[]> { async function downloadDependencyGraphs(): Promise<string[]> {
if (github.context.payload.workflow_run) { const workspaceDirectory = layout.workspaceDirectory()
return await retrieveDependencyGraphsForWorkflowRun(github.context.payload.workflow_run.id, workspaceDirectory)
const findBy = github.context.payload.workflow_run
? {
token: getGithubToken(),
workflowRunId: github.context.payload.workflow_run.id,
repositoryName: github.context.repo.repo,
repositoryOwner: github.context.repo.owner
} }
return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory) : undefined
}
async function retrieveDependencyGraphsForWorkflowRun(runId: number, workspaceDirectory: string): Promise<string[]> { const artifactClient = new DefaultArtifactClient()
const octokit = getOctokit()
// 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') const downloadPath = path.resolve(workspaceDirectory, 'dependency-graph')
await artifactClient.downloadArtifact(DEPENDENCY_GRAPH_ARTIFACT, downloadPath)
return await findDependencyGraphFiles(downloadPath) const dependencyGraphArtifacts = (
await artifactClient.listArtifacts({
latest: true,
findBy
})
).artifacts.filter(candidate => candidate.name.startsWith(DEPENDENCY_GRAPH_PREFIX))
for (const artifact of dependencyGraphArtifacts) {
const downloadedArtifact = await artifactClient.downloadArtifact(artifact.id, {
path: downloadPath,
findBy
})
core.info(`Downloading dependency-graph artifact ${artifact.name} to ${downloadedArtifact.downloadPath}`)
}
return findDependencyGraphFiles(downloadPath)
} }
async function findDependencyGraphFiles(dir: string): Promise<string[]> { async function findDependencyGraphFiles(dir: string): Promise<string[]> {

View file

@ -84,11 +84,13 @@ export function getDependencyGraphOption(): DependencyGraphOption {
return DependencyGraphOption.Generate return DependencyGraphOption.Generate
case 'generate-and-submit': case 'generate-and-submit':
return DependencyGraphOption.GenerateAndSubmit return DependencyGraphOption.GenerateAndSubmit
case 'generate-and-upload':
return DependencyGraphOption.GenerateAndUpload
case 'download-and-submit': case 'download-and-submit':
return DependencyGraphOption.DownloadAndSubmit return DependencyGraphOption.DownloadAndSubmit
} }
throw TypeError( throw TypeError(
`The value '${val} is not valid for 'dependency-graph. Valid values are: [disabled, generate-and-upload, generate-and-submit, download-and-submit]. The default value is 'disabled'.` `The value '${val} is not valid for 'dependency-graph. Valid values are: [disabled, generate, generate-and-submit, generate-and-upload, download-and-submit]. The default value is 'disabled'.`
) )
} }
@ -123,8 +125,9 @@ function getBooleanInput(paramName: string, paramDefault = false): boolean {
} }
export enum DependencyGraphOption { export enum DependencyGraphOption {
Disabled, Disabled = 'disabled',
Generate, Generate = 'generate',
GenerateAndSubmit, GenerateAndSubmit = 'generate-and-submit',
DownloadAndSubmit GenerateAndUpload = 'generate-and-upload',
DownloadAndSubmit = 'download-and-submit'
} }

View file

@ -27,7 +27,6 @@ if (isTopLevelBuild) {
new File(githubOutput) << "dependency-graph-file=${reportFile.absolutePath}\n" new File(githubOutput) << "dependency-graph-file=${reportFile.absolutePath}\n"
} }
println "Generating dependency graph into '${reportFile}'" println "Generating dependency graph into '${reportFile}'"
} }