-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: stage assets under .cdk.assets
To ensure that assets are available for the toolchain to deploy after the CDK app exists, the CLI will, by default, request that the app will stage the assets under the `.cdk.assets` directory (relative to working directory). The CDK will then *copy* all assets from their source locations to this staging directory and will refer to the staging location as the asset path. Assets will be stored using their content fingerprint (md5 hash) so they will never be copied twice unless they change. Fixes #1716 TODO: - [ ] docker assets - [ ] toolkit support - [ ] toolkit test
- Loading branch information
Elad Ben-Israel
committed
Apr 4, 2019
1 parent
a90130e
commit 96aa88b
Showing
24 changed files
with
740 additions
and
17 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
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,89 @@ | ||
import fs = require('fs'); | ||
import minimatch = require('minimatch'); | ||
import path = require('path'); | ||
import { FollowMode } from './follow-mode'; | ||
|
||
export interface CopyOptions { | ||
/** | ||
* @default External only follows symlinks that are external to the source directory | ||
*/ | ||
follow?: FollowMode; | ||
|
||
/** | ||
* glob patterns to exclude from the copy. | ||
*/ | ||
exclude?: string[]; | ||
} | ||
|
||
export function copyDirectory(srcDir: string, destDir: string, options: CopyOptions = { }, rootDir?: string) { | ||
const follow = options.follow !== undefined ? options.follow : FollowMode.External; | ||
const exclude = options.exclude || []; | ||
|
||
rootDir = rootDir || srcDir; | ||
|
||
if (!fs.statSync(srcDir).isDirectory()) { | ||
throw new Error(`${srcDir} is not a directory`); | ||
} | ||
|
||
const files = fs.readdirSync(srcDir); | ||
for (const file of files) { | ||
const sourceFilePath = path.join(srcDir, file); | ||
|
||
if (shouldExclude(path.relative(rootDir, sourceFilePath))) { | ||
continue; | ||
} | ||
|
||
const destFilePath = path.join(destDir, file); | ||
|
||
let stat: fs.Stats | undefined = follow === FollowMode.Always | ||
? fs.statSync(sourceFilePath) | ||
: fs.lstatSync(sourceFilePath); | ||
|
||
if (stat && stat.isSymbolicLink()) { | ||
const target = fs.readlinkSync(sourceFilePath); | ||
|
||
// determine if this is an external link (i.e. the target's absolute path | ||
// is outside of the root directory). | ||
const targetPath = path.normalize(path.resolve(srcDir, target)); | ||
const rootPath = path.normalize(rootDir); | ||
const external = !targetPath.startsWith(rootPath); | ||
|
||
if (follow === FollowMode.External && external) { | ||
stat = fs.statSync(sourceFilePath); | ||
} else { | ||
fs.symlinkSync(target, destFilePath); | ||
stat = undefined; | ||
} | ||
} | ||
|
||
if (stat && stat.isDirectory()) { | ||
fs.mkdirSync(destFilePath); | ||
copyDirectory(sourceFilePath, destFilePath, options, rootDir); | ||
stat = undefined; | ||
} | ||
|
||
if (stat && stat.isFile()) { | ||
fs.copyFileSync(sourceFilePath, destFilePath); | ||
stat = undefined; | ||
} | ||
} | ||
|
||
function shouldExclude(filePath: string): boolean { | ||
let excludeOutput = false; | ||
|
||
for (const pattern of exclude) { | ||
const negate = pattern.startsWith('!'); | ||
const match = minimatch(filePath, pattern, { matchBase: true, flipNegate: true }); | ||
|
||
if (!negate && match) { | ||
excludeOutput = true; | ||
} | ||
|
||
if (negate && match) { | ||
excludeOutput = false; | ||
} | ||
} | ||
|
||
return excludeOutput; | ||
} | ||
} |
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,86 @@ | ||
import crypto = require('crypto'); | ||
import fs = require('fs'); | ||
import path = require('path'); | ||
import { FollowMode } from './follow-mode'; | ||
|
||
const BUFFER_SIZE = 8 * 1024; | ||
|
||
export interface FingerprintOptions { | ||
/** | ||
* Extra information to encode into the fingerprint (e.g. build instructions | ||
* and other inputs) | ||
*/ | ||
extra?: string; | ||
|
||
/** | ||
* List of exclude patterns (see `CopyOptions`) | ||
* @default include all files | ||
*/ | ||
exclude?: string[]; | ||
|
||
/** | ||
* What to do when we encounter symlinks. | ||
* @default External only follows symlinks that are external to the source | ||
* directory | ||
*/ | ||
follow?: FollowMode; | ||
} | ||
|
||
/** | ||
* Produces fingerprint based on the contents of a single file or an entire directory tree. | ||
* | ||
* The fingerprint will also include: | ||
* 1. An extra string if defined in `options.extra`. | ||
* 2. The set of exclude patterns, if defined in `options.exclude` | ||
* 3. The symlink follow mode value. | ||
* | ||
* @param fileOrDirectory The directory or file to fingerprint | ||
* @param options Fingerprinting options | ||
*/ | ||
export function fingerprint(fileOrDirectory: string, options: FingerprintOptions = { }) { | ||
const follow = options.follow !== undefined ? options.follow : FollowMode.External; | ||
const hash = crypto.createHash('md5'); | ||
addToHash(fileOrDirectory); | ||
|
||
hash.update(`==follow==${follow}==\n\n`); | ||
|
||
if (options.extra) { | ||
hash.update(`==extra==${options.extra}==\n\n`); | ||
} | ||
|
||
for (const ex of options.exclude || []) { | ||
hash.update(`==exclude==${ex}==\n\n`); | ||
} | ||
|
||
return hash.digest('hex'); | ||
|
||
function addToHash(pathToAdd: string) { | ||
hash.update('==\n'); | ||
const relativePath = path.relative(fileOrDirectory, pathToAdd); | ||
hash.update(relativePath + '\n'); | ||
hash.update('~~~~~~~~~~~~~~~~~~\n'); | ||
const stat = fs.statSync(pathToAdd); | ||
|
||
if (stat.isSymbolicLink()) { | ||
const target = fs.readlinkSync(pathToAdd); | ||
hash.update(target); | ||
} else if (stat.isDirectory()) { | ||
for (const file of fs.readdirSync(pathToAdd)) { | ||
addToHash(path.join(pathToAdd, file)); | ||
} | ||
} else { | ||
const file = fs.openSync(pathToAdd, 'r'); | ||
const buffer = Buffer.alloc(BUFFER_SIZE); | ||
|
||
try { | ||
let bytesRead; | ||
do { | ||
bytesRead = fs.readSync(file, buffer, 0, BUFFER_SIZE, null); | ||
hash.update(buffer.slice(0, bytesRead)); | ||
} while (bytesRead === BUFFER_SIZE); | ||
} finally { | ||
fs.closeSync(file); | ||
} | ||
} | ||
} | ||
} |
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,29 @@ | ||
export enum FollowMode { | ||
/** | ||
* Never follow symlinks. | ||
*/ | ||
Never = 'never', | ||
|
||
/** | ||
* Materialize all symlinks, whether they are internal or external to the source directory. | ||
*/ | ||
Always = 'always', | ||
|
||
/** | ||
* Only follows symlinks that are external to the source directory. | ||
*/ | ||
External = 'external', | ||
|
||
// ----------------- TODO:::::::::::::::::::::::::::::::::::::::::::: | ||
/** | ||
* Forbids source from having any symlinks pointing outside of the source | ||
* tree. | ||
* | ||
* This is the safest mode of operation as it ensures that copy operations | ||
* won't materialize files from the user's file system. Internal symlinks are | ||
* not followed. | ||
* | ||
* If the copy operation runs into an external symlink, it will fail. | ||
*/ | ||
BlockExternal = 'internal-only', | ||
} |
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,3 @@ | ||
export * from './fingerprint'; | ||
export * from './follow-mode'; | ||
export * from './copy'; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.