Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacing archiver package with direct use of zip-stream for fixing macOS upload issues #1690

Open
wants to merge 75 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
1f22f9f
adding debug for mac upload testing
vmjoseph Mar 15, 2024
4617da2
audit fix
vmjoseph Mar 15, 2024
4090623
update debug statement
vmjoseph Mar 28, 2024
13d981a
Adding buffer and zlib options
vmjoseph Mar 28, 2024
e71ddb9
updating append and directory zips
vmjoseph Mar 28, 2024
14eee6f
updating event handlers
vmjoseph Mar 28, 2024
6e4aebb
cleaning up unused imports
vmjoseph Mar 28, 2024
6defe19
replacing constructor for zipstream
vmjoseph Mar 28, 2024
fda9d58
replacing constructor for zipstream with default
vmjoseph Mar 28, 2024
560ae4d
updating archive import
vmjoseph Mar 28, 2024
8e29fb2
adding default back to zipstream call
vmjoseph Mar 28, 2024
97629b8
adding archiver back for download
vmjoseph Mar 28, 2024
ca87c5e
testing debug statements
vmjoseph Mar 28, 2024
7441cc7
Removing pipe
vmjoseph Mar 28, 2024
06482c6
adding promise to upload
vmjoseph Mar 28, 2024
d97edf7
cleaning up warning callack
vmjoseph Mar 28, 2024
088b976
cleaning up callback methods
vmjoseph Mar 28, 2024
9299663
adding promise all
vmjoseph Mar 28, 2024
b1f55c6
adding finalize
vmjoseph Mar 28, 2024
1875173
update loop for upload
vmjoseph Mar 28, 2024
bef1fc5
adding check for running
vmjoseph Mar 28, 2024
fd88cbe
adding check for running
vmjoseph Mar 28, 2024
66343fa
add resolve all promises again
vmjoseph Mar 28, 2024
c7de68f
updating resolves
vmjoseph Mar 28, 2024
fe0c0de
adding lock
vmjoseph Mar 28, 2024
97e4fcf
wait on write stream
vmjoseph Mar 28, 2024
f33a3f4
Remove finalize
vmjoseph Mar 28, 2024
180b75b
wrap promises in chain
vmjoseph Mar 28, 2024
077846e
adding readable check
vmjoseph Mar 28, 2024
73f526b
adding closed check
vmjoseph Mar 28, 2024
26b62e0
adding more logging
vmjoseph Mar 28, 2024
e4c0440
adding more logging
vmjoseph Mar 28, 2024
a768aa3
adding more logging
vmjoseph Mar 28, 2024
d597cf2
adding upload stream check
vmjoseph Mar 28, 2024
6ac9cbf
check if upload and zipload streams are writable
vmjoseph Mar 28, 2024
d2d6999
adding write check
vmjoseph Mar 28, 2024
31a6086
Adding concurrency and upload response logs
vmjoseph Mar 28, 2024
c9e825e
add blob client property check
vmjoseph Mar 28, 2024
0a0e70d
Add another catch to see if upload stream is failing
vmjoseph Mar 28, 2024
5959333
defaulting compression level
vmjoseph Mar 28, 2024
17c4cab
adding constant zlib compression level
vmjoseph Mar 28, 2024
b6c87ce
Adding more error handlrs
vmjoseph Mar 28, 2024
9322468
Adding pipe back
vmjoseph Mar 28, 2024
bc893bf
adding compression level back
vmjoseph Mar 28, 2024
5288db3
removing debugging statements
vmjoseph Mar 28, 2024
0f23ae1
re-adding old async/await pattern
vmjoseph Mar 28, 2024
d02a834
Remove other debug statements
vmjoseph Mar 28, 2024
6d94ad1
Removing uneeded libs
vmjoseph Mar 28, 2024
a2852ce
adding catch to upload zip
vmjoseph Mar 28, 2024
c14e304
adding more debugging statements
vmjoseph Mar 28, 2024
90ee020
cleaning up debug statements
vmjoseph Apr 1, 2024
2da5288
cleanup
vmjoseph Apr 1, 2024
2d06555
Merge branch 'main' into vmjoseph/node-js-monitor
vmjoseph Apr 1, 2024
2f2738e
removing old packages
vmjoseph Apr 1, 2024
4c878a6
adding back archiver
vmjoseph Apr 1, 2024
dea3595
re-adding archiver for download
vmjoseph Apr 1, 2024
2c4f0f5
adding error checks
vmjoseph Apr 1, 2024
6eff4e9
adding more debug statements
vmjoseph Apr 1, 2024
4778aeb
adding asnyc handler back
vmjoseph Apr 1, 2024
23039a4
adding asnyc handler back
vmjoseph Apr 1, 2024
a8fa53b
test async eachof
vmjoseph Apr 1, 2024
a2a8a72
test queue
vmjoseph Apr 1, 2024
a926fff
adding queue back
vmjoseph Apr 1, 2024
84d3cef
Removing extra logs
vmjoseph Apr 1, 2024
96ef8d5
adding more error handling
vmjoseph Apr 1, 2024
c5f3463
adding more descriptive error
vmjoseph Apr 2, 2024
38f54e9
Adding debug statement for upload
vmjoseph Apr 2, 2024
ce60352
adding asysnc zip entry logic handling
vmjoseph Apr 8, 2024
e7f4586
Merge branch 'main' into vmjoseph/node-js-monitor
vmjoseph Apr 8, 2024
4f54b86
updating errors
vmjoseph Apr 8, 2024
67c3837
updating errors
vmjoseph Apr 8, 2024
6e1d754
updating tests
vmjoseph Apr 8, 2024
da5e926
removing promise wrap
vmjoseph Apr 8, 2024
bcaba45
reverting http update
vmjoseph Apr 8, 2024
42b0077
resolve merge conflicts
jtamsut Apr 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
updating errors
  • Loading branch information
vmjoseph committed Apr 8, 2024
commit 67c383759c81ccff5a529046352d022e8ae675c2
205 changes: 100 additions & 105 deletions packages/artifact/src/internal/upload/upload-artifact.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,119 @@
import * as stream from 'stream'
import * as ZipStream from 'zip-stream'
import * as core from '@actions/core'
import async from 'async'
import {createReadStream} from 'fs'
import {UploadZipSpecification} from './upload-zip-specification'
import {getUploadChunkSize} from '../shared/config'
import {
UploadArtifactOptions,
UploadArtifactResponse
} from '../shared/interfaces'
import {getExpiration} from './retention'
import {validateArtifactName} from './path-and-artifact-name-validation'
import {internalArtifactTwirpClient} from '../shared/artifact-twirp-client'
import {
UploadZipSpecification,
getUploadZipSpecification,
validateRootDirectory
} from './upload-zip-specification'
import {getBackendIdsFromToken} from '../shared/util'
import {uploadZipToBlobStorage} from './blob-upload'
import {createZipUploadStream} from './zip'
import {
CreateArtifactRequest,
FinalizeArtifactRequest,
StringValue
} from '../../generated'
import {FilesNotFoundError, InvalidResponseError} from '../shared/errors'

export const DEFAULT_COMPRESSION_LEVEL = 6
export async function uploadArtifact(
name: string,
files: string[],
rootDirectory: string,
options?: UploadArtifactOptions | undefined
): Promise<UploadArtifactResponse> {
validateArtifactName(name)
validateRootDirectory(rootDirectory)

// Custom stream transformer so we can set the highWaterMark property
// See https://github.com/nodejs/node/issues/8855
export class ZipUploadStream extends stream.Transform {
constructor(bufferSize: number) {
super({
highWaterMark: bufferSize
})
const zipSpecification: UploadZipSpecification[] = getUploadZipSpecification(
files,
rootDirectory
)
if (zipSpecification.length === 0) {
throw new FilesNotFoundError(
zipSpecification.flatMap(s => (s.sourcePath ? [s.sourcePath] : []))
)
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
_transform(chunk: any, enc: any, cb: any): void {
cb(null, chunk)
}
}
// get the IDs needed for the artifact creation
const backendIds = getBackendIdsFromToken()

export async function createZipUploadStream(
uploadSpecification: UploadZipSpecification[],
compressionLevel: number = DEFAULT_COMPRESSION_LEVEL
): Promise<ZipUploadStream> {
core.debug(
`Creating Artifact archive with compressionLevel: ${compressionLevel}`
)
// create the artifact client
const artifactClient = internalArtifactTwirpClient()

const zlibOptions = {
zlib: {
level: compressionLevel,
bufferSize: getUploadChunkSize()
}
// create the artifact
const createArtifactReq: CreateArtifactRequest = {
workflowRunBackendId: backendIds.workflowRunBackendId,
workflowJobRunBackendId: backendIds.workflowJobRunBackendId,
name,
version: 4
}
const zip = new ZipStream.default(zlibOptions)

const bufferSize = getUploadChunkSize()
const zipUploadStream = new ZipUploadStream(bufferSize)
zip.pipe(zipUploadStream)
// register callbacks for various events during the zip lifecycle
zip.on('error', zipErrorCallback)
zip.on('warning', zipWarningCallback)
zip.on('finish', zipFinishCallback)
zip.on('end', zipEndCallback)
const addFileToZip = (
file: UploadZipSpecification,
callback: (error?: Error) => void
) => {
if (file.sourcePath !== null) {
zip.entry(
createReadStream(file.sourcePath),
{name: file.destinationPath},
(error: any) => {
if (error) {
callback(error)
return
}
callback()
}
)
} else {
zip.entry('', {name: file.destinationPath}, (error: any) => {
if (error) {
callback(error)
return
}
callback()
})
}
// if there is a retention period, add it to the request
const expiresAt = getExpiration(options?.retentionDays)
if (expiresAt) {
createArtifactReq.expiresAt = expiresAt
}

async.eachSeries(uploadSpecification, addFileToZip, (error: any) => {
if (error) {
core.error('Failed to add a file to the zip:')
core.info(error)
return
}
zip.finalize() // Finalize the archive once all files have been added
const createArtifactResp =
await artifactClient.CreateArtifact(createArtifactReq)
if (!createArtifactResp.ok) {
throw new InvalidResponseError(
'CreateArtifact: response from backend was not ok'
)
}
// Create the zipupload stream for use in blob upload
const zipUploadStream = await createZipUploadStream(
zipSpecification,
options?.compressionLevel
).catch(err => {
throw new InvalidResponseError(
`createZipUploadStream: response from backend was not ok: ${err}`
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message might be a bit misleading since errors can come from that function without reaching out to a backend.

})

core.debug(
`Zip write high watermark value ${zipUploadStream.writableHighWaterMark}`
)
core.debug(
`Zip read high watermark value ${zipUploadStream.readableHighWaterMark}`
)

return zipUploadStream
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const zipErrorCallback = (error: any): void => {
core.error('An error has occurred while creating the zip file for upload')
core.info(error)

throw new Error('An error has occurred during zip creation for the artifact')
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const zipWarningCallback = (error: any): void => {
if (error.code === 'ENOENT') {
core.warning(
'ENOENT warning during artifact zip creation. No such file or directory'
// Upload zip to blob storage
const uploadResult = await uploadZipToBlobStorage(
createArtifactResp.signedUploadUrl,
zipUploadStream
).catch(err => {
throw new InvalidResponseError(
`uploadZipToBlobStorage: response blob was not ok: ${err}`
)
core.info(error)
} else {
core.warning(
`A non-blocking warning has occurred during artifact zip creation: ${error.code}`
})
// finalize the artifact
const finalizeArtifactReq: FinalizeArtifactRequest = {
workflowRunBackendId: backendIds.workflowRunBackendId,
workflowJobRunBackendId: backendIds.workflowJobRunBackendId,
name,
size: uploadResult.uploadSize ? uploadResult.uploadSize.toString() : '0'
}
if (uploadResult.sha256Hash) {
finalizeArtifactReq.hash = StringValue.create({
value: `sha256:${uploadResult.sha256Hash}`
})
}
core.info(`Finalizing artifact upload`)
const finalizeArtifactResp =
await artifactClient.FinalizeArtifact(finalizeArtifactReq)
if (!finalizeArtifactResp.ok) {
throw new InvalidResponseError(
'FinalizeArtifact: response from backend was not ok'
)
core.info(error)
}
}

const zipFinishCallback = (): void => {
core.debug('Zip stream for upload has finished.')
}
const artifactId = BigInt(finalizeArtifactResp.artifactId)
core.info(
`Artifact ${name}.zip successfully finalized. Artifact ID ${artifactId}`
)

const zipEndCallback = (): void => {
core.debug('Zip stream for upload has ended.')
return {
size: uploadResult.uploadSize,
id: Number(artifactId)
}
}
Loading