使用 Bazel 为 AWS Lambda 创建 Python zip

Ric*_*eca 5 python aws-lambda bazel bazel-rules bazel-python

我有一个包含一组 Python AWS lambda 的 monorepo,并且我正在使用Bazel来构建和打包 lambda。我现在尝试使用 Bazel 创建一个遵循预期AWS Lambda 打包的zip 文件,并且我可以将其上传到 Lambda。想知道使用 Bazel 执行此操作的最佳方法是什么?

以下是我迄今为止尝试过的一些不同的事情:

尝试1:py_binary

构建.bazel

py_binary(
name = "main_binary",
srcs = glob(["*.py"]),
main = "main.py",
visibility = ["//appcode/api/transaction_details:__subpackages__"],
deps = [
        requirement("Faker"),
    ],
)
Run Code Online (Sandbox Code Playgroud)

问题:

这会生成以下内容:

  • main_binary(python 可执行文件)
  • main_binary.run文件
  • main_binary.runfiles_manifest

Lambda 期望处理程序的格式为lambda_function.lambda_handler. 由于main_binary是可执行文件而不是 python 文件,因此它不会公开实际的处理程序方法,并且 lambda 会因为找不到它而崩溃。我尝试更新处理程序配置以简单地指向main_binary但它爆炸了,因为它需要两个参数(即lambda_function.lambda_handler)。

尝试2:py_library + pkg_zip

构建.bazel

py_library(
name = "main",
srcs = glob(["*.py"]),
visibility = ["//appcode/api/transaction_details:__subpackages__"],
deps = [
        requirement("Faker"),
    ],
)

pkg_zip(
name = "main_zip",
srcs =["//appcode/api/transaction_details/src:main" ],
)
Run Code Online (Sandbox Code Playgroud)

问题:

这会生成一个 zip 文件,其中包含:

  • 主要.py
  • __init__.py

zip 文件现在包含main.py但不包含任何运行时依赖项。因此 lambda 会因为找不到 而爆炸Faker

其他尝试:

我还尝试使用该--build_python_zip标志以及@bazel_tools//tools/zip:zipper通用规则,但它们都会导致与前两次尝试类似的结果。

Ric*_*eca 2

以下是我对之前生成 lambda zip 的答案所做的更改。感谢@jvolkman 最初的建议。

project/BUILD.bazel:requirements_lock.txt添加了生成规则project/requirements.txt

load("@rules_python//python:pip.bzl", "compile_pip_requirements")

compile_pip_requirements(
    name = "requirements",
    extra_args = ["--allow-unsafe"],
    requirements_in = "requirements.txt",
    requirements_txt = "requirements_lock.txt",
)
Run Code Online (Sandbox Code Playgroud)

project/WORKSPACE.bazel:将 pip_install 与 pip_parse 交换

workspace(name = "mdc-eligibility")

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "rules_python",
    sha256 = "9fcf91dbcc31fde6d1edb15f117246d912c33c36f44cf681976bd886538deba6",
    strip_prefix = "rules_python-0.8.0",
    url = "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.8.0.tar.gz",
)

load("@rules_python//python:repositories.bzl", "python_register_toolchains")
python_register_toolchains(
    name = "python3_9",
    python_version = "3.9",
)

load("@rules_python//python:pip.bzl", "pip_parse")
load("@python3_9//:defs.bzl", "interpreter")
pip_parse(
   name = "mndc-eligibility-deps",
   requirements_lock = "//:requirements_lock.txt",
   python_interpreter_target = interpreter,
   quiet = False
)
load("@mndc-eligibility-deps//:requirements.bzl", "install_deps")
install_deps()
Run Code Online (Sandbox Code Playgroud)

project/build_rules/lambda_packaging/lambda.bzl:修改了 @jvolkman 提供的自定义规则,以在生成的邮政编码中包含源代码。

def contains(pattern):
    return "contains:" + pattern

def startswith(pattern):
    return "startswith:" + pattern

def endswith(pattern):
    return "endswith:" + pattern

def _is_ignored(path, patterns):
    for p in patterns:
        if p.startswith("contains:"):
            if p[len("contains:"):] in path:
                return True
        elif p.startswith("startswith:"):
            if path.startswith(p[len("startswith:"):]):
                return True
        elif p.startswith("endswith:"):
            if path.endswith(p[len("endswith:"):]):
                return True
        else:
            fail("Invalid pattern: " + p)

    return False

def _short_path(file_):
    # Remove prefixes for external and generated files.
    # E.g.,
    #   ../py_deps_pypi__pydantic/pydantic/__init__.py -> pydantic/__init__.py
    short_path = file_.short_path
    if short_path.startswith("../"):
        second_slash = short_path.index("/", 3)
        short_path = short_path[second_slash + 1:]
    return short_path

# steven chambers

def _py_lambda_zip_impl(ctx):
    deps = ctx.attr.target[DefaultInfo].default_runfiles.files

    f = ctx.outputs.output

    args = []
    for dep in deps.to_list():
        short_path = _short_path(dep)

        # Skip ignored patterns
        if _is_ignored(short_path, ctx.attr.ignore):
            continue

        args.append(short_path + "=" + dep.path)

    # MODIFICATION: Added source files to the map of files to zip
    source_files = ctx.attr.target[DefaultInfo].files
    for source_file in source_files.to_list():
        args.append(source_file.basename+"="+source_file.path)

    ctx.actions.run(
        outputs = [f],
        inputs = deps,
        executable = ctx.executable._zipper,
        arguments = ["cC", f.path] + args,
        progress_message = "Creating archive...",
        mnemonic = "archiver",
    )

    out = depset(direct = [f])
    return [
        DefaultInfo(
            files = out,
        ),
        OutputGroupInfo(
            all_files = out,
        ),
    ]

_py_lambda_zip = rule(
    implementation = _py_lambda_zip_impl,
    attrs = {
        "target": attr.label(),
        "ignore": attr.string_list(),
        "_zipper": attr.label(
            default = Label("@bazel_tools//tools/zip:zipper"),
            cfg = "host",
            executable = True,
        ),
        "output": attr.output(),
    },
    executable = False,
    test = False,
)

def py_lambda_zip(name, target, ignore, **kwargs):
    _py_lambda_zip(
        name = name,
        target = target,
        ignore = ignore,
        output = name + ".zip",
        **kwargs
    )
Run Code Online (Sandbox Code Playgroud)

project/appcode/api/transaction_details/src/BUILD.bazel:使用自定义py_lambda_zip规则进行压缩py_library

load("@mndc-eligibility-deps//:requirements.bzl", "requirement")
load("@python3_9//:defs.bzl", "interpreter")
load("//build_rules/lambda_packaging:lambda.bzl", "contains", "endswith", "py_lambda_zip", "startswith")

py_library(
    name = "main",
    srcs = glob(["*.py"]),
    visibility = ["//appcode/api/transaction_details:__subpackages__"],
    deps = [
            requirement("Faker"),
        ],
)

py_lambda_zip(
    name = "lambda_archive",
    ignore = [
        contains("/__pycache__/"),
        endswith(".pyc"),
        endswith(".pyo"),

        # Ignore boto since it's provided by Lambda.
        startswith("boto3/"),
        startswith("botocore/"),

        # With the move to hermetic toolchains, the zip gets a lib/ directory containing the
        # python runtime. We don't need that.
        startswith("lib/"),
    ],
    target = ":main",
)
Run Code Online (Sandbox Code Playgroud)