Compare commits
17 Commits
v2023.7.5-
...
k8s
Author | SHA1 | Date | |
---|---|---|---|
1b7344bcb5 | |||
bcc73a97cc | |||
b44ff2a68e | |||
c719fdca37 | |||
2b91c1e167 | |||
cebf1daf72 | |||
6734cf6526 | |||
368273f1ee | |||
553513d647 | |||
60487c11d6 | |||
e6f18e7cd8 | |||
a207291c04 | |||
64b5182f8b | |||
ce2c19f9b3 | |||
1ffec1f173 | |||
aab5452fa2 | |||
a176b754cd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,4 +7,5 @@
|
||||
/admin-key.json
|
||||
/.bouncer-token
|
||||
/data
|
||||
/out
|
||||
/out
|
||||
.dockerconfigjson
|
||||
|
27
Jenkinsfile
vendored
27
Jenkinsfile
vendored
@ -29,7 +29,7 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage('Release') {
|
||||
stage('Release binaries and packages') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
@ -50,6 +50,31 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build and release Docker image') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'master'
|
||||
branch 'develop'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
withCredentials([
|
||||
usernamePassword([
|
||||
credentialsId: 'kipp-credentials',
|
||||
usernameVariable: 'DOCKER_REGISTRY_USERNAME',
|
||||
passwordVariable: 'DOCKER_REGISTRY_PASSWORD'
|
||||
])
|
||||
]) {
|
||||
sh """
|
||||
echo '${env.DOCKER_REGISTRY_PASSWORD}' | docker login --username '${env.DOCKER_REGISTRY_USERNAME}' --password-stdin reg.cadoles.com
|
||||
make docker-build docker-release
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
|
20
Makefile
20
Makefile
@ -101,6 +101,12 @@ gitea-release: tools/gitea-release/bin/gitea-release.sh goreleaser
|
||||
GITEA_RELEASE_ATTACHMENTS="$$(find .gitea-release/* -type f)" \
|
||||
tools/gitea-release/bin/gitea-release.sh
|
||||
|
||||
grafterm: tools/grafterm/bin/grafterm
|
||||
tools/grafterm/bin/grafterm -c ./misc/grafterm/dashboard.json -v job=bouncer-proxy -r 5s
|
||||
|
||||
siege:
|
||||
siege -i -c 100 -f ./misc/siege/urls.txt
|
||||
|
||||
tools/gitea-release/bin/gitea-release.sh:
|
||||
mkdir -p tools/gitea-release/bin
|
||||
curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh
|
||||
@ -110,6 +116,10 @@ tools/modd/bin/modd:
|
||||
mkdir -p tools/modd/bin
|
||||
GOBIN=$(PWD)/tools/modd/bin go install github.com/cortesi/modd/cmd/modd@latest
|
||||
|
||||
tools/grafterm/bin/grafterm:
|
||||
mkdir -p tools/grafterm/bin
|
||||
GOBIN=$(PWD)/tools/grafterm/bin go install github.com/slok/grafterm/cmd/grafterm@v0.2.0
|
||||
|
||||
full-version:
|
||||
@echo $(FULL_VERSION)
|
||||
|
||||
@ -128,4 +138,12 @@ run-redis:
|
||||
redis-shell:
|
||||
docker exec -it \
|
||||
bouncer-redis \
|
||||
redis-cli
|
||||
redis-cli
|
||||
|
||||
run-prometheus:
|
||||
docker kill bouncer-prometheus || exit 0
|
||||
docker run --rm -t \
|
||||
--name bouncer-prometheus \
|
||||
--network host \
|
||||
-v $(PWD)/misc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
|
||||
prom/prometheus
|
@ -9,15 +9,16 @@
|
||||
## Référence
|
||||
|
||||
- [(FR) - Layers](./fr/references/layers/README.md)
|
||||
- [Fichier de configuration](../misc/packaging/common/config.yml)
|
||||
- [(FR) - Fichier de configuration](../misc/packaging/common/config.yml)
|
||||
- [(FR) - API d'administration](./fr/references/admin_api.md)
|
||||
|
||||
## Tutoriels
|
||||
|
||||
### Utilisation
|
||||
|
||||
- [(FR) - Ajouter un calque de type "file d'attente"](./fr/tutorials/add-queue-layer.md)
|
||||
- [(FR) - Ajouter un layer 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) - Démarrer avec les sources](./fr/tutorials/getting-started-with-sources.md)
|
||||
- [(FR) - Créer son propre layer](./fr/tutorials/create-custom-layer.md)
|
182
doc/fr/references/admin_api.md
Normal file
182
doc/fr/references/admin_api.md
Normal file
@ -0,0 +1,182 @@
|
||||
# API d'administration
|
||||
|
||||
## Authentification
|
||||
|
||||
L'ensemble des appels aux APIs HTTP du service `bouncer-admin` sont authentifiées via l'utilisation d'un jeton [JWT](https://datatracker.ietf.org/doc/html/rfc7519) signé par la clé privée du serveur.
|
||||
|
||||
Le jeton d'accès doit être transmis avec l'ensemble des appels aux points d'entrée via l'entête HTTP `Authorization` en respectant la forme suivante:
|
||||
|
||||
```
|
||||
Authorization: Bearer <jwt>
|
||||
```
|
||||
|
||||
### Génération d'un jeton d'authentification
|
||||
|
||||
La génération d'un jeton d'authentification s'effectue via la commande suivante:
|
||||
|
||||
```shell
|
||||
bouncer auth create-token --subject "<subject>" --role "<role>"
|
||||
```
|
||||
|
||||
Où:
|
||||
|
||||
- `"<subject>"` est une chaîne de caractère arbitraire ayant pour objectif d'identifier de manière unique l'utilisateur associé au jeton;
|
||||
- `"<role>"` peut prendre une des deux valeurs `reader` ou `writer` correspondant aux droits suivants respectifs:
|
||||
- droit en lecture sur l'ensemble des entités (proxy, layer);
|
||||
- droit en lecture ET en écriture sur l'ensemble des entités.
|
||||
|
||||
|
||||
## Points d'entrée
|
||||
|
||||
### `POST /api/v1/proxies`
|
||||
|
||||
Créer un nouveau proxy
|
||||
|
||||
#### Exemple de corps de requête
|
||||
|
||||
```json5
|
||||
{
|
||||
"name": "myproxy", // OBLIGATOIRE - Nom du proxy
|
||||
"to": "https://www.cadoles.com", // OBLIGATOIRE - Site distant ciblé par le proxy
|
||||
"from": ["*"] // OPTIONNEL - Liste de patrons de filtrage associés au proxy
|
||||
}
|
||||
```
|
||||
|
||||
#### Exemple de résultat
|
||||
|
||||
```json5
|
||||
{
|
||||
"data": {
|
||||
"proxy": {
|
||||
"name": "myproxy",
|
||||
"weight": 0,
|
||||
"enabled": false,
|
||||
"to": "https://www.cadoles.com",
|
||||
"from": ["*"],
|
||||
"createdAt": "2018-12-10T13:45:00.000Z",
|
||||
"updatedAt": "2018-12-10T13:45:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
Voir [`internal/admin/proxy_route.go#createProxy()`](../../../internal/admin/proxy_route.go#createProxy)
|
||||
|
||||
### `GET /api/v1/proxies/{proxyName}`
|
||||
|
||||
Récupérer les informations complètes sur un proxy
|
||||
|
||||
#### Paramètres
|
||||
|
||||
- `{proxyName}` - Nom du proxy
|
||||
|
||||
#### Exemple de résultat
|
||||
|
||||
```json5
|
||||
{
|
||||
"data": {
|
||||
"proxy": {
|
||||
"name": "myproxy",
|
||||
"weight": 0,
|
||||
"enabled": false,
|
||||
"to": "https://www.cadoles.com",
|
||||
"from": ["*"],
|
||||
"createdAt": "2018-12-10T13:45:00.000Z",
|
||||
"updatedAt": "2018-12-10T13:45:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
Voir [`internal/admin/proxy_route.go#getProxy()`](../../../internal/admin/proxy_route.go#getProxy)
|
||||
|
||||
### `PUT /api/v1/proxies/{proxyName}`
|
||||
|
||||
Modifier un proxy
|
||||
|
||||
#### Exemple de corps de requête
|
||||
|
||||
```json5
|
||||
{
|
||||
"to": "https://www.cadoles.com", // OPTIONNEL - Site distant ciblé par le proxy
|
||||
"from": ["mylocalproxydomain:*"], // OPTIONNEL - Liste de patrons de filtrage associés au proxy
|
||||
"weight": 100, // OPTIONNEL - Poids à associer au proxy
|
||||
"enabled": true, // OPTIONNEL - Activer/désactiver le proxy
|
||||
}
|
||||
```
|
||||
|
||||
#### Exemple de résultat
|
||||
|
||||
```json5
|
||||
{
|
||||
"data": {
|
||||
"proxy": {
|
||||
"name": "myproxy",
|
||||
"weight": 100,
|
||||
"enabled": true,
|
||||
"to": "https://www.cadoles.com",
|
||||
"from": ["mylocalproxydomain:*"],
|
||||
"createdAt": "2018-12-10T13:45:00.000Z",
|
||||
"updatedAt": "2020-10-02T15:09:00.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
Voir [`internal/admin/proxy_route.go#updateProxy()`](../../../internal/admin/proxy_route.go#updateProxy)
|
||||
|
||||
### `GET /api/v1/proxies?names={name1,name2,...}`
|
||||
|
||||
Lister les proxies existants
|
||||
|
||||
#### Paramètres
|
||||
|
||||
- `{names}` - Optionnel - Liste des noms de proxy à appliquer en tant que filtre
|
||||
|
||||
#### Exemple de résultat
|
||||
|
||||
```json5
|
||||
{
|
||||
"data": {
|
||||
"proxies": [
|
||||
{
|
||||
"name": "myproxy",
|
||||
"weight": 0,
|
||||
"enabled": false,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
Voir [`internal/admin/proxy_route.go#queryProxy()`](../../../internal/admin/proxy_route.go#queryProxy)
|
||||
|
||||
## `DELETE /api/v1/proxies/{proxyName}`
|
||||
|
||||
Supprimer le proxy
|
||||
|
||||
#### Paramètres
|
||||
|
||||
- `{proxyName}` - Nom du proxy
|
||||
|
||||
#### Exemple de résultat
|
||||
|
||||
```json5
|
||||
{
|
||||
"data": {
|
||||
"proxyName": "myproxy"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Source
|
||||
|
||||
Voir [`internal/admin/proxy_route.go#deleteProxy()`](../../../internal/admin/proxy_route.go#deleteProxy)
|
@ -2,4 +2,5 @@
|
||||
|
||||
Vous trouverez ci-dessous la liste des entités "Layer" activables sur vos entité "Proxy":
|
||||
|
||||
- [Queue](./queue.md) - File d'attente dynamique
|
||||
- [Queue](./queue.md) - File d'attente dynamique
|
||||
- [Circuit Breaker](./circuitbreaker.md) - Coupure d'accès à un site ou une sous section de celui ci
|
37
doc/fr/references/layers/circuitbreaker.md
Normal file
37
doc/fr/references/layers/circuitbreaker.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Layer "Circuit Breaker"
|
||||
|
||||
## Description
|
||||
|
||||
Ce layer permet de bloquer l'accès à un site (ou une section de celui ci) ciblé par un proxy.
|
||||
|
||||
## Type
|
||||
|
||||
`circuitbreaker`
|
||||
|
||||
## Options
|
||||
|
||||
### `authorizedCIDRs`
|
||||
|
||||
- **Type:** `[]string`
|
||||
- **Valeur par défaut:** `[]`
|
||||
- **Description:** Autoriser les adresses distantes contenues dans un des masques réseau (en notation ["CIDR"](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation) définis à contourner la restriction d'accès.
|
||||
|
||||
### `matchURLs`
|
||||
|
||||
- **Type:** `[]string`
|
||||
- **Valeur par défaut:** `["*"]`
|
||||
- **Description:** Limiter l'action du layer à cette liste de patrons d'URLs.
|
||||
|
||||
Par exemple, si vous souhaitez limiter votre restriction d'accès à l'ensemble d'une section "`/blog`" d'un site, vous pouvez déclarer la valeur `["*/blog*"]`. Les autres URLs du site ne seront pas affectées par la restriction.
|
||||
|
||||
### `templateBlock`
|
||||
|
||||
- **Type:** `string`
|
||||
- **Valeur par défaut:** `"default"`
|
||||
- **Description:** Bloc du template HTML pour effectuer le rendu de la page indiquant la restriction d'accès.
|
||||
|
||||
Voir le [fichier de configuration de référence](../../../../misc/packaging/common/config.yml), section `layers.circuitbreaker` pour voir les options permettant de personnaliser le chemin du répertoire contenant les templates.
|
||||
|
||||
### Schéma
|
||||
|
||||
Voir le [schéma JSON](../../../../internal/proxy/director/layer/circuitbreaker/layer-options.json).
|
@ -22,6 +22,14 @@ Ce layer permet d'ajouter un mécanisme de file d'attente dynamique au proxy ass
|
||||
- **Valeur par défaut:** `1m`
|
||||
- **Description:** Durée de vie d'une session dans la file d'attente sans activité avant expiration.
|
||||
|
||||
### `matchURLs`
|
||||
|
||||
- **Type:** `[]string`
|
||||
- **Valeur par défaut:** `["*"]`
|
||||
- **Description:** Limiter l'action de la file d'attente à cette liste de patrons d'URLs.
|
||||
|
||||
Par exemple, si vous souhaitez limiter votre file à l'ensemble d'une section "`/blog`" d'un site, vous pouvez déclarer la valeur `["*/blog*"]`. Les autres URLs du site ne seront pas affectées par cette file d'attente.
|
||||
|
||||
### Schéma
|
||||
|
||||
Voir le [schéma JSON](../../../../internal/proxy/director/layer/queue/schema/layer-options.json).
|
@ -70,15 +70,15 @@ docker run --rm -t \
|
||||
|
||||
Surveiller les sources, compiler celles ci en cas de modifications et lancer les services `bouncer-proxy` et `bouncer-admin`.
|
||||
|
||||
#### `make test`
|
||||
### `make test`
|
||||
|
||||
Exécuter les tests unitaires/d'intégration du projet.
|
||||
|
||||
#### `make build`
|
||||
### `make build`
|
||||
|
||||
Compiler une version de développement du binaire `bouncer`.
|
||||
|
||||
#### `make docker-build`
|
||||
### `make docker-build`
|
||||
|
||||
Construire une image Docker pour Bouncer.
|
||||
|
||||
@ -92,6 +92,13 @@ docker run \
|
||||
bouncer server proxy run
|
||||
```
|
||||
|
||||
### `make grafterm`
|
||||
|
||||
Afficher un tableau de bord [`grafterm`](https://github.com/slok/grafterm) branché sur l'instance Prometheus locale.
|
||||
|
||||
### `make siege`
|
||||
|
||||
Lancer une session de test [`siege`](https://github.com/JoeDog/siege) sur l'instance `bouncer-proxy` locale.
|
||||
## Arborescence du projet
|
||||
|
||||
```bash
|
||||
|
22
go.mod
22
go.mod
@ -6,6 +6,8 @@ require (
|
||||
forge.cadoles.com/Cadoles/go-proxy v0.0.0-20230701194111-c6b3d482cca6
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3
|
||||
github.com/drone/envsubst v1.0.3
|
||||
github.com/getsentry/sentry-go v0.22.0
|
||||
github.com/go-chi/chi/v5 v5.0.8
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
@ -51,12 +53,14 @@ require (
|
||||
github.com/qri-io/jsonpointer v0.1.1 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
@ -66,21 +70,21 @@ require (
|
||||
cdr.dev/slog v1.4.2 // indirect
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/go-playground/locales v0.14.0 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lib/pq v1.10.0 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
@ -92,10 +96,10 @@ require (
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
gitlab.com/wpetit/goweb v0.0.0-20230419082146-a94d9ed7202b
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.9.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/term v0.7.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.8.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
|
||||
|
54
go.sum
54
go.sum
@ -143,9 +143,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
@ -162,6 +163,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g=
|
||||
github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -178,19 +181,24 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM=
|
||||
github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
@ -314,8 +322,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
@ -324,8 +333,8 @@ github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJG
|
||||
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9 h1:TRX4Q630UXxPVLvP5vGaqVJO7S+0PE6msRZUsFSBoC8=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifbJUydCTPmgM=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
@ -382,6 +391,7 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh
|
||||
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
|
||||
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@ -414,13 +424,16 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
@ -438,8 +451,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@ -485,9 +498,8 @@ golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -570,8 +582,8 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -666,22 +678,24 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -693,8 +707,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
@ -1,11 +1,14 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const ErrCodeAlreadyExist api.ErrorCode = "already-exist"
|
||||
@ -29,3 +32,8 @@ func invalidDataErrorResponse(w http.ResponseWriter, r *http.Request, err *schem
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func logAndCaptureError(ctx context.Context, message string, err error) {
|
||||
sentry.CaptureException(err)
|
||||
logger.Error(ctx, message, logger.E(err))
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
@ -10,7 +11,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryLayerResponse struct {
|
||||
@ -38,7 +38,7 @@ func (s *Server) queryLayer(w http.ResponseWriter, r *http.Request) {
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list layers", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not list layers", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -85,7 +85,7 @@ func (s *Server) getLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -120,7 +120,7 @@ func (s *Server) deleteLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not delete layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -156,7 +156,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
layerName, err := store.ValidateName(createLayerReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "invalid 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "invalid 'name' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||
|
||||
return
|
||||
@ -165,7 +165,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
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))
|
||||
logAndCaptureError(ctx, fmt.Sprintf("unknown layer type '%s'", layerType), errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil)
|
||||
|
||||
return
|
||||
@ -179,7 +179,7 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not create layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -223,7 +223,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -247,7 +247,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
if updateLayerReq.Options != 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)))
|
||||
logAndCaptureError(ctx, "could not retrieve layer options schema", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -258,7 +258,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
}(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)))
|
||||
logAndCaptureError(ctx, "could not validate layer options", errors.WithStack(err))
|
||||
|
||||
var invalidDataErr *schema.InvalidDataError
|
||||
if errors.As(err, &invalidDataErr) {
|
||||
@ -286,7 +286,7 @@ func (s *Server) updateLayer(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update layer", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not update layer", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -300,21 +300,7 @@ func getLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
return store.LayerName(name), true
|
||||
}
|
||||
|
||||
func geLayerName(w http.ResponseWriter, r *http.Request) (store.LayerName, bool) {
|
||||
rawLayerName := chi.URLParam(r, "layerName")
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse layer name", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse layer name", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type QueryProxyResponse struct {
|
||||
@ -37,7 +36,7 @@ func (s *Server) queryProxy(w http.ResponseWriter, r *http.Request) {
|
||||
options...,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not list proxies", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not list proxies", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -79,7 +78,7 @@ func (s *Server) getProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not get proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not get proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -109,7 +108,7 @@ func (s *Server) deleteProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not delete proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not delete proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -140,14 +139,14 @@ func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
name, err := store.ValidateName(createProxyReq.Name)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'name' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := url.Parse(createProxyReq.To); err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
@ -161,7 +160,7 @@ func (s *Server) createProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not create proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not create proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -207,7 +206,7 @@ func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
if updateProxyReq.To != nil {
|
||||
_, err := url.Parse(*updateProxyReq.To)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse 'to' parameter", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not parse 'to' parameter", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return
|
||||
@ -235,7 +234,7 @@ func (s *Server) updateProxy(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Error(ctx, "could not update proxy", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(ctx, "could not update proxy", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
@ -249,7 +248,7 @@ func getProxyName(w http.ResponseWriter, r *http.Request) (store.ProxyName, bool
|
||||
|
||||
name, err := store.ValidateName(rawProxyName)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse proxy name", logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse proxy name", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return "", false
|
||||
@ -263,7 +262,7 @@ func getIntQueryParam(w http.ResponseWriter, r *http.Request, param string, defa
|
||||
if rawValue != "" {
|
||||
value, err := strconv.ParseInt(rawValue, 10, 64)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse int param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse int param", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return 0, false
|
||||
@ -296,7 +295,7 @@ func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request,
|
||||
for _, rv := range rawValues {
|
||||
v, err := validate(rv)
|
||||
if err != nil {
|
||||
logger.Error(r.Context(), "could not parse ids slice param", logger.F("param", param), logger.E(errors.WithStack(err)))
|
||||
logAndCaptureError(r.Context(), "could not parse ids slice param", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
||||
return nil, false
|
||||
|
@ -9,9 +9,11 @@ import (
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||
bouncerChi "forge.cadoles.com/cadoles/bouncer/internal/chi"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
@ -90,7 +92,21 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
router.Use(middleware.Logger)
|
||||
if s.serverConfig.HTTP.UseRealIP {
|
||||
router.Use(middleware.RealIP)
|
||||
}
|
||||
|
||||
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
||||
|
||||
if s.serverConfig.Sentry.DSN != "" {
|
||||
logger.Info(ctx, "enabling sentry http middleware")
|
||||
|
||||
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
})
|
||||
|
||||
router.Use(sentryMiddleware.Handle)
|
||||
}
|
||||
|
||||
corsMiddleware := cors.New(cors.Options{
|
||||
AllowedOrigins: s.serverConfig.CORS.AllowedOrigins,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -90,6 +91,8 @@ func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands
|
||||
return
|
||||
}
|
||||
|
||||
sentry.CaptureException(err)
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
if !debug {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
@ -27,6 +28,14 @@ func RunCommand() *cli.Command {
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
projectVersion := ctx.String("projectVersion")
|
||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Admin.Sentry, projectVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize sentry client")
|
||||
}
|
||||
|
||||
defer flushSentry()
|
||||
|
||||
srv := admin.NewServer(
|
||||
admin.WithServerConfig(conf.Admin),
|
||||
admin.WithRedisConfig(conf.Redis),
|
||||
|
@ -28,6 +28,14 @@ func RunCommand() *cli.Command {
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
projectVersion := ctx.String("projectVersion")
|
||||
flushSentry, err := setup.SetupSentry(ctx.Context, conf.Proxy.Sentry, projectVersion)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize sentry client")
|
||||
}
|
||||
|
||||
defer flushSentry()
|
||||
|
||||
layers, err := setup.GetLayers(ctx.Context, conf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize director layers")
|
||||
|
@ -5,6 +5,7 @@ type AdminServerConfig struct {
|
||||
CORS CORSConfig `yaml:"cors"`
|
||||
Auth AuthConfig `yaml:"auth"`
|
||||
Metrics MetricsConfig `yaml:"metrics"`
|
||||
Sentry SentryConfig `yaml:"sentry"`
|
||||
}
|
||||
|
||||
func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||
@ -13,6 +14,7 @@ func NewDefaultAdminServerConfig() AdminServerConfig {
|
||||
CORS: NewDefaultCORSConfig(),
|
||||
Auth: NewDefaultAuthConfig(),
|
||||
Metrics: NewDefaultMetricsConfig(),
|
||||
Sentry: NewDefaultSentryConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,13 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/drone/envsubst"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var reVar = regexp.MustCompile(`^\${(\w+)}$`)
|
||||
// var reVar = regexp.MustCompile(`^\${(\w+)}$`)
|
||||
var reVar = regexp.MustCompile(`\${(.*?)}`)
|
||||
|
||||
type InterpolatedString string
|
||||
|
||||
@ -53,6 +55,29 @@ func (ii *InterpolatedInt) UnmarshalYAML(value *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedFloat float64
|
||||
|
||||
func (ifl *InterpolatedFloat) UnmarshalYAML(value *yaml.Node) error {
|
||||
var str string
|
||||
|
||||
if err := value.Decode(&str); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into string", value.Value, value.Line)
|
||||
}
|
||||
|
||||
if match := reVar.FindStringSubmatch(str); len(match) > 0 {
|
||||
str = os.Getenv(match[1])
|
||||
}
|
||||
|
||||
floatVal, err := strconv.ParseFloat(str, 10)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not parse float '%v', line '%d'", str, value.Line)
|
||||
}
|
||||
|
||||
*ifl = InterpolatedFloat(floatVal)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type InterpolatedBool bool
|
||||
|
||||
func (ib *InterpolatedBool) UnmarshalYAML(value *yaml.Node) error {
|
||||
@ -107,14 +132,22 @@ type InterpolatedStringSlice []string
|
||||
|
||||
func (iss *InterpolatedStringSlice) UnmarshalYAML(value *yaml.Node) error {
|
||||
var data []string
|
||||
var evErr error
|
||||
|
||||
if err := value.Decode(&data); err != nil {
|
||||
return errors.Wrapf(err, "could not decode value '%v' (line '%d') into map", value.Value, value.Line)
|
||||
}
|
||||
|
||||
for index, value := range data {
|
||||
if match := reVar.FindStringSubmatch(value); len(match) > 0 {
|
||||
value = os.Getenv(match[1])
|
||||
//match := reVar.FindStringSubmatch(value)
|
||||
re := regexp.MustCompile(`\${(.*?)}`)
|
||||
|
||||
res := re.FindAllStringSubmatch(value, 10)
|
||||
if len(res) > 0 {
|
||||
value, evErr = envsubst.EvalEnv(value)
|
||||
if evErr != nil {
|
||||
return evErr
|
||||
}
|
||||
}
|
||||
|
||||
data[index] = value
|
||||
|
@ -1,13 +1,15 @@
|
||||
package config
|
||||
|
||||
type HTTPConfig struct {
|
||||
Host InterpolatedString `yaml:"host"`
|
||||
Port InterpolatedInt `yaml:"port"`
|
||||
Host InterpolatedString `yaml:"host"`
|
||||
Port InterpolatedInt `yaml:"port"`
|
||||
UseRealIP InterpolatedBool `yaml:"useRealIP"`
|
||||
}
|
||||
|
||||
func NewHTTPConfig(host string, port int) HTTPConfig {
|
||||
return HTTPConfig{
|
||||
Host: InterpolatedString(host),
|
||||
Port: InterpolatedInt(port),
|
||||
Host: InterpolatedString(host),
|
||||
Port: InterpolatedInt(port),
|
||||
UseRealIP: true,
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ package config
|
||||
import "time"
|
||||
|
||||
type LayersConfig struct {
|
||||
Queue QueueLayerConfig `yaml:"queue"`
|
||||
Queue QueueLayerConfig `yaml:"queue"`
|
||||
CircuitBreaker CircuitBreakerLayerConfig `yaml:"circuitbreaker"`
|
||||
}
|
||||
|
||||
func NewDefaultLayersConfig() LayersConfig {
|
||||
@ -12,6 +13,9 @@ func NewDefaultLayersConfig() LayersConfig {
|
||||
TemplateDir: "./layers/queue/templates",
|
||||
DefaultKeepAlive: NewInterpolatedDuration(time.Minute),
|
||||
},
|
||||
CircuitBreaker: CircuitBreakerLayerConfig{
|
||||
TemplateDir: "./layers/circuitbreaker/templates",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +23,7 @@ type QueueLayerConfig struct {
|
||||
TemplateDir InterpolatedString `yaml:"templateDir"`
|
||||
DefaultKeepAlive *InterpolatedDuration `yaml:"defaultKeepAlive"`
|
||||
}
|
||||
|
||||
type CircuitBreakerLayerConfig struct {
|
||||
TemplateDir InterpolatedString `yaml:"templateDir"`
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ type ProxyServerConfig struct {
|
||||
Metrics MetricsConfig `yaml:"metrics"`
|
||||
Transport TransportConfig `yaml:"transport"`
|
||||
Dial DialConfig `yaml:"dial"`
|
||||
Sentry SentryConfig `yaml:"sentry"`
|
||||
}
|
||||
|
||||
// See https://pkg.go.dev/net/http#Transport
|
||||
@ -32,6 +33,7 @@ func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||
Metrics: NewDefaultMetricsConfig(),
|
||||
Transport: NewDefaultTransportConfig(),
|
||||
Dial: NewDefaultDialConfig(),
|
||||
Sentry: NewDefaultSentryConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
|
43
internal/config/sentry.go
Normal file
43
internal/config/sentry.go
Normal file
@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// Sentry configuration
|
||||
// See https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
||||
type SentryConfig struct {
|
||||
DSN InterpolatedString `yaml:"dsn"`
|
||||
Debug InterpolatedBool `yaml:"debug"`
|
||||
FlushTimeout *InterpolatedDuration `yaml:"flushTimeout"`
|
||||
AttachStacktrace InterpolatedBool `yaml:"attachStacktrace"`
|
||||
SampleRate InterpolatedFloat `yaml:"sampleRate"`
|
||||
EnableTracing InterpolatedBool `yaml:"enableTracing"`
|
||||
TracesSampleRate InterpolatedFloat `yaml:"tracesSampleRate"`
|
||||
ProfilesSampleRate InterpolatedFloat `yaml:"profilesSampleRate"`
|
||||
IgnoreErrors InterpolatedStringSlice `yaml:"ignoreErrors"`
|
||||
SendDefaultPII InterpolatedBool `yaml:"sendDefaultPII"`
|
||||
ServerName InterpolatedString `yaml:"serverName"`
|
||||
Environment InterpolatedString `yaml:"environment"`
|
||||
MaxBreadcrumbs InterpolatedInt `yaml:"maxBreadcrumbs"`
|
||||
MaxSpans InterpolatedInt `yaml:"maxSpans"`
|
||||
MaxErrorDepth InterpolatedInt `yaml:"maxErrorDepth"`
|
||||
}
|
||||
|
||||
func NewDefaultSentryConfig() SentryConfig {
|
||||
return SentryConfig{
|
||||
DSN: "",
|
||||
Debug: false,
|
||||
FlushTimeout: NewInterpolatedDuration(2 * time.Second),
|
||||
AttachStacktrace: true,
|
||||
SampleRate: 1,
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 0.2,
|
||||
ProfilesSampleRate: 1,
|
||||
IgnoreErrors: []string{},
|
||||
SendDefaultPII: false,
|
||||
ServerName: "",
|
||||
Environment: "",
|
||||
MaxBreadcrumbs: 0,
|
||||
MaxSpans: 1000,
|
||||
MaxErrorDepth: 10,
|
||||
}
|
||||
}
|
4
internal/config/testdata/config.yml
vendored
4
internal/config/testdata/config.yml
vendored
@ -2,5 +2,5 @@ logger:
|
||||
level: 0
|
||||
format: human
|
||||
http:
|
||||
host: "0.0.0.0"
|
||||
port: 3000
|
||||
host: "${LISTEN_ADDR}"
|
||||
port: 3000
|
||||
|
43
internal/logger/writer.go
Normal file
43
internal/logger/writer.go
Normal file
@ -0,0 +1,43 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
ctx context.Context
|
||||
level logger.Level
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
w.log(string(p))
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *Writer) log(message string) {
|
||||
switch w.level {
|
||||
case logger.LevelDebug:
|
||||
logger.Debug(w.ctx, message)
|
||||
case logger.LevelInfo:
|
||||
logger.Info(w.ctx, message)
|
||||
case logger.LevelWarn:
|
||||
logger.Warn(w.ctx, message)
|
||||
case logger.LevelError:
|
||||
logger.Error(w.ctx, message)
|
||||
case logger.LevelCritical:
|
||||
logger.Critical(w.ctx, message)
|
||||
default:
|
||||
logger.Debug(w.ctx, message)
|
||||
}
|
||||
}
|
||||
|
||||
func NewWriter(ctx context.Context, level logger.Level) *Writer {
|
||||
return &Writer{ctx, level}
|
||||
}
|
||||
|
||||
var _ io.Writer = &Writer{}
|
@ -0,0 +1,23 @@
|
||||
{
|
||||
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/circuitbreaker-layer-options",
|
||||
"title": "Circuit breaker layer options",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matchURLs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"authorizedCIDRs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"templateBlock": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
151
internal/proxy/director/layer/circuitbreaker/layer.go
Normal file
151
internal/proxy/director/layer/circuitbreaker/layer.go
Normal file
@ -0,0 +1,151 @@
|
||||
package circuitbreaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
const LayerType store.LayerType = "circuitbreaker"
|
||||
|
||||
type Layer struct {
|
||||
templateDir string
|
||||
loadOnce sync.Once
|
||||
tmpl *template.Template
|
||||
}
|
||||
|
||||
// LayerType implements director.MiddlewareLayer
|
||||
func (l *Layer) LayerType() store.LayerType {
|
||||
return LayerType
|
||||
}
|
||||
|
||||
// Middleware implements director.MiddlewareLayer
|
||||
func (l *Layer) Middleware(layer *store.Layer) proxy.Middleware {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
options, err := fromStoreOptions(layer.Options)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not parse layer options", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
matches, err := l.matchAnyAuthorizedCIDRs(ctx, r.RemoteAddr, options.AuthorizedCIDRs)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not match authorized cidrs", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if matches {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
matches = wildcard.MatchAny(r.URL.String(), options.MatchURLs...)
|
||||
if !matches {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
l.renderCircuitBreakerPage(w, r, layer, options)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Layer) matchAnyAuthorizedCIDRs(ctx context.Context, remoteHostPort string, CIDRs []string) (bool, error) {
|
||||
remoteHost, _, err := net.SplitHostPort(remoteHostPort)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
remoteAddr := net.ParseIP(remoteHost)
|
||||
if remoteAddr == nil {
|
||||
return false, errors.Errorf("remote host '%s' is not a valid ip address", remoteHost)
|
||||
}
|
||||
|
||||
for _, rawCIDR := range CIDRs {
|
||||
_, net, err := net.ParseCIDR(rawCIDR)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
match := net.Contains(remoteAddr)
|
||||
if !match {
|
||||
continue
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "comparing remote host with authorized cidrs", logger.F("remoteAddr", remoteAddr))
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (l *Layer) renderCircuitBreakerPage(w http.ResponseWriter, r *http.Request, layer *store.Layer, options *LayerOptions) {
|
||||
ctx := r.Context()
|
||||
|
||||
pattern := filepath.Join(l.templateDir, "*.gohtml")
|
||||
|
||||
logger.Info(ctx, "loading circuit breaker page templates", logger.F("pattern", pattern))
|
||||
|
||||
tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not load circuit breaker templates", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
templateData := struct {
|
||||
Layer *store.Layer
|
||||
LayerOptions *LayerOptions
|
||||
}{
|
||||
Layer: layer,
|
||||
LayerOptions: options,
|
||||
}
|
||||
|
||||
w.Header().Add("Cache-Control", "no-cache")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if err := tmpl.ExecuteTemplate(w, options.TemplateBlock, templateData); err != nil {
|
||||
logger.Error(ctx, "could not render circuit breaker page", logger.E(errors.WithStack(err)))
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func New(funcs ...OptionFunc) *Layer {
|
||||
opts := defaultOptions()
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return &Layer{
|
||||
templateDir: opts.TemplateDir,
|
||||
}
|
||||
}
|
||||
|
||||
var _ director.MiddlewareLayer = &Layer{}
|
@ -0,0 +1,36 @@
|
||||
package circuitbreaker
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type LayerOptions struct {
|
||||
MatchURLs []string `mapstructure:"matchURLs"`
|
||||
AuthorizedCIDRs []string `mapstructure:"authorizedCIDRs"`
|
||||
TemplateBlock string `mapstructure:"templateBlock"`
|
||||
}
|
||||
|
||||
func fromStoreOptions(storeOptions store.LayerOptions) (*LayerOptions, error) {
|
||||
layerOptions := LayerOptions{
|
||||
MatchURLs: []string{"*"},
|
||||
AuthorizedCIDRs: []string{},
|
||||
TemplateBlock: "default",
|
||||
}
|
||||
|
||||
config := mapstructure.DecoderConfig{
|
||||
Result: &layerOptions,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(storeOptions); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &layerOptions, nil
|
||||
}
|
19
internal/proxy/director/layer/circuitbreaker/options.go
Normal file
19
internal/proxy/director/layer/circuitbreaker/options.go
Normal file
@ -0,0 +1,19 @@
|
||||
package circuitbreaker
|
||||
|
||||
type Options struct {
|
||||
TemplateDir string
|
||||
}
|
||||
|
||||
type OptionFunc func(*Options)
|
||||
|
||||
func defaultOptions() *Options {
|
||||
return &Options{
|
||||
TemplateDir: "./templates",
|
||||
}
|
||||
}
|
||||
|
||||
func WithTemplateDir(templateDir string) OptionFunc {
|
||||
return func(o *Options) {
|
||||
o.TemplateDir = templateDir
|
||||
}
|
||||
}
|
8
internal/proxy/director/layer/circuitbreaker/schema.go
Normal file
8
internal/proxy/director/layer/circuitbreaker/schema.go
Normal file
@ -0,0 +1,8 @@
|
||||
package circuitbreaker
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
//go:embed layer-options.json
|
||||
var RawLayerOptionsSchema []byte
|
@ -11,15 +11,15 @@ import (
|
||||
|
||||
type LayerOptions struct {
|
||||
Capacity int64 `mapstructure:"capacity"`
|
||||
Matchers []string `mapstructure:"matchers"`
|
||||
KeepAlive time.Duration `mapstructure:"keepAlive"`
|
||||
MatchURLs []string `mapstructure:"matchURLs"`
|
||||
}
|
||||
|
||||
func fromStoreOptions(storeOptions store.LayerOptions, defaultKeepAlive time.Duration) (*LayerOptions, error) {
|
||||
layerOptions := LayerOptions{
|
||||
Capacity: 1000,
|
||||
Matchers: []string{"*"},
|
||||
KeepAlive: defaultKeepAlive,
|
||||
MatchURLs: []string{"*"},
|
||||
}
|
||||
|
||||
config := mapstructure.DecoderConfig{
|
||||
|
@ -1,6 +1,8 @@
|
||||
package queue
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
TemplateDir string
|
||||
|
@ -7,11 +7,13 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy"
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
@ -56,6 +58,13 @@ func (q *Queue) Middleware(layer *store.Layer) proxy.Middleware {
|
||||
return
|
||||
}
|
||||
|
||||
matches := wildcard.MatchAny(r.URL.String(), options.MatchURLs...)
|
||||
if !matches {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
defer q.updateMetrics(ctx, layer.Proxy, layer.Name, options)
|
||||
|
||||
cookieName := q.getCookieName(layer.Name)
|
||||
@ -181,6 +190,8 @@ func (q *Queue) renderQueuePage(w http.ResponseWriter, r *http.Request, queueNam
|
||||
RefreshRate: refreshRate,
|
||||
}
|
||||
|
||||
w.Header().Add("Cache-Control", "no-cache")
|
||||
w.Header().Add("Retry-After", strconv.FormatInt(int64(refreshRate.Seconds()), 10))
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
if err := q.tmpl.ExecuteTemplate(w, "queue", templateData); err != nil {
|
||||
|
@ -30,7 +30,7 @@ func (a *Adapter) Refresh(ctx context.Context, queueName string, keepAlive time.
|
||||
|
||||
cmd := tx.ZRangeByScore(ctx, lastSeenKey, &redis.ZRangeBy{
|
||||
Min: "0",
|
||||
Max: strconv.FormatInt(expires.Unix(), 10),
|
||||
Max: strconv.FormatInt(expires.UnixNano(), 10),
|
||||
})
|
||||
|
||||
members, err := cmd.Result()
|
||||
@ -75,7 +75,7 @@ func (a *Adapter) Touch(ctx context.Context, queueName string, sessionId string)
|
||||
|
||||
for retry > 0 {
|
||||
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()
|
||||
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)
|
||||
}
|
@ -9,6 +9,12 @@
|
||||
},
|
||||
"keepAlive": {
|
||||
"type": "string"
|
||||
},
|
||||
"matchURLs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
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)
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/pkg/errors"
|
||||
@ -87,8 +89,22 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
s.directorLayers...,
|
||||
)
|
||||
|
||||
if s.serverConfig.HTTP.UseRealIP {
|
||||
router.Use(middleware.RealIP)
|
||||
}
|
||||
|
||||
router.Use(middleware.RequestLogger(bouncerChi.NewLogFormatter()))
|
||||
|
||||
if s.serverConfig.Sentry.DSN != "" {
|
||||
logger.Info(ctx, "enabling sentry http middleware")
|
||||
|
||||
sentryMiddleware := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
})
|
||||
|
||||
router.Use(sentryMiddleware.Handle)
|
||||
}
|
||||
|
||||
if s.serverConfig.Metrics.Enabled {
|
||||
metrics := s.serverConfig.Metrics
|
||||
|
||||
@ -169,7 +185,10 @@ func (s *Server) createReverseProxy(ctx context.Context, target *url.URL) *httpu
|
||||
}
|
||||
|
||||
func (s *Server) errorHandler(w http.ResponseWriter, r *http.Request, err error) {
|
||||
logger.Error(r.Context(), "proxy error", logger.E(errors.WithStack(err)))
|
||||
err = errors.WithStack(err)
|
||||
|
||||
logger.Error(r.Context(), "proxy error", logger.E(err))
|
||||
sentry.CaptureException(err)
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
|
21
internal/setup/circuitbreaker_layer.go
Normal file
21
internal/setup/circuitbreaker_layer.go
Normal file
@ -0,0 +1,21 @@
|
||||
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/circuitbreaker"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterLayer(circuitbreaker.LayerType, setupCircuitBreakerLayer, circuitbreaker.RawLayerOptionsSchema)
|
||||
}
|
||||
|
||||
func setupCircuitBreakerLayer(conf *config.Config) (director.Layer, error) {
|
||||
options := []circuitbreaker.OptionFunc{
|
||||
circuitbreaker.WithTemplateDir(string(conf.Layers.CircuitBreaker.TemplateDir)),
|
||||
}
|
||||
|
||||
return circuitbreaker.New(
|
||||
options...,
|
||||
), nil
|
||||
}
|
42
internal/setup/sentry.go
Normal file
42
internal/setup/sentry.go
Normal file
@ -0,0 +1,42 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
loggerWriter "forge.cadoles.com/cadoles/bouncer/internal/logger"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func SetupSentry(ctx context.Context, conf config.SentryConfig, release string) (func(), error) {
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: string(conf.DSN),
|
||||
Debug: bool(conf.Debug),
|
||||
AttachStacktrace: bool(conf.AttachStacktrace),
|
||||
SampleRate: float64(conf.SampleRate),
|
||||
EnableTracing: bool(conf.EnableTracing),
|
||||
TracesSampleRate: float64(conf.TracesSampleRate),
|
||||
ProfilesSampleRate: float64(conf.ProfilesSampleRate),
|
||||
IgnoreErrors: conf.IgnoreErrors,
|
||||
SendDefaultPII: bool(conf.SendDefaultPII),
|
||||
ServerName: string(conf.ServerName),
|
||||
Release: release,
|
||||
Environment: string(conf.Environment),
|
||||
MaxBreadcrumbs: int(conf.MaxBreadcrumbs),
|
||||
MaxSpans: int(conf.MaxSpans),
|
||||
MaxErrorDepth: int(conf.MaxErrorDepth),
|
||||
DebugWriter: loggerWriter.NewWriter(ctx, logger.LevelDebug),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
flush := func() {
|
||||
sentry.Flush(time.Duration(*conf.FlushTimeout))
|
||||
}
|
||||
|
||||
return flush, nil
|
||||
}
|
@ -2,6 +2,7 @@ package testsuite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
@ -48,6 +49,187 @@ var layerRepositoryTestCases = []layerRepositoryTestCase{
|
||||
return errors.Errorf("layer.UpdatedAt should not be zero value")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create then get layer",
|
||||
Do: func(repo store.LayerRepository) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var proxyName store.ProxyName = "create_then_get_layer_proxy"
|
||||
var layerName store.LayerName = "create_then_get_layer"
|
||||
var layerType store.LayerType = "dummy"
|
||||
var layerOptions store.LayerOptions = store.LayerOptions{
|
||||
"foo": "bar",
|
||||
"test": struct {
|
||||
Items []int `json:"items"`
|
||||
}{
|
||||
Items: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
foundLayer, err := repo.GetLayer(ctx, proxyName, layerName)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.Name, foundLayer.Name; e != g {
|
||||
return errors.Errorf("foundLayer.Name: expected '%v', got '%v'", createdLayer.Name, foundLayer.Name)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.CreatedAt, foundLayer.CreatedAt; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.CreatedAt: expected '%v', got '%v'", createdLayer.CreatedAt, foundLayer.CreatedAt)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.UpdatedAt, foundLayer.UpdatedAt; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.UpdatedAt: expected '%v', got '%v'", createdLayer.UpdatedAt, foundLayer.UpdatedAt)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.Enabled, foundLayer.Enabled; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.Enabled: expected '%v', got '%v'", createdLayer.Enabled, foundLayer.Enabled)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.Weight, foundLayer.Weight; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.Weight: expected '%v', got '%v'", createdLayer.Weight, foundLayer.Weight)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.Proxy, foundLayer.Proxy; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.Proxy: expected '%v', got '%v'", createdLayer.Proxy, foundLayer.Proxy)
|
||||
}
|
||||
|
||||
if e, g := createdLayer.Options, foundLayer.Options; !reflect.DeepEqual(e, g) {
|
||||
return errors.Errorf("foundLayer.Options: expected '%v', got '%v'", createdLayer.Options, foundLayer.Options)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create then delete layer",
|
||||
Do: func(repo store.LayerRepository) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var layerName store.LayerName = "create_then_delete_layer"
|
||||
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
|
||||
var layerType store.LayerType = "dummy"
|
||||
var layerOptions store.LayerOptions = store.LayerOptions{
|
||||
"foo": "bar",
|
||||
"test": struct {
|
||||
Items []int `json:"items"`
|
||||
}{
|
||||
Items: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := repo.DeleteLayer(ctx, createdLayer.Proxy, createdLayer.Name); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
foundLayer, err := repo.GetLayer(ctx, createdLayer.Proxy, createdLayer.Name)
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
return errors.Errorf("err should be store.ErrNotFound, got '%+v'", err)
|
||||
}
|
||||
|
||||
if foundLayer != nil {
|
||||
return errors.Errorf("foundLayer should be nil, got '%v'", foundLayer)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create already existing layer",
|
||||
Do: func(repo store.LayerRepository) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var layerName store.LayerName = "create_already_existing_layer"
|
||||
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
|
||||
var layerType store.LayerType = "dummy"
|
||||
var layerOptions store.LayerOptions = store.LayerOptions{
|
||||
"foo": "bar",
|
||||
"test": struct {
|
||||
Items []int `json:"items"`
|
||||
}{
|
||||
Items: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err = repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||
if err == nil {
|
||||
return errors.New("err should not be nil")
|
||||
}
|
||||
|
||||
if !errors.Is(err, store.ErrAlreadyExist) {
|
||||
return errors.Errorf("err: expected store.ErrAlreadyExists, got '%+v'", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create then query layer",
|
||||
Do: func(repo store.LayerRepository) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var layerName store.LayerName = "create_then_query_layer"
|
||||
var proxyName store.ProxyName = store.ProxyName(string(layerName) + "_proxy")
|
||||
var layerType store.LayerType = "dummy"
|
||||
var layerOptions store.LayerOptions = store.LayerOptions{
|
||||
"foo": "bar",
|
||||
"test": struct {
|
||||
Items []int `json:"items"`
|
||||
}{
|
||||
Items: []int{1, 2, 3},
|
||||
},
|
||||
}
|
||||
|
||||
createdLayer, err := repo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
headers, err := repo.QueryLayers(ctx, createdLayer.Proxy)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if len(headers) < 1 {
|
||||
return errors.Errorf("len(headers): expected value > 1, got '%v'", len(headers))
|
||||
}
|
||||
|
||||
found := false
|
||||
|
||||
for _, h := range headers {
|
||||
if h.Name == createdLayer.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("could not find created layer in query results")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
@ -83,6 +83,14 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
|
||||
return errors.Errorf("foundProxy.To: expected '%v', got '%v'", createdProxy.To, foundProxy.To)
|
||||
}
|
||||
|
||||
if e, g := createdProxy.Enabled, foundProxy.Enabled; e != g {
|
||||
return errors.Errorf("foundProxy.Enabled: expected '%v', got '%v'", createdProxy.Enabled, foundProxy.Enabled)
|
||||
}
|
||||
|
||||
if e, g := createdProxy.Weight, foundProxy.Weight; e != g {
|
||||
return errors.Errorf("foundProxy.Weight: expected '%v', got '%v'", createdProxy.Weight, foundProxy.Weight)
|
||||
}
|
||||
|
||||
if e, g := createdProxy.CreatedAt, foundProxy.CreatedAt; e != g {
|
||||
return errors.Errorf("foundProxy.CreatedAt: expected '%v', got '%v'", createdProxy.CreatedAt, foundProxy.CreatedAt)
|
||||
}
|
||||
@ -127,7 +135,7 @@ var proxyRepositoryTestCases = []proxyRepositoryTestCase{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Create then query",
|
||||
Name: "Create then query layer",
|
||||
Do: func(repo store.ProxyRepository) error {
|
||||
ctx := context.Background()
|
||||
|
||||
|
73
layers/circuitbreaker/templates/default.gohtml
Normal file
73
layers/circuitbreaker/templates/default.gohtml
Normal file
@ -0,0 +1,73 @@
|
||||
{{ define "default" }}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Accès bloqué - {{ .Layer.Name }}</title>
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p, ol, ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#card {
|
||||
padding: 1.5em 1em;
|
||||
border: 1px solid #e0e0e0;
|
||||
background-color: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 2px 2px #cccccc1c;
|
||||
color: #333333 !important;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.footer {
|
||||
font-size: 0.7em;
|
||||
margin-top: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="card">
|
||||
<h2 class="title">Page indisponible</h2>
|
||||
<p>La page à laquelle vous souhaitez accéder est actuellement indisponible.</p>
|
||||
<p class="footer">Propulsé par <a href="https://forge.cadoles.com/Cadoles/bouncer">Bouncer</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{ end }}
|
196
misc/grafterm/dashboard.json
Normal file
196
misc/grafterm/dashboard.json
Normal file
@ -0,0 +1,196 @@
|
||||
{
|
||||
"version": "v1",
|
||||
"datasources": {
|
||||
"prometheus": {
|
||||
"prometheus": {
|
||||
"address": "http://127.0.0.1:9090"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"variables": {
|
||||
"job": {
|
||||
"constant": { "value": "bouncer-proxy" }
|
||||
},
|
||||
"interval": {
|
||||
"interval": { "steps": 50 }
|
||||
}
|
||||
},
|
||||
"widgets": [
|
||||
{
|
||||
"title": "Bouncer - Total queue sessions",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"thresholds": [{ "color": "#47D038" }],
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(bouncer_layer_queue_sessions{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Bouncer Traffic",
|
||||
"gridPos": {
|
||||
"w": 80
|
||||
},
|
||||
"graph": {
|
||||
"queries": [
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(rate(bouncer_proxy_director_proxy_requests_total{job=\"{{.job}}\"}[{{.interval}}]))",
|
||||
"legend": "req/s"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Goroutines",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"thresholds": [{ "color": "#47D038" }],
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_goroutines{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "GC duration",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"unit": "second",
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "max(go_gc_duration_seconds{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Stack",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"unit": "bytes",
|
||||
"thresholds": [{ "color": "#22F1F1" }],
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_stack_inuse_bytes{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Heap",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"unit": "bytes",
|
||||
"thresholds": [{ "color": "#22F1F1" }],
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_heap_inuse_bytes{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Alloc",
|
||||
"gridPos": { "w": 20 },
|
||||
"singlestat": {
|
||||
"unit": "bytes",
|
||||
"thresholds": [{ "color": "#22F1F1" }],
|
||||
"query": {
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_alloc_bytes{job=\"{{.job}}\"})"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Goroutines",
|
||||
"gridPos": { "w": 50 },
|
||||
"graph": {
|
||||
"visualization": {
|
||||
"legend": { "disable": true },
|
||||
"yAxis": { "unit": "", "decimals": 2 }
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_goroutines{job=\"{{.job}}\"})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "GC duration",
|
||||
"gridPos": { "w": 50 },
|
||||
"graph": {
|
||||
"queries": [
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "max(go_gc_duration_seconds{job=\"{{.job}}\"}) by (quantile)",
|
||||
"legend": "Q{{.quantile}}"
|
||||
}
|
||||
],
|
||||
"visualization": {
|
||||
"yAxis": { "unit": "second" },
|
||||
"seriesOverride": [
|
||||
{ "regex": "^Q0$", "color": "#F9E2D2" },
|
||||
{ "regex": "^Q0.25$", "color": "#F2C96D" },
|
||||
{ "regex": "^Q0.5(0)?$", "color": "#EAB839" },
|
||||
{ "regex": "^Q0.75$", "color": "#EF843C" },
|
||||
{ "regex": "^Q1(.0)?$", "color": "#E24D42" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Memory",
|
||||
"gridPos": { "w": 50 },
|
||||
"graph": {
|
||||
"visualization": {
|
||||
"yAxis": { "unit": "byte", "decimals": 0 }
|
||||
},
|
||||
"queries": [
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_stack_inuse_bytes{job=\"{{.job}}\"})",
|
||||
"legend": "stack inuse"
|
||||
},
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_heap_inuse_bytes{job=\"{{.job}}\"})",
|
||||
"legend": "heap inuse"
|
||||
},
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(go_memstats_alloc_bytes{job=\"{{.job}}\"})",
|
||||
"legend": "alloc"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Memory ops rate",
|
||||
"gridPos": {
|
||||
"w": 50
|
||||
},
|
||||
"graph": {
|
||||
"queries": [
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(rate(go_memstats_frees_total{job=\"{{.job}}\"}[{{.interval}}]))",
|
||||
"legend": "frees/s"
|
||||
},
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(rate(go_memstats_mallocs_total{job=\"{{.job}}\"}[{{.interval}}]))",
|
||||
"legend": "mallocs/s"
|
||||
},
|
||||
{
|
||||
"datasourceID": "prometheus",
|
||||
"expr": "sum(rate(go_memstats_lookups_total{job=\"{{.job}}\"}[{{.interval}}]))",
|
||||
"legend": "lookups/s"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
49
misc/images/bouncer/Dockerfile
Normal file
49
misc/images/bouncer/Dockerfile
Normal file
@ -0,0 +1,49 @@
|
||||
FROM golang:1.20 AS BUILD
|
||||
|
||||
RUN apt-get update \
|
||||
&& 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
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
|
||||
|
||||
# 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
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
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 \
|
||||
&& chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
|
||||
|
||||
RUN mkdir -p /usr/local/bin /usr/share/bouncer/bin /etc/bouncer
|
||||
|
||||
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 8081
|
||||
|
||||
ENV BOUNCER_CONFIG=/etc/bouncer/config.yml
|
||||
|
||||
CMD ["bouncer"]
|
@ -7,12 +7,22 @@ ARG https_proxy=
|
||||
|
||||
# Install dev environment dependencies
|
||||
RUN export DEBIAN_FRONTEND=noninteractive &&\
|
||||
apt clean &&\
|
||||
apt-get update -y &&\
|
||||
apt-get install -y --no-install-recommends curl ca-certificates build-essential wget unzip tar git jq
|
||||
apt-get install -y --no-install-recommends curl ca-certificates build-essential wget unzip tar git jq gnupg
|
||||
|
||||
# Add LetsEncrypt certificates
|
||||
RUN curl -k https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/common/add-letsencrypt-ca.sh | bash
|
||||
|
||||
RUN install -m 0755 -d /etc/apt/keyrings \
|
||||
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
|
||||
&& chmod a+r /etc/apt/keyrings/docker.gpg \
|
||||
&& echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" \
|
||||
| tee /etc/apt/sources.list.d/docker.list > /dev/null \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y docker-ce-cli
|
||||
|
||||
ARG GO_VERSION=1.20.4
|
||||
|
||||
# Install Go
|
||||
|
10
misc/k8s/kustomization/base/kustomization.yaml
Normal file
10
misc/k8s/kustomization/base/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: bouncer
|
||||
|
||||
resources:
|
||||
- ./resources/namespace.yaml
|
||||
- ./resources/bouncer-server
|
||||
- ./resources/bouncer-admin
|
||||
- ./resources/redis
|
||||
|
@ -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"}
|
@ -0,0 +1,36 @@
|
||||
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
|
||||
|
||||
redis:
|
||||
addresses:
|
||||
- ${REDIS_SENTINEL_HOST}:${REDIS_SENTINEL_PORT}
|
||||
master: "${REDIS_MASTER_NAME}"
|
||||
|
||||
logger:
|
||||
level: 3
|
||||
format: human
|
@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./resources/service.yaml
|
||||
- ./resources/deployment.yaml
|
||||
|
||||
configMapGenerator:
|
||||
- name: bouncer-admin-config
|
||||
files:
|
||||
- ./files/config.yml
|
||||
- ./files/admin-key.json
|
||||
- name: bouncer-admin-env
|
||||
literals:
|
||||
- REDIS_SENTINEL_HOST="rfs-$(REDIS_SERVICE_NAME)"
|
||||
- REDIS_SENTINEL_PORT="26379"
|
||||
- REDIS_MASTER_NAME="mymaster"
|
@ -0,0 +1,38 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bouncer-admin
|
||||
labels:
|
||||
app: bouncer-admin
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: bouncer-admin
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: bouncer-admin
|
||||
spec:
|
||||
containers:
|
||||
- name: bouncer-admin
|
||||
image: reg.cadoles.com/cadoles/bouncer:v2023.7.8-b44ff2a
|
||||
command: ["bouncer"]
|
||||
args: ["--debug", "-c", "/etc/bouncer/config.yml", "server", "admin", "run"]
|
||||
imagePullPolicy: Always
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: bouncer-admin-env
|
||||
env:
|
||||
- name: REDIS_SENTINEL_HOST
|
||||
value: "rfs-$(REDIS_SERVICE_NAME)"
|
||||
ports:
|
||||
- name: bouncer-admin
|
||||
containerPort: 8081
|
||||
volumeMounts:
|
||||
- mountPath: /etc/bouncer/
|
||||
name: bouncer-admin-config
|
||||
volumes:
|
||||
- name: bouncer-admin-config
|
||||
configMap:
|
||||
name: bouncer-admin-config
|
@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
io.kompose.service: bouncer-admin
|
||||
name: bouncer-admin
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: bouncer-admin
|
||||
port: 8081
|
||||
targetPort: 8080
|
||||
selector:
|
||||
io.kompose.service: bouncer-admin
|
@ -0,0 +1,22 @@
|
||||
proxy:
|
||||
http:
|
||||
host: 0.0.0.0
|
||||
port: 8080
|
||||
metrics:
|
||||
enabled: true
|
||||
endpoint: /.bouncer/metrics
|
||||
basicAuth: null
|
||||
|
||||
layers:
|
||||
queue:
|
||||
templateDir: /usr/share/bouncer/layers/queue/templates
|
||||
defaultKeepAlive: 1m0s
|
||||
|
||||
redis:
|
||||
addresses:
|
||||
- ${RFS_BOUNCER_REDIS_SERVICE_HOST}:${RFS_BOUNCER_REDIS_SERVICE_PORT}
|
||||
master: ""
|
||||
|
||||
logger:
|
||||
level: 3
|
||||
format: human
|
@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./resources/service.yaml
|
||||
- ./resources/deployment.yaml
|
||||
|
||||
configMapGenerator:
|
||||
- name: bouncer-server-config
|
||||
files:
|
||||
- ./files/config.yml
|
@ -0,0 +1,31 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bouncer-server
|
||||
labels:
|
||||
app: bouncer-server
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: bouncer-server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: bouncer-server
|
||||
spec:
|
||||
containers:
|
||||
- name: bouncer-server
|
||||
image: reg.cadoles.com/cadoles/bouncer:v2023.7.8-b44ff2a
|
||||
command: ["bouncer", "-c", "/etc/bouncer/config.yml", "server", "proxy", "run"]
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: bouncer-server
|
||||
containerPort: 8080
|
||||
volumeMounts:
|
||||
- mountPath: /etc/bouncer/
|
||||
name: bouncer-server-config
|
||||
volumes:
|
||||
- name: bouncer-server-config
|
||||
configMap:
|
||||
name: bouncer-server-config
|
@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
labels:
|
||||
io.kompose.service: bouncer-server
|
||||
name: bouncer-server
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: bouncer-server
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
io.kompose.service: bouncer-server
|
4
misc/k8s/kustomization/base/resources/namespace.yaml
Normal file
4
misc/k8s/kustomization/base/resources/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: bouncer
|
@ -0,0 +1,15 @@
|
||||
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- ./resources/redis-cluster.yaml
|
||||
|
||||
vars:
|
||||
- name: REDIS_SERVICE_NAME
|
||||
objref:
|
||||
name: bouncer-redis
|
||||
apiVersion: databases.spotahome.com/v1
|
||||
kind: RedisFailover
|
||||
fieldref:
|
||||
fieldpath: metadata.name
|
@ -0,0 +1,21 @@
|
||||
apiVersion: databases.spotahome.com/v1
|
||||
kind: RedisFailover
|
||||
metadata:
|
||||
name: bouncer-redis
|
||||
spec:
|
||||
sentinel:
|
||||
replicas: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
limits:
|
||||
memory: 100Mi
|
||||
redis:
|
||||
replicas: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
limits:
|
||||
cpu: 400m
|
||||
memory: 500Mi
|
18
misc/k8s/kustomization/overlays/dev/kustomization.yaml
Normal file
18
misc/k8s/kustomization/overlays/dev/kustomization.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: bouncer-dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
secretGenerator:
|
||||
- files:
|
||||
- secrets/dockerconfig/.dockerconfigjson
|
||||
name: regcred-dev
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
|
||||
patches:
|
||||
- path: patches/add-registry-pull-secret.patch.yaml
|
||||
target:
|
||||
kind: Deployment
|
||||
version: v1
|
@ -0,0 +1,4 @@
|
||||
- op: add
|
||||
path: "/spec/template/spec/imagePullSecrets"
|
||||
value:
|
||||
- name: regcred-dev
|
@ -6,6 +6,9 @@ admin:
|
||||
host: 127.0.0.1
|
||||
# Port d'écoute du service
|
||||
port: 8081
|
||||
# Utiliser les entêtes HTTP True-Client-IP, X-Real-IP ou X-Forwarded-For
|
||||
# pour le calcul de l'adresse distante à l'origine des requêtes
|
||||
useRealIP: true
|
||||
|
||||
# Configuration CORS du service
|
||||
# Uniquement nécessaire si un frontend web
|
||||
@ -45,6 +48,25 @@ admin:
|
||||
# de publication
|
||||
# Mettre à null pour désactiver l'authentification
|
||||
basicAuth: null
|
||||
|
||||
# Configuration de l'intégration Sentry
|
||||
# Voir https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
||||
sentry:
|
||||
dsn: ""
|
||||
debug: false
|
||||
flushTimeout: 2s
|
||||
attachStacktrace: true
|
||||
sampleRate: 1
|
||||
enableTracing: true
|
||||
tracesSampleRate: 0.2
|
||||
profilesSampleRate: 1
|
||||
ignoreErrors: []
|
||||
sendDefaultPII: false
|
||||
serverName: ""
|
||||
environment: ""
|
||||
maxBreadcrumbs: 0
|
||||
maxSpans: 1000
|
||||
maxErrorDepth: 10
|
||||
|
||||
# Configuration du service "proxy"
|
||||
proxy:
|
||||
@ -54,6 +76,9 @@ proxy:
|
||||
host: 0.0.0.0
|
||||
# Port d'écoute du service
|
||||
port: 8080
|
||||
# Utiliser les entêtes HTTP True-Client-IP, X-Real-IP ou X-Forwarded-For
|
||||
# pour le calcul de l'adresse distante à l'origine des requêtes
|
||||
useRealIP: true
|
||||
|
||||
# Métriques Prometheus
|
||||
metrics:
|
||||
@ -85,6 +110,25 @@ proxy:
|
||||
readBufferSize: 4096
|
||||
maxResponseHeaderBytes: 0
|
||||
|
||||
# Configuration de l'intégration Sentry
|
||||
# Voir https://pkg.go.dev/github.com/getsentry/sentry-go?utm_source=godoc#ClientOptions
|
||||
sentry:
|
||||
dsn: ""
|
||||
debug: false
|
||||
flushTimeout: 2s
|
||||
attachStacktrace: true
|
||||
sampleRate: 1
|
||||
enableTracing: true
|
||||
tracesSampleRate: 0.2
|
||||
profilesSampleRate: 1
|
||||
ignoreErrors: []
|
||||
sendDefaultPII: false
|
||||
serverName: ""
|
||||
environment: ""
|
||||
maxBreadcrumbs: 0
|
||||
maxSpans: 1000
|
||||
maxErrorDepth: 10
|
||||
|
||||
# Configuration des connexions TCP
|
||||
# Voir https://pkg.go.dev/net#Dialer
|
||||
dial:
|
||||
@ -123,4 +167,10 @@ layers:
|
||||
# Répertoire contenant les templates
|
||||
templateDir: "/etc/bouncer/layers/queue/templates"
|
||||
# Temps de vie par défaut d'une session
|
||||
defaultKeepAlive: 1m
|
||||
defaultKeepAlive: 1m
|
||||
|
||||
# Configuration du layer "circuitbreaker"
|
||||
circuitbreaker:
|
||||
# Répertoire contenant les templates
|
||||
templateDir: "/etc/bouncer/layers/circuitbreaker/templates"
|
||||
|
7
misc/prometheus/prometheus.yml
Normal file
7
misc/prometheus/prometheus.yml
Normal file
@ -0,0 +1,7 @@
|
||||
scrape_configs:
|
||||
- job_name: bouncer-proxy
|
||||
metrics_path: /.bouncer/metrics
|
||||
static_configs:
|
||||
- targets:
|
||||
- "localhost:8080"
|
||||
scrape_interval: 5s
|
6
misc/siege/urls.txt
Normal file
6
misc/siege/urls.txt
Normal file
@ -0,0 +1,6 @@
|
||||
http://localhost:8080/blog/
|
||||
http://localhost:8080/services/
|
||||
http://localhost:8080/
|
||||
http://localhost:8080/recrutement/
|
||||
http://localhost:8080/faq/
|
||||
http://localhost:8080/societe/histoire/
|
@ -14,4 +14,9 @@ layers/**
|
||||
|
||||
{
|
||||
daemon +sigint: make run-redis
|
||||
}
|
||||
|
||||
misc/prometheus/prometheus.yml
|
||||
{
|
||||
daemon +sigint: make run-prometheus
|
||||
}
|
42
skaffold.yaml
Normal file
42
skaffold.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
apiVersion: skaffold/v3
|
||||
kind: Config
|
||||
|
||||
metadata:
|
||||
name: bouncer
|
||||
|
||||
manifests:
|
||||
kustomize:
|
||||
paths:
|
||||
- misc/k8s/kustomization/base
|
||||
|
||||
profiles:
|
||||
- name: dev
|
||||
manifests:
|
||||
kustomize:
|
||||
paths:
|
||||
- misc/k8s/kustomization/overlays/dev
|
||||
activation:
|
||||
- command: dev
|
||||
|
||||
build:
|
||||
local:
|
||||
push: true
|
||||
|
||||
tagPolicy:
|
||||
sha256: {}
|
||||
|
||||
artifacts:
|
||||
- image: reg.cadoles.com/cadoles/bouncer
|
||||
context: .
|
||||
sync:
|
||||
infer:
|
||||
- cmd/**
|
||||
- internal/**
|
||||
- layers/**
|
||||
- tools/**
|
||||
- data/**
|
||||
docker:
|
||||
dockerfile: misc/images/bouncer/Dockerfile
|
||||
|
||||
deploy:
|
||||
statusCheckDeadlineSeconds: 600
|
Reference in New Issue
Block a user