feat(plugins): add Caddy reverse proxy integration

This commit is contained in:
Alexis Couvreur
2023-02-19 15:36:53 -05:00
parent 5e85cdbc69
commit 60b50971b7
29 changed files with 3042 additions and 31 deletions

View File

@@ -89,7 +89,7 @@ jobs:
strategy:
fail-fast: false
matrix:
provider: [docker_classic, docker_swarm, kubernetes]
provider: [docker, docker_swarm] # , kubernetes]
steps:
- name: Set up Go 1.18
uses: actions/setup-go@v2
@@ -115,4 +115,71 @@ jobs:
run: docker load --input /tmp/sablier.tar
- name: Test ${{ matrix.provider }}
run: cd plugins/nginx/e2e && bash ./${{ matrix.provider }}.sh
run: cd plugins/nginx/e2e && bash ./${{ matrix.provider }}.sh
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@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build and push
uses: docker/build-push-action@v2
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@v2
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: Set up Go 1.18
uses: actions/setup-go@v2
with:
go-version: 1.18
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Download artifact
uses: actions/download-artifact@v2
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@v2
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 && bash ./${{ matrix.provider }}.sh

View File

@@ -27,4 +27,6 @@ update-doc-version:
update-doc-version-middleware:
find . -type f \( -name "*.md" -o -name "*.yml" \) -exec sed -i 's/version: "v$(LAST)"/version: "v$(NEXT)"/g' {} +
find . -type f \( -name "*.md" -o -name "*.yml" \) -exec sed -i 's/version=v$(LAST)/version=v$(NEXT)/g' {} +
find . -type f \( -name "*.md" -o -name "*.yml" \) -exec sed -i 's/version=v$(LAST)/version=v$(NEXT)/g' {} +
sed -i 's/SABLIER_VERSION=v$(LAST)/SABLIER_VERSION=v$(NEXT)/g' plugins/caddy/Dockerfile.remote
sed -i 's/v$(LAST)/v$(NEXT)/g' plugins/caddy/README.md

View File

@@ -13,8 +13,6 @@ Which allows you to start your containers on demand and shut them down automatic
- [Sablier](#sablier)
- [Quick start with Traefik](#quick-start-with-traefik)
- [Reverse proxies integration plugins](#reverse-proxies-integration-plugins)
- [Traefik](#traefik)
- [Nginx](#nginx)
- [Guides](#guides)
- [Sablier Guide: Code-Server + Traefik + Kubernetes Ingress](#sablier-guide-code-server--traefik--kubernetes-ingress)
- [Configuration](#configuration)
@@ -86,24 +84,11 @@ It leverage the API calls to Sablier to your reverse proxy middleware to wake up
![Reverse Proxy Integration](./docs/img/reverse-proxy-integration.png)
| Reverse Proxy | Docker | Docker Swarm mode | Kubernetes | Podman |
| ------------- | :-------------------------------------------------------: | :---------------: | :-----------: | :-------------------------------------------------------: |
| Traefik | ✅ | ✅ | ✅ *(partial)* | [See #70](https://github.com/acouvreur/sablier/issues/70) |
| Nginx | ✅ | ✅ | ❌ |
| Apache | *Coming soon* |
| Caddy | [See #67](https://github.com/acouvreur/sablier/issues/67) |
### Traefik
See [Traefik Middleware Plugin](https://github.com/acouvreur/sablier/tree/main/plugins/traefik/README.md)
- [Traefik Middleware Plugin with Docker classic](https://github.com/acouvreur/sablier/tree/main/plugins/traefik/README.md#traefik-with-docker-classic)
- [Traefik Middleware Plugin with Docker Swarm](https://github.com/acouvreur/sablier/tree/main/plugins/traefik/README.md#traefik-with-docker-swarm)
- [Traefik Middleware Plugin with Kubernetes](https://github.com/acouvreur/sablier/tree/main/plugins/traefik/README.md#traefik-with-kubernetes)
### Nginx
See [Nginx Middleware Plugin](/plugins/nginx/README.md)
| Reverse Proxy | Docker | Docker Swarm mode | Kubernetes | Podman |
| ----------------------------- | :-----------: | :---------------: | :-----------: | :-------------------------------------------------------: |
| [Traefik](./plugins/traefik/) | ✅ | ✅ | ✅ *(partial)* | [See #70](https://github.com/acouvreur/sablier/issues/70) |
| [Nginx](./plugins/nginx/) | ✅ | ✅ | ❌ |
| [Caddy](./plugins/caddy/) | | | |
## Guides

View File

@@ -66,13 +66,6 @@ func Test_Multiple(t *testing.T) {
func Test_Healthy(t *testing.T) {
e := httpexpect.New(t, "http://localhost:8080/healthy/")
e.GET("/nginx").
Expect().
Status(http.StatusOK).
Body().
Contains(`Healthy Nginx`).
Contains(`Your instance(s) will stop after 1 minutes of inactivity`)
e.GET("/nginx").
Expect().
Status(http.StatusOK).

View File

@@ -3,4 +3,5 @@ go 1.19
use (
.
./plugins/traefik
./plugins/caddy
)

View File

@@ -1 +1,316 @@
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE=
cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg=
cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4=
cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc=
cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04=
cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak=
cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0=
cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc=
cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY=
cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0=
cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM=
cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI=
cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE=
cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8=
cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc=
cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y=
cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk=
cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590=
cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk=
cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA=
cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk=
cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo=
cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM=
cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ=
cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0=
cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w=
cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ=
cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A=
cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI=
cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA=
cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM=
cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4=
cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s=
cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0=
cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q=
cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4=
cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg=
cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w=
cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU=
cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8=
cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw=
cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI=
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08=
cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM=
cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo=
cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A=
cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0=
cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI=
cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc=
cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE=
cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A=
cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY=
cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g=
cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0=
cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg=
cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8=
cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08=
cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc=
cloud.google.com/go/longrunning v0.4.0/go.mod h1:eF3Qsw58iX/bkKtVjMTYpH0LRjQ2goDkjkNQTlzq/ZM=
cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM=
cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI=
cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w=
cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY=
cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI=
cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8=
cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4=
cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU=
cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0=
cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs=
cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk=
cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc=
cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw=
cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo=
cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA=
cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE=
cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI=
cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0=
cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg=
cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U=
cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4=
cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70=
cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM=
cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0=
cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg=
cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y=
cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo=
cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44=
cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4=
cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA=
cloud.google.com/go/security v1.11.0/go.mod h1:qL8hSHb3MqXtsVRgSPOt/igsHrs5pWAy0nrP1zl4j5I=
cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk=
cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s=
cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U=
cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo=
cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU=
cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw=
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco=
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA=
cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4=
cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg=
cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y=
cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg=
cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw=
cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU=
cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E=
cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g=
cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208=
cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8=
cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A=
cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ=
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo=
google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0=
google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY=
google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo=
google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

16
plugins/caddy/.gitignore vendored Normal file
View File

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

11
plugins/caddy/Dockerfile Normal file
View File

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

View File

@@ -0,0 +1,12 @@
ARG CADDY_VERSION=2.6.4
ARG SABLIER_VERSION=v1.4.0-beta.1
FROM caddy:${CADDY_VERSION}-builder AS builder
ADD https://github.com/acouvreur/sablier.git#${SABLIER_VERSION} /sablier
RUN xcaddy build \
--with github.com/acouvreur/sablier/plugins/caddy=/sablier/plugins/caddy
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

91
plugins/caddy/README.md Normal file
View File

@@ -0,0 +1,91 @@
# 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
```
docker build https://github.com/acouvreur/sablier.git#v1.4.0-beta.1:plugins/caddy
--build-arg=CADDY_VERSION=2.6.4
-t caddy:2.6.4-with-sablier
```
**Note:** You can change `main` for any other branch (such as `beta`, or tags `v1.4.0-beta.1`)
### By updating your Caddy Dockerfile
```
ARG CADDY_VERSION=2.6.4
FROM caddy:${CADDY_VERSION}-builder AS builder
ADD https://github.com/acouvreur/sablier.git#v1.4.0-beta.1 /sablier
RUN xcaddy build \
--with github.com/acouvreur/sablier/plugins/caddy=/sablier/plugins/caddy
FROM caddy:${CADDY_VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```
## Configuration
You can have the following configuration:
```
: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.
```
: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 acouvreur/sablier:local ../..`
3. Run test
`cd e2e/docker && bash ./run.sh`

278
plugins/caddy/config.go Normal file
View File

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

@@ -0,0 +1,450 @@
package caddy_test
import (
"io"
"net/http"
"reflect"
"testing"
"time"
"github.com/acouvreur/sablier/plugins/caddy"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
)
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

@@ -0,0 +1,60 @@
: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
}
}

View File

@@ -0,0 +1,28 @@
version: "3.7"
services:
proxy:
image: caddy:local
ports:
- "8080:80"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
restart: "no"
sablier:
image: acouvreur/sablier:local
command:
- start
- --provider.name=docker
- --logging.level=trace
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: containous/whoami:v1.5.0
nginx:
image: nginx:1.23.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s

View File

@@ -0,0 +1,39 @@
#!/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/acouvreur/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
exit $errors

View File

@@ -0,0 +1,60 @@
: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
}
}

View File

@@ -0,0 +1,37 @@
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: acouvreur/sablier:local
command:
- start
- --provider.name=swarm
- --logging.level=trace
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
whoami:
image: containous/whoami:v1.5.0
deploy:
replicas: 0
nginx:
image: nginx:1.23.1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 5s
deploy:
replicas: 0

View File

@@ -0,0 +1,52 @@
#!/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/acouvreur/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
exit $errors

View File

@@ -0,0 +1,22 @@
: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
}
}

View File

@@ -0,0 +1,25 @@
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/acouvreur/sablier'
ports:
- 6443:6443 # Kubernetes API Server
- 8080:80 # Ingress controller port 80

View File

@@ -0,0 +1,66 @@
#!/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 acouvreur/sablier:local into k3s..."
docker save acouvreur/sablier:local | docker exec -i ${DOCKER_COMPOSE_PROJECT_NAME}-server-1 ctr images import -
echo "Loading succeeded."
}
destroy_kubernetes() {
docker compose -f $DOCKER_COMPOSE_FILE -p $DOCKER_COMPOSE_PROJECT_NAME down --volumes
}
prepare_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/acouvreur/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

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

109
plugins/caddy/go.mod Normal file
View File

@@ -0,0 +1,109 @@
module github.com/acouvreur/sablier/plugins/caddy
go 1.18
require github.com/caddyserver/caddy/v2 v2.6.4
require (
filippo.io/edwards25519 v1.0.0 // indirect
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/certmagic v0.17.2 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // 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.1.0 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/cel-go v0.13.0 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.13.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.12.0 // indirect
github.com/jackc/pgx/v4 v4.17.2 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mholt/acmez v1.1.0 // indirect
github.com/micromdm/scep/v2 v2.1.0 // indirect
github.com/miekg/dns v1.1.50 // 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/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
github.com/quic-go/quic-go v0.32.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.2.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.23.2 // indirect
github.com/smallstep/nosql v0.5.0 // indirect
github.com/smallstep/truststore v0.12.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/tailscale/tscert v0.0.0-20230124224810-c6dc1f4049b2 // indirect
github.com/urfave/cli v1.22.12 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
go.step.sm/cli-utils v0.7.5 // indirect
go.step.sm/crypto v0.23.2 // indirect
go.step.sm/linkedca v0.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 // indirect
google.golang.org/grpc v1.52.3 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
)

1087
plugins/caddy/go.sum Normal file

File diff suppressed because it is too large Load Diff

73
plugins/caddy/main.go Normal file
View File

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

108
plugins/caddy/main_test.go Normal file
View File

@@ -0,0 +1,108 @@
package caddy_test
import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
plugin "github.com/acouvreur/sablier/plugins/caddy"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
)
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

@@ -22,7 +22,6 @@ 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