forked from actions/toolkit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add HashFiles to the toolkit (actions#830)
* add hash files to the toolkit
- Loading branch information
Showing
4 changed files
with
200 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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,126 @@ | ||
import * as io from '../../io/src/io' | ||
import * as path from 'path' | ||
import {hashFiles} from '../src/glob' | ||
import {promises as fs} from 'fs' | ||
|
||
const IS_WINDOWS = process.platform === 'win32' | ||
|
||
/** | ||
* These test focus on the ability of globber to find files | ||
* and not on the pattern matching aspect | ||
*/ | ||
describe('globber', () => { | ||
beforeAll(async () => { | ||
await io.rmRF(getTestTemp()) | ||
}) | ||
|
||
it('basic hashfiles test', async () => { | ||
const root = path.join(getTestTemp(), 'basic-hashfiles') | ||
await fs.mkdir(path.join(root), {recursive: true}) | ||
await fs.writeFile(path.join(root, 'test.txt'), 'test file content') | ||
const hash = await hashFiles(`${root}/*`) | ||
expect(hash).toEqual( | ||
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
) | ||
}) | ||
|
||
it('basic hashfiles no match should return empty string', async () => { | ||
const root = path.join(getTestTemp(), 'empty-hashfiles') | ||
const hash = await hashFiles(`${root}/*`) | ||
expect(hash).toEqual('') | ||
}) | ||
|
||
it('followSymbolicLinks defaults to true', async () => { | ||
const root = path.join( | ||
getTestTemp(), | ||
'defaults-to-follow-symbolic-links-true' | ||
) | ||
await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
await fs.writeFile( | ||
path.join(root, 'realdir', 'file.txt'), | ||
'test file content' | ||
) | ||
await createSymlinkDir( | ||
path.join(root, 'realdir'), | ||
path.join(root, 'symDir') | ||
) | ||
const testPath = path.join(root, `symDir`) | ||
const hash = await hashFiles(testPath) | ||
expect(hash).toEqual( | ||
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
) | ||
}) | ||
|
||
it('followSymbolicLinks set to true', async () => { | ||
const root = path.join(getTestTemp(), 'set-to-true') | ||
await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content') | ||
await createSymlinkDir( | ||
path.join(root, 'realdir'), | ||
path.join(root, 'symDir') | ||
) | ||
const testPath = path.join(root, `symDir`) | ||
const hash = await hashFiles(testPath, {followSymbolicLinks: true}) | ||
expect(hash).toEqual( | ||
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273' | ||
) | ||
}) | ||
|
||
it('followSymbolicLinks set to false', async () => { | ||
// Create the following layout: | ||
// <root> | ||
// <root>/folder-a | ||
// <root>/folder-a/file | ||
// <root>/symDir -> <root>/folder-a | ||
const root = path.join(getTestTemp(), 'set-to-false') | ||
await fs.mkdir(path.join(root, 'realdir'), {recursive: true}) | ||
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content') | ||
await createSymlinkDir( | ||
path.join(root, 'realdir'), | ||
path.join(root, 'symDir') | ||
) | ||
const testPath = path.join(root, 'symdir') | ||
const hash = await hashFiles(testPath, {followSymbolicLinks: false}) | ||
expect(hash).toEqual('') | ||
}) | ||
|
||
it('multipath test basic', async () => { | ||
// Create the following layout: | ||
// <root> | ||
// <root>/folder-a | ||
// <root>/folder-a/file | ||
// <root>/symDir -> <root>/folder-a | ||
const root = path.join(getTestTemp(), 'set-to-false') | ||
await fs.mkdir(path.join(root, 'dir1'), {recursive: true}) | ||
await fs.mkdir(path.join(root, 'dir2'), {recursive: true}) | ||
await fs.writeFile( | ||
path.join(root, 'dir1', 'testfile1.txt'), | ||
'test file content' | ||
) | ||
await fs.writeFile( | ||
path.join(root, 'dir2', 'testfile2.txt'), | ||
'test file content' | ||
) | ||
const testPath = `${path.join(root, 'dir1')}\n${path.join(root, 'dir2')}` | ||
const hash = await hashFiles(testPath) | ||
expect(hash).toEqual( | ||
'4e911ea5824830b6a3ec096c7833d5af8381c189ffaa825c3503a5333a73eadc' | ||
) | ||
}) | ||
}) | ||
|
||
function getTestTemp(): string { | ||
return path.join(__dirname, '_temp', 'hash_files') | ||
} | ||
|
||
/** | ||
* Creates a symlink directory on OSX/Linux, and a junction point directory on Windows. | ||
* A symlink directory is not created on Windows since it requires an elevated context. | ||
*/ | ||
async function createSymlinkDir(real: string, link: string): Promise<void> { | ||
if (IS_WINDOWS) { | ||
await fs.symlink(real, link, 'junction') | ||
} else { | ||
await fs.symlink(real, link) | ||
} | ||
} |
This file contains 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
This file contains 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,12 @@ | ||
/** | ||
* Options to control globbing behavior | ||
*/ | ||
export interface HashFileOptions { | ||
/** | ||
* Indicates whether to follow symbolic links. Generally should set to false | ||
* when deleting files. | ||
* | ||
* @default true | ||
*/ | ||
followSymbolicLinks?: boolean | ||
} |
This file contains 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,42 @@ | ||
import * as crypto from 'crypto' | ||
import * as core from '@actions/core' | ||
import * as fs from 'fs' | ||
import * as stream from 'stream' | ||
import * as util from 'util' | ||
import * as path from 'path' | ||
import {Globber} from './glob' | ||
|
||
export async function hashFiles(globber: Globber): Promise<string> { | ||
let hasMatch = false | ||
const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd() | ||
const result = crypto.createHash('sha256') | ||
let count = 0 | ||
for await (const file of globber.globGenerator()) { | ||
core.debug(file) | ||
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) { | ||
core.debug(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`) | ||
continue | ||
} | ||
if (fs.statSync(file).isDirectory()) { | ||
core.debug(`Skip directory '${file}'.`) | ||
continue | ||
} | ||
const hash = crypto.createHash('sha256') | ||
const pipeline = util.promisify(stream.pipeline) | ||
await pipeline(fs.createReadStream(file), hash) | ||
result.write(hash.digest()) | ||
count++ | ||
if (!hasMatch) { | ||
hasMatch = true | ||
} | ||
} | ||
result.end() | ||
|
||
if (hasMatch) { | ||
core.debug(`Found ${count} files to hash.`) | ||
return result.digest('hex') | ||
} else { | ||
core.warning(`No matches found for glob`) | ||
return '' | ||
} | ||
} |