Compare commits
5 Commits
v2024.3.28
...
v2024.4.5-
Author | SHA1 | Date | |
---|---|---|---|
83fcb9a39d | |||
ad907576dc | |||
3a894972f1 | |||
274bef13d8 | |||
f548c8c8e7 |
@ -25,7 +25,6 @@ Où:
|
|||||||
- droit en lecture sur l'ensemble des entités (proxy, layer);
|
- droit en lecture sur l'ensemble des entités (proxy, layer);
|
||||||
- droit en lecture ET en écriture sur l'ensemble des entités.
|
- droit en lecture ET en écriture sur l'ensemble des entités.
|
||||||
|
|
||||||
|
|
||||||
## Points d'entrée
|
## Points d'entrée
|
||||||
|
|
||||||
### `POST /api/v1/proxies`
|
### `POST /api/v1/proxies`
|
||||||
@ -34,7 +33,7 @@ Créer un nouveau proxy
|
|||||||
|
|
||||||
#### Exemple de corps de requête
|
#### Exemple de corps de requête
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"name": "myproxy", // OBLIGATOIRE - Nom du proxy
|
"name": "myproxy", // OBLIGATOIRE - Nom du proxy
|
||||||
"to": "https://www.cadoles.com", // OBLIGATOIRE - Site distant ciblé par le proxy
|
"to": "https://www.cadoles.com", // OBLIGATOIRE - Site distant ciblé par le proxy
|
||||||
@ -44,7 +43,7 @@ Créer un nouveau proxy
|
|||||||
|
|
||||||
#### Exemple de résultat
|
#### Exemple de résultat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"proxy": {
|
"proxy": {
|
||||||
@ -74,7 +73,7 @@ Récupérer les informations complètes sur un proxy
|
|||||||
|
|
||||||
#### Exemple de résultat
|
#### Exemple de résultat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"proxy": {
|
"proxy": {
|
||||||
@ -100,18 +99,18 @@ Modifier un proxy
|
|||||||
|
|
||||||
#### Exemple de corps de requête
|
#### Exemple de corps de requête
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"to": "https://www.cadoles.com", // OPTIONNEL - Site distant ciblé par le proxy
|
"to": "https://www.cadoles.com", // OPTIONNEL - Site distant ciblé par le proxy
|
||||||
"from": ["mylocalproxydomain:*"], // OPTIONNEL - Liste de patrons de filtrage associés au proxy
|
"from": ["mylocalproxydomain:*"], // OPTIONNEL - Liste de patrons de filtrage associés au proxy
|
||||||
"weight": 100, // OPTIONNEL - Poids à associer au proxy
|
"weight": 100, // OPTIONNEL - Poids à associer au proxy
|
||||||
"enabled": true, // OPTIONNEL - Activer/désactiver le proxy
|
"enabled": true // OPTIONNEL - Activer/désactiver le proxy
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Exemple de résultat
|
#### Exemple de résultat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"proxy": {
|
"proxy": {
|
||||||
@ -141,14 +140,14 @@ Lister les proxies existants
|
|||||||
|
|
||||||
#### Exemple de résultat
|
#### Exemple de résultat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"proxies": [
|
"proxies": [
|
||||||
{
|
{
|
||||||
"name": "myproxy",
|
"name": "myproxy",
|
||||||
"weight": 0,
|
"weight": 0,
|
||||||
"enabled": false,
|
"enabled": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -169,7 +168,7 @@ Supprimer le proxy
|
|||||||
|
|
||||||
#### Exemple de résultat
|
#### Exemple de résultat
|
||||||
|
|
||||||
```json5
|
```json
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"proxyName": "myproxy"
|
"proxyName": "myproxy"
|
||||||
|
@ -22,7 +22,7 @@ func (s *Server) bootstrapProxies(ctx context.Context) error {
|
|||||||
layerRepo := s.layerRepository
|
layerRepo := s.layerRepository
|
||||||
|
|
||||||
lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout)
|
lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout)
|
||||||
locker := redis.NewLocker(s.redisClient)
|
locker := redis.NewLocker(s.redisClient, int(s.bootstrapConfig.MaxConnectionRetries))
|
||||||
|
|
||||||
err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
|
err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
|
||||||
logger.Info(ctx, "bootstrapping proxies")
|
logger.Info(ctx, "bootstrapping proxies")
|
||||||
|
@ -16,6 +16,7 @@ type LogFormatter struct{}
|
|||||||
func (*LogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry {
|
func (*LogFormatter) NewLogEntry(r *http.Request) middleware.LogEntry {
|
||||||
return &LogEntry{
|
return &LogEntry{
|
||||||
method: r.Method,
|
method: r.Method,
|
||||||
|
host: r.Host,
|
||||||
path: r.URL.Path,
|
path: r.URL.Path,
|
||||||
ctx: r.Context(),
|
ctx: r.Context(),
|
||||||
}
|
}
|
||||||
@ -29,6 +30,7 @@ var _ middleware.LogFormatter = &LogFormatter{}
|
|||||||
|
|
||||||
type LogEntry struct {
|
type LogEntry struct {
|
||||||
method string
|
method string
|
||||||
|
host string
|
||||||
path string
|
path string
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
}
|
}
|
||||||
@ -41,6 +43,7 @@ func (e *LogEntry) Panic(v interface{}, stack []byte) {
|
|||||||
// Write implements middleware.LogEntry
|
// Write implements middleware.LogEntry
|
||||||
func (e *LogEntry) Write(status int, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
func (e *LogEntry) Write(status int, bytes int, header http.Header, elapsed time.Duration, extra interface{}) {
|
||||||
logger.Info(e.ctx, fmt.Sprintf("%s %s - %d", e.method, e.path, status),
|
logger.Info(e.ctx, fmt.Sprintf("%s %s - %d", e.method, e.path, status),
|
||||||
|
logger.F("host", e.host),
|
||||||
logger.F("status", status),
|
logger.F("status", status),
|
||||||
logger.F("bytes", bytes),
|
logger.F("bytes", bytes),
|
||||||
logger.F("elapsed", elapsed),
|
logger.F("elapsed", elapsed),
|
||||||
|
@ -15,6 +15,7 @@ type BootstrapConfig struct {
|
|||||||
Proxies map[store.ProxyName]BootstrapProxyConfig `yaml:"proxies"`
|
Proxies map[store.ProxyName]BootstrapProxyConfig `yaml:"proxies"`
|
||||||
Dir InterpolatedString `yaml:"dir"`
|
Dir InterpolatedString `yaml:"dir"`
|
||||||
LockTimeout InterpolatedDuration `yaml:"lockTimeout"`
|
LockTimeout InterpolatedDuration `yaml:"lockTimeout"`
|
||||||
|
MaxConnectionRetries InterpolatedInt `yaml:"maxRetries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BootstrapConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
func (c *BootstrapConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
@ -64,6 +65,7 @@ func NewDefaultBootstrapConfig() BootstrapConfig {
|
|||||||
return BootstrapConfig{
|
return BootstrapConfig{
|
||||||
Dir: "",
|
Dir: "",
|
||||||
LockTimeout: *NewInterpolatedDuration(30 * time.Second),
|
LockTimeout: *NewInterpolatedDuration(30 * time.Second),
|
||||||
|
MaxConnectionRetries: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ type RedisConfig struct {
|
|||||||
ReadTimeout InterpolatedDuration `yaml:"readTimeout"`
|
ReadTimeout InterpolatedDuration `yaml:"readTimeout"`
|
||||||
WriteTimeout InterpolatedDuration `yaml:"writeTimeout"`
|
WriteTimeout InterpolatedDuration `yaml:"writeTimeout"`
|
||||||
DialTimeout InterpolatedDuration `yaml:"dialTimeout"`
|
DialTimeout InterpolatedDuration `yaml:"dialTimeout"`
|
||||||
|
LockMaxRetries InterpolatedInt `yaml:"lockMaxRetries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultRedisConfig() RedisConfig {
|
func NewDefaultRedisConfig() RedisConfig {
|
||||||
@ -23,5 +24,6 @@ func NewDefaultRedisConfig() RedisConfig {
|
|||||||
ReadTimeout: InterpolatedDuration(30 * time.Second),
|
ReadTimeout: InterpolatedDuration(30 * time.Second),
|
||||||
WriteTimeout: InterpolatedDuration(30 * time.Second),
|
WriteTimeout: InterpolatedDuration(30 * time.Second),
|
||||||
DialTimeout: InterpolatedDuration(30 * time.Second),
|
DialTimeout: InterpolatedDuration(30 * time.Second),
|
||||||
|
LockMaxRetries: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
type Locker struct {
|
type Locker struct {
|
||||||
client redis.UniversalClient
|
client redis.UniversalClient
|
||||||
timeout time.Duration
|
maxRetries int
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLock implements lock.Locker.
|
// WithLock implements lock.Locker.
|
||||||
@ -26,6 +26,7 @@ func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration
|
|||||||
|
|
||||||
logger.Debug(ctx, "acquiring lock")
|
logger.Debug(ctx, "acquiring lock")
|
||||||
|
|
||||||
|
err := retryWithBackoff(ctx, l.maxRetries, func(ctx context.Context) error {
|
||||||
lock, err := locker.Obtain(ctx, key, timeout, &redislock.Options{
|
lock, err := locker.Obtain(ctx, key, timeout, &redislock.Options{
|
||||||
RetryStrategy: backoff,
|
RetryStrategy: backoff,
|
||||||
})
|
})
|
||||||
@ -48,11 +49,18 @@ func (l *Locker) WithLock(ctx context.Context, key string, timeout time.Duration
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocker(client redis.UniversalClient) *Locker {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLocker(client redis.UniversalClient, maxRetries int) *Locker {
|
||||||
return &Locker{
|
return &Locker{
|
||||||
client: client,
|
client: client,
|
||||||
|
maxRetries: maxRetries,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
internal/lock/redis/retry.go
Normal file
42
internal/lock/redis/retry.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/wpetit/goweb/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
baseWatchBackoffDelay = time.Millisecond * 500
|
||||||
|
maxDelay = time.Minute * 10
|
||||||
|
)
|
||||||
|
|
||||||
|
func retryWithBackoff(ctx context.Context, attempts int, fn func(ctx context.Context) error) error {
|
||||||
|
backoffDelay := baseWatchBackoffDelay
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := fn(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.WithStack(err)
|
||||||
|
|
||||||
|
count++
|
||||||
|
if count >= attempts {
|
||||||
|
return errors.Wrapf(err, "execution failed after %d attempts", attempts)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Error(ctx, "error while executing func, retrying with backoff", logger.E(err), logger.F("backoffDelay", backoffDelay), logger.F("remainingAttempts", attempts-count))
|
||||||
|
|
||||||
|
time.Sleep(backoffDelay)
|
||||||
|
|
||||||
|
backoffDelay *= 2
|
||||||
|
if backoffDelay > maxDelay {
|
||||||
|
backoffDelay = maxDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package director
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy"
|
"forge.cadoles.com/Cadoles/go-proxy"
|
||||||
@ -28,15 +27,27 @@ func (d *Director) rewriteRequest(r *http.Request) (*http.Request, error) {
|
|||||||
return r, errors.WithStack(err)
|
return r, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url := getRequestURL(r)
|
||||||
|
ctx = logger.With(r.Context(), logger.F("url", url.String()))
|
||||||
|
|
||||||
var match *store.Proxy
|
var match *store.Proxy
|
||||||
|
|
||||||
MAIN:
|
MAIN:
|
||||||
for _, p := range proxies {
|
for _, p := range proxies {
|
||||||
for _, from := range p.From {
|
for _, from := range p.From {
|
||||||
if matches := wildcard.Match(r.Host, from); !matches {
|
logger.Debug(
|
||||||
|
ctx, "matching request with proxy's from",
|
||||||
|
logger.F("from", from),
|
||||||
|
)
|
||||||
|
if matches := wildcard.Match(url.String(), from); !matches {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Debug(
|
||||||
|
ctx, "proxy's from matched",
|
||||||
|
logger.F("from", from),
|
||||||
|
)
|
||||||
|
|
||||||
match = p
|
match = p
|
||||||
break MAIN
|
break MAIN
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package director
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy"
|
"forge.cadoles.com/Cadoles/go-proxy"
|
||||||
"forge.cadoles.com/Cadoles/go-proxy/util"
|
"forge.cadoles.com/Cadoles/go-proxy/util"
|
||||||
@ -16,3 +17,19 @@ func createMiddlewareChain(handler http.Handler, middlewares []proxy.Middleware)
|
|||||||
|
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRequestURL(r *http.Request) *url.URL {
|
||||||
|
scheme := "http"
|
||||||
|
if r.URL.Scheme != "" {
|
||||||
|
scheme = r.URL.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
url := url.URL{
|
||||||
|
Host: r.Host,
|
||||||
|
Scheme: scheme,
|
||||||
|
Path: r.URL.Path,
|
||||||
|
RawQuery: r.URL.RawQuery,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &url
|
||||||
|
}
|
||||||
|
@ -28,7 +28,7 @@ func SetupIntegrations(ctx context.Context, conf *config.Config) ([]integration.
|
|||||||
|
|
||||||
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
|
func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kubernetes.Integration, error) {
|
||||||
client := newRedisClient(conf.Redis)
|
client := newRedisClient(conf.Redis)
|
||||||
locker := redis.NewLocker(client)
|
locker := redis.NewLocker(client, 10)
|
||||||
|
|
||||||
integration := kubernetes.NewIntegration(
|
integration := kubernetes.NewIntegration(
|
||||||
kubernetes.WithReaderTokenSecret(string(conf.Integrations.Kubernetes.ReaderTokenSecret)),
|
kubernetes.WithReaderTokenSecret(string(conf.Integrations.Kubernetes.ReaderTokenSecret)),
|
||||||
|
@ -10,6 +10,6 @@ import (
|
|||||||
|
|
||||||
func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
|
func SetupLocker(ctx context.Context, conf *config.Config) (lock.Locker, error) {
|
||||||
client := newRedisClient(conf.Redis)
|
client := newRedisClient(conf.Redis)
|
||||||
locker := redis.NewLocker(client)
|
locker := redis.NewLocker(client, int(conf.Redis.LockMaxRetries))
|
||||||
return locker, nil
|
return locker, nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user