diff --git a/package.json b/package.json index 04fb80b659ee8..900c9ae1bd3c5 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "**/jszip/**", "@aws-cdk/cdk-assets-schema/semver", "@aws-cdk/cdk-assets-schema/semver/**", - "@aws-cdk/assets/minimatch", - "@aws-cdk/assets/minimatch/**", + "@aws-cdk/core/minimatch", + "@aws-cdk/core/minimatch/**", "@aws-cdk/aws-codepipeline-actions/case", "@aws-cdk/aws-codepipeline-actions/case/**", "@aws-cdk/aws-ecr-assets/minimatch", diff --git a/packages/@aws-cdk/assets/README.md b/packages/@aws-cdk/assets/README.md index c7d1ae9654b5c..6ebca5ae0d9e6 100644 --- a/packages/@aws-cdk/assets/README.md +++ b/packages/@aws-cdk/assets/README.md @@ -2,15 +2,11 @@ --- -![cdk-constructs: Experimental](https://img.shields.io/badge/cdk--constructs-experimental-important.svg?style=for-the-badge) +![Deprecated](https://img.shields.io/badge/deprecated-critical.svg?style=for-the-badge) -> The APIs of higher level constructs in this module are experimental and under active development. They are subject to non-backward compatible changes or removal in any future version. These are not subject to the [Semantic Versioning](https://semver.org/) model and breaking changes will be announced in the release notes. This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package. +> This API may emit warnings. Backward compatibility is not guaranteed. --- -This module includes core classes for to CDK assets, used for copying asset -files to a staging area. Most CDK users should not need to use the classes in -this package directly. - - +All types moved to @aws-cdk/core. \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/compat.ts b/packages/@aws-cdk/assets/lib/compat.ts new file mode 100644 index 0000000000000..07bd050d77f5d --- /dev/null +++ b/packages/@aws-cdk/assets/lib/compat.ts @@ -0,0 +1,17 @@ +import { SymlinkFollowMode } from '@aws-cdk/core'; +import { FollowMode } from './fs/follow-mode'; + +export function toSymlinkFollow(follow?: FollowMode): SymlinkFollowMode | undefined { + if (!follow) { + return undefined; + } + + switch (follow) { + case FollowMode.NEVER: return SymlinkFollowMode.NEVER; + case FollowMode.ALWAYS: return SymlinkFollowMode.ALWAYS; + case FollowMode.BLOCK_EXTERNAL: return SymlinkFollowMode.BLOCK_EXTERNAL; + case FollowMode.EXTERNAL: return SymlinkFollowMode.EXTERNAL; + default: + throw new Error(`unknown follow mode: ${follow}`); + } +} diff --git a/packages/@aws-cdk/assets/lib/fs/follow-mode.ts b/packages/@aws-cdk/assets/lib/fs/follow-mode.ts index 81f81a36eac24..5aca6f8446532 100644 --- a/packages/@aws-cdk/assets/lib/fs/follow-mode.ts +++ b/packages/@aws-cdk/assets/lib/fs/follow-mode.ts @@ -1,3 +1,7 @@ +/** + * Symlink follow mode. + * @deprecated see `core.SymlinkFollowMode` + */ export enum FollowMode { /** * Never follow symlinks. diff --git a/packages/@aws-cdk/assets/lib/fs/index.ts b/packages/@aws-cdk/assets/lib/fs/index.ts deleted file mode 100644 index a1a4c68a83cef..0000000000000 --- a/packages/@aws-cdk/assets/lib/fs/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './copy'; -export * from './fingerprint'; -export * from './follow-mode'; -export * from './options'; diff --git a/packages/@aws-cdk/assets/lib/fs/options.ts b/packages/@aws-cdk/assets/lib/fs/options.ts index 727da36568502..434dd091a7256 100644 --- a/packages/@aws-cdk/assets/lib/fs/options.ts +++ b/packages/@aws-cdk/assets/lib/fs/options.ts @@ -2,6 +2,7 @@ import { FollowMode } from './follow-mode'; /** * Obtains applied when copying directories into the staging location. + * @deprecated see `core.CopyOptions` */ export interface CopyOptions { /** @@ -21,6 +22,7 @@ export interface CopyOptions { /** * Options related to calculating source hash. + * @deprecated see `core.FingerprintOptions` */ export interface FingerprintOptions extends CopyOptions { /** diff --git a/packages/@aws-cdk/assets/lib/index.ts b/packages/@aws-cdk/assets/lib/index.ts index e2a67003867bd..c651e06cc2ac1 100644 --- a/packages/@aws-cdk/assets/lib/index.ts +++ b/packages/@aws-cdk/assets/lib/index.ts @@ -1,4 +1,4 @@ export * from './api'; export * from './fs/follow-mode'; export * from './fs/options'; -export * from './staging'; +export * from './staging'; \ No newline at end of file diff --git a/packages/@aws-cdk/assets/lib/staging.ts b/packages/@aws-cdk/assets/lib/staging.ts index a95f7e26107a8..87800ae8aa40f 100644 --- a/packages/@aws-cdk/assets/lib/staging.ts +++ b/packages/@aws-cdk/assets/lib/staging.ts @@ -1,92 +1,29 @@ -import { Construct, ISynthesisSession } from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; -import * as path from 'path'; -import { copyDirectory, fingerprint, FingerprintOptions } from './fs'; +import { AssetStaging, Construct } from '@aws-cdk/core'; +import { toSymlinkFollow } from './compat'; +import { FingerprintOptions } from './fs/options'; +/** + * Deprecated + * @deprecated use `core.AssetStagingProps` + */ export interface StagingProps extends FingerprintOptions { + /** + * Local file or directory to stage. + */ readonly sourcePath: string; } /** - * Stages a file or directory from a location on the file system into a staging - * directory. - * - * This is controlled by the context key 'aws:cdk:asset-staging' and enabled - * by the CLI by default in order to ensure that when the CDK app exists, all - * assets are available for deployment. Otherwise, if an app references assets - * in temporary locations, those will not be available when it exists (see - * https://github.com/aws/aws-cdk/issues/1716). - * - * The `stagedPath` property is a stringified token that represents the location - * of the file or directory after staging. It will be resolved only during the - * "prepare" stage and may be either the original path or the staged path - * depending on the context setting. - * - * The file/directory are staged based on their content hash (fingerprint). This - * means that only if content was changed, copy will happen. + * Deprecated + * @deprecated use `core.AssetStaging` */ -export class Staging extends Construct { - - /** - * The path to the asset (stringinfied token). - * - * If asset staging is disabled, this will just be the original path. - * If asset staging is enabled it will be the staged path. - */ - public readonly stagedPath: string; - - /** - * The path of the asset as it was referenced by the user. - */ - public readonly sourcePath: string; - - /** - * A cryptographic hash of the source document(s). - */ - public readonly sourceHash: string; - - private readonly fingerprintOptions: FingerprintOptions; - - private readonly relativePath?: string; - +export class Staging extends AssetStaging { constructor(scope: Construct, id: string, props: StagingProps) { - super(scope, id); - - this.sourcePath = props.sourcePath; - this.fingerprintOptions = props; - this.sourceHash = fingerprint(this.sourcePath, props); - - const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); - if (stagingDisabled) { - this.stagedPath = this.sourcePath; - } else { - this.relativePath = 'asset.' + this.sourceHash + path.extname(this.sourcePath); - this.stagedPath = this.relativePath; // always relative to outdir - } - } - - protected synthesize(session: ISynthesisSession) { - if (!this.relativePath) { - return; - } - - const targetPath = path.join(session.assembly.outdir, this.relativePath); - - // asset already staged - if (fs.existsSync(targetPath)) { - return; - } - - // copy file/directory to staging directory - const stat = fs.statSync(this.sourcePath); - if (stat.isFile()) { - fs.copyFileSync(this.sourcePath, targetPath); - } else if (stat.isDirectory()) { - fs.mkdirSync(targetPath); - copyDirectory(this.sourcePath, targetPath, this.fingerprintOptions); - } else { - throw new Error(`Unknown file type: ${this.sourcePath}`); - } + super(scope, id, { + sourcePath: props.sourcePath, + exclude: props.exclude, + extraHash: props.extraHash, + follow: toSymlinkFollow(props.follow), + }); } } diff --git a/packages/@aws-cdk/assets/package.json b/packages/@aws-cdk/assets/package.json index 28386204806c3..6d60dba8c1876 100644 --- a/packages/@aws-cdk/assets/package.json +++ b/packages/@aws-cdk/assets/package.json @@ -1,7 +1,7 @@ { "name": "@aws-cdk/assets", "version": "0.0.0", - "description": "Integration of CDK apps with local assets", + "description": "This module is deprecated. All types are now available under the core module", "main": "lib/index.js", "types": "lib/index.d.ts", "jsii": { @@ -64,7 +64,6 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/minimatch": "^3.0.3", "@types/nodeunit": "^0.0.30", "@types/sinon": "^9.0.0", "aws-cdk": "0.0.0", @@ -78,7 +77,6 @@ "dependencies": { "@aws-cdk/core": "0.0.0", "@aws-cdk/cx-api": "0.0.0", - "minimatch": "^3.0.4", "constructs": "^3.0.2" }, "homepage": "https://github.com/aws/aws-cdk", @@ -90,18 +88,8 @@ "engines": { "node": ">= 10.12.0" }, - "bundledDependencies": [ - "minimatch" - ], - "stability": "experimental", - "maturity": "experimental", - "awslint": { - "exclude": [ - "docs-public-apis:@aws-cdk/assets.StagingProps", - "docs-public-apis:@aws-cdk/assets.StagingProps.sourcePath", - "docs-public-apis:@aws-cdk/assets.FollowMode" - ] - }, + "stability": "deprecated", + "maturity": "deprecated", "awscdkio": { "announce": false } diff --git a/packages/@aws-cdk/assets/test/test.compat.ts b/packages/@aws-cdk/assets/test/test.compat.ts new file mode 100644 index 0000000000000..65b50d0b37daf --- /dev/null +++ b/packages/@aws-cdk/assets/test/test.compat.ts @@ -0,0 +1,15 @@ +import { SymlinkFollowMode } from '@aws-cdk/core'; +import { Test } from 'nodeunit'; +import { FollowMode } from '../lib'; +import { toSymlinkFollow } from '../lib/compat'; + +export = { + 'FollowMode compatibility'(test: Test) { + test.equal(toSymlinkFollow(undefined), null); + test.equal(toSymlinkFollow(FollowMode.ALWAYS), SymlinkFollowMode.ALWAYS); + test.equal(toSymlinkFollow(FollowMode.BLOCK_EXTERNAL), SymlinkFollowMode.BLOCK_EXTERNAL); + test.equal(toSymlinkFollow(FollowMode.EXTERNAL), SymlinkFollowMode.EXTERNAL); + test.equal(toSymlinkFollow(FollowMode.NEVER), SymlinkFollowMode.NEVER); + test.done(); + }, +}; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json index d5110f3790691..a90c9b3d75d66 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2-targets/package.json @@ -84,7 +84,6 @@ "pkglint": "0.0.0" }, "dependencies": { - "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", @@ -94,7 +93,6 @@ }, "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { - "@aws-cdk/assets": "0.0.0", "@aws-cdk/aws-ec2": "0.0.0", "@aws-cdk/aws-elasticloadbalancingv2": "0.0.0", "@aws-cdk/aws-iam": "0.0.0", diff --git a/packages/@aws-cdk/core/lib/asset-staging.ts b/packages/@aws-cdk/core/lib/asset-staging.ts new file mode 100644 index 0000000000000..0fb9dc3da8265 --- /dev/null +++ b/packages/@aws-cdk/core/lib/asset-staging.ts @@ -0,0 +1,98 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs'; +import * as path from 'path'; +import { Construct, ISynthesisSession } from './construct-compat'; +import { FileSystem, FingerprintOptions } from './fs'; + +/** + * Initialization properties for `AssetStaging`. + */ +export interface AssetStagingProps extends FingerprintOptions { + /** + * The source file or directory to copy from. + */ + readonly sourcePath: string; +} + +/** + * Stages a file or directory from a location on the file system into a staging + * directory. + * + * This is controlled by the context key 'aws:cdk:asset-staging' and enabled + * by the CLI by default in order to ensure that when the CDK app exists, all + * assets are available for deployment. Otherwise, if an app references assets + * in temporary locations, those will not be available when it exists (see + * https://github.com/aws/aws-cdk/issues/1716). + * + * The `stagedPath` property is a stringified token that represents the location + * of the file or directory after staging. It will be resolved only during the + * "prepare" stage and may be either the original path or the staged path + * depending on the context setting. + * + * The file/directory are staged based on their content hash (fingerprint). This + * means that only if content was changed, copy will happen. + */ +export class AssetStaging extends Construct { + + /** + * The path to the asset (stringinfied token). + * + * If asset staging is disabled, this will just be the original path. + * If asset staging is enabled it will be the staged path. + */ + public readonly stagedPath: string; + + /** + * The path of the asset as it was referenced by the user. + */ + public readonly sourcePath: string; + + /** + * A cryptographic hash of the source document(s). + */ + public readonly sourceHash: string; + + private readonly fingerprintOptions: FingerprintOptions; + + private readonly relativePath?: string; + + constructor(scope: Construct, id: string, props: AssetStagingProps) { + super(scope, id); + + this.sourcePath = props.sourcePath; + this.fingerprintOptions = props; + this.sourceHash = FileSystem.fingerprint(this.sourcePath, props); + + const stagingDisabled = this.node.tryGetContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); + if (stagingDisabled) { + this.stagedPath = this.sourcePath; + } else { + this.relativePath = 'asset.' + this.sourceHash + path.extname(this.sourcePath); + this.stagedPath = this.relativePath; // always relative to outdir + } + } + + protected synthesize(session: ISynthesisSession) { + if (!this.relativePath) { + return; + } + + const targetPath = path.join(session.assembly.outdir, this.relativePath); + + // asset already staged + if (fs.existsSync(targetPath)) { + return; + } + + // copy file/directory to staging directory + const stat = fs.statSync(this.sourcePath); + if (stat.isFile()) { + fs.copyFileSync(this.sourcePath, targetPath); + } else if (stat.isDirectory()) { + fs.mkdirSync(targetPath); + FileSystem.copyDirectory(this.sourcePath, targetPath, this.fingerprintOptions); + } else { + throw new Error(`Unknown file type: ${this.sourcePath}`); + } + } +} diff --git a/packages/@aws-cdk/assets/lib/fs/copy.ts b/packages/@aws-cdk/core/lib/fs/copy.ts similarity index 90% rename from packages/@aws-cdk/assets/lib/fs/copy.ts rename to packages/@aws-cdk/core/lib/fs/copy.ts index 01b6c19cc66a6..d4c2841feeb6b 100644 --- a/packages/@aws-cdk/assets/lib/fs/copy.ts +++ b/packages/@aws-cdk/core/lib/fs/copy.ts @@ -1,11 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import { FollowMode } from './follow-mode'; -import { CopyOptions } from './options'; +import { CopyOptions, SymlinkFollowMode } from './options'; import { shouldExclude, shouldFollow } from './utils'; export function copyDirectory(srcDir: string, destDir: string, options: CopyOptions = { }, rootDir?: string) { - const follow = options.follow !== undefined ? options.follow : FollowMode.EXTERNAL; + const follow = options.follow !== undefined ? options.follow : SymlinkFollowMode.EXTERNAL; const exclude = options.exclude || []; rootDir = rootDir || srcDir; @@ -24,7 +23,7 @@ export function copyDirectory(srcDir: string, destDir: string, options: CopyOpti const destFilePath = path.join(destDir, file); - let stat: fs.Stats | undefined = follow === FollowMode.ALWAYS + let stat: fs.Stats | undefined = follow === SymlinkFollowMode.ALWAYS ? fs.statSync(sourceFilePath) : fs.lstatSync(sourceFilePath); diff --git a/packages/@aws-cdk/assets/lib/fs/fingerprint.ts b/packages/@aws-cdk/core/lib/fs/fingerprint.ts similarity index 95% rename from packages/@aws-cdk/assets/lib/fs/fingerprint.ts rename to packages/@aws-cdk/core/lib/fs/fingerprint.ts index 1d75850a17e56..dcac8f5ba5fce 100644 --- a/packages/@aws-cdk/assets/lib/fs/fingerprint.ts +++ b/packages/@aws-cdk/core/lib/fs/fingerprint.ts @@ -1,8 +1,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; -import { FollowMode } from './follow-mode'; -import { FingerprintOptions } from './options'; +import { FingerprintOptions, SymlinkFollowMode } from './options'; import { shouldExclude, shouldFollow } from './utils'; const BUFFER_SIZE = 8 * 1024; @@ -24,7 +23,7 @@ const CTRL_ETX = '\x03'; export function fingerprint(fileOrDirectory: string, options: FingerprintOptions = { }) { const hash = crypto.createHash('sha256'); _hashField(hash, 'options.extra', options.extraHash || ''); - const follow = options.follow || FollowMode.EXTERNAL; + const follow = options.follow || SymlinkFollowMode.EXTERNAL; _hashField(hash, 'options.follow', follow); const rootDirectory = fs.statSync(fileOrDirectory).isDirectory() diff --git a/packages/@aws-cdk/core/lib/fs/index.ts b/packages/@aws-cdk/core/lib/fs/index.ts new file mode 100644 index 0000000000000..ac7f3c9d0f8da --- /dev/null +++ b/packages/@aws-cdk/core/lib/fs/index.ts @@ -0,0 +1,36 @@ +import { copyDirectory } from './copy'; +import { fingerprint } from './fingerprint'; +import { CopyOptions, FingerprintOptions } from './options'; + +export * from './options'; + +/** + * File system utilities. + */ +export class FileSystem { + /** + * Copies an entire directory structure. + * @param srcDir Source directory + * @param destDir Destination directory + * @param options options + * @param rootDir Root directory to calculate exclusions from + */ + public static copyDirectory(srcDir: string, destDir: string, options: CopyOptions = { }, rootDir?: string) { + return copyDirectory(srcDir, destDir, options, rootDir); + } + + /** + * 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 + */ + public static fingerprint(fileOrDirectory: string, options: FingerprintOptions = { }) { + return fingerprint(fileOrDirectory, options); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/core/lib/fs/options.ts b/packages/@aws-cdk/core/lib/fs/options.ts new file mode 100644 index 0000000000000..eef3fcc499b08 --- /dev/null +++ b/packages/@aws-cdk/core/lib/fs/options.ts @@ -0,0 +1,63 @@ +/** + * Determines how symlinks are followed. + */ +export enum SymlinkFollowMode { + /** + * 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', + + /** + * 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. + */ + BLOCK_EXTERNAL = 'internal-only', +} + +/** + * Obtains applied when copying directories into the staging location. + */ +export interface CopyOptions { + /** + * A strategy for how to handle symlinks. + * + * @default SymlinkFollowMode.NEVER + */ + readonly follow?: SymlinkFollowMode; + + /** + * Glob patterns to exclude from the copy. + * + * @default - nothing is excluded + */ + readonly exclude?: string[]; +} + +/** + * Options related to calculating source hash. + */ +export interface FingerprintOptions extends CopyOptions { + /** + * Extra information to encode into the fingerprint (e.g. build instructions + * and other inputs) + * + * @default - hash is only based on source content + */ + readonly extraHash?: string; +} diff --git a/packages/@aws-cdk/assets/lib/fs/utils.ts b/packages/@aws-cdk/core/lib/fs/utils.ts similarity index 83% rename from packages/@aws-cdk/assets/lib/fs/utils.ts rename to packages/@aws-cdk/core/lib/fs/utils.ts index 4e58be4e09866..5624c3448f5ef 100644 --- a/packages/@aws-cdk/assets/lib/fs/utils.ts +++ b/packages/@aws-cdk/core/lib/fs/utils.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as minimatch from 'minimatch'; import * as path from 'path'; -import { FollowMode } from './follow-mode'; +import { SymlinkFollowMode } from './options'; /** * Determines whether a given file should be excluded or not based on given @@ -40,15 +40,15 @@ export function shouldExclude(exclude: string[], filePath: string): boolean { * * @returns true if the link should be followed. */ -export function shouldFollow(mode: FollowMode, sourceRoot: string, realPath: string): boolean { +export function shouldFollow(mode: SymlinkFollowMode, sourceRoot: string, realPath: string): boolean { switch (mode) { - case FollowMode.ALWAYS: + case SymlinkFollowMode.ALWAYS: return fs.existsSync(realPath); - case FollowMode.EXTERNAL: + case SymlinkFollowMode.EXTERNAL: return !_isInternal() && fs.existsSync(realPath); - case FollowMode.BLOCK_EXTERNAL: + case SymlinkFollowMode.BLOCK_EXTERNAL: return _isInternal() && fs.existsSync(realPath); - case FollowMode.NEVER: + case SymlinkFollowMode.NEVER: return false; default: throw new Error(`Unsupported FollowMode: ${mode}`); diff --git a/packages/@aws-cdk/core/lib/index.ts b/packages/@aws-cdk/core/lib/index.ts index 828d9eca25057..5bec1208abd3e 100644 --- a/packages/@aws-cdk/core/lib/index.ts +++ b/packages/@aws-cdk/core/lib/index.ts @@ -43,6 +43,8 @@ export * from './assets'; export * from './tree'; +export * from './asset-staging'; +export * from './fs'; // WARNING: Should not be exported, but currently is because of a bug. See the // class description for more information. export * from './private/intrinsic'; diff --git a/packages/@aws-cdk/core/package.json b/packages/@aws-cdk/core/package.json index 57232866cdd3b..b3fce8771ae50 100644 --- a/packages/@aws-cdk/core/package.json +++ b/packages/@aws-cdk/core/package.json @@ -118,6 +118,11 @@ "build+test": "npm run build && npm test", "compat": "cdk-compat" }, + "cdk-build": { + "pre": [ + "rm -rf test/fs/fixtures && cd test/fs && tar -xzf fixtures.tar.gz" + ] + }, "nyc": { "statements": 55, "lines": 55, @@ -139,18 +144,24 @@ "@types/lodash": "^4.14.150", "@types/node": "^10.17.21", "@types/nodeunit": "^0.0.30", + "@types/minimatch": "^3.0.3", "cdk-build-tools": "0.0.0", "cfn2ts": "0.0.0", "fast-check": "^1.24.2", "lodash": "^4.17.15", "nodeunit": "^0.11.3", - "pkglint": "0.0.0" + "pkglint": "0.0.0", + "ts-mock-imports": "^1.3.0" }, "dependencies": { + "minimatch": "^3.0.4", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/cloud-assembly-schema": "0.0.0", "constructs": "^3.0.2" }, + "bundledDependencies": [ + "minimatch" + ], "homepage": "https://github.com/aws/aws-cdk", "peerDependencies": { "@aws-cdk/cx-api": "0.0.0", diff --git a/packages/@aws-cdk/core/test/fs/.gitignore b/packages/@aws-cdk/core/test/fs/.gitignore new file mode 100644 index 0000000000000..b13de5858771b --- /dev/null +++ b/packages/@aws-cdk/core/test/fs/.gitignore @@ -0,0 +1,3 @@ +# codepipeline looses symlinks so we bundled fixtures into a tarball +# and unpack in pre-build script (yak) +fixtures/** diff --git a/packages/@aws-cdk/core/test/fs/fixtures.tar.gz b/packages/@aws-cdk/core/test/fs/fixtures.tar.gz new file mode 100644 index 0000000000000..50f5ba0f4a259 Binary files /dev/null and b/packages/@aws-cdk/core/test/fs/fixtures.tar.gz differ diff --git a/packages/@aws-cdk/assets/test/fs/test.fs-copy.ts b/packages/@aws-cdk/core/test/fs/test.fs-copy.ts similarity index 84% rename from packages/@aws-cdk/assets/test/fs/test.fs-copy.ts rename to packages/@aws-cdk/core/test/fs/test.fs-copy.ts index 68cb3dc234d06..47b55842a90c4 100644 --- a/packages/@aws-cdk/assets/test/fs/test.fs-copy.ts +++ b/packages/@aws-cdk/core/test/fs/test.fs-copy.ts @@ -2,8 +2,7 @@ import * as fs from 'fs'; import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; -import { copyDirectory } from '../../lib/fs/copy'; -import { FollowMode } from '../../lib/fs/follow-mode'; +import { FileSystem, SymlinkFollowMode } from '../../lib/fs'; export = { 'Default: copies all files and subdirectories, with default follow mode is "External"'(test: Test) { @@ -11,7 +10,7 @@ export = { const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); // WHEN - copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir); + FileSystem.copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir); // THEN test.deepEqual(tree(outdir), [ @@ -34,8 +33,8 @@ export = { const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); // WHEN - copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { - follow: FollowMode.ALWAYS, + FileSystem.copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: SymlinkFollowMode.ALWAYS, }); // THEN @@ -59,8 +58,8 @@ export = { const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); // WHEN - copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { - follow: FollowMode.NEVER, + FileSystem.copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: SymlinkFollowMode.NEVER, }); // THEN @@ -82,8 +81,8 @@ export = { const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); // WHEN - copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { - follow: FollowMode.EXTERNAL, + FileSystem.copyDirectory(path.join(__dirname, 'fixtures', 'symlinks'), outdir, { + follow: SymlinkFollowMode.EXTERNAL, }); // THEN @@ -107,7 +106,7 @@ export = { const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); // WHEN - copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir, { + FileSystem.copyDirectory(path.join(__dirname, 'fixtures', 'test1'), outdir, { exclude: [ '*', '!subdir2', diff --git a/packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts b/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts similarity index 76% rename from packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts rename to packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts index ea6b10ec250f6..7be40d4b31ad4 100644 --- a/packages/@aws-cdk/assets/test/fs/test.fs-fingerprint.ts +++ b/packages/@aws-cdk/core/test/fs/test.fs-fingerprint.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import { Test } from 'nodeunit'; import * as os from 'os'; import * as path from 'path'; -import * as libfs from '../../lib/fs'; +import { FileSystem, SymlinkFollowMode } from '../../lib/fs'; export = { files: { @@ -18,9 +18,9 @@ export = { fs.writeFileSync(input3, content + '.'); // add one character, hash should be different // WHEN - const hash1 = libfs.fingerprint(input1); - const hash2 = libfs.fingerprint(input2); - const hash3 = libfs.fingerprint(input3); + const hash1 = FileSystem.fingerprint(input1); + const hash2 = FileSystem.fingerprint(input2); + const hash3 = FileSystem.fingerprint(input3); // THEN test.deepEqual(hash1, hash2); @@ -37,8 +37,8 @@ export = { fs.writeFileSync(input2, ''); // WHEN - const hash1 = libfs.fingerprint(input1); - const hash2 = libfs.fingerprint(input2); + const hash1 = FileSystem.fingerprint(input1); + const hash2 = FileSystem.fingerprint(input2); // THEN test.deepEqual(hash1, hash2); @@ -51,11 +51,11 @@ export = { // GIVEN const srcdir = path.join(__dirname, 'fixtures', 'symlinks'); const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); - libfs.copyDirectory(srcdir, outdir); + FileSystem.copyDirectory(srcdir, outdir); // WHEN - const hashSrc = libfs.fingerprint(srcdir); - const hashCopy = libfs.fingerprint(outdir); + const hashSrc = FileSystem.fingerprint(srcdir); + const hashCopy = FileSystem.fingerprint(outdir); // THEN test.deepEqual(hashSrc, hashCopy); @@ -66,13 +66,13 @@ export = { // GIVEN const srcdir = path.join(__dirname, 'fixtures', 'symlinks'); const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'copy-tests')); - libfs.copyDirectory(srcdir, outdir); + FileSystem.copyDirectory(srcdir, outdir); // WHEN - const hashSrc = libfs.fingerprint(srcdir, { exclude: ['*.ignoreme'] }); + const hashSrc = FileSystem.fingerprint(srcdir, { exclude: ['*.ignoreme'] }); fs.writeFileSync(path.join(outdir, `${hashSrc}.ignoreme`), 'Ignore me!'); - const hashCopy = libfs.fingerprint(outdir, { exclude: ['*.ignoreme'] }); + const hashCopy = FileSystem.fingerprint(outdir, { exclude: ['*.ignoreme'] }); // THEN test.deepEqual(hashSrc, hashCopy); @@ -83,14 +83,14 @@ export = { // GIVEN const srcdir = path.join(__dirname, 'fixtures', 'symlinks'); const cpydir = fs.mkdtempSync(path.join(os.tmpdir(), 'fingerprint-tests')); - libfs.copyDirectory(srcdir, cpydir); + FileSystem.copyDirectory(srcdir, cpydir); // be careful not to break a symlink fs.renameSync(path.join(cpydir, 'normal-dir', 'file-in-subdir.txt'), path.join(cpydir, 'move-me.txt')); // WHEN - const hashSrc = libfs.fingerprint(srcdir); - const hashCopy = libfs.fingerprint(cpydir); + const hashSrc = FileSystem.fingerprint(srcdir); + const hashCopy = FileSystem.fingerprint(cpydir); // THEN test.notDeepEqual(hashSrc, hashCopy); @@ -111,15 +111,15 @@ export = { // now dir2 contains a symlink to a file in dir1 // WHEN - const original = libfs.fingerprint(dir2); + const original = FileSystem.fingerprint(dir2); // now change the contents of the target fs.writeFileSync(target, 'changning you!'); - const afterChange = libfs.fingerprint(dir2); + const afterChange = FileSystem.fingerprint(dir2); // revert the content to original and expect hash to be reverted fs.writeFileSync(target, content); - const afterRevert = libfs.fingerprint(dir2); + const afterRevert = FileSystem.fingerprint(dir2); // THEN test.notDeepEqual(original, afterChange); @@ -139,15 +139,15 @@ export = { // now dir2 contains a symlink to a file in dir1 // WHEN - const original = libfs.fingerprint(dir2, { follow: libfs.FollowMode.NEVER }); + const original = FileSystem.fingerprint(dir2, { follow: SymlinkFollowMode.NEVER }); // now change the contents of the target fs.writeFileSync(target, 'changning you!'); - const afterChange = libfs.fingerprint(dir2, { follow: libfs.FollowMode.NEVER }); + const afterChange = FileSystem.fingerprint(dir2, { follow: SymlinkFollowMode.NEVER }); // revert the content to original and expect hash to be reverted fs.writeFileSync(target, content); - const afterRevert = libfs.fingerprint(dir2, { follow: libfs.FollowMode.NEVER }); + const afterRevert = FileSystem.fingerprint(dir2, { follow: SymlinkFollowMode.NEVER }); // THEN test.deepEqual(original, afterChange); @@ -163,8 +163,8 @@ export = { const options2 = {path: dir, exclude: ['**', '!otherfile.py'], sourcePath: dir}; // WHEN - const f1 = libfs.fingerprint(dir, options1); - const f2 = libfs.fingerprint(dir, options2); + const f1 = FileSystem.fingerprint(dir, options1); + const f2 = FileSystem.fingerprint(dir, options2); // THEN test.notDeepEqual(f1, f2); diff --git a/packages/@aws-cdk/assets/test/fs/test.utils.ts b/packages/@aws-cdk/core/test/fs/test.utils.ts similarity index 84% rename from packages/@aws-cdk/assets/test/fs/test.utils.ts rename to packages/@aws-cdk/core/test/fs/test.utils.ts index 3fe6ab55ab0ac..c8962c7f4022f 100644 --- a/packages/@aws-cdk/assets/test/fs/test.utils.ts +++ b/packages/@aws-cdk/core/test/fs/test.utils.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import { Test } from 'nodeunit'; import * as path from 'path'; import { ImportMock } from 'ts-mock-imports'; -import { FollowMode } from '../../lib/fs'; +import { SymlinkFollowMode } from '../../lib/fs'; import * as util from '../../lib/fs/utils'; export = { @@ -34,7 +34,7 @@ export = { const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', true); try { - test.ok(util.shouldFollow(FollowMode.ALWAYS, sourceRoot, linkTarget)); + test.ok(util.shouldFollow(SymlinkFollowMode.ALWAYS, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -47,7 +47,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', true); try { - test.ok(util.shouldFollow(FollowMode.ALWAYS, sourceRoot, linkTarget)); + test.ok(util.shouldFollow(SymlinkFollowMode.ALWAYS, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -60,7 +60,7 @@ export = { const linkTarget = path.join(sourceRoot, 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', false); try { - test.ok(!util.shouldFollow(FollowMode.ALWAYS, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.ALWAYS, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -73,7 +73,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', false); try { - test.ok(!util.shouldFollow(FollowMode.ALWAYS, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.ALWAYS, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -88,7 +88,7 @@ export = { const linkTarget = path.join(sourceRoot, 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync'); try { - test.ok(!util.shouldFollow(FollowMode.EXTERNAL, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.notCalled); test.done(); } finally { @@ -101,7 +101,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', true); try { - test.ok(util.shouldFollow(FollowMode.EXTERNAL, sourceRoot, linkTarget)); + test.ok(util.shouldFollow(SymlinkFollowMode.EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -114,7 +114,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', false); try { - test.ok(!util.shouldFollow(FollowMode.EXTERNAL, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -129,7 +129,7 @@ export = { const linkTarget = path.join(sourceRoot, 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', true); try { - test.ok(util.shouldFollow(FollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); + test.ok(util.shouldFollow(SymlinkFollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -142,7 +142,7 @@ export = { const linkTarget = path.join(sourceRoot, 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync', false); try { - test.ok(!util.shouldFollow(FollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.calledOnceWith(linkTarget)); test.done(); } finally { @@ -155,7 +155,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync'); try { - test.ok(!util.shouldFollow(FollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.BLOCK_EXTERNAL, sourceRoot, linkTarget)); test.ok(mockFsExists.notCalled); test.done(); } finally { @@ -170,7 +170,7 @@ export = { const linkTarget = path.join(sourceRoot, 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync'); try { - test.ok(!util.shouldFollow(FollowMode.NEVER, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.NEVER, sourceRoot, linkTarget)); test.ok(mockFsExists.notCalled); test.done(); } finally { @@ -183,7 +183,7 @@ export = { const linkTarget = path.join('alternate', 'referent'); const mockFsExists = ImportMock.mockFunction(fs, 'existsSync'); try { - test.ok(!util.shouldFollow(FollowMode.NEVER, sourceRoot, linkTarget)); + test.ok(!util.shouldFollow(SymlinkFollowMode.NEVER, sourceRoot, linkTarget)); test.ok(mockFsExists.notCalled); test.done(); } finally { diff --git a/packages/@aws-cdk/core/test/test.staging.ts b/packages/@aws-cdk/core/test/test.staging.ts new file mode 100644 index 0000000000000..3faeea3e95396 --- /dev/null +++ b/packages/@aws-cdk/core/test/test.staging.ts @@ -0,0 +1,77 @@ +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs'; +import { Test } from 'nodeunit'; +import * as path from 'path'; +import { App, AssetStaging, Stack } from '../lib'; + +export = { + 'base case'(test: Test) { + // GIVEN + const stack = new Stack(); + const sourcePath = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 's1', { sourcePath }); + + test.deepEqual(staging.sourceHash, '2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00'); + test.deepEqual(staging.sourcePath, sourcePath); + test.deepEqual(stack.resolve(staging.stagedPath), 'asset.2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00'); + test.done(); + }, + + 'staging can be disabled through context'(test: Test) { + // GIVEN + const stack = new Stack(); + stack.node.setContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT, true); + const sourcePath = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 's1', { sourcePath }); + + test.deepEqual(staging.sourceHash, '2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00'); + test.deepEqual(staging.sourcePath, sourcePath); + test.deepEqual(stack.resolve(staging.stagedPath), sourcePath); + test.done(); + }, + + 'files are copied to the output directory during synth'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + const file = path.join(__dirname, 'fs', 'fixtures.tar.gz'); + + // WHEN + new AssetStaging(stack, 's1', { sourcePath: directory }); + new AssetStaging(stack, 'file', { sourcePath: file }); + + // THEN + const assembly = app.synth(); + test.deepEqual(fs.readdirSync(assembly.directory), [ + 'asset.2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00', + 'asset.af10ac04b3b607b0f8659c8f0cee8c343025ee75baf0b146f10f0e5311d2c46b.gz', + 'cdk.out', + 'manifest.json', + 'stack.template.json', + 'tree.json', + ]); + test.done(); + }, + + 'allow specifying extra data to include in the source hash'(test: Test) { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const withoutExtra = new AssetStaging(stack, 'withoutExtra', { sourcePath: directory }); + const withExtra = new AssetStaging(stack, 'withExtra', { sourcePath: directory, extraHash: 'boom' }); + + // THEN + test.notEqual(withoutExtra.sourceHash, withExtra.sourceHash); + test.deepEqual(withoutExtra.sourceHash, '2f37f937c51e2c191af66acf9b09f548926008ec68c575bd2ee54b6e997c0e00'); + test.deepEqual(withExtra.sourceHash, 'c95c915a5722bb9019e2c725d11868e5a619b55f36172f76bcbcaa8bb2d10c5f'); + test.done(); + }, +}; diff --git a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts index 41d38f04e198b..12ca6896ea4c5 100644 --- a/packages/aws-cdk/lib/api/aws-auth/account-cache.ts +++ b/packages/aws-cdk/lib/api/aws-auth/account-cache.ts @@ -94,7 +94,7 @@ export class AccountAccessKeyCache { } catch (e) { // File doesn't exist or file/dir isn't writable. This is a cache, // if we can't write it then too bad. - if (e.code === 'ENOENT' || e.code === 'EACCES') { return; } + if (e.code === 'ENOENT' || e.code === 'EACCES' || e.code === 'EROFS') { return; } throw e; } } diff --git a/tools/cdk-build-tools/config/nyc.config.js b/tools/cdk-build-tools/config/nyc.config.js index 7704b5dc47e27..985320565879e 100644 --- a/tools/cdk-build-tools/config/nyc.config.js +++ b/tools/cdk-build-tools/config/nyc.config.js @@ -24,7 +24,9 @@ module.exports = { "examples/**", "lambda-packages/**", "lib/*.generated.js", - "build-tools/**" + "build-tools/**", + ".eslintrc.js", + "nyc.config.js" ], // Configuration in package.json supercedes that of the defaults above. ...nycConfig,