Skip to content
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

feat(core): customize bundling output packaging #13076

Merged
merged 20 commits into from
Feb 17, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,26 @@ new assets.Asset(this, 'BundledAsset', {
Although optional, it's recommended to provide a local bundling method which can
greatly improve performance.

If the bundling output contains a single archive file (zip or jar) it will be uploaded
to S3 as-is and will not be zipped. Otherwise it will be zipped.
jogold marked this conversation as resolved.
Show resolved Hide resolved

This behavior can be changed by setting the `bundling.packaging` option:

```ts
const asset = new assets.Asset(this, 'BundledAsset', {
path: '/path/to/asset',
bundling: {
image: BundlingDockerImage.fromRegistry('alpine'),
command: ['command-that-produces-an-archive.sh'],
],
packaging: BundlePackaging.ALWAYS_ZIP, // Bundling output will be zipped even though it produces a single archive file.
},
jogold marked this conversation as resolved.
Show resolved Hide resolved
});
```

Use `BundlePackaging.NEVER_ZIP` if the bundling output contains a single file and you don't
want it to be zippped.

## CloudFormation Resource Metadata

> NOTE: This section is relevant for authors of AWS Resource Constructs.
Expand Down
30 changes: 3 additions & 27 deletions packages/@aws-cdk/aws-s3-assets/lib/asset.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as fs from 'fs';
import * as path from 'path';
import * as assets from '@aws-cdk/assets';
import * as iam from '@aws-cdk/aws-iam';
Expand All @@ -13,8 +12,6 @@ import { toSymlinkFollow } from './compat';
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct as CoreConstruct } from '@aws-cdk/core';

const ARCHIVE_EXTENSIONS = ['.zip', '.jar'];

export interface AssetOptions extends assets.CopyOptions, cdk.AssetOptions {
/**
* A list of principals that should be able to read this asset from S3.
Expand Down Expand Up @@ -139,17 +136,12 @@ export class Asset extends CoreConstruct implements cdk.IAsset {

this.assetPath = staging.relativeStagedPath(stack);

const packaging = determinePackaging(staging.sourcePath);

this.isFile = packaging === cdk.FileAssetPackaging.FILE;
this.isFile = staging.packaging === cdk.FileAssetPackaging.FILE;

// sets isZipArchive based on the type of packaging and file extension
this.isZipArchive = packaging === cdk.FileAssetPackaging.ZIP_DIRECTORY
? true
: ARCHIVE_EXTENSIONS.some(ext => staging.sourcePath.toLowerCase().endsWith(ext));
this.isZipArchive = staging.isArchive;

const location = stack.synthesizer.addFileAsset({
packaging,
packaging: staging.packaging,
sourceHash: this.sourceHash,
fileName: this.assetPath,
});
Expand Down Expand Up @@ -210,19 +202,3 @@ export class Asset extends CoreConstruct implements cdk.IAsset {
this.bucket.grantRead(grantee);
}
}

function determinePackaging(assetPath: string): cdk.FileAssetPackaging {
if (!fs.existsSync(assetPath)) {
throw new Error(`Cannot find asset at ${assetPath}`);
}

if (fs.statSync(assetPath).isDirectory()) {
return cdk.FileAssetPackaging.ZIP_DIRECTORY;
}

if (fs.statSync(assetPath).isFile()) {
return cdk.FileAssetPackaging.FILE;
}

throw new Error(`Asset ${assetPath} is expected to be either a directory or a regular file`);
}
73 changes: 68 additions & 5 deletions packages/@aws-cdk/core/lib/asset-staging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';
import * as fs from 'fs-extra';
import * as minimatch from 'minimatch';
import { AssetHashType, AssetOptions } from './assets';
import { BundlingOptions } from './bundling';
import { AssetHashType, AssetOptions, FileAssetPackaging } from './assets';
import { BundlingOptions, BundlePackaging } from './bundling';
import { FileSystem, FingerprintOptions } from './fs';
import { Names } from './names';
import { Cache } from './private/cache';
Expand All @@ -17,6 +17,8 @@ import { Stage } from './stage';
// eslint-disable-next-line
import { Construct as CoreConstruct } from './construct-compat';

const ARCHIVE_EXTENSIONS = ['.zip', '.jar'];

/**
* A previously staged asset
*/
Expand Down Expand Up @@ -138,6 +140,9 @@ export class AssetStaging extends CoreConstruct {

private readonly cacheKey: string;

private _packaging = FileAssetPackaging.ZIP_DIRECTORY;
private _isArchive = true;

constructor(scope: Construct, id: string, props: AssetStagingProps) {
super(scope, id);

Expand Down Expand Up @@ -203,6 +208,20 @@ export class AssetStaging extends CoreConstruct {
return this.assetHash;
}

/**
* How this asset should be packaged.
*/
public get packaging(): FileAssetPackaging {
return this._packaging;
}

/**
* Whether this asset is an archive (zip or jar).
*/
public get isArchive(): boolean {
return this._isArchive;
}

/**
* Return the path to the staged asset, relative to the Cloud Assembly (manifest) directory of the given stack
*
Expand Down Expand Up @@ -281,11 +300,34 @@ export class AssetStaging extends CoreConstruct {
const bundleDir = this.determineBundleDir(this.assetOutdir, assetHash);
this.bundle(bundling, bundleDir);

let assetPath = bundleDir;
let extension: string | undefined;

const packaging = bundling.packaging ?? BundlePackaging.AUTO;
if (packaging !== BundlePackaging.ALWAYS_ZIP) {
eladb marked this conversation as resolved.
Show resolved Hide resolved
// Check whether bundling resulted in a single file
const bundledFile = singleFile(bundleDir);
eladb marked this conversation as resolved.
Show resolved Hide resolved
if (packaging === BundlePackaging.NEVER_ZIP && !bundledFile) {
throw new Error('Packaging was set to `NEVER_ZIP` but the bundling output did not produce a single file.');
eladb marked this conversation as resolved.
Show resolved Hide resolved
}

if (bundledFile) { // It's a single file
const bundledExtension = path.extname(bundledFile).toLowerCase();
const isSingleArchive = ARCHIVE_EXTENSIONS.includes(bundledExtension);
if (packaging === BundlePackaging.NEVER_ZIP || (packaging === BundlePackaging.AUTO && isSingleArchive)) {
this._packaging = FileAssetPackaging.FILE;
this._isArchive = isSingleArchive;
assetPath = bundledFile;
extension = bundledExtension;
}
}
}

// Calculate assetHash afterwards if we still must
assetHash = assetHash ?? this.calculateHash(this.hashType, bundling, bundleDir);
const stagedPath = path.resolve(this.assetOutdir, renderAssetFilename(assetHash));
assetHash = assetHash ?? this.calculateHash(this.hashType, bundling, assetPath);
const stagedPath = path.resolve(this.assetOutdir, renderAssetFilename(assetHash, extension));

this.stageAsset(bundleDir, stagedPath, 'move');
this.stageAsset(assetPath, stagedPath, 'move');
return { assetHash, stagedPath };
}

Expand Down Expand Up @@ -323,6 +365,8 @@ export class AssetStaging extends CoreConstruct {
const stat = fs.statSync(sourcePath);
if (stat.isFile()) {
fs.copyFileSync(sourcePath, targetPath);
this._packaging = FileAssetPackaging.FILE;
this._isArchive = ARCHIVE_EXTENSIONS.includes(path.extname(sourcePath).toLowerCase());
} else if (stat.isDirectory()) {
fs.mkdirSync(targetPath);
FileSystem.copyDirectory(sourcePath, targetPath, this.fingerprintOptions);
Expand Down Expand Up @@ -502,3 +546,22 @@ function sortObject(object: { [key: string]: any }): { [key: string]: any } {
}
return ret;
}

/**
* Returns the single file of a directory or undefined
*/
function singleFile(directory: string): string | undefined {
if (!fs.statSync(directory).isDirectory()) {
eladb marked this conversation as resolved.
Show resolved Hide resolved
return undefined;
}

const content = fs.readdirSync(directory);
if (content.length === 1) {
const file = path.join(directory, content[0]);
if (fs.statSync(file).isFile()) {
return file;
}
}

return undefined;
}
34 changes: 34 additions & 0 deletions packages/@aws-cdk/core/lib/bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ export interface BundlingOptions {
* @experimental
*/
readonly local?: ILocalBundling;

/**
* The type of packaging to apply on the bundling output.
*
* @default BundlePackaging.AUTO
*
* @experimental
*/
readonly packaging?: BundlePackaging;
}

/**
* The type of packaging to apply on the bundled asset.
*
* @experimental
*/
export enum BundlePackaging {
eladb marked this conversation as resolved.
Show resolved Hide resolved
/**
* The bundling output will always be zipped and uploaded to S3, regardless
* of its content.
*/
ALWAYS_ZIP = 'zip',

/**
* The bundling output will not be zipped. Bundling will fail if the bundling
* output doesn't contain a single file.
eladb marked this conversation as resolved.
Show resolved Hide resolved
*/
NEVER_ZIP = 'no-zip',

/**
* If the bundling output contains a single archive file (zip or jar) it will
jogold marked this conversation as resolved.
Show resolved Hide resolved
* not be zipped. Otherwise it will be zipped.
*/
AUTO = 'auto'
}

/**
Expand Down
Empty file.
15 changes: 14 additions & 1 deletion packages/@aws-cdk/core/test/docker-stub.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,18 @@ if echo "$@" | grep "DOCKER_STUB_SUCCESS"; then
exit 0
fi

echo "Docker mock only supports one of the following commands: DOCKER_STUB_SUCCESS_NO_OUTPUT,DOCKER_STUB_FAIL,DOCKER_STUB_SUCCESS"
if echo "$@" | grep "DOCKER_STUB_MULTIPLE_FILES"; then
outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1)
touch ${outdir}/test1.txt
touch ${outdir}/test2.txt
exit 0
fi

if echo "$@" | grep "DOCKER_STUB_SINGLE_ARCHIVE"; then
outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1)
touch ${outdir}/test.zip
exit 0
fi

echo "Docker mock only supports one of the following commands: DOCKER_STUB_SUCCESS_NO_OUTPUT,DOCKER_STUB_FAIL,DOCKER_STUB_SUCCESS,DOCKER_STUB_MULTIPLE_FILES,DOCKER_SINGLE_ARCHIVE"
exit 1
Loading