fix\!: remove plugins from the repository

This commit is contained in:
Alexis Couvreur
2025-11-09 17:28:18 -05:00
parent 96750a50da
commit 6d880928c7
95 changed files with 18 additions and 11148 deletions

View File

@@ -3,9 +3,6 @@ updates:
- package-ecosystem: "gomod"
directories:
- "/"
- "/plugins/caddy"
- "/plugins/proxywasm"
- "/plugins/traefik"
schedule:
interval: "daily"
groups:
@@ -22,15 +19,11 @@ updates:
- "k8s.io/cli-runtime"
- "k8s.io/client-go"
- "k8s.io/kubectl"
assignees:
- "acouvreur"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
assignees:
- "acouvreur"
# Enable version updates for Docker
- package-ecosystem: "docker"
@@ -40,5 +33,3 @@ updates:
# Check for updates once a week
schedule:
interval: "weekly"
assignees:
- "acouvreur"

4
.github/labeler.yml vendored
View File

@@ -2,10 +2,6 @@ documentation:
- changed-files:
- any-glob-to-any-file: 'docs/**'
reverse-proxy:
- changed-files:
- any-glob-to-any-file: 'plugins/**'
provider:
- changed-files:
- any-glob-to-any-file: 'pkg/provider/**'

View File

@@ -1,360 +0,0 @@
name: Build Sablier plugins
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
jobs:
build:
name: Build Sablier docker image once and share it to E2E jobs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
tags: sablierapp/sablier:local
outputs: type=docker,dest=/tmp/sablier.tar
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: sablier-image-tar
path: /tmp/sablier.tar
traefik:
name: Build Traefik Sablier Plugin
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: plugins/traefik/go.sum
- name: Build
run: cd plugins/traefik && go build -v .
- name: Test
run: cd plugins/traefik && go test -v ./...
traefik_e2e:
name: Run Sablier E2E tests for Traefik middleware
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
provider: [docker, docker_swarm, kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: |
go.sum
plugins/traefik/go.sum
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v5
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Test ${{ matrix.provider }}
run: cd plugins/traefik/e2e/${{ matrix.provider }} && bash ./run.sh
nginx_e2e:
name: Run Sablier E2E tests for Nginx NJS module with Sablier
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
provider: [docker, docker_swarm] # , kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: |
go.sum
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v5
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Test ${{ matrix.provider }}
run: cd plugins/nginx/e2e/${{ matrix.provider }} && bash ./run.sh
caddy:
name: Build Caddy Sablier Plugin
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Go 1.22
uses: actions/setup-go@v5
with:
go-version: 1.22
cache-dependency-path: plugins/caddy/go.sum
- name: Build
run: cd plugins/caddy && go build -v .
- name: Test
run: cd plugins/caddy && go test -v ./...
build-caddy:
name: Build Caddy docker image once and share it to Caddy E2E jobs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: plugins/caddy
file: plugins/caddy/Dockerfile
tags: caddy:local
outputs: type=docker,dest=/tmp/caddy.tar
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: caddy-image-tar
path: /tmp/caddy.tar
caddy_e2e:
name: Run Sablier E2E tests for Caddy middleware
runs-on: ubuntu-latest
needs:
- build
- build-caddy
strategy:
fail-fast: false
matrix:
provider: [docker, docker_swarm] # , kubernetes]
steps:
- name: Checkout code
uses: actions/checkout@v5
- 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@v5
with:
name: sablier-image-tar
path: /tmp
- name: Load Docker image
run: docker load --input /tmp/sablier.tar
- name: Download Caddy artifact
uses: actions/download-artifact@v5
with:
name: caddy-image-tar
path: /tmp
- name: Load Caddy Docker image
run: docker load --input /tmp/caddy.tar
- 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@v5
- name: Set up Go 1.24
uses: actions/setup-go@v5
with:
go-version: 1.24
cache-dependency-path: |
plugins/proxywasm/go.sum
- 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@v5
- name: Set up Go 1.24
uses: actions/setup-go@v5
with:
go-version: 1.24
cache-dependency-path: |
go.sum
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Download artifact
uses: actions/download-artifact@v5
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@v5
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@v5
- 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@v5
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@v5
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@v5
- 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@v5
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@v5
with:
name: proxywasm-plugin-wasm
path: ./plugins/proxywasm
- name: Test ${{ matrix.provider }}
run: cd plugins/proxywasm/e2e/nginx/${{ matrix.provider }} && bash ./run.sh

View File

@@ -1,34 +0,0 @@
name: Release
on:
push:
branches:
- main
- beta
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Go 1.24
uses: actions/setup-go@v5
with:
go-version: ^1.24
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
npm i -G semantic-release@~23.0.0 @semantic-release/exec@~6.0.3 @semantic-release/git@~10.0.1
npx semantic-release

View File

@@ -1,28 +0,0 @@
displayName: Sablier
type: middleware
iconPath: ./docs/assets/img/icon.png
bannerPath: ./docs/assets/img/banner.png
import: github.com/sablierapp/sablier/plugins/traefik
summary: "Start your containers on demand, shut them down automatically when there's no activity. Docker, Docker Swarm Mode and Kubernetes compatible."
testData:
sablierUrl: http://sablier:10000 # The sablier URL service, must be reachable from the Traefik instance
names: whoami,nginx # Comma separated names of containers/services/deployments etc.
group: default # Group name to use to filter by label, ignored if names is set
sessionDuration: 1m # The session duration after which containers/services/deployments instances are shutdown
# You can only use one strategy at a time
# To do so, only declare `dynamic` or `blocking`
# Dynamic strategy, provides the waiting webui
dynamic:
displayName: My Title # (Optional) Defaults to the middleware name
showDetails: true # (Optional) Set to true or false to show details specifcally for this middleware, unset to use Sablier server defaults
theme: hacker-terminal # (Optional) The theme to use
refreshFrequency: 5s # (Optional) The loading page refresh frequency
# Blocking strategy, waits until services are up and running
# but will not wait more than `timeout`
# blocking:
# timeout: 1m

View File

@@ -29,34 +29,12 @@ build:
test:
go test -tags="nomsgpack,remote,exclude_graphdriver_btrfs,containers_image_openpgp" ./...
plugins: build-plugin-traefik test-plugin-traefik build-plugin-caddy test-plugin-caddy
build-plugin-traefik:
cd plugins/traefik && go build -v .
test-plugin-traefik:
cd plugins/traefik && go test -v ./...
build-plugin-caddy:
cd plugins/caddy && go build -v .
test-plugin-caddy:
cd plugins/caddy && go test -v .
.PHONY: docker
docker:
docker build --build-arg BUILDTIME=$(BUILDTIME) --build-arg VERSION=$(VERSION) --build-arg REVISION=$(GIT_REVISION) -t sablierapp/sablier:local .
caddy:
docker build -t caddy:local plugins/caddy
release: $(PLATFORMS)
proxywasm:
go generate ./plugins/proxywasm
env GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o ./plugins/proxywasm/sablierproxywasm.wasm ./plugins/proxywasm
cp ./plugins/proxywasm/sablierproxywasm.wasm ./sablierproxywasm_$(VERSION).wasm
.PHONY: release $(PLATFORMS)
LAST = 0.0.0
@@ -73,42 +51,3 @@ update-doc-version-middleware:
.PHONY: docs
docs:
npx --yes docsify-cli serve docs
# End to end tests
e2e: e2e-caddy e2e-nginx e2e-traefik
## Caddy
e2e-caddy-docker:
cd plugins/caddy/e2e/docker && bash ./run.sh
e2e-caddy-swarm:
cd plugins/caddy/e2e/docker_swarm && bash ./run.sh
# e2e-caddy-kubernetes:
# cd plugins/caddy/e2e/kubernetes && bash ./run.sh
e2e-caddy: e2e-caddy-docker e2e-caddy-swarm # e2e-caddy-kubernetes
## NGinx
e2e-nginx-docker:
cd plugins/nginx/e2e/docker && bash ./run.sh
e2e-nginx-swarm:
cd plugins/nginx/e2e/docker_swarm && bash ./run.sh
e2e-nginx-kubernetes:
cd plugins/nginx/e2e/kubernetes && bash ./run.sh
e2e-nginx: e2e-nginx-docker e2e-nginx-swarm e2e-nginx-kubernetes
## Traefik
e2e-traefik-docker:
cd plugins/traefik/e2e/docker && bash ./run.sh
e2e-traefik-swarm:
cd plugins/traefik/e2e/docker_swarm && bash ./run.sh
e2e-traefik-kubernetes:
cd plugins/traefik/e2e/kubernetes && bash ./run.sh
e2e-traefik: e2e-traefik-docker e2e-traefik-swarm e2e-traefik-kubernetes

View File

@@ -1,125 +0,0 @@
//go:build e2e
// +build e2e
package e2e
import (
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/gavv/httpexpect/v2"
)
func Test_Dynamic(t *testing.T) {
e := httpexpect.Default(t, "http://localhost:8080/dynamic/")
e.GET("/whoami").
Expect().
Status(http.StatusOK).
Body().
Contains(`Dynamic Whoami`).
Contains(`Your instance(s) will stop after 1 minute of inactivity`)
e.GET("/whoami").
WithMaxRetries(20).
WithRetryDelay(50*time.Millisecond, time.Second*2).
WithRetryPolicyFunc(RetryUntilBodyContains("Host: localhost:8080")).
Expect().
Status(http.StatusOK).
Body().Contains(`Host: localhost:8080`)
}
func Test_Blocking(t *testing.T) {
e := httpexpect.Default(t, "http://localhost:8080/blocking/")
e.GET("/whoami").
Expect().
Status(http.StatusOK).
Body().Contains(`Host: localhost:8080`)
}
func Test_Multiple(t *testing.T) {
e := httpexpect.Default(t, "http://localhost:8080/multiple/")
e.GET("/whoami").
Expect().
Status(http.StatusOK).
Body().
Contains(`Multiple Whoami`).
Contains(`Your instance(s) will stop after 1 minute of inactivity`)
e.GET("/whoami").
WithMaxRetries(20).
WithRetryDelay(50*time.Millisecond, time.Second*2).
WithRetryPolicyFunc(RetryUntilBodyContains("Host: localhost:8080")).
Expect().
Status(http.StatusOK).
Body().Contains(`Host: localhost:8080`)
e.GET("/nginx").
WithMaxRetries(20).
WithRetryDelay(50*time.Millisecond, time.Second*2).
WithRetryPolicyFunc(RetryUntilBodyContains("nginx/")).
Expect().
Status(http.StatusNotFound).
Body().Contains(`nginx/`)
}
func Test_Healthy(t *testing.T) {
e := httpexpect.Default(t, "http://localhost:8080/healthy/")
e.GET("/nginx").
Expect().
Status(http.StatusOK).
Body().
Contains(`Healthy Nginx`).
Contains(`Your instance(s) will stop after 1 minute of inactivity`)
e.GET("/nginx").
WithMaxRetries(20).
WithRetryDelay(50*time.Millisecond, time.Second*2).
WithRetryPolicyFunc(RetryUntilBodyContains("nginx/")).
Expect().
Status(http.StatusNotFound).
Body().Contains(`nginx/`)
}
func Test_Group(t *testing.T) {
e := httpexpect.Default(t, "http://localhost:8080/")
e.GET("/group").
Expect().
Status(http.StatusOK).
Body().
Contains(`Group E2E`).
Contains(`Your instance(s) will stop after 1 minute of inactivity`)
e.GET("/group").
WithMaxRetries(20).
WithRetryDelay(50*time.Millisecond, time.Second*2).
WithRetryPolicyFunc(RetryUntilBodyContains("Host: localhost:8080")).
Expect().
Status(http.StatusOK).
Body().Contains(`Host: localhost:8080`)
}
func RetryUntilBodyContains(contains string) func(resp *http.Response, err error) bool {
return func(resp *http.Response, err error) bool {
if err != nil {
return true
}
if resp.Body != nil {
// Check body if available, etc.
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return true
}
return !strings.Contains(string(body), contains)
}
return true
}
}

24
go.mod
View File

@@ -5,7 +5,6 @@ go 1.24.0
require (
github.com/containers/podman/v5 v5.6.2
github.com/docker/docker v28.5.1+incompatible
github.com/gavv/httpexpect/v2 v2.17.0
github.com/gin-gonic/gin v1.10.1
github.com/google/go-cmp v0.7.0
github.com/lmittmann/tint v1.1.2
@@ -38,16 +37,13 @@ require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/Zxilly/go-size-analyzer v1.7.7 // indirect
github.com/ZxillyFork/gore v0.0.0-20250212071411-56f1a74740b2 // indirect
github.com/ZxillyFork/gosym v0.0.0-20240510024817-deed2b882525 // indirect
github.com/ZxillyFork/trie v0.0.0-20240512061834-f75150731646 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/alecthomas/kong v1.8.1 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blacktop/go-dwarf v1.0.10 // indirect
github.com/blacktop/go-macho v1.1.238 // indirect
@@ -90,8 +86,6 @@ require (
github.com/ebitengine/purego v0.8.4 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@@ -110,7 +104,6 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -119,15 +112,12 @@ require (
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hpcloud/tail v1.0.0 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jedib0t/go-pretty/v6 v6.6.6 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
@@ -147,7 +137,6 @@ require (
github.com/magiconair/properties v1.8.10 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
@@ -155,7 +144,6 @@ require (
github.com/mdelapenya/tlscert v0.2.0 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/patternmatcher v0.6.0 // indirect
@@ -175,6 +163,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nikolaydubina/treemap v1.2.5 // indirect
github.com/nxadm/tail v1.4.11 // indirect
github.com/onsi/ginkgo v1.10.1 // indirect
github.com/opencontainers/cgroups v0.0.4 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
@@ -192,9 +181,7 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/sanity-io/litter v1.5.6 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
github.com/sigstore/fulcio v1.6.6 // indirect
github.com/sigstore/protobuf-specs v0.4.1 // indirect
@@ -215,17 +202,9 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.58.0 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vbauerster/mpb/v8 v8.10.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
@@ -258,7 +237,6 @@ require (
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

61
go.sum
View File

@@ -12,8 +12,6 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/Zxilly/go-size-analyzer v1.7.7 h1:z+OwZgSENyR5EYh2Oce3qvDgC/ghEIwL2cf86ilKkJA=
@@ -26,16 +24,12 @@ github.com/ZxillyFork/trie v0.0.0-20240512061834-f75150731646 h1:Mkm17zXN+CiAXdV
github.com/ZxillyFork/trie v0.0.0-20240512061834-f75150731646/go.mod h1:wbU6+GbSHDwVSMpzFyOVrX/WdFHXH4j7j8Rg8rbyo+4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE=
github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
@@ -127,7 +121,6 @@ github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -162,10 +155,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -177,8 +166,6 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gavv/httpexpect/v2 v2.17.0 h1:nIJqt5v5e4P7/0jODpX2gtSw+pHXUqdP28YcjqwDZmE=
github.com/gavv/httpexpect/v2 v2.17.0/go.mod h1:E8ENFlT9MZ3Si2sfM6c6ONdwXV2noBCGkhA+lkJgkP0=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
@@ -217,8 +204,6 @@ github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -249,7 +234,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -259,8 +243,6 @@ github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
@@ -272,8 +254,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -283,12 +263,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.6.6 h1:LyezkL+1SuqH2z47e5IMQkYUIcs2BD+MnpdPRiRcN0c=
@@ -319,11 +295,8 @@ github.com/knadh/profiler v0.2.0/go.mod h1:LqNkAu++MfFkbEDA63AmRaIf6UkGrLXyZ5VQQ
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
@@ -342,8 +315,6 @@ github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
@@ -359,8 +330,6 @@ github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=
github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
@@ -431,12 +400,10 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -466,15 +433,12 @@ github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/samber/slog-gin v1.17.2 h1:eKi0x9brNl7vwLl3+9Zuk2ZiIsneHd55/R01TqV9bM8=
github.com/samber/slog-gin v1.17.2/go.mod h1:7R4VMQGENllRLLnwGyoB5nUSB+qzxThpGe5G02xla6o=
github.com/sanity-io/litter v1.5.6 h1:hCFycYzhRnW4niFbbmR7QKdmds69PbVa/sNmEN5euSU=
github.com/sanity-io/litter v1.5.6/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc=
github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
@@ -511,9 +475,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -526,7 +488,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI=
github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/testcontainers/testcontainers-go v0.39.0 h1:uCUJ5tA+fcxbFAB0uP3pIK3EJ2IjjDUHFSZ1H1UxAts=
@@ -553,33 +514,18 @@ github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valkey-io/valkey-go v1.0.67 h1:QPaRcuBmazhyoWTxk7I2XcSALhoL7UhAReR5o/rh1Po=
github.com/valkey-io/valkey-go v1.0.67/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.58.0 h1:GGB2dWxSbEprU9j0iMJHgdKYJVDyjrOwF9RE59PbRuE=
github.com/valyala/fasthttp v1.58.0/go.mod h1:SYXvHHaFp7QZHGKSHmoMipInhrI5StHrhDTYVEjK/Kw=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
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=
@@ -638,7 +584,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -737,7 +682,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
@@ -777,7 +721,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
@@ -788,8 +731,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -809,8 +750,6 @@ k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOP
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=

10
go.work
View File

@@ -1,10 +0,0 @@
go 1.24.0
toolchain go1.24.0
use (
.
./plugins/caddy
./plugins/proxywasm
./plugins/traefik
)

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,25 @@ import (
"fmt"
"log/slog"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/bindings/containers"
)
func (p *Provider) InstanceStart(ctx context.Context, name string) error {
p.l.DebugContext(ctx, "starting container", "name", name)
spec, err := containers.Inspect(p.conn, name, nil)
if err != nil {
p.l.ErrorContext(ctx, "cannot inspect container after starting", slog.String("name", name), slog.Any("error", err))
return fmt.Errorf("cannot inspect container %s after starting: %w", name, err)
}
status, err := define.StringToContainerStatus(spec.State.Status)
if err != nil {
return fmt.Errorf("cannot convert container status: %w", err)
}
if status == define.ContainerStateRunning {
}
// TODO: Create a context from the ctx argument with the p.conn
err := containers.Start(p.conn, name, nil)
@@ -17,5 +31,6 @@ func (p *Provider) InstanceStart(ctx context.Context, name string) error {
p.l.ErrorContext(ctx, "cannot start container", slog.String("name", name), slog.Any("error", err))
return fmt.Errorf("cannot start container %s: %w", name, err)
}
return nil
}

View File

@@ -1,16 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
traefik

View File

@@ -1,11 +0,0 @@
ARG CADDY_VERSION=2.10.0
FROM caddy:${CADDY_VERSION}-builder AS builder
COPY . .
RUN xcaddy build \
--with github.com/sablierapp/sablier/plugins/caddy=.
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

View File

@@ -1,92 +0,0 @@
> [!IMPORTANT]
> This plugin now has its own reepository: https://github.com/sablierapp/sablier-caddy-plugin
>
> This plugin will no longer receive updates.
# Caddy Sablier Plugin
- [Caddy Sablier Plugin](#caddy-sablier-plugin)
- [Build the custom Caddy image with Sablier middleware in it](#build-the-custom-caddy-image-with-sablier-middleware-in-it)
- [By using the provided Dockerfile](#by-using-the-provided-dockerfile)
- [By updating your Caddy Dockerfile](#by-updating-your-caddy-dockerfile)
- [Configuration](#configuration)
- [Exemple with a minimal configuration](#exemple-with-a-minimal-configuration)
- [Running end-to-end tests](#running-end-to-end-tests)
## Build the custom Caddy image with Sablier middleware in it
In order to use the custom plugin for Caddy, you need to bundle it with Caddy.
Here I'll show you two options with Docker.
### By using the provided Dockerfile
```bash
docker build https://github.com/sablierapp/sablier.git#v1.10.1:plugins/caddy -t caddy:with-sablier
```
**Note:** You can change `main` for any other branch (such as `beta`, or tags `v1.10.1`)
### By updating your Caddy Dockerfile
```Dockerfile
ARG CADDY_VERSION=2.9.1
FROM caddy:${CADDY_VERSION}-builder AS builder
RUN xcaddy build \
--with github.com/sablierapp/sablier/plugins/caddy
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```
## Configuration
You can have the following configuration:
```Caddyfile
:80 {
route /my/route {
sablier [<sablierURL>=http://sablier:10000] {
[names container1,container2,...]
[group mygroup]
[session_duration 30m]
dynamic {
[display_name This is my display name]
[show_details yes|true|on]
[theme hacker-terminal]
[refresh_frequency 2s]
}
blocking {
[timeout 1m]
}
}
reverse_proxy myservice:port
}
}
```
### Exemple with a minimal configuration
Almost all options are optional and you can setup very simple rules to use the server default values.
```Caddyfile
:80 {
route /my/route {
sablier {
group mygroup
dynamic
}
reverse_proxy myservice:port
}
}
```
## Running end-to-end tests
1. Build local sablier
`docker build -t caddy:local .`
2. Build local caddy
`docker build -t sablierapp/sablier:local ../..`
3. Run test
`cd e2e/docker && bash ./run.sh`

View File

@@ -1,278 +0,0 @@
package caddy
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
httpcaddyfile.RegisterHandlerDirective("sablier", parseCaddyfile)
}
type DynamicConfiguration struct {
DisplayName string
ShowDetails *bool
Theme string
RefreshFrequency *time.Duration
}
type BlockingConfiguration struct {
Timeout *time.Duration
}
type Config struct {
SablierURL string
Names []string
Group string
SessionDuration *time.Duration
Dynamic *DynamicConfiguration
Blocking *BlockingConfiguration
}
func CreateConfig() *Config {
return &Config{
SablierURL: "http://sablier:10000",
Names: []string{},
SessionDuration: nil,
Dynamic: nil,
Blocking: nil,
}
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. Syntax:
//
// sablier [<sablierURL>] {
// [names container1,container2,...]
// [group mygroup]
// [session_duration 30m]
// dynamic {
// [display_name This is my display name]
// [show_details yes|true|on]
// [theme hacker-terminal]
// [refresh_frequency 2s]
// }
// blocking {
// [timeout 1m]
// }
// }
//
func (c *Config) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
for d.Next() {
if d.NextArg() {
c.SablierURL = d.Val()
} else {
c.SablierURL = "http://sablier:10000"
}
if d.NextArg() {
return d.ArgErr()
}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdirective := d.Val()
args := strings.Join(d.RemainingArgs(), " ")
switch subdirective {
case "names":
c.Names = parseNames(args)
case "group":
c.Group = args
case "session_duration":
duration, err := time.ParseDuration(args)
if err != nil {
return err
}
c.SessionDuration = &duration
case "dynamic":
dynamic, err := parseDynamic(d)
if err != nil {
return err
}
c.Dynamic = dynamic
case "blocking":
blocking, err := parseBlocking(d)
if err != nil {
return err
}
c.Blocking = blocking
}
}
}
if c.Blocking == nil && c.Dynamic == nil {
return fmt.Errorf("you must specify one strategy (dynamic or blocking)")
}
if c.Blocking != nil && c.Dynamic != nil {
return fmt.Errorf("you must specify only one strategy")
}
if len(c.Names) == 0 && len(c.Group) == 0 {
return fmt.Errorf("you must specify names or group")
}
if len(c.Names) > 0 && len(c.Group) > 0 {
return fmt.Errorf("you must specify either names or group")
}
return nil
}
func parseNames(value string) []string {
names := strings.Split(value, " ")
for i := range names {
names[i] = strings.TrimSpace(names[i])
}
if len(names) == 1 && names[0] == "" {
return make([]string, 0)
}
return names
}
func parseDynamic(d *caddyfile.Dispenser) (*DynamicConfiguration, error) {
conf := &DynamicConfiguration{}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdirective := d.Val()
args := strings.Join(d.RemainingArgs(), " ")
switch subdirective {
case "display_name":
conf.DisplayName = args
case "show_details":
shouldShow := isEnabledArg(args)
conf.ShowDetails = &shouldShow
case "theme":
conf.Theme = args
case "refresh_frequency":
duration, err := time.ParseDuration(args)
if err != nil {
return nil, err
}
conf.RefreshFrequency = &duration
}
}
return conf, nil
}
func parseBlocking(d *caddyfile.Dispenser) (*BlockingConfiguration, error) {
conf := &BlockingConfiguration{}
for nesting := d.Nesting(); d.NextBlock(nesting); {
subdirective := d.Val()
args := strings.Join(d.RemainingArgs(), " ")
switch subdirective {
case "timeout":
duration, err := time.ParseDuration(args)
if err != nil {
return nil, err
}
conf.Timeout = &duration
}
}
return conf, nil
}
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var c Config
err := c.UnmarshalCaddyfile(h.Dispenser)
return SablierMiddleware{Config: c}, err
}
func isEnabledArg(s string) bool {
if s == "yes" || s == "true" || s == "on" {
return true
}
return false
}
func (c *Config) BuildRequest() (*http.Request, error) {
if c.Dynamic != nil {
return c.buildDynamicRequest()
} else if c.Blocking != nil {
return c.buildBlockingRequest()
}
return nil, fmt.Errorf("no strategy configured")
}
func (c *Config) buildDynamicRequest() (*http.Request, error) {
if c.Dynamic == nil {
return nil, fmt.Errorf("dynamic config is nil")
}
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/strategies/dynamic", c.SablierURL), nil)
if err != nil {
return nil, err
}
q := request.URL.Query()
if c.SessionDuration != nil {
q.Add("session_duration", c.SessionDuration.String())
}
for _, name := range c.Names {
q.Add("names", name)
}
if c.Group != "" {
q.Add("group", c.Group)
}
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 != nil {
q.Add("refresh_frequency", c.Dynamic.RefreshFrequency.String())
}
if c.Dynamic.ShowDetails != nil {
q.Add("show_details", strconv.FormatBool(*c.Dynamic.ShowDetails))
}
request.URL.RawQuery = q.Encode()
return request, nil
}
func (c *Config) buildBlockingRequest() (*http.Request, error) {
if c.Blocking == nil {
return nil, fmt.Errorf("blocking config is nil")
}
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/strategies/blocking", c.SablierURL), nil)
if err != nil {
return nil, err
}
q := request.URL.Query()
if c.SessionDuration != nil {
q.Add("session_duration", c.SessionDuration.String())
}
for _, name := range c.Names {
q.Add("names", name)
}
if c.Group != "" {
q.Add("group", c.Group)
}
if c.Blocking.Timeout != nil {
q.Add("timeout", c.Blocking.Timeout.String())
}
request.URL.RawQuery = q.Encode()
return request, nil
}

View File

@@ -1,450 +0,0 @@
package caddy_test
import (
"io"
"net/http"
"reflect"
"testing"
"time"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/sablierapp/sablier/plugins/caddy"
)
var fals bool = false
var tru bool = true
var oneMinute = 1 * time.Minute
func TestConfig_BuildRequest(t *testing.T) {
tests := []struct {
name string
fields caddy.Config
want *http.Request
wantErr bool
}{
{
name: "dynamic session with required values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
Dynamic: &caddy.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache", nil),
wantErr: false,
},
{
name: "dynamic session with default values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with group",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Group: "default",
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?group=default&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with theme values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
Theme: "hacker-terminal",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&session_duration=1m&theme=hacker-terminal", nil),
wantErr: false,
},
{
name: "dynamic session with theme and display name values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
Theme: "hacker-terminal",
DisplayName: "Hello World!",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=Hello+World%21&names=nginx&names=apache&session_duration=1m&theme=hacker-terminal", nil),
wantErr: false,
},
{
name: "dynamic session with refresh frequency",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
RefreshFrequency: &oneMinute,
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&refresh_frequency=1m&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with show details to true",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
ShowDetails: &tru,
RefreshFrequency: &oneMinute,
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=true", nil),
wantErr: false,
},
{
name: "dynamic session with show details to false",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
ShowDetails: &fals,
RefreshFrequency: &oneMinute,
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=false", nil),
wantErr: false,
},
{
name: "dynamic session without show details set",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
ShowDetails: nil,
RefreshFrequency: &oneMinute,
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?names=nginx&names=apache&refresh_frequency=1m&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with required values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
Blocking: &caddy.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache", nil),
wantErr: false,
},
{
name: "blocking session with default values",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Blocking: &caddy.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with group",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Group: "default",
SessionDuration: &oneMinute,
Blocking: &caddy.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?group=default&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with timeout value",
fields: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"nginx", "apache"},
SessionDuration: &oneMinute,
Blocking: &caddy.BlockingConfiguration{
Timeout: nil,
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache&session_duration=1m&timeout=5m", nil),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &caddy.Config{
SablierURL: tt.fields.SablierURL,
Names: tt.fields.Names,
Group: tt.fields.Group,
SessionDuration: tt.fields.SessionDuration,
Dynamic: tt.fields.Dynamic,
Blocking: tt.fields.Blocking,
}
got, err := c.BuildRequest()
if (err != nil) != tt.wantErr {
t.Errorf("Config.BuildRequest() error = %v, wantErr %v", err, tt.wantErr)
} else if got.RequestURI != tt.want.RequestURI {
t.Errorf("Config.BuildRequest() = %v, want %v", got, tt.want)
}
})
}
}
func TestConfig_UnmarshalCaddyfile(t *testing.T) {
tests := []struct {
name string
input string
want caddy.Config
wantErr bool
wantErrValue string
}{
{
name: "default sablier URL",
input: `sablier {
group mygroup
dynamic
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
Group: "mygroup",
Dynamic: &caddy.DynamicConfiguration{},
},
wantErr: false,
},
{
name: "specific sablier URL",
input: `sablier http://mysablier:3000 {
names container1 container2 container3
blocking
}`,
want: caddy.Config{
SablierURL: "http://mysablier:3000",
Names: []string{"container1", "container2", "container3"},
Blocking: &caddy.BlockingConfiguration{},
},
wantErr: false,
},
{
name: "parse valid names dynamic",
input: `sablier {
names container1 container2 container3
session_duration 1m
dynamic {
display_name This is a display name!
show_details on
theme hacker-terminal
refresh_frequency 1m
}
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
Names: []string{"container1", "container2", "container3"},
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
DisplayName: "This is a display name!",
ShowDetails: &tru,
Theme: "hacker-terminal",
RefreshFrequency: &oneMinute,
},
},
wantErr: false,
},
{
name: "parse valid group dynamic",
input: `sablier {
group mygroup
session_duration 1m
dynamic {
display_name This is a display name!
show_details on
theme hacker-terminal
refresh_frequency 1m
}
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
Group: "mygroup",
SessionDuration: &oneMinute,
Dynamic: &caddy.DynamicConfiguration{
DisplayName: "This is a display name!",
ShowDetails: &tru,
Theme: "hacker-terminal",
RefreshFrequency: &oneMinute,
},
},
wantErr: false,
},
{
name: "parse valid names blocking",
input: `sablier {
group mygroup
session_duration 1m
blocking {
timeout 1m
}
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
Group: "mygroup",
SessionDuration: &oneMinute,
Blocking: &caddy.BlockingConfiguration{
Timeout: &oneMinute,
},
},
wantErr: false,
},
{
name: "parse invalid no strategies",
input: `sablier`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify one strategy (dynamic or blocking)",
},
{
name: "parse invalid two strategies",
input: `sablier {
blocking
dynamic
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify only one strategy",
},
{
name: "parse invalid no names or group",
input: `sablier {
blocking
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify names or group",
},
{
name: "parse empty names",
input: `sablier {
names
blocking
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify names or group",
},
{
name: "parse empty group",
input: `sablier {
group
blocking
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify names or group",
},
{
name: "parse invalid names and group",
input: `sablier {
names container1 container2
group mygroup
blocking
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "you must specify either names or group",
},
{
name: "parse invalid session_duration",
input: `sablier {
group mygroup
session_duration invalid
blocking
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "time: invalid duration \"invalid\"",
},
{
name: "parse invalid refresh_frequency",
input: `sablier {
group mygroup
dynamic {
refresh_frequency invalid
}
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "time: invalid duration \"invalid\"",
},
{
name: "parse invalid timeout",
input: `sablier {
group mygroup
blocking {
timeout invalid
}
}`,
want: caddy.Config{
SablierURL: "http://sablier:10000",
},
wantErr: true,
wantErrValue: "time: invalid duration \"invalid\"",
},
}
for _, tt := range tests {
h := httpcaddyfile.Helper{
Dispenser: caddyfile.NewTestDispenser(tt.input),
}
got := caddy.Config{}
err := got.UnmarshalCaddyfile(h.Dispenser)
if tt.wantErr {
if (err != nil) != tt.wantErr {
t.Errorf("%s: UnmarshalCaddyfile() error = %v, wantErr = %v", tt.name, err, tt.wantErr)
}
if err.Error() != tt.wantErrValue {
t.Errorf("%s: UnmarshalCaddyfile() error = %v, wantErrValue = %v", tt.name, err.Error(), tt.wantErrValue)
}
} else if !reflect.DeepEqual(tt.want, got) {
t.Errorf("%s: UnmarshalCaddyfile() = %v, want %v", tt.name, got, tt.want)
}
}
}
func createRequest(method string, url string, body io.Reader) *http.Request {
request, err := http.NewRequest(method, url, body)
if err != nil {
panic(err)
}
return request
}

View File

@@ -1,72 +0,0 @@
:80 {
route /dynamic/whoami {
sablier http://sablier:10000 {
names docker_classic_e2e-whoami-1
session_duration 1m
dynamic {
display_name Dynamic Whoami
theme hacker-terminal
}
}
reverse_proxy whoami:80
}
route /blocking/whoami {
sablier http://sablier:10000 {
names docker_classic_e2e-whoami-1
session_duration 1m
blocking {
timeout 30s
}
}
reverse_proxy whoami:80
}
route /multiple/whoami {
sablier http://sablier:10000 {
names docker_classic_e2e-whoami-1 docker_classic_e2e-nginx-1
session_duration 1m
dynamic {
display_name Multiple Whoami
theme=hacker-terminal
}
}
reverse_proxy whoami:80
}
route /multiple/nginx {
sablier http://sablier:10000 {
names docker_classic_e2e-whoami-1 docker_classic_e2e-nginx-1
session_duration 1m
dynamic {
display_name Multiple Whoami
theme=hacker-terminal
}
}
reverse_proxy nginx:80
}
route /healthy/nginx {
sablier http://sablier:10000 {
names docker_classic_e2e-nginx-1
session_duration 1m
dynamic {
display_name Healthy Nginx
theme hacker-terminal
}
}
reverse_proxy nginx:80
}
route /group {
sablier http://sablier:10000 {
group E2E
session_duration 1m
dynamic {
display_name Group E2E
theme hacker-terminal
}
}
reverse_proxy whoami:80
}
}

View File

@@ -1,37 +0,0 @@
version: "3.7"
services:
proxy:
image: caddy:local
ports:
- "8080:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
restart: "no"
sablier:
image: sablierapp/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E

View File

@@ -1,40 +0,0 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=docker-compose.yml
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/sablierapp/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier proxy
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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,72 +0,0 @@
:80 {
route /dynamic/whoami {
sablier http://tasks.sablier:10000 {
names DOCKER_SWARM_E2E_whoami
session_duration 1m
dynamic {
display_name Dynamic Whoami
theme hacker-terminal
}
}
reverse_proxy whoami:80
}
route /blocking/whoami {
sablier http://tasks.sablier:10000 {
names DOCKER_SWARM_E2E_whoami
session_duration 1m
blocking {
timeout 30s
}
}
reverse_proxy whoami:80
}
route /multiple/whoami {
sablier http://tasks.sablier:10000 {
names DOCKER_SWARM_E2E_whoami DOCKER_SWARM_E2E_nginx
session_duration 1m
dynamic {
display_name Multiple Whoami
theme=hacker-terminal
}
}
reverse_proxy whoami:80
}
route /multiple/nginx {
sablier http://tasks.sablier:10000 {
names DOCKER_SWARM_E2E_whoami DOCKER_SWARM_E2E_nginx
session_duration 1m
dynamic {
display_name Multiple Whoami
theme=hacker-terminal
}
}
reverse_proxy nginx:80
}
route /healthy/nginx {
sablier http://tasks.sablier:10000 {
names DOCKER_SWARM_E2E_nginx
session_duration 1m
dynamic {
display_name Healthy Nginx
theme hacker-terminal
}
}
reverse_proxy nginx:80
}
route /group {
sablier http://sablier:10000 {
group E2E
session_duration 1m
dynamic {
display_name Group E2E
theme hacker-terminal
}
}
reverse_proxy whoami:80
}
}

View File

@@ -1,46 +0,0 @@
version: "3.7"
services:
proxy:
image: caddy:local
ports:
- target: 80
published: 8080
protocol: tcp
mode: host # Won't work in github actions otherwise
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
deploy:
restart_policy:
condition: none # Do not restart on setup failure
sablier:
image: sablierapp/sablier:local
command:
- start
- --provider.name=swarm
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
deploy:
labels:
- sablier.enable=true
- sablier.group=E2E
replicas: 0
nginx:
image: nginx:1.23.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
deploy:
labels:
- sablier.enable=true
- sablier.group=E2E
replicas: 0

View File

@@ -1,53 +0,0 @@
#!/bin/bash
DOCKER_STACK_FILE=docker-stack.yml
DOCKER_STACK_NAME=DOCKER_SWARM_E2E
errors=0
echo "Using Docker version:"
docker version
prepare_docker_swarm() {
docker swarm init
}
prepare_docker_stack() {
docker stack deploy --compose-file $DOCKER_STACK_FILE ${DOCKER_STACK_NAME}
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock sudobmitch/docker-stack-wait -t 60 ${DOCKER_STACK_NAME}
}
destroy_docker_stack() {
docker stack rm ${DOCKER_STACK_NAME}
# Sometimes, the network is not well cleaned up, see https://github.com/moby/moby/issues/30942#issuecomment-540699206
until [ -z "$(docker stack ps ${DOCKER_STACK_NAME} -q)" ]; do sleep 1; done
}
destroy_docker_swarm() {
docker swarm leave -f || true
}
run_docker_swarm_test() {
echo "Running Docker Swarm Test: $1"
prepare_docker_stack
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
docker service logs ${DOCKER_STACK_NAME}_sablier
docker service logs ${DOCKER_STACK_NAME}_proxy
fi
destroy_docker_stack
}
trap destroy_docker_swarm EXIT
prepare_docker_swarm
prepare_docker_stack
run_docker_swarm_test Test_Dynamic
run_docker_swarm_test Test_Blocking
run_docker_swarm_test Test_Multiple
run_docker_swarm_test Test_Healthy
run_docker_swarm_test Test_Group
exit $errors

View File

@@ -1,34 +0,0 @@
:80 {
route /dynamic/whoami {
sablier url=http://tasks.sablier:10000 names=e2e-whoami-1 session_duration=1m dynamic.display_name=Dynamic-Whoami dynamic.theme=hacker-terminal
reverse_proxy whoami:80
}
route /blocking/whoami {
sablier url=http://tasks.sablier:10000 names=e2e-whoami-1 session_duration=1m blocking.timeout=30s
reverse_proxy whoami:80
}
route /multiple {
sablier url=http://tasks.sablier:10000 names=e2e-whoami-1,e2e-nginx-1 session_duration=1m dynamic.display_name=Multiple-Whoami dynamic.theme=hacker-terminal
reverse_proxy /multiple/whoami whoami:80
reverse_proxy /multiple/nginx nginx:80
}
route /healthy/nginx {
sablier url=http://tasks.sablier:10000 names=e2e-nginx-1 session_duration=1m dynamic.display_name=Healthy-Nginx dynamic.theme=hacker-terminal
reverse_proxy nginx:80
}
route /group {
sablier url=http://tasks.sablier:10000 {
group E2E
session_duration 1m
dynamic {
display_name Group E2E
theme hacker-terminal
}
}
reverse_proxy whoami:80
}
}

View File

@@ -1,25 +0,0 @@
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
- '../../..:/plugins-local/src/github.com/sablierapp/sablier'
ports:
- 6443:6443 # Kubernetes API Server
- 8080:80 # Ingress controller port 80

View File

@@ -1,66 +0,0 @@
#!/bin/bash
export DOCKER_COMPOSE_FILE=docker-kubernetes.yml
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 sablierapp/sablier:local into k3s..."
docker save sablierapp/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_deployment() {
kubectl apply -f ./manifests/sablier.yml
kubectl apply -f ./manifests/deployment.yml
}
destroy_deployment() {
kubectl delete -f ./manifests/deployment.yml
kubectl delete -f ./manifests/sablier.yml
}
prepare_stateful_set() {
kubectl apply -f ./manifests/statefulset.yml
}
destroy_stateful_set() {
kubectl delete -f ./manifests/statefulset.yml
}
run_kubernetes_deployment_test() {
echo "---- Running Kubernetes Test: $1 ----"
prepare_deployment
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
kubectl -n kube-system logs deployments/sablier-deployment
# kubectl -n kube-system logs deployments/caddy
fi
destroy_deployment
}
trap destroy_kubernetes EXIT
prepare_kubernetes
prepare_caddy # TODO: Implement this, will fail for now
# run_kubernetes_deployment_test Test_Dynamic
# run_kubernetes_deployment_test Test_Blocking # Blocking is not yet supported
# run_kubernetes_deployment_test Test_Multiple
# run_kubernetes_deployment_test Test_Healthy
exit $errors

View File

@@ -1,25 +0,0 @@
# traefik helm values
image:
tag: "2.9.1"
additionalArguments:
- "--experimental.localPlugins.sablier.moduleName=github.com/sablierapp/sablier"
providers:
kubernetesIngress:
allowEmptyServices: true
kubernetesCRD:
allowEmptyServices: true
additionalVolumeMounts:
- name: local-sablier-plugin
mountPath: /plugins-local/src/github.com/sablierapp/sablier
deployment:
additionalVolumes:
- name: local-sablier-plugin
hostPath:
# directory location on host
path: /plugins-local/src/github.com/sablierapp/sablier
# this field is optional
type: Directory

View File

@@ -1,121 +0,0 @@
module github.com/sablierapp/sablier/plugins/caddy
go 1.24.0
toolchain go1.24.1
require github.com/caddyserver/caddy/v2 v2.10.0
require (
cel.dev/expr v0.19.1 // indirect
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/KimMachineGun/automemlimit v0.7.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/certmagic v0.23.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.24.1 // indirect
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.3 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/libdns/libdns v1.0.0-beta.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/acmez/v3 v3.1.2 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.50.1 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/slackhq/nebula v1.6.1 // indirect
github.com/smallstep/certificates v0.26.1 // indirect
github.com/smallstep/nosql v0.6.1 // indirect
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 // indirect
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d // indirect
github.com/smallstep/truststore v0.13.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
github.com/urfave/cli v1.22.14 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.3.9 // indirect
go.step.sm/cli-utils v0.9.0 // indirect
go.step.sm/crypto v0.45.0 // indirect
go.step.sm/linkedca v0.20.1 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 // indirect
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
)

View File

@@ -1,732 +0,0 @@
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg=
cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0=
cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE=
cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY=
cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/KimMachineGun/automemlimit v0.7.1 h1:QcG/0iCOLChjfUweIMC3YL5Xy9C3VBeNmCZHrZfJMBw=
github.com/KimMachineGun/automemlimit v0.7.1/go.mod h1:QZxpHaGOQoYvFhv/r4u3U0JTC2ZcOwbSr11UZF46UBM=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b h1:uUXgbcPDK3KpW29o4iy7GtuappbWT0l5NaMo9H9pJDw=
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/config v1.27.13 h1:WbKW8hOzrWoOA/+35S5okqO/2Ap8hkkFUzoW8Hzq24A=
github.com/aws/aws-sdk-go-v2/config v1.27.13/go.mod h1:XLiyiTMnguytjRER7u5RIkhIqS8Nyz41SwAWb4xEjxs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.13 h1:XDCJDzk/u5cN7Aple7D/MiAhx1Rjo/0nueJ0La8mRuE=
github.com/aws/aws-sdk-go-v2/credentials v1.17.13/go.mod h1:FMNcjQrmuBYvOTZDtOLCIu0esmxjF7RuA/89iSXWzQI=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1 h1:5wtyAwuUiJiM3DHYeGZmP5iMonM7DFBWAEaaVPHYZA0=
github.com/aws/aws-sdk-go-v2/service/kms v1.31.1/go.mod h1:2snWQJQUKsbN66vAawJuOGX7dr37pfOq9hb0tZDGIqQ=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6 h1:o5cTaeunSpfXiLTIBx5xo2enQmiChtu1IBbzXnfU9Hs=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.6/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0 h1:Qe0r0lVURDDeBQJ4yP+BOrJkvkiCo/3FH/t+wY11dmw=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.0/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7 h1:et3Ta53gotFR4ERLXXHIHl/Uuk1qYpP5uU7cvNql8ns=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.7/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/caddyserver/caddy/v2 v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=
github.com/caddyserver/caddy/v2 v2.10.0/go.mod h1:q+dgBS3xtIJJGYI2H5Nyh9+4BvhQQ9yCGmECv4Ubdjo=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY=
github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-kit/kit v0.4.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745 h1:heyoXNxkRT155x4jTAiSv5BVSVkueifPUm+Q8LUXMRo=
github.com/google/certificate-transparency-go v1.1.8-0.20240110162603-74a5dd331745/go.mod h1:zN0wUQgV9LjwLZeFHnrAbQi8hzMVvEWePyk+MhPOk7k=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=
github.com/google/go-tspi v0.3.0/go.mod h1:xfMGI3G0PhxCdNVcYr1C4C+EizojDg/TXuX5by8CiHI=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ=
github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q=
github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/jsonstore v1.1.0 h1:WZBDjgezFS34CHI+myb4s8GGpir3UMpy7vWoCeO0n6E=
github.com/schollz/jsonstore v1.1.0/go.mod h1:15c6+9guw8vDRyozGjN3FoILt0wpruJk9Pi66vjaZfg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
github.com/slackhq/nebula v1.6.1/go.mod h1:UmkqnXe4O53QwToSl/gG7sM4BroQwAB7dd4hUaT6MlI=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY=
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
github.com/smallstep/certificates v0.26.1 h1:FIUliEBcExSfJJDhRFA/s8aZgMIFuorexnRSKQd884o=
github.com/smallstep/certificates v0.26.1/go.mod h1:OQMrW39IrGKDViKSHrKcgSQArMZ8c7EcjhYKK7mYqis=
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935 h1:kjYvkvS/Wdy0PVRDUAA0gGJIVSEZYhiAJtfwYgOYoGA=
github.com/smallstep/go-attestation v0.4.4-0.20240109183208-413678f90935/go.mod h1:vNAduivU014fubg6ewygkAvQC0IQVXqdc8vaGl/0er4=
github.com/smallstep/nosql v0.6.1 h1:X8IBZFTRIp1gmuf23ne/jlD/BWKJtDQbtatxEn7Et1Y=
github.com/smallstep/nosql v0.6.1/go.mod h1:vrN+CftYYNnDM+DQqd863ATynvYFm/6FuY9D4TeAm2Y=
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81 h1:B6cED3iLJTgxpdh4tuqByDjRRKan2EvtnOfHr2zHJVg=
github.com/smallstep/pkcs7 v0.0.0-20231024181729-3b98ecc1ca81/go.mod h1:SoUAr/4M46rZ3WaLstHxGhLEgoYIDRqxQEXLOmOEB0Y=
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d h1:06LUHn4Ia2X6syjIaCMNaXXDNdU+1N/oOHynJbWgpXw=
github.com/smallstep/scep v0.0.0-20231024192529-aee96d7ad34d/go.mod h1:4d0ub42ut1mMtvGyMensjuHYEUpRrASvkzLEJvoRQcU=
github.com/smallstep/truststore v0.13.0 h1:90if9htAOblavbMeWlqNLnO9bsjjgVv2hQeQJCi/py4=
github.com/smallstep/truststore v0.13.0/go.mod h1:3tmMp2aLKZ/OA/jnFUB0cYPcho402UG2knuJoPh4j7A=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 h1:uxMgm0C+EjytfAqyfBG55ZONKQ7mvd7x4YYCWsf8QHQ=
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
go.step.sm/cli-utils v0.9.0 h1:55jYcsQbnArNqepZyAwcato6Zy2MoZDRkWW+jF+aPfQ=
go.step.sm/cli-utils v0.9.0/go.mod h1:Y/CRoWl1FVR9j+7PnAewufAwKmBOTzR6l9+7EYGAnp8=
go.step.sm/crypto v0.45.0 h1:Z0WYAaaOYrJmKP9sJkPW+6wy3pgN3Ija8ek/D4serjc=
go.step.sm/crypto v0.45.0/go.mod h1:6IYlT0L2jfj81nVyCPpvA5cORy0EVHPhieSgQyuwHIY=
go.step.sm/linkedca v0.20.1 h1:bHDn1+UG1NgRrERkWbbCiAIvv4lD5NOFaswPDTyO5vU=
go.step.sm/linkedca v0.20.1/go.mod h1:Vaq4+Umtjh7DLFI1KuIxeo598vfBzgSYZUjgVJ7Syxw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=
go.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810 h1:V5+zy0jmgNYmK1uW/sPpBw8ioFvalrhaUrYWmu1Fpe4=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250305170421-49bf5b80c810/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/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=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4=
google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWhkkZGohVC6KRrc1oJNr4jwtQMOQXw=
google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -1,73 +0,0 @@
package caddy
import (
"context"
"io"
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
func init() {
caddy.RegisterModule(SablierMiddleware{})
}
type SablierMiddleware struct {
Config Config
client *http.Client
request *http.Request
}
// CaddyModule returns the Caddy module information.
func (SablierMiddleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.sablier",
New: func() caddy.Module { return new(SablierMiddleware) },
}
}
// Provision implements caddy.Provisioner.
func (m *SablierMiddleware) Provision(ctx caddy.Context) error {
req, err := m.Config.BuildRequest()
if err != nil {
return err
}
m.request = req
m.client = &http.Client{}
return nil
}
// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (sm SablierMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request, next caddyhttp.Handler) error {
sablierRequest := sm.request.Clone(context.TODO())
resp, err := sm.client.Do(sablierRequest)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return nil
}
defer resp.Body.Close()
if resp.Header.Get("X-Sablier-Session-Status") == "ready" {
next.ServeHTTP(rw, req)
} else {
forward(resp, rw)
}
return nil
}
func forward(resp *http.Response, rw http.ResponseWriter) {
rw.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
rw.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
io.Copy(rw, resp.Body)
}
// Interface guards
var (
_ caddy.Provisioner = (*SablierMiddleware)(nil)
_ caddyhttp.MiddlewareHandler = (*SablierMiddleware)(nil)
)

View File

@@ -1,108 +0,0 @@
package caddy_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
plugin "github.com/sablierapp/sablier/plugins/caddy"
)
func TestSablierMiddleware_ServeHTTP(t *testing.T) {
type fields struct {
Next caddyhttp.Handler
SablierMiddleware *plugin.SablierMiddleware
}
type sablier struct {
headers map[string]string
body string
}
tests := []struct {
name string
fields fields
sablier sablier
expected string
}{
{
name: "sablier service is ready",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "ready",
},
body: "response from sablier",
},
fields: fields{
Next: caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
_, err := fmt.Fprint(w, "response from service")
return err
}),
SablierMiddleware: &plugin.SablierMiddleware{
Config: plugin.Config{
SessionDuration: &oneMinute,
Dynamic: &plugin.DynamicConfiguration{},
},
},
},
expected: "response from service",
},
{
name: "sablier service is not ready",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "not-ready",
},
body: "response from sablier",
},
fields: fields{
Next: caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
_, err := fmt.Fprint(w, "response from service")
return err
}),
SablierMiddleware: &plugin.SablierMiddleware{
Config: plugin.Config{
SessionDuration: &oneMinute,
Dynamic: &plugin.DynamicConfiguration{},
},
},
},
expected: "response from sablier",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sablierMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for key, value := range tt.sablier.headers {
w.Header().Add(key, value)
}
w.Write([]byte(tt.sablier.body))
}))
defer sablierMockServer.Close()
tt.fields.SablierMiddleware.Config.SablierURL = sablierMockServer.URL
err := tt.fields.SablierMiddleware.Provision(caddy.Context{})
if err != nil {
panic(err)
}
req := httptest.NewRequest(http.MethodGet, "/my-nginx", nil)
w := httptest.NewRecorder()
tt.fields.SablierMiddleware.ServeHTTP(w, req, tt.fields.Next)
res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("expected error to be nil got %v", err)
}
if string(data) != tt.expected {
t.Errorf("expected %s got %v", tt.expected, string(data))
}
})
}
}

View File

@@ -1,9 +0,0 @@
ARG CADDY_VERSION=2.10.0
FROM caddy:${CADDY_VERSION}-builder AS builder
RUN xcaddy build \
--with github.com/sablierapp/sablier/plugins/caddy
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

View File

@@ -1,91 +0,0 @@
# Nginx NJS Sablier Module
Nginx NJS Sablier Module
**Kubernetes is not supported yet**
## Configuration
1. Load the `ngx_http_js_module.so` in the main nginx config file `/etc/nginx/nginx.conf`
```
load_module modules/ngx_http_js_module.so;
```
2. Copy/volume the `sablier.js` file to `/etc/nginx/conf.d/sablier.js`
3. Use this sample for your APIs
```nginx
js_import conf.d/sablier.js;
resolver 127.0.0.11 valid=10s ipv6=off;
server {
listen 80;
subrequest_output_buffer_size 32k;
# The internal location to reach sablier API
set $sablierUrl /sablier;
# Shared variable for default session duration
set $sablierSessionDuration 1m;
# internal location for sablier middleware
# here, the sablier API is a container named "sablier" inside the same network as nginx
location /sablier/ {
internal;
proxy_method GET;
proxy_pass http://sablier:10000/;
}
# A named location that can be used by the sablier middleware to redirect
location @whoami {
# Here is your container name, same in
# set $sablierNames whoami;
# Use variable in order to refresh DNS cache
set $whoami_server whoami;
proxy_pass http://$whoami_server:80;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# The actual location to match your API
# Will answer by a waiting page or redirect to your app if
# it is already up
location /dynamic/whoami {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
# Here is your container name
set $sablierNames whoami;
set $sablierDynamicName "Dynamic Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
}
```
### Available variables
You can configure the middleware behavior with the following variables:
**General Configuration**
- `set $sablierUrl` The internal routing to reach Sablier API
- `set $sablierNames` Comma separated names of containers/services/deployments etc.
- `set $sablierGroup` Group name to use to filter by label, ignored if sablierNames is set
- `set $sablierSessionDuration` The session duration after which containers/services/deployments instances are shutdown
- `set $sablierNginxInternalRedirect` The internal location for the service to redirect e.g. @nginx
**Dynamic Configuration**
*if any of these variables is set, then all Blocking Configuration is ignored*
- `set $sablierDynamicName`
- `set $sablierDynamicShowDetails` Set to true or false to show details specifcally for this middleware, unset to use Sablier server defaults
- `set $sablierDynamicTheme` The theme to use
- `set $sablierDynamicRefreshFrequency` The loading page refresh frequency
**Blocking Configuration**
- `set $sablierBlockingTimeout` waits until services are up and running but will not wait more than `timeout`
## Development
Change the `njs/sablier.js` configuration and start the tests for the given provider `e2e/<provider>.sh` (docker, kubernetes, etc.)

View File

@@ -1,41 +0,0 @@
version: '3.9'
services:
proxy:
image: nginx:1.27.1
ports:
- 8080:80
volumes:
# Used to load js module
- ../nginx.conf:/etc/nginx/nginx.conf
- ../../njs/sablier.js:/etc/nginx/conf.d/sablier.js
- ./nginx.conf:/etc/nginx/conf.d/default.conf
sablier:
image: sablierapp/sablier:local
ports:
- 10000:10000
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E

View File

@@ -1,92 +0,0 @@
js_import conf.d/sablier.js;
# internal docker resolver, see /etc/resolv.conf on proxy container
resolver 127.0.0.11 valid=10s ipv6=off;
server {
listen 80;
subrequest_output_buffer_size 32k;
set $sablierUrl /sablier;
set $sablierSessionDuration 1m;
location @whoami {
# Use variable in order to refresh DNS cache
set $whoami_server whoami;
proxy_pass http://$whoami_server:80;
proxy_set_header Host localhost:8080; # e2e test compliance
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location @nginx {
# Use variable in order to refresh DNS cache
set $nginx_server nginx;
proxy_pass http://$nginx_server:80;
proxy_set_header Host localhost:8080; # e2e test compliance
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /sablier/ {
internal;
proxy_method GET;
proxy_pass http://sablier:10000/;
}
location /dynamic/whoami {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierNames docker_classic_e2e-whoami-1;
set $sablierDynamicName "Dynamic Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /blocking/whoami {
set $sablierNginxInternalRedirect @whoami;
set $sablierNames docker_classic_e2e-whoami-1;
set $sablierBlockingTimeout 30s;
js_content sablier.call;
}
location /multiple/nginx {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @nginx;
set $sablierNames docker_classic_e2e-nginx-1,docker_classic_e2e-whoami-1;
set $sablierDynamicName "Multiple Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /multiple/whoami {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierNames docker_classic_e2e-nginx-1,docker_classic_e2e-whoami-1;
set $sablierDynamicName "Multiple Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /healthy/nginx {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @nginx;
set $sablierNames docker_classic_e2e-nginx-1;
set $sablierDynamicName "Healthy Nginx";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /group {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierGroup E2E;
set $sablierDynamicName "Group E2E";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
}

View File

@@ -1,39 +0,0 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=docker-compose.yml
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
}
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/sablierapp/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier proxy
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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,48 +0,0 @@
version: '3.9'
services:
proxy:
image: nginx:1.23.1
ports:
- target: 80
published: 8080
protocol: tcp
mode: host # Won't work in github actions otherwise
volumes:
# Used to load js module
- ../nginx.conf:/etc/nginx/nginx.conf
- ../../njs/sablier.js:/etc/nginx/conf.d/sablier.js
- ./nginx.conf:/etc/nginx/conf.d/default.conf
sablier:
image: sablierapp/sablier:local
ports:
- 10000:10000
command:
- start
- --provider.name=swarm
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
deploy:
labels:
- sablier.enable=true
- sablier.group=E2E
replicas: 0
nginx:
image: nginx:1.23.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
deploy:
labels:
- sablier.enable=true
- sablier.group=E2E
replicas: 0

View File

@@ -1,92 +0,0 @@
js_import conf.d/sablier.js;
# internal docker resolver, see /etc/resolv.conf on proxy container
resolver 127.0.0.11 valid=10s ipv6=off;
server {
listen 80;
subrequest_output_buffer_size 32k;
set $sablierUrl /sablier;
set $sablierSessionDuration 1m;
location @whoami {
# Use variable in order to refresh DNS cache
set $whoami_server whoami;
proxy_pass http://$whoami_server:80;
proxy_set_header Host localhost:8080; # e2e test compliance
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location @nginx {
# Use variable in order to refresh DNS cache
set $nginx_server nginx;
proxy_pass http://$nginx_server:80;
proxy_set_header Host localhost:8080; # e2e test compliance
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /sablier/ {
internal;
proxy_method GET;
proxy_pass http://sablier:10000/;
}
location /dynamic/whoami {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierNames DOCKER_SWARM_E2E_whoami;
set $sablierDynamicName "Dynamic Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /blocking/whoami {
set $sablierNginxInternalRedirect @whoami;
set $sablierNames DOCKER_SWARM_E2E_whoami;
set $sablierBlockingTimeout 30s;
js_content sablier.call;
}
location /multiple/nginx {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @nginx;
set $sablierNames DOCKER_SWARM_E2E_nginx,DOCKER_SWARM_E2E_whoami;
set $sablierDynamicName "Multiple Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /multiple/whoami {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierNames DOCKER_SWARM_E2E_nginx,DOCKER_SWARM_E2E_whoami;
set $sablierDynamicName "Multiple Whoami";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /healthy/nginx {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @nginx;
set $sablierNames DOCKER_SWARM_E2E_nginx;
set $sablierDynamicName "Healthy Nginx";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
location /group {
set $sablierDynamicShowDetails true;
set $sablierDynamicRefreshFrequency 5s;
set $sablierNginxInternalRedirect @whoami;
set $sablierGroup E2E;
set $sablierDynamicName "Group E2E";
set $sablierDynamicTheme hacker-terminal;
js_content sablier.call;
}
}

View File

@@ -1,52 +0,0 @@
#!/bin/bash
DOCKER_STACK_FILE=docker-stack.yml
DOCKER_STACK_NAME=DOCKER_SWARM_E2E
errors=0
echo "Using Docker version:"
docker version
prepare_docker_swarm() {
docker swarm init
}
prepare_docker_stack() {
docker stack deploy --compose-file $DOCKER_STACK_FILE ${DOCKER_STACK_NAME}
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock sudobmitch/docker-stack-wait -t 60 ${DOCKER_STACK_NAME}
}
destroy_docker_stack() {
docker stack rm ${DOCKER_STACK_NAME}
# Sometimes, the network is not well cleaned up, see https://github.com/moby/moby/issues/30942#issuecomment-540699206
until [ -z "$(docker stack ps ${DOCKER_STACK_NAME} -q)" ]; do sleep 1; done
}
destroy_docker_swarm() {
docker swarm leave -f || true
}
run_docker_swarm_test() {
echo "Running Docker Swarm Test: $1"
prepare_docker_stack
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
docker service logs ${DOCKER_STACK_NAME}_sablier
docker service logs ${DOCKER_STACK_NAME}_proxy
fi
destroy_docker_stack
}
trap destroy_docker_swarm EXIT
prepare_docker_swarm
run_docker_swarm_test Test_Dynamic
run_docker_swarm_test Test_Blocking
run_docker_swarm_test Test_Multiple
run_docker_swarm_test Test_Healthy
run_docker_swarm_test Test_Group
exit $errors

View File

@@ -1,25 +0,0 @@
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
- '../../..:/plugins-local/src/github.com/sablierapp/sablier'
ports:
- 6443:6443 # Kubernetes API Server
- 8080:80 # Ingress controller port 80

View File

@@ -1,124 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
sablier.enable: true
sablier.group: E2E
spec:
replicas: 0
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: acouvreur/whoami:v1.10.2
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: whoami
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
sablier.enable: true
sablier.group: E2E
spec:
replicas: 0
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.29.3
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: nginx
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-multiple-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: localhost
http:
paths:
- path: /multiple/nginx
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-healthy-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: localhost
http:
paths:
- path: /healthy/nginx
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: group-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: localhost
http:
paths:
- path: /group
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80

View File

@@ -1,78 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sablier-deployment
namespace: kube-system
labels:
app: sablier
spec:
replicas: 1
selector:
matchLabels:
app: sablier
template:
metadata:
labels:
app: sablier
spec:
serviceAccountName: sablier
containers:
- name: sablier
image: sablierapp/sablier:local
args: ["start", "--provider.name=kubernetes", "--logging.level=debug"]
ports:
- containerPort: 10000
---
apiVersion: v1
kind: Service
metadata:
name: sablier
namespace: kube-system
spec:
selector:
app: sablier
ports:
- protocol: TCP
port: 10000
targetPort: 10000
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sablier
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sablier
namespace: kube-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: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sablier
subjects:
- kind: ServiceAccount
name: sablier
namespace: kube-system

View File

@@ -1,66 +0,0 @@
#!/bin/bash
export DOCKER_COMPOSE_FILE=docker-kubernetes.yml
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 sablierapp/sablier:local into k3s..."
docker save sablierapp/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_deployment() {
kubectl apply -f ./manifests/sablier.yml
kubectl apply -f ./manifests/deployment.yml
}
destroy_deployment() {
kubectl delete -f ./manifests/deployment.yml
kubectl delete -f ./manifests/sablier.yml
}
prepare_stateful_set() {
kubectl apply -f ./manifests/statefulset.yml
}
destroy_stateful_set() {
kubectl delete -f ./manifests/statefulset.yml
}
run_kubernetes_deployment_test() {
echo "---- Running Kubernetes Test: $1 ----"
prepare_deployment
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
kubectl -n kube-system logs deployments/sablier-deployment
# kubectl -n kube-system logs deployments/nginx
fi
destroy_deployment
}
trap destroy_kubernetes EXIT
prepare_kubernetes
prepare_nginx # TODO: Implement this, will fail for now
# run_kubernetes_deployment_test Test_Dynamic
# run_kubernetes_deployment_test Test_Blocking # Blocking is not yet supported
# run_kubernetes_deployment_test Test_Multiple
# run_kubernetes_deployment_test Test_Healthy
exit $errors

View File

@@ -1,33 +0,0 @@
load_module modules/ngx_http_js_module.so;
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,120 +0,0 @@
const querystring = require('querystring')
function call(r) {
const config = createConfigurationFromVariables(r)
const request = buildRequest(config)
r.subrequest(request.url, request.query,
function(reply) {
if (reply.headersOut["X-Sablier-Session-Status"] == "ready") {
r.internalRedirect(config.internalRedirect);
} else {
r.headersOut["Content-Type"] = reply.headersOut["Content-Type"]
r.headersOut["Content-Length"] = reply.headersOut["Content-Length"]
r.return(200, reply.responseBuffer);
}
}
);
}
/**
* @typedef {Object} SablierConfig
* @property {string} sablierUrl
* @property {string} names
* @property {string} group
* @property {string} sessionDuration
* @property {string} internalRedirect
* @property {string} displayName
* @property {string} showDetails
* @property {string} theme
* @property {string} refreshFrequency
* @property {string} timeout
*
*/
/**
*
* @param {*} headers
* @returns {SablierConfig}
*/
function createConfigurationFromVariables(r) {
return {
sablierUrl: r.variables.sablierUrl,
names: r.variables.sablierNames,
group: r.variables.sablierGroup,
sessionDuration: r.variables.sablierSessionDuration,
internalRedirect: r.variables.sablierNginxInternalRedirect,
displayName: r.variables.sablierDynamicName,
showDetails: r.variables.sablierDynamicShowDetails,
theme: r.variables.sablierDynamicTheme,
refreshFrequency: r.variables.sablierDynamicRefreshFrequency,
timeout: r.variables.sablierBlockingTimeout,
}
}
/**
*
* @param {SablierConfig} c
* @returns
*/
function buildRequest(c) {
if (c.timeout == undefined || c.timeout == "") {
return createDynamicUrl(c)
} else {
return createBlockingUrl(c)
}
}
/**
*
* @param {SablierConfig} config
* @returns
*/
function createDynamicUrl(config) {
const url = `${config.sablierUrl}/api/strategies/dynamic`
const query = {
session_duration: config.sessionDuration,
display_name:config.displayName,
theme: config.theme,
refresh_frequency: config.refreshFrequency,
show_details: config.showDetails
};
if(config.names) {
query.names = config.names.split(",").map(name => name.trim())
} else if(config.group) {
query.group = config.group
} else {
throw new Error('you must specify names or group');
}
return {url, query: querystring.stringify(query)}
}
/**
*
* @param {SablierConfig} config
* @returns
*/
function createBlockingUrl(config) {
const url = `${config.sablierUrl}/api/strategies/blocking`
const query = {
session_duration: config.sessionDuration,
timeout:config.timeout,
};
if(config.names) {
query.names = config.names.split(",").map(name => name.trim())
} else if(config.group) {
query.group = config.group
} else {
throw new Error('you must specify names or group');
}
return {url, query: querystring.stringify(query)}
}
export default { call };

View File

@@ -1,44 +0,0 @@
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

@@ -1,132 +0,0 @@
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

@@ -1,15 +0,0 @@
FROM golang:1.24 AS build
WORKDIR /go/src/sablier/plugins/proxywasm
COPY go.mod ./
COPY go.sum ./
RUN go mod download
COPY . /go/src/sablier/plugins/proxywasm
RUN make build
FROM scratch
COPY --from=build /go/src/sablier/plugins/proxywasm/sablierproxywasm.wasm ./plugin.wasm

View File

@@ -1,73 +0,0 @@
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

@@ -1,6 +0,0 @@
build:
go generate
env GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o sablierproxywasm.wasm .
docker:
docker build -t sablierapp/sablier-proxy-wasm:latest .

View File

@@ -1,10 +0,0 @@
> [!IMPORTANT]
> This plugin now has its own reepository: https://github.com/sablierapp/sablier-proxywasm-plugin
>
> This plugin will no longer receive updates.
# Proxy Wasm Sablier Plugin
See more at
- https://github.com/proxy-wasm/spec
- https://github.com/proxy-wasm/proxy-wasm-go-sdk

View File

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

View File

@@ -1,55 +0,0 @@
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
- uri: "/group"
plugins:
proxywasm_sablier_plugin:
conf: '{ "sablier_url": "sablier:10000", "group": "E2E", "session_duration": "1m", "dynamic": { "display_name": "Group E2E" } }'
upstream:
type: roundrobin
nodes:
"whoami:80": 1
#END

View File

@@ -1,37 +0,0 @@
services:
apisix:
image: apache/apisix:3.11.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: sablierapp/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E

View File

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

View File

@@ -1,40 +0,0 @@
#!/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/sablierapp/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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,36 +0,0 @@
services:
envoy:
image: envoyproxy/envoy:v1.33-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: sablierapp/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E

View File

@@ -1,268 +0,0 @@
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
- match:
path: "/group"
route:
cluster: whoami
typed_per_filter_config:
sablier-wasm-group:
"@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: sablier-wasm-group
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-group"
root_id: "sablier-wasm-group"
configuration:
"@type": "type.googleapis.com/google.protobuf.StringValue"
value: |
{
"sablier_url": "sablier:10000",
"cluster": "sablier",
"group": "E2E",
"session_duration": "1m",
"dynamic": {
"display_name": "Group E2E"
}
}
vm_config:
runtime: "envoy.wasm.runtime.v8"
vm_id: "vm.sablier.sablier-wasm-group"
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

@@ -1,40 +0,0 @@
#!/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/sablierapp/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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,27 +0,0 @@
# 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

@@ -1,24 +0,0 @@
version: '3'
services:
server:
image: "rancher/k3s:v1.31.5-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

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

View File

@@ -1,15 +0,0 @@
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

@@ -1,55 +0,0 @@
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.29.2
---
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

@@ -1,83 +0,0 @@
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: sablierapp/sablier:local
args: ["start", "--provider.name=kubernetes", "--logging.level=debug"]
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

@@ -1,96 +0,0 @@
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

@@ -1,57 +0,0 @@
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: acouvreur/whoami:v1.10.2
---
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

@@ -1,68 +0,0 @@
#!/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 sablierapp/sablier:local into k3s..."
docker save sablierapp/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/sablierapp/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

@@ -1,13 +0,0 @@
FROM ubuntu:25.10
RUN apt update && apt install -y libatomic1
ADD https://github.com/Kong/ngx_wasm_module/releases/download/prerelease-0.6.0/wasmx-prerelease-0.6.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.6.0-v8-x86_64-ubuntu22.04/* /etc/nginx/
WORKDIR /etc/nginx
CMD [ "./nginx", "-g", "daemon off;" ]

View File

@@ -1,37 +0,0 @@
services:
reverseproxy:
build:
context: ..
dockerfile: Dockerfile
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ../../../sablierproxywasm.wasm:/wasm/sablierproxywasm.wasm
ports:
- "8080:8080"
sablier:
image: sablierapp/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E

View File

@@ -1,70 +0,0 @@
# 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
}
location /group {
proxy_wasm proxywasm_sablier_plugin '{ "sablier_url": "sablier:10000", "group": "E2E", "session_duration": "1m", "dynamic": { "display_name": "Group E2E" } }';
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

@@ -1,40 +0,0 @@
#!/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/sablierapp/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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,21 +0,0 @@
module github.com/sablierapp/sablier/plugins/proxy-wasm
go 1.24.0
require (
github.com/json-iterator/tinygo v0.0.0-20211221071957-84b5b690c8a0
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/proxy-wasm-go-sdk v0.24.0
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
)
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/tetratelabs/wazero v1.7.2 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,33 +0,0 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924 h1:wTcK6gcyTKJMeDka69AMjZYvisdI8CBXzTEfZ+2pOxI=
github.com/proxy-wasm/proxy-wasm-go-sdk v0.0.0-20250212164326-ab4161dcf924/go.mod h1:9mBRvh8I6Td6sg3CwEY+zGFE4DKaIoieCaca1kQnDBE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/proxy-wasm-go-sdk v0.24.0 h1:Xuwzknb4+OHBSYFXif0aBdV0F4MShL8L5YYFda9uUIs=
github.com/tetratelabs/proxy-wasm-go-sdk v0.24.0/go.mod h1:niJQcnEDtftzrVC0/qqlSs2Kzr1dwb7VxpIPHBO2XXk=
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,321 +0,0 @@
package main
import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"time"
jsoniter "github.com/json-iterator/tinygo"
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm"
"github.com/proxy-wasm/proxy-wasm-go-sdk/proxywasm/types"
"golang.org/x/exp/slices"
)
var Version string
func main() {}
func init() {
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 && !errors.Is(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

@@ -1,97 +0,0 @@
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

@@ -1,16 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
traefik

View File

@@ -1,186 +0,0 @@
> [!IMPORTANT]
> This plugin now has its own reepository: https://github.com/sablierapp/sablier-traefik-plugin
>
> This plugin will no longer receive updates.
# Traefik Sablier Plugin
- [Traefik Sablier Plugin](#traefik-sablier-plugin)
- [Installation](#installation)
- [Traefik with Docker classic](#traefik-with-docker-classic)
- [Traefik with Docker Swarm](#traefik-with-docker-swarm)
- [Traefik with Kubernetes](#traefik-with-kubernetes)
- [Plugin](#plugin)
- [Development](#development)
## Installation
1. Add this snippet in the Traefik Static configuration
```yaml
experimental:
plugins:
sablier:
moduleName: "github.com/sablierapp/sablier"
version: "v1.8.0-beta.22"
```
2. Configure the plugin using the Dynamic Configuration. Example:
```yaml
http:
middlewares:
my-sablier:
plugin:
sablier:
sablierUrl: http://sablier:10000 # The sablier URL service, must be reachable from the Traefik instance
names: whoami,nginx # Comma separated names of containers/services/deployments etc.
sessionDuration: 1m # The session duration after which containers/services/deployments instances are shutdown
# You can only use one strategy at a time
# To do so, only declare `dynamic` or `blocking`
# Dynamic strategy, provides the waiting webui
dynamic:
displayName: My Title # (Optional) Defaults to the middleware name
showDetails: true # (Optional) Set to true or false to show details specifcally for this middleware, unset to use Sablier server defaults
theme: hacker-terminal # (Optional) The theme to use
refreshFrequency: 5s # (Optional) The loading page refresh frequency
# Blocking strategy, waits until services are up and running
# but will not wait more than `timeout`
# blocking:
# timeout: 1m
```
You can also checkout the End to End tests here: [e2e](./e2e/).
## Traefik with Docker classic
**Container labels cannot be used**
Traefik will evict the container from its pool if it's not running. Which means the middleware won't trigger on incoming request.
You must use the dynamic configuration to route to the container whatever the container state is.
**Example**
*docker-compose.yml*
```yaml
version: "3.9"
services:
traefik:
image: traefik:2.9.1
command:
- --entryPoints.http.address=:80
- --providers.docker=true
- --providers.file.filename=/etc/traefik/dynamic-config.yml
- --experimental.plugins.sablier.moduleName=github.com/sablierapp/sablier/plugins/traefik
- --experimental.plugins.sablier.version=v1.10.1
ports:
- "8080:80"
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- './dynamic-config.yml:/etc/traefik/dynamic-config.yml'
sablier:
image: sablierapp/sablier:1.8.0
command:
- start
- --provider.name=docker
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
labels:
- traefik.enable=true
# Dynamic Middleware
- traefik.http.middlewares.dynamic.plugin.sablier.names=sablier-whoami-1
- traefik.http.middlewares.dynamic.plugin.sablier.sablierUrl=http://sablier:10000
- traefik.http.middlewares.dynamic.plugin.sablier.dynamic.sessionDuration=1m
whoami:
image: acouvreur/whoami:v1.10.2
# Cannot use labels because as soon as the container is stopped, the labels are not treated by Traefik
# The route doesn't exist anymore. Use dynamic-config.yml file instead.
# labels:
# - traefik.enable
# - traefik.http.routers.whoami-dynamic.rule=PathPrefix(`/dynamic/whoami`)
# - traefik.http.routers.whoami-dynamic.middlewares=dynamic@docker
```
*dynamic-config.yaml*
```yaml
http:
services:
whoami:
loadBalancer:
servers:
- url: "http://whoami:80"
routers:
whoami-dynamic:
rule: PathPrefix(`/dynamic/whoami`)
entryPoints:
- "http"
middlewares:
- dynamic@docker
service: "whoami"
```
## Traefik with Docker Swarm
⚠️ Limitations
- Traefik will evict the service from its pool as soon as the service is 0/0. You must add the [`traefik.docker.lbswarm`](https://doc.traefik.io/traefik/routing/providers/docker/#traefikdockerlbswarm) label.
```yaml
services:
whoami:
image: acouvreur/whoami:v1.10.2
deploy:
replicas: 0
labels:
- traefik.docker.lbswarm=true
```
- We cannot use [allowEmptyServices](https://doc.traefik.io/traefik/providers/docker/#allowemptyservices) because if you use the [blocking strategy](LINKHERE) you will receive a `503`.
## Traefik with Kubernetes
- The format of the `names` section is `<KIND>_<NAMESPACE>_<NAME>_<REPLICACOUNT>` where `_` is the delimiter.
- Thus no `_` are allowed in `<NAME>`
- `KIND` can be either `deployment` or `statefulset`
⚠️ Limitations
- Traefik will evict the service from its pool as soon as there is no endpoint available. You must use [`allowEmptyServices`](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowemptyservices)
- Blocking Strategy is not yet supported because of how Traefik handles the pod ip.
See [Kubernetes E2E Traefik Test script](./e2e/kubernetes.sh) to see how it is reproduced
## Plugin
The plugin is available in the Traefik [Plugin Catalog](https://plugins.traefik.io/plugins/633b4658a4caa9ddeffda119/sablier)
## Development
You can use this to load the plugin.
```yaml
version: "3.7"
services:
traefik:
image: traefik:2.9.1
command:
- --experimental.localPlugins.sablier.moduleName=github.com/sablierapp/sablier
- --entryPoints.http.address=:80
- --providers.docker=true
ports:
- "8080:80"
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- '../../..:/plugins-local/src/github.com/sablierapp/sablier'
- './dynamic-config.yml:/etc/traefik/dynamic-config.yml'
```
But I recommend you to use the [`e2e`](./e2e/) folder.

View File

@@ -1,178 +0,0 @@
package traefik
import (
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
type DynamicConfiguration struct {
DisplayName string `yaml:"displayname"`
ShowDetails *bool `yaml:"showDetails"`
Theme string `yaml:"theme"`
RefreshFrequency string `yaml:"refreshFrequency"`
}
type BlockingConfiguration struct {
Timeout string `yaml:"timeout"`
}
type Config struct {
SablierURL string `yaml:"sablierUrl"`
Names string `yaml:"names"`
Group string `yaml:"group"`
SessionDuration string `yaml:"sessionDuration"`
splittedNames []string
Dynamic *DynamicConfiguration `yaml:"dynamic"`
Blocking *BlockingConfiguration `yaml:"blocking"`
}
func CreateConfig() *Config {
return &Config{
SablierURL: "http://sablier:10000",
Names: "",
Group: "",
SessionDuration: "",
splittedNames: []string{},
Dynamic: nil,
Blocking: nil,
}
}
func (c *Config) BuildRequest(middlewareName string) (*http.Request, error) {
if len(c.SablierURL) == 0 {
return nil, fmt.Errorf("sablierURL cannot be empty")
}
names := strings.Split(c.Names, ",")
for i := range names {
names[i] = strings.TrimSpace(names[i])
}
if len(names) >= 1 && len(names[0]) > 0 {
c.splittedNames = names
}
if len(names) == 0 && len(c.Group) == 0 {
return nil, fmt.Errorf("you must specify at least one name or a group")
}
if c.Dynamic != nil && c.Blocking != nil {
return nil, fmt.Errorf("only supply one strategy: dynamic or blocking")
}
if c.Dynamic != nil {
return c.buildDynamicRequest(middlewareName)
} else if c.Blocking != nil {
return c.buildBlockingRequest()
}
return nil, fmt.Errorf("no strategy configured")
}
func (c *Config) buildDynamicRequest(middlewareName string) (*http.Request, error) {
if c.Dynamic == nil {
return nil, fmt.Errorf("dynamic config is nil")
}
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/strategies/dynamic", c.SablierURL), nil)
if err != nil {
return nil, err
}
q := request.URL.Query()
if c.SessionDuration != "" {
_, err = time.ParseDuration(c.SessionDuration)
if err != nil {
return nil, fmt.Errorf("error parsing dynamic.sessionDuration: %v", err)
}
q.Add("session_duration", c.SessionDuration)
}
for _, name := range c.splittedNames {
q.Add("names", name)
}
if c.Group != "" {
q.Add("group", c.Group)
}
if c.Dynamic.DisplayName != "" {
q.Add("display_name", c.Dynamic.DisplayName)
} else {
// display name defaults as middleware name
q.Add("display_name", middlewareName)
}
if c.Dynamic.Theme != "" {
q.Add("theme", c.Dynamic.Theme)
}
if c.Dynamic.RefreshFrequency != "" {
_, err := time.ParseDuration(c.Dynamic.RefreshFrequency)
if err != nil {
return nil, fmt.Errorf("error parsing dynamic.refreshFrequency: %v", err)
}
q.Add("refresh_frequency", c.Dynamic.RefreshFrequency)
}
if c.Dynamic.ShowDetails != nil {
q.Add("show_details", strconv.FormatBool(*c.Dynamic.ShowDetails))
}
request.URL.RawQuery = q.Encode()
return request, nil
}
func (c *Config) buildBlockingRequest() (*http.Request, error) {
if c.Blocking == nil {
return nil, fmt.Errorf("blocking config is nil")
}
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/strategies/blocking", c.SablierURL), nil)
if err != nil {
return nil, err
}
q := request.URL.Query()
if c.SessionDuration != "" {
_, err = time.ParseDuration(c.SessionDuration)
if err != nil {
return nil, fmt.Errorf("error parsing dynamic.sessionDuration: %v", err)
}
q.Add("session_duration", c.SessionDuration)
}
for _, name := range c.splittedNames {
q.Add("names", name)
}
if c.Group != "" {
q.Add("group", c.Group)
}
if c.Blocking.Timeout != "" {
_, err := time.ParseDuration(c.Blocking.Timeout)
if err != nil {
return nil, fmt.Errorf("error paring blocking.timeout: %v", err)
}
q.Add("timeout", c.Blocking.Timeout)
}
request.URL.RawQuery = q.Encode()
return request, nil
}

View File

@@ -1,268 +0,0 @@
package traefik_test
import (
"io"
"net/http"
"reflect"
"testing"
"github.com/sablierapp/sablier/plugins/traefik"
)
var fals bool = false
var tru bool = true
func TestConfig_BuildRequest(t *testing.T) {
type fields struct {
SablierURL string
Names string
Group string
SessionDuration string
Dynamic *traefik.DynamicConfiguration
Blocking *traefik.BlockingConfiguration
}
tests := []struct {
name string
fields fields
want *http.Request
wantErr bool
}{
{
name: "dynamic session with required values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
Dynamic: &traefik.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache", nil),
wantErr: false,
},
{
name: "dynamic session with default values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with group",
fields: fields{
SablierURL: "http://sablier:10000",
Group: "default",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&group=default&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with theme values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
Theme: "hacker-terminal",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&session_duration=1m&theme=hacker-terminal", nil),
wantErr: false,
},
{
name: "dynamic session with theme and display name values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
Theme: "hacker-terminal",
DisplayName: "Hello World!",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=Hello+World%21&names=nginx&names=apache&session_duration=1m&theme=hacker-terminal", nil),
wantErr: false,
},
{
name: "dynamic session with invalid session duration",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "invalid",
Dynamic: &traefik.DynamicConfiguration{},
},
want: nil,
wantErr: true,
},
{
name: "dynamic session with refresh frequency",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m", nil),
wantErr: false,
},
{
name: "dynamic session with invalid refresh frequency",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
RefreshFrequency: "invalid",
},
},
want: nil,
wantErr: true,
},
{
name: "dynamic session with show details to true",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: &tru,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=true", nil),
wantErr: false,
},
{
name: "dynamic session with show details to false",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: &fals,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m&show_details=false", nil),
wantErr: false,
},
{
name: "dynamic session without show details set",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{
ShowDetails: nil,
RefreshFrequency: "1m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/dynamic?display_name=sablier-middleware&names=nginx&names=apache&refresh_frequency=1m&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with required values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
Blocking: &traefik.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache", nil),
wantErr: false,
},
{
name: "blocking session with default values",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Blocking: &traefik.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with group",
fields: fields{
SablierURL: "http://sablier:10000",
Group: "default",
SessionDuration: "1m",
Blocking: &traefik.BlockingConfiguration{},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?group=default&session_duration=1m", nil),
wantErr: false,
},
{
name: "blocking session with timeout value",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Blocking: &traefik.BlockingConfiguration{
Timeout: "5m",
},
},
want: createRequest("GET", "http://sablier:10000/api/strategies/blocking?names=nginx&names=apache&session_duration=1m&timeout=5m", nil),
wantErr: false,
},
{
name: "blocking session with invalid timeout value",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Blocking: &traefik.BlockingConfiguration{
Timeout: "invalid",
},
},
want: nil,
wantErr: true,
},
{
name: "both strategies defined",
fields: fields{
SablierURL: "http://sablier:10000",
Names: "nginx , apache",
SessionDuration: "1m",
Dynamic: &traefik.DynamicConfiguration{},
Blocking: &traefik.BlockingConfiguration{},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &traefik.Config{
SablierURL: tt.fields.SablierURL,
Names: tt.fields.Names,
Group: tt.fields.Group,
SessionDuration: tt.fields.SessionDuration,
Dynamic: tt.fields.Dynamic,
Blocking: tt.fields.Blocking,
}
got, err := c.BuildRequest("sablier-middleware")
if (err != nil) != tt.wantErr {
t.Errorf("Config.BuildRequest() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Config.BuildRequest() = %v, want %v", got, tt.want)
}
})
}
}
func createRequest(method string, url string, body io.Reader) *http.Request {
request, err := http.NewRequest(method, url, body)
if err != nil {
panic(err)
}
return request
}

View File

@@ -1,54 +0,0 @@
version: "3.7"
services:
traefik:
image: traefik:v3.1.4
command:
- --experimental.localPlugins.sablier.moduleName=github.com/sablierapp/sablier
- --entryPoints.http.address=:80
- --providers.docker=true
- --providers.file.filename=/etc/traefik/dynamic-config.yml
ports:
- "8080:80"
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- '../../../..:/plugins-local/src/github.com/sablierapp/sablier'
- './dynamic-config.yml:/etc/traefik/dynamic-config.yml'
restart: "no"
sablier:
image: sablierapp/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: acouvreur/whoami:v1.10.2
# Cannot use labels because as soon as the container is stopped, the labels are not treated by Traefik
# The route doesn't exist anymore. Use dynamic-config.yml file instead.
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
labels:
- sablier.enable=true
- sablier.group=E2E
# - traefik.enable
# - traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)
# - traefik.http.routers.whoami.middlewares=ondemand
nginx:
image: nginx:1.27.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
# Cannot use labels because as soon as the container is stopped, the labels are not treated by Traefik
# The route doesn't exist anymore. Use dynamic-config.yml file instead.
labels:
- sablier.enable=true
- sablier.group=E2E
# - traefik.enable
# - traefik.http.routers.nginx.rule=PathPrefix(`/nginx`)
# - traefik.http.routers.nginx.middlewares=ondemand

View File

@@ -1,103 +0,0 @@
http:
services:
whoami:
loadBalancer:
servers:
- url: "http://whoami:80"
nginx:
loadBalancer:
servers:
- url: "http://nginx:80"
routers:
whoami-dynamic:
rule: PathPrefix(`/dynamic/whoami`)
entryPoints:
- "http"
middlewares:
- dynamic@file
service: "whoami"
whoami-blocking:
rule: PathPrefix(`/blocking/whoami`)
entryPoints:
- "http"
middlewares:
- blocking@file
service: "whoami"
whoami-multiple:
rule: PathPrefix(`/multiple/whoami`)
entryPoints:
- "http"
middlewares:
- multiple@file
service: "whoami"
nginx-multiple:
rule: PathPrefix(`/multiple/nginx`)
entryPoints:
- "http"
middlewares:
- multiple@file
service: "nginx"
nginx-healthy:
rule: PathPrefix(`/healthy/nginx`)
entryPoints:
- "http"
middlewares:
- healthy@file
service: "nginx"
group:
rule: PathPrefix(`/group`)
entryPoints:
- "http"
middlewares:
- group@file
service: "whoami"
middlewares:
dynamic:
plugin:
sablier:
names: docker_classic_e2e-whoami-1
sablierUrl: http://sablier:10000
sessionDuration: 1m
dynamic:
displayName: Dynamic Whoami
theme: hacker-terminal
blocking:
plugin:
sablier:
names: docker_classic_e2e-whoami-1
sablierUrl: http://sablier:10000
sessionDuration: 1m
blocking:
timeout: 30s
multiple:
plugin:
sablier:
names: docker_classic_e2e-whoami-1,docker_classic_e2e-nginx-1
sablierUrl: http://sablier:10000
sessionDuration: 1m
dynamic:
displayName: Multiple Whoami
theme: hacker-terminal
healthy:
plugin:
sablier:
names: docker_classic_e2e-nginx-1
sablierUrl: http://sablier:10000
sessionDuration: 1m
dynamic:
displayName: Healthy Nginx
group:
plugin:
sablier:
group: E2E
sablierUrl: http://sablier:10000
sessionDuration: 1m
dynamic:
displayName: Group E2E

View File

@@ -1,40 +0,0 @@
#!/bin/bash
DOCKER_COMPOSE_FILE=docker-compose.yml
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/sablierapp/sablier/e2e; then
errors=1
docker compose -f ${DOCKER_COMPOSE_FILE} -p ${DOCKER_COMPOSE_PROJECT_NAME} logs sablier traefik
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
run_docker_classic_test Test_Group
exit $errors

View File

@@ -1,112 +0,0 @@
version: "3.7"
services:
traefik:
image: traefik:v3.0.4
command:
- --experimental.localPlugins.sablier.moduleName=github.com/sablierapp/sablier
- --entryPoints.http.address=:80
- --providers.swarm=true
- --providers.swarm.refreshSeconds=1 # Default is 15s
- --providers.swarm.allowemptyservices=true
ports:
- target: 80
published: 8080
protocol: tcp
mode: host # Won't work in github actions otherwise
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- '../../../..:/plugins-local/src/github.com/sablierapp/sablier'
deploy:
labels:
- traefik.http.services.traefik.loadbalancer.server.port=8080
restart_policy:
condition: none # Do not restart on setup failure
sablier:
image: sablierapp/sablier:local
command:
- start
- --provider.name=swarm
- --logging.level=debug
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
deploy:
labels:
- traefik.enable=true
# Dynamic Middleware
- traefik.http.middlewares.dynamic.plugin.sablier.names=DOCKER_SWARM_E2E_whoami
- traefik.http.middlewares.dynamic.plugin.sablier.sablierUrl=http://tasks.sablier:10000
- traefik.http.middlewares.dynamic.plugin.sablier.sessionDuration=1m
- traefik.http.middlewares.dynamic.plugin.sablier.dynamic.displayName=Dynamic Whoami
# Blocking Middleware
- traefik.http.middlewares.blocking.plugin.sablier.names=DOCKER_SWARM_E2E_whoami
- traefik.http.middlewares.blocking.plugin.sablier.sablierUrl=http://tasks.sablier:10000
- traefik.http.middlewares.blocking.plugin.sablier.sessionDuration=1m
- traefik.http.middlewares.blocking.plugin.sablier.blocking.timeout=30s
# Multiple Dynamic Middleware
- traefik.http.middlewares.multiple.plugin.sablier.names=DOCKER_SWARM_E2E_whoami,DOCKER_SWARM_E2E_nginx
- traefik.http.middlewares.multiple.plugin.sablier.sablierUrl=http://tasks.sablier:10000
- traefik.http.middlewares.multiple.plugin.sablier.sessionDuration=1m
- traefik.http.middlewares.multiple.plugin.sablier.dynamic.displayName=Multiple Whoami
# Healthy Middleware
- traefik.http.middlewares.healthy.plugin.sablier.names=DOCKER_SWARM_E2E_nginx
- traefik.http.middlewares.healthy.plugin.sablier.sablierUrl=http://tasks.sablier:10000
- traefik.http.middlewares.healthy.plugin.sablier.sessionDuration=1m
- traefik.http.middlewares.healthy.plugin.sablier.dynamic.displayName=Healthy Nginx
# Group Middleware
- traefik.http.middlewares.group.plugin.sablier.group=E2E
- traefik.http.middlewares.group.plugin.sablier.sablierUrl=http://tasks.sablier:10000
- traefik.http.middlewares.group.plugin.sablier.sessionDuration=1m
- traefik.http.middlewares.group.plugin.sablier.dynamic.displayName=Group E2E
- traefik.http.services.sablier.loadbalancer.server.port=10000
whoami:
image: acouvreur/whoami:v1.10.2
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost" ]
interval: 5s
deploy:
replicas: 0
labels:
- sablier.enable=true
- sablier.group=E2E
- traefik.enable=true
# If you do not use the swarm load balancer, traefik will evict the service from its pool
# as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage.
- traefik.docker.lbswarm=true
- traefik.http.routers.whoami-dynamic.middlewares=dynamic@swarm
- traefik.http.routers.whoami-dynamic.rule=PathPrefix(`/dynamic/whoami`)
- traefik.http.routers.whoami-dynamic.service=whoami
- traefik.http.routers.whoami-blocking.middlewares=blocking@swarm
- traefik.http.routers.whoami-blocking.rule=PathPrefix(`/blocking/whoami`)
- traefik.http.routers.whoami-blocking.service=whoami
- traefik.http.routers.whoami-multiple.middlewares=multiple@swarm
- traefik.http.routers.whoami-multiple.rule=PathPrefix(`/multiple/whoami`)
- traefik.http.routers.whoami-multiple.service=whoami
- traefik.http.routers.whoami-group.middlewares=group@swarm
- traefik.http.routers.whoami-group.rule=PathPrefix(`/group`)
- traefik.http.routers.whoami-group.service=whoami
- traefik.http.services.whoami.loadbalancer.server.port=80
nginx:
image: nginx:1.23.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
deploy:
replicas: 0
labels:
- sablier.enable=true
- sablier.group=E2E
- traefik.enable=true
# If you do not use the swarm load balancer, traefik will evict the service from its pool
# as soon as the service is 0/0. If you do not set that, fallback to dynamic-config.yml file usage.
- traefik.docker.lbswarm=true
- traefik.http.routers.nginx-multiple.middlewares=multiple@swarm
- traefik.http.routers.nginx-multiple.rule=PathPrefix(`/multiple/nginx`)
- traefik.http.routers.nginx-multiple.service=nginx
- traefik.http.routers.nginx-healthy.middlewares=healthy@swarm
- traefik.http.routers.nginx-healthy.rule=PathPrefix(`/healthy/nginx`)
- traefik.http.routers.nginx-healthy.service=nginx
- traefik.http.services.nginx.loadbalancer.server.port=80

View File

@@ -1,52 +0,0 @@
#!/bin/bash
DOCKER_STACK_FILE=docker-stack.yml
DOCKER_STACK_NAME=DOCKER_SWARM_E2E
errors=0
echo "Using Docker version:"
docker version
prepare_docker_swarm() {
docker swarm init
}
prepare_docker_stack() {
docker stack deploy --compose-file $DOCKER_STACK_FILE ${DOCKER_STACK_NAME}
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock sudobmitch/docker-stack-wait -t 60 ${DOCKER_STACK_NAME}
}
destroy_docker_stack() {
docker stack rm ${DOCKER_STACK_NAME}
# Sometimes, the network is not well cleaned up, see https://github.com/moby/moby/issues/30942#issuecomment-540699206
until [ -z "$(docker stack ps ${DOCKER_STACK_NAME} -q)" ]; do sleep 1; done
}
destroy_docker_swarm() {
docker swarm leave -f || true
}
run_docker_swarm_test() {
echo "Running Docker Swarm Test: $1"
prepare_docker_stack
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
docker service logs ${DOCKER_STACK_NAME}_sablier
docker service logs ${DOCKER_STACK_NAME}_traefik
fi
destroy_docker_stack
}
trap destroy_docker_swarm EXIT
prepare_docker_swarm
run_docker_swarm_test Test_Dynamic
run_docker_swarm_test Test_Blocking
run_docker_swarm_test Test_Multiple
run_docker_swarm_test Test_Healthy
run_docker_swarm_test Test_Group
exit $errors

View File

@@ -1,25 +0,0 @@
version: '3'
services:
server:
image: "rancher/k3s:v1.30.2-k3s1"
command: server --disable=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
- '../../../..:/plugins-local/src/github.com/sablierapp/sablier'
ports:
- 6443:6443 # Kubernetes API Server
- 8080:80 # Ingress controller port 80

View File

@@ -1,272 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: whoami-deployment
labels:
app: whoami
sablier.enable: "true"
sablier.group: "E2E"
spec:
replicas: 0
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: acouvreur/whoami:v1.10.2
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: whoami
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: dynamic
namespace: default
spec:
plugin:
sablier:
names: deployment_default_whoami-deployment_1
sablierUrl: 'http://sablier:10000'
sessionDuration: 1m
dynamic:
displayName: 'Dynamic Whoami'
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: blocking
namespace: default
spec:
plugin:
sablier:
names: deployment_default_whoami-deployment_1
sablierUrl: 'http://sablier:10000'
sessionDuration: 1m
blocking:
timeout: 30s
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: multiple
namespace: default
spec:
plugin:
sablier:
names: deployment_default_whoami-deployment_1,deployment_default_nginx-deployment_1
sablierUrl: 'http://sablier:10000'
sessionDuration: 1m
dynamic:
displayName: 'Multiple Whoami'
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: group
namespace: default
spec:
plugin:
sablier:
group: E2E
sablierUrl: 'http://sablier:10000'
sessionDuration: 1m
dynamic:
displayName: 'Group E2E'
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-dynamic-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-dynamic@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /dynamic/whoami
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-blocking-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-blocking@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /blocking/whoami
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-multiple-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-multiple@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /multiple/whoami
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
sablier.enable: "true"
sablier.group: "E2E"
spec:
replicas: 0
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.29.3
livenessProbe:
exec:
command:
- curl
- -f
- http://localhost
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- name: http
targetPort: 80
port: 80
selector:
app: nginx
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: healthy
namespace: default
spec:
plugin:
sablier:
names: deployment_default_nginx-deployment_1
sablierUrl: 'http://sablier:10000'
sessionDuration: 1m
dynamic:
displayName: 'Healthy Nginx'
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-multiple-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-multiple@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /multiple/nginx
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-healthy-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-healthy@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /healthy/nginx
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: group-ingress
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: default-group@kubernetescrd
spec:
rules:
- host: localhost
http:
paths:
- path: /group
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80

View File

@@ -1,78 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sablier-deployment
namespace: kube-system
labels:
app: sablier
spec:
replicas: 1
selector:
matchLabels:
app: sablier
template:
metadata:
labels:
app: sablier
spec:
serviceAccountName: sablier
containers:
- name: sablier
image: sablierapp/sablier:local
args: ["start", "--provider.name=kubernetes", "--logging.level=debug"]
ports:
- containerPort: 10000
---
apiVersion: v1
kind: Service
metadata:
name: sablier
namespace: kube-system
spec:
selector:
app: sablier
ports:
- protocol: TCP
port: 10000
targetPort: 10000
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sablier
namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sablier
namespace: kube-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: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sablier
subjects:
- kind: ServiceAccount
name: sablier
namespace: kube-system

View File

@@ -1,73 +0,0 @@
#!/bin/bash
export DOCKER_COMPOSE_FILE=docker-kubernetes.yml
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 sablierapp/sablier:local into k3s..."
docker save sablierapp/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_traefik() {
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm install traefik --version 28.3.0 traefik/traefik -f values.yaml --namespace kube-system
}
prepare_deployment() {
kubectl apply -f ./manifests/sablier.yml
kubectl apply -f ./manifests/deployment.yml
}
destroy_deployment() {
kubectl delete -f ./manifests/deployment.yml
kubectl delete -f ./manifests/sablier.yml
}
prepare_stateful_set() {
kubectl apply -f ./manifests/statefulset.yml
}
destroy_stateful_set() {
kubectl delete -f ./manifests/statefulset.yml
}
run_kubernetes_deployment_test() {
echo "---- Running Kubernetes Test: $1 ----"
prepare_deployment
sleep 10
go clean -testcache
if ! go test -count=1 -tags e2e -timeout 30s -run ^${1}$ github.com/sablierapp/sablier/e2e; then
errors=1
kubectl -n kube-system logs deployments/sablier-deployment
kubectl -n kube-system logs deployments/traefik
fi
destroy_deployment
}
trap destroy_kubernetes EXIT
prepare_kubernetes
prepare_traefik
run_kubernetes_deployment_test Test_Dynamic
run_kubernetes_deployment_test Test_Blocking
run_kubernetes_deployment_test Test_Multiple
run_kubernetes_deployment_test Test_Healthy
run_kubernetes_deployment_test Test_Group
exit $errors

View File

@@ -1,21 +0,0 @@
additionalArguments:
- "--experimental.localPlugins.sablier.moduleName=github.com/sablierapp/sablier"
providers:
kubernetesIngress:
allowEmptyServices: true
kubernetesCRD:
allowEmptyServices: true
additionalVolumeMounts:
- name: local-sablier-plugin
mountPath: /plugins-local/src/github.com/sablierapp/sablier
deployment:
additionalVolumes:
- name: local-sablier-plugin
hostPath:
# directory location on host
path: /plugins-local/src/github.com/sablierapp/sablier
# this field is optional
type: Directory

View File

@@ -1,3 +0,0 @@
module github.com/sablierapp/sablier/plugins/traefik
go 1.24.0

View File

View File

@@ -1,146 +0,0 @@
package traefik
import (
"bufio"
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptrace"
)
type SablierMiddleware struct {
client *http.Client
request *http.Request
next http.Handler
useRedirect bool
}
// New function creates the configuration
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
req, err := config.BuildRequest(name)
if err != nil {
return nil, err
}
return &SablierMiddleware{
request: req,
client: &http.Client{},
next: next,
// there is no way to make blocking work in traefik without redirect so let's make it default
useRedirect: config.Blocking != nil,
}, nil
}
func (sm *SablierMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
sablierRequest := sm.request.Clone(context.TODO())
resp, err := sm.client.Do(sablierRequest)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
conditonalResponseWriter := newResponseWriter(rw)
useRedirect := false
if resp.Header.Get("X-Sablier-Session-Status") == "ready" {
// Check if the backend already received request data
trace := &httptrace.ClientTrace{
WroteHeaders: func() {
conditonalResponseWriter.ready = true
},
WroteRequest: func(info httptrace.WroteRequestInfo) {
conditonalResponseWriter.ready = true
},
}
newCtx := httptrace.WithClientTrace(req.Context(), trace)
sm.next.ServeHTTP(conditonalResponseWriter, req.WithContext(newCtx))
useRedirect = sm.useRedirect
}
if conditonalResponseWriter.ready == false {
conditonalResponseWriter.ready = true
if useRedirect {
conditonalResponseWriter.Header().Set("Location", req.URL.String())
status := http.StatusFound
if req.Method != http.MethodGet {
status = http.StatusTemporaryRedirect
}
conditonalResponseWriter.WriteHeader(status)
_, err := conditonalResponseWriter.Write([]byte(http.StatusText(status)))
if err != nil {
http.Error(conditonalResponseWriter, err.Error(), http.StatusInternalServerError)
}
} else {
conditonalResponseWriter.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
io.Copy(conditonalResponseWriter, resp.Body)
}
}
}
func newResponseWriter(rw http.ResponseWriter) *responseWriter {
return &responseWriter{
responseWriter: rw,
headers: make(http.Header),
}
}
type responseWriter struct {
responseWriter http.ResponseWriter
headers http.Header
ready bool
}
func (r *responseWriter) Header() http.Header {
if r.ready {
return r.responseWriter.Header()
}
return r.headers
}
func (r *responseWriter) Write(buf []byte) (int, error) {
if r.ready == false {
return len(buf), nil
}
return r.responseWriter.Write(buf)
}
func (r *responseWriter) WriteHeader(code int) {
if r.ready == false && code == http.StatusServiceUnavailable {
// We get a 503 HTTP Status Code when there is no backend server in the pool
// to which the request could be sent. Also, note that r.ready
// will never return false in case there was a connection established to
// the backend server and so we can be sure that the 503 was produced
// inside Traefik already
return
}
headers := r.responseWriter.Header()
for header, value := range r.headers {
headers[header] = value
}
r.responseWriter.WriteHeader(code)
}
func (r *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := r.responseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
}
return hijacker.Hijack()
}
func (r *responseWriter) Flush() {
if flusher, ok := r.responseWriter.(http.Flusher); ok {
flusher.Flush()
}
}

View File

@@ -1,193 +0,0 @@
package traefik
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/http/httptrace"
"testing"
)
func TestSablierMiddleware_ServeHTTP(t *testing.T) {
type fields struct {
Next http.Handler
Config *Config
}
type sablier struct {
headers map[string]string
body string
}
tests := []struct {
name string
fields fields
sablier sablier
expected string
code int
}{
{
name: "sablier service is ready",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httptrace.ContextClientTrace(r.Context()).WroteHeaders()
fmt.Fprint(w, "response from service")
}),
Config: &Config{
SessionDuration: "1m",
Dynamic: &DynamicConfiguration{},
},
},
expected: "response from service",
code: 200,
},
{
name: "sablier service is not ready",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "not-ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httptrace.ContextClientTrace(r.Context()).WroteHeaders()
fmt.Fprint(w, "response from service")
}),
Config: &Config{
SessionDuration: "1m",
Dynamic: &DynamicConfiguration{},
},
},
expected: "response from sablier",
code: 200,
},
{
name: "sablier service is ready but 503",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}),
Config: &Config{
SessionDuration: "1m",
Dynamic: &DynamicConfiguration{},
},
},
expected: "response from sablier",
code: 200,
},
{
name: "sablier service is ready blocking",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httptrace.ContextClientTrace(r.Context()).WroteHeaders()
fmt.Fprint(w, "response from service")
}),
Config: &Config{
SessionDuration: "1m",
Blocking: &BlockingConfiguration{},
},
},
expected: "response from service",
code: 200,
},
{
name: "sablier service is not ready blocking",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "not-ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httptrace.ContextClientTrace(r.Context()).WroteHeaders()
fmt.Fprint(w, "response from service")
}),
Config: &Config{
SessionDuration: "1m",
Blocking: &BlockingConfiguration{},
},
},
expected: "response from sablier",
// is this correct for blocking? I would expect to get error
code: 200,
},
{
name: "sablier service is ready blocking but 503",
sablier: sablier{
headers: map[string]string{
"X-Sablier-Session-Status": "ready",
},
body: "response from sablier",
},
fields: fields{
Next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusServiceUnavailable)
}),
Config: &Config{
SessionDuration: "1m",
Blocking: &BlockingConfiguration{},
},
},
expected: "Found",
code: 302,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sablierMockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for key, value := range tt.sablier.headers {
w.Header().Add(key, value)
}
w.Write([]byte(tt.sablier.body))
}))
defer sablierMockServer.Close()
tt.fields.Config.SablierURL = sablierMockServer.URL
sm, err := New(context.Background(), tt.fields.Next, tt.fields.Config, "middleware")
if err != nil {
panic(err)
}
req := httptest.NewRequest(http.MethodGet, "/my-nginx", nil)
w := httptest.NewRecorder()
sm.ServeHTTP(w, req)
res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Errorf("expected error to be nil got %v", err)
}
if string(data) != tt.expected {
t.Errorf("expected '%s' got '%v'", tt.expected, string(data))
}
if res.StatusCode != tt.code {
t.Errorf("expected '%d' got '%d'", tt.code, res.StatusCode)
}
})
}
}

View File

@@ -1,25 +0,0 @@
module.exports = {
"branches": [
{ "name": "main" },
{ "name": "beta", "channel": "beta", "prerelease": "beta" },
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
["@semantic-release/exec", {
"publishCmd": "make VERSION=${nextRelease.version} release -j 3 && make VERSION=${nextRelease.version} proxywasm"
}],
["@semantic-release/github", {
"assets": [
"sablier*"
]
}],
["@semantic-release/exec", {
"prepareCmd": "make LAST=${lastRelease.version} NEXT=${nextRelease.version} update-doc-version update-doc-version-middleware"
}],
["@semantic-release/git", {
"assets": [["**/*.{md,yml}", "!node_modules/**/*.{md,yml}"]],
"message": "docs(release): update doc version from ${lastRelease.version} to ${nextRelease.version}"
}]
]
}