From c8879bf5aef7bef66f9b82b197f34c4eeeb1731b Mon Sep 17 00:00:00 2001 From: Konrad Pabjan Date: Fri, 31 Jul 2020 19:22:08 +0200 Subject: [PATCH] Detect case insensitive uploads + Bump @actions/artifact to version 0.3.3 (#106) * Detect case insensitive uploads * PR feedback --- README.md | 27 ++++++++++++++++++++++++++- dist/index.js | 29 +++++++++++++++++++++-------- package-lock.json | 6 +++--- package.json | 2 +- src/search.ts | 17 ++++++++++++++++- 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d576747..27881df 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,32 @@ Environment variables along with context expressions can also be used for input. In the top right corner of a workflow run, once the run is over, if you used this action, there will be a `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like
-There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository. +There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository. + +# Limitations + +### Permission Loss + +:exclamation: File permissions are not maintained during artifact upload :exclamation: For example, if you make a file executable using `chmod` and then upload that file, post-download the file is no longer guaranteed to be set as an executable. + +### Case Insensitive Uploads + +:exclamation: File uploads are case insensitive :exclamation: If you upload `A.txt` and `a.txt` with the same root path, only a single file will be saved and available during download. + +### Maintaining file permissions and case sensitive files + +If file permissions and case sensitivity are required, you can `tar` all of your files together before artifact upload. Post download, the `tar` file will maintain file permissions and case sensitivity. + +```yaml + - name: 'Tar files' + run: tar -cvf my_files.tar /path/to/my/directory + + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: my-artifact + path: my_files.tar +``` ## Additional Documentation diff --git a/dist/index.js b/dist/index.js index e9321ce..264f2de 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4992,11 +4992,12 @@ const utils_1 = __webpack_require__(870); * Used for managing http clients during either upload or download */ class HttpManager { - constructor(clientCount) { + constructor(clientCount, userAgent) { if (clientCount < 1) { throw new Error('There must be at least one client'); } - this.clients = new Array(clientCount).fill(utils_1.createHttpClient()); + this.userAgent = userAgent; + this.clients = new Array(clientCount).fill(utils_1.createHttpClient(userAgent)); } getClient(index) { return this.clients[index]; @@ -5005,7 +5006,7 @@ class HttpManager { // for more information see: https://github.com/actions/http-client/blob/04e5ad73cd3fd1f5610a32116b0759eddf6570d2/index.ts#L292 disposeAndReplaceClient(index) { this.clients[index].dispose(); - this.clients[index] = utils_1.createHttpClient(); + this.clients[index] = utils_1.createHttpClient(this.userAgent); } disposeAndReplaceAllClients() { for (const [index] of this.clients.entries()) { @@ -6288,7 +6289,7 @@ function getMultiPathLCA(searchPaths) { } return true; } - // Loop over all the search paths until there is a non-common ancestor or we go out of bounds + // loop over all the search paths until there is a non-common ancestor or we go out of bounds while (splitIndex < smallestPathLength) { if (!isPathTheSame()) { break; @@ -6304,6 +6305,11 @@ function findFilesToUpload(searchPath, globOptions) { const searchResults = []; const globber = yield glob.create(searchPath, globOptions || getDefaultGlobOptions()); const rawSearchResults = yield globber.glob(); + /* + Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten + Detect any files that could be overwritten for user awareness + */ + const set = new Set(); /* Directories will be rejected if attempted to be uploaded. This includes just empty directories so filter any directories out from the raw search results @@ -6314,6 +6320,13 @@ function findFilesToUpload(searchPath, globOptions) { if (!fileStats.isDirectory()) { core_1.debug(`File:${searchResult} was found using the provided searchPath`); searchResults.push(searchResult); + // detect any files that would be overwritten because of case insensitivity + if (set.has(searchResult.toLowerCase())) { + core_1.info(`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`); + } + else { + set.add(searchResult.toLowerCase()); + } } else { core_1.debug(`Removing ${searchResult} from rawSearchResults because it is a directory`); @@ -6645,7 +6658,7 @@ const upload_gzip_1 = __webpack_require__(647); const stat = util_1.promisify(fs.stat); class UploadHttpClient { constructor() { - this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency()); + this.uploadHttpManager = new http_manager_1.HttpManager(config_variables_1.getUploadFileConcurrency(), 'actions/upload-artifact'); this.statusReporter = new status_reporter_1.StatusReporter(10000); } /** @@ -7399,7 +7412,7 @@ const http_manager_1 = __webpack_require__(452); const config_variables_1 = __webpack_require__(401); class DownloadHttpClient { constructor() { - this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency()); + this.downloadHttpManager = new http_manager_1.HttpManager(config_variables_1.getDownloadFileConcurrency(), 'actions/download-artifact'); // downloads are usually significantly faster than uploads so display status information every second this.statusReporter = new status_reporter_1.StatusReporter(1000); } @@ -8034,8 +8047,8 @@ function getUploadHeaders(contentType, isKeepAlive, isGzip, uncompressedLength, return requestOptions; } exports.getUploadHeaders = getUploadHeaders; -function createHttpClient() { - return new http_client_1.HttpClient('actions/artifact', [ +function createHttpClient(userAgent) { + return new http_client_1.HttpClient(userAgent, [ new auth_1.BearerCredentialHandler(config_variables_1.getRuntimeToken()) ]); } diff --git a/package-lock.json b/package-lock.json index 61facf7..e789485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@actions/artifact": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.2.tgz", - "integrity": "sha512-KzUe5DEeVXprAodxfGKtx9f7ukuVKE6V6pge6t5GDGk0cdkfiMEfahoq7HfBsOsmVy4J7rr1YZQPUTvXveYinw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-0.3.3.tgz", + "integrity": "sha512-sKC1uA5p6064C6Qypmmt6O8iKlpDyMTfqqDlS4/zfJX1Hs8NbbzPLLN81RpewuJPWQNnroeF52w4VCWypbSNaA==", "dev": true, "requires": { "@actions/core": "^1.2.1", diff --git a/package.json b/package.json index e13d64c..12511ee 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/actions/upload-artifact#readme", "devDependencies": { - "@actions/artifact": "^0.3.2", + "@actions/artifact": "^0.3.3", "@actions/core": "^1.2.3", "@actions/glob": "^0.1.0", "@actions/io": "^1.0.2", diff --git a/src/search.ts b/src/search.ts index 5a4911a..bd80164 100644 --- a/src/search.ts +++ b/src/search.ts @@ -66,7 +66,7 @@ function getMultiPathLCA(searchPaths: string[]): string { return true } - // Loop over all the search paths until there is a non-common ancestor or we go out of bounds + // loop over all the search paths until there is a non-common ancestor or we go out of bounds while (splitIndex < smallestPathLength) { if (!isPathTheSame()) { break @@ -89,6 +89,12 @@ export async function findFilesToUpload( ) const rawSearchResults: string[] = await globber.glob() + /* + Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten + Detect any files that could be overwritten for user awareness + */ + const set = new Set() + /* Directories will be rejected if attempted to be uploaded. This includes just empty directories so filter any directories out from the raw search results @@ -99,6 +105,15 @@ export async function findFilesToUpload( if (!fileStats.isDirectory()) { debug(`File:${searchResult} was found using the provided searchPath`) searchResults.push(searchResult) + + // detect any files that would be overwritten because of case insensitivity + if (set.has(searchResult.toLowerCase())) { + info( + `Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path` + ) + } else { + set.add(searchResult.toLowerCase()) + } } else { debug( `Removing ${searchResult} from rawSearchResults because it is a directory`