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 { if err := s.initRedisClient(ctx); err != nil { return errors.WithStack(err) } if err := s.initLayerRepository(ctx); err != nil { return errors.WithStack(err) } if err := s.initProxyRepository(ctx); err != nil { return errors.WithStack(err) } return nil } func (s *Server) initRedisClient(ctx context.Context) error { client := setup.NewRedisClient(ctx, s.redisConfig) s.redisClient = client return nil } func (s *Server) initLayerRepository(ctx context.Context) error { layerRepository, err := setup.NewLayerRepository(ctx, s.redisClient) if err != nil { return errors.WithStack(err) } s.layerRepository = layerRepository return nil } func (s *Server) initProxyRepository(ctx context.Context) error { proxyRepository, err := setup.NewProxyRepository(ctx, s.redisClient) if err != nil { return errors.WithStack(err) } s.proxyRepository = proxyRepository 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 }