-
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
(aws-lambda / assets): Ability to use token in Code.fromAsset, or stage lambda code lazily #28732
Comments
Makes sense. Thank you for the use case sharing. |
I've hacked together a temporary solution to this (although it doesn't operate directly on The setup consists of:
It looks like the following, Aspect (bundler)export class NixBundler implements IAspect {
built = false;
assets: LazyS3Asset[] = [];
public visit(node: IConstruct): void {
if (node instanceof CfnFunction) {
const code = node.code as CfnFunction.CodeProperty;
if (Tokenization.isResolvable(code.s3Bucket) && Tokenization.isResolvable(code.s3Key)) {
// We have to store the asset reference as a property on each key,
// because the asset construct is an intermediate and is not directly
// exposed in the visitor
this.assets.push((code.s3Bucket as unknown as LazyS3AssetProperty).asset);
}
}
}
build() {
if (this.built) {
return;
}
if (!this.assets.length) {
return;
}
// https://github.com/NixOS/nix/issues/9042 - let's assume there are no duplicate entries
const result = execSync(`nix build ${this.assets.map((asset) => asset.installable).join(' ')} --impure --print-out-paths`);
const outputs = result.toString().trim().split('\n');
for (const [asset, output] of zip(this.assets, outputs)) {
if (!(asset && output)) {
continue;
}
asset.sourcePath = output;
}
this.built = true;
}
} LazyAssetCodeexport class LazyS3AssetProperty implements IResolvable {
private _property?: string;
public constructor(public readonly asset: LazyS3Asset) {
}
creationStack: string[] = [];
typeHint?: ResolutionTypeHint | undefined;
public resolve() {
return this._property;
}
public resolveTo(value: string) {
this._property = value;
}
public toString(): string {
return Token.asString(this);
}
}
export class LazyS3Asset {
public bucketName = new LazyS3AssetProperty(this);
public objectKey = new LazyS3AssetProperty(this);
set sourcePath(v: string) {
const asset = this.fn(v);
// Not super happy with this - we're essentially manually resolving the tokens
this.bucketName.resolveTo(asset.s3BucketName);
this.objectKey.resolveTo(asset.s3ObjectKey);
}
public static from(installable: string, fn: (sourcePath: string) => s3_assets.Asset) {
return new LazyS3Asset(installable, fn);
}
private constructor(public readonly installable: string, private readonly fn: (sourcePath: string) => s3_assets.Asset) {
}
}
/**
* (Nix) Lambda code from a local directory.
*/
export class LazyAssetCode extends Code {
private asset?: s3_assets.Asset;
public sourcePath?: string;
constructor(private readonly installable: string, private readonly options: s3_assets.AssetOptions = { }) {
super();
}
public bind(scope: Construct): CodeConfig {
const s3Location = LazyS3Asset.from(this.installable, (sourcePath: string) => {
this.asset = new s3_assets.Asset(scope, 'Code', {
path: sourcePath,
deployTime: true,
});
return this.asset;
});
return {
s3Location: {
bucketName: s3Location.bucketName as unknown as string,
objectKey: s3Location.objectKey as unknown as string,
},
};
}
public bindToResource(resource: CfnResource, options: ResourceBindOptions = { }) {
if (!this.asset) {
return;
}
const resourceProperty = options.resourceProperty || 'Code';
// https://github.com/aws/aws-cdk/issues/1432
this.asset.addResourceMetadata(resource, resourceProperty);
}
} Wrapped Appclass DefaultStackBundlerSynthesizer extends DefaultStackSynthesizer {
constructor(public readonly bundler: NixBundler) {
super();
}
synthesize(session: ISynthesisSession): void {
// Collects the asset-constructs
this.bundler.build();
super.synthesize(session);
}
}
export class CustomApp extends App {
constructor(public readonly bundler: NixBundler = new NixBundler()) {
super({ defaultStackSynthesizer: new DefaultStackBundlerSynthesizer(bundler)});
// Traverses the tree
Aspects.of(this).add(this.bundler);
}
} Usageconst func = new Function(this, 'entry', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'hello.handler',
code: new LazyAssetCode('nixpkgs#hello'),
}); With this, Nix is invoked with all the installables once the tree has been traversed and blocks until all the assets are available. It either utilizes the binary cache (best case) or forwards the work to an arbitrary number of builders as is configured by the system. |
Describe the feature
Provide lazy strings or inputs to Code.fromAsset:
Use Case
In some situations, I might not have all of the lambda code available for my CDK app available before I run
cdk synth
orcdk deploy
. For example, in Winglang (language that uses CDK constructs under the hood) we're trying to generate some lambda code dynamically based on the graph of resources in the construct tree. In this case, I won't know the exact contents of my lambda code until the entire tree is constructed (but we can assume the contents will be known beforeapp.synth()
is called).Proposed Solution
No response
Other Information
I tried extending the abstract
Code
class and implementing thebind()
method, but looking at the existing implementation ofAssetCode
here, I couldn't figure out a way to instantiates3_assets.Asset
lazily. Even if I use thebundling
option from the assets module described here, it seems like the bundling command gets invoked while the tree is being created (in other words, in the middle of the evaluation ofnew MyCdkStack()
), not afterwards.Acknowledgements
CDK version used
2.92.0
Environment details (OS name and version, etc.)
macOS
The text was updated successfully, but these errors were encountered: