2023-08-21 21:23:54 +00:00
import fs from 'fs/promises'
2023-08-21 17:47:17 -04:00
import * as github from '@actions/github'
import * as core from '@actions/core'
import * as httpClient from '@actions/http-client'
2023-12-11 12:15:40 -05:00
import unzip from 'unzip-stream'
2023-08-17 14:40:33 -04:00
import {
DownloadArtifactOptions ,
DownloadArtifactResponse
} from '../shared/interfaces'
2023-08-21 21:23:54 +00:00
import { getUserAgentString } from '../shared/user-agent'
2023-08-22 11:47:14 -07:00
import { getGitHubWorkspaceDir } from '../shared/config'
2023-11-30 03:47:04 +00:00
import { internalArtifactTwirpClient } from '../shared/artifact-twirp-client'
2023-12-01 00:31:27 +00:00
import {
GetSignedArtifactURLRequest ,
2023-12-01 09:05:46 -05:00
Int64Value ,
2023-12-01 00:31:27 +00:00
ListArtifactsRequest
} from '../../generated'
2023-11-30 03:47:04 +00:00
import { getBackendIdsFromToken } from '../shared/util'
2023-12-05 18:35:26 +00:00
import { ArtifactNotFoundError } from '../shared/errors'
2023-08-21 21:23:54 +00:00
const scrubQueryParameters = ( url : string ) : string => {
const parsed = new URL ( url )
parsed . search = ''
return parsed . toString ()
}
async function exists ( path : string ) : Promise < boolean > {
try {
await fs . access ( path )
return true
} catch ( error ) {
if ( error . code === 'ENOENT' ) {
return false
} else {
throw error
}
}
}
2023-08-22 09:17:43 -07:00
async function streamExtract ( url : string , directory : string ) : Promise < void > {
2023-08-21 21:23:54 +00:00
const client = new httpClient . HttpClient ( getUserAgentString ())
const response = await client . get ( url )
if ( response . message . statusCode !== 200 ) {
throw new Error (
`Unexpected HTTP response from blob storage: ${ response . message . statusCode } ${ response . message . statusMessage } `
)
}
2023-12-11 12:15:40 -05:00
return new Promise (( resolve , reject ) => {
response . message
. pipe ( unzip . Extract ({ path : directory }))
. on ( 'close' , resolve )
. on ( 'error' , reject )
})
2023-08-21 21:23:54 +00:00
}
2023-08-17 14:40:33 -04:00
2023-11-30 03:47:04 +00:00
export async function downloadArtifactPublic (
2023-08-17 14:40:33 -04:00
artifactId : number ,
repositoryOwner : string ,
repositoryName : string ,
token : string ,
options? : DownloadArtifactOptions
) : Promise < DownloadArtifactResponse > {
2023-11-30 03:47:04 +00:00
const downloadPath = await resolveOrCreateDirectory ( options ? . path )
2023-08-21 21:23:54 +00:00
const api = github . getOctokit ( token )
core . info (
2023-08-21 17:47:17 -04:00
`Downloading artifact ' ${ artifactId } ' from ' ${ repositoryOwner } / ${ repositoryName } '`
2023-08-21 21:23:54 +00:00
)
const { headers , status } = await api . rest . actions . downloadArtifact ({
owner : repositoryOwner ,
repo : repositoryName ,
artifact_id : artifactId ,
archive_format : 'zip' ,
request : {
redirect : 'manual'
}
})
if ( status !== 302 ) {
throw new Error ( `Unable to download artifact. Unexpected status: ${ status } ` )
}
const { location } = headers
if ( ! location ) {
throw new Error ( `Unable to redirect to artifact download url` )
}
core . info (
`Redirecting to blob download url: ${ scrubQueryParameters ( location ) } `
)
try {
core . info ( `Starting download of artifact to: ${ downloadPath } ` )
await streamExtract ( location , downloadPath )
core . info ( `Artifact download completed successfully.` )
} catch ( error ) {
throw new Error ( `Unable to download and extract artifact: ${ error . message } ` )
}
2023-12-05 18:35:26 +00:00
return { downloadPath }
2023-08-17 14:40:33 -04:00
}
2023-11-30 03:47:04 +00:00
export async function downloadArtifactInternal (
artifactId : number ,
options? : DownloadArtifactOptions
) : Promise < DownloadArtifactResponse > {
const downloadPath = await resolveOrCreateDirectory ( options ? . path )
const artifactClient = internalArtifactTwirpClient ()
const { workflowRunBackendId , workflowJobRunBackendId } =
getBackendIdsFromToken ()
const listReq : ListArtifactsRequest = {
workflowRunBackendId ,
2023-12-01 09:05:46 -05:00
workflowJobRunBackendId ,
idFilter : Int64Value.create ({ value : artifactId.toString ()})
2023-11-30 03:47:04 +00:00
}
const { artifacts } = await artifactClient . ListArtifacts ( listReq )
if ( artifacts . length === 0 ) {
2023-12-05 18:35:26 +00:00
throw new ArtifactNotFoundError (
2023-11-30 03:47:04 +00:00
`No artifacts found for ID: ${ artifactId } \ nAre you trying to download from a different run? Try specifying a github-token with \`actions:read\` scope.`
)
}
if ( artifacts . length > 1 ) {
core . warning ( 'Multiple artifacts found, defaulting to first.' )
}
const signedReq : GetSignedArtifactURLRequest = {
workflowRunBackendId : artifacts [ 0 ]. workflowRunBackendId ,
workflowJobRunBackendId : artifacts [ 0 ]. workflowJobRunBackendId ,
name : artifacts [ 0 ]. name
}
const { signedUrl } = await artifactClient . GetSignedArtifactURL ( signedReq )
core . info (
`Redirecting to blob download url: ${ scrubQueryParameters ( signedUrl ) } `
)
try {
core . info ( `Starting download of artifact to: ${ downloadPath } ` )
await streamExtract ( signedUrl , downloadPath )
core . info ( `Artifact download completed successfully.` )
} catch ( error ) {
throw new Error ( `Unable to download and extract artifact: ${ error . message } ` )
}
2023-12-05 18:35:26 +00:00
return { downloadPath }
2023-11-30 03:47:04 +00:00
}
async function resolveOrCreateDirectory (
downloadPath = getGitHubWorkspaceDir ()
) : Promise < string > {
if ( ! ( await exists ( downloadPath ))) {
core . debug (
`Artifact destination folder does not exist, creating: ${ downloadPath } `
)
await fs . mkdir ( downloadPath , { recursive : true })
} else {
core . debug ( `Artifact destination folder already exists: ${ downloadPath } ` )
}
return downloadPath
}