1
0
Fork 0

Merge pull request #1844 from actions/robherley/artifact-2.1.11

Properly resolve relative symlinks
pull/1845/head
Rob Herley 2024-10-08 13:08:45 -04:00 committed by GitHub
commit 201b082ce1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 83 additions and 22 deletions

View File

@ -1,5 +1,9 @@
# @actions/artifact Releases # @actions/artifact Releases
### 2.1.11
- Fixed a bug with relative symlinks resolution [#1844](https://github.com/actions/toolkit/pull/1844)
### 2.1.10 ### 2.1.10
- Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830) - Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830)

View File

@ -10,6 +10,7 @@ import {FilesNotFoundError} from '../src/internal/shared/errors'
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import unzip from 'unzip-stream'
const uploadStreamMock = jest.fn() const uploadStreamMock = jest.fn()
const blockBlobClientMock = jest.fn().mockImplementation(() => ({ const blockBlobClientMock = jest.fn().mockImplementation(() => ({
@ -31,9 +32,20 @@ const fixtures = {
{name: 'file2.txt', content: 'test 2 file content'}, {name: 'file2.txt', content: 'test 2 file content'},
{name: 'file3.txt', content: 'test 3 file content'}, {name: 'file3.txt', content: 'test 3 file content'},
{ {
name: 'from_symlink.txt', name: 'real.txt',
content: 'from a symlink'
},
{
name: 'relative.txt',
content: 'from a symlink', content: 'from a symlink',
symlink: '../symlinked.txt' symlink: 'real.txt',
relative: true
},
{
name: 'absolute.txt',
content: 'from a symlink',
symlink: 'real.txt',
relative: false
} }
], ],
backendIDs: { backendIDs: {
@ -55,14 +67,17 @@ const fixtures = {
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeAll(() => { beforeAll(() => {
if (!fs.existsSync(fixtures.uploadDirectory)) { fs.mkdirSync(fixtures.uploadDirectory, {
fs.mkdirSync(fixtures.uploadDirectory, {recursive: true}) recursive: true
} })
for (const file of fixtures.files) { for (const file of fixtures.files) {
if (file.symlink) { if (file.symlink) {
const symlinkPath = path.join(fixtures.uploadDirectory, file.symlink) let symlinkPath = file.symlink
fs.writeFileSync(symlinkPath, file.content) if (!file.relative) {
symlinkPath = path.join(fixtures.uploadDirectory, file.symlink)
}
if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) { if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) {
fs.symlinkSync( fs.symlinkSync(
symlinkPath, symlinkPath,
@ -227,6 +242,12 @@ describe('upload-artifact', () => {
}) })
) )
let loadedBytes = 0
const uploadedZip = path.join(
fixtures.uploadDirectory,
'..',
'uploaded.zip'
)
uploadStreamMock.mockImplementation( uploadStreamMock.mockImplementation(
async ( async (
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
@ -234,19 +255,28 @@ describe('upload-artifact', () => {
maxConcurrency?: number, maxConcurrency?: number,
options?: BlockBlobUploadStreamOptions options?: BlockBlobUploadStreamOptions
) => { ) => {
const {onProgress, abortSignal} = options || {} const {onProgress} = options || {}
if (fs.existsSync(uploadedZip)) {
fs.unlinkSync(uploadedZip)
}
const uploadedZipStream = fs.createWriteStream(uploadedZip)
onProgress?.({loadedBytes: 0}) onProgress?.({loadedBytes: 0})
return new Promise((resolve, reject) => {
return new Promise(resolve => { stream.on('data', chunk => {
const timerId = setTimeout(() => { loadedBytes += chunk.length
onProgress?.({loadedBytes: 256}) uploadedZipStream.write(chunk)
resolve({}) onProgress?.({loadedBytes})
}, 1_000) })
abortSignal?.addEventListener('abort', () => { stream.on('end', () => {
clearTimeout(timerId) onProgress?.({loadedBytes})
uploadedZipStream.end()
resolve({}) resolve({})
}) })
stream.on('error', err => {
reject(err)
})
}) })
} }
) )
@ -260,7 +290,34 @@ describe('upload-artifact', () => {
) )
expect(id).toBe(1) expect(id).toBe(1)
expect(size).toBe(256) expect(size).toBe(loadedBytes)
const extractedDirectory = path.join(
fixtures.uploadDirectory,
'..',
'extracted'
)
if (fs.existsSync(extractedDirectory)) {
fs.rmdirSync(extractedDirectory, {recursive: true})
}
const extract = new Promise((resolve, reject) => {
fs.createReadStream(uploadedZip)
.pipe(unzip.Extract({path: extractedDirectory}))
.on('close', () => {
resolve(true)
})
.on('error', err => {
reject(err)
})
})
await expect(extract).resolves.toBe(true)
for (const file of fixtures.files) {
const filePath = path.join(extractedDirectory, file.name)
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf8')).toBe(file.content)
}
}) })
it('should throw an error uploading blob chunks get delayed', async () => { it('should throw an error uploading blob chunks get delayed', async () => {

View File

@ -1,12 +1,12 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.10", "version": "2.1.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.10", "version": "2.1.11",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.10", "version": "2.1.11",
"preview": true, "preview": true,
"description": "Actions artifact lib", "description": "Actions artifact lib",
"keywords": [ "keywords": [

View File

@ -1,5 +1,5 @@
import * as stream from 'stream' import * as stream from 'stream'
import {readlink} from 'fs/promises' import {realpath} from 'fs/promises'
import * as archiver from 'archiver' import * as archiver from 'archiver'
import * as core from '@actions/core' import * as core from '@actions/core'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
@ -46,7 +46,7 @@ export async function createZipUploadStream(
// Check if symlink and resolve the source path // Check if symlink and resolve the source path
let sourcePath = file.sourcePath let sourcePath = file.sourcePath
if (file.stats.isSymbolicLink()) { if (file.stats.isSymbolicLink()) {
sourcePath = await readlink(file.sourcePath) sourcePath = await realpath(file.sourcePath)
} }
// Add the file to the zip // Add the file to the zip