Compare commits
24 Commits
v2023.6.23
...
v2023.7.5-
Author | SHA1 | Date | |
---|---|---|---|
a176b754cd | |||
7b04eb2418 | |||
f8d9ff15b5 | |||
5bd7cbc132 | |||
1b06f07ce8 | |||
82228fd115 | |||
15daddbe13 | |||
5a7062d53e | |||
74409f18e8 | |||
ab7f64a684 | |||
d5cc15de3b | |||
56609ec316 | |||
5bf391b6bf | |||
74928fe413 | |||
ff1d01828d | |||
851f5d64cc | |||
e0d81c061b | |||
440d467938 | |||
f8d33299b9 | |||
6fed6358b2 | |||
ef869a02ea | |||
6559d1f594 | |||
8d91f646c2 | |||
e32c72e030 |
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/admin-key.json
|
||||||
|
/config.yml
|
||||||
|
/tools
|
||||||
|
/out
|
||||||
|
/dist
|
||||||
|
/data
|
||||||
|
/bin
|
||||||
|
/.bouncer-token
|
||||||
|
/.env
|
31
Dockerfile
31
Dockerfile
@ -1,30 +1,49 @@
|
|||||||
FROM golang:1.19 AS BUILD
|
FROM golang:1.20 AS BUILD
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y make
|
&& apt-get install -y make
|
||||||
|
|
||||||
|
ARG YQ_VERSION=4.34.1
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/local/bin \
|
||||||
|
&& wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 \
|
||||||
|
&& chmod +x /usr/local/bin/yq
|
||||||
|
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
|
RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
|
||||||
|
|
||||||
FROM busybox:latest AS RUNTIME
|
# Patch config
|
||||||
|
RUN /src/dist/bouncer_linux_amd64_v1/bouncer -c '' config dump > /src/dist/bouncer_linux_amd64_v1/config.yml \
|
||||||
|
&& yq -i '.layers.queue.templateDir = "/usr/share/bouncer/layers/queue/templates"' /src/dist/bouncer_linux_amd64_v1/config.yml \
|
||||||
|
&& yq -i '.admin.auth.privateKey = "/etc/bouncer/admin-key.json"' /src/dist/bouncer_linux_amd64_v1/config.yml \
|
||||||
|
&& yq -i '.redis.adresses = ["redis:6379"]' /src/dist/bouncer_linux_amd64_v1/config.yml
|
||||||
|
|
||||||
|
FROM alpine:3.18 AS RUNTIME
|
||||||
|
|
||||||
ARG DUMB_INIT_VERSION=1.2.5
|
ARG DUMB_INIT_VERSION=1.2.5
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
RUN mkdir -p /usr/local/bin \
|
RUN mkdir -p /usr/local/bin \
|
||||||
&& wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \
|
&& wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v${DUMB_INIT_VERSION}/dumb-init_${DUMB_INIT_VERSION}_x86_64 \
|
||||||
&& chmod +x /usr/local/bin/dumb-init
|
&& chmod +x /usr/local/bin/dumb-init
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||||
|
|
||||||
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1 /app
|
RUN mkdir -p /usr/local/bin /usr/share/bouncer/bin /etc/bouncer
|
||||||
COPY --from=BUILD /src/config.yml /etc/bouncer/config.yml
|
|
||||||
|
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1/bouncer /usr/share/bouncer/bin/bouncer
|
||||||
|
COPY --from=BUILD /src/layers /usr/share/bouncer/layers
|
||||||
|
COPY --from=BUILD /src/dist/bouncer_linux_amd64_v1/config.yml /etc/bouncer/config.yml
|
||||||
|
|
||||||
|
RUN ln -s /usr/share/bouncer/bin/bouncer /usr/local/bin/bouncer
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|
||||||
ENTRYPOINT ["/app/bouncer"]
|
ENV BOUNCER_CONFIG=/etc/bouncer/config.yml
|
||||||
|
|
||||||
CMD ["bouncer", "run", "-c", "/etc/bouncer/config.yml"]
|
CMD ["bouncer"]
|
23
Makefile
23
Makefile
@ -5,11 +5,11 @@ GITCHLOG_ARGS ?=
|
|||||||
SHELL := /bin/bash
|
SHELL := /bin/bash
|
||||||
|
|
||||||
BOUNCER_VERSION ?=
|
BOUNCER_VERSION ?=
|
||||||
GIT_VERSION := $(shell git describe --always)
|
GIT_COMMIT := $(shell git rev-parse --short HEAD)
|
||||||
DATE_VERSION := $(shell date +%Y.%-m.%-d)
|
DATE_VERSION := $(shell date +%Y.%-m.%-d)
|
||||||
FULL_VERSION := v$(DATE_VERSION)-$(GIT_VERSION)$(if $(shell git diff --stat),-dirty,)
|
FULL_VERSION := v$(DATE_VERSION)-$(GIT_COMMIT)$(if $(shell git diff --stat),-dirty,)
|
||||||
|
|
||||||
DOCKER_IMAGE_NAME ?= cadoles/bouncer
|
DOCKER_IMAGE_NAME ?= reg.cadoles.com/cadoles/bouncer
|
||||||
DOCKER_IMAGE_TAG ?= $(FULL_VERSION)
|
DOCKER_IMAGE_TAG ?= $(FULL_VERSION)
|
||||||
|
|
||||||
GOTEST_ARGS ?= -short
|
GOTEST_ARGS ?= -short
|
||||||
@ -25,16 +25,6 @@ test: test-go ## Executing tests
|
|||||||
test-go: deps
|
test-go: deps
|
||||||
( set -o allexport && source .env && set +o allexport && go test -v -count=1 $(GOTEST_ARGS) ./... )
|
( set -o allexport && source .env && set +o allexport && go test -v -count=1 $(GOTEST_ARGS) ./... )
|
||||||
|
|
||||||
test-install-script: tools/bin/bash_unit
|
|
||||||
tools/bin/bash_unit ./misc/script/test_install.sh
|
|
||||||
|
|
||||||
tools/bin/bash_unit:
|
|
||||||
mkdir -p tools/bin
|
|
||||||
cd tools/bin && bash <(curl -s https://raw.githubusercontent.com/pgrange/bash_unit/master/install.sh)
|
|
||||||
|
|
||||||
lint: ## Lint sources code
|
|
||||||
golangci-lint run --enable-all $(LINT_ARGS)
|
|
||||||
|
|
||||||
build: build-bouncer ## Build artefacts
|
build: build-bouncer ## Build artefacts
|
||||||
|
|
||||||
build-bouncer: deps ## Build executable
|
build-bouncer: deps ## Build executable
|
||||||
@ -83,14 +73,13 @@ finish-release:
|
|||||||
git push --all
|
git push --all
|
||||||
git push --tags
|
git push --tags
|
||||||
|
|
||||||
install-git-hooks:
|
|
||||||
git config core.hooksPath .githooks
|
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) .
|
docker build -t $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) .
|
||||||
|
docker tag $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG) $(DOCKER_IMAGE_NAME):latest
|
||||||
|
|
||||||
docker-release:
|
docker-release:
|
||||||
docker push $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
|
docker push $(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
|
||||||
|
docker push $(DOCKER_IMAGE_NAME):latest
|
||||||
|
|
||||||
gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
||||||
mkdir -p .gitea-release
|
mkdir -p .gitea-release
|
||||||
@ -106,7 +95,7 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
|||||||
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
|
GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \
|
||||||
GITEA_RELEASE_VERSION="$(FULL_VERSION)" \
|
GITEA_RELEASE_VERSION="$(FULL_VERSION)" \
|
||||||
GITEA_RELEASE_NAME="$(FULL_VERSION)" \
|
GITEA_RELEASE_NAME="$(FULL_VERSION)" \
|
||||||
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \
|
GITEA_RELEASE_COMMITISH_TARGET="$(GIT_COMMIT)" \
|
||||||
GITEA_RELEASE_IS_DRAFT="false" \
|
GITEA_RELEASE_IS_DRAFT="false" \
|
||||||
GITEA_RELEASE_BODY="" \
|
GITEA_RELEASE_BODY="" \
|
||||||
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
|
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
- [(FR) - Premiers pas](./fr/getting-started.md)
|
- [(FR) - Premiers pas](./fr/getting-started.md)
|
||||||
- [(FR) - Architecture générale](./fr/general-architecture.md)
|
- [(FR) - Architecture générale](./fr/general-architecture.md)
|
||||||
|
|
||||||
|
## Exemples
|
||||||
|
|
||||||
|
- [(FR) - Exemple de déploiement multi-noeuds](../misc/docker-compose/README.md)
|
||||||
## Référence
|
## Référence
|
||||||
|
|
||||||
- [(FR) - Layers](./fr/references/layers/README.md)
|
- [(FR) - Layers](./fr/references/layers/README.md)
|
||||||
@ -10,4 +13,11 @@
|
|||||||
|
|
||||||
## Tutoriels
|
## Tutoriels
|
||||||
|
|
||||||
- [(FR) - Ajouter un calque de type "file d'attente"](./fr/tutorials/add-queue-layer.md)
|
### Utilisation
|
||||||
|
|
||||||
|
- [(FR) - Ajouter un calque de type "file d'attente"](./fr/tutorials/add-queue-layer.md)
|
||||||
|
|
||||||
|
### Développement
|
||||||
|
|
||||||
|
- [(FR) - Démarrer avec les sources](./fr/tutorials/getting-start-with-sources.md)
|
||||||
|
- [(FR) - Créer son propre layer](./fr/tutorials/create-custom-layer.md)
|
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Création du proxy nommé 'cadoles' vers https://www.cadoles.com
|
# Création du proxy nommé 'cadoles' vers https://www.cadoles.com
|
||||||
bouncer admin proxy create --to https://www.cadoles.com --proxy-name cadoles
|
bouncer admin proxy create --proxy-to https://www.cadoles.com --proxy-name cadoles
|
||||||
```
|
```
|
||||||
|
|
||||||
Un message équivalent à celui ci devrait s'afficher:
|
Un message équivalent à celui ci devrait s'afficher:
|
||||||
@ -77,7 +77,7 @@
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Activation du proxy
|
# Activation du proxy
|
||||||
bouncer admin proxy update --proxy-name cadoles --enabled=true
|
bouncer admin proxy update --proxy-name cadoles --proxy-enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Un message équivalent à celui ci devrait s'afficher:
|
Un message équivalent à celui ci devrait s'afficher:
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
Vous trouverez ci-dessous la liste des entités "Layer" activables sur vos entité "Proxy":
|
Vous trouverez ci-dessous la liste des entités "Layer" activables sur vos entité "Proxy":
|
||||||
|
|
||||||
- [Queue](./queue) - File d'attente dynamique
|
- [Queue](./queue.md) - File d'attente dynamique
|
@ -24,4 +24,4 @@ Ce layer permet d'ajouter un mécanisme de file d'attente dynamique au proxy ass
|
|||||||
|
|
||||||
### Schéma
|
### Schéma
|
||||||
|
|
||||||
Voir le [schéma JSON](../../../../internal/queue/schema/layer-options.json).
|
Voir le [schéma JSON](../../../../internal/proxy/director/layer/queue/schema/layer-options.json).
|
@ -1,8 +1,8 @@
|
|||||||
# Ajouter un calque de type "file d'attente"
|
# Ajouter un layer de type "file d'attente"
|
||||||
|
|
||||||
## Étapes
|
## Étapes
|
||||||
|
|
||||||
1. Sur le serveur hébergeant les services Bouncer, utiliser le CLI pour créer un nouveau calque ("layer") pour votre proxy. Dans l'exemple, nous utiliserons le proxy `cadoles` créé dans le cadre du tutoriels ["Premiers pas"](../getting-started.md).
|
1. Sur le serveur hébergeant les services Bouncer, utiliser le CLI pour créer un nouveau layer pour votre proxy. Dans l'exemple, nous utiliserons le proxy `cadoles` créé dans le cadre du tutoriels ["Premiers pas"](../getting-started.md).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Création d'un calque nommé 'my-queue' pour le proxy 'cadoles' de type 'queue'
|
# Création d'un calque nommé 'my-queue' pour le proxy 'cadoles' de type 'queue'
|
||||||
@ -19,10 +19,10 @@
|
|||||||
+----------+-------+---------+--------+---------+-------------------------+-------------------------+
|
+----------+-------+---------+--------+---------+-------------------------+-------------------------+
|
||||||
```
|
```
|
||||||
|
|
||||||
2. À ce stade, le calque est encore inactif. Définir la capacité de la file d'attente à 1 et activer le calque en utilisant le CLI
|
2. À ce stade, le layer est encore inactif. Définir la capacité de la file d'attente à 1 et activer le layer en utilisant le CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bouncer admin layer update --proxy-name cadoles --layer-name my-queue --enabled=true --options '{"capacity": 1}'
|
bouncer admin layer update --proxy-name cadoles --layer-name my-queue --layer-enabled=true --layer-options '{"capacity": 1}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Un message équivalent à celui ci devrait s'afficher:
|
Un message équivalent à celui ci devrait s'afficher:
|
||||||
@ -43,6 +43,6 @@
|
|||||||
|
|
||||||
3. Le proxy `cadoles` a désormais une file d'attente avec une capacité d'un seul utilisateur. Vous pouvez effectuer le test en ouvrant votre navigateur sur l'adresse `http://<ip_serveur>:8080/` puis en ouvrant une fenêtre de navigation privée sur la même adresse:
|
3. Le proxy `cadoles` a désormais une file d'attente avec une capacité d'un seul utilisateur. Vous pouvez effectuer le test en ouvrant votre navigateur sur l'adresse `http://<ip_serveur>:8080/` puis en ouvrant une fenêtre de navigation privée sur la même adresse:
|
||||||
- La première fenêtre devrait afficher le site Cadoles;
|
- La première fenêtre devrait afficher le site Cadoles;
|
||||||
- La seconde fenêtre devrait afficher le message suivant: `queued (rank: 2, status: 2/1)`.
|
- La seconde fenêtre devrait afficher une page indiquant qu'on est en file d'attente.
|
||||||
|
|
||||||
Si vous laissez expirer la "session" de la première fenêtre (environ 1 minute par défaut) et que vous rafraîchissez la seconde, vous devriez avoir une inversion des états.
|
Si vous laissez expirer la "session" de la première fenêtre (environ 1 minute par défaut) et que vous rafraîchissez la seconde, vous devriez avoir une inversion des états.
|
446
doc/fr/tutorials/create-custom-layer.md
Normal file
446
doc/fr/tutorials/create-custom-layer.md
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
# Créer son propre layer
|
||||||
|
|
||||||
|
Dans ce tutoriel, nous verrons comment implémenter un layer personnalisé qui permettra d'ajouter une authentification de type [`Basic Auth](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) à un proxy.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
Avoir un environnement de développement local fonctionnel. Voir tutoriel ["Démarrer avec les sources"](./getting-started-with-sources.md).
|
||||||
|
## Étapes
|
||||||
|
### Préparer la structure de base du nouveau layer
|
||||||
|
|
||||||
|
Une implémetation d'un layer se compose majoritairement de 3 éléments:
|
||||||
|
|
||||||
|
- Une structure qui implémente une ou plusieurs interfaces (`director.MiddlewareLayer`, `director.RequestTransformerLayer` et/ou `director.ResponseTransformerLayer`);
|
||||||
|
- Un schéma au format [JSON Schema](http://json-schema.org/) qui permettra de valider les "options" de notre layer;
|
||||||
|
- Un fichier d'amorçage qui permettra à Bouncer de référencer notre nouveau layer.
|
||||||
|
|
||||||
|
1. Créer le répertoire du `package` Go qui contiendra le code de votre layer. Celui ci s'appelera `basicauth`:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir -p internal/proxy/director/layer/basicauth
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Créer la structure de base du layer:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Fichier internal/proxy/director/layer/basicauth/basicauth.go
|
||||||
|
|
||||||
|
package basicauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// On définit le "type" (la chaîne de caractères) qui
|
||||||
|
// sera associé à notre layer
|
||||||
|
const LayerType store.LayerType = "basicauth"
|
||||||
|
|
||||||
|
// On déclare la structure qui
|
||||||
|
// servira de socle pour notre layer
|
||||||
|
type BasicAuth struct{}
|
||||||
|
|
||||||
|
// Les deux méthodes suivantes, attachées à
|
||||||
|
// notre structure BasicAuth, permettent de remplir
|
||||||
|
// le contrat définit par l'interface
|
||||||
|
// director.MiddlewareLayer
|
||||||
|
|
||||||
|
// LayerType implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) LayerType() store.LayerType {
|
||||||
|
return LayerType
|
||||||
|
}
|
||||||
|
|
||||||
|
// C'est dans la méthode "Middleware" qu'on pourra implémenter la
|
||||||
|
// logique appliquée par notre layer.
|
||||||
|
// En l'état actuel l'exécution de la méthode provoquera un panic().
|
||||||
|
|
||||||
|
// Middleware implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *BasicAuth {
|
||||||
|
return &BasicAuth{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cette déclaration permet de profiter
|
||||||
|
// des capacités du compilateur pour s'assurer
|
||||||
|
// que la structure BasicAuth remplie toujours le
|
||||||
|
// contrat imposé par l'interface director.MiddlewareLayer
|
||||||
|
var _ director.MiddlewareLayer = &BasicAuth{}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Créer le schéma JSON qui sera associé aux options possibles pour notre layer:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Fichier internal/proxy/director/layer/basicauth/layer-options.json
|
||||||
|
|
||||||
|
{
|
||||||
|
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/basicauth-layer-options",
|
||||||
|
"title": "BasicAuth layer options",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Puis créer le fichier Go qui embarquera ces données dans notre binaire via le package [`embed`](https://pkg.go.dev/embed):
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Fichier internal/proxy/director/layer/basicauth/layer_options.go
|
||||||
|
|
||||||
|
package basicauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed layer-options.json
|
||||||
|
var RawLayerOptionsSchema []byte
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Enfin, créer le fichier d'amorçage pour référencer notre nouveau layer avec Bouncer:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Fichier internal/setup/basicauth_layer.go
|
||||||
|
|
||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/basicauth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// On créait une function d'initialisation qui enregistra les éléments suivants auprès de Bouncer:
|
||||||
|
// - Notre nouveau type de layer
|
||||||
|
// - Une fonction capable de créer une instance de notre layer
|
||||||
|
// - Le schéma associé aux options de notre layer
|
||||||
|
func init() {
|
||||||
|
RegisterLayer(basicauth.LayerType, setupBasicAuthLayer, basicauth.RawLayerOptionsSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// La fonction de création de notre layer
|
||||||
|
// reçoit automatiquement la configuration actuelle de Bouncer.
|
||||||
|
|
||||||
|
// Une layer plus avancé pourrait être configurable de cette manière
|
||||||
|
// en créant une nouvelle section de configuration dédiée.
|
||||||
|
func setupBasicAuthLayer(conf *config.Config) (director.Layer, error) {
|
||||||
|
return &basicauth.BasicAuth{}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tester l'intégration de notre nouveau layer
|
||||||
|
|
||||||
|
À ce stade, notre nouveau layer est normalement référencé et donc "utilisable" dans Bouncer (si on omet le fait qu'il déclenchera une `panic()`).
|
||||||
|
|
||||||
|
1. Vérifier que notre layer est bien référencé en exécutant la commande:
|
||||||
|
|
||||||
|
```
|
||||||
|
./bin/bouncer admin layer create --help
|
||||||
|
```
|
||||||
|
|
||||||
|
La sortie devrait ressembler à:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
bouncer admin layer create - Create layer
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
bouncer admin layer create [command options] [arguments...]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--layer-type LAYER_TYPE Set LAYER_TYPE as layer's type (available: [basicauth queue])
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
Comme vous devriez le voir nous pouvons désormais créer des layers de type `basicauth`.
|
||||||
|
|
||||||
|
2. Créer un proxy puis une instance de notre nouveau layer associée à celui ci:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Créer un proxy
|
||||||
|
./bin/bouncer admin proxy create --proxy-name cadoles --proxy-to https://www.cadoles.com
|
||||||
|
|
||||||
|
# Activer celui-ci
|
||||||
|
./bin/bouncer admin proxy update --proxy-name cadoles --proxy-enabled=true
|
||||||
|
|
||||||
|
# Ajouter un layer de notre nouveau type à notre proxy
|
||||||
|
./bin/bouncer admin layer create --proxy-name cadoles --layer-name mybasicauth --layer-type basicauth
|
||||||
|
|
||||||
|
# Activer notre nouveau layer
|
||||||
|
./bin/bouncer admin layer update --proxy-name cadoles --layer-name mybasicauth --layer-enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notre layer est actif** ! Cependant il est loin d'être fonctionnel. En effet, si vous faites un:
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -v http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
Vous n'aurez guère en retour qu'un:
|
||||||
|
|
||||||
|
```
|
||||||
|
* Trying 127.0.0.1:8080...
|
||||||
|
* Connected to localhost (127.0.0.1) port 8080 (#0)
|
||||||
|
> GET / HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/8.1.2
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
* Empty reply from server
|
||||||
|
* Closing connection 0
|
||||||
|
curl: (52) Empty reply from server
|
||||||
|
```
|
||||||
|
|
||||||
|
Et également dans la console où s'exécute le service `bouncer-proxy`, vous aurez le message:
|
||||||
|
|
||||||
|
```
|
||||||
|
2023/06/23 18:39:59 http: panic serving 127.0.0.1:59868: unimplemented
|
||||||
|
```
|
||||||
|
|
||||||
|
**Il est temps d'implémenter réellement la logique associée à notre layer !**
|
||||||
|
|
||||||
|
> **Note** Vous pouvez désactiver votre layer via le drapeau `--layer-enabled=false` et voir le site Cadoles s'afficher à nouveau !
|
||||||
|
|
||||||
|
## Implémenter l'authentification sur notre nouveau layer
|
||||||
|
|
||||||
|
Nous allons modifier la méthode `Middleware(layer *store.Layer) proxy.Middleware` attachée à notre structure `BasicAuth`.
|
||||||
|
|
||||||
|
1. Modifier le fichier contenant la structure de notre layer de la manière suivante:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Fichier internal/proxy/director/layer/basicauth/basicauth.go
|
||||||
|
|
||||||
|
// [...]
|
||||||
|
|
||||||
|
// Middleware implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
|
// La méthode doit retourner un "Middleware" qui est un alias
|
||||||
|
// pour les fonctions généralement utilisées
|
||||||
|
// dans les librairies http en Go pour créer
|
||||||
|
// une fonction d'interception/transformation de requête.
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// On récupère les identifiants "basic auth" transmis (ou non)
|
||||||
|
// avec la requête
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
|
// On créait une méthode locale pour gérer le cas d'une erreur d'authentification.
|
||||||
|
unauthorized := func() {
|
||||||
|
// On ajoute cette entête HTTP à la réponse pour déclencher l'affichage
|
||||||
|
// de la popup d'authentification dans le navigateur web de l'utilisateur.
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
|
|
||||||
|
// On retoure un code d'erreur HTTP 401 (Unauthorized)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// L'entête Authorization est absente ou ne correspondant
|
||||||
|
// pas à du Basic Auth, on retourne une erreur HTTP 401 et
|
||||||
|
// on interrompt le traitement de la requête ici
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// On vérifie les identifiants associés à la requête
|
||||||
|
isAuthenticated := authenticate(username, password)
|
||||||
|
|
||||||
|
// Si les identifiants sont non reconnus alors
|
||||||
|
// on interrompt le traitement de la requête
|
||||||
|
if !isAuthenticated {
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// L'authentification a réussie ! On passe la main au handler HTTP suivant
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// La méthode authenticate() prend un couple d'identifiants
|
||||||
|
// est vérifie en temps constant si ceux ci correspondent à un couple
|
||||||
|
// d'identifiants attendus.
|
||||||
|
func authenticate(username, password string) bool {
|
||||||
|
// On génère une empreinte au format sha256 pour nos identifiants
|
||||||
|
usernameHash := sha256.Sum256([]byte(username))
|
||||||
|
passwordHash := sha256.Sum256([]byte(password))
|
||||||
|
|
||||||
|
// On effectue de même avec les identifiants attendus.
|
||||||
|
// Pour l'instant, on utilise un couple d'identifiants en "dur".
|
||||||
|
expectedUsernameHash := sha256.Sum256([]byte("foo"))
|
||||||
|
expectedPasswordHash := sha256.Sum256([]byte("baz"))
|
||||||
|
|
||||||
|
// On utilise la méthode subtle.ConstantTimeCompare()
|
||||||
|
// pour faire la comparaison des identifiants en temps constant
|
||||||
|
// et ainsi éviter les attaques par timing.
|
||||||
|
usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1)
|
||||||
|
passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1)
|
||||||
|
|
||||||
|
// L'utilisateur est authentifié si son nom et son mot de passe
|
||||||
|
// correspondent avec ceux attendus.
|
||||||
|
return usernameMatch && passwordMatch
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Dans votre navigateur, essayez d'ouvrir l'URL http://127.0.0.1:8080. La popup d'authentification devrait s'afficher et vous devriez pouvoir utiliser les identifiants définis dans la fonction `authenticate()` pour vous authentifier et accéder au site de Cadoles !
|
||||||
|
|
||||||
|
> **Note** Essayez de désactiver le layer. L'authentification est automatiquement désactivée également !
|
||||||
|
|
||||||
|
## Déclarer des options pour pouvoir utiliser des identifiants dynamiques
|
||||||
|
|
||||||
|
En l'état actuel notre layer est fonctionnel. Cependant il souffre d'un problème notable: les identifiants attendus sont statiques et embarqués en dur dans le code. Nous allons utiliser le schéma associé à nos options, jusqu'alors vide, pour pouvoir créer une paire d'identifiants attendus dynamique.
|
||||||
|
|
||||||
|
|
||||||
|
1. Modifier le schéma JSON des options de notre layer:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Fichier internal/proxy/director/layer/basicauth/layer-options.json
|
||||||
|
|
||||||
|
{
|
||||||
|
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/basicauth-layer-options",
|
||||||
|
"title": "BasicAuth layer options",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. On modifie notre méthode `Middleware()` et la fonction `authenticate()` pour utiliser ces nouvelles options:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package basicauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
proxy "forge.cadoles.com/Cadoles/go-proxy"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const LayerType store.LayerType = "basicauth"
|
||||||
|
|
||||||
|
type BasicAuth struct{}
|
||||||
|
|
||||||
|
// LayerType implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) LayerType() store.LayerType {
|
||||||
|
return LayerType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware implements director.MiddlewareLayer.
|
||||||
|
func (*BasicAuth) Middleware(layer *store.Layer) proxy.Middleware {
|
||||||
|
// La méthode doit retourner un "Middleware" qui est un alias
|
||||||
|
// pour les fonctions généralement utilisées
|
||||||
|
// dans les librairies http en Go pour créer
|
||||||
|
// une fonction d'interception/transformation de requête.
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// On récupère les identifiants "basic auth" transmis (ou non)
|
||||||
|
// avec la requête
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
|
||||||
|
// On créait une méthode locale pour gérer le cas d'une erreur d'authentification.
|
||||||
|
unauthorized := func() {
|
||||||
|
// On ajoute cette entête HTTP à la réponse pour déclencher l'affichage
|
||||||
|
// de la popup d'authentification dans le navigateur web de l'utilisateur.
|
||||||
|
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
||||||
|
|
||||||
|
// On retoure un code d'erreur HTTP 401 (Unauthorized)
|
||||||
|
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// L'entête Authorization est absente ou ne correspondant
|
||||||
|
// pas à du Basic Auth, on retourne une erreur HTTP 401 et
|
||||||
|
// on interrompt le traitement de la requête ici
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// On extrait les identifiants des options associées à notre layer
|
||||||
|
expectedUsername, usernameExists := layer.Options["username"].(string)
|
||||||
|
expectedPassword, passwordExists := layer.Options["password"].(string)
|
||||||
|
|
||||||
|
// Si le nom d'utilisateur ou le mot de passe attendu n'existe pas
|
||||||
|
// alors on retourne une erreur HTTP 500 à l'utilisateur.
|
||||||
|
if !usernameExists || !passwordExists {
|
||||||
|
logger.Error(r.Context(), "basicauth layer missing password or username option")
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// On vérifie les identifiants associés à la requête
|
||||||
|
isAuthenticated := authenticate(username, password, expectedUsername, expectedPassword)
|
||||||
|
|
||||||
|
// Si les identifiants sont non reconnus alors
|
||||||
|
// on interrompt le traitement de la requête
|
||||||
|
if !isAuthenticated {
|
||||||
|
unauthorized()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// L'authentification a réussie ! On passe la main au handler HTTP suivant
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(username, password string, expectedUsername, expectedPassword string) bool {
|
||||||
|
// On génère une empreinte au format sha256 pour nos identifiants
|
||||||
|
usernameHash := sha256.Sum256([]byte(username))
|
||||||
|
passwordHash := sha256.Sum256([]byte(password))
|
||||||
|
|
||||||
|
// On effectue de même avec les identifiants attendus.
|
||||||
|
expectedUsernameHash := sha256.Sum256([]byte(expectedUsername))
|
||||||
|
expectedPasswordHash := sha256.Sum256([]byte(expectedPassword))
|
||||||
|
|
||||||
|
// On utilise la méthode subtle.ConstantTimeCompare()
|
||||||
|
// pour faire la comparaison des identifiants en temps constant
|
||||||
|
// et ainsi éviter les attaques par timing.
|
||||||
|
usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1)
|
||||||
|
passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1)
|
||||||
|
|
||||||
|
// L'utilisateur est authentifié si son nom et son mot de passe
|
||||||
|
// correspondent avec ceux attendus.
|
||||||
|
return usernameMatch && passwordMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *BasicAuth {
|
||||||
|
return &BasicAuth{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ director.MiddlewareLayer = &BasicAuth{}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Modifiez votre layer via la commande d'administration pour déclarer une paire d'identifiants:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/bouncer admin layer update --proxy-name cadoles --layer-name mybasicauth --layer-options='{"username":"jdoe","password":"notsosecret"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Essayer d'accéder à l'adresse http://127.0.0.1:8080 avec votre navigateur. La popup d'authentification devrait s'afficher et vous devriez pouvoir vous authentifier avec le nouveau couple d'identifiants définis dans les options de votre layer !
|
||||||
|
|
||||||
|
> **Note** Vous pouvez modifier les identifiants plusieurs fois via la commande et vérifier que la fenêtre s'affiche toujours à nouveau, demandant les nouveaux identifiants.
|
125
doc/fr/tutorials/getting-started-with-sources.md
Normal file
125
doc/fr/tutorials/getting-started-with-sources.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Démarrer avec les sources
|
||||||
|
|
||||||
|
Dans ce tutoriel, nous verrons comment lancer un environnement de développement en local sur notre machine afin de travailler sur les sources de Bouncer.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
Les éléments suivants doivent être installés sur votre machine:
|
||||||
|
|
||||||
|
- [Golang > 1.20](https://go.dev/)
|
||||||
|
- [Docker](https://www.docker.com/)
|
||||||
|
- [Git](https://git-scm.com/)
|
||||||
|
- [GNU Make](https://www.gnu.org/software/make/)
|
||||||
|
|
||||||
|
Les ports suivants doivent être disponibles sur votre machine:
|
||||||
|
|
||||||
|
- `8080`
|
||||||
|
- `8081`
|
||||||
|
|
||||||
|
## Étapes
|
||||||
|
|
||||||
|
1. Cloner le dépôt des sources du projet Bouncer
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://forge.cadoles.com/Cadoles/bouncer
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Se positionner dans le répertoire du projet
|
||||||
|
|
||||||
|
```
|
||||||
|
cd bouncer
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Lancer le projet en mode "développement"
|
||||||
|
|
||||||
|
```
|
||||||
|
make watch
|
||||||
|
```
|
||||||
|
|
||||||
|
Si toutes les dépendances sont correctement installées et configurées sur votre machine, la console devrait afficher une série de messages pour ensuite s'arrêter sur quelque chose ressemblant à:
|
||||||
|
|
||||||
|
```
|
||||||
|
14:47:06: daemon: make run BOUNCER_CMD="--config config.yml server admin run"
|
||||||
|
2023-06-23 20:47:06.095 [INFO] <./internal/command/server/admin/run.go:42> RunCommand.func1 listening {"url": "http://127.0.0.1:8081"}
|
||||||
|
2023-06-23 20:47:06.095 [INFO] <./internal/admin/server.go:126> (*Server).run http server listening
|
||||||
|
14:47:06: daemon: make run-redis
|
||||||
|
bouncer-redis
|
||||||
|
docker run --rm -t \
|
||||||
|
--name bouncer-redis \
|
||||||
|
-v /home/wpetit/workspace/bouncer/data/redis:/data \
|
||||||
|
-p 6379:6379 \
|
||||||
|
redis:alpine3.17 \
|
||||||
|
redis-server --save 60 1 --loglevel warning
|
||||||
|
1:C 23 Jun 2023 20:47:06.754 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
||||||
|
1:C 23 Jun 2023 20:47:06.754 # Redis version=7.0.11, bits=64, commit=00000000, modified=0, pid=1, just started
|
||||||
|
1:C 23 Jun 2023 20:47:06.754 # Configuration loaded
|
||||||
|
1:M 23 Jun 2023 20:47:06.759 # Warning: Could not create server TCP listening socket ::*:6379: unable to bind socket, errno: 97
|
||||||
|
1:M 23 Jun 2023 20:47:06.760 # Server initialized
|
||||||
|
1:M 23 Jun 2023 20:47:06.760 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect
|
||||||
|
```
|
||||||
|
|
||||||
|
À ce stade, le serveur `bouncer-admin` écoute sur http://127.0.0.1:8081 et le serveur `bouncer-proxy` sur http://127.0.0.1:8080.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> L'outil [`modd`](https://github.com/cortesi/modd) est utilisé pour surveiller les modifications sur les sources et relancer automatiquement la compilation et les services en cas de changement.
|
||||||
|
|
||||||
|
## Commandes `make` utiles
|
||||||
|
|
||||||
|
### `make watch`
|
||||||
|
|
||||||
|
Surveiller les sources, compiler celles ci en cas de modifications et lancer les services `bouncer-proxy` et `bouncer-admin`.
|
||||||
|
|
||||||
|
#### `make test`
|
||||||
|
|
||||||
|
Exécuter les tests unitaires/d'intégration du projet.
|
||||||
|
|
||||||
|
#### `make build`
|
||||||
|
|
||||||
|
Compiler une version de développement du binaire `bouncer`.
|
||||||
|
|
||||||
|
#### `make docker-build`
|
||||||
|
|
||||||
|
Construire une image Docker pour Bouncer.
|
||||||
|
|
||||||
|
Vous pouvez ensuite lancer l'image localement avec la commande:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
-it --rm \
|
||||||
|
reg.cadoles.com/cadoles/bouncer:<tag> \
|
||||||
|
-p 8080:8080 \
|
||||||
|
bouncer server proxy run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Arborescence du projet
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.
|
||||||
|
├── bin # Répertoire de destination des binaires Go de développement
|
||||||
|
├── cmd # Package principal (main) du binaire Bouncer
|
||||||
|
├── data # Répertoire des données de développement (Redis)
|
||||||
|
├── dist # Répertoire de destination des archives/paquets pour la publication
|
||||||
|
├── doc # Répertoire de documentation du projet
|
||||||
|
├── internal # Source Go du projet
|
||||||
|
│ ├── admin
|
||||||
|
│ ├── auth
|
||||||
|
│ ├── chi
|
||||||
|
│ ├── client
|
||||||
|
│ ├── command
|
||||||
|
│ ├── config
|
||||||
|
│ ├── format
|
||||||
|
│ ├── imports
|
||||||
|
│ ├── jwk
|
||||||
|
│ ├── proxy
|
||||||
|
│ ├── schema
|
||||||
|
│ ├── setup
|
||||||
|
│ └── store
|
||||||
|
├── layers # Fichiers annexes liés aux layers (templates HTML)
|
||||||
|
│ └── queue
|
||||||
|
├── misc # Fichiers annexes
|
||||||
|
│ ├── jenkins # Fichiers liés au pipeline d'intégration continue Jenkins
|
||||||
|
│ ├── logo # Logo du projet
|
||||||
|
│ └── packaging # Fichiers liés à l'empaquetage des binaires
|
||||||
|
└── tools # Outils utilisés en développement
|
||||||
|
```
|
17
go.mod
17
go.mod
@ -3,13 +3,15 @@ module forge.cadoles.com/cadoles/bouncer
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333
|
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230701194111-c6b3d482cca6
|
||||||
github.com/Masterminds/sprig/v3 v3.2.3
|
github.com/Masterminds/sprig/v3 v3.2.3
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||||
github.com/mitchellh/mapstructure v1.4.1
|
github.com/mitchellh/mapstructure v1.4.1
|
||||||
github.com/ory/dockertest/v3 v3.10.0
|
github.com/ory/dockertest/v3 v3.10.0
|
||||||
|
github.com/prometheus/client_golang v1.16.0
|
||||||
github.com/qri-io/jsonschema v0.2.1
|
github.com/qri-io/jsonschema v0.2.1
|
||||||
github.com/redis/go-redis/v9 v9.0.4
|
github.com/redis/go-redis/v9 v9.0.4
|
||||||
)
|
)
|
||||||
@ -21,6 +23,7 @@ require (
|
|||||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/containerd/continuity v0.3.0 // indirect
|
github.com/containerd/continuity v0.3.0 // indirect
|
||||||
@ -30,18 +33,22 @@ require (
|
|||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/huandu/xstrings v1.3.3 // indirect
|
github.com/huandu/xstrings v1.3.3 // indirect
|
||||||
github.com/imdario/mergo v0.3.12 // indirect
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
github.com/mitchellh/reflectwalk v1.0.0 // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/opencontainers/runc v1.1.5 // indirect
|
github.com/opencontainers/runc v1.1.5 // indirect
|
||||||
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.10.1 // indirect
|
||||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||||
github.com/rivo/uniseg v0.2.0 // indirect
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||||
@ -52,7 +59,7 @@ require (
|
|||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,7 +95,7 @@ require (
|
|||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
golang.org/x/crypto v0.8.0 // indirect
|
golang.org/x/crypto v0.8.0 // indirect
|
||||||
golang.org/x/mod v0.9.0 // indirect
|
golang.org/x/mod v0.9.0 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
golang.org/x/sys v0.8.0 // indirect
|
||||||
golang.org/x/term v0.7.0 // indirect
|
golang.org/x/term v0.7.0 // indirect
|
||||||
golang.org/x/tools v0.7.0 // indirect
|
golang.org/x/tools v0.7.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||||
|
34
go.sum
34
go.sum
@ -49,8 +49,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333 h1:dAajr9wX8WuFPrwjbKNXRmbF+4AaAT7bUj66G7gdZ+c=
|
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230701194111-c6b3d482cca6 h1:FTk0ZoaV5N8Tkps5Da5RrDMZZXSHZIuD67Hy1Y4fsos=
|
||||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230512083245-e2dc3e1a0333/go.mod h1:o8ZK5v/3J1dRmklFVn1l6WHAyQ3LgegyHjRIT8KLAFw=
|
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230701194111-c6b3d482cca6/go.mod h1:o8ZK5v/3J1dRmklFVn1l6WHAyQ3LgegyHjRIT8KLAFw=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
@ -80,6 +80,8 @@ github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUS
|
|||||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
|
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/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao=
|
||||||
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y=
|
||||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
@ -228,8 +230,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
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 v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
@ -248,7 +251,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
@ -307,8 +309,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@ -347,6 +348,8 @@ github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp9
|
|||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
@ -386,7 +389,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||||
|
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||||
|
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||||
|
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||||
|
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||||
|
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
|
||||||
|
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||||
github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
|
github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA=
|
||||||
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
|
github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64=
|
||||||
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
|
github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0=
|
||||||
@ -397,7 +408,6 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
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/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@ -590,8 +600,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -663,8 +673,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||||
@ -889,12 +899,12 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
|||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-20180628173108-788fd7840127/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 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -155,13 +156,22 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
layerName, err := store.ValidateName(createLayerReq.Name)
|
layerName, err := store.ValidateName(createLayerReq.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err)))
|
logger.Error(r.Context(), "invalid 'name' parameter", logger.E(errors.WithStack(err)))
|
||||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), store.LayerType(createLayerReq.Type), createLayerReq.Options)
|
layerType := store.LayerType(createLayerReq.Type)
|
||||||
|
|
||||||
|
if !setup.LayerTypeExists(layerType) {
|
||||||
|
logger.Error(r.Context(), "unknown layer type", logger.E(errors.WithStack(err)), logger.F("layerType", layerType))
|
||||||
|
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), layerType, createLayerReq.Options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, store.ErrAlreadyExist) {
|
if errors.Is(err, store.ErrAlreadyExist) {
|
||||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil)
|
api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil)
|
||||||
@ -235,7 +245,19 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if updateLayerReq.Options != nil {
|
if updateLayerReq.Options != nil {
|
||||||
if err := schema.ValidateLayerOptions(ctx, layer.Type, updateLayerReq.Options); err != nil {
|
layerOptionsSchema, err := setup.GetLayerOptionsSchema(layer.Type)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(r.Context(), "could not retrieve layer options schema", logger.E(errors.WithStack(err)))
|
||||||
|
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rawOptions := func(opts *store.LayerOptions) map[string]any {
|
||||||
|
return *opts
|
||||||
|
}(updateLayerReq.Options)
|
||||||
|
|
||||||
|
if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {
|
||||||
logger.Error(r.Context(), "could not validate layer options", logger.E(errors.WithStack(err)))
|
logger.Error(r.Context(), "could not validate layer options", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
var invalidDataErr *schema.InvalidDataError
|
var invalidDataErr *schema.InvalidDataError
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -101,6 +102,25 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
|||||||
|
|
||||||
router.Use(corsMiddleware.Handler)
|
router.Use(corsMiddleware.Handler)
|
||||||
|
|
||||||
|
if s.serverConfig.Metrics.Enabled {
|
||||||
|
metrics := s.serverConfig.Metrics
|
||||||
|
|
||||||
|
logger.Info(ctx, "enabling metrics", logger.F("endpoint", metrics.Endpoint))
|
||||||
|
|
||||||
|
router.Group(func(r chi.Router) {
|
||||||
|
if metrics.BasicAuth != nil {
|
||||||
|
logger.Info(ctx, "enabling authentication on metrics endpoint")
|
||||||
|
|
||||||
|
r.Use(middleware.BasicAuth(
|
||||||
|
"metrics",
|
||||||
|
metrics.BasicAuth.CredentialsMap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handle(string(metrics.Endpoint), promhttp.Handler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
router.Route("/api/v1", func(r chi.Router) {
|
router.Route("/api/v1", func(r chi.Router) {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(auth.Middleware(
|
r.Use(auth.Middleware(
|
||||||
|
@ -2,27 +2,22 @@ package flag
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FlagLayerName = "layer-name"
|
FlagKeyLayerType = "layer-type"
|
||||||
FlagLayerType = "layer-type"
|
|
||||||
FlagLayerOptions = "layer-options"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithLayerFlags(flags ...cli.Flag) []cli.Flag {
|
func WithLayerFlags(flags ...cli.Flag) []cli.Flag {
|
||||||
baseFlags := proxyFlag.WithProxyFlags(
|
baseFlags := proxyFlag.WithProxyFlags(
|
||||||
&cli.StringFlag{
|
LayerName(),
|
||||||
Name: FlagLayerName,
|
|
||||||
Usage: "use `LAYER_NAME` as targeted layer",
|
|
||||||
Value: "",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
flags = append(flags, baseFlags...)
|
flags = append(flags, baseFlags...)
|
||||||
@ -32,22 +27,63 @@ func WithLayerFlags(flags ...cli.Flag) []cli.Flag {
|
|||||||
|
|
||||||
func WithLayerCreateFlags(flags ...cli.Flag) []cli.Flag {
|
func WithLayerCreateFlags(flags ...cli.Flag) []cli.Flag {
|
||||||
return WithLayerFlags(
|
return WithLayerFlags(
|
||||||
&cli.StringFlag{
|
LayerType(),
|
||||||
Name: FlagLayerType,
|
LayerOptions(),
|
||||||
Usage: "Set `LAYER_TYPE` as layer's type",
|
|
||||||
Value: "",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: FlagLayerOptions,
|
|
||||||
Usage: "Set `LAYER_OPTIONS` as layer's options",
|
|
||||||
Value: "{}",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KeyLayerName = "layer-name"
|
||||||
|
|
||||||
|
func LayerName() cli.Flag {
|
||||||
|
return &cli.StringFlag{
|
||||||
|
Name: KeyLayerName,
|
||||||
|
Usage: "use `LAYER_NAME` as targeted layer",
|
||||||
|
Value: "",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyLayerType = "layer-type"
|
||||||
|
|
||||||
|
func LayerType() cli.Flag {
|
||||||
|
return &cli.StringFlag{
|
||||||
|
Name: KeyLayerType,
|
||||||
|
Usage: fmt.Sprintf("Set `LAYER_TYPE` as layer's type (available: %v)", setup.GetLayerTypes()),
|
||||||
|
Value: "",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyLayerOptions = "layer-options"
|
||||||
|
|
||||||
|
func LayerOptions() cli.Flag {
|
||||||
|
return &cli.StringFlag{
|
||||||
|
Name: KeyLayerOptions,
|
||||||
|
Usage: "Set `LAYER_OPTIONS` as layer's options",
|
||||||
|
Value: "{}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyLayerWeight = "layer-weight"
|
||||||
|
|
||||||
|
func LayerWeight() cli.Flag {
|
||||||
|
return &cli.IntFlag{
|
||||||
|
Name: KeyLayerWeight,
|
||||||
|
Usage: "Set `LAYER_WEIGHT` as layer's weight",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyLayerEnabled = "layer-enabled"
|
||||||
|
|
||||||
|
func LayerEnabled() cli.Flag {
|
||||||
|
return &cli.BoolFlag{
|
||||||
|
Name: KeyLayerEnabled,
|
||||||
|
Usage: "Enable or disable layer",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AssertLayerName(ctx *cli.Context) (store.LayerName, error) {
|
func AssertLayerName(ctx *cli.Context) (store.LayerName, error) {
|
||||||
rawLayerName := ctx.String(FlagLayerName)
|
rawLayerName := ctx.String(KeyLayerName)
|
||||||
|
|
||||||
name, err := store.ValidateName(rawLayerName)
|
name, err := store.ValidateName(rawLayerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -58,13 +94,18 @@ func AssertLayerName(ctx *cli.Context) (store.LayerName, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AssertLayerType(ctx *cli.Context) (store.LayerType, error) {
|
func AssertLayerType(ctx *cli.Context) (store.LayerType, error) {
|
||||||
rawLayerType := ctx.String(FlagLayerType)
|
rawLayerType := ctx.String(FlagKeyLayerType)
|
||||||
|
|
||||||
return store.LayerType(rawLayerType), nil
|
layerType := store.LayerType(rawLayerType)
|
||||||
|
if !setup.LayerTypeExists(layerType) {
|
||||||
|
return "", errors.Errorf("unknown layer type '%s'", layerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layerType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssertLayerOptions(ctx *cli.Context) (store.LayerOptions, error) {
|
func AssertLayerOptions(ctx *cli.Context) (store.LayerOptions, error) {
|
||||||
rawLayerOptions := ctx.String(FlagLayerOptions)
|
rawLayerOptions := ctx.String(KeyLayerOptions)
|
||||||
|
|
||||||
layerOptions := store.LayerOptions{}
|
layerOptions := store.LayerOptions{}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||||
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag"
|
||||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||||
@ -20,18 +21,9 @@ func UpdateCommand() *cli.Command {
|
|||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Update layer",
|
Usage: "Update layer",
|
||||||
Flags: layerFlag.WithLayerFlags(
|
Flags: layerFlag.WithLayerFlags(
|
||||||
&cli.BoolFlag{
|
flag.LayerEnabled(),
|
||||||
Name: "enabled",
|
flag.LayerWeight(),
|
||||||
Usage: "Enable or disable proxy",
|
flag.LayerOptions(),
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "weight",
|
|
||||||
Usage: "Set `WEIGHT` as proxy's weight",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "options",
|
|
||||||
Usage: "Set `OPTIONS` as proxy's options",
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
@ -53,22 +45,22 @@ func UpdateCommand() *cli.Command {
|
|||||||
|
|
||||||
opts := &client.UpdateLayerOptions{}
|
opts := &client.UpdateLayerOptions{}
|
||||||
|
|
||||||
if ctx.IsSet("options") {
|
if ctx.IsSet(flag.KeyLayerOptions) {
|
||||||
var options store.LayerOptions
|
var options store.LayerOptions
|
||||||
if err := json.Unmarshal([]byte(ctx.String("options")), &options); err != nil {
|
if err := json.Unmarshal([]byte(ctx.String(flag.KeyLayerOptions)), &options); err != nil {
|
||||||
return errors.Wrap(err, "could not parse options")
|
return errors.Wrap(err, "could not parse layer's options")
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.Options = &options
|
opts.Options = &options
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("weight") {
|
if ctx.IsSet(flag.KeyLayerWeight) {
|
||||||
weight := ctx.Int("weight")
|
weight := ctx.Int(flag.KeyLayerWeight)
|
||||||
opts.Weight = &weight
|
opts.Weight = &weight
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("enabled") {
|
if ctx.IsSet(flag.KeyLayerEnabled) {
|
||||||
enabled := ctx.Bool("enabled")
|
enabled := ctx.Bool(flag.KeyLayerEnabled)
|
||||||
opts.Enabled = &enabled
|
opts.Enabled = &enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -18,17 +19,8 @@ func CreateCommand() *cli.Command {
|
|||||||
Name: "create",
|
Name: "create",
|
||||||
Usage: "Create proxy",
|
Usage: "Create proxy",
|
||||||
Flags: proxyFlag.WithProxyFlags(
|
Flags: proxyFlag.WithProxyFlags(
|
||||||
&cli.StringFlag{
|
flag.ProxyTo(true),
|
||||||
Name: "to",
|
flag.ProxyFrom(),
|
||||||
Usage: "Set `TO` as proxy's destination url",
|
|
||||||
Value: "",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "from",
|
|
||||||
Usage: "Set `FROM` as proxy's patterns to match incoming requests",
|
|
||||||
Value: cli.NewStringSlice("*"),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
@ -43,12 +35,12 @@ func CreateCommand() *cli.Command {
|
|||||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||||
}
|
}
|
||||||
|
|
||||||
to, err := url.Parse(ctx.String("to"))
|
to, err := url.Parse(ctx.String(flag.KeyProxyTo))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||||
}
|
}
|
||||||
|
|
||||||
from := ctx.StringSlice("from")
|
from := ctx.StringSlice(flag.KeyProxyFrom)
|
||||||
|
|
||||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||||
|
|
||||||
|
@ -7,16 +7,9 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FlagProxyName = "proxy-name"
|
|
||||||
|
|
||||||
func WithProxyFlags(flags ...cli.Flag) []cli.Flag {
|
func WithProxyFlags(flags ...cli.Flag) []cli.Flag {
|
||||||
baseFlags := clientFlag.ComposeFlags(
|
baseFlags := clientFlag.ComposeFlags(
|
||||||
&cli.StringFlag{
|
ProxyName(),
|
||||||
Name: FlagProxyName,
|
|
||||||
Usage: "use `PROXY_NAME` as targeted proxy",
|
|
||||||
Value: "",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
flags = append(flags, baseFlags...)
|
flags = append(flags, baseFlags...)
|
||||||
@ -24,8 +17,58 @@ func WithProxyFlags(flags ...cli.Flag) []cli.Flag {
|
|||||||
return flags
|
return flags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KeyProxyName = "proxy-name"
|
||||||
|
|
||||||
|
func ProxyName() cli.Flag {
|
||||||
|
return &cli.StringFlag{
|
||||||
|
Name: KeyProxyName,
|
||||||
|
Usage: "use `PROXY_NAME` as targeted proxy",
|
||||||
|
Value: "",
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyProxyTo = "proxy-to"
|
||||||
|
|
||||||
|
func ProxyTo(required bool) cli.Flag {
|
||||||
|
return &cli.StringFlag{
|
||||||
|
Name: KeyProxyTo,
|
||||||
|
Usage: "Set `PROXY_TO` as proxy's destination url",
|
||||||
|
Value: "",
|
||||||
|
Required: required,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyProxyFrom = "proxy-from"
|
||||||
|
|
||||||
|
func ProxyFrom() cli.Flag {
|
||||||
|
return &cli.StringSliceFlag{
|
||||||
|
Name: KeyProxyFrom,
|
||||||
|
Usage: "Set `PROXY_FROM` as proxy's patterns to match incoming requests",
|
||||||
|
Value: cli.NewStringSlice("*"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyProxyWeight = "proxy-weight"
|
||||||
|
|
||||||
|
func ProxyWeight() cli.Flag {
|
||||||
|
return &cli.IntFlag{
|
||||||
|
Name: KeyProxyWeight,
|
||||||
|
Usage: "Set `PROXY_WEIGHT` as proxy's weight",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyProxyEnabled = "proxy-enabled"
|
||||||
|
|
||||||
|
func ProxyEnabled() cli.Flag {
|
||||||
|
return &cli.BoolFlag{
|
||||||
|
Name: KeyProxyEnabled,
|
||||||
|
Usage: "Enable or disable proxy",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func AssertProxyName(ctx *cli.Context) (store.ProxyName, error) {
|
func AssertProxyName(ctx *cli.Context) (store.ProxyName, error) {
|
||||||
rawProxyName := ctx.String(FlagProxyName)
|
rawProxyName := ctx.String(KeyProxyName)
|
||||||
|
|
||||||
name, err := store.ValidateName(rawProxyName)
|
name, err := store.ValidateName(rawProxyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -18,22 +19,10 @@ func UpdateCommand() *cli.Command {
|
|||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Update proxy",
|
Usage: "Update proxy",
|
||||||
Flags: proxyFlag.WithProxyFlags(
|
Flags: proxyFlag.WithProxyFlags(
|
||||||
&cli.StringFlag{
|
flag.ProxyTo(false),
|
||||||
Name: "to",
|
flag.ProxyFrom(),
|
||||||
Usage: "Set `TO` as proxy's destination url",
|
flag.ProxyEnabled(),
|
||||||
},
|
flag.ProxyWeight(),
|
||||||
&cli.StringSliceFlag{
|
|
||||||
Name: "from",
|
|
||||||
Usage: "Set `FROM` as proxy's patterns to match incoming requests",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "enabled",
|
|
||||||
Usage: "Enable or disable proxy",
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "weight",
|
|
||||||
Usage: "Set `WEIGHT` as proxy's weight",
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||||
@ -50,27 +39,29 @@ func UpdateCommand() *cli.Command {
|
|||||||
|
|
||||||
opts := &client.UpdateProxyOptions{}
|
opts := &client.UpdateProxyOptions{}
|
||||||
|
|
||||||
if ctx.IsSet("to") {
|
if ctx.IsSet(flag.KeyProxyTo) {
|
||||||
to := ctx.String("to")
|
to := ctx.String(flag.KeyProxyTo)
|
||||||
if _, err := url.Parse(to); err != nil {
|
if _, err := url.Parse(to); err != nil {
|
||||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
return errors.Wrapf(err, "'%s' parameter should be a valid url", flag.KeyProxyTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.To = &to
|
opts.To = &to
|
||||||
}
|
}
|
||||||
|
|
||||||
from := ctx.StringSlice("from")
|
if ctx.IsSet(flag.KeyProxyFrom) {
|
||||||
if from != nil {
|
from := ctx.StringSlice(flag.KeyProxyFrom)
|
||||||
opts.From = from
|
if from != nil {
|
||||||
|
opts.From = from
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("weight") {
|
if ctx.IsSet(flag.KeyProxyWeight) {
|
||||||
weight := ctx.Int("weight")
|
weight := ctx.Int(flag.KeyProxyWeight)
|
||||||
opts.Weight = &weight
|
opts.Weight = &weight
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsSet("enabled") {
|
if ctx.IsSet(flag.KeyProxyEnabled) {
|
||||||
enabled := ctx.Bool("enabled")
|
enabled := ctx.Bool(flag.KeyProxyEnabled)
|
||||||
opts.Enabled = &enabled
|
opts.Enabled = &enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ func Dump() *cli.Command {
|
|||||||
Usage: "Dump the current configuration",
|
Usage: "Dump the current configuration",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: func(ctx *cli.Context) error {
|
Action: func(ctx *cli.Context) error {
|
||||||
|
logger.SetLevel(logger.LevelError)
|
||||||
|
|
||||||
conf, err := common.LoadConfig(ctx)
|
conf, err := common.LoadConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "Could not load configuration")
|
return errors.Wrap(err, "Could not load configuration")
|
||||||
|
@ -50,9 +50,10 @@ func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands
|
|||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "workdir",
|
Name: "workdir",
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: "The working directory",
|
EnvVars: []string{"BOUNCER_WORKDIR"},
|
||||||
|
Usage: "The working directory",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "projectVersion",
|
Name: "projectVersion",
|
||||||
|
@ -1,16 +1,11 @@
|
|||||||
package proxy
|
package proxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy"
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/queue"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
@ -33,7 +28,7 @@ func RunCommand() *cli.Command {
|
|||||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||||
|
|
||||||
layers, err := initDirectorLayers(ctx.Context, conf)
|
layers, err := setup.GetLayers(ctx.Context, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not initialize director layers")
|
return errors.Wrap(err, "could not initialize director layers")
|
||||||
}
|
}
|
||||||
@ -64,36 +59,3 @@ func RunCommand() *cli.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDirectorLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) {
|
|
||||||
layers := make([]director.Layer, 0)
|
|
||||||
|
|
||||||
queue, err := initQueueLayer(ctx, conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "could not initialize queue layer")
|
|
||||||
}
|
|
||||||
|
|
||||||
layers = append(layers, queue)
|
|
||||||
|
|
||||||
return layers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initQueueLayer(ctx context.Context, conf *config.Config) (*queue.Queue, error) {
|
|
||||||
adapter, err := setup.NewQueueAdapter(ctx, conf.Redis)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := []queue.OptionFunc{
|
|
||||||
queue.WithTemplateDir(string(conf.Layers.Queue.TemplateDir)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Layers.Queue.DefaultKeepAlive != nil {
|
|
||||||
options = append(options, queue.WithDefaultKeepAlive(time.Duration(*conf.Layers.Queue.DefaultKeepAlive)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return queue.New(
|
|
||||||
adapter,
|
|
||||||
options...,
|
|
||||||
), nil
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type AdminServerConfig struct {
|
type AdminServerConfig struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
CORS CORSConfig `yaml:"cors"`
|
CORS CORSConfig `yaml:"cors"`
|
||||||
Auth AuthConfig `yaml:"auth"`
|
Auth AuthConfig `yaml:"auth"`
|
||||||
|
Metrics MetricsConfig `yaml:"metrics"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultAdminServerConfig() AdminServerConfig {
|
func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||||
return AdminServerConfig{
|
return AdminServerConfig{
|
||||||
HTTP: NewHTTPConfig("127.0.0.1", 8081),
|
HTTP: NewHTTPConfig("127.0.0.1", 8081),
|
||||||
CORS: NewDefaultCORSConfig(),
|
CORS: NewDefaultCORSConfig(),
|
||||||
Auth: NewDefaultAuthConfig(),
|
Auth: NewDefaultAuthConfig(),
|
||||||
|
Metrics: NewDefaultMetricsConfig(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ type LoggerConfig struct {
|
|||||||
|
|
||||||
func NewDefaultLoggerConfig() LoggerConfig {
|
func NewDefaultLoggerConfig() LoggerConfig {
|
||||||
return LoggerConfig{
|
return LoggerConfig{
|
||||||
Level: InterpolatedInt(logger.LevelInfo),
|
Level: InterpolatedInt(logger.LevelError),
|
||||||
Format: InterpolatedString(logger.FormatHuman),
|
Format: InterpolatedString(logger.FormatHuman),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
internal/config/metrics.go
Normal file
35
internal/config/metrics.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type MetricsConfig struct {
|
||||||
|
Enabled InterpolatedBool `yaml:"enabled"`
|
||||||
|
Endpoint InterpolatedString `yaml:"endpoint"`
|
||||||
|
BasicAuth *BasicAuthConfig `yaml:"basicAuth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicAuthConfig struct {
|
||||||
|
Credentials *InterpolatedMap `yaml:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *BasicAuthConfig) CredentialsMap() map[string]string {
|
||||||
|
if c.Credentials == nil {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
credentials := make(map[string]string, len(*c.Credentials))
|
||||||
|
|
||||||
|
for k, v := range *c.Credentials {
|
||||||
|
credentials[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultMetricsConfig() MetricsConfig {
|
||||||
|
return MetricsConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Endpoint: "/.bouncer/metrics",
|
||||||
|
BasicAuth: nil,
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,71 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type ProxyServerConfig struct {
|
type ProxyServerConfig struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
|
Metrics MetricsConfig `yaml:"metrics"`
|
||||||
|
Transport TransportConfig `yaml:"transport"`
|
||||||
|
Dial DialConfig `yaml:"dial"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://pkg.go.dev/net/http#Transport
|
||||||
|
type TransportConfig struct {
|
||||||
|
ForceAttemptHTTP2 InterpolatedBool `yaml:"forceAttemptHTTP2"`
|
||||||
|
MaxIdleConns InterpolatedInt `yaml:"maxIdleConns"`
|
||||||
|
MaxIdleConnsPerHost InterpolatedInt `yaml:"maxIdleConnsPerHost"`
|
||||||
|
MaxConnsPerHost InterpolatedInt `yaml:"maxConnsPerHost"`
|
||||||
|
IdleConnTimeout *InterpolatedDuration `yaml:"idleConnTimeout"`
|
||||||
|
TLSHandshakeTimeout *InterpolatedDuration `yaml:"tlsHandshakeTimeout"`
|
||||||
|
ExpectContinueTimeout *InterpolatedDuration `yaml:"expectContinueTimeout"`
|
||||||
|
DisableKeepAlives InterpolatedBool `yaml:"disableKeepAlives"`
|
||||||
|
DisableCompression InterpolatedBool `yaml:"disableCompression"`
|
||||||
|
ResponseHeaderTimeout *InterpolatedDuration `yaml:"responseHeaderTimeout"`
|
||||||
|
WriteBufferSize InterpolatedInt `yaml:"writeBufferSize"`
|
||||||
|
ReadBufferSize InterpolatedInt `yaml:"readBufferSize"`
|
||||||
|
MaxResponseHeaderBytes InterpolatedInt `yaml:"maxResponseHeaderBytes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultProxyServerConfig() ProxyServerConfig {
|
func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||||
return ProxyServerConfig{
|
return ProxyServerConfig{
|
||||||
HTTP: NewHTTPConfig("0.0.0.0", 8080),
|
HTTP: NewHTTPConfig("0.0.0.0", 8080),
|
||||||
|
Metrics: NewDefaultMetricsConfig(),
|
||||||
|
Transport: NewDefaultTransportConfig(),
|
||||||
|
Dial: NewDefaultDialConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://pkg.go.dev/net#Dialer
|
||||||
|
type DialConfig struct {
|
||||||
|
Timeout *InterpolatedDuration `yaml:"timeout"`
|
||||||
|
KeepAlive *InterpolatedDuration `yaml:"keepAlive"`
|
||||||
|
FallbackDelay *InterpolatedDuration `yaml:"fallbackDelay"`
|
||||||
|
DualStack InterpolatedBool `yaml:"dualStack"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultDialConfig() DialConfig {
|
||||||
|
return DialConfig{
|
||||||
|
Timeout: NewInterpolatedDuration(30 * time.Second),
|
||||||
|
KeepAlive: NewInterpolatedDuration(30 * time.Second),
|
||||||
|
FallbackDelay: NewInterpolatedDuration(300 * time.Millisecond),
|
||||||
|
DualStack: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultTransportConfig() TransportConfig {
|
||||||
|
return TransportConfig{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
MaxIdleConnsPerHost: 100,
|
||||||
|
MaxConnsPerHost: 100,
|
||||||
|
IdleConnTimeout: NewInterpolatedDuration(90 * time.Second),
|
||||||
|
TLSHandshakeTimeout: NewInterpolatedDuration(10 * time.Second),
|
||||||
|
ExpectContinueTimeout: NewInterpolatedDuration(1 * time.Second),
|
||||||
|
ResponseHeaderTimeout: NewInterpolatedDuration(10 * time.Second),
|
||||||
|
DisableCompression: false,
|
||||||
|
DisableKeepAlives: false,
|
||||||
|
ReadBufferSize: 4096,
|
||||||
|
WriteBufferSize: 4096,
|
||||||
|
MaxResponseHeaderBytes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,6 +60,8 @@ MAIN:
|
|||||||
logger.F("remoteAddr", r.RemoteAddr),
|
logger.F("remoteAddr", r.RemoteAddr),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
metricProxyRequestsTotal.With(prometheus.Labels{metricLabelProxy: string(match.Name)}).Add(1)
|
||||||
|
|
||||||
ctx = withProxy(ctx, match)
|
ctx = withProxy(ctx, match)
|
||||||
|
|
||||||
layers, err := d.getLayers(ctx, match.Name)
|
layers, err := d.getLayers(ctx, match.Name)
|
||||||
|
73
internal/proxy/director/layer/queue/debouncer.go
Normal file
73
internal/proxy/director/layer/queue/debouncer.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DebouncerMap struct {
|
||||||
|
debouncers sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDebouncerMap() *DebouncerMap {
|
||||||
|
return &DebouncerMap{
|
||||||
|
debouncers: sync.Map{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *DebouncerMap) Do(key string, after time.Duration, fn func()) {
|
||||||
|
newDebouncer := NewDebouncer(after)
|
||||||
|
rawDebouncer, loaded := m.debouncers.LoadOrStore(key, newDebouncer)
|
||||||
|
|
||||||
|
debouncer, ok := rawDebouncer.(*Debouncer)
|
||||||
|
if !ok {
|
||||||
|
panic(errors.Errorf("unexpected debouncer value, expected '%T', got '%T'", newDebouncer, rawDebouncer))
|
||||||
|
}
|
||||||
|
|
||||||
|
if loaded {
|
||||||
|
debouncer.Update(after)
|
||||||
|
}
|
||||||
|
|
||||||
|
debouncer.Do(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDebouncer(after time.Duration) *Debouncer {
|
||||||
|
return &Debouncer{after: after}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Debouncer struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
after time.Duration
|
||||||
|
timer *time.Timer
|
||||||
|
fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Debouncer) Do(fn func()) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
d.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.fn = fn
|
||||||
|
d.timer = time.AfterFunc(d.after, d.fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Debouncer) Update(after time.Duration) {
|
||||||
|
d.mu.Lock()
|
||||||
|
defer d.mu.Unlock()
|
||||||
|
|
||||||
|
if after == d.after {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
d.timer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
d.after = after
|
||||||
|
d.timer = time.AfterFunc(d.after, d.fn)
|
||||||
|
}
|
31
internal/proxy/director/layer/queue/metrics.go
Normal file
31
internal/proxy/director/layer/queue/metrics.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricNamespace = "bouncer_layer_queue"
|
||||||
|
metricLabelProxy = "proxy"
|
||||||
|
metricLabelLayer = "layer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
metricQueueSessions = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "sessions",
|
||||||
|
Help: "Bouncer's queue layer current sessions",
|
||||||
|
Namespace: metricNamespace,
|
||||||
|
},
|
||||||
|
[]string{metricLabelProxy, metricLabelLayer},
|
||||||
|
)
|
||||||
|
metricQueueCapacity = promauto.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "capacity",
|
||||||
|
Help: "Bouncer's queue layer capacity",
|
||||||
|
Namespace: metricNamespace,
|
||||||
|
},
|
||||||
|
[]string{metricLabelProxy, metricLabelLayer},
|
||||||
|
)
|
||||||
|
)
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/Masterminds/sprig/v3"
|
"github.com/Masterminds/sprig/v3"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,7 +32,9 @@ type Queue struct {
|
|||||||
loadOnce sync.Once
|
loadOnce sync.Once
|
||||||
tmpl *template.Template
|
tmpl *template.Template
|
||||||
|
|
||||||
refreshJobRunning uint32
|
refreshJobRunning uint32
|
||||||
|
updateMetricsJobRunning uint32
|
||||||
|
postKeepAliveDebouncer *DebouncerMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// LayerType implements director.MiddlewareLayer
|
// LayerType implements director.MiddlewareLayer
|
||||||
@ -52,6 +56,8 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer q.updateMetrics(ctx, layer.Proxy, layer.Name, options)
|
||||||
|
|
||||||
cookieName := q.getCookieName(layer.Name)
|
cookieName := q.getCookieName(layer.Name)
|
||||||
|
|
||||||
cookie, err := r.Cookie(cookieName)
|
cookie, err := r.Cookie(cookieName)
|
||||||
@ -72,8 +78,6 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
|||||||
sessionID := cookie.Value
|
sessionID := cookie.Value
|
||||||
queueName := string(layer.Name)
|
queueName := string(layer.Name)
|
||||||
|
|
||||||
q.refreshQueue(queueName, options.KeepAlive)
|
|
||||||
|
|
||||||
rank, err := q.adapter.Touch(ctx, queueName, sessionID)
|
rank, err := q.adapter.Touch(ctx, queueName, sessionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(ctx, "could not retrieve session rank", logger.E(errors.WithStack(err)))
|
logger.Error(ctx, "could not retrieve session rank", logger.E(errors.WithStack(err)))
|
||||||
@ -102,6 +106,30 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queue) updateSessionsMetric(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName) {
|
||||||
|
if !atomic.CompareAndSwapUint32(&q.updateMetricsJobRunning, 0, 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer atomic.StoreUint32(&q.updateMetricsJobRunning, 0)
|
||||||
|
|
||||||
|
queueName := string(layerName)
|
||||||
|
|
||||||
|
status, err := q.adapter.Status(ctx, queueName)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "could not retrieve queue status", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
metricQueueSessions.With(
|
||||||
|
prometheus.Labels{
|
||||||
|
metricLabelLayer: string(layerName),
|
||||||
|
metricLabelProxy: string(proxyName),
|
||||||
|
},
|
||||||
|
).Set(float64(status.Sessions))
|
||||||
|
}
|
||||||
|
|
||||||
func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueName string, options *LayerOptions, rank int64) {
|
func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueName string, options *LayerOptions, rank int64) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
@ -135,20 +163,22 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshRate := time.Duration(int64(options.KeepAlive.Seconds()/2)) * time.Second
|
||||||
|
|
||||||
templateData := struct {
|
templateData := struct {
|
||||||
QueueName string
|
QueueName string
|
||||||
LayerOptions *LayerOptions
|
LayerOptions *LayerOptions
|
||||||
Rank int64
|
Rank int64
|
||||||
CurrentSessions int64
|
CurrentSessions int64
|
||||||
MaxSessions int64
|
MaxSessions int64
|
||||||
RefreshRate int64
|
RefreshRate time.Duration
|
||||||
}{
|
}{
|
||||||
QueueName: queueName,
|
QueueName: queueName,
|
||||||
LayerOptions: options,
|
LayerOptions: options,
|
||||||
Rank: rank + 1,
|
Rank: rank + 1,
|
||||||
CurrentSessions: status.Sessions,
|
CurrentSessions: status.Sessions,
|
||||||
MaxSessions: options.Capacity,
|
MaxSessions: options.Capacity,
|
||||||
RefreshRate: 5,
|
RefreshRate: refreshRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
@ -161,24 +191,55 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) refreshQueue(queueName string, keepAlive time.Duration) {
|
func (q *Queue) refreshQueue(ctx context.Context, layerName store.LayerName, keepAlive time.Duration) {
|
||||||
if !atomic.CompareAndSwapUint32(&q.refreshJobRunning, 0, 1) {
|
if !atomic.CompareAndSwapUint32(&q.refreshJobRunning, 0, 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
defer atomic.StoreUint32(&q.refreshJobRunning, 0)
|
||||||
defer atomic.StoreUint32(&q.refreshJobRunning, 0)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), keepAlive*2)
|
if err := q.adapter.Refresh(ctx, string(layerName), keepAlive); err != nil {
|
||||||
defer cancel()
|
logger.Error(ctx, "could not refresh queue",
|
||||||
|
logger.E(errors.WithStack(err)),
|
||||||
|
logger.F("queue", layerName),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := q.adapter.Refresh(ctx, queueName, keepAlive); err != nil {
|
func (q *Queue) updateMetrics(ctx context.Context, proxyName store.ProxyName, layerName store.LayerName, options *LayerOptions) {
|
||||||
logger.Error(ctx, "could not refresh queue",
|
// Update queue capacity metric
|
||||||
logger.E(errors.WithStack(err)),
|
metricQueueCapacity.With(
|
||||||
logger.F("queue", queueName),
|
prometheus.Labels{
|
||||||
)
|
metricLabelLayer: string(layerName),
|
||||||
}
|
metricLabelProxy: string(proxyName),
|
||||||
}()
|
},
|
||||||
|
).Set(float64(options.Capacity))
|
||||||
|
|
||||||
|
// Refresh queue data and metrics
|
||||||
|
q.refreshQueue(ctx, layerName, options.KeepAlive)
|
||||||
|
q.updateSessionsMetric(ctx, proxyName, layerName)
|
||||||
|
|
||||||
|
// (Re)schedule an update job after session ttl + semi-random time padding
|
||||||
|
// to update metrics after last session expiration
|
||||||
|
randDuration := rand.Int63n(int64(options.KeepAlive))
|
||||||
|
timePadding := options.KeepAlive/2 + time.Duration(randDuration)
|
||||||
|
after := options.KeepAlive + timePadding
|
||||||
|
|
||||||
|
debouncingKey := fmt.Sprintf("%s/%s", proxyName, layerName)
|
||||||
|
|
||||||
|
q.postKeepAliveDebouncer.Do(debouncingKey, after, func() {
|
||||||
|
ctx := logger.With(
|
||||||
|
context.Background(),
|
||||||
|
logger.F("proxy", proxyName),
|
||||||
|
logger.F("layer", layerName),
|
||||||
|
logger.F("after", after),
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.Info(ctx, "running post keep alive refresh job")
|
||||||
|
|
||||||
|
q.refreshQueue(ctx, layerName, options.KeepAlive)
|
||||||
|
q.updateSessionsMetric(ctx, proxyName, layerName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queue) getCookieName(layerName store.LayerName) string {
|
func (q *Queue) getCookieName(layerName store.LayerName) string {
|
||||||
@ -192,9 +253,10 @@ func New(adapter Adapter, funcs ...OptionFunc) *Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Queue{
|
return &Queue{
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
templateDir: opts.TemplateDir,
|
templateDir: opts.TemplateDir,
|
||||||
defaultKeepAlive: opts.DefaultKeepAlive,
|
defaultKeepAlive: opts.DefaultKeepAlive,
|
||||||
|
postKeepAliveDebouncer: NewDebouncerMap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/queue"
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/queue"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
@ -30,7 +30,7 @@ func (a *Adapter) Refresh(ctx context.Context, queueName string, keepAlive time.
|
|||||||
|
|
||||||
cmd := tx.ZRangeByScore(ctx, lastSeenKey, &redis.ZRangeBy{
|
cmd := tx.ZRangeByScore(ctx, lastSeenKey, &redis.ZRangeBy{
|
||||||
Min: "0",
|
Min: "0",
|
||||||
Max: strconv.FormatInt(expires.Unix(), 10),
|
Max: strconv.FormatInt(expires.UnixNano(), 10),
|
||||||
})
|
})
|
||||||
|
|
||||||
members, err := cmd.Result()
|
members, err := cmd.Result()
|
||||||
@ -75,7 +75,7 @@ func (a *Adapter) Touch(ctx context.Context, queueName string, sessionId string)
|
|||||||
|
|
||||||
for retry > 0 {
|
for retry > 0 {
|
||||||
err := withTx(ctx, a.client, func(ctx context.Context, tx *redis.Tx) error {
|
err := withTx(ctx, a.client, func(ctx context.Context, tx *redis.Tx) error {
|
||||||
now := time.Now().UTC().Unix()
|
now := time.Now().UTC().UnixNano()
|
||||||
|
|
||||||
err := tx.ZAddNX(ctx, rankKey, redis.Z{Score: float64(now), Member: sessionId}).Err()
|
err := tx.ZAddNX(ctx, rankKey, redis.Z{Score: float64(now), Member: sessionId}).Err()
|
||||||
if err != nil {
|
if err != nil {
|
12
internal/proxy/director/layer/queue/redis/adapter_test.go
Normal file
12
internal/proxy/director/layer/queue/redis/adapter_test.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/queue/testsuite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdapter(t *testing.T) {
|
||||||
|
adapter := NewAdapter(client, 3)
|
||||||
|
testsuite.TestAdapter(t, adapter)
|
||||||
|
}
|
58
internal/proxy/director/layer/queue/redis/main_test.go
Normal file
58
internal/proxy/director/layer/queue/redis/main_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ory/dockertest/v3"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var client redis.UniversalClient
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
|
||||||
|
pool, err := dockertest.NewPool("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// uses pool to try to connect to Docker
|
||||||
|
err = pool.Client.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pulls an image, creates a container based on it and runs it
|
||||||
|
resource, err := pool.Run("redis", "alpine3.17", []string{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pool.Retry(func() error {
|
||||||
|
client = redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
|
Addrs: []string{resource.GetHostPort("6379/tcp")},
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if cmd := client.Ping(ctx); cmd.Err() != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
if err := pool.Purge(resource); err != nil {
|
||||||
|
log.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
8
internal/proxy/director/layer/queue/schema.go
Normal file
8
internal/proxy/director/layer/queue/schema.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed schema/layer-options.json
|
||||||
|
var RawLayerOptionsSchema []byte
|
99
internal/proxy/director/layer/queue/testsuite/adapter.go
Normal file
99
internal/proxy/director/layer/queue/testsuite/adapter.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package testsuite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/queue"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type adapterTestCase struct {
|
||||||
|
Name string
|
||||||
|
Do func(adapter queue.Adapter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapterTestCases = []adapterTestCase{
|
||||||
|
{
|
||||||
|
Name: "Test queue ranking",
|
||||||
|
Do: func(adapter queue.Adapter) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
queueName := "test_queue_ranking"
|
||||||
|
|
||||||
|
sessionIdPattern := "session-%d"
|
||||||
|
totalSessions := int64(100)
|
||||||
|
|
||||||
|
for idx := int64(0); idx < totalSessions; idx++ {
|
||||||
|
sessionId := fmt.Sprintf(sessionIdPattern, idx)
|
||||||
|
rank, err := adapter.Touch(ctx, queueName, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not touch session '%s' (index: %d, rank: %d)", sessionId, idx, rank)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := int64(idx), rank; e != g {
|
||||||
|
return errors.Errorf("rank('%s'): expected '%v', got '%v'", sessionId, e, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := adapter.Status(ctx, queueName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve queue status")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := totalSessions, status.Sessions; e != g {
|
||||||
|
return errors.Errorf("status.Sessions: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Test session expiration",
|
||||||
|
Do: func(adapter queue.Adapter) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
queueName := "test_session_expiration"
|
||||||
|
|
||||||
|
sessionId := "session-1"
|
||||||
|
|
||||||
|
rank, err := adapter.Touch(ctx, queueName, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not touch session '%s'", sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := int64(0), rank; e != g {
|
||||||
|
return errors.Errorf("rank('%s'): expected '%v', got '%v'", sessionId, e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-time.After(time.Second)
|
||||||
|
|
||||||
|
if err := adapter.Refresh(ctx, queueName, time.Second); err != nil {
|
||||||
|
return errors.Wrap(err, "could not refresh queue")
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := adapter.Status(ctx, queueName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "could not retrieve queue status")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, g := int64(0), status.Sessions; e != g {
|
||||||
|
return errors.Errorf("status.Sessions: expected '%v', got '%v'", e, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdapter(t *testing.T, adapter queue.Adapter) {
|
||||||
|
for _, tc := range adapterTestCases {
|
||||||
|
func(tc adapterTestCase) {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
if err := tc.Do(adapter); err != nil {
|
||||||
|
t.Fatalf("%+v", errors.WithStack(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}(tc)
|
||||||
|
}
|
||||||
|
}
|
20
internal/proxy/director/metrics.go
Normal file
20
internal/proxy/director/metrics.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package director
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
metricNamespace = "bouncer_proxy_director"
|
||||||
|
metricLabelProxy = "proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var metricProxyRequestsTotal = promauto.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "proxy_requests_total",
|
||||||
|
Help: "Bouncer proxy total requests",
|
||||||
|
Namespace: metricNamespace,
|
||||||
|
},
|
||||||
|
[]string{metricLabelProxy},
|
||||||
|
)
|
@ -6,6 +6,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy"
|
"forge.cadoles.com/Cadoles/go-proxy"
|
||||||
bouncerChi "forge.cadoles.com/cadoles/bouncer/internal/chi"
|
bouncerChi "forge.cadoles.com/cadoles/bouncer/internal/chi"
|
||||||
@ -15,6 +18,7 @@ import (
|
|||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"gitlab.com/wpetit/goweb/logger"
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,18 +88,41 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
|||||||
)
|
)
|
||||||
|
|
||||||
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
||||||
router.Use(director.Middleware())
|
|
||||||
|
|
||||||
handler := proxy.New(
|
if s.serverConfig.Metrics.Enabled {
|
||||||
proxy.WithRequestTransformers(
|
metrics := s.serverConfig.Metrics
|
||||||
director.RequestTransformer(),
|
|
||||||
),
|
|
||||||
proxy.WithResponseTransformers(
|
|
||||||
director.ResponseTransformer(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
router.Handle("/*", handler)
|
logger.Info(ctx, "enabling metrics", logger.F("endpoint", metrics.Endpoint))
|
||||||
|
|
||||||
|
router.Group(func(r chi.Router) {
|
||||||
|
if metrics.BasicAuth != nil {
|
||||||
|
logger.Info(ctx, "enabling authentication on metrics endpoint")
|
||||||
|
|
||||||
|
r.Use(middleware.BasicAuth(
|
||||||
|
"metrics",
|
||||||
|
metrics.BasicAuth.CredentialsMap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Handle(string(metrics.Endpoint), promhttp.Handler())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Group(func(r chi.Router) {
|
||||||
|
r.Use(director.Middleware())
|
||||||
|
|
||||||
|
handler := proxy.New(
|
||||||
|
proxy.WithRequestTransformers(
|
||||||
|
director.RequestTransformer(),
|
||||||
|
),
|
||||||
|
proxy.WithResponseTransformers(
|
||||||
|
director.ResponseTransformer(),
|
||||||
|
),
|
||||||
|
proxy.WithReverseProxyFactory(s.createReverseProxy),
|
||||||
|
)
|
||||||
|
|
||||||
|
r.Handle("/*", handler)
|
||||||
|
})
|
||||||
|
|
||||||
if err := http.Serve(listener, router); err != nil && !errors.Is(err, net.ErrClosed) {
|
if err := http.Serve(listener, router); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||||
errs <- errors.WithStack(err)
|
errs <- errors.WithStack(err)
|
||||||
@ -104,6 +131,49 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
|||||||
logger.Info(ctx, "http server exiting")
|
logger.Info(ctx, "http server exiting")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) createReverseProxy(ctx context.Context, target *url.URL) *httputil.ReverseProxy {
|
||||||
|
reverseProxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
|
||||||
|
dialConfig := s.serverConfig.Dial
|
||||||
|
|
||||||
|
dialer := &net.Dialer{
|
||||||
|
Timeout: time.Duration(*dialConfig.Timeout),
|
||||||
|
KeepAlive: time.Duration(*dialConfig.KeepAlive),
|
||||||
|
FallbackDelay: time.Duration(*dialConfig.FallbackDelay),
|
||||||
|
DualStack: bool(dialConfig.DualStack),
|
||||||
|
}
|
||||||
|
|
||||||
|
transportConfig := s.serverConfig.Transport
|
||||||
|
|
||||||
|
reverseProxy.Transport = &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
DialContext: dialer.DialContext,
|
||||||
|
ForceAttemptHTTP2: bool(transportConfig.ForceAttemptHTTP2),
|
||||||
|
MaxIdleConns: int(transportConfig.MaxIdleConns),
|
||||||
|
MaxIdleConnsPerHost: int(transportConfig.MaxIdleConnsPerHost),
|
||||||
|
MaxConnsPerHost: int(transportConfig.MaxConnsPerHost),
|
||||||
|
IdleConnTimeout: time.Duration(*transportConfig.IdleConnTimeout),
|
||||||
|
TLSHandshakeTimeout: time.Duration(*transportConfig.TLSHandshakeTimeout),
|
||||||
|
ExpectContinueTimeout: time.Duration(*transportConfig.ExpectContinueTimeout),
|
||||||
|
DisableKeepAlives: bool(transportConfig.DisableKeepAlives),
|
||||||
|
DisableCompression: bool(transportConfig.DisableCompression),
|
||||||
|
ResponseHeaderTimeout: time.Duration(*transportConfig.ResponseHeaderTimeout),
|
||||||
|
WriteBufferSize: int(transportConfig.WriteBufferSize),
|
||||||
|
ReadBufferSize: int(transportConfig.ReadBufferSize),
|
||||||
|
MaxResponseHeaderBytes: int64(transportConfig.MaxResponseHeaderBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseProxy.ErrorHandler = s.errorHandler
|
||||||
|
|
||||||
|
return reverseProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) errorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
logger.Error(r.Context(), "proxy error", logger.E(errors.WithStack(err)))
|
||||||
|
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
func NewServer(funcs ...OptionFunc) *Server {
|
func NewServer(funcs ...OptionFunc) *Server {
|
||||||
opt := defaultOption()
|
opt := defaultOption()
|
||||||
for _, fn := range funcs {
|
for _, fn := range funcs {
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package queue
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed schema/layer-options.json
|
|
||||||
var rawLayerOptionsSchema []byte
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
layerOptionsSchema, err := schema.Parse(rawLayerOptionsSchema)
|
|
||||||
if err != nil {
|
|
||||||
panic(errors.Wrap(err, "could not parse queue layer options schema"))
|
|
||||||
}
|
|
||||||
|
|
||||||
schema.RegisterLayerOptionsSchema(LayerType, layerOptionsSchema)
|
|
||||||
}
|
|
@ -7,8 +7,10 @@ import (
|
|||||||
"github.com/qri-io/jsonschema"
|
"github.com/qri-io/jsonschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Parse(data []byte) (*jsonschema.Schema, error) {
|
type Schema = jsonschema.Schema
|
||||||
var schema jsonschema.Schema
|
|
||||||
|
func Parse(data []byte) (*Schema, error) {
|
||||||
|
var schema Schema
|
||||||
if err := json.Unmarshal(data, &schema); err != nil {
|
if err := json.Unmarshal(data, &schema); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package schema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/qri-io/jsonschema"
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultRegistry = NewRegistry()
|
|
||||||
|
|
||||||
func RegisterLayerOptionsSchema(layerType store.LayerType, schema *jsonschema.Schema) {
|
|
||||||
defaultRegistry.RegisterLayerOptionsSchema(layerType, schema)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ValidateLayerOptions(ctx context.Context, layerType store.LayerType, options *store.LayerOptions) error {
|
|
||||||
if err := defaultRegistry.ValidateLayerOptions(ctx, layerType, options); err != nil {
|
|
||||||
return errors.WithStack(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
layerOptionSchemas map[store.LayerType]*jsonschema.Schema
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) RegisterLayerOptionsSchema(layerType store.LayerType, schema *jsonschema.Schema) {
|
|
||||||
r.layerOptionSchemas[layerType] = schema
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Registry) ValidateLayerOptions(ctx context.Context, layerType store.LayerType, options *store.LayerOptions) error {
|
|
||||||
schema, exists := r.layerOptionSchemas[layerType]
|
|
||||||
if !exists {
|
|
||||||
return errors.WithStack(ErrSchemaNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
rawOptions := func(opts *store.LayerOptions) map[string]any {
|
|
||||||
return *opts
|
|
||||||
}(options)
|
|
||||||
|
|
||||||
state := schema.Validate(ctx, rawOptions)
|
|
||||||
|
|
||||||
if len(*state.Errs) > 0 {
|
|
||||||
return errors.WithStack(NewInvalidDataError(*state.Errs...))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
layerOptionSchemas: make(map[store.LayerType]*jsonschema.Schema),
|
|
||||||
}
|
|
||||||
}
|
|
17
internal/schema/validate.go
Normal file
17
internal/schema/validate.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Validate(ctx context.Context, schema *Schema, data map[string]any) error {
|
||||||
|
state := schema.Validate(ctx, data)
|
||||||
|
|
||||||
|
if len(*state.Errs) > 0 {
|
||||||
|
return errors.WithStack(NewInvalidDataError(*state.Errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
43
internal/setup/default_registry.go
Normal file
43
internal/setup/default_registry.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/qri-io/jsonschema"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultRegistry = NewRegistry()
|
||||||
|
|
||||||
|
func RegisterLayer(layerType store.LayerType, setupFunc LayerSetupFunc, rawOptionsSchema []byte) {
|
||||||
|
defaultRegistry.RegisterLayer(layerType, setupFunc, rawOptionsSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLayerOptionsSchema(layerType store.LayerType) (*jsonschema.Schema, error) {
|
||||||
|
schema, err := defaultRegistry.GetLayerOptionsSchema(layerType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) {
|
||||||
|
layers, err := defaultRegistry.GetLayers(ctx, conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLayerTypes() []store.LayerType {
|
||||||
|
return defaultRegistry.GetLayerTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func LayerTypeExists(layerType store.LayerType) bool {
|
||||||
|
return defaultRegistry.LayerTypeExists(layerType)
|
||||||
|
}
|
5
internal/setup/error.go
Normal file
5
internal/setup/error.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrNotFound = errors.New("not found")
|
45
internal/setup/queue_layer.go
Normal file
45
internal/setup/queue_layer.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/queue"
|
||||||
|
queueRedis "forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/queue/redis"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterLayer(queue.LayerType, setupQueueLayer, queue.RawLayerOptionsSchema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupQueueLayer(conf *config.Config) (director.Layer, error) {
|
||||||
|
adapter, err := newQueueAdapter(conf.Redis)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options := []queue.OptionFunc{
|
||||||
|
queue.WithTemplateDir(string(conf.Layers.Queue.TemplateDir)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Layers.Queue.DefaultKeepAlive != nil {
|
||||||
|
options = append(options, queue.WithDefaultKeepAlive(time.Duration(*conf.Layers.Queue.DefaultKeepAlive)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return queue.New(
|
||||||
|
adapter,
|
||||||
|
options...,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newQueueAdapter(redisConf config.RedisConfig) (queue.Adapter, error) {
|
||||||
|
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
|
Addrs: redisConf.Adresses,
|
||||||
|
MasterName: string(redisConf.Master),
|
||||||
|
})
|
||||||
|
|
||||||
|
return queueRedis.NewAdapter(rdb, 2), nil
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
package setup
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/queue"
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
|
|
||||||
queueRedis "forge.cadoles.com/cadoles/bouncer/internal/queue/redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewQueueAdapter(ctx context.Context, redisConf config.RedisConfig) (queue.Adapter, error) {
|
|
||||||
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
|
||||||
Addrs: redisConf.Adresses,
|
|
||||||
MasterName: string(redisConf.Master),
|
|
||||||
})
|
|
||||||
|
|
||||||
return queueRedis.NewAdapter(rdb, 2), nil
|
|
||||||
}
|
|
80
internal/setup/registry.go
Normal file
80
internal/setup/registry.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||||
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type layerEntry struct {
|
||||||
|
setup LayerSetupFunc
|
||||||
|
rawOptionsSchema []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
layers map[store.LayerType]layerEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type LayerSetupFunc func(*config.Config) (director.Layer, error)
|
||||||
|
|
||||||
|
func (r *Registry) RegisterLayer(layerType store.LayerType, layerSetup LayerSetupFunc, rawOptionsSchema []byte) {
|
||||||
|
r.layers[layerType] = layerEntry{
|
||||||
|
setup: layerSetup,
|
||||||
|
rawOptionsSchema: rawOptionsSchema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetLayerOptionsSchema(layerType store.LayerType) (*schema.Schema, error) {
|
||||||
|
layerEntry, exists := r.layers[layerType]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.WithStack(ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema, err := schema.Parse(layerEntry.rawOptionsSchema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) {
|
||||||
|
layers := make([]director.Layer, 0, len(r.layers))
|
||||||
|
|
||||||
|
for layerType, layerEntry := range r.layers {
|
||||||
|
layer, err := layerEntry.setup(conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "could not create layer '%s'", layerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
layers = append(layers, layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) LayerTypeExists(layerType store.LayerType) bool {
|
||||||
|
_, exists := r.layers[layerType]
|
||||||
|
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) GetLayerTypes() []store.LayerType {
|
||||||
|
layerTypes := make([]store.LayerType, 0, len(r.layers))
|
||||||
|
|
||||||
|
for layerType := range r.layers {
|
||||||
|
layerTypes = append(layerTypes, layerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return layerTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
layers: make(map[store.LayerType]layerEntry),
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Veuillez patienter - {{ .QueueName }}</title>
|
<title>Veuillez patienter - {{ .QueueName }}</title>
|
||||||
<meta http-equiv="refresh" content="{{ .RefreshRate }}">
|
<meta http-equiv="refresh" content="{{ .RefreshRate.Seconds }}">
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
37
misc/docker-compose/README.md
Normal file
37
misc/docker-compose/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Exemple de déploiement multi-noeuds avec Docker-Compose
|
||||||
|
|
||||||
|
Le répertoire [`misc/docker-compose`](./) contient un exemple de déploiement de Bouncer multi-noeuds avec:
|
||||||
|
|
||||||
|
- 3 instances du service `bouncer-proxy`;
|
||||||
|
- 1 instance du service `haproxy` en frontal en charge du load-balancing;
|
||||||
|
- 1 instance du service `bouncer-admin`;
|
||||||
|
- 1 serveur Redis.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
- [Docker Compose](https://docs.docker.com/compose/)
|
||||||
|
|
||||||
|
## Étapes
|
||||||
|
|
||||||
|
1. Se positionner dans le répertoire puis lancer l'environnement avec la commande `docker-compose`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd misc/docker-compose
|
||||||
|
docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Entrer dans le conteneur `bouncer-admin` puis créer un jeton d'accès:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose exec bouncer-admin /bin/sh
|
||||||
|
bouncer auth create-token --role writer > .bouncer-token
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Créer un proxy via le CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bouncer admin proxy create --proxy-name myproxy --proxy-to "https://www.cadoles.com/"
|
||||||
|
bouncer admin proxy update --proxy-name myproxy --proxy-enabled=true
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Via votre navigateur, accéder à l'URL http://127.0.0.1:8080. La page du site Cadoles devrait s'afficher. Dans le log de la commande `docker-compose up` vous devriez voir que les requêtes sont routées à tour de rôle sur les 3 instances de Bouncer en exécution.
|
1
misc/docker-compose/bouncer/admin-key.json
Normal file
1
misc/docker-compose/bouncer/admin-key.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"d":"JuBw5OsGv3rPgVczxUgtJ6iUQ41LQu4Xpu-t8IKI_z8r-BZBlbndxidPmRlGZASLGL3rhY4qw6_ScFxakrMpCreO1RMU0kqtz--N48BXFnW5tEgr1voyyKP__bPssQNn6PgkoyAd11es7MEKlBff_DtGrcSkVRgU0zDZB-vIU0aNEIZPNw0icbYqc1u_QQNPpBU9cw6P33WHhzvfCVAkZKRszwznhiPM08n1vjpiA7e1kQ8a6OC4IFZBvohkmpmyOq1g1OLRABQ83YPCjGjCAejO-jEWkbLksp6rAl_YYpCvfBAjFV76JuZq4eh5IU82LsSfi3PGYBkhxWuLY779XQ","dp":"gljHOQowGK7fVn2DJizWtgRIDJuKpKnoX2PWNJUbm2WZwcEPZalAkxn7Y-w_reLVJZuRpfKEUMS-Tn3-CwI1ZjCHPqMPTXcoG0Pe2E-Z88jOs9lW4XSOASiiM980VIvkV1xCxDJkN3NsDFQ9j9kRGnKuMnsucCW3AKaU917hXNU","dq":"mqY19JcEBDnzS70_XkAsOKqPzemOScax66b-4N6zrsgeLVlRjHffY9uCAgBWzlxOidRdQN8q23ZJB4fqsKB2w00Iw7Jxx94IoAKGjKDT5iB48Y_kdKLAwSHRTXsqA9GG3po_H_JpP_EqX4TDBYtqQZuBD_tACP9HbLYMi_V2YU8","e":"AQAB","kty":"RSA","n":"sam0X0BGcuFwX8z3Wde8cv2o_zl6A9ghpkT0tCjw8qH3GNWrbAqzncSWdHBzoChBgAbuTOVs-ixYC0KeUhwFdc8Ul-jmKJWFaS8kIr3y4EH62-vLgMuIKfaxbsyUG6KMkJfnftge1jPO4ccddNej9msxcqTxu37dcgstutwtd6QkS9p5RrNbDBc8-Z7SQ4TuxJfP8msXRnCPJ-I44yszGdQF1Np2DXakJHVn8PBrDh3iSFwORw8jxNS4oS0OlBl5aSc0t5XkkaNcSU2a50SKts290w54fl6MPJ1sLnnznLy4uu37-nrfEUvqRLDZL9B1F82RM1dtLIIiN4gnSrMlpQ","p":"wOmFPhAT_wXWzMuwtEdYIer3-CiOWxFKpFL09eEJkJ29MIUchEaoiJaUAghqPxM48llfOVaUaLbFVxmo5U3fyjNMaP-nHMUBwojutykMK-gC2R3J4bQgFWfKbGSL7M7UsextAvpq9iiOuR0LNE-xTfCgPIxHVdPZskO3yx0DkjM","q":"68OGRb0tLRjb_PpkGctcSjEz_vvcyjzxGL-fn4_h4GCw98Xrj6Y4rZ4lfWWRSeDohSvdd-ICSlxvxkQOIOcA0H7jyJcBC0KDs4hX5BRGJNDri3QX0ry4_F1ptAdbfiFgQGqCfMRCr7L60Tfd_6tLczvny7eEBKQNGdj6dLfhgMc","qi":"DFwixyxUDf0REPLLa8hOKieRL95_AH9rbYWzStBOdSjKWra5l0reD6a4bbvAYvl0e8qCcRI6S8Nzpz0BYm4sJL7poVOnjxqvBY3Q9Ppf4Mq8lW39pOCJcqOHIvvYHsMjTC5uwp7Yg2p0GvxuUibbyNL1PXf6WZ_szVP_oSMrCXA"}
|
47
misc/docker-compose/bouncer/config.yml
Normal file
47
misc/docker-compose/bouncer/config.yml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
admin:
|
||||||
|
http:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 8081
|
||||||
|
cors:
|
||||||
|
allowedOrigins:
|
||||||
|
- http://localhost:3001
|
||||||
|
allowCredentials: true
|
||||||
|
allowMethods:
|
||||||
|
- POST
|
||||||
|
- GET
|
||||||
|
- PUT
|
||||||
|
- DELETE
|
||||||
|
allowedHeaders:
|
||||||
|
- Origin
|
||||||
|
- Accept
|
||||||
|
- Content-Type
|
||||||
|
- Authorization
|
||||||
|
- Sentry-Trace
|
||||||
|
debug: false
|
||||||
|
auth:
|
||||||
|
issuer: http://127.0.0.1:8081
|
||||||
|
privateKey: /etc/bouncer/admin-key.json
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
endpoint: /.bouncer/metrics
|
||||||
|
basicAuth: null
|
||||||
|
proxy:
|
||||||
|
http:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 8080
|
||||||
|
metrics:
|
||||||
|
enabled: true
|
||||||
|
endpoint: /.bouncer/metrics
|
||||||
|
basicAuth: null
|
||||||
|
|
||||||
|
redis:
|
||||||
|
addresses:
|
||||||
|
- redis:6379
|
||||||
|
master: ""
|
||||||
|
logger:
|
||||||
|
level: 1
|
||||||
|
format: human
|
||||||
|
layers:
|
||||||
|
queue:
|
||||||
|
templateDir: /usr/share/bouncer/layers/queue/templates
|
||||||
|
defaultKeepAlive: 1m0s
|
42
misc/docker-compose/docker-compose.yml
Normal file
42
misc/docker-compose/docker-compose.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
haproxy:
|
||||||
|
image: reg.cadoles.com/proxy_cache/library/haproxy:2.7-alpine
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
links:
|
||||||
|
- bouncer-proxy-1
|
||||||
|
- bouncer-proxy-2
|
||||||
|
- bouncer-proxy-3
|
||||||
|
volumes:
|
||||||
|
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
|
||||||
|
|
||||||
|
bouncer-admin:
|
||||||
|
image: reg.cadoles.com/cadoles/bouncer:latest
|
||||||
|
command: bouncer -c /etc/bouncer/config.yml server admin run
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ./bouncer/config.yml:/etc/bouncer/config.yml
|
||||||
|
- ./bouncer/admin-key.json:/etc/bouncer/admin-key.json
|
||||||
|
|
||||||
|
bouncer-proxy-1: &bouncer-proxy
|
||||||
|
image: reg.cadoles.com/cadoles/bouncer:latest
|
||||||
|
command: bouncer -c /etc/bouncer/config.yml server proxy run
|
||||||
|
links:
|
||||||
|
- redis
|
||||||
|
volumes:
|
||||||
|
- ./bouncer/config.yml:/etc/bouncer/config.yml
|
||||||
|
- ./bouncer/admin-key.json:/etc/bouncer/admin-key.json
|
||||||
|
|
||||||
|
bouncer-proxy-2: *bouncer-proxy
|
||||||
|
bouncer-proxy-3: *bouncer-proxy
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: reg.cadoles.com/proxy_cache/library/redis:7-alpine
|
||||||
|
command: redis-server --save 60 1 --loglevel verbose
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
redis-data:
|
21
misc/docker-compose/haproxy/haproxy.cfg
Normal file
21
misc/docker-compose/haproxy/haproxy.cfg
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
global
|
||||||
|
maxconn 100
|
||||||
|
log stdout format raw local0 info
|
||||||
|
|
||||||
|
defaults
|
||||||
|
mode http
|
||||||
|
timeout client 30s
|
||||||
|
timeout server 30s
|
||||||
|
timeout connect 30s
|
||||||
|
log global
|
||||||
|
option httplog
|
||||||
|
|
||||||
|
frontend proxy
|
||||||
|
bind *:8080
|
||||||
|
default_backend bouncer-proxy
|
||||||
|
|
||||||
|
backend bouncer-proxy
|
||||||
|
mode http
|
||||||
|
server bouncer-proxy-1 bouncer-proxy-1:8080 check
|
||||||
|
server bouncer-proxy-2 bouncer-proxy-2:8080 check
|
||||||
|
server bouncer-proxy-3 bouncer-proxy-3:8080 check
|
BIN
misc/logo/bouncer.png
Normal file
BIN
misc/logo/bouncer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -26,10 +26,26 @@ admin:
|
|||||||
- Authorization
|
- Authorization
|
||||||
- Sentry-Trace
|
- Sentry-Trace
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
|
# Authentification JWT
|
||||||
auth:
|
auth:
|
||||||
|
# Origine du jeton JWT
|
||||||
issuer: http://127.0.0.1:8081
|
issuer: http://127.0.0.1:8081
|
||||||
|
# Clé privée permettant de signer les jetons
|
||||||
|
# JWT générés pour l'usage de l'API d'administration.
|
||||||
privateKey: /etc/bouncer/admin-key.json
|
privateKey: /etc/bouncer/admin-key.json
|
||||||
|
|
||||||
|
# Métriques Prometheus
|
||||||
|
metrics:
|
||||||
|
# Activer ou désactiver la publication des métriques
|
||||||
|
enabled: true
|
||||||
|
# Route de publication des métriques
|
||||||
|
endpoint: /.bouncer/metrics
|
||||||
|
# Authentification "basic auth" sur la page
|
||||||
|
# de publication
|
||||||
|
# Mettre à null pour désactiver l'authentification
|
||||||
|
basicAuth: null
|
||||||
|
|
||||||
# Configuration du service "proxy"
|
# Configuration du service "proxy"
|
||||||
proxy:
|
proxy:
|
||||||
http:
|
http:
|
||||||
@ -38,6 +54,44 @@ proxy:
|
|||||||
host: 0.0.0.0
|
host: 0.0.0.0
|
||||||
# Port d'écoute du service
|
# Port d'écoute du service
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
|
# Métriques Prometheus
|
||||||
|
metrics:
|
||||||
|
# Activer ou désactiver la publication des métriques
|
||||||
|
enabled: true
|
||||||
|
# Route de publication des métriques
|
||||||
|
endpoint: /.bouncer/metrics
|
||||||
|
# Authentification "basic auth" sur la page
|
||||||
|
# de publication
|
||||||
|
# Mettre à null pour désactiver l'authentification
|
||||||
|
basicAuth:
|
||||||
|
credentials:
|
||||||
|
prom: etheus
|
||||||
|
|
||||||
|
# Configuration du transport HTTP(S)
|
||||||
|
# Voir https://pkg.go.dev/net/http#Transport
|
||||||
|
transport:
|
||||||
|
forceAttemptHTTP2: true
|
||||||
|
maxIdleConns: 100
|
||||||
|
maxIdleConnsPerHost: 100
|
||||||
|
maxConnsPerHost: 100
|
||||||
|
idleConnTimeout: 1m30s
|
||||||
|
tlsHandshakeTimeout: 10s
|
||||||
|
expectContinueTimeout: 1s
|
||||||
|
disableKeepAlives: false
|
||||||
|
disableCompression: false
|
||||||
|
responseHeaderTimeout: 10s
|
||||||
|
writeBufferSize: 4096
|
||||||
|
readBufferSize: 4096
|
||||||
|
maxResponseHeaderBytes: 0
|
||||||
|
|
||||||
|
# Configuration des connexions TCP
|
||||||
|
# Voir https://pkg.go.dev/net#Dialer
|
||||||
|
dial:
|
||||||
|
timeout: 30s
|
||||||
|
keepAlive: 30s
|
||||||
|
fallbackDelay: 300ms
|
||||||
|
dualStack: true
|
||||||
|
|
||||||
# Configuration du client Redis
|
# Configuration du client Redis
|
||||||
#
|
#
|
||||||
|
@ -8,8 +8,8 @@ layers/**
|
|||||||
prep: make build-bouncer
|
prep: make build-bouncer
|
||||||
prep: make config.yml
|
prep: make config.yml
|
||||||
prep: make .bouncer-token
|
prep: make .bouncer-token
|
||||||
daemon: make run BOUNCER_CMD="--config config.yml server admin run"
|
daemon: make run BOUNCER_CMD="--debug --config config.yml server admin run"
|
||||||
daemon: make run BOUNCER_CMD="--config config.yml server proxy run"
|
daemon: make run BOUNCER_CMD="--debug --config config.yml server proxy run"
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
Reference in New Issue
Block a user