-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add tool-cache #12
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
Merged
Merged
Add tool-cache #12
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
20bb325
Add tool-cache
044ccbe
Format
9ca4353
Fix linux bug
2c78503
Update readme
21da019
Package zip/unzip
911e847
Merge branch 'features/tool-cache' of https://github.com/actions/tool…
87cf56d
Reorganize
7e6cc16
Format
f27fda8
unzip somehow got corrupted, fixing
a08760e
Resolve remaining todos
eb02e1f
Don't log everything
17167af
Pass error codes
d994756
linting
e3cbf7b
Branding and nits
1ad4b89
Fix for mac
2d58290
clean up windows/nix logic
950b8a8
Clean up tests
4f35290
Export HTTPError
020aa68
Add option for custom 7z tool
6e5e3c3
Lint/format
2e8d81f
Dont wipe temp/tool directories
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ | |
| "core", | ||
| "actions" | ||
| ], | ||
| "author": "Bryan MacFarlane <[email protected]>", | ||
| "homepage": "https://github.com/actions/toolkit/tree/master/packages/core", | ||
| "license": "MIT", | ||
| "main": "lib/core.js", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ | |
| "exec", | ||
| "actions" | ||
| ], | ||
| "author": "Bryan MacFarlane <[email protected]>", | ||
| "homepage": "https://github.com/actions/toolkit/tree/master/packages/exec", | ||
| "license": "MIT", | ||
| "main": "lib/exec.js", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,6 @@ | |
| "io", | ||
| "actions" | ||
| ], | ||
| "author": "Danny McCormick <[email protected]>", | ||
| "homepage": "https://github.com/actions/toolkit/tree/master/packages/io", | ||
| "license": "MIT", | ||
| "main": "lib/io.js", | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # `@actions/tool-cache` | ||
|
|
||
| > Functions necessary for downloading and caching tools. | ||
|
|
||
| ## Usage | ||
|
|
||
| See [src/tool-cache.ts](src/tool-cache.ts). |
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,338 @@ | ||
| import * as fs from 'fs' | ||
| import * as nock from 'nock' | ||
| import * as path from 'path' | ||
| import * as io from '@actions/io' | ||
| import * as exec from '@actions/exec' | ||
|
|
||
| const cachePath = path.join(__dirname, 'CACHE') | ||
| const tempPath = path.join(__dirname, 'TEMP') | ||
| // Set temp and tool directories before importing (used to set global state) | ||
| process.env['RUNNER_TEMPDIRECTORY'] = tempPath | ||
| process.env['RUNNER_TOOLSDIRECTORY'] = cachePath | ||
|
|
||
| // eslint-disable-next-line import/first | ||
| import * as tc from '../src/tool-cache' | ||
|
|
||
| const IS_WINDOWS = process.platform === 'win32' | ||
|
|
||
| describe('@actions/tool-cache', function() { | ||
| beforeAll(function() { | ||
| nock('http://example.com') | ||
| .persist() | ||
| .get('/bytes/35') | ||
| .reply(200, { | ||
| username: 'abc', | ||
| password: 'def' | ||
| }) | ||
| }) | ||
|
|
||
| beforeEach(async function() { | ||
| await io.rmRF(cachePath) | ||
| await io.rmRF(tempPath) | ||
| await io.mkdirP(cachePath) | ||
| await io.mkdirP(tempPath) | ||
| }) | ||
|
|
||
| afterAll(async function() { | ||
| await io.rmRF(tempPath) | ||
| await io.rmRF(cachePath) | ||
| }) | ||
|
|
||
| it('downloads a 35 byte file', async () => { | ||
| const downPath: string = await tc.downloadTool( | ||
| 'http://example.com/bytes/35' | ||
| ) | ||
|
|
||
| expect(fs.existsSync(downPath)).toBeTruthy() | ||
| expect(fs.statSync(downPath).size).toBe(35) | ||
| }) | ||
|
|
||
| it('downloads a 35 byte file after a redirect', async () => { | ||
| nock('http://example.com') | ||
| .get('/redirect-to') | ||
| .reply(303, undefined, { | ||
| location: 'http://example.com/bytes/35' | ||
| }) | ||
|
|
||
| const downPath: string = await tc.downloadTool( | ||
| 'http://example.com/redirect-to' | ||
| ) | ||
|
|
||
| expect(fs.existsSync(downPath)).toBeTruthy() | ||
| expect(fs.statSync(downPath).size).toBe(35) | ||
| }) | ||
|
|
||
| it('has status code in exception dictionary for HTTP error code responses', async () => { | ||
| nock('http://example.com') | ||
| .get('/bytes/bad') | ||
| .reply(400, { | ||
| username: 'bad', | ||
| password: 'file' | ||
| }) | ||
|
|
||
| expect.assertions(2) | ||
|
|
||
| try { | ||
| const errorCodeUrl = 'http://example.com/bytes/bad' | ||
| await tc.downloadTool(errorCodeUrl) | ||
| } catch (err) { | ||
| expect(err.toString()).toContain('Unexpected HTTP response: 400') | ||
| expect(err['httpStatusCode']).toBe(400) | ||
| } | ||
| }) | ||
|
|
||
| it('works with redirect code 302', async function() { | ||
| nock('http://example.com') | ||
| .get('/redirect-to') | ||
| .reply(302, undefined, { | ||
| location: 'http://example.com/bytes/35' | ||
| }) | ||
|
|
||
| const downPath: string = await tc.downloadTool( | ||
| 'http://example.com/redirect-to' | ||
| ) | ||
|
|
||
| expect(fs.existsSync(downPath)).toBeTruthy() | ||
| expect(fs.statSync(downPath).size).toBe(35) | ||
| }) | ||
|
|
||
| it('installs a binary tool and finds it', async () => { | ||
| const downPath: string = await tc.downloadTool( | ||
| 'http://example.com/bytes/35' | ||
| ) | ||
|
|
||
| expect(fs.existsSync(downPath)).toBeTruthy() | ||
|
|
||
| await tc.cacheFile(downPath, 'foo', 'foo', '1.1.0') | ||
|
|
||
| const toolPath: string = tc.find('foo', '1.1.0') | ||
| expect(fs.existsSync(toolPath)).toBeTruthy() | ||
| expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy() | ||
|
|
||
| const binaryPath: string = path.join(toolPath, 'foo') | ||
| expect(fs.existsSync(binaryPath)).toBeTruthy() | ||
| }) | ||
|
|
||
| if (IS_WINDOWS) { | ||
| it('installs a 7z and finds it', async () => { | ||
| const tempDir = path.join(__dirname, 'test-install-7z') | ||
| try { | ||
| await io.mkdirP(tempDir) | ||
|
|
||
| // copy the 7z file to the test dir | ||
| const _7zFile: string = path.join(tempDir, 'test.7z') | ||
| await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile) | ||
|
|
||
| // extract/cache | ||
| const extPath: string = await tc.extract7z(_7zFile) | ||
| await tc.cacheDir(extPath, 'my-7z-contents', '1.1.0') | ||
| const toolPath: string = tc.find('my-7z-contents', '1.1.0') | ||
|
|
||
| expect(fs.existsSync(toolPath)).toBeTruthy() | ||
| expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy() | ||
| expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt')) | ||
| ).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt')) | ||
| ).toBeTruthy() | ||
| } finally { | ||
| await io.rmRF(tempDir) | ||
| } | ||
| }) | ||
|
|
||
| it('extract 7z using custom 7z tool', async function() { | ||
| const tempDir = path.join( | ||
| __dirname, | ||
| 'test-extract-7z-using-custom-7z-tool' | ||
| ) | ||
| try { | ||
| await io.mkdirP(tempDir) | ||
| // create mock7zr.cmd | ||
| const mock7zrPath: string = path.join(tempDir, 'mock7zr.cmd') | ||
| fs.writeFileSync( | ||
| mock7zrPath, | ||
| [ | ||
| 'echo %* > "%~dp0mock7zr-args.txt"', | ||
| `"${path.join( | ||
| __dirname, | ||
| '..', | ||
| 'scripts', | ||
| 'externals', | ||
| '7zdec.exe' | ||
| )}" x %5` | ||
| ].join('\r\n') | ||
| ) | ||
|
|
||
| // copy the 7z file to the test dir | ||
| const _7zFile: string = path.join(tempDir, 'test.7z') | ||
| await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile) | ||
|
|
||
| // extract | ||
| const extPath: string = await tc.extract7z( | ||
| _7zFile, | ||
| undefined, | ||
| mock7zrPath | ||
| ) | ||
|
|
||
| expect(fs.existsSync(extPath)).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(tempDir, 'mock7zr-args.txt')) | ||
| ).toBeTruthy() | ||
| expect( | ||
| fs | ||
| .readFileSync(path.join(tempDir, 'mock7zr-args.txt')) | ||
| .toString() | ||
| .trim() | ||
| ).toBe(`x -bb1 -bd -sccUTF-8 ${_7zFile}`) | ||
| expect(fs.existsSync(path.join(extPath, 'file.txt'))).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(extPath, 'file-with-ç-character.txt')) | ||
| ).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(extPath, 'folder', 'nested-file.txt')) | ||
| ).toBeTruthy() | ||
| } finally { | ||
| await io.rmRF(tempDir) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| it('installs a zip and finds it', async () => { | ||
| const tempDir = path.join(__dirname, 'test-install-zip') | ||
| try { | ||
| await io.mkdirP(tempDir) | ||
|
|
||
| // stage the layout for a zip file: | ||
| // file.txt | ||
| // folder/nested-file.txt | ||
| const stagingDir = path.join(tempDir, 'zip-staging') | ||
| await io.mkdirP(path.join(stagingDir, 'folder')) | ||
| fs.writeFileSync(path.join(stagingDir, 'file.txt'), '') | ||
| fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '') | ||
|
|
||
| // create the zip | ||
| const zipFile = path.join(tempDir, 'test.zip') | ||
| await io.rmRF(zipFile) | ||
| if (IS_WINDOWS) { | ||
| const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes | ||
| const escapedZipFile = zipFile.replace(/'/g, "''") | ||
| const powershellPath = await io.which('powershell', true) | ||
| const args = [ | ||
| '-NoLogo', | ||
| '-Sta', | ||
| '-NoProfile', | ||
| '-NonInteractive', | ||
| '-ExecutionPolicy', | ||
| 'Unrestricted', | ||
| '-Command', | ||
| `$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')` | ||
| ] | ||
| await exec.exec(`"${powershellPath}"`, args) | ||
| } else { | ||
| const zipPath: string = path.join(__dirname, 'externals', 'zip') | ||
| await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {cwd: stagingDir}) | ||
| } | ||
|
|
||
| const extPath: string = await tc.extractZip(zipFile) | ||
| await tc.cacheDir(extPath, 'foo', '1.1.0') | ||
| const toolPath: string = tc.find('foo', '1.1.0') | ||
|
|
||
| expect(fs.existsSync(toolPath)).toBeTruthy() | ||
| expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy() | ||
| expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt')) | ||
| ).toBeTruthy() | ||
| } finally { | ||
| await io.rmRF(tempDir) | ||
| } | ||
| }) | ||
|
|
||
| it('installs a zip and extracts it to specified directory', async function() { | ||
| const tempDir = path.join(__dirname, 'test-install-zip') | ||
| try { | ||
| await io.mkdirP(tempDir) | ||
|
|
||
| // stage the layout for a zip file: | ||
| // file.txt | ||
| // folder/nested-file.txt | ||
| const stagingDir = path.join(tempDir, 'zip-staging') | ||
| await io.mkdirP(path.join(stagingDir, 'folder')) | ||
| fs.writeFileSync(path.join(stagingDir, 'file.txt'), '') | ||
| fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '') | ||
|
|
||
| // create the zip | ||
| const zipFile = path.join(tempDir, 'test.zip') | ||
| await io.rmRF(zipFile) | ||
| if (IS_WINDOWS) { | ||
| const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes | ||
| const escapedZipFile = zipFile.replace(/'/g, "''") | ||
| const powershellPath = await io.which('powershell', true) | ||
| const args = [ | ||
| '-NoLogo', | ||
| '-Sta', | ||
| '-NoProfile', | ||
| '-NonInteractive', | ||
| '-ExecutionPolicy', | ||
| 'Unrestricted', | ||
| '-Command', | ||
| `$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')` | ||
| ] | ||
| await exec.exec(`"${powershellPath}"`, args) | ||
| } else { | ||
| const zipPath = path.join(__dirname, 'externals', 'zip') | ||
| await exec.exec(zipPath, [zipFile, '-r', '.'], {cwd: stagingDir}) | ||
| } | ||
|
|
||
| const destDir = path.join(__dirname, 'unzip-dest') | ||
| await io.rmRF(destDir) | ||
| await io.mkdirP(destDir) | ||
| try { | ||
| const extPath: string = await tc.extractZip(zipFile, destDir) | ||
| await tc.cacheDir(extPath, 'foo', '1.1.0') | ||
| const toolPath: string = tc.find('foo', '1.1.0') | ||
| expect(fs.existsSync(toolPath)).toBeTruthy() | ||
| expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy() | ||
| expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy() | ||
| expect( | ||
| fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt')) | ||
| ).toBeTruthy() | ||
| } finally { | ||
| await io.rmRF(destDir) | ||
| } | ||
| } finally { | ||
| await io.rmRF(tempDir) | ||
| } | ||
| }) | ||
|
|
||
| it('works with a 502 temporary failure', async function() { | ||
| nock('http://example.com') | ||
| .get('/temp502') | ||
| .twice() | ||
| .reply(502, undefined) | ||
| nock('http://example.com') | ||
| .get('/temp502') | ||
| .reply(200, undefined) | ||
|
|
||
| const statusCodeUrl = 'http://example.com/temp502' | ||
| await tc.downloadTool(statusCodeUrl) | ||
| }) | ||
|
|
||
| it("doesn't retry 502s more than 3 times", async function() { | ||
| nock('http://example.com') | ||
| .get('/perm502') | ||
| .times(3) | ||
| .reply(502, undefined) | ||
|
|
||
| expect.assertions(1) | ||
|
|
||
| try { | ||
| const statusCodeUrl = 'http://example.com/perm502' | ||
| await tc.downloadTool(statusCodeUrl) | ||
| } catch (err) { | ||
| expect(err.toString()).toContain('502') | ||
| } | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.