You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
11 KiB
296 lines
11 KiB
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()
|
|
|