Skip to content

Add a tested, docs-first native filter build example (filter-cc) using bzlmod #1389

Description

@phlax

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

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions