const core = require('@actions/core') const github = require('@actions/github') const artifact = require('@actions/artifact') const AdmZip = require('adm-zip') const filesize = require('filesize') const pathname = require('path') const fs = require('fs') async function downloadAction(name, path) { const artifactClient = artifact.create() const downloadOptions = { createArtifactFolder: false } const downloadResponse = await artifactClient.downloadArtifact( name, path, downloadOptions ) core.setOutput("found_artifact", true) } async function main() { try { const token = core.getInput("github_token", { required: true }) const [owner, repo] = core.getInput("repo", { required: true }).split("/") const path = core.getInput("path", { required: true }) const names = core.getInput("artifacts") ? core.getInput("artifacts") : "*" const nameIsRegExp = core.getBooleanInput("name_is_regexp") const skipUnpack = core.getBooleanInput("skip_unpack") const ifNoArtifactFound = core.getInput("if_no_artifact_found") let workflow = core.getInput("workflow") let workflowConclusion = core.getInput("workflow_conclusion") let pr = core.getInput("pr") let commit = core.getInput("commit") let branch = core.getInput("branch") let event = core.getInput("event") let runID = core.getInput("run_id") let runNumber = core.getInput("run_number") let checkArtifacts = core.getBooleanInput("check_artifacts") let searchArtifacts = core.getBooleanInput("search_artifacts") const allowForks = core.getBooleanInput("allow_forks") let dryRun = core.getInput("dry_run") const client = github.getOctokit(token) core.info(`==> Repository: ${owner}/${repo}`) core.info(`==> Artifact name(s): ${names}`) core.info(`==> Local path: ${path}`) if (!workflow) { const run = await client.rest.actions.getWorkflowRun({ owner: owner, repo: repo, run_id: runID || github.context.runId, }) workflow = run.data.workflow_id } core.info(`==> Workflow name: ${workflow}`) core.info(`==> Workflow conclusion: ${workflowConclusion}`) const uniqueInputSets = [ { "pr": pr, "commit": commit, "branch": branch, "run_id": runID } ] uniqueInputSets.forEach((inputSet) => { const inputs = Object.values(inputSet) const providedInputs = inputs.filter(input => input !== '') if (providedInputs.length > 1) { throw new Error(`The following inputs cannot be used together: ${Object.keys(inputSet).join(", ")}`) } }) if (pr) { core.info(`==> PR: ${pr}`) const pull = await client.rest.pulls.get({ owner: owner, repo: repo, pull_number: pr, }) commit = pull.data.head.sha //branch = pull.data.head.ref } if (commit) { core.info(`==> Commit: ${commit}`) } if (branch) { branch = branch.replace(/^refs\/heads\//, "") core.info(`==> Branch: ${branch}`) } if (event) { core.info(`==> Event: ${event}`) } if (runNumber) { core.info(`==> Run number: ${runNumber}`) } core.info(`==> Allow forks: ${allowForks}`) if (!runID) { // Note that the runs are returned in most recent first order. for await (const runs of client.paginate.iterator(client.rest.actions.listWorkflowRuns, { owner: owner, repo: repo, workflow_id: workflow, ...(branch ? { branch } : {}), ...(event ? { event } : {}), ...(commit ? { head_sha: commit } : {}), } )) { for (const run of runs.data) { if (runNumber && run.run_number != runNumber) { continue } if (workflowConclusion && (workflowConclusion != run.conclusion && workflowConclusion != run.status)) { continue } if (!allowForks && run.head_repository.full_name !== `${owner}/${repo}`) { core.info(`==> Skipping run from fork: ${run.head_repository.full_name}`) continue } if (checkArtifacts || searchArtifacts) { let artifacts = await client.paginate(client.rest.actions.listWorkflowRunArtifacts, { owner: owner, repo: repo, run_id: run.id, }) if (!artifacts || artifacts.length == 0) { continue } if (searchArtifacts) { const artifact = artifacts.find((artifact) => { if (nameIsRegExp) { return artifact.name.match(name) !== null } return artifact.name == name }) if (!artifact) { continue } } } runID = run.id core.info(`==> (found) Run ID: ${runID}`) core.info(`==> (found) Run date: ${run.created_at}`) break } if (runID) { break } } } if (!runID) { if (workflowConclusion && (workflowConclusion != 'in_progress')) { return setExitMessage(ifNoArtifactFound, "no matching workflow run found with any artifacts?") } try { return await downloadAction(name, path) } catch (error) { return setExitMessage(ifNoArtifactFound, "no matching artifact in this workflow?") } } let artifacts = await client.paginate(client.rest.actions.listWorkflowRunArtifacts, { owner: owner, repo: repo, run_id: runID, }) // One artifact, a list of artifacts, or all if `artifacts` input is not specified. const matchesWithRegex = (stringToTest, regexRule) => { const escapeSpecialChars = (string) => string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); const builtRegexRule = "^" + regexRule.split("*").map(escapeSpecialChars).join(".*") + "$" return new RegExp(builtRegexRule).test(stringToTest) } const artifactNames = names.split(",").map(artifactName => artifactName.trim()) artifacts = artifacts.filter(artifact => { return artifactNames.map(name => matchesWithRegex(artifact.name, name)).reduce((prevValue, currValue) => prevValue || currValue) }) core.setOutput("artifacts", artifacts) const artifactBuildCommit = artifacts[0].workflow_run.head_sha; core.setOutput("artifact-build-commit", artifactBuildCommit); const artifactBuildBranch = artifacts[0].workflow_run.head_branch; core.setOutput("artifact-build-branch", artifactBuildBranch); if (dryRun) { if (artifacts.length == 0) { core.setOutput("dry_run", false) core.setOutput("found_artifact", false) return } else { core.setOutput("dry_run", true) core.setOutput("found_artifact", true) core.info('==> (found) Artifacts') for (const artifact of artifacts) { const size = filesize(artifact.size_in_bytes, { base: 10 }) core.info(`\t==> Artifact:`) core.info(`\t==> ID: ${artifact.id}`) core.info(`\t==> Name: ${artifact.name}`) core.info(`\t==> Size: ${size}`) } return } } if (artifacts.length == 0) { return setExitMessage(ifNoArtifactFound, "no artifacts found") } core.setOutput("found_artifact", true) for (const artifact of artifacts) { core.info(`==> Artifact: ${artifact.id}`) const size = filesize(artifact.size_in_bytes, { base: 10 }) core.info(`==> Downloading: ${artifact.name}.zip (${size})`) let zip try { zip = await client.rest.actions.downloadArtifact({ owner: owner, repo: repo, artifact_id: artifact.id, archive_format: "zip", }) } catch (error) { if (error.message === "Artifact has expired") { return setExitMessage(ifNoArtifactFound, "no downloadable artifacts found (expired)") } else { throw new Error(error.message) } } if (skipUnpack) { fs.mkdirSync(path, { recursive: true }) fs.writeFileSync(`${pathname.join(path, artifact.name)}.zip`, Buffer.from(zip.data), 'binary') continue } const dir = path fs.mkdirSync(dir, { recursive: true }) const adm = new AdmZip(Buffer.from(zip.data)) core.startGroup(`==> Extracting: ${artifact.name}.zip`) adm.getEntries().forEach((entry) => { const action = entry.isDirectory ? "creating" : "inflating" const filepath = pathname.join(dir, entry.entryName) core.info(` ${action}: ${filepath}`) }) adm.extractAllTo(dir, true) core.endGroup() } } catch (error) { core.setOutput("found_artifact", false) core.setOutput("error_message", error.message) core.setFailed(error.message) } function setExitMessage(ifNoArtifactFound, message) { core.setOutput("found_artifact", false) switch (ifNoArtifactFound) { case "fail": core.setFailed(message) break case "warn": core.warning(message) break case "ignore": default: core.info(message) break } } } main()