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, int(s.bootstrapConfig.MaxConnectionRetries)) 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 { loopCtx := logger.With(ctx, logger.F("proxyName", proxyName), logger.F("proxyFrom", proxyConfig.From), logger.F("proxyTo", proxyConfig.To)) _, err := s.proxyRepository.GetProxy(ctx, proxyName) if !errors.Is(err, store.ErrNotFound) { if err != nil { return errors.WithStack(err) } if proxyConfig.Recreate { logger.Info(loopCtx, "force recreating proxy") if err := s.deleteProxyAndLayers(ctx, proxyName); err != nil { return errors.WithStack(err) } } else { logger.Info(loopCtx, "ignoring existing proxy") continue } } logger.Info(loopCtx, "creating proxy") 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 }