From 388f53d31392693090ed505b5a2ca90404885925 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 1 Sep 2023 10:20:31 -0700 Subject: [PATCH] feat: introduce aws_py_lambda macro Includes an integration test using testcontainers --- MODULE.bazel | 32 ++- aws/defs.bzl | 53 ++++ aws/private/py_lambda.bzl | 116 +++++++++ aws/repositories.oci.bzl | 25 ++ examples/python_lambda/BUILD.bazel | 28 ++ examples/python_lambda/README.md | 7 + examples/python_lambda/lambda_function.py | 9 + examples/python_lambda/requirements.in | 3 + examples/python_lambda/requirements.txt | 241 ++++++++++++++++++ examples/python_lambda/test/BUILD.bazel | 36 +++ .../test/container_structure_test.yaml | 12 + .../python_lambda/test/integration_test.py | 41 +++ 12 files changed, 598 insertions(+), 5 deletions(-) create mode 100644 aws/private/py_lambda.bzl create mode 100644 aws/repositories.oci.bzl create mode 100644 examples/python_lambda/BUILD.bazel create mode 100644 examples/python_lambda/README.md create mode 100644 examples/python_lambda/lambda_function.py create mode 100644 examples/python_lambda/requirements.in create mode 100644 examples/python_lambda/requirements.txt create mode 100644 examples/python_lambda/test/BUILD.bazel create mode 100644 examples/python_lambda/test/container_structure_test.yaml create mode 100644 examples/python_lambda/test/integration_test.py diff --git a/MODULE.bazel b/MODULE.bazel index efb4ba9..0a5860c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -8,16 +8,19 @@ module( # Lower-bound dependency versions. # Do not change unless the rules no longer work with the current version. +bazel_dep(name = "aspect_rules_py", version = "0.3.0") bazel_dep(name = "bazel_skylib", version = "1.4.1") -bazel_dep(name = "platforms", version = "0.0.5") +bazel_dep(name = "platforms", version = "0.0.7") +bazel_dep(name = "rules_oci", version = "1.4.0") +bazel_dep(name = "rules_pkg", version = "0.9.1") +bazel_dep(name = "rules_python", version = "0.25.0") # Development dependencies which are not exposed to users -bazel_dep(name = "gazelle", version = "0.32.0", dev_dependency = True, repo_name = "bazel_gazelle") -bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.4.2", dev_dependency = True) bazel_dep(name = "aspect_bazel_lib", version = "1.32.1", dev_dependency = True) +bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.4.2", dev_dependency = True) bazel_dep(name = "buildifier_prebuilt", version = "6.1.2", dev_dependency = True) -bazel_dep(name = "rules_pkg", version = "0.9.1", dev_dependency = True) -bazel_dep(name = "rules_oci", version = "1.2.0", dev_dependency = True) +bazel_dep(name = "container_structure_test", version = "1.16.0", dev_dependency = True) +bazel_dep(name = "gazelle", version = "0.32.0", dev_dependency = True, repo_name = "bazel_gazelle") aws = use_extension("//aws:extensions.bzl", "aws") aws.toolchain(aws_cli_version = "2.13.0") @@ -40,3 +43,22 @@ oci.pull( use_repo(oci, "ubuntu") register_toolchains("@aws_toolchains//:all") + +aws_py_lambda = use_extension( + "@aspect_rules_aws//aws:repositories.oci.bzl", + "aws_py_lambda", + dev_dependency = True, +) +use_repo(aws_py_lambda, "aws_lambda_python") + +pip = use_extension( + "@rules_python//python/extensions:pip.bzl", + "pip", + dev_dependency = True, +) +pip.parse( + hub_name = "pip", + python_version = "3.11", + requirements_lock = "//examples/python_lambda:requirements.txt", +) +use_repo(pip, "pip") diff --git a/aws/defs.bzl b/aws/defs.bzl index fff639f..2f09642 100644 --- a/aws/defs.bzl +++ b/aws/defs.bzl @@ -1 +1,54 @@ "Public API re-exports" + +load("@rules_oci//oci:defs.bzl", "oci_image") +load("//aws/private:py_lambda.bzl", "py_lambda_tars") +load("@rules_python//python:defs.bzl", "py_binary") + +def aws_py_lambda(name, entry_point = "lambda_function.py", deps = [], base = "@aws_lambda_python"): + """Defines a Lambda run on the Python runtime. + + See https://docs.aws.amazon.com/lambda/latest/dg/lambda-python.html + + Produces an oci_image target following https://docs.aws.amazon.com/lambda/latest/dg/python-image.html + + Args: + name: name of resulting target + entry_point: python source file implementing the handler + deps: third-party packages required at runtime + base: a base image that includes the AWS Runtime Interface Emulator + + TODO: + - produce a [name].zip output following https://docs.aws.amazon.com/lambda/latest/dg/python-package.html#python-package-create-dependencies + """ + + bin_target = "_{}.bin".format(name) + tars_target = "_{}.tars".format(name) + + # Convert //my/pkg:entry_point.py to my.pkg.entry_point.handler + cmd = "{}.{}.handler".format(native.package_name().replace("/", "."), entry_point.replace(".py", "")) + + py_binary( + name = bin_target, + srcs = [entry_point], + main = entry_point, + deps = deps, + ) + + py_lambda_tars( + name = tars_target, + target = bin_target, + ) + + oci_image( + name = name, + base = base, + cmd = [cmd], + # Only allow building on linux, since we don't want to upload a lambda zip file + # with e.g. macos compiled binaries. + target_compatible_with = ["@platforms//os:linux"], + # N.B. deps layer appears first since it's larger and changes less frequently. + tars = [ + "{}.deps".format(tars_target), + tars_target, + ], + ) diff --git a/aws/private/py_lambda.bzl b/aws/private/py_lambda.bzl new file mode 100644 index 0000000..40e469f --- /dev/null +++ b/aws/private/py_lambda.bzl @@ -0,0 +1,116 @@ +"Rule to produce tar files with py_binary deps and app" + +# buildifier: disable=bzl-visibility +load( + "@rules_pkg//pkg/private:pkg_files.bzl", + "add_empty_file", + "add_single_file", + "write_manifest", +) + +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 + +def _py_lambda_tar_impl(ctx): + deps = ctx.attr.target[DefaultInfo].default_runfiles.files + content_map = {} # content handled in the manifest + files = [] # Files needed by rule implementation at runtime + args = ctx.actions.args() + args.add("--output", ctx.outputs.output.path) + + for dep in deps.to_list(): + short_path = _short_path(dep) + + if dep.owner.workspace_name == "" and ctx.attr.kind == "app": + add_single_file( + content_map, + ctx.attr.prefix + "/" + dep.short_path, + dep, + ctx.label, + ) + elif short_path.startswith("site-packages") and ctx.attr.kind == "deps": + short_path = short_path[len("site-packages"):] + add_single_file( + content_map, + ctx.attr.prefix + short_path, + dep, + ctx.label, + ) + + if ctx.attr.kind == "app" and ctx.attr.init_files: + path = "" + for dir in ctx.attr.init_files.split("/"): + path = path + "/" + dir + add_empty_file( + content_map, + ctx.attr.prefix + path + "/__init__.py", + ctx.label, + ) + + manifest_file = ctx.actions.declare_file(ctx.label.name + ".manifest") + files.append(manifest_file) + write_manifest(ctx, manifest_file, content_map) + args.add("--manifest", manifest_file.path) + args.add("--directory", "/") + args.set_param_file_format("flag_per_line") + args.use_param_file("@%s", use_always = False) + inputs = depset(direct = files, transitive = [deps]) + ctx.actions.run( + outputs = [ctx.outputs.output], + inputs = inputs, + executable = ctx.executable._tar, + arguments = [args], + progress_message = "Creating archive...", + mnemonic = "PackageTar", + ) + + out = depset(direct = [ctx.outputs.output]) + return [ + DefaultInfo(files = out), + OutputGroupInfo(all_files = out), + ] + +_py_lambda_tar = rule( + implementation = _py_lambda_tar_impl, + attrs = { + "target": attr.label( + # require PyRuntimeInfo provider to be sure it's a py_binary ? + ), + "_tar": attr.label( + default = Label("@rules_pkg//pkg/private/tar:build_tar"), + cfg = "exec", + executable = True, + ), + "prefix": attr.string(doc = "path prefix for each entry in the tar"), + "init_files": attr.string(doc = "path where __init__ files will be placed"), + "kind": attr.string(values = ["app", "deps"]), + "output": attr.output(), + }, +) + +def py_lambda_tars(name, target, prefix = "/var/task", init_files = "examples/python_lambda", **kwargs): + _py_lambda_tar( + name = name, + kind = "app", + target = target, + prefix = prefix, + init_files = init_files, + output = name + ".app.tar", + **kwargs + ) + + _py_lambda_tar( + name = name + ".deps", + kind = "deps", + target = target, + prefix = prefix, + output = name + ".deps.tar", + **kwargs + ) diff --git a/aws/repositories.oci.bzl b/aws/repositories.oci.bzl new file mode 100644 index 0000000..f0afd30 --- /dev/null +++ b/aws/repositories.oci.bzl @@ -0,0 +1,25 @@ +"Dependencies to fetch from remote docker registries" + +load("@rules_oci//oci:pull.bzl", "oci_pull") + +# As of 30 August 2023 +PY_LAMBDA_LATEST = "sha256:489d4abc8644060e2e16db2ffaaafa157359761feaf9438bf26ed88e37e43d9c" + +# See https://docs.aws.amazon.com/lambda/latest/dg/python-image.html#python-image-base +def aws_py_lambda_repositories(digest = PY_LAMBDA_LATEST): + oci_pull( + name = "aws_lambda_python", + # tag = "3.11", + digest = digest, + platforms = ["linux/arm64/v8", "linux/amd64"], + image = "public.ecr.aws/lambda/python", + ) + +def _aws_py_lambda_impl(_): + aws_py_lambda_repositories() + +aws_py_lambda = module_extension( + implementation = _aws_py_lambda_impl, + # TODO: allow bzlmod users to control the digest + # tag_classes = {"digest": digest}, +) diff --git a/examples/python_lambda/BUILD.bazel b/examples/python_lambda/BUILD.bazel new file mode 100644 index 0000000..19183da --- /dev/null +++ b/examples/python_lambda/BUILD.bazel @@ -0,0 +1,28 @@ +"Example Python AWS lambda using a container" + +load("@aspect_rules_aws//aws:defs.bzl", "aws_py_lambda") +load("@rules_oci//oci:defs.bzl", "oci_tarball") +load("@rules_python//python:pip.bzl", "compile_pip_requirements") +load("@pip//:requirements.bzl", "requirement") + +compile_pip_requirements( + name = "requirements", +) + +aws_py_lambda( + name = "image", + entry_point = "lambda_function.py", + deps = [requirement("requests")], +) + +# Manually verify the image in a local container: +# $ bazel run //examples/python_lambda:tarball +# $ docker run -p 9000:8080 --rm aws_lambda_hello_world:latest +# (in another terminal) +# $ curl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}' +oci_tarball( + name = "tarball", + image = ":image", + repo_tags = ["aws_lambda_hello_world:latest"], + visibility = [":__subpackages__"], +) diff --git a/examples/python_lambda/README.md b/examples/python_lambda/README.md new file mode 100644 index 0000000..536bdf2 --- /dev/null +++ b/examples/python_lambda/README.md @@ -0,0 +1,7 @@ +# AWS Lambda example in Python + +This is a reference project for python AWS Lambda: + +- Generates python container image to be deployed to AWS Lambda +- Integration test running AWS Lambda locally in docker using Runtime Interface Emulator +- Test to verify container image file structure diff --git a/examples/python_lambda/lambda_function.py b/examples/python_lambda/lambda_function.py new file mode 100644 index 0000000..5dfbb2f --- /dev/null +++ b/examples/python_lambda/lambda_function.py @@ -0,0 +1,9 @@ +# Copied from https://docs.aws.amazon.com/lambda/latest/dg/python-image.html#python-image-instructions +import requests +import sys + +def handler(event, context): + r = requests.get("https://www.example.com") + print(r.ok) + + return 'Hello from AWS Lambda using Python' + sys.version + '!' diff --git a/examples/python_lambda/requirements.in b/examples/python_lambda/requirements.in new file mode 100644 index 0000000..13f07af --- /dev/null +++ b/examples/python_lambda/requirements.in @@ -0,0 +1,3 @@ +boto3 +testcontainers +pytest diff --git a/examples/python_lambda/requirements.txt b/examples/python_lambda/requirements.txt new file mode 100644 index 0000000..92f3dc8 --- /dev/null +++ b/examples/python_lambda/requirements.txt @@ -0,0 +1,241 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# bazel run //examples/python_lambda:requirements.update +# +boto3==1.28.57 \ + --hash=sha256:5ddf24cf52c7fb6aaa332eaa08ae8c2afc8f2d1e8860680728533dd573904e32 \ + --hash=sha256:e2d2824ba6459b330d097e94039a9c4f96ae3f4bcdc731d620589ad79dcd16d3 + # via -r examples/python_lambda/requirements.in +botocore==1.31.57 \ + --hash=sha256:301436174635bec739b225b840fc365ca00e5c1a63e5b2a19ee679d204e01b78 \ + --hash=sha256:af006248276ff8e19e3ec7214478f6257035eb40aed865e405486500471ae71b + # via + # boto3 + # s3transfer +certifi==2023.7.22 \ + --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 + # via requests +charset-normalizer==3.2.0 \ + --hash=sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96 \ + --hash=sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c \ + --hash=sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710 \ + --hash=sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706 \ + --hash=sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020 \ + --hash=sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252 \ + --hash=sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad \ + --hash=sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329 \ + --hash=sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a \ + --hash=sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f \ + --hash=sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6 \ + --hash=sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4 \ + --hash=sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a \ + --hash=sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46 \ + --hash=sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2 \ + --hash=sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23 \ + --hash=sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace \ + --hash=sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd \ + --hash=sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982 \ + --hash=sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10 \ + --hash=sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2 \ + --hash=sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea \ + --hash=sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09 \ + --hash=sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5 \ + --hash=sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149 \ + --hash=sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489 \ + --hash=sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9 \ + --hash=sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80 \ + --hash=sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592 \ + --hash=sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3 \ + --hash=sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6 \ + --hash=sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed \ + --hash=sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c \ + --hash=sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200 \ + --hash=sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a \ + --hash=sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e \ + --hash=sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d \ + --hash=sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6 \ + --hash=sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623 \ + --hash=sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669 \ + --hash=sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3 \ + --hash=sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa \ + --hash=sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9 \ + --hash=sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2 \ + --hash=sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f \ + --hash=sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1 \ + --hash=sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4 \ + --hash=sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a \ + --hash=sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8 \ + --hash=sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3 \ + --hash=sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029 \ + --hash=sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f \ + --hash=sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959 \ + --hash=sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22 \ + --hash=sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7 \ + --hash=sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952 \ + --hash=sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346 \ + --hash=sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e \ + --hash=sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d \ + --hash=sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299 \ + --hash=sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd \ + --hash=sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a \ + --hash=sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3 \ + --hash=sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037 \ + --hash=sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94 \ + --hash=sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c \ + --hash=sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858 \ + --hash=sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a \ + --hash=sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449 \ + --hash=sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c \ + --hash=sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918 \ + --hash=sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1 \ + --hash=sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c \ + --hash=sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac \ + --hash=sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa + # via requests +deprecation==2.1.0 \ + --hash=sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff \ + --hash=sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a + # via testcontainers +docker==6.1.3 \ + --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ + --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 + # via testcontainers +idna==3.4 \ + --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 + # via requests +iniconfig==2.0.0 \ + --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ + --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 + # via pytest +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe + # via + # boto3 + # botocore +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f + # via + # deprecation + # docker + # pytest +pluggy==1.3.0 \ + --hash=sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12 \ + --hash=sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7 + # via pytest +pytest==7.4.2 \ + --hash=sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002 \ + --hash=sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069 + # via -r examples/python_lambda/requirements.in +python-dateutil==2.8.2 \ + --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \ + --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 + # via botocore +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 + # via docker +s3transfer==0.7.0 \ + --hash=sha256:10d6923c6359175f264811ef4bf6161a3156ce8e350e705396a7557d6293c33a \ + --hash=sha256:fd3889a66f5fe17299fe75b82eae6cf722554edca744ca5d5fe308b104883d2e + # via boto3 +six==1.16.0 \ + --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ + --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 + # via python-dateutil +testcontainers==3.7.1 \ + --hash=sha256:7f48cef4bf0ccd78f1a4534d4b701a003a3bace851f24eae58a32f9e3f0aeba0 + # via -r examples/python_lambda/requirements.in +urllib3==1.26.16 \ + --hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \ + --hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14 + # via + # botocore + # docker + # requests +websocket-client==1.6.3 \ + --hash=sha256:3aad25d31284266bcfcfd1fd8a743f63282305a364b8d0948a43bd606acc652f \ + --hash=sha256:6cfc30d051ebabb73a5fa246efdcc14c8fbebbd0330f8984ac3bb6d9edd2ad03 + # via docker +wrapt==1.15.0 \ + --hash=sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0 \ + --hash=sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420 \ + --hash=sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a \ + --hash=sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c \ + --hash=sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079 \ + --hash=sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923 \ + --hash=sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f \ + --hash=sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1 \ + --hash=sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8 \ + --hash=sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86 \ + --hash=sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0 \ + --hash=sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364 \ + --hash=sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e \ + --hash=sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c \ + --hash=sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e \ + --hash=sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c \ + --hash=sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727 \ + --hash=sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff \ + --hash=sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e \ + --hash=sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29 \ + --hash=sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7 \ + --hash=sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72 \ + --hash=sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475 \ + --hash=sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a \ + --hash=sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317 \ + --hash=sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2 \ + --hash=sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd \ + --hash=sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640 \ + --hash=sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98 \ + --hash=sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248 \ + --hash=sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e \ + --hash=sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d \ + --hash=sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec \ + --hash=sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1 \ + --hash=sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e \ + --hash=sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9 \ + --hash=sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92 \ + --hash=sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb \ + --hash=sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094 \ + --hash=sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46 \ + --hash=sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29 \ + --hash=sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd \ + --hash=sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705 \ + --hash=sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8 \ + --hash=sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975 \ + --hash=sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb \ + --hash=sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e \ + --hash=sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b \ + --hash=sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418 \ + --hash=sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019 \ + --hash=sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1 \ + --hash=sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba \ + --hash=sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6 \ + --hash=sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2 \ + --hash=sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3 \ + --hash=sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7 \ + --hash=sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752 \ + --hash=sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416 \ + --hash=sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f \ + --hash=sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1 \ + --hash=sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc \ + --hash=sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145 \ + --hash=sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee \ + --hash=sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a \ + --hash=sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7 \ + --hash=sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b \ + --hash=sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653 \ + --hash=sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0 \ + --hash=sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90 \ + --hash=sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29 \ + --hash=sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6 \ + --hash=sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034 \ + --hash=sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09 \ + --hash=sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559 \ + --hash=sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639 + # via testcontainers diff --git a/examples/python_lambda/test/BUILD.bazel b/examples/python_lambda/test/BUILD.bazel new file mode 100644 index 0000000..7e71238 --- /dev/null +++ b/examples/python_lambda/test/BUILD.bazel @@ -0,0 +1,36 @@ +load("@aspect_rules_py//py:defs.bzl", "py_pytest_main") +load("@container_structure_test//:defs.bzl", "container_structure_test") +load("@pip//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_test") + +# Verify we built a container with files in expected locations +container_structure_test( + name = "smoke_test", + configs = ["container_structure_test.yaml"], + driver = "tar", + image = "//examples/python_lambda:tarball", +) + +py_pytest_main(name = "__test__") + +py_test( + name = "integration_test", + size = "small", + srcs = [ + "integration_test.py", + ":__test__", + ], + data = ["//examples/python_lambda:tarball"], + main = ":__test__.py", + tags = [ + "requires-docker", + "requires-network", + ], + deps = [ + ":__test__", + requirement("docker"), + requirement("pytest"), + requirement("requests"), + requirement("testcontainers"), + ], +) diff --git a/examples/python_lambda/test/container_structure_test.yaml b/examples/python_lambda/test/container_structure_test.yaml new file mode 100644 index 0000000..2efa64c --- /dev/null +++ b/examples/python_lambda/test/container_structure_test.yaml @@ -0,0 +1,12 @@ +# See https://github.com/GoogleContainerTools/container-structure-test +schemaVersion: 2.0.0 + +# Verify that we put files in the paths dictated by the AWS instructions: +# https://docs.aws.amazon.com/lambda/latest/dg/python-image.html#python-image-instructions +fileExistenceTests: + - name: lambda + path: "/var/task/examples/python_lambda/lambda_function.py" + - name: requests + path: "/var/task/requests/api.py" + - name: requests metadata + path: "/var/task/requests-2.31.0.dist-info/METADATA" diff --git a/examples/python_lambda/test/integration_test.py b/examples/python_lambda/test/integration_test.py new file mode 100644 index 0000000..8ae92c9 --- /dev/null +++ b/examples/python_lambda/test/integration_test.py @@ -0,0 +1,41 @@ +import json + +import docker +import pytest +import requests +from testcontainers.core.container import DockerContainer + +TAR_PATH = "examples/python_lambda/tarball/tarball.tar" +IMAGE_NAME = "aws_lambda_hello_world:latest" + + +def _load_latest_tarball(): + """ + Load latest image to local Docker images + + This will load the latest tarball to Docker + So that we run the test against the latest image + """ + client = docker.from_env() + with open(TAR_PATH, "rb") as f: + client.images.load(f) + + +def test_thing(): + _load_latest_tarball() + + with DockerContainer( + IMAGE_NAME, + ).with_bind_ports( + container=8080, + host=9000, + ) as container: + # get_exposed_port waits for the container to be ready + # https://github.com/testcontainers/testcontainers-python/blob/2bcb931063e84da1364aa26937778f0e45708000/core/testcontainers/core/container.py#L107-L108 + port = container.get_exposed_port(8080) + data = json.dumps({}) + res = requests.post( + f"http://localhost:{port}/2015-03-31/functions/function/invocations", + data=data, + ) + assert res.json().startswith("Hello from AWS Lambda using Python3")