aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.67k stars 3.92k forks source link

Lambda "build" assets #1435

Closed eladb closed 4 years ago

eladb commented 5 years ago

We would like to leverage the aws-lambda-builders project (part of SAM CLI) in order to allow CDK users to build AWS Lambda bundles.

Requirement (API sketch):

new lambda.Function(this, 'MyFunction', {
  code: lambda.Code.build('/path/to/lambda/handler'),
  runtime: lambda.Runtime.Xxx,
  handler: 'foo.bar'
});

Ideally, this is all the user should need to specify. The lambda.Code.build class should be able to deduce most of the information needed in order to tell the toolkit to invoke aws-lambda-builders via it's JSON RPC protocol when assets are prepared (similar to any other asset).

Notes:

eladb commented 5 years ago

Copy @jfuss

jfuss commented 5 years ago

Additional context:

sam-goodwin commented 5 years ago

Working with aws-lambda-builders to extract the logic within SAM that maps a function runtime to a build workflow, e.g. taking a java8 function and determining if it's a maven or gradle project by looking for a pom.xml or build.gradle file. This wil enable us to provide the following experience:

const myFunction = new lambda.JavaFunction(stack, 'MyJavaFunction', {
  projectPath: './lambda'
});
sam-goodwin commented 5 years ago

I am currently using the asset's bind function as a hook to inspect the runtime property of Function to determine which lambda-builder workflow to run. It enables a familiar experience, only requiring you to use lambda.Code.build instead of asset or file:

const gradle = new lambda.Function(stack, 'Gradle', {
  code: lambda.Code.build('./gradle-project'),
  runtime: lambda.Runtime.Java8,
  handler: 'com.example.Main:handle'
});

It's simple, but lambda.Code.bind takes a Construct instead of a Function, so I'm not sure if I can safely assume I'll always have access to the function's runtime . Is there a use-case here to pass anything other than a Function to bind?

I like the idea of a type-safe JvmFunction because it enables the injection of hooks specific to an ecosystem, but I'm wondering how we should draw the lines between the CDK's responsibility and that of tools like aws-lambda-builders. I have a prototype integrating lambda-builders as an Asset, where I have duplicated logic from SAM to detects things like gradle vs maven for java. The following PR proposes centralizing that logic in a common tool for both CDK and SAM, but we could also solve it with an explicit parameter:

const myFunction = new lambda.JvmFunction(stack, 'MyJavaFunction', {
  projectPath: './lambda',
  dependencyManager: 'maven'
});

Which begs the question: what information should be injectable as props and what information should be inferred by the tool?

eladb commented 5 years ago

Is there a use-case here to pass anything other than a Function to bind?

Yes, for example, Layers also use the same Code mechanism. Maybe we should define an interface :-)

Which begs the question: what information should be injectable as props and what information should be inferred by the tool?

I think the default behavior should be to infer as much as possible, but I really like the option of letting users specify many of the project settings here (like dependencies etc) if they wish.

sam-goodwin commented 5 years ago

Layers changes things up a bit because they aren't specific to a single runtime: compatibleRuntimes?: Runtime[] with default All.

I see the following options for proceeding:

  1. Pass the runtime/build information to lambda.Code.build so it doesn't need to inspect the Layer/Function. This would mean we are redundantly passing runtime information to both the Layer/Function and the Code asset.
    const fn = new lambda.Function(this, 'F', {
    runtime: lambda.Runtime.Java8,
    code: lambda.Code.build({
    path: './path',
    language: lambda.Runtime.Java8,
    // maybe we only need to pass the family, not the specific language and infer the rest?
    // language: lambda.RuntimeFamily.Java,
    })
    });
    const fn = new lambda.Layer(this, 'F', {
    code: lambda.Code.build({
    path: './path',
    language: lambda.Runtime.Java8
    })
    });
  2. Only support builds in higher-level constructs such as PythonFunction or JvmLayer.
    const fn = new lambda.PythonFunction(this, 'F', {
    path: './path',
    version: '3.7'
    });
    const layer = new lambda.NodeLayer(this, 'L', {
    path: './path',
    });
  3. Ditch lambda.Code.build and instead have various options such as JvmCode, NodeCode:
const fn = new lambda.Function(this, 'F', {
  runtime: lambda.Runtime.Java8,
  code: new JvmCode('./path')
});
const layer = new lambda.Layer(this, 'L', {
  code: new NodeCode('./path'),
  compatibleRuntimes: [
    lambda.Runtime.NodeJs,
    lambda.Runtime.NodeJs810,
    // etc.
  ]
});

// the static method could still be useful for discoverability and consistency.
// again, requires redundant 'runtime' information to be passed
const fn = new lambda.Function(this, 'F', {
  runtime: lambda.Runtime.Java8,
  code: lambda.Code.java('./path')
});

I'm leaning towards a layered approach incorporating option 2. and 3. - provide high-level Function constructs for each target we support, built on top of a set of Code classes so developers can drop down and customize where they see fit.

zoonman commented 5 years ago

Hey guys, is there any progress on the issue for typescript version?

andreimcristof commented 5 years ago

Hi there, is there any news on this feature? I wrote all my provisioning code with Typescript, and just found out that the packaging of the lambdas must be done manually - I thought that there would be some equivalent of "sam build". Is there any way to actually invoke "sam build" on the synthesized cloudformation template, so that sam cli takes over the packaging?

Thank you

nija-at commented 5 years ago

Unfortunately, we don't yet have a plan or update on this feature at this time.

@andreimcristof - this issue is only focusing on using the lambda builders tooling, i.e., equivalent of sam build. Packaging and deployment would still be handled by the CDK.

If you're only looking to invoke sam build on the synthesized template (found in cdk.out/ folder), this should be possible by passing in the right values to the --base-dir and --template parameters of the sam build command. At this point, it should be possible to run sam package followed by sam deploy on the output. However, it would not be possible to bring the generated template and build directory to work with cdk deploy.

eladb commented 5 years ago

I think at a minimum we should provide guidance on how to use the SAM build capabilities as a pre synthesis step in order to produce lambda bundle zip files that can later be referenced as file assets for Lambda code.

andreimcristof commented 5 years ago

...At this point, it should be possible to run sam package followed by sam deploy on the output. However, it would not be possible to bring the generated template and build directory to work with cdk deploy.

@nija-at but can sam build and the cdk not play nice with eachother? Meaning: before initializing the cdk infra, I trigger a sam build, and customise the path of the cdk lambdas to read from the sam build output? Can that not work? because, if I understood correctly, all that the cdk needs for the functions, is a path where to read the zips from:

return new lambda.Function(scope, `${funcHandler}_lambda`, {
    runtime: lambda.Runtime.NODEJS_10_X,
    handler: funcHandler,
    code: lambda.Code.fromAsset(path.join(__dirname, 'crud')),
  } as lambda.FunctionProps);

... so then, I just configure the .fromAsset to read from the sam build output? ah, exactly what @eladb suggested. No, wait. I'm missing something. @eladb - the sam build cannot be pre-synthesis, as it needs the template. the synth generates the template. The way I underrstand it, this would happen like this:

coderbyheart commented 5 years ago

I had the same problem and solved it by having a separate stack for storing the lambda archives on S3, and before the stack is deployed building and uploading the lambdas so they can be referenced in CDK from the bucket: https://coderbyheart.com/how-i-package-typescript-lambdas-for-aws/

eladb commented 4 years ago

Addressed by #5532 and #9182

polothy commented 4 years ago

@eladb are there plans to make a aws-lambda-go or aws-lambda-golang package? Maybe I'm supposed to use Bundling Asset Code and its easy enough for Go?

eladb commented 4 years ago

@eladb are there plans to make a aws-lambda-go or aws-lambda-golang package? Maybe I'm supposed to use Bundling Asset Code and its easy enough for Go?

No concrete plans but more than happy to take contributions! Bundling is the right way.

polothy commented 4 years ago

Thanks for the reply! I might just do that, though it'll be a while before I have time :)