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): allow running a build file #30196

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open

feat(lambda): allow running a build file #30196

wants to merge 30 commits into from

Conversation

bergjaak
Copy link
Contributor

@bergjaak bergjaak commented May 14, 2024

Issue # (if applicable)

Closes #18470

Reason for this change

This allows customers to execute an arbitrary build script as part of cdk synth, which will enable customer to use esbuild plugins. The rationale for this decision is given the issue that is linked above.

Description of changes

  1. Expose the code field on the aws-lambda-nodejs construct, so that customers can specify code in ways other than bundling, which was the default and abstracted away from customers before this change.
  2. Add a new static method on Code, namely Code.fromCustomCommand. This method takes in the commands to run an arbitrary script during cdk synthesis that the customer provides. The customer also provides the location of the output from the buildscript. Then this output is supplied to a lambda function.

Description of how you validated changes

manual testing (involving inspecting output in the AWS Lambda console and invoking the function), integration tests, and full unit test coverage of new changes.

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions github-actions bot added the p2 label May 14, 2024
@aws-cdk-automation aws-cdk-automation requested a review from a team May 14, 2024 21:23
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label May 14, 2024
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a comment

Choose a reason for hiding this comment

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

The pull request linter has failed. See the aws-cdk-automation comment below for failure reasons. If you believe this pull request should receive an exemption, please comment and provide a justification.

A comment requesting an exemption should contain the text Exemption Request. Additionally, if clarification is needed add Clarification Request to a comment.

@github-actions github-actions bot added effort/large Large work item – several weeks of effort feature-request A feature should be added or improved. p1 and removed p2 labels May 14, 2024
@bergjaak bergjaak changed the title Bergjaak/18470 feat(Lambda): allow running a build file May 14, 2024
@bergjaak bergjaak added effort/medium Medium work item – several days of effort and removed effort/large Large work item – several weeks of effort labels May 14, 2024
Comment on lines 114 to 125
if (props.code !== undefined) {
if (props.handler === undefined) {
throw new Error('Cannot determine handler when `code` property is specified. Use `handler` property to specify a handler.');
}

super(scope, id, {
...props,
runtime,
architecture,
depsLockFilePath,
projectRoot,
}),
handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`,
});
code: props.code,
handler: props.handler!,
});
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you help me understand why we need this new code block here? Why can't we just determine the handler in the same way we are doing it right now? Couldn't we just keep the existing code and do something where if code is supplied we use it, otherwise we do Bundling.bundle? So something like:

  // Entry and defaults
  const entry = path.resolve(findEntry(id, props.entry));
  const architecture = props.architecture ?? Architecture.X86_64;
  const depsLockFilePath = findLockFile(props.depsLockFilePath);
  const projectRoot = props.projectRoot ?? path.dirname(depsLockFilePath);
  const handler = props.handler ?? 'handler';

  super(scope, id, {
    ...props,
    runtime,
    code: props.code ?? Bundling.bundle(scope, {
      ...props.bundling ?? {},
      entry,
      runtime,
      architecture,
      depsLockFilePath,
      projectRoot,
    }),
    handler: handler.indexOf('.') !== -1 ? `${handler}` : `index.${handler}`,
  });
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I originally had, but I think it's a worse customer experience. Because there's nothing forcing the customer to name the file containing the exported handler index. It's entirely up to the customer to decide how they want to name that file, if they're using the Code.fromCustomCommand. Therefore, I think this is a better customer experience, even if it's a few more lines of code

Copy link
Contributor

Choose a reason for hiding this comment

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

I see where you're coming from but I'm not sure I agree. I think this construct has already established that the expected behavior is we will default the handler if it isn't supplied. Consider a customer that is using the nodejs function and they currently don't supply a handler and just use the default. Now, once this feature is released, they decide to use the new code property to run a custom build script. However, suddenly they're encountering errors since they don't set a handler. I recognize that it is trivial to just specify a handler, but it is a change from the expectation for the construct as a whole. Additionally, we generally try to avoid properties where we have to do a bunch of validation around if a property is set then another one must also be set. We consider it a bit of an anti-pattern. Our VPC construct is a more extreme example of this, but it shows where this can get to be hard to deal with when we have to start doing a ton of property validation - https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, once this feature is released, they decide to use the new code property to run a custom build script. However, suddenly they're encountering errors since they don't set a handler.

My understanding is, if a customer does switch to using Code.fromCustomCommand and wants an error-free transition with default handler values, then their build script will need to create a file (either in a directory or a zip file) named index.js with an exported function called handler. Defaulting to handler makes sense in this case since it's unlikely that the customer would have changed the name of the function. However, if the customer never knew that the file that contained their lambda code, when deployed to AWS Lambda, was called index.js (since that's abstracted away by this construct), then wouldn't the customer be confused when their lambda fails to work because their build file didn't output the handler in a file called index.js? That seems more confusing to me. By throwing the Error here, we push the error that the customer may run into much earlier, rather than the customer's lambda failing at runtime after deploying successfully -- which could cause an outage if a customer is deploying directly to production.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

basically, to avoid having an outage in production, if a customer is switching to using this Code.fromCustomCommand, the customer would need to know that their build file must output a file called index.js that exports a function called handler. That's not something that I think should be hidden from the customer. By requiring handler, we don't abstract away a detail that could potentially lead to an outage

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that this is more complicated, but the alternative is to push this complexity onto the customer after they've deployed their lambda

Copy link
Contributor

Choose a reason for hiding this comment

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

Happy to change my mind on this, and with the way you've explained it I agree. Doesn't make sense to let the customer fail during deploy time.

Comment on lines 9 to 16
"request": "launch",
"args": ["jest", "--runTestsByPath", "/Users/bergjak/workplace/CDK/aws-cdk/packages/aws-cdk-lib/aws-lambda-nodejs/test/function.test.ts"],
"name": "debug unit test",
"program": "/opt/homebrew/bin/yarn",
"cwd": "/Users/bergjak/workplace/CDK/aws-cdk/packages/aws-cdk-lib",
"console": "integratedTerminal",
"sourceMaps": true,
"skipFiles": [ "<node_internals>/**/*" ],
Copy link
Contributor

Choose a reason for hiding this comment

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

leftover?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, I'll remove this in my final revision

@colifran colifran changed the title feat(Lambda): allow running a build file feat(lambda): allow running a build file May 15, 2024
@github-actions github-actions bot added effort/large Large work item – several weeks of effort and removed effort/medium Medium work item – several days of effort labels May 15, 2024
@colifran colifran added p2 and removed p1 labels May 15, 2024
@aws-cdk-automation aws-cdk-automation dismissed their stale review May 15, 2024 19:25

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@github-actions github-actions bot added p1 and removed p2 labels May 15, 2024
@bergjaak bergjaak marked this pull request as ready for review May 15, 2024 19:43
@aws-cdk-automation aws-cdk-automation added the pr/needs-maintainer-review This PR needs a review from a Core Team Member label May 15, 2024
Comment on lines 72 to 73
commandOptions?: {[option: string]: any}, // jsii build fails if the type is SpawnSyncOptions... so best we can do is point user to those options.
assetOptions?: s3_assets.AssetOptions,
Copy link
Contributor

@colifran colifran May 16, 2024

Choose a reason for hiding this comment

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

I generally prefer to use interfaces where we can so that if there is ever a chance that a new argument is introduced we can just add it to the interface. What do you think about creating a new interface that just extends AssetOptions? We could call it CustomCommandOptions. Something like this:

export interface CustomCommandOptions extends AssetOptions {
	readonly commands?: { [options: string]: any },
}

Then the function definition could just be:

public static fromCustomCommand(output: string, command: string[], options: CustomCommandOptions = {})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you, that's an improvement

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: 1e59d4a
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS. effort/large Large work item – several weeks of effort feature-request A feature should be added or improved. p1 pr/needs-maintainer-review This PR needs a review from a Core Team Member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

(aws-lambda-nodejs): Use esbuild API instead of CLI for bundling
3 participants