-
Notifications
You must be signed in to change notification settings - Fork 4k
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
madeline-k
wants to merge
42
commits into
main
from
madeline-k/reduce-package-size/lambda-layer-awscli
Closed
Changes from all 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 3819c26
add to README
madeline-k 6d45a8f
feat: Dynamically load asset for AwsCliLayer, with bundled fallback
madeline-k 7e68272
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k c244bc8
Revert "feat(lambda): add Code.fromAssetConstruct API for creating Co…
madeline-k a829e28
Revert "add to README"
madeline-k b01414b
update to use latest name and api from @aws-cdk/asset-awscli-v1 package
madeline-k dcaf2ce
temporarily adjust coverage thresholds so that jest succeeds, and the…
madeline-k 5a35d5a
wip
madeline-k da1f1ba
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k 1f856c7
updates
madeline-k f0376b4
re-add original zip generation
madeline-k 403313a
download zip from the external npm package, and fallback to use that
madeline-k 18fc81c
refactor to use a slightly more functional pattern. This should be mo…
madeline-k 9312860
remove saving state in static variable
madeline-k 3f901f1
update jest config to raise coverage thresholds again
madeline-k 42b84ed
make some static methods public and @internal so they can be tested
kaizencc f236539
add quiet option on awscli layer
kaizencc 0f0a89e
include requirements.txt in the directory that is used to generate th…
madeline-k 19b9c8b
some logging updates
madeline-k a14b01a
update logging, tests, and add a construct for the Cli Notice
madeline-k 7c7083f
add README troubleshooting instructions
madeline-k a5e37e7
package.json updates
madeline-k c9b8740
Merge branch 'main' into madeline-k/reduce-package-size/lambda-layer-…
madeline-k 4e84738
update yarn.lock to use 2.0.0, linter fixes in README, set CDK_DEBUG …
madeline-k 6d5dd99
add set -eu
madeline-k 7657203
modify pre-build script to copy files from local node_modules
madeline-k bda4f88
refactor functions into a separate file so they can be private and us…
madeline-k 8c2edba
more updates
madeline-k b2af0c6
get target version at build time
madeline-k 01f194e
update warning message
madeline-k 4b11d81
remove WARNING from the warning. It's a warning.
madeline-k a5e35f6
fix comma placement, and add real github issue link
madeline-k 7415ae8
use semver
madeline-k 729f9cb
yarn build --fix
madeline-k 9227e93
fix string match in test
madeline-k af77de9
use compatible semver range
madeline-k 33df916
update asset-awscli dependency, add error handling for mkdirSync
madeline-k 70d9fb7
remove requirement.txt from being tracked by git, since it is now gen…
madeline-k b9d5bd8
temporarily lower coverage thresholds
madeline-k 2ab6282
fix: remove node_modules folder after test adds it in
kaizencc e97124c
fix download location to avoid duplicate node_modules folders
kaizencc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,25 @@ | ||
import * as fs from 'fs'; | ||
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(); | ||
} |
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
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 |
---|---|---|
@@ -1,2 +1,9 @@ | ||
const baseConfig = require('@aws-cdk/cdk-build-tools/config/jest.config'); | ||
module.exports = baseConfig; | ||
module.exports = { | ||
...baseConfig, | ||
coverageThreshold: { | ||
global: { | ||
branches: 45, | ||
}, | ||
}, | ||
}; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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,16 @@ | ||||||
#!/bin/bash | ||||||
|
||||||
# Copy files from the @aws-cdk/asset-awscli-v1 devDependency into | ||||||
# this package, so that they will be included in the released package. | ||||||
|
||||||
# Set bash to exit the script immediately on any error (e) and if any unset (u) | ||||||
# variable is referenced. | ||||||
set -eu | ||||||
|
||||||
dir=$(node -pe 'path.resolve(require.resolve("@aws-cdk/asset-awscli-v1"), "../..")') | ||||||
|
||||||
cp $dir/layer/requirements.txt ./layer | ||||||
cp $dir/lib/layer.zip ./lib | ||||||
|
||||||
target_version=$(node -pe 'require("@aws-cdk/asset-awscli-v1/package.json").version') | ||||||
echo "export const TARGET_VERSION = '${target_version}';" > lib/asset-package-version.ts | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
? |
1 change: 1 addition & 0 deletions
1
packages/@aws-cdk/lambda-layer-awscli/lib/asset-package-version.ts
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 @@ | ||
export const TARGET_VERSION = '2.1.0'; |
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 | ||||
---|---|---|---|---|---|---|
@@ -1,19 +1,70 @@ | ||||||
import * as path from 'path'; | ||||||
import * as lambda from '@aws-cdk/aws-lambda'; | ||||||
import { FileSystem } from '@aws-cdk/core'; | ||||||
import { Annotations, FileSystem } from '@aws-cdk/core'; | ||||||
import { debugModeEnabled } from '@aws-cdk/core/lib/debug'; | ||||||
import { Construct } from 'constructs'; | ||||||
import * as semver from 'semver'; | ||||||
import { TARGET_VERSION } from './asset-package-version'; | ||||||
import { installAndLoadPackage, _downloadPackage, _tryLoadPackage } from './private/package-loading-functions'; | ||||||
|
||||||
/** | ||||||
* An AWS Lambda layer that includes the AWS CLI. | ||||||
*/ | ||||||
export class AwsCliLayer extends lambda.LayerVersion { | ||||||
|
||||||
private static readonly assetPackageName: string = '@aws-cdk/asset-awscli-v1'; | ||||||
private static readonly assetPackageNpmTarPrefix: string = 'aws-cdk-asset-awscli-v1-'; | ||||||
|
||||||
constructor(scope: Construct, id: string) { | ||||||
super(scope, id, { | ||||||
code: lambda.Code.fromAsset(path.join(__dirname, 'layer.zip'), { | ||||||
const logs: string[] = []; | ||||||
let fallback = false; | ||||||
|
||||||
let assetPackage = _tryLoadPackage(AwsCliLayer.assetPackageName, new semver.Range(`^${TARGET_VERSION}`), logs); | ||||||
|
||||||
if (!assetPackage) { | ||||||
const downloadPath = _downloadPackage(AwsCliLayer.assetPackageName, AwsCliLayer.assetPackageNpmTarPrefix, TARGET_VERSION, logs); | ||||||
if (downloadPath) { | ||||||
assetPackage = installAndLoadPackage(AwsCliLayer.assetPackageName, downloadPath, logs); | ||||||
} | ||||||
} | ||||||
|
||||||
let code; | ||||||
if (assetPackage) { | ||||||
// ask for feedback here | ||||||
if (!assetPackage.AwsCliAsset) { | ||||||
logs.push(`ERROR: loaded ${AwsCliLayer.assetPackageName}, but AwsCliAsset is undefined in the module.`); | ||||||
} else { | ||||||
const asset = new assetPackage.AwsCliAsset(scope, `${id}-asset`); | ||||||
code = lambda.Code.fromBucket(asset.bucket, asset.s3ObjectKey); | ||||||
} | ||||||
} | ||||||
|
||||||
if (!code) { | ||||||
fallback = true; | ||||||
logs.push(`Unable to load ${AwsCliLayer.assetPackageName}. Falling back to use layer.zip bundled with aws-cdk-lib`); | ||||||
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 (debugModeEnabled()) { | ||||||
Annotations.of(this).addInfo(logs.join('\n')); | ||||||
} | ||||||
|
||||||
if (fallback) { | ||||||
Annotations.of(this).addWarning(`[ACTION REQUIRED] Your CDK application is using ${this.constructor.name}. Add a dependency on ${AwsCliLayer.assetPackageName}, or the equivalent in your language, to remove this warning. See https://github.com/aws/aws-cdk/issues/22470 for more information.`); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
new Notice(this, 'cli-notice'); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* An empty construct that can be added to the tree as a marker for the CLI Notices | ||||||
*/ | ||||||
class Notice extends Construct {} |
116 changes: 116 additions & 0 deletions
116
packages/@aws-cdk/lambda-layer-awscli/lib/private/package-loading-functions.ts
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,116 @@ | ||
import * as childproc from 'child_process'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as cxapi from '@aws-cdk/cx-api'; | ||
import * as semver from 'semver'; | ||
|
||
export function _tryLoadPackage(packageName: string, targetVersionRange: semver.Range, logs: string[]): any { | ||
let availableVersion; | ||
let assetPackagePath; | ||
try { | ||
assetPackagePath = require.resolve(`${packageName}`); | ||
} catch (e) { | ||
logs.push(`require.resolve('${packageName}') failed`); | ||
const eAsError = e as Error; | ||
if (eAsError?.stack) { | ||
logs.push(eAsError.stack); | ||
} | ||
return; | ||
} | ||
availableVersion = requireWrapper(path.join(assetPackagePath, '../../package.json'), logs).version; | ||
|
||
if (semver.satisfies(availableVersion, targetVersionRange)) { | ||
logs.push(`${packageName} already installed with compatible version: ${availableVersion}.`); | ||
const result = requireWrapper(packageName, logs); | ||
if (result) { | ||
logs.push(`Successfully loaded ${packageName} from pre-installed packages.`); | ||
return result; | ||
} | ||
} else { | ||
logs.push(`${packageName} already installed with incompatible version: ${availableVersion}. Target version range was: ${targetVersionRange}.`); | ||
} | ||
} | ||
|
||
export function _downloadPackage(packageName: string, packageNpmTarPrefix: string, targetVersion: string, logs: string[]): string | undefined { | ||
const cdkHomeDir = cxapi.cdkHomeDir(); | ||
const downloadDir = path.join(cdkHomeDir, 'npm-cache'); | ||
const downloadPath = path.join(downloadDir, `${packageNpmTarPrefix}${targetVersion}.tgz`); | ||
|
||
if (fs.existsSync(downloadPath)) { | ||
logs.push(`Using package archive already available at location: ${downloadPath}`); | ||
return downloadPath; | ||
} | ||
|
||
logs.push(`Creating directory: ${downloadDir}`); | ||
try { | ||
fs.mkdirSync(downloadDir); | ||
} catch (e) { | ||
if ((e as any)?.code === 'EEXIST') { | ||
logs.push(`Directory ${downloadDir} already exists.`); | ||
} else { | ||
logs.push('mkdirSync() failed'); | ||
const eAsError = e as Error; | ||
if (eAsError.stack) { | ||
logs.push(eAsError.stack); | ||
} | ||
return undefined; | ||
} | ||
} | ||
|
||
try { | ||
childproc.execSync(`npm pack ${packageName}@${targetVersion} -q`, { | ||
cwd: downloadDir, | ||
}); | ||
} catch (e) { | ||
logs.push('npm pack failed or timed out'); | ||
const eAsError = e as Error; | ||
logs.push(eAsError.name); | ||
logs.push(eAsError.message); | ||
if (eAsError.stack) { | ||
logs.push(eAsError.stack); | ||
} | ||
} | ||
|
||
if (fs.existsSync(downloadPath)) { | ||
logs.push('Successfully downloaded using npm pack.'); | ||
return downloadPath; | ||
} | ||
|
||
logs.push('Failed to download using npm pack.'); | ||
return undefined; | ||
} | ||
|
||
export function requireWrapper(id: string, logs: string[]): any { | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-require-imports | ||
const result = require(id); | ||
if (result) { | ||
logs.push(`require('${id}') succeeded.`); | ||
} | ||
return result; | ||
} catch (e) { | ||
logs.push(`require('${id}') failed.`); | ||
const eAsError = e as Error; | ||
if (eAsError?.stack) { | ||
logs.push(eAsError.stack); | ||
} | ||
} | ||
} | ||
|
||
export function installAndLoadPackage(packageName: string, from: string, logs: string[]): any { | ||
const installDir = findInstallDir(); | ||
if (!installDir) { | ||
logs.push('Unable to find an install directory. require.main.paths[0] is undefined.'); | ||
return; | ||
} | ||
logs.push(`Installing from: ${from} to: ${installDir}`); | ||
childproc.execSync(`npm install ${from} --no-save --prefix ${installDir} -q`); | ||
return requireWrapper(path.join(installDir, 'node_modules', packageName, 'lib/index.js'), logs); | ||
} | ||
|
||
export function findInstallDir(): string | undefined { | ||
if (!require.main?.paths) { | ||
return undefined; | ||
} | ||
return path.dirname(require.main.paths[0]); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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!