feat: initial commit
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
This commit is contained in:
91
internal/command/admin/apierr/wrap.go
Normal file
91
internal/command/admin/apierr/wrap.go
Normal file
@ -0,0 +1,91 @@
|
||||
package apierr
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
)
|
||||
|
||||
func Wrap(err error) error {
|
||||
apiErr := &api.Error{}
|
||||
if !errors.As(err, &apiErr) {
|
||||
return err
|
||||
}
|
||||
|
||||
switch apiErr.Code {
|
||||
case api.ErrCodeInvalidFieldValue:
|
||||
return wrapInvalidFieldValueErr(apiErr)
|
||||
|
||||
default:
|
||||
return wrapApiErrorWithMessage(apiErr)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapApiErrorWithMessage(err *api.Error) error {
|
||||
data, ok := err.Data.(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
rawMessage, exists := data["message"]
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
message, ok := rawMessage.(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.Wrapf(err, message)
|
||||
}
|
||||
|
||||
func wrapInvalidFieldValueErr(err *api.Error) error {
|
||||
data, ok := err.Data.(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
rawFields, exists := data["Fields"]
|
||||
if !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
fields, ok := rawFields.([]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
field string
|
||||
rule string
|
||||
)
|
||||
|
||||
if len(fields) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
firstField, ok := fields[0].(map[string]any)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
param, ok := firstField["Param"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
tag, ok := firstField["Tag"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
fieldName, ok := firstField["Field"].(string)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
field = fieldName
|
||||
rule = tag + "=" + param
|
||||
|
||||
return errors.Wrapf(err, "server expected field '%s' to match rule '%s'", field, rule)
|
||||
}
|
98
internal/command/admin/flag/flag.go
Normal file
98
internal/command/admin/flag/flag.go
Normal file
@ -0,0 +1,98 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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"
|
||||
)
|
||||
|
||||
func ComposeFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "server",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "use `SERVER` as server url",
|
||||
Value: "http://127.0.0.1:8081",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "format",
|
||||
Aliases: []string{"f"},
|
||||
Usage: fmt.Sprintf("use `FORMAT` as output format (available: %s)", format.Available()),
|
||||
Value: string(table.Format),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "output-mode",
|
||||
Aliases: []string{"m"},
|
||||
Usage: fmt.Sprintf("use `MODE` as output mode (available: %s)", []format.OutputMode{format.OutputModeCompact, format.OutputModeWide}),
|
||||
Value: string(format.OutputModeCompact),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
EnvVars: []string{`BOUNCER_TOKEN`},
|
||||
Usage: "use `TOKEN` as authentication token",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token-file",
|
||||
EnvVars: []string{`BOUNCER_TOKEN_FILE`},
|
||||
Usage: "use `TOKEN_FILE` as file containing the authentication token",
|
||||
Value: ".bouncer-token",
|
||||
TakesFile: true,
|
||||
},
|
||||
}
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
type BaseFlags struct {
|
||||
ServerURL string
|
||||
Format format.Format
|
||||
OutputMode format.OutputMode
|
||||
Token string
|
||||
TokenFile string
|
||||
}
|
||||
|
||||
func GetBaseFlags(ctx *cli.Context) *BaseFlags {
|
||||
serverURL := ctx.String("server")
|
||||
rawFormat := ctx.String("format")
|
||||
rawOutputMode := ctx.String("output-mode")
|
||||
tokenFile := ctx.String("token-file")
|
||||
token := ctx.String("token")
|
||||
|
||||
return &BaseFlags{
|
||||
ServerURL: serverURL,
|
||||
Format: format.Format(rawFormat),
|
||||
OutputMode: format.OutputMode(rawOutputMode),
|
||||
Token: token,
|
||||
TokenFile: tokenFile,
|
||||
}
|
||||
}
|
||||
|
||||
func GetToken(flags *BaseFlags) (string, error) {
|
||||
if flags.Token != "" {
|
||||
return flags.Token, nil
|
||||
}
|
||||
|
||||
if flags.TokenFile == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
rawToken, err := ioutil.ReadFile(flags.TokenFile)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if rawToken == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(rawToken)), nil
|
||||
}
|
11
internal/command/admin/flag/util.go
Normal file
11
internal/command/admin/flag/util.go
Normal file
@ -0,0 +1,11 @@
|
||||
package flag
|
||||
|
||||
func AsAnySlice[T any](src []T) []any {
|
||||
dst := make([]any, len(src))
|
||||
|
||||
for i, s := range src {
|
||||
dst[i] = s
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
72
internal/command/admin/layer/create.go
Normal file
72
internal/command/admin/layer/create.go
Normal file
@ -0,0 +1,72 @@
|
||||
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/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"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "Create layer",
|
||||
Flags: layerFlag.WithLayerCreateFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
layerName, err := flag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerType, err := flag.AssertLayerType(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerOptions, err := flag.AssertLayerOptions(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layer, err := client.CreateLayer(
|
||||
ctx.Context,
|
||||
proxyName,
|
||||
layerName,
|
||||
layerType,
|
||||
layerOptions,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, layer); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
62
internal/command/admin/layer/delete.go
Normal file
62
internal/command/admin/layer/delete.go
Normal file
@ -0,0 +1,62 @@
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete layer",
|
||||
Flags: layerFlag.WithLayerFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layerName, err = client.DeleteLayer(ctx.Context, proxyName, layerName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
|
||||
Name store.LayerName `json:"id"`
|
||||
}{
|
||||
Name: layerName,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
76
internal/command/admin/layer/flag/flag.go
Normal file
76
internal/command/admin/layer/flag/flag.go
Normal file
@ -0,0 +1,76 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy/flag"
|
||||
"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"
|
||||
)
|
||||
|
||||
func WithLayerFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := proxyFlag.WithProxyFlags(
|
||||
&cli.StringFlag{
|
||||
Name: FlagLayerName,
|
||||
Usage: "use `LAYER_NAME` as targeted layer",
|
||||
Value: "",
|
||||
Required: true,
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
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: "{}",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func AssertLayerName(ctx *cli.Context) (store.LayerName, error) {
|
||||
rawLayerName := ctx.String(FlagLayerName)
|
||||
|
||||
name, err := store.ValidateName(rawLayerName)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.LayerName(name), nil
|
||||
}
|
||||
|
||||
func AssertLayerType(ctx *cli.Context) (store.LayerType, error) {
|
||||
rawLayerType := ctx.String(FlagLayerType)
|
||||
|
||||
return store.LayerType(rawLayerType), nil
|
||||
}
|
||||
|
||||
func AssertLayerOptions(ctx *cli.Context) (store.LayerOptions, error) {
|
||||
rawLayerOptions := ctx.String(FlagLayerOptions)
|
||||
|
||||
layerOptions := store.LayerOptions{}
|
||||
|
||||
if err := json.Unmarshal([]byte(rawLayerOptions), &layerOptions); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return layerOptions, nil
|
||||
}
|
55
internal/command/admin/layer/get.go
Normal file
55
internal/command/admin/layer/get.go
Normal file
@ -0,0 +1,55 @@
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get layer",
|
||||
Flags: layerFlag.WithLayerFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
layer, err := client.GetLayer(ctx.Context, proxyName, layerName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, layer); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
69
internal/command/admin/layer/query.go
Normal file
69
internal/command/admin/layer/query.go
Normal file
@ -0,0 +1,69 @@
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query layers",
|
||||
Flags: proxyFlag.WithProxyFlags(
|
||||
&cli.StringSliceFlag{
|
||||
Name: "with-name",
|
||||
Usage: "use `WITH_NAME` 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))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := make([]client.QueryLayerOptionFunc, 0)
|
||||
|
||||
rawNames := ctx.StringSlice("with-name")
|
||||
if rawNames != nil {
|
||||
layerNames := func(names []string) []store.LayerName {
|
||||
layerNames := make([]store.LayerName, len(names))
|
||||
for i, name := range names {
|
||||
layerNames[i] = store.LayerName(name)
|
||||
}
|
||||
return layerNames
|
||||
}(rawNames)
|
||||
options = append(options, client.WithQueryLayerNames(layerNames...))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxies, err := client.QueryLayer(ctx.Context, proxyName, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHeaderHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(proxies)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
19
internal/command/admin/layer/root.go
Normal file
19
internal/command/admin/layer/root.go
Normal file
@ -0,0 +1,19 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "layer",
|
||||
Usage: "Execute actions related to layers",
|
||||
Subcommands: []*cli.Command{
|
||||
CreateCommand(),
|
||||
GetCommand(),
|
||||
QueryCommand(),
|
||||
UpdateCommand(),
|
||||
DeleteCommand(),
|
||||
},
|
||||
}
|
||||
}
|
91
internal/command/admin/layer/update.go
Normal file
91
internal/command/admin/layer/update.go
Normal file
@ -0,0 +1,91 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
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"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &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",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
layerName, err := layerFlag.AssertLayerName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
opts := &client.UpdateLayerOptions{}
|
||||
|
||||
if ctx.IsSet("options") {
|
||||
var options store.LayerOptions
|
||||
if err := json.Unmarshal([]byte(ctx.String("options")), &options); err != nil {
|
||||
return errors.Wrap(err, "could not parse options")
|
||||
}
|
||||
|
||||
opts.Options = &options
|
||||
}
|
||||
|
||||
if ctx.IsSet("weight") {
|
||||
weight := ctx.Int("weight")
|
||||
opts.Weight = &weight
|
||||
}
|
||||
|
||||
if ctx.IsSet("enabled") {
|
||||
enabled := ctx.Bool("enabled")
|
||||
opts.Enabled = &enabled
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.UpdateLayer(ctx.Context, proxyName, layerName, opts)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := layerHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
33
internal/command/admin/layer/util.go
Normal file
33
internal/command/admin/layer/util.go
Normal file
@ -0,0 +1,33 @@
|
||||
package layer
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
)
|
||||
|
||||
func layerHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Type", "Type"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func layerHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Type", "Type"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
format.NewProp("Options", "Options"),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
69
internal/command/admin/proxy/create.go
Normal file
69
internal/command/admin/proxy/create.go
Normal file
@ -0,0 +1,69 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"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"
|
||||
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"
|
||||
)
|
||||
|
||||
func CreateCommand() *cli.Command {
|
||||
return &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("*"),
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
to, err := url.Parse(ctx.String("to"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
from := ctx.StringSlice("from")
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.CreateProxy(ctx.Context, proxyName, to, from)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
56
internal/command/admin/proxy/delete.go
Normal file
56
internal/command/admin/proxy/delete.go
Normal file
@ -0,0 +1,56 @@
|
||||
package proxy
|
||||
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
func DeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "Delete proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxyName, err = client.DeleteProxy(ctx.Context, proxyName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := format.Hints{
|
||||
OutputMode: baseFlags.OutputMode,
|
||||
}
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, struct {
|
||||
Name store.ProxyName `json:"id"`
|
||||
}{
|
||||
Name: proxyName,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
36
internal/command/admin/proxy/flag/flag.go
Normal file
36
internal/command/admin/proxy/flag/flag.go
Normal file
@ -0,0 +1,36 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
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"
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func AssertProxyName(ctx *cli.Context) (store.ProxyName, error) {
|
||||
rawProxyName := ctx.String(FlagProxyName)
|
||||
|
||||
name, err := store.ValidateName(rawProxyName)
|
||||
if err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return store.ProxyName(name), nil
|
||||
}
|
49
internal/command/admin/proxy/get.go
Normal file
49
internal/command/admin/proxy/get.go
Normal file
@ -0,0 +1,49 @@
|
||||
package proxy
|
||||
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
func GetCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "get",
|
||||
Usage: "Get proxy",
|
||||
Flags: proxyFlag.WithProxyFlags(),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.GetProxy(ctx.Context, proxyName)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
63
internal/command/admin/proxy/query.go
Normal file
63
internal/command/admin/proxy/query.go
Normal file
@ -0,0 +1,63 @@
|
||||
package proxy
|
||||
|
||||
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/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func QueryCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "query",
|
||||
Usage: "Query proxies",
|
||||
Flags: clientFlag.ComposeFlags(
|
||||
&cli.Int64SliceFlag{
|
||||
Name: "ids",
|
||||
Usage: "use `IDS` 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.QueryProxyOptionFunc, 0)
|
||||
|
||||
rawNames := ctx.StringSlice("ids")
|
||||
if rawNames != nil {
|
||||
proxyNames := func(names []string) []store.ProxyName {
|
||||
proxyNames := make([]store.ProxyName, len(names))
|
||||
for i, name := range names {
|
||||
proxyNames[i] = store.ProxyName(name)
|
||||
}
|
||||
return proxyNames
|
||||
}(rawNames)
|
||||
options = append(options, client.WithQueryProxyNames(proxyNames...))
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxies, err := client.QueryProxy(ctx.Context, options...)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHeaderHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, clientFlag.AsAnySlice(proxies)...); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
19
internal/command/admin/proxy/root.go
Normal file
19
internal/command/admin/proxy/root.go
Normal file
@ -0,0 +1,19 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "proxy",
|
||||
Usage: "Execute actions related to proxies",
|
||||
Subcommands: []*cli.Command{
|
||||
GetCommand(),
|
||||
CreateCommand(),
|
||||
QueryCommand(),
|
||||
DeleteCommand(),
|
||||
UpdateCommand(),
|
||||
},
|
||||
}
|
||||
}
|
93
internal/command/admin/proxy/update.go
Normal file
93
internal/command/admin/proxy/update.go
Normal file
@ -0,0 +1,93 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"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"
|
||||
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"
|
||||
)
|
||||
|
||||
func UpdateCommand() *cli.Command {
|
||||
return &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",
|
||||
},
|
||||
),
|
||||
Action: func(ctx *cli.Context) error {
|
||||
baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
token, err := clientFlag.GetToken(baseFlags)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
proxyName, err := proxyFlag.AssertProxyName(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
opts := &client.UpdateProxyOptions{}
|
||||
|
||||
if ctx.IsSet("to") {
|
||||
to := ctx.String("to")
|
||||
if _, err := url.Parse(to); err != nil {
|
||||
return errors.Wrap(err, "'to' parameter should be a valid url")
|
||||
}
|
||||
|
||||
opts.To = &to
|
||||
}
|
||||
|
||||
from := ctx.StringSlice("from")
|
||||
if from != nil {
|
||||
opts.From = from
|
||||
}
|
||||
|
||||
if ctx.IsSet("weight") {
|
||||
weight := ctx.Int("weight")
|
||||
opts.Weight = &weight
|
||||
}
|
||||
|
||||
if ctx.IsSet("enabled") {
|
||||
enabled := ctx.Bool("enabled")
|
||||
opts.Enabled = &enabled
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.UpdateProxy(ctx.Context, proxyName, opts)
|
||||
if err != nil {
|
||||
return errors.WithStack(apierr.Wrap(err))
|
||||
}
|
||||
|
||||
hints := proxyHints(baseFlags.OutputMode)
|
||||
|
||||
if err := format.Write(baseFlags.Format, os.Stdout, hints, proxy); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
32
internal/command/admin/proxy/util.go
Normal file
32
internal/command/admin/proxy/util.go
Normal file
@ -0,0 +1,32 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/format/table"
|
||||
)
|
||||
|
||||
func proxyHeaderHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func proxyHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("Name", "Name"),
|
||||
format.NewProp("From", "From"),
|
||||
format.NewProp("To", "To"),
|
||||
format.NewProp("Enabled", "Enabled"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
18
internal/command/admin/root.go
Normal file
18
internal/command/admin/root.go
Normal file
@ -0,0 +1,18 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/layer"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/admin/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Admin related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
layer.Root(),
|
||||
},
|
||||
}
|
||||
}
|
54
internal/command/auth/create_token.go
Normal file
54
internal/command/auth/create_token.go
Normal file
@ -0,0 +1,54 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/auth/jwt"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/jwk"
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func CreateTokenCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create-token",
|
||||
Usage: "Create a new authentication token",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "role",
|
||||
Usage: fmt.Sprintf("associate `ROLE` to the token (available: %v)", []jwt.Role{jwt.RoleReader, jwt.RoleWriter}),
|
||||
Value: string(jwt.RoleReader),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "subject",
|
||||
Usage: "associate `SUBJECT` to the token",
|
||||
Value: fmt.Sprintf("user-%s", shortuuid.New()),
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not load configuration")
|
||||
}
|
||||
|
||||
subject := ctx.String("subject")
|
||||
role := ctx.String("role")
|
||||
|
||||
key, err := jwk.LoadOrGenerate(string(conf.Admin.Auth.PrivateKey), jwk.DefaultKeySize)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
token, err := jwt.GenerateToken(ctx.Context, key, string(conf.Admin.Auth.Issuer), subject, jwt.Role(role))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
fmt.Println(token)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
15
internal/command/auth/root.go
Normal file
15
internal/command/auth/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "auth",
|
||||
Usage: "Authentication related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
CreateTokenCommand(),
|
||||
},
|
||||
}
|
||||
}
|
7
internal/command/common/flags.go
Normal file
7
internal/command/common/flags.go
Normal file
@ -0,0 +1,7 @@
|
||||
package common
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func Flags() []cli.Flag {
|
||||
return []cli.Flag{}
|
||||
}
|
27
internal/command/common/load_config.go
Normal file
27
internal/command/common/load_config.go
Normal file
@ -0,0 +1,27 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func LoadConfig(ctx *cli.Context) (*config.Config, error) {
|
||||
configFile := ctx.String("config")
|
||||
|
||||
var (
|
||||
conf *config.Config
|
||||
err error
|
||||
)
|
||||
|
||||
if configFile != "" {
|
||||
conf, err = config.NewFromFile(configFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not load config file '%s'", configFile)
|
||||
}
|
||||
} else {
|
||||
conf = config.NewDefault()
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
36
internal/command/config/dump.go
Normal file
36
internal/command/config/dump.go
Normal file
@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func Dump() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump the current configuration",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
if err := config.Dump(conf, os.Stdout); err != nil {
|
||||
return errors.Wrap(err, "Could not dump configuration")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
13
internal/command/config/root.go
Normal file
13
internal/command/config/root.go
Normal file
@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Config related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
Dump(),
|
||||
},
|
||||
}
|
||||
}
|
107
internal/command/main.go
Normal file
107
internal/command/main.go
Normal file
@ -0,0 +1,107 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Main(buildDate, projectVersion, gitRef, defaultConfigPath string, commands ...*cli.Command) {
|
||||
ctx := context.Background()
|
||||
|
||||
compiled, err := time.Parse(time.RFC3339, buildDate)
|
||||
if err != nil {
|
||||
panic(errors.Wrapf(err, "could not parse build date '%s'", buildDate))
|
||||
}
|
||||
|
||||
app := &cli.App{
|
||||
Version: fmt.Sprintf("%s (%s, %s)", projectVersion, gitRef, buildDate),
|
||||
Compiled: compiled,
|
||||
Name: "bouncer",
|
||||
Usage: "reverse proxy server with dynamic queuing management",
|
||||
Commands: commands,
|
||||
Before: func(ctx *cli.Context) error {
|
||||
workdir := ctx.String("workdir")
|
||||
// Switch to new working directory if defined
|
||||
if workdir != "" {
|
||||
if err := os.Chdir(workdir); err != nil {
|
||||
return errors.Wrap(err, "could not change working directory")
|
||||
}
|
||||
}
|
||||
|
||||
if err := ctx.Set("projectVersion", projectVersion); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := ctx.Set("gitRef", gitRef); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := ctx.Set("buildDate", buildDate); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "workdir",
|
||||
Value: "",
|
||||
Usage: "The working directory",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "projectVersion",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "gitRef",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "buildDate",
|
||||
Value: "",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
EnvVars: []string{"BOUNCER_DEBUG"},
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
EnvVars: []string{"BOUNCER_CONFIG"},
|
||||
Value: defaultConfigPath,
|
||||
TakesFile: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.ExitErrHandler = func(ctx *cli.Context, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
if !debug {
|
||||
fmt.Printf("[ERROR] %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
if err := app.RunContext(ctx, os.Args); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
15
internal/command/server/admin/root.go
Normal file
15
internal/command/server/admin/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Admin server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
RunCommand(),
|
||||
},
|
||||
}
|
||||
}
|
54
internal/command/server/admin/run.go
Normal file
54
internal/command/server/admin/run.go
Normal file
@ -0,0 +1,54 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func RunCommand() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Run the admin server",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
srv := admin.NewServer(
|
||||
admin.WithServerConfig(conf.Admin),
|
||||
admin.WithRedisConfig(conf.Redis),
|
||||
)
|
||||
|
||||
addrs, srvErrs := srv.Start(ctx.Context)
|
||||
|
||||
select {
|
||||
case addr := <-addrs:
|
||||
url := fmt.Sprintf("http://%s", addr.String())
|
||||
url = strings.Replace(url, "0.0.0.0", "127.0.0.1", 1)
|
||||
|
||||
logger.Info(ctx.Context, "listening", logger.F("url", url))
|
||||
case err = <-srvErrs:
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err = <-srvErrs; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
15
internal/command/server/proxy/root.go
Normal file
15
internal/command/server/proxy/root.go
Normal file
@ -0,0 +1,15 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "proxy",
|
||||
Usage: "Proxy server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
RunCommand(),
|
||||
},
|
||||
}
|
||||
}
|
87
internal/command/server/proxy/run.go
Normal file
87
internal/command/server/proxy/run.go
Normal file
@ -0,0 +1,87 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
func RunCommand() *cli.Command {
|
||||
flags := common.Flags()
|
||||
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Usage: "Run the proxy server",
|
||||
Flags: flags,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
conf, err := common.LoadConfig(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not load configuration")
|
||||
}
|
||||
|
||||
logger.SetFormat(logger.Format(conf.Logger.Format))
|
||||
logger.SetLevel(logger.Level(conf.Logger.Level))
|
||||
|
||||
layers, err := initDirectorLayers(ctx.Context, conf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not initialize director layers")
|
||||
}
|
||||
|
||||
srv := proxy.NewServer(
|
||||
proxy.WithServerConfig(conf.Proxy),
|
||||
proxy.WithRedisConfig(conf.Redis),
|
||||
proxy.WithDirectorLayers(layers...),
|
||||
)
|
||||
|
||||
addrs, srvErrs := srv.Start(ctx.Context)
|
||||
|
||||
select {
|
||||
case addr := <-addrs:
|
||||
url := fmt.Sprintf("http://%s", addr.String())
|
||||
url = strings.Replace(url, "0.0.0.0", "127.0.0.1", 1)
|
||||
|
||||
logger.Info(ctx.Context, "listening", logger.F("url", url))
|
||||
case err = <-srvErrs:
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err = <-srvErrs; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return queue.New(adapter), nil
|
||||
}
|
18
internal/command/server/root.go
Normal file
18
internal/command/server/root.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/server/admin"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/server/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "server",
|
||||
Usage: "Server related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
admin.Root(),
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user