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(lambda-layer-awscli): Dynamically load asset for AwsCliLayer, with bundled fallback #21938

Closed
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
217a199
feat(lambda): add Code.fromAssetConstruct API for creating Code objec…
madeline-k Sep 7, 2022
3819c26
add to README
madeline-k Sep 7, 2022
6d45a8f
feat: Dynamically load asset for AwsCliLayer, with bundled fallback
madeline-k Sep 7, 2022
7e68272
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k Sep 20, 2022
c244bc8
Revert "feat(lambda): add Code.fromAssetConstruct API for creating Co…
madeline-k Sep 20, 2022
a829e28
Revert "add to README"
madeline-k Sep 20, 2022
b01414b
update to use latest name and api from @aws-cdk/asset-awscli-v1 package
madeline-k Sep 20, 2022
dcaf2ce
temporarily adjust coverage thresholds so that jest succeeds, and the…
madeline-k Sep 20, 2022
5a35d5a
wip
madeline-k Sep 21, 2022
da1f1ba
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k Sep 21, 2022
1f856c7
updates
madeline-k Sep 27, 2022
f0376b4
re-add original zip generation
madeline-k Sep 28, 2022
403313a
download zip from the external npm package, and fallback to use that
madeline-k Sep 29, 2022
18fc81c
refactor to use a slightly more functional pattern. This should be mo…
madeline-k Sep 29, 2022
9312860
remove saving state in static variable
madeline-k Sep 29, 2022
3f901f1
update jest config to raise coverage thresholds again
madeline-k Sep 29, 2022
42b84ed
make some static methods public and @internal so they can be tested
kaizencc Sep 29, 2022
f236539
add quiet option on awscli layer
kaizencc Oct 4, 2022
0f0a89e
include requirements.txt in the directory that is used to generate th…
madeline-k Oct 11, 2022
19b9c8b
some logging updates
madeline-k Oct 11, 2022
a14b01a
update logging, tests, and add a construct for the Cli Notice
madeline-k Oct 11, 2022
7c7083f
add README troubleshooting instructions
madeline-k Oct 11, 2022
a5e37e7
package.json updates
madeline-k Oct 11, 2022
c9b8740
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k Oct 11, 2022
4e84738
update yarn.lock to use 2.0.0, linter fixes in README, set CDK_DEBUG …
madeline-k Oct 11, 2022
6d5dd99
add set -eu
madeline-k Oct 11, 2022
7657203
modify pre-build script to copy files from local node_modules
madeline-k Oct 12, 2022
bda4f88
refactor functions into a separate file so they can be private and us…
madeline-k Oct 12, 2022
8c2edba
more updates
madeline-k Oct 12, 2022
b2af0c6
get target version at build time
madeline-k Oct 12, 2022
01f194e
update warning message
madeline-k Oct 12, 2022
4b11d81
remove WARNING from the warning. It's a warning.
madeline-k Oct 12, 2022
a5e35f6
fix comma placement, and add real github issue link
madeline-k Oct 12, 2022
7415ae8
use semver
madeline-k Oct 12, 2022
729f9cb
yarn build --fix
madeline-k Oct 12, 2022
9227e93
fix string match in test
madeline-k Oct 12, 2022
af77de9
use compatible semver range
madeline-k Oct 12, 2022
33df916
update asset-awscli dependency, add error handling for mkdirSync
madeline-k Oct 12, 2022
70d9fb7
remove requirement.txt from being tracked by git, since it is now gen…
madeline-k Oct 12, 2022
b9d5bd8
temporarily lower coverage thresholds
madeline-k Oct 12, 2022
2ab6282
fix: remove node_modules folder after test adds it in
kaizencc Oct 13, 2022
e97124c
fix download location to avoid duplicate node_modules folders
kaizencc Oct 13, 2022
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
25 changes: 25 additions & 0 deletions packages/@aws-cdk/cx-api/lib/directories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as fs from 'fs';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relocating this file from the CLI package (aws-cdk), so that it can be re-used in the CLI and the Framework. Let me know if there is a better location for this!

import * as os from 'os';
import * as path from 'path';

/**
* Return a location that will be used as the CDK home directory.
* Currently the only thing that is placed here is the cache.
*
* First try to use the users home directory (i.e. /home/someuser/),
* but if that directory does not exist for some reason create a tmp directory.
*
* Typically it wouldn't make sense to create a one time use tmp directory for
* the purpose of creating a cache, but since this only applies to users that do
* not have a home directory (some CI systems?) this should be fine.
*/
export function cdkHomeDir() {
const tmpDir = fs.realpathSync(os.tmpdir());
let home;
try {
home = path.join((os.userInfo().homedir ?? os.homedir()).trim(), '.cdk');
} catch {}
return process.env.CDK_HOME
? path.resolve(process.env.CDK_HOME)
: home || fs.mkdtempSync(path.join(tmpDir, '.cdk')).trim();
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/cx-api/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ export * from './metadata';
export * from './features';
export * from './placeholders';
export * from './app';
export * from './directories';
9 changes: 8 additions & 1 deletion packages/@aws-cdk/lambda-layer-awscli/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config');
module.exports = baseConfig;
module.exports = {
...baseConfig,
coverageThreshold: {
global: {
branches: 60,
},
},
};
1 change: 0 additions & 1 deletion packages/@aws-cdk/lambda-layer-awscli/layer/.dockerignore

This file was deleted.

54 changes: 0 additions & 54 deletions packages/@aws-cdk/lambda-layer-awscli/layer/Dockerfile

This file was deleted.

18 changes: 0 additions & 18 deletions packages/@aws-cdk/lambda-layer-awscli/layer/build.sh

This file was deleted.

20 changes: 20 additions & 0 deletions packages/@aws-cdk/lambda-layer-awscli/layer/download-fallback.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
madeline-k marked this conversation as resolved.
Show resolved Hide resolved

# Get the version to use from the package.json devDependencies
lineWithPackageVersion=$(grep '@aws-cdk/asset-awscli-v1' ./package.json)
version=$(echo $lineWithPackageVersion | cut -d '"' -f 4)

echo "Downloading @aws-cdk/asset-awscli-v1@$version from npm"
npm pack @aws-cdk/asset-awscli-v1@$version -q
madeline-k marked this conversation as resolved.
Show resolved Hide resolved

echo "Extracting layer.zip from aws-cdk-asset-awscli-v1-$version.tgz"
tar -zxvf aws-cdk-asset-awscli-v1-$version.tgz package/lib/layer.zip

echo "Copying layer.zip to ./layer"
mv ./package/lib/layer.zip ./lib/

echo "Cleaning up"
rm aws-cdk-asset-awscli-v1-$version.tgz
rm -r ./package

echo "download-fallback.sh complete"

This file was deleted.

139 changes: 135 additions & 4 deletions packages/@aws-cdk/lambda-layer-awscli/lib/awscli-layer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,150 @@
/* eslint-disable no-console */
import * as childproc from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as lambda from '@aws-cdk/aws-lambda';
import { FileSystem } from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import { Construct } from 'constructs';

/**
* Options for AwsCliLayer
*/
export interface AwsCliLayerProps {
/**
* Filter out logging statements.
*
* @default true
*/
readonly quiet?: boolean;
}

/**
* An AWS Lambda layer that includes the AWS CLI.
*/
export class AwsCliLayer extends lambda.LayerVersion {
constructor(scope: Construct, id: string) {
super(scope, id, {
code: lambda.Code.fromAsset(path.join(__dirname, 'layer.zip'), {
/**
* @internal
*/
public static _tryLoadPackage(targetVersion: string, log: (s: any) => void): any {
let availableVersion;
try {
const assetPackagePath = require.resolve(`${AwsCliLayer.assetPackageName}`);
availableVersion = AwsCliLayer.requireWrapper(path.join(assetPackagePath, '../../package.json'), log).version;
} catch (err) {
log('require.resolve error');
}

if (targetVersion === availableVersion) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care exactly? Do we need semver in here to check range compatibility?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to log if the version is there but we rejected it btw. That's good info to know.

return AwsCliLayer.requireWrapper(AwsCliLayer.assetPackageName, log);
}
}

/**
* @internal
*/
public static _downloadPackage(targetVersion: string, log: (s: string) => void): string | undefined {
const cdkHomeDir = cxapi.cdkHomeDir();
const downloadDir = path.join(cdkHomeDir, 'npm-cache');
const downloadPath = path.join(downloadDir, `${AwsCliLayer.assetPackageNpmTarPrefix}${targetVersion}.tgz`);
log(downloadPath);

if (fs.existsSync(downloadPath)) {
return downloadPath;
}
childproc.execSync(`mkdir -p ${downloadDir}; cd ${downloadDir}; npm pack ${AwsCliLayer.assetPackageName}@${targetVersion} -q`);
madeline-k marked this conversation as resolved.
Show resolved Hide resolved
if (fs.existsSync(downloadPath)) {
return downloadPath;
}

return undefined;
}

private static readonly assetPackageName: string = '@aws-cdk/asset-awscli-v1';
private static readonly assetPackageNpmTarPrefix: string = 'aws-cdk-asset-awscli-v1-';

private static requireWrapper(id: string, log: (s: any) => void): any {
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require(id);
} catch (err) {
log(`require('${id}') failed`);
log(err);
if (err instanceof Error) {
console.error(err.name, err.message.split('\n')[0]);
}
}
}

private static installAndLoadPackage(from: string, log: (s: any) => void): any {
const installDir = AwsCliLayer.findInstallDir(log);
if (!installDir) {
return;
}
log(`install dir: ${installDir}`);
childproc.execSync(`npm install ${from} --no-save --prefix ${installDir} -q`);
return AwsCliLayer.requireWrapper(path.join(installDir, 'node_modules', AwsCliLayer.assetPackageName, 'lib/index.js'), log);
}

private static findInstallDir(log: (s: any) => void): string | undefined {
if (!require.main?.paths) {
return undefined;
}
for (let p of require.main.paths) {
log(`p: ${p}`);
if (fs.existsSync(p)) {
return p;
}
}
return undefined;
}

constructor(scope: Construct, id: string, props?: AwsCliLayerProps) {
const quiet = props?.quiet ?? true;
const log = (s: any) => {
if (!quiet) {
console.log(s);
}
};

const targetVersion = AwsCliLayer.requireWrapper(path.join(__dirname, '../package.json'), log).devDependencies[AwsCliLayer.assetPackageName];

let assetPackage;

let downloadStyle = 'require';
log('trying regular require');
assetPackage = AwsCliLayer._tryLoadPackage(targetVersion, log);

if (!assetPackage) {
downloadStyle = 'dynamic';
log('trying to download package');
const downloadPath = AwsCliLayer._downloadPackage(targetVersion, log);
if (downloadPath) {
log('trying to load from install location');
assetPackage = AwsCliLayer.installAndLoadPackage(downloadPath, log);
}
}

let code;
if (assetPackage) {
const asset = new assetPackage.AwsCliAsset(scope, `${id}-asset`);
code = lambda.Code.fromBucket(asset.bucket, asset.s3ObjectKey);
} else {
downloadStyle = 'fallback';
log('using fallback to original version');
code = lambda.Code.fromAsset(path.join(__dirname, 'layer.zip'), {
// we hash the layer directory (it contains the tools versions and Dockerfile) because hashing the zip is non-deterministic
assetHash: FileSystem.fingerprint(path.join(__dirname, '../layer')),
}),
});
}

super(scope, id, {
code: code,
description: '/opt/awscli/aws',
});

if (downloadStyle === 'fallback') {
log('we used the fallback when creating this construct, so a marker construct should be added to the tree for CLI notices');
}
}
}
12 changes: 11 additions & 1 deletion packages/@aws-cdk/lambda-layer-awscli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,23 @@
"@aws-cdk/integ-runner": "0.0.0",
"@aws-cdk/integ-tests": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@aws-cdk/asset-awscli-v1": "0.0.13",
"@types/jest": "^27.5.2",
"jest": "^27.5.1"
},
"dependencies": {
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^10.0.0"
},
"homepage": "https://github.com/aws/aws-cdk",
"peerDependencies": {
"@aws-cdk/aws-lambda": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/cx-api": "0.0.0",
"constructs": "^10.0.0"
},
"engines": {
Expand All @@ -102,7 +107,7 @@
},
"cdk-build": {
"pre": [
"layer/build.sh"
"layer/download-fallback.sh"
],
"env": {
"AWSLINT_BASE_CONSTRUCT": true
Expand All @@ -120,5 +125,10 @@
"publishConfig": {
"tag": "latest"
},
"awslint": {
"exclude": [
"props-physical-name:@aws-cdk/lambda-layer-awscli.AwsCliLayerProps"
]
},
"private": true
}
Loading