2020-02-11 09:49:46 -05:00
import * as core from '@actions/core'
import {
UploadSpecification ,
getUploadSpecification
2020-03-12 14:50:27 +01:00
} from './upload-specification'
import { UploadHttpClient } from './upload-http-client'
import { UploadResponse } from './upload-response'
import { UploadOptions } from './upload-options'
import { DownloadOptions } from './download-options'
import { DownloadResponse } from './download-response'
2020-04-09 17:14:12 +02:00
import {
createDirectoriesForArtifact ,
createEmptyFilesForArtifact
} from './utils'
2021-12-01 16:31:37 -05:00
import { checkArtifactName } from './path-and-artifact-name-validation'
2020-03-12 14:50:27 +01:00
import { DownloadHttpClient } from './download-http-client'
import { getDownloadSpecification } from './download-specification'
import { getWorkSpaceDirectory } from './config-variables'
2020-02-13 18:24:11 -05:00
import { normalize , resolve } from 'path'
2020-02-11 09:49:46 -05:00
export interface ArtifactClient {
/**
* Uploads an artifact
*
* @param name the name of the artifact, required
* @param files a list of absolute or relative paths that denote what files should be uploaded
* @param rootDirectory an absolute or relative file path that denotes the root parent directory of the files being uploaded
* @param options extra options for customizing the upload behavior
* @returns single UploadInfo object
*/
uploadArtifact (
name : string ,
files : string [],
rootDirectory : string ,
options? : UploadOptions
) : Promise < UploadResponse >
2020-02-13 18:24:11 -05:00
/**
* Downloads a single artifact associated with a run
*
* @param name the name of the artifact being downloaded
* @param path optional path that denotes where the artifact will be downloaded to
* @param options extra options that allow for the customization of the download behavior
*/
downloadArtifact (
name : string ,
path? : string ,
options? : DownloadOptions
) : Promise < DownloadResponse >
/**
* Downloads all artifacts associated with a run. Because there are multiple artifacts being downloaded, a folder will be created for each one in the specified or default directory
* @param path optional path that denotes where the artifacts will be downloaded to
*/
downloadAllArtifacts ( path? : string ) : Promise < DownloadResponse [] >
2020-02-11 09:49:46 -05:00
}
export class DefaultArtifactClient implements ArtifactClient {
/**
* Constructs a DefaultArtifactClient
*/
static create () : DefaultArtifactClient {
return new DefaultArtifactClient ()
}
/**
* Uploads an artifact
*/
async uploadArtifact (
name : string ,
files : string [],
rootDirectory : string ,
options? : UploadOptions | undefined
) : Promise < UploadResponse > {
2021-11-30 12:53:24 -05:00
core . info (
`Starting artifact upload
For more detailed logs during the artifact upload process, enable step-debugging: https://docs.github.com/actions/monitoring-and-troubleshooting-workflows/enabling-debug-logging#enabling-step-debug-logging`
)
2020-02-11 09:49:46 -05:00
checkArtifactName ( name )
// Get specification for the files being uploaded
const uploadSpecification : UploadSpecification [] = getUploadSpecification (
name ,
rootDirectory ,
files
)
const uploadResponse : UploadResponse = {
artifactName : name ,
artifactItems : [],
size : 0 ,
failedItems : []
}
2020-03-12 14:50:27 +01:00
const uploadHttpClient = new UploadHttpClient ()
2020-02-11 09:49:46 -05:00
if ( uploadSpecification . length === 0 ) {
core . warning ( `No files found that can be uploaded` )
} else {
// Create an entry for the artifact in the file container
2020-03-12 14:50:27 +01:00
const response = await uploadHttpClient . createArtifactInFileContainer (
2020-09-18 11:30:00 -04:00
name ,
options
2020-03-12 14:50:27 +01:00
)
2020-02-11 09:49:46 -05:00
if ( ! response . fileContainerResourceUrl ) {
core . debug ( response . toString ())
throw new Error (
'No URL provided by the Artifact Service to upload an artifact to'
)
}
2021-11-30 12:53:24 -05:00
2020-02-11 09:49:46 -05:00
core . debug ( `Upload Resource URL: ${ response . fileContainerResourceUrl } ` )
2021-11-30 12:53:24 -05:00
core . info (
`Container for artifact " ${ name } " successfully created. Starting upload of file(s)`
)
2020-02-11 09:49:46 -05:00
// Upload each of the files that were found concurrently
2020-03-12 14:50:27 +01:00
const uploadResult = await uploadHttpClient . uploadArtifactToFileContainer (
2020-02-11 09:49:46 -05:00
response . fileContainerResourceUrl ,
uploadSpecification ,
options
)
2020-03-12 14:50:27 +01:00
// Update the size of the artifact to indicate we are done uploading
// The uncompressed size is used for display when downloading a zip of the artifact from the UI
2021-11-30 12:53:24 -05:00
core . info (
`File upload process has finished. Finalizing the artifact upload`
)
2020-03-12 14:50:27 +01:00
await uploadHttpClient . patchArtifactSize ( uploadResult . totalSize , name )
2020-02-11 09:49:46 -05:00
2021-11-30 12:53:24 -05:00
if ( uploadResult . failedItems . length > 0 ) {
core . info (
`Upload finished. There were ${ uploadResult . failedItems . length } items that failed to upload`
)
} else {
core . info (
`Artifact has been finalized. All files have been successfully uploaded!`
)
}
2020-02-11 09:49:46 -05:00
core . info (
2021-11-30 12:53:24 -05:00
`
The raw size of all the files that were specified for upload is ${ uploadResult . totalSize } bytes
The size of all the files that were uploaded is ${ uploadResult . uploadSize } bytes. This takes into account any gzip compression used to reduce the upload size, time and storage
Note: The size of downloaded zips can differ significantly from the reported size. For more information see: https://github.com/actions/upload-artifact#zipped-artifact-downloads \ r \ n`
2020-02-11 09:49:46 -05:00
)
uploadResponse . artifactItems = uploadSpecification . map (
item => item . absoluteFilePath
)
2020-03-12 14:50:27 +01:00
uploadResponse . size = uploadResult . uploadSize
2020-02-11 09:49:46 -05:00
uploadResponse . failedItems = uploadResult . failedItems
}
return uploadResponse
}
2020-02-13 18:24:11 -05:00
async downloadArtifact (
name : string ,
path? : string | undefined ,
options? : DownloadOptions | undefined
) : Promise < DownloadResponse > {
2020-03-12 14:50:27 +01:00
const downloadHttpClient = new DownloadHttpClient ()
const artifacts = await downloadHttpClient . listArtifacts ()
2020-02-13 18:24:11 -05:00
if ( artifacts . count === 0 ) {
throw new Error (
`Unable to find any artifacts for the associated workflow`
)
}
2020-02-11 09:49:46 -05:00
2020-02-13 18:24:11 -05:00
const artifactToDownload = artifacts . value . find ( artifact => {
return artifact . name === name
})
if ( ! artifactToDownload ) {
throw new Error ( `Unable to find an artifact with the name: ${ name } ` )
}
2020-02-11 09:49:46 -05:00
2020-03-12 14:50:27 +01:00
const items = await downloadHttpClient . getContainerItems (
2020-02-13 18:24:11 -05:00
artifactToDownload . name ,
artifactToDownload . fileContainerResourceUrl
)
if ( ! path ) {
path = getWorkSpaceDirectory ()
}
path = normalize ( path )
path = resolve ( path )
// During upload, empty directories are rejected by the remote server so there should be no artifacts that consist of only empty directories
const downloadSpecification = getDownloadSpecification (
name ,
items . value ,
path ,
options ? . createArtifactFolder || false
)
if ( downloadSpecification . filesToDownload . length === 0 ) {
core . info (
`No downloadable files were found for the artifact: ${ artifactToDownload . name } `
)
} else {
// Create all necessary directories recursively before starting any download
await createDirectoriesForArtifact (
downloadSpecification . directoryStructure
)
2023-01-19 10:57:29 -05:00
core . info ( 'Directory structure has been set up for the artifact' )
2020-04-09 17:14:12 +02:00
await createEmptyFilesForArtifact (
downloadSpecification . emptyFilesToCreate
)
2020-03-12 14:50:27 +01:00
await downloadHttpClient . downloadSingleArtifact (
downloadSpecification . filesToDownload
)
2020-02-13 18:24:11 -05:00
}
return {
artifactName : name ,
downloadPath : downloadSpecification.rootDownloadLocation
}
2020-02-11 09:49:46 -05:00
}
2020-02-13 18:24:11 -05:00
async downloadAllArtifacts (
path? : string | undefined
) : Promise < DownloadResponse [] > {
2020-03-12 14:50:27 +01:00
const downloadHttpClient = new DownloadHttpClient ()
2020-02-13 18:24:11 -05:00
const response : DownloadResponse [] = []
2020-03-12 14:50:27 +01:00
const artifacts = await downloadHttpClient . listArtifacts ()
2020-02-13 18:24:11 -05:00
if ( artifacts . count === 0 ) {
core . info ( 'Unable to find any artifacts for the associated workflow' )
return response
}
2020-02-11 09:49:46 -05:00
2020-02-13 18:24:11 -05:00
if ( ! path ) {
path = getWorkSpaceDirectory ()
}
path = normalize ( path )
path = resolve ( path )
2020-02-11 09:49:46 -05:00
2020-02-13 18:24:11 -05:00
let downloadedArtifacts = 0
2020-03-12 14:50:27 +01:00
while ( downloadedArtifacts < artifacts . count ) {
const currentArtifactToDownload = artifacts . value [ downloadedArtifacts ]
downloadedArtifacts += 1
2021-12-06 18:39:23 -05:00
core . info (
`starting download of artifact ${ currentArtifactToDownload . name } : ${ downloadedArtifacts } / ${ artifacts . count } `
)
2020-02-13 18:24:11 -05:00
2020-03-12 14:50:27 +01:00
// Get container entries for the specific artifact
const items = await downloadHttpClient . getContainerItems (
currentArtifactToDownload . name ,
currentArtifactToDownload . fileContainerResourceUrl
)
2020-02-13 18:24:11 -05:00
2020-03-12 14:50:27 +01:00
const downloadSpecification = getDownloadSpecification (
currentArtifactToDownload . name ,
items . value ,
path ,
true
)
if ( downloadSpecification . filesToDownload . length === 0 ) {
core . info (
`No downloadable files were found for any artifact ${ currentArtifactToDownload . name } `
)
} else {
await createDirectoriesForArtifact (
downloadSpecification . directoryStructure
)
2020-04-09 17:14:12 +02:00
await createEmptyFilesForArtifact (
downloadSpecification . emptyFilesToCreate
)
2020-03-12 14:50:27 +01:00
await downloadHttpClient . downloadSingleArtifact (
downloadSpecification . filesToDownload
)
}
2020-02-13 18:24:11 -05:00
2020-03-12 14:50:27 +01:00
response . push ({
artifactName : currentArtifactToDownload.name ,
downloadPath : downloadSpecification.rootDownloadLocation
2020-02-13 18:24:11 -05:00
})
2020-03-12 14:50:27 +01:00
}
2020-02-13 18:24:11 -05:00
return response
2020-02-11 09:49:46 -05:00
}
}