Context
envoyproxy/envoy#47155 (docs/bazel: Switch to bzlmod) moves the Envoy docs build to bzlmod. Once it lands, the final phase of the bzlmod migration is unblocked.
It has been agreed that a hard blocker for completing the migration is that we provide an example, somewhere, showing how a downstream developer builds an Envoy extension using bzlmod.
- Historically this was covered by
envoy-filter-example, which has long since stopped being maintained.
- This repo already has one build example —
wasm-cc — which was recently brought back to working order and is the natural model for a new example.
Whichever path is taken, the end state is the same: a full, tested example in this repo. Examples are docs-first, so a working example here covers the documentation requirement as well.
A dynamic modules (dynmod) example will also be added at some point; the structure/testing approach chosen here should be reusable for that.
Principles
- Docs-first. The example is the documentation.
example.rst + README.md + the config/MODULE.bazel are what people read and copy.
- Must be tested. Every example has a
verify.sh and is exercised in CI.
- Tests test the docs — not the other way round. The tested artefacts must be exactly what is documented (the
MODULE.bazel, BUILD, envoy.yaml, etc. that users see). We should not maintain a separate "real" build that the docs merely describe.
Proposal: filter-cc
A native HTTP filter built and statically linked into Envoy with bzlmod, modelled on wasm-cc.
filter-cc/
├── .bazelrc
├── .bazelversion
├── MODULE.bazel # the thing people actually copy
├── BUILD
├── http_filter.proto # filter config proto
├── http_filter.h / .cc # StreamDecoderFilter that adds a header
├── http_filter_config.cc # NamedHttpFilterConfigFactory + REGISTER_FACTORY
├── http_filter_integration_test.cc
├── envoy.yaml # uses the filter
├── docker-compose.yaml # proxy (from built binary) + echo backend
├── docker-compose-build.yaml # builds the binary via shared/build, as wasm-cc does
├── Dockerfile-proxy
├── verify.sh
├── example.rst
└── README.md
Plus .bcr/filter-cc/{presubmit.yml,source.template.json} so it is published to the registry alongside wasm-cc.
MODULE.bazel (sketch)
module(
name = "envoy-example-filter-cc",
version = "0.2.5.envoy",
)
bazel_dep(name = "envoy", version = "1.40.0-dev")
bazel_dep(name = "envoy_api", version = "1.40.0-dev")
bazel_dep(name = "rules_cc", version = "0.2.22")
bazel_dep(name = "protobuf", version = "35.1.bcr.envoy")
bazel_dep(name = "protoc-gen-validate", version = "1.3.0.envoy")
bazel_dep(name = "rules_pkg", version = "1.1.0")
envoy_toolchains_ext = use_extension("@envoy//bazel:extensions.bzl", "envoy_toolchains_extension")
use_repo(envoy_toolchains_ext, "clang_platform")
# Compiler toolchain as dev_dependency — identical setup to wasm-cc/MODULE.bazel
bazel_dep(name = "toolchains_llvm", version = "1.8.0.envoy", dev_dependency = True)
bazel_dep(name = "envoy_toolshed", version = "0.4.10.envoy", dev_dependency = True)
The exact use_extension wiring (e.g. extension registry / envoy_build_config) needs validating against @envoy//bazel:extensions.bzl on head once #47155 is merged.
BUILD (sketch)
load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_binary", "envoy_cc_extension", "envoy_cc_test")
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
api_proto_package()
envoy_cc_extension(
name = "http_filter_lib",
srcs = ["http_filter.cc"],
hdrs = ["http_filter.h"],
deps = [
":pkg_cc_proto",
"@envoy//envoy/http:filter_interface",
"@envoy//source/common/http:header_map_lib",
],
)
envoy_cc_extension(
name = "http_filter_config",
srcs = ["http_filter_config.cc"],
deps = [
":http_filter_lib",
"@envoy//envoy/registry",
"@envoy//envoy/server:filter_config_interface",
],
)
envoy_cc_binary(
name = "envoy",
repository = "@envoy",
deps = [
":http_filter_config",
"@envoy//source/exe:envoy_main_entry_lib",
],
)
envoy_cc_test(
name = "http_filter_integration_test",
srcs = ["http_filter_integration_test.cc"],
repository = "@envoy",
deps = [
":http_filter_config",
"@envoy//test/integration:http_integration_lib",
],
)
Filter content
Simplest credible thing: a decoder filter that reads key/val from its proto config and adds it as a response header — the same as the old http-filter-example, which readers may recognise.
Testing
- Standalone (
verify.sh): docker compose -f docker-compose-build.yaml run build builds //:envoy via shared/build (same pattern as wasm_compile_update), the binary is copied into Dockerfile-proxy, docker compose up, and the test asserts the filter's header appears on responses. envoy_cc_test provides a real integration test for bazel test //... and BCR presubmit.
- Against envoy dev head: add
bazel_dep(name = "envoy-example-filter-cc", ...) to envoy-examples and to envoy's docs/MODULE.bazel, so the local_path_override on envoy builds/tests it against the current checkout — the same mechanism as wasm-cc.
- Docs:
example.rst goes into the sandboxes toctree via the existing docs_rst / examples_docs genrule; envoy.yaml into :configs for the config test.
Related: test the build examples against envoy dev head
The wasm-cc example is currently not tested in the envoy repo. This is probably bzlmod+ work, but the build examples (wasm-cc, filter-cc, and later dynmod) should be tested against dev head in envoy, as is already done for the rest of the examples. The head-testing mechanism above should be set up so it covers all build examples, not just the new one.
Open decisions
- Full binary vs. library-only in envoy CI. Building
//:envoy (a full static link) in verify_examples is heavy. Suggested split: BCR presubmit + verify.sh build the full binary (that is what is being demonstrated); the envoy-side head test only runs http_filter_integration_test, which proves the wiring at a fraction of the cost.
- Filter content. Port the old
http-filter-example header filter vs. inventing something new (suggest porting).
Blocked by
Follow-ups
dynmod example (dynamic modules) using the same docs-first/tested structure.
- Test all build examples (
wasm-cc, filter-cc, dynmod) against envoy dev head.
Context
envoyproxy/envoy#47155 (docs/bazel: Switch to bzlmod) moves the Envoy docs build to bzlmod. Once it lands, the final phase of the bzlmod migration is unblocked.
It has been agreed that a hard blocker for completing the migration is that we provide an example, somewhere, showing how a downstream developer builds an Envoy extension using bzlmod.
envoy-filter-example, which has long since stopped being maintained.wasm-cc— which was recently brought back to working order and is the natural model for a new example.Whichever path is taken, the end state is the same: a full, tested example in this repo. Examples are docs-first, so a working example here covers the documentation requirement as well.
A dynamic modules (
dynmod) example will also be added at some point; the structure/testing approach chosen here should be reusable for that.Principles
example.rst+README.md+ the config/MODULE.bazelare what people read and copy.verify.shand is exercised in CI.MODULE.bazel,BUILD,envoy.yaml, etc. that users see). We should not maintain a separate "real" build that the docs merely describe.Proposal:
filter-ccA native HTTP filter built and statically linked into Envoy with bzlmod, modelled on
wasm-cc.Plus
.bcr/filter-cc/{presubmit.yml,source.template.json}so it is published to the registry alongsidewasm-cc.MODULE.bazel(sketch)The exact
use_extensionwiring (e.g. extension registry /envoy_build_config) needs validating against@envoy//bazel:extensions.bzlon head once #47155 is merged.BUILD(sketch)Filter content
Simplest credible thing: a decoder filter that reads
key/valfrom its proto config and adds it as a response header — the same as the oldhttp-filter-example, which readers may recognise.Testing
verify.sh):docker compose -f docker-compose-build.yaml run buildbuilds//:envoyviashared/build(same pattern aswasm_compile_update), the binary is copied intoDockerfile-proxy,docker compose up, and the test asserts the filter's header appears on responses.envoy_cc_testprovides a real integration test forbazel test //...and BCR presubmit.bazel_dep(name = "envoy-example-filter-cc", ...)toenvoy-examplesand to envoy'sdocs/MODULE.bazel, so thelocal_path_overrideonenvoybuilds/tests it against the current checkout — the same mechanism aswasm-cc.example.rstgoes into the sandboxes toctree via the existingdocs_rst/examples_docsgenrule;envoy.yamlinto:configsfor the config test.Related: test the build examples against envoy dev head
The
wasm-ccexample is currently not tested in the envoy repo. This is probably bzlmod+ work, but the build examples (wasm-cc,filter-cc, and laterdynmod) should be tested against dev head in envoy, as is already done for the rest of the examples. The head-testing mechanism above should be set up so it covers all build examples, not just the new one.Open decisions
//:envoy(a full static link) inverify_examplesis heavy. Suggested split: BCR presubmit +verify.shbuild the full binary (that is what is being demonstrated); the envoy-side head test only runshttp_filter_integration_test, which proves the wiring at a fraction of the cost.http-filter-exampleheader filter vs. inventing something new (suggest porting).Blocked by
MODULE.bazelextension wiring can only be finalised once this is merged.Follow-ups
dynmodexample (dynamic modules) using the same docs-first/tested structure.wasm-cc,filter-cc,dynmod) against envoy dev head.