diff --git a/doc/fr/getting-started.md b/doc/fr/getting-started.md index 1d85ba3..db675e0 100644 --- a/doc/fr/getting-started.md +++ b/doc/fr/getting-started.md @@ -59,7 +59,7 @@ ```bash # Création du proxy nommé 'cadoles' vers https://www.cadoles.com - bouncer admin proxy create --to https://www.cadoles.com --proxy-name cadoles + bouncer admin proxy create --proxy-to https://www.cadoles.com --proxy-name cadoles ``` Un message équivalent à celui ci devrait s'afficher: @@ -77,7 +77,7 @@ ```bash # Activation du proxy - bouncer admin proxy update --proxy-name cadoles --enabled=true + bouncer admin proxy update --proxy-name cadoles --proxy-enabled=true ``` Un message équivalent à celui ci devrait s'afficher: diff --git a/doc/fr/tutorials/add-queue-layer.md b/doc/fr/tutorials/add-queue-layer.md index 99980a7..fb10be8 100644 --- a/doc/fr/tutorials/add-queue-layer.md +++ b/doc/fr/tutorials/add-queue-layer.md @@ -22,7 +22,7 @@ 2. À ce stade, le calque est encore inactif. Définir la capacité de la file d'attente à 1 et activer le calque en utilisant le CLI ```bash - bouncer admin layer update --proxy-name cadoles --layer-name my-queue --enabled=true --options '{"capacity": 1}' + bouncer admin layer update --proxy-name cadoles --layer-name my-queue --layer-enabled=true --layer-options '{"capacity": 1}' ``` Un message équivalent à celui ci devrait s'afficher: diff --git a/internal/admin/layer_route.go b/internal/admin/layer_route.go index df39163..e732142 100644 --- a/internal/admin/layer_route.go +++ b/internal/admin/layer_route.go @@ -5,6 +5,7 @@ import ( "sort" "forge.cadoles.com/cadoles/bouncer/internal/schema" + "forge.cadoles.com/cadoles/bouncer/internal/setup" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/go-chi/chi/v5" "github.com/pkg/errors" @@ -155,13 +156,22 @@ func (s *Server) createLayer(w http.ResponseWriter, r *http.Request) { layerName, err := store.ValidateName(createLayerReq.Name) if err != nil { - logger.Error(r.Context(), "could not parse 'name' parameter", logger.E(errors.WithStack(err))) - api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil) + logger.Error(r.Context(), "invalid 'name' parameter", logger.E(errors.WithStack(err))) + api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil) return } - layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), store.LayerType(createLayerReq.Type), createLayerReq.Options) + 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)) + api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeInvalidRequest, nil) + + return + } + + layer, err := s.layerRepository.CreateLayer(ctx, proxyName, store.LayerName(layerName), layerType, createLayerReq.Options) if err != nil { if errors.Is(err, store.ErrAlreadyExist) { api.ErrorResponse(w, http.StatusConflict, ErrCodeAlreadyExist, nil) diff --git a/internal/command/admin/layer/flag/flag.go b/internal/command/admin/layer/flag/flag.go index 61a5a5a..e7505b2 100644 --- a/internal/command/admin/layer/flag/flag.go +++ b/internal/command/admin/layer/flag/flag.go @@ -2,27 +2,22 @@ package flag import ( "encoding/json" + "fmt" proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" + "forge.cadoles.com/cadoles/bouncer/internal/setup" "forge.cadoles.com/cadoles/bouncer/internal/store" "github.com/pkg/errors" "github.com/urfave/cli/v2" ) const ( - FlagLayerName = "layer-name" - FlagLayerType = "layer-type" - FlagLayerOptions = "layer-options" + FlagKeyLayerType = "layer-type" ) func WithLayerFlags(flags ...cli.Flag) []cli.Flag { baseFlags := proxyFlag.WithProxyFlags( - &cli.StringFlag{ - Name: FlagLayerName, - Usage: "use `LAYER_NAME` as targeted layer", - Value: "", - Required: true, - }, + LayerName(), ) flags = append(flags, baseFlags...) @@ -32,22 +27,63 @@ func WithLayerFlags(flags ...cli.Flag) []cli.Flag { func WithLayerCreateFlags(flags ...cli.Flag) []cli.Flag { return WithLayerFlags( - &cli.StringFlag{ - Name: FlagLayerType, - Usage: "Set `LAYER_TYPE` as layer's type", - Value: "", - Required: true, - }, - &cli.StringFlag{ - Name: FlagLayerOptions, - Usage: "Set `LAYER_OPTIONS` as layer's options", - Value: "{}", - }, + LayerType(), + LayerOptions(), ) } +const KeyLayerName = "layer-name" + +func LayerName() cli.Flag { + return &cli.StringFlag{ + Name: KeyLayerName, + Usage: "use `LAYER_NAME` as targeted layer", + Value: "", + Required: true, + } +} + +const KeyLayerType = "layer-type" + +func LayerType() cli.Flag { + return &cli.StringFlag{ + Name: KeyLayerType, + Usage: fmt.Sprintf("Set `LAYER_TYPE` as layer's type (available: %v)", setup.GetLayerTypes()), + Value: "", + Required: true, + } +} + +const KeyLayerOptions = "layer-options" + +func LayerOptions() cli.Flag { + return &cli.StringFlag{ + Name: KeyLayerOptions, + Usage: "Set `LAYER_OPTIONS` as layer's options", + Value: "{}", + } +} + +const KeyLayerWeight = "layer-weight" + +func LayerWeight() cli.Flag { + return &cli.IntFlag{ + Name: KeyLayerWeight, + Usage: "Set `LAYER_WEIGHT` as layer's weight", + } +} + +const KeyLayerEnabled = "layer-enabled" + +func LayerEnabled() cli.Flag { + return &cli.BoolFlag{ + Name: KeyLayerEnabled, + Usage: "Enable or disable layer", + } +} + func AssertLayerName(ctx *cli.Context) (store.LayerName, error) { - rawLayerName := ctx.String(FlagLayerName) + rawLayerName := ctx.String(KeyLayerName) name, err := store.ValidateName(rawLayerName) if err != nil { @@ -58,13 +94,18 @@ func AssertLayerName(ctx *cli.Context) (store.LayerName, error) { } func AssertLayerType(ctx *cli.Context) (store.LayerType, error) { - rawLayerType := ctx.String(FlagLayerType) + rawLayerType := ctx.String(FlagKeyLayerType) - return store.LayerType(rawLayerType), nil + layerType := store.LayerType(rawLayerType) + if !setup.LayerTypeExists(layerType) { + return "", errors.Errorf("unknown layer type '%s'", layerType) + } + + return layerType, nil } func AssertLayerOptions(ctx *cli.Context) (store.LayerOptions, error) { - rawLayerOptions := ctx.String(FlagLayerOptions) + rawLayerOptions := ctx.String(KeyLayerOptions) layerOptions := store.LayerOptions{} diff --git a/internal/command/admin/layer/update.go b/internal/command/admin/layer/update.go index 0bf438c..fe80c41 100644 --- a/internal/command/admin/layer/update.go +++ b/internal/command/admin/layer/update.go @@ -7,6 +7,7 @@ import ( "forge.cadoles.com/cadoles/bouncer/internal/client" "forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr" clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag" + "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag" layerFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer/flag" proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" "forge.cadoles.com/cadoles/bouncer/internal/format" @@ -20,18 +21,9 @@ func UpdateCommand() *cli.Command { Name: "update", Usage: "Update layer", Flags: layerFlag.WithLayerFlags( - &cli.BoolFlag{ - Name: "enabled", - Usage: "Enable or disable proxy", - }, - &cli.IntFlag{ - Name: "weight", - Usage: "Set `WEIGHT` as proxy's weight", - }, - &cli.StringFlag{ - Name: "options", - Usage: "Set `OPTIONS` as proxy's options", - }, + flag.LayerEnabled(), + flag.LayerWeight(), + flag.LayerOptions(), ), Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) @@ -53,22 +45,22 @@ func UpdateCommand() *cli.Command { opts := &client.UpdateLayerOptions{} - if ctx.IsSet("options") { + if ctx.IsSet(flag.KeyLayerOptions) { var options store.LayerOptions - if err := json.Unmarshal([]byte(ctx.String("options")), &options); err != nil { - return errors.Wrap(err, "could not parse options") + if err := json.Unmarshal([]byte(ctx.String(flag.KeyLayerOptions)), &options); err != nil { + return errors.Wrap(err, "could not parse layer's options") } opts.Options = &options } - if ctx.IsSet("weight") { - weight := ctx.Int("weight") + if ctx.IsSet(flag.KeyLayerWeight) { + weight := ctx.Int(flag.KeyLayerWeight) opts.Weight = &weight } - if ctx.IsSet("enabled") { - enabled := ctx.Bool("enabled") + if ctx.IsSet(flag.KeyLayerEnabled) { + enabled := ctx.Bool(flag.KeyLayerEnabled) opts.Enabled = &enabled } diff --git a/internal/command/admin/proxy/create.go b/internal/command/admin/proxy/create.go index 32b25eb..90f8bd6 100644 --- a/internal/command/admin/proxy/create.go +++ b/internal/command/admin/proxy/create.go @@ -7,6 +7,7 @@ import ( "forge.cadoles.com/cadoles/bouncer/internal/client" "forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr" clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag" + "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" "forge.cadoles.com/cadoles/bouncer/internal/format" "github.com/pkg/errors" @@ -18,17 +19,8 @@ func CreateCommand() *cli.Command { Name: "create", Usage: "Create proxy", Flags: proxyFlag.WithProxyFlags( - &cli.StringFlag{ - Name: "to", - Usage: "Set `TO` as proxy's destination url", - Value: "", - Required: true, - }, - &cli.StringSliceFlag{ - Name: "from", - Usage: "Set `FROM` as proxy's patterns to match incoming requests", - Value: cli.NewStringSlice("*"), - }, + flag.ProxyTo(), + flag.ProxyFrom(), ), Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) @@ -43,12 +35,12 @@ func CreateCommand() *cli.Command { return errors.Wrap(err, "'to' parameter should be a valid url") } - to, err := url.Parse(ctx.String("to")) + to, err := url.Parse(ctx.String(flag.KeyProxyTo)) if err != nil { return errors.Wrap(err, "'to' parameter should be a valid url") } - from := ctx.StringSlice("from") + from := ctx.StringSlice(flag.KeyProxyFrom) client := client.New(baseFlags.ServerURL, client.WithToken(token)) diff --git a/internal/command/admin/proxy/flag/flag.go b/internal/command/admin/proxy/flag/flag.go index f6f562f..932d81e 100644 --- a/internal/command/admin/proxy/flag/flag.go +++ b/internal/command/admin/proxy/flag/flag.go @@ -7,16 +7,9 @@ import ( "github.com/urfave/cli/v2" ) -const FlagProxyName = "proxy-name" - func WithProxyFlags(flags ...cli.Flag) []cli.Flag { baseFlags := clientFlag.ComposeFlags( - &cli.StringFlag{ - Name: FlagProxyName, - Usage: "use `PROXY_NAME` as targeted proxy", - Value: "", - Required: true, - }, + ProxyName(), ) flags = append(flags, baseFlags...) @@ -24,8 +17,58 @@ func WithProxyFlags(flags ...cli.Flag) []cli.Flag { return flags } +const KeyProxyName = "proxy-name" + +func ProxyName() cli.Flag { + return &cli.StringFlag{ + Name: KeyProxyName, + Usage: "use `PROXY_NAME` as targeted proxy", + Value: "", + Required: true, + } +} + +const KeyProxyTo = "proxy-to" + +func ProxyTo() cli.Flag { + return &cli.StringFlag{ + Name: KeyProxyTo, + Usage: "Set `PROXY_TO` as proxy's destination url", + Value: "", + Required: true, + } +} + +const KeyProxyFrom = "proxy-from" + +func ProxyFrom() cli.Flag { + return &cli.StringSliceFlag{ + Name: KeyProxyFrom, + Usage: "Set `PROXY_FROM` as proxy's patterns to match incoming requests", + Value: cli.NewStringSlice("*"), + } +} + +const KeyProxyWeight = "proxy-weight" + +func ProxyWeight() cli.Flag { + return &cli.IntFlag{ + Name: KeyProxyWeight, + Usage: "Set `PROXY_WEIGHT` as proxy's weight", + } +} + +const KeyProxyEnabled = "proxy-enabled" + +func ProxyEnabled() cli.Flag { + return &cli.BoolFlag{ + Name: KeyProxyEnabled, + Usage: "Enable or disable proxy", + } +} + func AssertProxyName(ctx *cli.Context) (store.ProxyName, error) { - rawProxyName := ctx.String(FlagProxyName) + rawProxyName := ctx.String(KeyProxyName) name, err := store.ValidateName(rawProxyName) if err != nil { diff --git a/internal/command/admin/proxy/update.go b/internal/command/admin/proxy/update.go index ff03532..4980bce 100644 --- a/internal/command/admin/proxy/update.go +++ b/internal/command/admin/proxy/update.go @@ -7,6 +7,7 @@ import ( "forge.cadoles.com/cadoles/bouncer/internal/client" "forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr" clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag" + "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag" "forge.cadoles.com/cadoles/bouncer/internal/format" "github.com/pkg/errors" @@ -18,22 +19,10 @@ func UpdateCommand() *cli.Command { Name: "update", Usage: "Update proxy", Flags: proxyFlag.WithProxyFlags( - &cli.StringFlag{ - Name: "to", - Usage: "Set `TO` as proxy's destination url", - }, - &cli.StringSliceFlag{ - Name: "from", - Usage: "Set `FROM` as proxy's patterns to match incoming requests", - }, - &cli.BoolFlag{ - Name: "enabled", - Usage: "Enable or disable proxy", - }, - &cli.IntFlag{ - Name: "weight", - Usage: "Set `WEIGHT` as proxy's weight", - }, + flag.ProxyTo(), + flag.ProxyFrom(), + flag.ProxyEnabled(), + flag.ProxyWeight(), ), Action: func(ctx *cli.Context) error { baseFlags := clientFlag.GetBaseFlags(ctx) @@ -50,27 +39,29 @@ func UpdateCommand() *cli.Command { opts := &client.UpdateProxyOptions{} - if ctx.IsSet("to") { - to := ctx.String("to") + if ctx.IsSet(flag.KeyProxyTo) { + to := ctx.String(flag.KeyProxyTo) if _, err := url.Parse(to); err != nil { - return errors.Wrap(err, "'to' parameter should be a valid url") + return errors.Wrapf(err, "'%s' parameter should be a valid url", flag.KeyProxyTo) } opts.To = &to } - from := ctx.StringSlice("from") - if from != nil { - opts.From = from + if ctx.IsSet(flag.KeyProxyFrom) { + from := ctx.StringSlice(flag.KeyProxyFrom) + if from != nil { + opts.From = from + } } - if ctx.IsSet("weight") { - weight := ctx.Int("weight") + if ctx.IsSet(flag.KeyProxyWeight) { + weight := ctx.Int(flag.KeyProxyWeight) opts.Weight = &weight } - if ctx.IsSet("enabled") { - enabled := ctx.Bool("enabled") + if ctx.IsSet(flag.KeyProxyEnabled) { + enabled := ctx.Bool(flag.KeyProxyEnabled) opts.Enabled = &enabled } diff --git a/internal/command/server/proxy/run.go b/internal/command/server/proxy/run.go index 3806952..b0683d7 100644 --- a/internal/command/server/proxy/run.go +++ b/internal/command/server/proxy/run.go @@ -1,16 +1,11 @@ package proxy import ( - "context" "fmt" "strings" - "time" "forge.cadoles.com/cadoles/bouncer/internal/command/common" - "forge.cadoles.com/cadoles/bouncer/internal/config" "forge.cadoles.com/cadoles/bouncer/internal/proxy" - "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" - "forge.cadoles.com/cadoles/bouncer/internal/queue" "forge.cadoles.com/cadoles/bouncer/internal/setup" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -33,7 +28,7 @@ func RunCommand() *cli.Command { logger.SetFormat(logger.Format(conf.Logger.Format)) logger.SetLevel(logger.Level(conf.Logger.Level)) - layers, err := initDirectorLayers(ctx.Context, conf) + layers, err := setup.CreateLayers(ctx.Context, conf) if err != nil { return errors.Wrap(err, "could not initialize director layers") } @@ -64,36 +59,3 @@ func RunCommand() *cli.Command { }, } } - -func initDirectorLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) { - layers := make([]director.Layer, 0) - - queue, err := initQueueLayer(ctx, conf) - if err != nil { - return nil, errors.Wrap(err, "could not initialize queue layer") - } - - layers = append(layers, queue) - - return layers, nil -} - -func initQueueLayer(ctx context.Context, conf *config.Config) (*queue.Queue, error) { - adapter, err := setup.NewQueueAdapter(ctx, conf.Redis) - if err != nil { - return nil, errors.WithStack(err) - } - - options := []queue.OptionFunc{ - queue.WithTemplateDir(string(conf.Layers.Queue.TemplateDir)), - } - - if conf.Layers.Queue.DefaultKeepAlive != nil { - options = append(options, queue.WithDefaultKeepAlive(time.Duration(*conf.Layers.Queue.DefaultKeepAlive))) - } - - return queue.New( - adapter, - options..., - ), nil -} diff --git a/internal/setup/default_registry.go b/internal/setup/default_registry.go new file mode 100644 index 0000000..ace49c0 --- /dev/null +++ b/internal/setup/default_registry.go @@ -0,0 +1,33 @@ +package setup + +import ( + "context" + + "forge.cadoles.com/cadoles/bouncer/internal/config" + "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" + "forge.cadoles.com/cadoles/bouncer/internal/store" + "github.com/pkg/errors" +) + +var defaultRegistry = NewRegistry() + +func RegisterLayer(layerType store.LayerType, setupFunc LayerSetupFunc) { + defaultRegistry.RegisterLayer(layerType, setupFunc) +} + +func CreateLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) { + layers, err := defaultRegistry.CreateLayers(ctx, conf) + if err != nil { + return nil, errors.WithStack(err) + } + + return layers, nil +} + +func GetLayerTypes() []store.LayerType { + return defaultRegistry.GetLayerTypes() +} + +func LayerTypeExists(layerType store.LayerType) bool { + return defaultRegistry.LayerTypeExists(layerType) +} diff --git a/internal/setup/queue_layer.go b/internal/setup/queue_layer.go new file mode 100644 index 0000000..d34b08c --- /dev/null +++ b/internal/setup/queue_layer.go @@ -0,0 +1,35 @@ +package setup + +import ( + "context" + "time" + + "forge.cadoles.com/cadoles/bouncer/internal/config" + "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" + "forge.cadoles.com/cadoles/bouncer/internal/queue" + "github.com/pkg/errors" +) + +func init() { + RegisterLayer(queue.LayerType, setupQueueLayer) +} + +func setupQueueLayer(ctx context.Context, conf *config.Config) (director.Layer, error) { + adapter, err := NewQueueAdapter(ctx, conf.Redis) + if err != nil { + return nil, errors.WithStack(err) + } + + options := []queue.OptionFunc{ + queue.WithTemplateDir(string(conf.Layers.Queue.TemplateDir)), + } + + if conf.Layers.Queue.DefaultKeepAlive != nil { + options = append(options, queue.WithDefaultKeepAlive(time.Duration(*conf.Layers.Queue.DefaultKeepAlive))) + } + + return queue.New( + adapter, + options..., + ), nil +} diff --git a/internal/setup/registry.go b/internal/setup/registry.go new file mode 100644 index 0000000..7a7a88c --- /dev/null +++ b/internal/setup/registry.go @@ -0,0 +1,57 @@ +package setup + +import ( + "context" + + "forge.cadoles.com/cadoles/bouncer/internal/config" + "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" + "forge.cadoles.com/cadoles/bouncer/internal/store" + "github.com/pkg/errors" +) + +type Registry struct { + layers map[store.LayerType]LayerSetupFunc +} + +type LayerSetupFunc func(context.Context, *config.Config) (director.Layer, error) + +func (r *Registry) RegisterLayer(layerType store.LayerType, layerSetup LayerSetupFunc) { + r.layers[layerType] = layerSetup +} + +func (r *Registry) CreateLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) { + layers := make([]director.Layer, 0, len(r.layers)) + + for layerType, layerSetup := range r.layers { + layer, err := layerSetup(ctx, conf) + if err != nil { + return nil, errors.Wrapf(err, "could not create layer '%s'", layerType) + } + + layers = append(layers, layer) + } + + return layers, nil +} + +func (r *Registry) LayerTypeExists(layerType store.LayerType) bool { + _, exists := r.layers[layerType] + + return exists +} + +func (r *Registry) GetLayerTypes() []store.LayerType { + layerTypes := make([]store.LayerType, 0, len(r.layers)) + + for layerType := range r.layers { + layerTypes = append(layerTypes, layerType) + } + + return layerTypes +} + +func NewRegistry() *Registry { + return &Registry{ + layers: make(map[store.LayerType]LayerSetupFunc), + } +}