feat: kubernetes basic integration
This commit is contained in:
112
internal/admin/bootstrap.go
Normal file
112
internal/admin/bootstrap.go
Normal file
@ -0,0 +1,112 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/lock/redis"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func (s *Server) bootstrapProxies(ctx context.Context) error {
|
||||
if err := s.validateBootstrap(ctx); err != nil {
|
||||
return errors.Wrap(err, "could not validate bootstrapped proxies")
|
||||
}
|
||||
|
||||
proxyRepo := s.proxyRepository
|
||||
layerRepo := s.layerRepository
|
||||
|
||||
lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout)
|
||||
locker := redis.NewLocker(s.redisClient)
|
||||
|
||||
err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
|
||||
logger.Info(ctx, "bootstrapping proxies")
|
||||
|
||||
for proxyName, proxyConfig := range s.bootstrapConfig.Proxies {
|
||||
_, err := s.proxyRepository.GetProxy(ctx, proxyName)
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
logger.Info(ctx, "ignoring existing proxy", logger.F("proxyName", proxyName))
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info(ctx, "creating proxy", logger.F("proxyName", proxyName))
|
||||
|
||||
if _, err := proxyRepo.CreateProxy(ctx, proxyName, string(proxyConfig.To), proxyConfig.From...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err = proxyRepo.UpdateProxy(
|
||||
ctx, proxyName,
|
||||
store.WithProxyUpdateEnabled(bool(proxyConfig.Enabled)),
|
||||
store.WithProxyUpdateWeight(int(proxyConfig.Weight)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for layerName, layerConfig := range proxyConfig.Layers {
|
||||
layerType := store.LayerType(layerConfig.Type)
|
||||
layerOptions := store.LayerOptions(layerConfig.Options)
|
||||
|
||||
if _, err := layerRepo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err := layerRepo.UpdateLayer(
|
||||
ctx,
|
||||
proxyName, layerName,
|
||||
store.WithLayerUpdateEnabled(bool(layerConfig.Enabled)),
|
||||
store.WithLayerUpdateOptions(layerOptions),
|
||||
store.WithLayerUpdateWeight(int(layerConfig.Weight)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const validateErrMessage = "could not validate proxy '%s': could not validate layer '%s'"
|
||||
|
||||
func (s *Server) validateBootstrap(ctx context.Context) error {
|
||||
for proxyName, proxyConf := range s.bootstrapConfig.Proxies {
|
||||
for layerName, layerConf := range proxyConf.Layers {
|
||||
layerType := store.LayerType(layerConf.Type)
|
||||
if !setup.LayerTypeExists(layerType) {
|
||||
return errors.Errorf(validateErrMessage+": could not find layer type '%s'", proxyName, layerName, layerType)
|
||||
}
|
||||
|
||||
layerOptionsSchema, err := setup.GetLayerOptionsSchema(layerType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
|
||||
}
|
||||
|
||||
rawOptions := func(opts config.InterpolatedMap) map[string]any {
|
||||
return opts
|
||||
}(layerConf.Options)
|
||||
|
||||
if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {
|
||||
return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -2,15 +2,9 @@ package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/schema"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/bsm/redislock"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func (s *Server) initRepositories(ctx context.Context) error {
|
||||
@ -58,112 +52,3 @@ func (s *Server) initProxyRepository(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const bootstrapLockKey = "bouncer-bootstrap"
|
||||
|
||||
func (s *Server) bootstrapProxies(ctx context.Context) error {
|
||||
if err := s.validateBootstrap(ctx); err != nil {
|
||||
return errors.Wrap(err, "could not validate bootstrapped proxies")
|
||||
}
|
||||
|
||||
proxyRepo := s.proxyRepository
|
||||
layerRepo := s.layerRepository
|
||||
|
||||
locker := redislock.New(s.redisClient)
|
||||
|
||||
backoff := redislock.ExponentialBackoff(time.Second, time.Duration(s.bootstrapConfig.LockTimeout)*2)
|
||||
|
||||
logger.Debug(ctx, "acquiring proxies bootstrap lock", logger.F("lockTimeout", s.bootstrapConfig.LockTimeout))
|
||||
|
||||
lock, err := locker.Obtain(ctx, bootstrapLockKey, time.Duration(s.bootstrapConfig.LockTimeout), &redislock.Options{
|
||||
RetryStrategy: backoff,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := lock.Release(ctx); err != nil {
|
||||
logger.Error(ctx, "could not release lock", logger.E(errors.WithStack(err)))
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Info(ctx, "bootstrapping proxies")
|
||||
|
||||
for proxyName, proxyConfig := range s.bootstrapConfig.Proxies {
|
||||
_, err := s.proxyRepository.GetProxy(ctx, proxyName)
|
||||
if !errors.Is(err, store.ErrNotFound) {
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
logger.Info(ctx, "ignoring existing proxy", logger.F("proxyName", proxyName))
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info(ctx, "creating proxy", logger.F("proxyName", proxyName))
|
||||
|
||||
if _, err := proxyRepo.CreateProxy(ctx, proxyName, string(proxyConfig.To), proxyConfig.From...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err = proxyRepo.UpdateProxy(
|
||||
ctx, proxyName,
|
||||
store.WithProxyUpdateEnabled(bool(proxyConfig.Enabled)),
|
||||
store.WithProxyUpdateWeight(int(proxyConfig.Weight)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
for layerName, layerConfig := range proxyConfig.Layers {
|
||||
layerType := store.LayerType(layerConfig.Type)
|
||||
layerOptions := store.LayerOptions(layerConfig.Options)
|
||||
|
||||
if _, err := layerRepo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
_, err := layerRepo.UpdateLayer(
|
||||
ctx,
|
||||
proxyName, layerName,
|
||||
store.WithLayerUpdateEnabled(bool(layerConfig.Enabled)),
|
||||
store.WithLayerUpdateOptions(layerOptions),
|
||||
store.WithLayerUpdateWeight(int(layerConfig.Weight)),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const validateErrMessage = "could not validate proxy '%s': could not validate layer '%s'"
|
||||
|
||||
func (s *Server) validateBootstrap(ctx context.Context) error {
|
||||
for proxyName, proxyConf := range s.bootstrapConfig.Proxies {
|
||||
for layerName, layerConf := range proxyConf.Layers {
|
||||
layerType := store.LayerType(layerConf.Type)
|
||||
if !setup.LayerTypeExists(layerType) {
|
||||
return errors.Errorf(validateErrMessage+": could not find layer type '%s'", proxyName, layerName, layerType)
|
||||
}
|
||||
|
||||
layerOptionsSchema, err := setup.GetLayerOptionsSchema(layerType)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
|
||||
}
|
||||
|
||||
rawOptions := func(opts config.InterpolatedMap) map[string]any {
|
||||
return opts
|
||||
}(layerConf.Options)
|
||||
|
||||
if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {
|
||||
return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package admin
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/integration"
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
BootstrapConfig config.BootstrapConfig
|
||||
ServerConfig config.AdminServerConfig
|
||||
RedisConfig config.RedisConfig
|
||||
Integrations []integration.Integration
|
||||
}
|
||||
|
||||
type OptionFunc func(*Option)
|
||||
@ -16,6 +18,7 @@ func defaultOption() *Option {
|
||||
return &Option{
|
||||
ServerConfig: config.NewDefaultAdminServerConfig(),
|
||||
RedisConfig: config.NewDefaultRedisConfig(),
|
||||
Integrations: make([]integration.Integration, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,3 +39,9 @@ func WithBootstrapConfig(conf config.BootstrapConfig) OptionFunc {
|
||||
opt.BootstrapConfig = conf
|
||||
}
|
||||
}
|
||||
|
||||
func WithIntegrations(integrations ...integration.Integration) OptionFunc {
|
||||
return func(opt *Option) {
|
||||
opt.Integrations = integrations
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"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/integration"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||
@ -24,9 +25,13 @@ import (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
serverConfig config.AdminServerConfig
|
||||
redisConfig config.RedisConfig
|
||||
redisClient redis.UniversalClient
|
||||
serverConfig config.AdminServerConfig
|
||||
redisConfig config.RedisConfig
|
||||
|
||||
redisClient redis.UniversalClient
|
||||
|
||||
integrations []integration.Integration
|
||||
|
||||
bootstrapConfig config.BootstrapConfig
|
||||
proxyRepository store.ProxyRepository
|
||||
layerRepository store.LayerRepository
|
||||
@ -62,6 +67,12 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
return
|
||||
}
|
||||
|
||||
if err := integration.RunOnStartup(ctx, s.integrations); err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.serverConfig.HTTP.Host, s.serverConfig.HTTP.Port))
|
||||
if err != nil {
|
||||
errs <- errors.WithStack(err)
|
||||
@ -187,5 +198,6 @@ func NewServer(funcs ...OptionFunc) *Server {
|
||||
serverConfig: opt.ServerConfig,
|
||||
redisConfig: opt.RedisConfig,
|
||||
bootstrapConfig: opt.BootstrapConfig,
|
||||
integrations: opt.Integrations,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user