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

Enable access to SHA256 of built Lambda zipfile #6750

Closed
1 of 2 tasks
blimmer opened this issue Mar 16, 2020 · 2 comments · Fixed by #6771
Closed
1 of 2 tasks

Enable access to SHA256 of built Lambda zipfile #6750

blimmer opened this issue Mar 16, 2020 · 2 comments · Fixed by #6771
Assignees
Labels
@aws-cdk/assets Related to the @aws-cdk/assets package @aws-cdk/aws-lambda Related to AWS Lambda feature-request A feature should be added or improved. in-progress This issue is being actively worked on. needs-triage This issue or PR still needs to be triaged.

Comments

@blimmer
Copy link
Contributor

blimmer commented Mar 16, 2020

I would like to be able to access the SHA256 hash of the zipfile created by a Lambda function's build process, whether built through Code.fromAsset or via a higher-level package like aws-lambda-nodejs.

Use Case

My specific use-case has to do with Lambda@Edge functions (see also #1575), and the requirement that those functions have a deployed version. However, this request would benefit any user who wanted a new lambda Version created when they change their Lambda's source code.

From the searching I've done, it appears that some folks manually read in the contents of the lambda function and create a hash manually, like this:

const sha256 = require('sha256-file');
const version = lambda.addVersion(':sha256:' + sha256('./lambda/index.js'));

However, with the introduction of additional build functionality via aws-lambda-nodejs, this process starts to break down. Imagine updating your lambda's dependencies via package.json or package-lock.json. The source file (e.g. index.ts) does not get updated, but the resultant zipfile would get a new hash, with the new dependencies. So, what you'd really want in this case, is access to the SHA256 of the zipfile after the build process has completed.

Proposed Solution

I could see allowing access to the source hash via a property on the lambda function. For instance:

const myLambda = new lambda.NodejsFunction(this, 'MyFunction', {
  entry: path.join(__dirname, 'my-lambda', 'index.ts')
  handler: 'myExportedFunc'
});

const version = new lambda.Version(this, `Version-${myLamda.zipFile.sha256}`, {
  lambda: myLambda
});

// Reference `version` later on, like in the CloudFront Lambda@Edge config

The only issue I see right away is when the lambda code is not generated locally, like when it references an S3 object. In that case, maybe we'd need to throw a runtime exception? I'm not 100% sure.

Other

  • 👋 I may be able to implement this feature request
  • ⚠️ This feature might incur a breaking change

This is a 🚀 Feature Request

@blimmer blimmer added feature-request A feature should be added or improved. needs-triage This issue or PR still needs to be triaged. labels Mar 16, 2020
@blimmer
Copy link
Contributor Author

blimmer commented Mar 16, 2020

In the meantime, I worked around the problem in a way that works, but is completely outside of the CDK environment, relying on node fs and crypto methods.

Given a filesystem that looks like this:

.
├── my-lambda
│   ├── index.ts
│   ├── package-lock.json
│   └── package.json
└── my-lambda-stack.ts

I can ascertain the SHA256 like this in my stack.

export class MyLambdaStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps)  {
    super(scope, id, props);

    this.createLambda()
  }

  private createLambda() {
    const myLambdaPath = path.join(__dirname, "my-lambda");
    const buildDir = path.join(myLambdaPath, ".build");
    const filename = "index";
    const lambda = new NodejsFunction(this, "my-lambda", {
      entry: path.join(myLambdaPath, `${filename}.ts`),
      runtime: Runtime.NODEJS_12_X,
      handler: "handler",
      minify: true,
      buildDir,
    });

    const builtLambdaSha256 = this.getBuiltLambdaSha256(buildDir, filename);

    return new Version(this, `LambdaVersion-${builtLambdaSha256}`, {
      lambda,
    });
  }

  private getBuiltLambdaSha256(buildDir: string, filename: string): string {
    const buildDirectories = fs.readdirSync(buildDir);

    if (buildDirectories.length > 1) {
      // If you're a future dev and encounter this error, you have a few options.
      // - You could try to find the "latest" directory via timestamp.
      // - If this GitHub issue (https://github.com/aws/aws-cdk/issues/6750) has been addressed, you might be able to get rid of this entirely.
      throw new Error(
        "Cannot auto-version lambda. At the time of writing, Parcel only left one folder in the build directory, so we could determine the SHA of the most recently built file. Now there are multiple directories in the build dir. See code comments for ideas on how to fix this.",
      );
    }

    const builtLambdaFile = path.join(buildDir, buildDirectories[0], `${filename}.js`);
    const fileContents = fs.readFileSync(builtLambdaFile);
    return crypto
      .createHash("sha256")
      .update(fileContents)
      .digest("hex");
  }
}

@SomayaB SomayaB added @aws-cdk/assets Related to the @aws-cdk/assets package @aws-cdk/aws-lambda Related to AWS Lambda labels Mar 16, 2020
eladb pushed a commit that referenced this issue Mar 17, 2020
It is a common use case to define an alias for the latest version of a function. In order to do that, one needs to define a Version resource that points to the latest version and then associate an Alias with it.

This was not trivial to do so far. Now it is:

    fn.addAlias('foo')

This will define a lambda.Version with a name the derives from the hash of the lambda's source code and then define a lambda.Alias associated with this version object. Since the name of the version resource is based on the hash, when the code changes, a new version will be created automatically.

To support this, `lambda.Code.bind()` now returns an optional `codeHash` attribute. It is supported for `lambda.Code.fromAsset` and `lambda.Code.fromInline`, for which we can calculate the source hash based on the content.

Resolves #6750
Resolves #5334
@SomayaB SomayaB added the in-progress This issue is being actively worked on. label Mar 19, 2020
@mergify mergify bot closed this as completed in #6771 Mar 25, 2020
mergify bot pushed a commit that referenced this issue Mar 25, 2020
It is common for AWS services to require an explicit AWS Lambda Version when referencing functions. When an `AWS::Lambda::Version` resource is defined in CloudFormation is captures the AWS Lambda configuration *at the time of the creation of the version resource. This means that if the function's configuration or code is updated, the Version resource will no longer point to the function defined in the stack.

To address this, we introduce a property `function.currentVersion` which will create a new AWS::Lambda::Version resource every time the function's configuration changes. This is done by encoding a hash of the function's CloudFormation properties into the logical ID of the version resource.

Additionally, this change adds `version.addAlias` which makes it easier to define an AWS Lambda alias for a version.

The result is this:

    fn.currentVersion.addAlias('live');

We employ an approach similar to apigateway's "Deployment" resource in order to implement `currentVersion`: during "prepare", we synthesize the CloudFormation template snippet of the AWS::Lambda::Function resource, calculate an MD5 for it and append it to the logical ID of the version resource.

Resolves #6750
Resolves #5334
@blimmer
Copy link
Contributor Author

blimmer commented Apr 13, 2020

Thanks @eladb - I upgraded to 1.32.x today and this worked perfectly!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/assets Related to the @aws-cdk/assets package @aws-cdk/aws-lambda Related to AWS Lambda feature-request A feature should be added or improved. in-progress This issue is being actively worked on. needs-triage This issue or PR still needs to be triaged.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants