diff --git a/.github/workflows/integ-test-dependency-graph.yml b/.github/workflows/integ-test-dependency-graph.yml index bf9f1f5..b05fb22 100644 --- a/.github/workflows/integ-test-dependency-graph.yml +++ b/.github/workflows/integ-test-dependency-graph.yml @@ -31,7 +31,7 @@ jobs: - name: Setup Gradle for dependency-graph generate uses: ./ with: - dependency-graph: generate + dependency-graph: generate-and-upload - name: Run gradle build run: ./gradlew build working-directory: .github/workflow-samples/groovy-dsl @@ -55,7 +55,7 @@ jobs: working-directory: .github/workflow-samples/kotlin-dsl submit: - needs: [groovy-generate, kotlin-generate] + needs: [groovy-generate] runs-on: "ubuntu-latest" steps: - name: Checkout sources @@ -80,7 +80,7 @@ jobs: - name: Setup Gradle for dependency-graph generate uses: ./ with: - dependency-graph: generate + dependency-graph: generate-and-submit - id: gradle-assemble run: ./gradlew assemble working-directory: .github/workflow-samples/groovy-dsl diff --git a/action.yml b/action.yml index 362e8b2..d5d1159 100644 --- a/action.yml +++ b/action.yml @@ -64,7 +64,7 @@ inputs: default: true 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 default: 'disabled' diff --git a/src/dependency-graph.ts b/src/dependency-graph.ts index 4f61881..14b5d66 100644 --- a/src/dependency-graph.ts +++ b/src/dependency-graph.ts @@ -1,8 +1,7 @@ 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 {DefaultArtifactClient} from '@actions/artifact' import {GitHub} from '@actions/github/lib/utils' import {RequestError} from '@octokit/request-error' import type {PullRequestEvent} from '@octokit/webhooks-types' @@ -13,7 +12,7 @@ import fs from 'fs' import * as layout from './repository-layout' 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 { if (option === DependencyGraphOption.Disabled) { @@ -39,37 +38,48 @@ export async function setup(option: DependencyGraphOption): Promise { } export async function complete(option: DependencyGraphOption): Promise { - switch (option) { - case DependencyGraphOption.Disabled: - case DependencyGraphOption.DownloadAndSubmit: // Performed in setup - return - case DependencyGraphOption.Generate: - await uploadDependencyGraphs() - return - case DependencyGraphOption.GenerateAndSubmit: - await submitDependencyGraphs(await uploadDependencyGraphs()) - return + try { + switch (option) { + case DependencyGraphOption.Disabled: + case DependencyGraphOption.Generate: // Performed via init-script: nothing to do here + case DependencyGraphOption.DownloadAndSubmit: // Performed in setup + return + case DependencyGraphOption.GenerateAndSubmit: + await submitDependencyGraphs(await findGeneratedDependencyGraphFiles()) + 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 { +async function findGeneratedDependencyGraphFiles(): Promise { const workspaceDirectory = layout.workspaceDirectory() - const graphFiles = await findDependencyGraphFiles(workspaceDirectory) + return await findDependencyGraphFiles(workspaceDirectory) +} - const relativeGraphFiles = graphFiles.map(x => getRelativePathFromWorkspace(x)) - core.info(`Uploading dependency graph files: ${relativeGraphFiles}`) +async function uploadDependencyGraphs(dependencyGraphFiles: string[]): Promise { + const workspaceDirectory = layout.workspaceDirectory() - const artifactClient = artifact.create() - artifactClient.uploadArtifact(DEPENDENCY_GRAPH_ARTIFACT, graphFiles, workspaceDirectory, { - retentionDays: getArtifactRetentionDays() - }) - - return graphFiles + const artifactClient = new DefaultArtifactClient() + 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() + }) + } } async function downloadAndSubmitDependencyGraphs(): Promise { - const workspaceDirectory = layout.workspaceDirectory() - submitDependencyGraphs(await retrieveDependencyGraphs(workspaceDirectory)) + try { + await submitDependencyGraphs(await downloadDependencyGraphs()) + } catch (e) { + core.warning(`Download and submit dependency graph failed. Will continue. ${String(e)}`) + } } async function submitDependencyGraphs(dependencyGraphFiles: string[]): Promise { @@ -111,56 +121,37 @@ async function submitDependencyGraphFile(jsonFile: string): Promise { core.notice(`Submitted ${relativeJsonFile}: ${response.data.message}`) } -async function retrieveDependencyGraphs(workspaceDirectory: string): Promise { - if (github.context.payload.workflow_run) { - return await retrieveDependencyGraphsForWorkflowRun(github.context.payload.workflow_run.id, workspaceDirectory) - } - return retrieveDependencyGraphsForCurrentWorkflow(workspaceDirectory) -} +async function downloadDependencyGraphs(): Promise { + const workspaceDirectory = layout.workspaceDirectory() -async function retrieveDependencyGraphsForWorkflowRun(runId: number, workspaceDirectory: string): Promise { - const octokit = getOctokit() + 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 + } + : undefined - // 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 { - const artifactClient = artifact.create() + const artifactClient = new DefaultArtifactClient() 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 { diff --git a/src/input-params.ts b/src/input-params.ts index d033134..a6a4108 100644 --- a/src/input-params.ts +++ b/src/input-params.ts @@ -84,11 +84,13 @@ export function getDependencyGraphOption(): DependencyGraphOption { return DependencyGraphOption.Generate case 'generate-and-submit': return DependencyGraphOption.GenerateAndSubmit + case 'generate-and-upload': + return DependencyGraphOption.GenerateAndUpload case 'download-and-submit': return DependencyGraphOption.DownloadAndSubmit } 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 { - Disabled, - Generate, - GenerateAndSubmit, - DownloadAndSubmit + Disabled = 'disabled', + Generate = 'generate', + GenerateAndSubmit = 'generate-and-submit', + GenerateAndUpload = 'generate-and-upload', + DownloadAndSubmit = 'download-and-submit' } diff --git a/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle b/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle index 76126bc..2abdac7 100644 --- a/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle +++ b/src/resources/init-scripts/gradle-build-action.github-dependency-graph.init.gradle @@ -27,7 +27,6 @@ if (isTopLevelBuild) { new File(githubOutput) << "dependency-graph-file=${reportFile.absolutePath}\n" } - println "Generating dependency graph into '${reportFile}'" }