feat(plugin): add proxywasm plugin (#284)

* feat(plugin): add `proxywasm` plugin

The `proxywasm` plugin is a WASM Filter following the ProxyWasm ABI Specification using the proxywasm go sdk

This allows extensibility with any reverse proxy who implements the ProxyWasm ABI Specification.

The current WASM Filter was successfully tested with APISIX, Envoy, Nginx with ngx_wasm_module from Kong and Istio.

Fixes #145
This commit is contained in:
Alexis Couvreur
2024-06-28 13:22:06 -04:00
parent 54bc622b45
commit 3891027e23
57 changed files with 2164 additions and 2891 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "Go",
"image": "mcr.microsoft.com/devcontainers/go:1.22-bookworm",
"image": "mcr.microsoft.com/devcontainers/go:1.22",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "lts"

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto eol=lf

View File

@@ -210,3 +210,150 @@ jobs:
- name: Test ${{ matrix.provider }}
run: cd plugins/caddy/e2e/${{ matrix.provider }} && bash ./run.sh
build-proxywasm:
name: Build ProxyWasm Plugin once and share it to ProxyWasm E2E jobs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup TinyGo
uses: acifani/setup-tinygo@v2
with:
tinygo-version: '0.31.2'
- name: Build
run: make proxywasm
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: proxywasm-plugin-wasm
path: ./plugins/proxywasm/sablierproxywasm.wasm
proxywasm_apisix_e2e:
name: Run Sablier E2E tests for Proxywasm middleware on Apache APISIX
runs-on: ubuntu-latest
needs:
- build
- build-proxywasm
strategy:
fail-fast: false
matrix:
provider: [ docker ] #, docker_swarm, kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: |
go.sum
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Download Proxywasm artifact
uses: actions/download-artifact@v4
with:
name: proxywasm-plugin-wasm
path: ./plugins/proxywasm
- name: Test ${{ matrix.provider }}
run: cd plugins/proxywasm/e2e/apacheapisix/${{ matrix.provider }} && bash ./run.sh
proxywasm_envoy_e2e:
name: Run Sablier E2E tests for Proxywasm middleware on Envoy
runs-on: ubuntu-latest
needs:
- build
- build-proxywasm
strategy:
fail-fast: false
matrix:
provider: [ docker ] #, docker_swarm, kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: |
go.sum
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Download Proxywasm artifact
uses: actions/download-artifact@v4
with:
name: proxywasm-plugin-wasm
path: ./plugins/proxywasm
- name: Test ${{ matrix.provider }}
run: cd plugins/proxywasm/e2e/envoy/${{ matrix.provider }} && bash ./run.sh
proxywasm_nginx_e2e:
name: Run Sablier E2E tests for Proxywasm middleware on Nginx
runs-on: ubuntu-latest
needs:
- build
- build-proxywasm
strategy:
fail-fast: false
matrix:
provider: [ docker ] #, docker_swarm, kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: |
go.sum
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Download Proxywasm artifact
uses: actions/download-artifact@v4
with:
name: proxywasm-plugin-wasm
path: ./plugins/proxywasm
- name: Test ${{ matrix.provider }}
run: cd plugins/proxywasm/e2e/nginx/${{ matrix.provider }} && bash ./run.sh

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ sablier.yaml
./plugins/traefik/e2e/kubeconfig.yaml
node_modules
.DS_Store
*.wasm
kubeconfig.yaml

15
.golangci.yml Normal file
View File

@@ -0,0 +1,15 @@
linters:
enable:
- dupword # Checks for duplicate words in the source code.
- goimports
- gosec
- gosimple
- govet
- importas
- ineffassign
- misspell
- revive
- staticcheck
- typecheck
- unconvert
- unused

View File

@@ -45,6 +45,12 @@ caddy:
docker build -t caddy:local plugins/caddy
release: $(PLATFORMS)
proxywasm:
go generate ./plugins/proxywasm
tinygo build -ldflags "-X 'main.Version=$(VERSION)'" -o ./plugins/proxywasm/sablierproxywasm.wasm -scheduler=none -target=wasi ./plugins/proxywasm
cp ./plugins/proxywasm/sablierproxywasm.wasm ./sablierproxywasm_$(VERSION).wasm
.PHONY: release $(PLATFORMS)
LAST = 0.0.0

View File

@@ -1,7 +1,5 @@
![Latest Build](https://img.shields.io/github/actions/workflow/status/acouvreur/sablier/build.yml?style=flat-square&branch=main)![Go Report](https://goreportcard.com/badge/github.com/acouvreur/sablier?style=flat-square) ![Go Version](https://img.shields.io/github/go-mod/go-version/acouvreur/sablier?style=flat-square) ![Latest Release](https://img.shields.io/github/release/acouvreur/sablier/all.svg?style=flat-square)
*This website is still a work in progress and is based on the **beta** branch version*
# Sablier - Scale to Zero
Sablier is a **free** and **open-source** software that can scale your workloads on demand.
@@ -19,7 +17,7 @@ Which allows you to start your containers on demand and shut them down automatic
## Glossary
I'll use these terms in order to be provider agnostic.
I'll use these terms in order to be provider-agnostic.
- **Session**: A Session is a set of **instances**
- **Instance**: An instance is either a docker container, docker swarm service, kubernetes deployment or kubernetes statefulset

View File

@@ -8,14 +8,18 @@
- [Versioning](/versioning)
- **Providers**
- [Overview](/providers/overview)
- [Docker](/providers/docker)
- [Docker Swarm](/providers/docker_swarm)
- [Kubernetes](/providers/kubernetes)
- [<img src="assets/img/docker.svg" height=24px width=24px />Docker](/providers/docker)
- [<img src="assets/img/docker_swarm.png" height=24px width=24px />Docker Swarm](/providers/docker_swarm)
- [<img src="assets/img/kubernetes.png" height=24px width=24px />Kubernetes](/providers/kubernetes)
- **Reverse Proxy Plugins**
- [Overview](/plugins/overview)
- [Traefik](/plugins/traefik)
- [Nginx](/plugins/nginx)
- [Caddy](/plugins/caddy)
- [<img src="assets/img/apacheapisix.png" height=24px width=24px />Apache APISIX](/plugins/apacheapisix)
- [<img src="assets/img/caddy.png" height=24px width=24px />Caddy](/plugins/caddy)
- [<img src="assets/img/envoy.png" height=24px width=24px />Envoy](/plugins/envoy)
- [<img src="assets/img/istio.png" height=24px width=24px />Istio](/plugins/istio)
- [<img src="assets/img/nginx.svg" height=24px width=24px />Nginx (NJS)](/plugins/nginx)
- [<img src="assets/img/nginx.svg" height=24px width=24px />Nginx (ProxyWasm)](/plugins/nginx_proxywasm)
- [<img src="assets/img/traefik.png" height=24px /> Traefik](/plugins/traefik)
- **Guides**
- [Overview](/guides/overview)
- [VSCode Server with Traefik and Kubernetes](/guides/code-server-traefik-kubernetes.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

BIN
docs/assets/img/caddy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
docs/assets/img/envoy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
docs/assets/img/istio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_nginx</title><path d="M15.948,2h.065a10.418,10.418,0,0,1,.972.528Q22.414,5.65,27.843,8.774a.792.792,0,0,1,.414.788c-.008,4.389,0,8.777-.005,13.164a.813.813,0,0,1-.356.507q-5.773,3.324-11.547,6.644a.587.587,0,0,1-.657.037Q9.912,26.6,4.143,23.274a.7.7,0,0,1-.4-.666q0-6.582,0-13.163a.693.693,0,0,1,.387-.67Q9.552,5.657,14.974,2.535c.322-.184.638-.379.974-.535" style="fill:#019639"/><path d="M8.767,10.538q0,5.429,0,10.859a1.509,1.509,0,0,0,.427,1.087,1.647,1.647,0,0,0,2.06.206,1.564,1.564,0,0,0,.685-1.293c0-2.62-.005-5.24,0-7.86q3.583,4.29,7.181,8.568a2.833,2.833,0,0,0,2.6.782,1.561,1.561,0,0,0,1.251-1.371q.008-5.541,0-11.081a1.582,1.582,0,0,0-3.152,0c0,2.662-.016,5.321,0,7.982-2.346-2.766-4.663-5.556-7-8.332A2.817,2.817,0,0,0,10.17,9.033,1.579,1.579,0,0,0,8.767,10.538Z" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
docs/assets/img/traefik.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View File

@@ -0,0 +1,33 @@
# Apache APISIX Plugin
The Apache APISIX Plugin is a WASM Plugin written with the Proxy Wasm SDK.
## Provider compatibility grid
| Provider | Dynamic | Blocking |
|-----------------------------------------|:-------:|:--------:|
| [Docker](/providers/docker) | ✅ | ✅ |
| [Docker Swarm](/providers/docker_swarm) | ❓ | ❓ |
| [Kubernetes](/providers/kubernetes) | ❓ | ❓ |
## Install the plugin to Apache APISIX
```yaml
wasm:
plugins:
- name: proxywasm_sablier_plugin
priority: 7997
file: /wasm/sablierproxywasm.wasm # Downloaded WASM Filter path
```
## Configuration
You can have the following configuration:
```yaml
routes:
- uri: "/"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "group": ["my-group"], "session_duration": "1m", "dynamic": { "display_name": "Dynamic Whoami" } }'
```

48
docs/plugins/envoy.md Normal file
View File

@@ -0,0 +1,48 @@
# Envoy Plugin
The Envoy Plugin is a WASM Plugin written with the Proxy Wasm SDK.
## Provider compatibility grid
| Provider | Dynamic | Blocking |
|-----------------------------------------|:-------:|:--------:|
| [Docker](/providers/docker) | ✅ | ✅ |
| [Docker Swarm](/providers/docker_swarm) | ❓ | ❓ |
| [Kubernetes](/providers/kubernetes) | ❓ | ❓ |
## Configuration
You can have the following configuration:
```yaml
http_filters:
- name: sablier-wasm-whoami-dynamic
disabled: true
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "sablier-wasm-whoami-dynamic"
root_id: "sablier-wasm-whoami-dynamic"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"names": ["docker_classic_e2e-whoami-1"],
"session_duration": "1m",
"dynamic": {
"display_name": "Dynamic Whoami",
"theme": "hacker-terminal"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-whoami-dynamic"
code:
local:
filename: "/etc/sablierproxywasm.wasm"
configuration: { }
```

45
docs/plugins/istio.md Normal file
View File

@@ -0,0 +1,45 @@
# Istio Plugin
The Istio Plugin is a WASM Plugin written with the Proxy Wasm SDK.
## Provider compatibility grid
| Provider | Dynamic | Blocking |
|-----------------------------------------|:-------:|:--------:|
| [Docker](/providers/docker) | ❌ | ❌ |
| [Docker Swarm](/providers/docker_swarm) | ❌ | ❌ |
| [Kubernetes](/providers/kubernetes) | ✅ | ✅ |
## Configuration
You can have the following configuration:
!> This only works for ingress gateways.
!> Attaching this filter to a side-car would not work because the side-car itself gets shutdown on scaling to zero.
```yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: sablier-wasm-whoami-dynamic
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
url: file:///opt/filters/sablierproxywasm.wasm/..data/sablierproxywasm.wasm
# Use https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#WasmPlugin-TrafficSelector
# To specify which service to apply this filter only
phase: UNSPECIFIED_PHASE
pluginConfig:
{
"sablier_url": "sablier.sablier-system.svc.cluster.local",
"cluster": "outbound|10000||sablier.sablier-system.svc.cluster.local",
"names": [ "deployment_default_whoami_1" ],
"session_duration": "1m",
"dynamic": {
"display_name": "Dynamic Whoami",
"theme": "hacker-terminal"
}
}
```

View File

@@ -0,0 +1,79 @@
# Nginx Plugin
The Nginx Plugin is a WASM Plugin written with the Proxy Wasm SDK.
## Provider compatibility grid
| Provider | Dynamic | Blocking |
|-----------------------------------------|:-------:|:--------:|
| [Docker](/providers/docker) | ✅ | ✅ |
| [Docker Swarm](/providers/docker_swarm) | ❓ | ❓ |
| [Kubernetes](/providers/kubernetes) | ❓ | ❓ |
# Install ngx_wasm_module
Install https://github.com/Kong/ngx_wasm_module.
Example for a Dockerfile:
```dockerfile
FROM ubuntu:22.04
RUN apt update && apt install libatomic1
ADD https://github.com/Kong/ngx_wasm_module/releases/download/prerelease-0.3.0/wasmx-prerelease-0.3.0-v8-x86_64-ubuntu22.04.tar.gz wasmx.tar.gz
RUN mkdir /etc/nginx
RUN tar -xvf wasmx.tar.gz
RUN mv /wasmx-prerelease-0.3.0-v8-x86_64-ubuntu22.04/* /etc/nginx/
WORKDIR /etc/nginx
CMD [ "./nginx", "-g", "daemon off;" ]
```
## Configuration
```nginx
# nginx.conf
events {}
# nginx master process gets a default 'main' VM
# a new top-level configuration block receives all configuration for this main VM
wasm {
module proxywasm_sablier_plugin /wasm/sablierproxywasm.wasm;
}
http {
access_log /dev/stdout;
# internal docker resolver, see /etc/resolv.conf on proxy container
# needed for docker name resolution
resolver 127.0.0.11 valid=1s ipv6=off;
server {
listen 8080;
location /dynamic {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "dynamic": { "display_name": "Dynamic Whoami", "theme": "hacker-terminal" } }';
# force dns resolution by using a variable
# because container will be restarted and change ip a lot of times
set $proxy_pass_host whoami:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
location /blocking {
wasm_socket_read_timeout 60s; # Blocking hangs the request
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "blocking": { "timeout": "30s" } }';
# force dns resolution by using a variable
# because container will be restarted and change ip a lot of times
set $proxy_pass_host whoami:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
}
}
```

View File

@@ -12,11 +12,24 @@ It leverages the API calls to plugin integration to catch in-flight requests to
## Available Reverse Proxies
| Reverse Proxy | Docker | Docker Swarm mode | Kubernetes | Podman |
| ---------------------------- | :----: | :---------------: | :--------: | :-------------------------------------------------------: |
| [Traefik](/plugins/traefik) | ✅ | ✅ | ✅ | [See #70](https://github.com/acouvreur/sablier/issues/70) |
| [Nginx](/plugins/nginx) | ✅ | ✅ | ❌ |
| Reverse Proxy | Docker | Docker Swarm mode | Kubernetes |
|-------------------------------------------------|:------:|:-----------------:|:----------:|
| [Apache APISIX](/plugins/apacheapisix) | ✅ | ✅ | ✅ |
| [Caddy](/plugins/caddy) | ✅ | ✅ | ❌ |
| [Envoy](/plugins/envoy) | ✅ | ❓ | ❓ |
| [Istio](plugins/istio) | ❌ | ❌ | ⚠️ |
| [Nginx (NJS Module)](/plugins/nginx_njs) | ✅ | ✅ | ✅ |
| [Nginx (WASM Module)](/plugins/nginx_proxywasm) | ✅ | ❓ | ❓ |
| [Traefik](/plugins/traefik) | ✅ | ✅ | ✅ |
| [ProxyWasm](/plugins/proxywasm) | ✅ | ✅ | ✅ |
> ✅ **Fully compatible**
>
> ⚠️ **Partially compatible**
>
> ❓ **Should be compatible (but not tested)**
>
> ❌ **Not compatible**
*Your Reverse Proxy is not on the list? [Open an issue to request the missing reverse proxy integration here!](https://github.com/acouvreur/sablier/issues/new?assignees=&labels=enhancement%2C+reverse-proxy&projects=&template=reverse-proxy-integration-request.md&title=Add+%60%5BREVERSE+PROXY%5D%60+reverse+proxy+integration)*

View File

@@ -13,7 +13,7 @@ A Provider typically have the following capabilities:
## Available providers
| Provider | Name | Details |
| --------------------------------------- | ------------------------- | ---------------------------------------------------------------- |
|-----------------------------------------|---------------------------|------------------------------------------------------------------|
| [Docker](/providers/docker) | `docker` | Stop and start **containers** on demand |
| [Docker Swarm](/providers/docker_swarm) | `docker_swarm` or `swarm` | Scale down to zero and up **services** on demand |
| [Kubernetes](/providers/kubernetes) | `kubernetes` | Scale down and up **deployments** and **statefulsets** on demand |

27
go.sum
View File

@@ -20,7 +20,6 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -30,8 +29,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo=
github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v26.1.4+incompatible h1:vuTpXDuoga+Z38m1OZHzl7NKisKWaWlhjQk7IDPSLsU=
github.com/docker/docker v26.1.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
@@ -83,7 +80,6 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@@ -123,8 +119,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
@@ -203,14 +198,10 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -258,8 +249,6 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
@@ -299,7 +288,6 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -309,8 +297,6 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -340,7 +326,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -361,11 +346,9 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
@@ -396,16 +379,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q=
k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=

View File

@@ -6,4 +6,5 @@ use (
.
./plugins/caddy
./plugins/traefik
./plugins/proxywasm
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
package main
import jsoniter "github.com/json-iterator/tinygo"
type BlockingConfiguration_json struct {
}
func (json BlockingConfiguration_json) Type() interface{} {
var val BlockingConfiguration
return val
}
func (json BlockingConfiguration_json) Unmarshal(iter *jsoniter.Iterator, out interface{}) {
BlockingConfiguration_json_unmarshal(iter, out.(*BlockingConfiguration))
}
func (json BlockingConfiguration_json) Marshal(stream *jsoniter.Stream, val interface{}) {
BlockingConfiguration_json_marshal(stream, val.(BlockingConfiguration))
}
func BlockingConfiguration_json_unmarshal(iter *jsoniter.Iterator, out *BlockingConfiguration) {
more := iter.ReadObjectHead()
for more {
field := iter.ReadObjectField()
if !BlockingConfiguration_json_unmarshal_field(iter, field, out) {
iter.Skip()
}
more = iter.ReadObjectMore()
}
}
func BlockingConfiguration_json_unmarshal_field(iter *jsoniter.Iterator, field string, out *BlockingConfiguration) bool {
switch {
case field == `timeout`:
iter.ReadString(&(*out).Timeout)
return true
}
return false
}
func BlockingConfiguration_json_marshal(stream *jsoniter.Stream, val BlockingConfiguration) {
stream.WriteObjectHead()
BlockingConfiguration_json_marshal_field(stream, val)
stream.WriteObjectTail()
}
func BlockingConfiguration_json_marshal_field(stream *jsoniter.Stream, val BlockingConfiguration) {
stream.WriteObjectField(`timeout`)
stream.WriteString(val.Timeout)
stream.WriteMore()
}

View File

@@ -0,0 +1,132 @@
package main
import jsoniter "github.com/json-iterator/tinygo"
type Config_json struct {
}
func (json Config_json) Type() interface{} {
var val Config
return val
}
func (json Config_json) Unmarshal(iter *jsoniter.Iterator, out interface{}) {
Config_json_unmarshal(iter, out.(*Config))
}
func (json Config_json) Marshal(stream *jsoniter.Stream, val interface{}) {
Config_json_marshal(stream, val.(Config))
}
func Config_json_unmarshal(iter *jsoniter.Iterator, out *Config) {
more := iter.ReadObjectHead()
for more {
field := iter.ReadObjectField()
if !Config_json_unmarshal_field(iter, field, out) {
iter.Skip()
}
more = iter.ReadObjectMore()
}
}
func Config_json_unmarshal_field(iter *jsoniter.Iterator, field string, out *Config) bool {
switch {
case field == `sablier_url`:
iter.ReadString(&(*out).SablierURL)
return true
case field == `cluster`:
iter.ReadString(&(*out).Cluster)
return true
case field == `names`:
Config_array1_json_unmarshal(iter, &(*out).Names)
return true
case field == `group`:
iter.ReadString(&(*out).Group)
return true
case field == `session_duration`:
iter.ReadString(&(*out).SessionDuration)
return true
case field == `dynamic`:
Config_ptr2_json_unmarshal(iter, &(*out).Dynamic)
return true
case field == `blocking`:
Config_ptr3_json_unmarshal(iter, &(*out).Blocking)
return true
}
return false
}
func Config_array1_json_unmarshal (iter *jsoniter.Iterator, out *[]string) {
i := 0
val := *out
more := iter.ReadArrayHead()
for more {
if i == len(val) {
val = append(val, make([]string, 4)...)
}
iter.ReadString(&val[i])
i++
more = iter.ReadArrayMore()
}
if i == 0 {
*out = []string{}
} else {
*out = val[:i]
}
}
func Config_ptr2_json_unmarshal (iter *jsoniter.Iterator, out **DynamicConfiguration) {
var val DynamicConfiguration
DynamicConfiguration_json_unmarshal(iter, &val)
if iter.Error == nil {
*out = &val
}
}
func Config_ptr3_json_unmarshal (iter *jsoniter.Iterator, out **BlockingConfiguration) {
var val BlockingConfiguration
BlockingConfiguration_json_unmarshal(iter, &val)
if iter.Error == nil {
*out = &val
}
}
func Config_json_marshal(stream *jsoniter.Stream, val Config) {
stream.WriteObjectHead()
Config_json_marshal_field(stream, val)
stream.WriteObjectTail()
}
func Config_json_marshal_field(stream *jsoniter.Stream, val Config) {
stream.WriteObjectField(`sablier_url`)
stream.WriteString(val.SablierURL)
stream.WriteMore()
stream.WriteObjectField(`cluster`)
stream.WriteString(val.Cluster)
stream.WriteMore()
stream.WriteObjectField(`names`)
Config_array4_json_marshal(stream, val.Names)
stream.WriteMore()
stream.WriteObjectField(`group`)
stream.WriteString(val.Group)
stream.WriteMore()
stream.WriteObjectField(`session_duration`)
stream.WriteString(val.SessionDuration)
stream.WriteMore()
stream.WriteObjectField(`dynamic`)
if val.Dynamic == nil {
stream.WriteNull()
} else {
DynamicConfiguration_json_marshal(stream, *val.Dynamic)
}
stream.WriteMore()
stream.WriteObjectField(`blocking`)
if val.Blocking == nil {
stream.WriteNull()
} else {
BlockingConfiguration_json_marshal(stream, *val.Blocking)
}
stream.WriteMore()
}
func Config_array4_json_marshal (stream *jsoniter.Stream, val []string) {
if len(val) == 0 {
stream.WriteEmptyArray()
} else {
stream.WriteArrayHead()
for i, elem := range val {
if i != 0 { stream.WriteMore() }
stream.WriteString(elem)
}
stream.WriteArrayTail()
}
}

View File

@@ -0,0 +1,18 @@
FROM golang:1.22 AS build
ADD https://github.com/tinygo-org/tinygo/releases/download/v0.31.2/tinygo_0.31.2_amd64.deb tinygo_amd64.deb
RUN dpkg -i tinygo_amd64.deb
WORKDIR /go/src/sablier/plugins/proxywasm
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . /go/src/sablier/plugins/proxywasm
RUN make
FROM scratch
COPY --from=build /go/src/sablier/plugins/proxywasm/sablierproxywasm.wasm ./plugin.wasm

View File

@@ -0,0 +1,73 @@
package main
import jsoniter "github.com/json-iterator/tinygo"
type DynamicConfiguration_json struct {
}
func (json DynamicConfiguration_json) Type() interface{} {
var val DynamicConfiguration
return val
}
func (json DynamicConfiguration_json) Unmarshal(iter *jsoniter.Iterator, out interface{}) {
DynamicConfiguration_json_unmarshal(iter, out.(*DynamicConfiguration))
}
func (json DynamicConfiguration_json) Marshal(stream *jsoniter.Stream, val interface{}) {
DynamicConfiguration_json_marshal(stream, val.(DynamicConfiguration))
}
func DynamicConfiguration_json_unmarshal(iter *jsoniter.Iterator, out *DynamicConfiguration) {
more := iter.ReadObjectHead()
for more {
field := iter.ReadObjectField()
if !DynamicConfiguration_json_unmarshal_field(iter, field, out) {
iter.Skip()
}
more = iter.ReadObjectMore()
}
}
func DynamicConfiguration_json_unmarshal_field(iter *jsoniter.Iterator, field string, out *DynamicConfiguration) bool {
switch {
case field == `display_name`:
iter.ReadString(&(*out).DisplayName)
return true
case field == `show_details`:
DynamicConfiguration_ptr1_json_unmarshal(iter, &(*out).ShowDetails)
return true
case field == `theme`:
iter.ReadString(&(*out).Theme)
return true
case field == `refresh_frequency`:
iter.ReadString(&(*out).RefreshFrequency)
return true
}
return false
}
func DynamicConfiguration_ptr1_json_unmarshal (iter *jsoniter.Iterator, out **bool) {
var val bool
iter.ReadBool(&val)
if iter.Error == nil {
*out = &val
}
}
func DynamicConfiguration_json_marshal(stream *jsoniter.Stream, val DynamicConfiguration) {
stream.WriteObjectHead()
DynamicConfiguration_json_marshal_field(stream, val)
stream.WriteObjectTail()
}
func DynamicConfiguration_json_marshal_field(stream *jsoniter.Stream, val DynamicConfiguration) {
stream.WriteObjectField(`display_name`)
stream.WriteString(val.DisplayName)
stream.WriteMore()
stream.WriteObjectField(`show_details`)
if val.ShowDetails == nil {
stream.WriteNull()
} else {
stream.WriteBool(*val.ShowDetails)
}
stream.WriteMore()
stream.WriteObjectField(`theme`)
stream.WriteString(val.Theme)
stream.WriteMore()
stream.WriteObjectField(`refresh_frequency`)
stream.WriteString(val.RefreshFrequency)
stream.WriteMore()
}

View File

@@ -0,0 +1,6 @@
build:
go generate
tinygo build -o sablierproxywasm.wasm -scheduler=none -target=wasi ./
docker:
docker build -t acouvreur/sablier-proxy-wasm:latest .

View File

@@ -0,0 +1,9 @@
# Proxy Wasm Sablier Plugin
See more at
- https://github.com/proxy-wasm/spec
- https://github.com/tetratelabs/proxy-wasm-go-sdk
## Prerequisite
- Install TinyGo: https://tinygo.org/getting-started/install/

View File

@@ -0,0 +1,2 @@
# Sablier ProxyWasm Plugin integration with Apache APISIX

View File

@@ -0,0 +1,46 @@
routes:
- uri: "/dynamic/whoami"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "dynamic": { "display_name": "Dynamic Whoami", "theme": "hacker-terminal" } }'
upstream:
type: roundrobin
nodes:
"whoami:80": 1
- uri: "/blocking/whoami"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "blocking": { "timeout": "30s" } }'
upstream:
type: roundrobin
nodes:
"whoami:80": 1
- uri: "/multiple/whoami"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1", "docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Multiple Whoami" } }'
upstream:
type: roundrobin
nodes:
"whoami:80": 1
- uri: "/multiple/nginx"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1", "docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Multiple Whoami" } }'
upstream:
type: roundrobin
nodes:
"nginx:80": 1
- uri: "/healthy/nginx"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Healthy Nginx" } }'
upstream:
type: roundrobin
nodes:
"nginx:80": 1
#END

View File

@@ -0,0 +1,28 @@
services:
apisix:
image: apache/apisix:3.9.0-debian
restart: always
volumes:
- ./config.yaml:/usr/local/apisix/conf/config.yaml:ro
- ./apisix.yaml:/usr/local/apisix/conf/apisix.yaml:ro
- ../../../sablierproxywasm.wasm:/wasm/sablierproxywasm.wasm
ports:
- "8080:9080/tcp"
sablier:
image: acouvreur/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=trace
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: containous/whoami:v1.5.0
nginx:
image: nginx:1.26.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s

View File

@@ -0,0 +1,11 @@
deployment:
role: data_plane
role_data_plane:
config_provider: yaml
wasm:
plugins:
- name: proxywasm_sablier_plugin
priority: 7997
file: /wasm/sablierproxywasm.wasm

View File

@@ -0,0 +1,39 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=compose.yaml
DOCKER_COMPOSE_PROJECT_NAME=docker_classic_e2e
errors=0
echo "Using Docker version:"
docker version
prepare_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME up -d
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME stop whoami nginx
}
destroy_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME down --remove-orphans || true
}
run_docker_classic_test() {
echo "Running Docker Classic Test: $1"
prepare_docker_classic
sleep 2
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/acouvreur/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier apisix
fi
destroy_docker_classic
}
trap destroy_docker_classic EXIT
run_docker_classic_test Test_Dynamic
run_docker_classic_test Test_Blocking
run_docker_classic_test Test_Multiple
run_docker_classic_test Test_Healthy
exit $errors

View File

@@ -0,0 +1,27 @@
services:
envoy:
image: envoyproxy/envoy:v1.30-latest
command: /usr/local/bin/envoy -c /etc/envoy.yaml
volumes:
- ./envoy.yaml:/etc/envoy.yaml
- ../../../sablierproxywasm.wasm:/etc/sablierproxywasm.wasm
ports:
- "8080:8080"
sablier:
image: acouvreur/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=trace
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: containous/whoami:v1.5.0
nginx:
image: nginx:1.26.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s

View File

@@ -0,0 +1,230 @@
static_resources:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
# Dynamic Whoami
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/dynamic/whoami"
route:
cluster: whoami
typed_per_filter_config:
sablier-wasm-whoami-dynamic:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
config: # Note this config field could not be empty because the xDS API requirement.
"@type": type.googleapis.com/google.protobuf.Empty # Empty as a placeholder.
is_optional: true
- match:
path: "/blocking/whoami"
route:
cluster: whoami
typed_per_filter_config:
sablier-wasm-whoami-blocking:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
config: # Note this config field could not be empty because the xDS API requirement.
"@type": type.googleapis.com/google.protobuf.Empty # Empty as a placeholder.
is_optional: true
- match:
prefix: "/multiple/whoami"
route:
cluster: whoami
typed_per_filter_config:
sablier-wasm-multiple:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
config: # Note this config field could not be empty because the xDS API requirement.
"@type": type.googleapis.com/google.protobuf.Empty # Empty as a placeholder.
is_optional: true
- match:
path: "/multiple/nginx"
route:
cluster: nginx
typed_per_filter_config:
sablier-wasm-multiple:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
config: # Note this config field could not be empty because the xDS API requirement.
"@type": type.googleapis.com/google.protobuf.Empty # Empty as a placeholder.
is_optional: true
- match:
path: "/healthy/nginx"
route:
cluster: nginx
typed_per_filter_config:
sablier-wasm-healthy:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
config: # Note this config field could not be empty because the xDS API requirement.
"@type": type.googleapis.com/google.protobuf.Empty # Empty as a placeholder.
is_optional: true
http_filters:
- name: sablier-wasm-whoami-dynamic
disabled: true
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "sablier-wasm-whoami-dynamic"
root_id: "sablier-wasm-whoami-dynamic"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"names": ["docker_classic_e2e-whoami-1"],
"session_duration": "1m",
"dynamic": {
"display_name": "Dynamic Whoami",
"theme": "hacker-terminal"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-whoami-dynamic"
code:
local:
filename: "/etc/sablierproxywasm.wasm"
configuration: { }
- name: sablier-wasm-whoami-blocking
disabled: true
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "sablier-wasm-whoami-blocking"
root_id: "sablier-wasm-whoami-blocking"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"names": ["docker_classic_e2e-whoami-1"],
"session_duration": "1m",
"blocking": {
"timeout": "30s"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-whoami-blocking"
code:
local:
filename: "/etc/sablierproxywasm.wasm"
configuration: { }
- name: sablier-wasm-multiple
disabled: true
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "sablier-wasm-multiple"
root_id: "sablier-wasm-multiple"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"names": ["docker_classic_e2e-whoami-1", "docker_classic_e2e-nginx-1"],
"session_duration": "1m",
"dynamic": {
"display_name": "Multiple Whoami"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-multiple"
code:
local:
filename: "/etc/sablierproxywasm.wasm"
configuration: { }
- name: sablier-wasm-healthy
disabled: true
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "sablier-wasm-healthy"
root_id: "sablier-wasm-healthy"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"names": ["docker_classic_e2e-nginx-1"],
"session_duration": "1m",
"dynamic": {
"display_name": "Healthy Nginx"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-healthy"
code:
local:
filename: "/etc/sablierproxywasm.wasm"
configuration: { }
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: sablier
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: round_robin
load_assignment:
cluster_name: sablier
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: sablier
port_value: 10000
- name: whoami
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: round_robin
load_assignment:
cluster_name: whoami
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: whoami
port_value: 80
- name: nginx
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: round_robin
load_assignment:
cluster_name: nginx
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: nginx
port_value: 80

View File

@@ -0,0 +1,39 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=compose.yaml
DOCKER_COMPOSE_PROJECT_NAME=docker_classic_e2e
errors=0
echo "Using Docker version:"
docker version
prepare_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME up -d
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME stop whoami nginx
}
destroy_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME down --remove-orphans || true
}
run_docker_classic_test() {
echo "Running Docker Classic Test: $1"
prepare_docker_classic
sleep 2
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/acouvreur/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier envoy
fi
destroy_docker_classic
}
trap destroy_docker_classic EXIT
run_docker_classic_test Test_Dynamic
run_docker_classic_test Test_Blocking
run_docker_classic_test Test_Multiple
run_docker_classic_test Test_Healthy
exit $errors

View File

@@ -0,0 +1,27 @@
# Install kubectl
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# Install helm3
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Start k3s
docker compose up -d
sudo chown vscode ./kubeconfig.yaml
chmod 600 ./kubeconfig.yaml
export KUBECONFIG=./kubeconfig.yaml
kubectl create configmap -n istio-system sablier-wasm-plugin --from-file ../../sablierproxywasm.wasm
# Install Istio Helm charts
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
helm install istio-base istio/base -n istio-system --wait
helm install istiod istio/istiod -n istio-system --wait
kubectl label namespace istio-system istio-injection=enabled
helm install istio-ingressgateway istio/gateway --values ./istio-gateway-values.yaml -n istio-system --wait
# Install Sablier
kubectl apply -f ./manifests/sablier.yml
# Build proxywasm
make docker

View File

@@ -0,0 +1,24 @@
version: '3'
services:
server:
image: "rancher/k3s:v1.23.12-k3s1"
command: server --no-deploy traefik
tmpfs:
- /run
- /var/run
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
privileged: true
restart: always
environment:
- K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
- K3S_KUBECONFIG_MODE=666
volumes:
# This is just so that we get the kubeconfig file out
- .:/output
ports:
- 6443:6443 # Kubernetes API Server
- 8080:80 # Ingress controller port 80

View File

@@ -0,0 +1,8 @@
volumes:
- name: wasmfilter
configMap:
name: sablier-wasm-plugin
volumeMounts:
- name: wasmfilter
mountPath: /opt/filters/sablierproxywasm.wasm

View File

@@ -0,0 +1,15 @@
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"

View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
version: v1
template:
metadata:
labels:
app: nginx
version: v1
spec:
containers:
- name: nginx
image: nginx:1.26.0
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
service: nginx
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: nginx
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: nginx
spec:
hosts:
- "*"
gateways:
- gateway.istio-system.svc.cluster.local
http:
- match:
- uri:
prefix: "/multiple/nginx"
- uri:
prefix: "/healthy/nginx"
route:
- destination:
port:
number: 80
host: nginx

View File

@@ -0,0 +1,83 @@
apiVersion: v1
kind: Namespace
metadata:
name: sablier-system
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sablier
namespace: sablier-system
labels:
app: sablier
spec:
replicas: 1
selector:
matchLabels:
app: sablier
template:
metadata:
labels:
app: sablier
spec:
serviceAccountName: sablier
containers:
- name: sablier
image: acouvreur/sablier:local
args: ["start", "--provider.name=kubernetes", "--logging.level=trace"]
ports:
- containerPort: 10000
---
apiVersion: v1
kind: Service
metadata:
name: sablier
namespace: sablier-system
spec:
selector:
app: sablier
ports:
- protocol: TCP
port: 10000
targetPort: 10000
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sablier
namespace: sablier-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sablier
namespace: sablier-system
rules:
- apiGroups:
- apps
- ""
resources:
- deployments
- deployments/scale
- statefulsets
- statefulsets/scale
verbs:
- patch # Scale up and down
- get # Retrieve info about specific dep
- update # Scale up and down
- list # Events
- watch # Events
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sablier
namespace: sablier-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sablier
subjects:
- kind: ServiceAccount
name: sablier
namespace: sablier-system

View File

@@ -0,0 +1,96 @@
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: sablier-wasm-whoami-dynamic
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
url: file:///opt/filters/sablierproxywasm.wasm/..data/sablierproxywasm.wasm
# Use https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#WasmPlugin-TrafficSelector
# To specify which service to apply this filter only
phase: UNSPECIFIED_PHASE
pluginConfig:
{
"sablier_url": "sablier.sablier-system.svc.cluster.local",
"cluster": "outbound|10000||sablier.sablier-system.svc.cluster.local",
"names": [ "deployment_default_whoami_1" ],
"session_duration": "1m",
"dynamic": {
"display_name": "Dynamic Whoami",
"theme": "hacker-terminal"
}
}
---
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: sablier-wasm-whoami-blocking
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
url: file:///opt/filters/sablierproxywasm.wasm/..data/sablierproxywasm.wasm
# Use https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#WasmPlugin-TrafficSelector
# To specify which service to apply this filter only
phase: UNSPECIFIED_PHASE
pluginConfig:
{
"sablier_url": "sablier.sablier-system.svc.cluster.local",
"cluster": "outbound|10000||sablier.sablier-system.svc.cluster.local",
"names": [ "deployment_default_whoami_1" ],
"session_duration": "1m",
"blocking": {
"timeout": "30s"
}
}
---
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: sablier-wasm-multiple
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
url: file:///opt/filters/sablierproxywasm.wasm/..data/sablierproxywasm.wasm
# Use https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#WasmPlugin-TrafficSelector
# To specify which service to apply this filter only
phase: UNSPECIFIED_PHASE
pluginConfig:
{
"sablier_url": "sablier.sablier-system.svc.cluster.local",
"cluster": "outbound|10000||sablier.sablier-system.svc.cluster.local",
"names": [ "deployment_default_whoami_1", "deployment_default_nginx_1" ],
"session_duration": "1m",
"dynamic": {
"display_name": "Multiple Whoami"
}
}
---
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: sablier-wasm-healthy
namespace: istio-system
spec:
selector:
matchLabels:
istio: ingressgateway
url: file:///opt/filters/sablierproxywasm.wasm/..data/sablierproxywasm.wasm
# Use https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/#WasmPlugin-TrafficSelector
# To specify which service to apply this filter only
phase: UNSPECIFIED_PHASE
pluginConfig:
{
"sablier_url": "sablier.sablier-system.svc.cluster.local",
"cluster": "outbound|10000||sablier.sablier-system.svc.cluster.local",
"names": [ "deployment_default_nginx_1" ],
"session_duration": "1m",
"dynamic": {
"display_name": "Healthy Nginx"
}
}

View File

@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami
spec:
replicas: 1
selector:
matchLabels:
app: whoami
version: v1
template:
metadata:
labels:
app: whoami
version: v1
spec:
containers:
- name: whoami
image: containous/whoami:v1.5.0
---
apiVersion: v1
kind: Service
metadata:
name: whoami
labels:
app: whoami
service: whoami
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: whoami
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: whoami
spec:
hosts:
- "*"
gateways:
- gateway.istio-system.svc.cluster.local
http:
- match:
- uri:
prefix: "/dynamic/whoami"
- uri:
prefix: "/blocking/whoami"
- uri:
prefix: "/multiple/whoami"
route:
- destination:
port:
number: 80
host: whoami

View File

@@ -0,0 +1,68 @@
#!/bin/bash
export DOCKER_COMPOSE_FILE=compose.yaml
export DOCKER_COMPOSE_PROJECT_NAME=kubernetes_e2e
errors=0
export KUBECONFIG=./kubeconfig.yaml
echo "Using Docker version:"
docker version
prepare_kubernetes() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME up -d
until kubectl get nodes | grep " Ready "; do sleep 1; done
echo "Loading acouvreur/sablier:local into k3s..."
docker save acouvreur/sablier:local | docker exec -i ${DOCKER_COMPOSE_PROJECT_NAME}-server-1 ctr images import -
echo "Loading succeeded."
}
destroy_kubernetes() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME down --volumes
}
prepare_istio() {
helm repo add istio https://istio-release.storage.googleapis.com/charts
helm repo update
kubectl create namespace istio-system
helm install istio-base istio/base -n istio-system --wait
helm install istiod istio/istiod -n istio-system --wait
kubectl label namespace istio-system istio-injection=enabled
kubectl label namespace default istio-injection=enabled
kubectl create configmap -n istio-system sablier-wasm-plugin --from-file ../../../sablierproxywasm.wasm
helm install istio-ingressgateway istio/gateway --values ./istio-gateway-values.yaml -n istio-system --wait
}
prepare_manifests() {
kubectl apply -f ./manifests
}
destroy_manifests() {
kubectl delete -f ./manifests
}
run_kubernetes_test() {
echo "---- Running Kubernetes Test: $1 ----"
prepare_manifests
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/acouvreur/sablier/e2e; then
errors=1
kubectl -n kube-system logs deployments/sablier-deployment
# kubectl -n kube-system logs deployments/traefik TODO: Log istio
fi
destroy_manifests
}
# trap destroy_kubernetes EXIT
prepare_kubernetes
prepare_istio
# run_kubernetes_test Test_Dynamic
# run_kubernetes_test Test_Blocking
# run_kubernetes_test Test_Multiple
# run_kubernetes_test Test_Healthy
# exit $errors

View File

@@ -0,0 +1,13 @@
FROM ubuntu:22.04
RUN apt update && apt install libatomic1
ADD https://github.com/Kong/ngx_wasm_module/releases/download/prerelease-0.3.0/wasmx-prerelease-0.3.0-v8-x86_64-ubuntu22.04.tar.gz wasmx.tar.gz
RUN mkdir /etc/nginx
RUN tar -xvf wasmx.tar.gz
RUN mv /wasmx-prerelease-0.3.0-v8-x86_64-ubuntu22.04/* /etc/nginx/
WORKDIR /etc/nginx
CMD [ "./nginx", "-g", "daemon off;" ]

View File

@@ -0,0 +1,28 @@
services:
reverseproxy:
build:
context: ..
dockerfile: Dockerfile
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ../../../sablierproxywasm.wasm:/wasm/sablierproxywasm.wasm
ports:
- "8080:8080"
sablier:
image: acouvreur/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=trace
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: containous/whoami:v1.5.0
nginx:
image: nginx:1.27.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s

View File

@@ -0,0 +1,62 @@
# nginx.conf
events {}
# nginx master process gets a default 'main' VM
# a new top-level configuration block receives all configuration for this main VM
wasm {
module proxywasm_sablier_plugin /wasm/sablierproxywasm.wasm;
}
error_log /dev/stdout info;
# each nginx worker process is able to instantiate wasm modules in its subsystems
http {
access_log /dev/stdout;
# internal docker resolver, see /etc/resolv.conf on proxy container
resolver 127.0.0.11 valid=1s ipv6=off;
server {
listen 8080;
location /dynamic/whoami {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "dynamic": { "display_name": "Dynamic Whoami", "theme": "hacker-terminal" } }';
set $proxy_pass_host whoami:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
location /blocking/whoami {
wasm_socket_read_timeout 60s; # Blocking hangs the request
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1"], "session_duration": "1m", "blocking": { "timeout": "30s" } }';
set $proxy_pass_host whoami:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
location /multiple/whoami {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1", "docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Multiple Whoami" } }';
proxy_pass http://whoami:80$request_uri;
proxy_set_header Host localhost:8080; # e2e test compliance
}
location /multiple/nginx {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-whoami-1", "docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Multiple Whoami" } }';
set $proxy_pass_host nginx:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
location /healthy/nginx {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "names": ["docker_classic_e2e-nginx-1"], "session_duration": "1m", "dynamic": { "display_name": "Healthy Nginx" } }';
set $proxy_pass_host nginx:80$request_uri;
proxy_pass http://$proxy_pass_host;
proxy_set_header Host localhost:8080; # e2e test compliance
}
}
}

View File

@@ -0,0 +1,39 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=compose.yaml
DOCKER_COMPOSE_PROJECT_NAME=docker_classic_e2e
errors=0
echo "Using Docker version:"
docker version
prepare_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME up -d
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME stop whoami nginx
}
destroy_docker_classic() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME down --remove-orphans || true
}
run_docker_classic_test() {
echo "Running Docker Classic Test: $1"
prepare_docker_classic
sleep 2
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/acouvreur/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier reverseproxy
fi
destroy_docker_classic
}
trap destroy_docker_classic EXIT
run_docker_classic_test Test_Dynamic
run_docker_classic_test Test_Blocking
run_docker_classic_test Test_Multiple
run_docker_classic_test Test_Healthy
exit $errors

16
plugins/proxywasm/go.mod Normal file
View File

@@ -0,0 +1,16 @@
module github.com/acouvreur/sablier/plugins/proxy-wasm
go 1.22
require (
github.com/json-iterator/tinygo v0.0.0-20211221071957-84b5b690c8a0
github.com/tetratelabs/proxy-wasm-go-sdk v0.23.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
)
require (
github.com/tetratelabs/wazero v1.6.0 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
)

16
plugins/proxywasm/go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/json-iterator/tinygo v0.0.0-20211221071957-84b5b690c8a0 h1:/cd98gHSKnKHcYYyBJ9qNvHxYZdXzJC8JMs42gMPp5I=
github.com/json-iterator/tinygo v0.0.0-20211221071957-84b5b690c8a0/go.mod h1:sR5SXbtbtp8PxPu3yGjZug4AS5aAur8jQZl9DXYTpP0=
github.com/tetratelabs/proxy-wasm-go-sdk v0.23.0 h1:e0dm/ypyd1xudIrg8VTsd8dawuYaSy2gqewH5zD4rU8=
github.com/tetratelabs/proxy-wasm-go-sdk v0.23.0/go.mod h1:YqR8JZaY3Ev9ihXgjzAQAMkXEzPKKmy4Q5rsVWt4XGk=
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=

322
plugins/proxywasm/main.go Normal file
View File

@@ -0,0 +1,322 @@
package main
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
jsoniter "github.com/json-iterator/tinygo"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"golang.org/x/exp/slices"
)
var Version string
func main() {
// SetVMContext is the entrypoint for setting up this entire Wasm VM.
// Please make sure that this entrypoint be called during "main()" function, otherwise
// this VM would fail.
proxywasm.SetVMContext(&vmContext{})
}
// vmContext implements types.VMContext interface of proxy-wasm-go SDK.
type vmContext struct {
// Embed the default VM context here,
// so that we don't need to reimplement all the methods.
types.DefaultVMContext
}
// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}
type pluginContext struct {
// Embed the default plugin context here,
// so that we don't need to reimplement all the methods.
types.DefaultPluginContext
configuration pluginConfiguration
}
type pluginConfiguration struct {
cluster string
method string
path string
authority string
timeout uint32
}
// newPluginConfiguration creates a pluginConfiguration with default values
func newPluginConfiguration() pluginConfiguration {
return pluginConfiguration{
cluster: "sablier:10000",
method: "GET",
path: "/",
authority: "sablier.cluster.local",
timeout: 5000, // timeout in milliseconds
}
}
// Override types.DefaultPluginContext.
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
proxywasm.LogInfof("sablier proxywasm plugin version %v loaded", Version)
data, err := proxywasm.GetPluginConfiguration()
if err != nil && err != types.ErrorStatusNotFound {
proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
proxywasm.LogInfof("plugin config: %s", string(data))
config, err := parsePluginConfiguration(data)
if err != nil {
proxywasm.LogCriticalf("error parsing plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
ctx.configuration = config
return types.OnPluginStartStatusOK
}
//go:generate go run github.com/json-iterator/tinygo/gen
type DynamicConfiguration struct {
DisplayName string `json:"display_name"`
ShowDetails *bool `json:"show_details"`
Theme string `json:"theme"`
RefreshFrequency string `json:"refresh_frequency"`
}
//go:generate go run github.com/json-iterator/tinygo/gen
type BlockingConfiguration struct {
Timeout string `json:"timeout"`
}
//go:generate go run github.com/json-iterator/tinygo/gen
type Config struct {
// SablierURL in the format of hostname:port. The scheme is excluded
SablierURL string `json:"sablier_url"`
// Cluster is an optional value that allows you to set override the
// first argument to `proxywasm.DispatchHttpCall`.
// In istio for exemple, the expected value would be: "outbound|port||hostname", e.g.: "outbound|10000||sablier"
// In APISIX and Nginx for example, the value would be the same as SablierURL, e.g.: sablier:10000
// Defaults to the same value of `SablierURL`.
Cluster string `json:"cluster"`
Names []string `json:"names"`
Group string `json:"group"`
SessionDuration string `json:"session_duration"`
Dynamic *DynamicConfiguration `json:"dynamic"`
Blocking *BlockingConfiguration `json:"blocking"`
}
func (c Config) GetPath() string {
path := url.URL{}
q := path.Query()
if c.SessionDuration != "" {
dur, err := time.ParseDuration(c.SessionDuration)
if err != nil {
proxywasm.LogWarnf("parsing session duration failed (ignoring value): %v", err)
} else {
q.Add("session_duration", dur.String())
}
}
for _, name := range c.Names {
q.Add("names", name)
}
if c.Group != "" {
q.Add("group", c.Group)
}
path.RawQuery = q.Encode()
if c.Dynamic != nil {
return c.getDynamicQuery(path)
} else if c.Blocking != nil {
return c.getBlockingQuery(path)
}
return "no strategy configured"
}
func (c Config) getDynamicQuery(path url.URL) string {
path.Path = "/api/strategies/dynamic"
q := path.Query()
if c.Dynamic.DisplayName != "" {
q.Add("display_name", c.Dynamic.DisplayName)
}
if c.Dynamic.Theme != "" {
q.Add("theme", c.Dynamic.Theme)
}
if c.Dynamic.RefreshFrequency != "" {
dur, err := time.ParseDuration(c.Dynamic.RefreshFrequency)
if err != nil {
proxywasm.LogWarnf("parsing dynamic refresh frequency failed (ignoring value): %v", err)
} else {
q.Add("refresh_frequency", dur.String())
}
}
if c.Dynamic.ShowDetails != nil {
q.Add("show_details", strconv.FormatBool(*c.Dynamic.ShowDetails))
}
path.RawQuery = q.Encode()
return path.String()
}
func (c Config) getBlockingQuery(path url.URL) string {
path.Path = "/api/strategies/blocking"
q := path.Query()
if c.Blocking.Timeout != "" {
dur, err := time.ParseDuration(c.Blocking.Timeout)
if err != nil {
proxywasm.LogWarnf("parsing blocking timeout duration failed (ignoring value): %v", err)
} else {
q.Add("timeout", dur.String())
}
}
path.RawQuery = q.Encode()
return path.String()
}
func parsePluginConfiguration(data []byte) (pluginConfiguration, error) {
pluginConf := newPluginConfiguration()
if len(data) == 0 {
return pluginConf, fmt.Errorf("the plugin configuration is not a valid: %q", string(data))
}
json := jsoniter.CreateJsonAdapter(Config_json{}, BlockingConfiguration_json{}, DynamicConfiguration_json{})
var c Config
err := json.Unmarshal(data, &c)
if err != nil {
proxywasm.LogErrorf("error parsing configuration: %v", err.Error())
return pluginConf, err
}
if c.Blocking == nil && c.Dynamic == nil {
return pluginConf, fmt.Errorf("you must specify one strategy (dynamic or blocking)")
}
if c.Blocking != nil && c.Dynamic != nil {
return pluginConf, fmt.Errorf("you must specify only one strategy")
}
if c.Blocking != nil && c.Blocking.Timeout != "" {
timeout, err := time.ParseDuration(c.Blocking.Timeout)
if err != nil {
return pluginConf, fmt.Errorf("cannot parse blocking timeout duration: %v", err)
}
pluginConf.timeout = uint32(timeout.Milliseconds())
}
if len(c.Names) == 0 && len(c.Group) == 0 {
return pluginConf, fmt.Errorf("you must specify names or group")
}
if len(c.Names) > 0 && len(c.Group) > 0 {
return pluginConf, fmt.Errorf("you must specify either names or group")
}
if c.SablierURL != "" {
pluginConf.authority = c.SablierURL
// Default to SablierURL
pluginConf.cluster = c.SablierURL
}
if c.Cluster != "" {
pluginConf.cluster = c.Cluster
}
pluginConf.path = c.GetPath()
return pluginConf, nil
}
// Override types.DefaultPluginContext.
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
headers := [][2]string{
{":method", ctx.configuration.method},
{":path", ctx.configuration.path},
{":authority", ctx.configuration.authority},
{"User-Agent", fmt.Sprintf("sablier-proxywasm-plugin/%s", Version)},
}
return &httpOnDemand{
contextID: contextID,
headers: headers,
cluster: ctx.configuration.cluster,
timeout: ctx.configuration.timeout,
}
}
type httpOnDemand struct {
// Embed the default http context here,
// so that we don't need to reimplement all the methods.
types.DefaultHttpContext
contextID uint32
headers [][2]string
cluster string
timeout uint32
}
// Override types.DefaultHttpContext.
func (ctx *httpOnDemand) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
proxywasm.LogInfof("DispatchHttpCall to %v", ctx.cluster)
proxywasm.LogInfof("DispatchHttpCall with headers %v", ctx.headers)
if _, err := proxywasm.DispatchHttpCall(ctx.cluster, ctx.headers, nil, nil,
ctx.timeout, httpCallResponseCallback); err != nil {
proxywasm.LogCriticalf("dipatch httpcall failed: %v", err)
proxywasm.LogDebugf("%s: %v", ctx.cluster, ctx.headers)
return types.ActionContinue
}
proxywasm.LogInfof("http call dispatched to %s", ctx.cluster)
return types.ActionPause
}
func httpCallResponseCallback(numHeaders, bodySize, numTrailers int) {
hs, err := proxywasm.GetHttpCallResponseHeaders()
if err != nil {
proxywasm.LogCriticalf("failed to get response headers: %v", err)
return
}
proxywasm.LogInfof("GetHttpCallResponseHeaders: %v", hs)
headerIndex := slices.IndexFunc(hs, func(h [2]string) bool { return strings.ToLower(h[0]) == "x-sablier-session-status" })
if headerIndex < 0 {
proxywasm.LogCriticalf("failed to find x-sablier-session-status header: %v", hs)
proxywasm.ResumeHttpRequest()
return
}
headerValue := hs[headerIndex][1]
if headerValue != "ready" {
b, err := proxywasm.GetHttpCallResponseBody(0, bodySize)
if err != nil {
proxywasm.LogCriticalf("failed to get response body: %v", err)
proxywasm.ResumeHttpRequest()
return
}
proxywasm.LogInfof("GetHttpCallResponseBody (%v bytes): %v", bodySize, string(b))
if err := proxywasm.SendHttpResponse(200, hs, b, -1); err != nil {
proxywasm.LogErrorf("failed to send local response: %v", err)
proxywasm.ResumeHttpRequest()
}
} else {
proxywasm.ResumeHttpRequest()
}
}

View File

@@ -0,0 +1,97 @@
package main
import (
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)
func TestUnmarshal(t *testing.T) {
data := `{
"sablier_url": "sablier",
"sablier_port": 10000,
"group": "demo",
"session_duration": "30s",
"dynamic": {
"display_dame": "From WASM!",
"show_details": true,
"theme": "hacker-terminal",
"refresh_frequency": "5s"
}
}`
config, err := parsePluginConfiguration([]byte(data))
if err != nil {
t.Error(err)
}
t.Log("path:", config.path)
t.Log("authority:", config.authority)
}
func TestPluginContext_OnTick(t *testing.T) {
vmTest(t, func(t *testing.T, vm types.VMContext) {
data := `{
"sablier_url": "sablier",
"sablier_port": 10000,
"group": "demo",
"session_duration": "30s",
"dynamic": {
"display_dame": "From WASM!",
"show_details": true,
"theme": "hacker-terminal",
"refresh_frequency": "5s"
}
}`
opt := proxytest.NewEmulatorOption().WithVMContext(vm).WithPluginConfiguration([]byte(data))
host, reset := proxytest.NewHostEmulator(opt)
defer reset()
// Create http context.
id := host.InitializeHttpContext()
// Call OnRequestHeaders.
action := host.CallOnRequestHeaders(id, [][2]string{
{"content-length", "10"},
}, false)
// Must be continued.
require.Equal(t, types.ActionPause, action)
// Check the final request headers
host.CallOnHttpCallResponse(id, [][2]string{
{"x-sablier-session-status", "not-ready"},
}, nil, []byte("Response from Sablier"))
response := host.GetCurrentResponseBody(id)
require.Equal(t,
"Response from Sablier",
response,
"response should be served from sablier.")
})
}
// vmTest executes f twice, once with a types.VMContext that executes plugin code directly
// in the host, and again by executing the plugin code within the compiled main.wasm binary.
// Execution with main.wasm will be skipped if the file cannot be found.
func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) {
t.Helper()
t.Run("go", func(t *testing.T) {
f(t, &vmContext{})
})
t.Run("wasm", func(t *testing.T) {
wasm, err := os.ReadFile("sablierproxywasm.wasm")
if err != nil {
t.Skip("wasm not found")
}
v, err := proxytest.NewWasmVMContext(wasm)
require.NoError(t, err)
defer v.Close()
f(t, v)
})
}

View File

@@ -7,7 +7,7 @@ module.exports = {
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/exec", {
"publishCmd": "make VERSION=${nextRelease.version} release -j 3"
"publishCmd": "make VERSION=${nextRelease.version} release -j 3 && make VERSION=${nextRelease.version} proxywasm"
}],
["@semantic-release/github", {
"assets": [