2023-08-17 14:40:33 -04:00
import { info , warning , debug } from '@actions/core'
import { getOctokit } from '@actions/github'
import { ListArtifactsResponse , Artifact } from '../shared/interfaces'
import { getUserAgentString } from '../shared/user-agent'
2023-08-23 10:36:33 -07:00
import { getRetryOptions } from './retry-options'
2023-08-17 14:40:33 -04:00
import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils'
import { requestLog } from '@octokit/plugin-request-log'
import { retry } from '@octokit/plugin-retry'
2023-08-23 10:36:33 -07:00
import { OctokitOptions } from '@octokit/core/dist-types/types'
2023-11-30 03:47:04 +00:00
import { internalArtifactTwirpClient } from '../shared/artifact-twirp-client'
import { getBackendIdsFromToken } from '../shared/util'
2025-10-21 09:22:11 -04:00
import { getMaxArtifactListCount } from '../shared/config'
2023-12-02 21:34:07 -05:00
import { ListArtifactsRequest , Timestamp } from '../../generated'
2023-08-17 14:40:33 -04:00
2025-10-21 09:22:11 -04:00
const maximumArtifactCount = getMaxArtifactListCount ()
2023-08-17 14:40:33 -04:00
const paginationCount = 100
2025-10-21 09:22:11 -04:00
const maxNumberOfPages = Math . ceil ( maximumArtifactCount / paginationCount )
2023-08-17 14:40:33 -04:00
2023-11-30 03:47:04 +00:00
export async function listArtifactsPublic (
2023-08-17 14:40:33 -04:00
workflowRunId : number ,
repositoryOwner : string ,
repositoryName : string ,
2023-12-03 05:01:20 +00:00
token : string ,
latest = false
2023-08-17 14:40:33 -04:00
) : Promise < ListArtifactsResponse > {
2023-08-22 09:57:14 -07:00
info (
`Fetching artifact list for workflow run ${ workflowRunId } in repository ${ repositoryOwner } / ${ repositoryName } `
)
2023-08-17 14:40:33 -04:00
2023-12-03 05:01:20 +00:00
let artifacts : Artifact [] = []
2023-08-23 10:36:33 -07:00
const [ retryOpts , requestOpts ] = getRetryOptions ( defaultGitHubOptions )
2023-08-17 14:40:33 -04:00
2023-08-23 10:36:33 -07:00
const opts : OctokitOptions = {
2023-08-17 14:40:33 -04:00
log : undefined ,
userAgent : getUserAgentString (),
previews : undefined ,
retry : retryOpts ,
request : requestOpts
}
const github = getOctokit ( token , opts , retry , requestLog )
let currentPageNumber = 1
2025-03-05 11:29:44 +00:00
const { data : listArtifactResponse } = await github . request (
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts' ,
{
2023-08-17 14:40:33 -04:00
owner : repositoryOwner ,
repo : repositoryName ,
run_id : workflowRunId ,
per_page : paginationCount ,
page : currentPageNumber
2025-03-05 11:29:44 +00:00
}
)
2023-08-17 14:40:33 -04:00
let numberOfPages = Math . ceil (
listArtifactResponse . total_count / paginationCount
)
const totalArtifactCount = listArtifactResponse . total_count
if ( totalArtifactCount > maximumArtifactCount ) {
warning (
2025-10-17 18:05:06 -04:00
`Workflow run ${ workflowRunId } has ${ totalArtifactCount } artifacts, exceeding the limit of ${ maximumArtifactCount } . Results will be incomplete as only the first ${ maximumArtifactCount } artifacts will be returned`
2023-08-17 14:40:33 -04:00
)
numberOfPages = maxNumberOfPages
}
// Iterate over the first page
2023-08-22 10:06:40 -07:00
for ( const artifact of listArtifactResponse . artifacts ) {
2023-08-17 14:40:33 -04:00
artifacts . push ({
name : artifact.name ,
id : artifact.id ,
2023-12-02 21:18:22 -05:00
size : artifact.size_in_bytes ,
2025-03-05 11:29:44 +00:00
createdAt : artifact.created_at
? new Date ( artifact . created_at )
: undefined ,
digest : ( artifact as ArtifactResponse ). digest
2023-08-17 14:40:33 -04:00
})
2023-08-22 10:06:40 -07:00
}
2025-03-05 09:27:35 +00:00
// Move to the next page
currentPageNumber ++
2023-08-17 14:40:33 -04:00
// Iterate over any remaining pages
for (
currentPageNumber ;
2025-10-21 09:22:11 -04:00
currentPageNumber <= numberOfPages ;
2023-08-17 14:40:33 -04:00
currentPageNumber ++
) {
debug ( `Fetching page ${ currentPageNumber } of artifact list` )
2025-03-05 11:29:44 +00:00
const { data : listArtifactResponse } = await github . request (
'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts' ,
{
2023-08-17 14:40:33 -04:00
owner : repositoryOwner ,
repo : repositoryName ,
run_id : workflowRunId ,
per_page : paginationCount ,
page : currentPageNumber
2025-03-05 11:29:44 +00:00
}
)
2023-08-17 14:40:33 -04:00
2023-08-22 10:06:40 -07:00
for ( const artifact of listArtifactResponse . artifacts ) {
2023-08-17 14:40:33 -04:00
artifacts . push ({
name : artifact.name ,
id : artifact.id ,
2023-12-02 21:18:22 -05:00
size : artifact.size_in_bytes ,
createdAt : artifact.created_at
? new Date ( artifact . created_at )
2025-03-05 11:29:44 +00:00
: undefined ,
digest : ( artifact as ArtifactResponse ). digest
2023-08-17 14:40:33 -04:00
})
2023-08-22 10:06:40 -07:00
}
2023-08-17 14:40:33 -04:00
}
2023-12-03 05:01:20 +00:00
if ( latest ) {
artifacts = filterLatest ( artifacts )
}
2023-11-30 03:47:04 +00:00
info ( `Found ${ artifacts . length } artifact(s)` )
return {
artifacts
}
}
2023-12-03 05:01:20 +00:00
export async function listArtifactsInternal (
latest = false
) : Promise < ListArtifactsResponse > {
2023-11-30 03:47:04 +00:00
const artifactClient = internalArtifactTwirpClient ()
const { workflowRunBackendId , workflowJobRunBackendId } =
getBackendIdsFromToken ()
const req : ListArtifactsRequest = {
workflowRunBackendId ,
2023-11-30 19:10:07 +00:00
workflowJobRunBackendId
2023-11-30 03:47:04 +00:00
}
const res = await artifactClient . ListArtifacts ( req )
2023-12-03 05:01:20 +00:00
let artifacts : Artifact [] = res . artifacts . map ( artifact => ({
2023-11-30 03:47:04 +00:00
name : artifact.name ,
id : Number ( artifact . databaseId ),
2023-12-02 21:18:22 -05:00
size : Number ( artifact . size ),
createdAt : artifact.createdAt
? Timestamp . toDate ( artifact . createdAt )
2025-03-05 11:29:44 +00:00
: undefined ,
digest : artifact.digest?.value
2023-11-30 03:47:04 +00:00
}))
2023-12-03 05:01:20 +00:00
if ( latest ) {
artifacts = filterLatest ( artifacts )
}
2023-11-30 03:47:04 +00:00
info ( `Found ${ artifacts . length } artifact(s)` )
2023-08-17 14:40:33 -04:00
return {
2023-08-22 10:06:40 -07:00
artifacts
2023-08-17 14:40:33 -04:00
}
}
2023-12-03 05:01:20 +00:00
2025-03-05 11:29:44 +00:00
/**
* This exists so that we don't have to use 'any' when receiving the artifact list from the GitHub API.
* The digest field is not present in OpenAPI/types at time of writing, which necessitates this change.
*/
interface ArtifactResponse {
name : string
id : number
size_in_bytes : number
created_at? : string
digest? : string
}
2023-12-03 05:01:20 +00:00
/**
* Filters a list of artifacts to only include the latest artifact for each name
* @param artifacts The artifacts to filter
* @returns The filtered list of artifacts
*/
function filterLatest ( artifacts : Artifact []) : Artifact [] {
2023-12-03 06:24:49 +00:00
artifacts . sort (( a , b ) => b . id - a . id )
2023-12-03 05:01:20 +00:00
const latestArtifacts : Artifact [] = []
const seenArtifactNames = new Set < string >()
for ( const artifact of artifacts ) {
if ( ! seenArtifactNames . has ( artifact . name )) {
latestArtifacts . push ( artifact )
seenArtifactNames . add ( artifact . name )
}
}
return latestArtifacts
}