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

[FR]: support for nodejs lambda #23

Open
alexeagle opened this issue Aug 1, 2023 · 1 comment
Open

[FR]: support for nodejs lambda #23

alexeagle opened this issue Aug 1, 2023 · 1 comment
Labels
enhancement New feature or request

Comments

@alexeagle
Copy link
Member

What is the current behavior?

AWS documents how to build, test, and release NodeJS lambdas.

They can be released as container images or zip files.

Describe the feature

Provide easy support in Bazel for doing all the build/test/release tasks related to NodeJS lambda.

@alexeagle alexeagle added the enhancement New feature or request label Aug 1, 2023
@alexeagle
Copy link
Member Author

alexeagle commented Aug 2, 2023

Rough design notes:

Fetch RIE

We need the AWS provided "Runtime Interface Emulator" to be able to run lambda logic locally. See https://github.com/aws/aws-lambda-runtime-interface-emulator

WORKSPACE something like

oci_pull(
    name = "aws_lambda_nodejs",
    digest = "sha256:715a39e5d7eb88bca7b25617b734087c12724178978401e038bd8e4964757dc0",
    image = "public.ecr.aws/lambda/nodejs",
    platforms = [
        "linux/amd64",
        "linux/arm64/v8",
    ],
)

Build the image

# Bundle the function with its dependencies because AWS Lambda requires that
# external dependencies be bundled in a flat node_modules structure without
# symlinks, which rules_js (which uses a pnpm dependency layout) cannot produce.
# TODO: Look into layering dependencies into a separate archive as an optimization
# if the bundle file becomes too large.
# https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
esbuild(
    name = "bundle",
    srcs = [":revert"],
    config = {
        "resolveExtensions": [".js"],
        "banner": {
            "js": "// Copyright 2022 Aspect Build Systems, Inc. All rights reserved.",
        },
    },
    entry_point = "index.js",
    minify = True,
    output = "bundle.js",
    platform = "node",
    sourcemap = "inline",
)

# Copy bundle.js into a new directory so that we can rename it to index.js and not
# conflict with the application's transpiled index.js. AWS Lambda requires an index.js
# file in the root of the zip archive.
copy_to_directory(
    name = "package",
    srcs = [
        "bundle.js",
    ],
    replace_prefixes = {
        "bundle.js": "index.js",
    },
)

pkg_tar(
    name = "tar",
    srcs = [":package"],
    package_dir = "/var/task",
    strip_prefix = "package",
)

# See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-image.html
oci_image(
    name = "image",
    base = "@aws_lambda_nodejs",
    cmd = ["index.handler"],
    tars = [":tar"],
)

We could probably provide a macro to make this convenient.

Testing


# Allow the image to be run locally for integration testing.
# See https://docs.aws.amazon.com/lambda/latest/dg/images-test.html#images-test-AWSbase
#
# bazel run //path/to:tarball
# docker run -p 9000:8080 --env ENVIRONMENT=INTEGRATION_TEST --env APP_ID=test-123 --env APP_WEBHOOK_SECRET=secretX --env APP_PRIVATE_KEY=keyX --rm revert:latest
#
# In another terminal:
# curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'
# -> {"statusCode":418,"body":""}
oci_tarball(
    name = "tarball",
    image = ":image",
    repo_tags = ["revert:latest"],
)

And the user would then write a normal testcontainers-style test like this:

// Copyright 2022 Aspect Build Systems, Inc. All rights reserved.

const { spawnSync } = require('node:child_process');
import { join } from 'node:path';
import axios from 'axios';

import { GenericContainer, StartedTestContainer } from 'testcontainers';

const IMAGE_TARBALL = join(
    process.env['TEST_SRCDIR']!,
    process.env['TEST_WORKSPACE']!,
    'path/to/tarball/tarball.tar'
);

describe('Revert lambda', () => {
    let container: StartedTestContainer | undefined;
    let endpoint: string;

    beforeAll(async () => {
        const res = spawnSync('docker', ['load', '-i', IMAGE_TARBALL]);
        if (res.status) {
            process.stderr.write(res.stderr);
            throw new Error('failed to load docker image' + res.status);
        }
        container = await new GenericContainer('revert:latest')
            .withExposedPorts(8080)
            .withEnvironment({
                // See comment in src/entrypoint.ts
                ENVIRONMENT: 'INTEGRATION_TEST',
                APP_ID: 'test-123',
                APP_WEBHOOK_SECRET: 'secretX',
                APP_PRIVATE_KEY: 'keyX',
            })
            .start();
        const port = container.getMappedPort(8080);
        endpoint = `http://localhost:${port}/2015-03-31/functions/function/invocations`;
    }, /* On a busy machine, the docker operations can take longer than 5sec */ 50000);

    afterAll(async () => {
        await container?.stop();
    });

    it('Handles a request', async () => {
        const resp = await axios.post(endpoint, {
            /* empty post data */
        });
        // We return this code when INTEGRATION_TEST is in the environment.
        expect(resp.data.statusCode).toBe(418);
    });
});

Deploying

Either deploy the same image we tested:


# Push the image to ECR.
# See "Deploying the image": https://docs.aws.amazon.com/lambda/latest/dg/typescript-image.html
oci_push(
    name = "push",
    image = ":image",
    remote_tags = ":tags",
    repository = "12345.dkr.ecr.us-west-2.amazonaws.com/revert-lambda",
)

or deploy a zip file:

# Production release is a zip-package.
# See https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html
pkg_zip(
    name = "zip",
    srcs = [":index.js"],
    out = "my-lambda.zip",
)

Then refer to that ZIP from a terraform deploy rule.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant