import {info, warning, debug} from '@actions/core' import {getOctokit} from '@actions/github' import {ListArtifactsResponse, Artifact} from '../shared/interfaces.js' import {getUserAgentString} from '../shared/user-agent.js' import {getRetryOptions} from './retry-options.js' import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' import {requestLog} from '@octokit/plugin-request-log' import {retry} from '@octokit/plugin-retry' import type {OctokitOptions} from '@octokit/core/types' import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client.js' import {getBackendIdsFromToken} from '../shared/util.js' import {getMaxArtifactListCount} from '../shared/config.js' import {ListArtifactsRequest, Timestamp} from '../../generated/index.js' const maximumArtifactCount = getMaxArtifactListCount() const paginationCount = 100 const maxNumberOfPages = Math.ceil(maximumArtifactCount / paginationCount) export async function listArtifactsPublic( workflowRunId: number, repositoryOwner: string, repositoryName: string, token: string, latest = false ): Promise { info( `Fetching artifact list for workflow run ${workflowRunId} in repository ${repositoryOwner}/${repositoryName}` ) let artifacts: Artifact[] = [] const [retryOpts, requestOpts] = getRetryOptions(defaultGitHubOptions) const opts: OctokitOptions = { log: undefined, userAgent: getUserAgentString(), previews: undefined, retry: retryOpts, request: requestOpts } const github = getOctokit(token, opts, retry, requestLog) let currentPageNumber = 1 const {data: listArtifactResponse} = await github.request( 'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts', { owner: repositoryOwner, repo: repositoryName, run_id: workflowRunId, per_page: paginationCount, page: currentPageNumber } ) let numberOfPages = Math.ceil( listArtifactResponse.total_count / paginationCount ) const totalArtifactCount = listArtifactResponse.total_count if (totalArtifactCount > maximumArtifactCount) { warning( `Workflow run ${workflowRunId} has ${totalArtifactCount} artifacts, exceeding the limit of ${maximumArtifactCount}. Results will be incomplete as only the first ${maximumArtifactCount} artifacts will be returned` ) numberOfPages = maxNumberOfPages } // Iterate over the first page for (const artifact of listArtifactResponse.artifacts) { artifacts.push({ name: artifact.name, id: artifact.id, size: artifact.size_in_bytes, createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined, digest: (artifact as ArtifactResponse).digest }) } // Move to the next page currentPageNumber++ // Iterate over any remaining pages for ( currentPageNumber; currentPageNumber <= numberOfPages; currentPageNumber++ ) { debug(`Fetching page ${currentPageNumber} of artifact list`) const {data: listArtifactResponse} = await github.request( 'GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts', { owner: repositoryOwner, repo: repositoryName, run_id: workflowRunId, per_page: paginationCount, page: currentPageNumber } ) for (const artifact of listArtifactResponse.artifacts) { artifacts.push({ name: artifact.name, id: artifact.id, size: artifact.size_in_bytes, createdAt: artifact.created_at ? new Date(artifact.created_at) : undefined, digest: (artifact as ArtifactResponse).digest }) } } if (latest) { artifacts = filterLatest(artifacts) } info(`Found ${artifacts.length} artifact(s)`) return { artifacts } } export async function listArtifactsInternal( latest = false ): Promise { const artifactClient = internalArtifactTwirpClient() const {workflowRunBackendId, workflowJobRunBackendId} = getBackendIdsFromToken() const req: ListArtifactsRequest = { workflowRunBackendId, workflowJobRunBackendId } const res = await artifactClient.ListArtifacts(req) let artifacts: Artifact[] = res.artifacts.map(artifact => ({ name: artifact.name, id: Number(artifact.databaseId), size: Number(artifact.size), createdAt: artifact.createdAt ? Timestamp.toDate(artifact.createdAt) : undefined, digest: artifact.digest?.value })) if (latest) { artifacts = filterLatest(artifacts) } info(`Found ${artifacts.length} artifact(s)`) return { artifacts } } /** * 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 } /** * 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[] { artifacts.sort((a, b) => b.id - a.id) const latestArtifacts: Artifact[] = [] const seenArtifactNames = new Set() for (const artifact of artifacts) { if (!seenArtifactNames.has(artifact.name)) { latestArtifacts.push(artifact) seenArtifactNames.add(artifact.name) } } return latestArtifacts }