feat: add layer definition api
This commit is contained in:
56
internal/admin/definition_route.go
Normal file
56
internal/admin/definition_route.go
Normal file
@ -0,0 +1,56 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/setup"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
)
|
||||
|
||||
type QueryLayerDefinitionResponse struct {
|
||||
Definitions []store.LayerDefinition `json:"definitions"`
|
||||
}
|
||||
|
||||
func (s *Server) queryLayerDefinition(w http.ResponseWriter, r *http.Request) {
|
||||
typesFilter, ok := getStringableSliceValues(w, r, "types", nil, transformLayerType)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
existingTypes := setup.GetLayerTypes()
|
||||
|
||||
slices.Sort(existingTypes)
|
||||
|
||||
definitions := make([]store.LayerDefinition, 0, len(existingTypes))
|
||||
|
||||
for _, layerType := range existingTypes {
|
||||
if len(typesFilter) != 0 && !slices.Contains(typesFilter, layerType) {
|
||||
continue
|
||||
}
|
||||
|
||||
schema, err := setup.GetLayerOptionsRawSchema(layerType)
|
||||
if err != nil {
|
||||
logAndCaptureError(r.Context(), "could not retrieve layer options schema", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, api.ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
definitions = append(definitions, store.LayerDefinition{
|
||||
Type: layerType,
|
||||
Options: json.RawMessage(schema),
|
||||
})
|
||||
}
|
||||
|
||||
api.DataResponse(w, http.StatusOK, QueryLayerDefinitionResponse{
|
||||
Definitions: definitions,
|
||||
})
|
||||
}
|
||||
|
||||
func transformLayerType(v string) (store.LayerType, error) {
|
||||
return store.LayerType(v), nil
|
||||
}
|
@ -285,7 +285,7 @@ func getStringSliceValues(w http.ResponseWriter, r *http.Request, param string,
|
||||
return defaultValue, true
|
||||
}
|
||||
|
||||
func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request, param string, defaultValue []T, validate func(string) (T, error)) ([]T, bool) {
|
||||
func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request, param string, defaultValue []T, transform func(string) (T, error)) ([]T, bool) {
|
||||
rawValue := r.URL.Query().Get(param)
|
||||
|
||||
if rawValue != "" {
|
||||
@ -293,7 +293,7 @@ func getStringableSliceValues[T ~string](w http.ResponseWriter, r *http.Request,
|
||||
values := make([]T, 0, len(rawValues))
|
||||
|
||||
for _, rv := range rawValues {
|
||||
v, err := validate(rv)
|
||||
v, err := transform(rv)
|
||||
if err != nil {
|
||||
logAndCaptureError(r.Context(), "could not parse ids slice param", errors.WithStack(err))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, api.ErrCodeMalformedRequest, nil)
|
||||
|
@ -161,6 +161,10 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
jwt.NewAuthenticator(s.publicKeys, string(s.serverConfig.Auth.Issuer), jwt.DefaultAcceptableSkew),
|
||||
))
|
||||
|
||||
r.Route("/definitions", func(r chi.Router) {
|
||||
r.With(assertReadAccess).Get("/layers", s.queryLayerDefinition)
|
||||
})
|
||||
|
||||
r.Route("/proxies", func(r chi.Router) {
|
||||
r.With(assertReadAccess).Get("/", s.queryProxy)
|
||||
r.With(assertWriteAccess).Post("/", s.createProxy)
|
||||
|
61
internal/client/query_layer_definition.go
Normal file
61
internal/client/query_layer_definition.go
Normal file
@ -0,0 +1,61 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type QueryLayerDefinitionOptionFunc func(*QueryLayerDefinitionOptions)
|
||||
|
||||
type QueryLayerDefinitionOptions struct {
|
||||
Options []OptionFunc
|
||||
Types []store.LayerType
|
||||
}
|
||||
|
||||
func WithQueryLayerDefinitionOptions(funcs ...OptionFunc) QueryLayerDefinitionOptionFunc {
|
||||
return func(opts *QueryLayerDefinitionOptions) {
|
||||
opts.Options = funcs
|
||||
}
|
||||
}
|
||||
|
||||
func WithQueryLayerDefinitionTypes(types ...store.LayerType) QueryLayerDefinitionOptionFunc {
|
||||
return func(opts *QueryLayerDefinitionOptions) {
|
||||
opts.Types = types
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) QueryLayerDefinition(ctx context.Context, funcs ...QueryLayerDefinitionOptionFunc) ([]store.LayerDefinition, error) {
|
||||
options := &QueryLayerDefinitionOptions{}
|
||||
for _, fn := range funcs {
|
||||
fn(options)
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
|
||||
if options.Types != nil && len(options.Types) > 0 {
|
||||
query.Set("types", joinSlice(options.Types))
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("/api/v1/definitions/layers?%s", query.Encode())
|
||||
|
||||
response := withResponse[admin.QueryLayerDefinitionResponse]()
|
||||
|
||||
if options.Options == nil {
|
||||
options.Options = make([]OptionFunc, 0)
|
||||
}
|
||||
|
||||
if err := c.apiGet(ctx, path, &response, options.Options...); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if response.Error != nil {
|
||||
return nil, errors.WithStack(response.Error)
|
||||
}
|
||||
|
||||
return response.Data.Definitions, nil
|
||||
}
|
63
internal/command/admin/definition/layer/query.go
Normal file
63
internal/command/admin/definition/layer/query.go
Normal file
@ -0,0 +1,63 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"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/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query layer definitions",
|
||||
Flags: clientFlag.ComposeFlags(
|
||||
&cli.StringSliceFlag{
|
||||
Name: "with-type",
|
||||
Usage: "use `WITH_TYPE` as query filter",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
options := make([]client.QueryLayerDefinitionOptionFunc, 0)
|
||||
|
||||
rawTypes := ctx.StringSlice("with-type")
|
||||
if len(rawTypes) > 0 {
|
||||
layerTypes := func(rawLayerTypes []string) []store.LayerType {
|
||||
layerTypes := make([]store.LayerType, len(rawLayerTypes))
|
||||
for i, layerType := range rawLayerTypes {
|
||||
layerTypes[i] = store.LayerType(layerType)
|
||||
}
|
||||
return layerTypes
|
||||
}(rawTypes)
|
||||
options = append(options, client.WithQueryLayerDefinitionTypes(layerTypes...))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxies, err := client.QueryLayerDefinition(ctx.Context, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerDefinitionHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(proxies)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
15
internal/command/admin/definition/layer/root.go
Normal file
15
internal/command/admin/definition/layer/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "layer",
|
||||
Usage: "Execute actions related to layer definitions",
|
||||
Subcommands: []*cli.Command{
|
||||
QueryCommand(),
|
||||
},
|
||||
}
|
||||
}
|
13
internal/command/admin/definition/layer/util.go
Normal file
13
internal/command/admin/definition/layer/util.go
Normal file
@ -0,0 +1,13 @@
|
||||
package layer
|
||||
|
||||
import "gitlab.com/wpetit/goweb/cli/format"
|
||||
|
||||
func layerDefinitionHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Type", "Type"),
|
||||
format.NewProp("Options", "Options"),
|
||||
},
|
||||
}
|
||||
}
|
16
internal/command/admin/definition/root.go
Normal file
16
internal/command/admin/definition/root.go
Normal file
@ -0,0 +1,16 @@
|
||||
package definition
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/definition/layer"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "definition",
|
||||
Usage: "Execute actions related to definitions",
|
||||
Subcommands: []*cli.Command{
|
||||
layer.Root(),
|
||||
},
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
"gitlab.com/wpetit/goweb/cli/format/table"
|
||||
)
|
||||
|
||||
func ComposeFlags(flags ...cli.Flag) []cli.Flag {
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
"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"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
|
@ -8,10 +8,10 @@ import (
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/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"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/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"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
"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"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
"gitlab.com/wpetit/goweb/cli/format/table"
|
||||
)
|
||||
|
||||
func layerHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
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"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
|
@ -7,9 +7,9 @@ import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
|
@ -6,10 +6,10 @@ 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/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
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"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
"gitlab.com/wpetit/goweb/cli/format"
|
||||
"gitlab.com/wpetit/goweb/cli/format/table"
|
||||
)
|
||||
|
||||
func proxyHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/definition"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -13,6 +14,7 @@ func Root() *cli.Command {
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
layer.Root(),
|
||||
definition.Root(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const Format format.Format = "json"
|
||||
|
||||
func init() {
|
||||
format.Register(Format, NewWriter())
|
||||
}
|
||||
|
||||
type Writer struct{}
|
||||
|
||||
// Format implements format.Writer.
|
||||
func (*Writer) Write(writer io.Writer, hints format.Hints, data ...any) error {
|
||||
encoder := json.NewEncoder(writer)
|
||||
|
||||
if hints.OutputMode == format.OutputModeWide {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter() *Writer {
|
||||
return &Writer{}
|
||||
}
|
||||
|
||||
var _ format.Writer = &Writer{}
|
@ -1,49 +0,0 @@
|
||||
package format
|
||||
|
||||
type PropHintName string
|
||||
|
||||
type PropHintFunc func() (PropHintName, any)
|
||||
|
||||
type Prop struct {
|
||||
name string
|
||||
label string
|
||||
hints map[PropHintName]any
|
||||
}
|
||||
|
||||
func (p *Prop) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
func (p *Prop) Label() string {
|
||||
return p.label
|
||||
}
|
||||
|
||||
func NewProp(name, label string, funcs ...PropHintFunc) Prop {
|
||||
hints := make(map[PropHintName]any)
|
||||
for _, fn := range funcs {
|
||||
name, value := fn()
|
||||
hints[name] = value
|
||||
}
|
||||
|
||||
return Prop{name, label, hints}
|
||||
}
|
||||
|
||||
func WithPropHint(name PropHintName, value any) PropHintFunc {
|
||||
return func() (PropHintName, any) {
|
||||
return name, value
|
||||
}
|
||||
}
|
||||
|
||||
func PropHint[T any](p Prop, name PropHintName, defaultValue T) T {
|
||||
rawValue, exists := p.hints[name]
|
||||
if !exists {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
value, ok := rawValue.(T)
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Format string
|
||||
|
||||
type Registry map[Format]Writer
|
||||
|
||||
var defaultRegistry = Registry{}
|
||||
|
||||
var ErrUnknownFormat = errors.New("unknown format")
|
||||
|
||||
func Write(format Format, writer io.Writer, hints Hints, data ...any) error {
|
||||
formatWriter, exists := defaultRegistry[format]
|
||||
if !exists {
|
||||
return errors.WithStack(ErrUnknownFormat)
|
||||
}
|
||||
|
||||
if hints.OutputMode == "" {
|
||||
hints.OutputMode = OutputModeCompact
|
||||
}
|
||||
|
||||
if err := formatWriter.Write(writer, hints, data...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Available() []Format {
|
||||
formats := make([]Format, 0, len(defaultRegistry))
|
||||
|
||||
for f := range defaultRegistry {
|
||||
formats = append(formats, f)
|
||||
}
|
||||
|
||||
return formats
|
||||
}
|
||||
|
||||
func Register(format Format, writer Writer) {
|
||||
defaultRegistry[format] = writer
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
hintCompactModeMaxColumnWidth format.PropHintName = "compactModeMaxColumnWidth"
|
||||
)
|
||||
|
||||
func WithCompactModeMaxColumnWidth(max int) format.PropHintFunc {
|
||||
return format.WithPropHint(hintCompactModeMaxColumnWidth, max)
|
||||
}
|
||||
|
||||
func getProps(d any) []format.Prop {
|
||||
props := make([]format.Prop, 0)
|
||||
|
||||
v := reflect.Indirect(reflect.ValueOf(d))
|
||||
typeOf := v.Type()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
name := typeOf.Field(i).Name
|
||||
props = append(props, format.NewProp(name, name))
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
func getFieldValue(obj any, name string) string {
|
||||
v := reflect.Indirect(reflect.ValueOf(obj))
|
||||
|
||||
fieldValue := v.FieldByName(name)
|
||||
|
||||
if !fieldValue.IsValid() {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
json, err := json.Marshal(fieldValue.Interface())
|
||||
if err != nil {
|
||||
panic(errors.WithStack(err))
|
||||
}
|
||||
|
||||
return string(json)
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("%v", fieldValue)
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
)
|
||||
|
||||
const Format format.Format = "table"
|
||||
|
||||
const DefaultCompactModeMaxColumnWidth = 30
|
||||
|
||||
func init() {
|
||||
format.Register(Format, NewWriter(DefaultCompactModeMaxColumnWidth))
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
compactModeMaxColumnWidth int
|
||||
}
|
||||
|
||||
// Write implements format.Writer.
|
||||
func (w *Writer) Write(writer io.Writer, hints format.Hints, data ...any) error {
|
||||
t := table.NewWriter()
|
||||
|
||||
t.SetOutputMirror(writer)
|
||||
|
||||
var props []format.Prop
|
||||
|
||||
if hints.Props != nil {
|
||||
props = hints.Props
|
||||
} else {
|
||||
if len(data) > 0 {
|
||||
props = getProps(data[0])
|
||||
} else {
|
||||
props = make([]format.Prop, 0)
|
||||
}
|
||||
}
|
||||
|
||||
labels := table.Row{}
|
||||
|
||||
for _, p := range props {
|
||||
labels = append(labels, p.Label())
|
||||
}
|
||||
|
||||
t.AppendHeader(labels)
|
||||
|
||||
isCompactMode := hints.OutputMode == format.OutputModeCompact
|
||||
|
||||
for _, d := range data {
|
||||
row := table.Row{}
|
||||
|
||||
for _, p := range props {
|
||||
value := getFieldValue(d, p.Name())
|
||||
|
||||
compactModeMaxColumnWidth := format.PropHint(p,
|
||||
hintCompactModeMaxColumnWidth,
|
||||
w.compactModeMaxColumnWidth,
|
||||
)
|
||||
|
||||
if isCompactMode && len(value) > compactModeMaxColumnWidth {
|
||||
value = value[:compactModeMaxColumnWidth] + "..."
|
||||
}
|
||||
|
||||
row = append(row, value)
|
||||
}
|
||||
|
||||
t.AppendRow(row)
|
||||
}
|
||||
|
||||
t.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriter(compactModeMaxColumnWidth int) *Writer {
|
||||
return &Writer{compactModeMaxColumnWidth}
|
||||
}
|
||||
|
||||
var _ format.Writer = &Writer{}
|
@ -1,86 +0,0 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dummyItem struct {
|
||||
MyString string
|
||||
MyInt int
|
||||
MySub subItem
|
||||
}
|
||||
|
||||
type subItem struct {
|
||||
MyBool bool
|
||||
}
|
||||
|
||||
var dummyItems = []any{
|
||||
dummyItem{
|
||||
MyString: "Foo",
|
||||
MyInt: 1,
|
||||
MySub: subItem{
|
||||
MyBool: false,
|
||||
},
|
||||
},
|
||||
dummyItem{
|
||||
MyString: "Bar",
|
||||
MyInt: 0,
|
||||
MySub: subItem{
|
||||
MyBool: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestWriterNoHints(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writer := NewWriter(DefaultCompactModeMaxColumnWidth)
|
||||
|
||||
if err := writer.Write(&buf, format.Hints{}, dummyItems...); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
expected := `+----------+-------+------------------+
|
||||
| MYSTRING | MYINT | MYSUB |
|
||||
+----------+-------+------------------+
|
||||
| Foo | 1 | {"MyBool":false} |
|
||||
| Bar | 0 | {"MyBool":true} |
|
||||
+----------+-------+------------------+`
|
||||
|
||||
if e, g := strings.TrimSpace(expected), strings.TrimSpace(buf.String()); e != g {
|
||||
t.Errorf("buf.String(): expected \n%v\ngot\n%v", e, g)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriterWithPropHints(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
writer := NewWriter(DefaultCompactModeMaxColumnWidth)
|
||||
|
||||
hints := format.Hints{
|
||||
Props: []format.Prop{
|
||||
format.NewProp("MyString", "MyString"),
|
||||
format.NewProp("MyInt", "MyInt"),
|
||||
},
|
||||
}
|
||||
|
||||
if err := writer.Write(&buf, hints, dummyItems...); err != nil {
|
||||
t.Fatalf("%+v", errors.WithStack(err))
|
||||
}
|
||||
|
||||
expected := `+----------+-------+
|
||||
| MYSTRING | MYINT |
|
||||
+----------+-------+
|
||||
| Foo | 1 |
|
||||
| Bar | 0 |
|
||||
+----------+-------+`
|
||||
|
||||
if e, g := strings.TrimSpace(expected), strings.TrimSpace(buf.String()); e != g {
|
||||
t.Errorf("buf.String(): expected \n%v\ngot\n%v", e, g)
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package format
|
||||
|
||||
import "io"
|
||||
|
||||
type OutputMode string
|
||||
|
||||
const (
|
||||
OutputModeWide OutputMode = "wide"
|
||||
OutputModeCompact OutputMode = "compact"
|
||||
)
|
||||
|
||||
type Hints struct {
|
||||
Props []Prop
|
||||
OutputMode OutputMode
|
||||
}
|
||||
|
||||
type Writer interface {
|
||||
Write(writer io.Writer, hints Hints, data ...any) error
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package format
|
||||
|
||||
import (
|
||||
_ "forge.cadoles.com/cadoles/bouncer/internal/format/json"
|
||||
_ "forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
_ "gitlab.com/wpetit/goweb/cli/format/json"
|
||||
_ "gitlab.com/wpetit/goweb/cli/format/table"
|
||||
)
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/authn-options",
|
||||
"title": "Options de configuration commune des layers 'authn-*'",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matchURLs": {
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/authn-oidc-layer-options",
|
||||
"title": "Options de configuration du layer 'authn-oidc'",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"oidc": {
|
||||
|
@ -1,21 +1,29 @@
|
||||
{
|
||||
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/circuitbreaker-layer-options",
|
||||
"title": "Circuit breaker layer options",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matchURLs": {
|
||||
"title": "Liste de filtrage des URLs sur lesquelles le layer est actif",
|
||||
"description": "Par exemple, si vous souhaitez limiter votre layer à l'ensemble d'une section '`/blog`' d'un site, vous pouvez déclarer la valeur `['*/blog*']`. Les autres URLs du site ne seront pas affectées par ce layer.",
|
||||
"default": [
|
||||
"*"
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"authorizedCIDRs": {
|
||||
"title": "Liste des adressages réseau d'origine autorisés (au format CIDR)",
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"templateBlock": {
|
||||
"title": "Nom du bloc au sein du template de la page d'information à rendre",
|
||||
"default": "default",
|
||||
"description": "Voir fichier layers/circuitbreaker/templates/default.gohtml",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -1,16 +1,24 @@
|
||||
{
|
||||
"$id": "https://forge.cadoles.com/cadoles/bouncer/schemas/queue-layer-options",
|
||||
"title": "Queue layer options",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capacity": {
|
||||
"title": "Capacité d'accueil de la file d'attente",
|
||||
"default": 1000,
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"keepAlive": {
|
||||
"title": "Temps de vie d'une session utilisateur sans activité",
|
||||
"description": "Durée sous forme de chaîne de caractères. Voir https://pkg.go.dev/time#ParseDuration pour plus d'informations.",
|
||||
"default": "1m",
|
||||
"type": "string"
|
||||
},
|
||||
"matchURLs": {
|
||||
"title": "Liste de filtrage des URLs sur lesquelles le layer est actif",
|
||||
"description": "Par exemple, si vous souhaitez limiter votre layer à l'ensemble d'une section '`/blog`' d'un site, vous pouvez déclarer la valeur `['*/blog*']`. Les autres URLs du site ne seront pas affectées par ce layer.",
|
||||
"default": [
|
||||
"*"
|
||||
],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
@ -20,9 +20,6 @@ func Extend(base []byte, schema []byte) ([]byte, error) {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
extended["$id"] = extension["$id"]
|
||||
extended["title"] = extension["title"]
|
||||
|
||||
props := extension["properties"].(map[string]any)
|
||||
extendedProps := extended["properties"].(map[string]any)
|
||||
for key, val := range props {
|
||||
|
@ -25,6 +25,15 @@ func GetLayerOptionsSchema(layerType store.LayerType) (*jsonschema.Schema, error
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func GetLayerOptionsRawSchema(layerType store.LayerType) ([]byte, error) {
|
||||
schema, err := defaultRegistry.GetLayerOptionsRawSchema(layerType)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
func GetLayers(ctx context.Context, conf *config.Config) ([]director.Layer, error) {
|
||||
layers, err := defaultRegistry.GetLayers(ctx, conf)
|
||||
if err != nil {
|
||||
|
@ -28,13 +28,22 @@ func (r *Registry) RegisterLayer(layerType store.LayerType, layerSetup LayerSetu
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) GetLayerOptionsSchema(layerType store.LayerType) (*schema.Schema, error) {
|
||||
func (r *Registry) GetLayerOptionsRawSchema(layerType store.LayerType) ([]byte, error) {
|
||||
layerEntry, exists := r.layers[layerType]
|
||||
if !exists {
|
||||
return nil, errors.WithStack(ErrNotFound)
|
||||
}
|
||||
|
||||
schema, err := schema.Parse(layerEntry.rawOptionsSchema)
|
||||
return layerEntry.rawOptionsSchema, nil
|
||||
}
|
||||
|
||||
func (r *Registry) GetLayerOptionsSchema(layerType store.LayerType) (*schema.Schema, error) {
|
||||
rawSchema, err := r.GetLayerOptionsRawSchema(layerType)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
schema, err := schema.Parse(rawSchema)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package store
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
LayerName Name
|
||||
@ -23,3 +26,8 @@ type Layer struct {
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Options LayerOptions `json:"options"`
|
||||
}
|
||||
|
||||
type LayerDefinition struct {
|
||||
Type LayerType `json:"type"`
|
||||
Options json.RawMessage `json:"options"`
|
||||
}
|
||||
|
Reference in New Issue
Block a user