diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..6e136e3 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,5 @@ +common --enable_bzlmod=0 +common --color=yes +common:ci --noshow_progress +common:ci --noshow_loading_progress +common:ci --test_output=errors diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..f22d756 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +6.5.0 diff --git a/.github/workflows/modules.yml b/.github/workflows/modules.yml new file mode 100644 index 0000000..16f4bff --- /dev/null +++ b/.github/workflows/modules.yml @@ -0,0 +1,59 @@ +name: Build modules + +on: + push: + branches: + - main + pull_request: + paths: + release: + types: + released + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + include: + - arch: x86_64 + runs-on: ubuntu-22.04 + - arch: aarch64 + runs-on: ubuntu-22.04-arm + steps: + - uses: actions/checkout@v4 + - name: Fetch versions + id: deps + run: | + bazel build //wasm/http-hello-world:module + + publish: + if: github.event_name == 'release' + runs-on: ubuntu-24.04 + needs: + - build + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + - name: Download all workflow run artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + - name: Upload release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + for artifact in artifacts/*; do + [[ ! -d "$artifact" ]] && continue + name=$(basename "$artifact") + file=$(ls "$artifact"/*.tar.xz) + echo "Uploading $file as $name.tar.xz" + gh release upload "${{ github.event.release.tag_name }}" \ + "$file#$name.tar.xz" \ + --clobber + done diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..fffa3c3 --- /dev/null +++ b/BUILD @@ -0,0 +1,8 @@ +licenses(["notice"]) # Apache 2 + +pkg_tar( + name = "modules", + deps = [ + "//wasm:modules", + ], +) diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..f1fc2e9 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,16 @@ +workspace(name = "envoy-modules") + +load(":archives.bzl", "load_archives") +load_archives() + +load(":deps.bzl", "resolve_dependencies") +resolve_dependencies() + +load(":toolchains.bzl", "load_toolchains") +load_toolchains() + +load(":packages.bzl", "load_packages") +load_packages() + +load("@toolshed_pip3//:requirements.bzl", "install_deps") +install_deps() diff --git a/archives.bzl b/archives.bzl new file mode 100644 index 0000000..f8b21b1 --- /dev/null +++ b/archives.bzl @@ -0,0 +1,38 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("//:versions.bzl", "VERSIONS") + +def load_github_archives(): + for k, v in VERSIONS.items(): + if type(v) == type("") or v.get("type") != "github_archive": + continue + kwargs = dict(name = k, **v) + # Format string values, but not lists + formatted_kwargs = {} + for arg_k, arg_v in kwargs.items(): + if arg_k in ["repo", "type", "version"]: + continue + if type(arg_v) == type(""): + formatted_kwargs[arg_k] = arg_v.format(**kwargs) + else: + formatted_kwargs[arg_k] = arg_v + http_archive(**formatted_kwargs) + +def load_http_archives(): + for k, v in VERSIONS.items(): + if type(v) == type("") or v.get("type") != "http_archive": + continue + kwargs = dict(name = k, **v) + # Format string values, but not lists + formatted_kwargs = {} + for arg_k, arg_v in kwargs.items(): + if arg_k in ["type", "version"]: + continue + if type(arg_v) == type(""): + formatted_kwargs[arg_k] = arg_v.format(**kwargs) + else: + formatted_kwargs[arg_k] = arg_v + http_archive(**formatted_kwargs) + +def load_archives(): + load_github_archives() + load_http_archives() diff --git a/deps.bzl b/deps.bzl new file mode 100644 index 0000000..cccea40 --- /dev/null +++ b/deps.bzl @@ -0,0 +1,32 @@ +load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies") +load("@rules_perl//perl:deps.bzl", "perl_register_toolchains", "perl_rules_dependencies") +load("@rules_python//python:repositories.bzl", "py_repositories") +load("@toolchains_llvm//toolchain:deps.bzl", "bazel_toolchain_dependencies") +load("@toolchains_llvm//toolchain:rules.bzl", "llvm_toolchain") +load("//:versions.bzl", "VERSIONS") +load("@envoy_toolshed//sysroot:sysroot.bzl", "setup_sysroots") + +def resolve_dependencies( + cmake_version=None, + llvm_version=None, + ninja_version=None, + setup_autotools_toolchain=True): + py_repositories() + bazel_toolchain_dependencies() + rules_foreign_cc_dependencies( + register_preinstalled_tools = True, + register_default_tools = True, + cmake_version = cmake_version or VERSIONS["cmake"], + ninja_version = ninja_version or VERSIONS["ninja"], + ) + perl_rules_dependencies() + perl_register_toolchains() + setup_sysroots() + llvm_toolchain( + name = "llvm_toolchain", + llvm_version = llvm_version or VERSIONS["llvm"], + sysroot = { + "linux-x86_64": "@sysroot_linux_amd64//:sysroot", + "linux-aarch64": "@sysroot_linux_arm64//:sysroot", + } + ) diff --git a/packages.bzl b/packages.bzl new file mode 100644 index 0000000..f44e804 --- /dev/null +++ b/packages.bzl @@ -0,0 +1,12 @@ +load("@bazel_features//:deps.bzl", "bazel_features_deps") +load("@rules_python//python:pip.bzl", "pip_parse") +load("//:versions.bzl", "VERSIONS") + +def load_packages(): + # This is empty - it should be overridden in your repo + pip_parse( + name = "toolshed_pip3", + requirements_lock = "@envoy_toolshed//:requirements.txt", + python_interpreter_target = "@python3_12_host//:python", + ) + bazel_features_deps() diff --git a/toolchains.bzl b/toolchains.bzl new file mode 100644 index 0000000..3f01505 --- /dev/null +++ b/toolchains.bzl @@ -0,0 +1,10 @@ +load("@llvm_toolchain//:toolchains.bzl", "llvm_register_toolchains") +load("@rules_python//python:repositories.bzl", "python_register_toolchains") +load("//:versions.bzl", "VERSIONS") + +def load_toolchains(): + llvm_register_toolchains() + python_register_toolchains( + name = "python%s" % VERSIONS["python"].replace(".", "_"), + python_version = VERSIONS["python"].replace("-", "_"), + ) diff --git a/versions.bzl b/versions.bzl new file mode 100644 index 0000000..985ef7d --- /dev/null +++ b/versions.bzl @@ -0,0 +1,65 @@ + +VERSIONS = { + "aspect_bazel_lib": { + "type": "github_archive", + "repo": "aspect-build/bazel-lib", + "version": "2.16.0", + "sha256": "092f841dd9ea8e736ea834f304877a25190a762d0f0a6c8edac9f94aac8bbf16", + "strip_prefix": "bazel-lib-{version}", + "url": "https://github.com/{repo}/archive/v{version}.tar.gz", + }, + + "bazel_skylib": { + "type": "github_archive", + "repo": "bazelbuild/bazel-skylib", + "version": "1.4.2", + "sha256": "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa", + "url": "https://github.com/{repo}/releases/download/{version}/bazel-skylib-{version}.tar.gz", + }, + + "envoy": { + "type": "github_archive", + "repo": "envoyproxy/envoy", + "version": "60c29b959217d66ddb54732df78e45ec767df427", + "sha256": "f2b3dec8eb49ab235ca3c60c60a6ea4b1bd58cc4f8ea9302f7bae817eb472d88", + "urls": ["https://github.com/{repo}/archive/{version}.tar.gz"], + "strip_prefix": "envoy-{version}", + }, + + "envoy_toolshed": { + "type": "github_archive", + "repo": "envoyproxy/toolshed", + "version": "0.3.3", + "sha256": "1ac69d5b1cbc138f779fc3858f06a6777455136260e1144010f0b51880f69814", + "urls": ["https://github.com/{repo}/archive/bazel-v{version}.tar.gz"], + "patch_args": ["-p1"], + "strip_prefix": "toolshed-bazel-v{version}/bazel", + }, + + "rules_python": { + "type": "github_archive", + "repo": "bazelbuild/rules_python", + "version": "1.4.1", + "sha256": "9f9f3b300a9264e4c77999312ce663be5dee9a56e361a1f6fe7ec60e1beef9a3", + "url": "https://github.com/{repo}/releases/download/{version}/{name}-{version}.tar.gz", + "strip_prefix": "{name}-{version}", + }, + + "rules_foreign_cc": { + "type": "github_archive", + "repo": "bazelbuild/rules_foreign_cc", + "version": "0.14.0", + "sha256": "e0f0ebb1a2223c99a904a565e62aa285bf1d1a8aeda22d10ea2127591624866c", + "url": "https://github.com/{repo}/releases/download/{version}/{name}-{version}.tar.gz", + "strip_prefix": "{name}-{version}", + }, + + "toolchains_llvm": { + "type": "github_archive", + "repo": "bazel-contrib/toolchains_llvm", + "version": "1.4.0", + "sha256": "fded02569617d24551a0ad09c0750dc53a3097237157b828a245681f0ae739f8", + "url": "https://github.com/{repo}/releases/download/v{version}/{name}-v{version}.tar.gz", + "strip_prefix": "{name}-v{version}", + }, +} diff --git a/wasm/BUILD b/wasm/BUILD new file mode 100644 index 0000000..a291d5d --- /dev/null +++ b/wasm/BUILD @@ -0,0 +1,8 @@ +licenses(["notice"]) # Apache 2 + +pkg_tar( + name = "modules", + deps = [ + "//wasm/http-hello-world:module", + ], +) diff --git a/wasm/http-hello-world/BUILD b/wasm/http-hello-world/BUILD new file mode 100644 index 0000000..178da94 --- /dev/null +++ b/wasm/http-hello-world/BUILD @@ -0,0 +1,23 @@ +load("@bazel_skylib//lib:selects.bzl", "selects") +load("@envoy//bazel/wasm:wasm.bzl", "envoy_wasm_cc_binary") + +licenses(["notice"]) # Apache 2 + +exports_files(["module.rst", "module.yaml"]) + +envoy_wasm_cc_binary( + name = "hello.wasm", + srcs = ["hello.cc"], +) + +filegroup( + name = "files", + srcs = glob(["**/*"], exclude = ["module.rst", "module.yaml", "BUILD"]), + visibility = ["//visibility:public"], +) + +alias( + name = "module", + actual = ":hello.wasm", + visibility = ["//visibility:public"], +) diff --git a/wasm/http-hello-world/hello.cc b/wasm/http-hello-world/hello.cc new file mode 100644 index 0000000..0647827 --- /dev/null +++ b/wasm/http-hello-world/hello.cc @@ -0,0 +1,91 @@ +// NOLINT(namespace-envoy) +#include +#include +#include + +#include "proxy_wasm_intrinsics.h" + +class ExampleRootContext : public RootContext { +public: + explicit ExampleRootContext(uint32_t id, std::string_view root_id) : RootContext(id, root_id) {} + + bool onStart(size_t) override; + bool onConfigure(size_t) override; + void onTick() override; +}; + +class ExampleContext : public Context { +public: + explicit ExampleContext(uint32_t id, RootContext* root) : Context(id, root) {} + + void onCreate() override; + FilterHeadersStatus onRequestHeaders(uint32_t headers, bool end_of_stream) override; + FilterDataStatus onRequestBody(size_t body_buffer_length, bool end_of_stream) override; + FilterHeadersStatus onResponseHeaders(uint32_t headers, bool end_of_stream) override; + FilterDataStatus onResponseBody(size_t body_buffer_length, bool end_of_stream) override; + void onDone() override; + void onLog() override; + void onDelete() override; +}; +static RegisterContextFactory register_ExampleContext(CONTEXT_FACTORY(ExampleContext), + ROOT_FACTORY(ExampleRootContext), + "my_root_id"); + +bool ExampleRootContext::onStart(size_t) { + LOG_TRACE("onStart"); + return true; +} + +bool ExampleRootContext::onConfigure(size_t) { + LOG_TRACE("onConfigure"); + proxy_set_tick_period_milliseconds(1000); // 1 sec + return true; +} + +void ExampleRootContext::onTick() { LOG_TRACE("onTick"); } + +void ExampleContext::onCreate() { LOG_WARN(std::string("onCreate " + std::to_string(id()))); } + +FilterHeadersStatus ExampleContext::onRequestHeaders(uint32_t, bool) { + LOG_DEBUG(std::string("onRequestHeaders ") + std::to_string(id())); + auto result = getRequestHeaderPairs(); + auto pairs = result->pairs(); + LOG_INFO(std::string("headers: ") + std::to_string(pairs.size())); + for (auto& p : pairs) { + LOG_INFO(std::string(p.first) + std::string(" -> ") + std::string(p.second)); + } + return FilterHeadersStatus::Continue; +} + +FilterHeadersStatus ExampleContext::onResponseHeaders(uint32_t, bool) { + LOG_DEBUG(std::string("onResponseHeaders ") + std::to_string(id())); + auto result = getResponseHeaderPairs(); + auto pairs = result->pairs(); + LOG_INFO(std::string("headers: ") + std::to_string(pairs.size())); + for (auto& p : pairs) { + LOG_INFO(std::string(p.first) + std::string(" -> ") + std::string(p.second)); + } + addResponseHeader("X-Wasm-custom", "FOO"); + replaceResponseHeader("content-type", "text/plain; charset=utf-8"); + removeResponseHeader("content-length"); + return FilterHeadersStatus::Continue; +} + +FilterDataStatus ExampleContext::onRequestBody(size_t body_buffer_length, + bool /* end_of_stream */) { + auto body = getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_buffer_length); + LOG_ERROR(std::string("onRequestBody ") + std::string(body->view())); + return FilterDataStatus::Continue; +} + +FilterDataStatus ExampleContext::onResponseBody(size_t body_buffer_length, + bool /* end_of_stream */) { + setBuffer(WasmBufferType::HttpResponseBody, 0, body_buffer_length, "Hello, world\n"); + return FilterDataStatus::Continue; +} + +void ExampleContext::onDone() { LOG_WARN(std::string("onDone " + std::to_string(id()))); } + +void ExampleContext::onLog() { LOG_WARN(std::string("onLog " + std::to_string(id()))); } + +void ExampleContext::onDelete() { LOG_WARN(std::string("onDelete " + std::to_string(id()))); } diff --git a/wasm/http-hello-world/module.rst b/wasm/http-hello-world/module.rst new file mode 100644 index 0000000..1015007 --- /dev/null +++ b/wasm/http-hello-world/module.rst @@ -0,0 +1,14 @@ +Download +-------- + + +Running +------- + + +Configuration +------------- + + +Building +-------- diff --git a/wasm/http-hello-world/module.yaml b/wasm/http-hello-world/module.yaml new file mode 100644 index 0000000..d88e484 --- /dev/null +++ b/wasm/http-hello-world/module.yaml @@ -0,0 +1,12 @@ +name: wasm-http-hello-world +title: Hello world (Wasm) +description: | + Wasm Hello world HTTP filter +type: wasm +language: c++ +labels: +- wasm +- http +- hello +- world +- c++