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/client/apierr/wrap.go
Normal file
91
internal/command/client/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/client/flag/flag.go
Normal file
98
internal/command/client/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/client/flag/util.go
Normal file
11
internal/command/client/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
|
||||
}
|
62
internal/command/client/proxy/create.go
Normal file
62
internal/command/client/proxy/create.go
Normal file
@ -0,0 +1,62 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/client"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/client/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/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: clientFlag.ComposeFlags(
|
||||
&cli.StringFlag{
|
||||
Name: "to",
|
||||
Usage: "SET `TO` as proxy destination url",
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "from",
|
||||
Usage: "Set `FROM` as 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))
|
||||
}
|
||||
|
||||
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, 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/client/proxy/delete.go
Normal file
56
internal/command/client/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/client/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/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))
|
||||
}
|
||||
|
||||
proxyID, err := proxyFlag.AssertProxyID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxyID, err = client.DeleteProxy(ctx.Context, proxyID)
|
||||
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 {
|
||||
ID store.ProxyID `json:"id"`
|
||||
}{
|
||||
ID: proxyID,
|
||||
}); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
33
internal/command/client/proxy/flag/flag.go
Normal file
33
internal/command/client/proxy/flag/flag.go
Normal file
@ -0,0 +1,33 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/flag"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func WithProxyFlags(flags ...cli.Flag) []cli.Flag {
|
||||
baseFlags := clientFlag.ComposeFlags(
|
||||
&cli.StringFlag{
|
||||
Name: "proxy-id",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "use `PROXY_ID` as targeted proxy",
|
||||
Value: "",
|
||||
},
|
||||
)
|
||||
|
||||
flags = append(flags, baseFlags...)
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func AssertProxyID(ctx *cli.Context) (store.ProxyID, error) {
|
||||
rawProxyID := ctx.String("proxy-id")
|
||||
|
||||
if rawProxyID == "" {
|
||||
return "", errors.New("'proxy-id' cannot be empty")
|
||||
}
|
||||
|
||||
return store.ProxyID(rawProxyID), nil
|
||||
}
|
49
internal/command/client/proxy/get.go
Normal file
49
internal/command/client/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/client/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/flag"
|
||||
proxyFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/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))
|
||||
}
|
||||
|
||||
proxyID, err := proxyFlag.AssertProxyID(ctx)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
proxy, err := client.GetProxy(ctx.Context, proxyID)
|
||||
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/client/proxy/query.go
Normal file
63
internal/command/client/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/client/apierr"
|
||||
clientFlag "forge.cadoles.com/cadoles/bouncer/internal/command/client/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)
|
||||
|
||||
rawIDs := ctx.StringSlice("ids")
|
||||
if rawIDs != nil {
|
||||
proxyIDs := func(ids []string) []store.ProxyID {
|
||||
agentIDs := make([]store.ProxyID, len(ids))
|
||||
for i, id := range ids {
|
||||
agentIDs[i] = store.ProxyID(id)
|
||||
}
|
||||
return agentIDs
|
||||
}(rawIDs)
|
||||
options = append(options, client.WithQueryProxyID(proxyIDs...))
|
||||
}
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
18
internal/command/client/proxy/root.go
Normal file
18
internal/command/client/proxy/root.go
Normal file
@ -0,0 +1,18 @@
|
||||
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(),
|
||||
},
|
||||
}
|
||||
}
|
72
internal/command/client/proxy/update.go
Normal file
72
internal/command/client/proxy/update.go
Normal file
@ -0,0 +1,72 @@
|
||||
package proxy
|
||||
|
||||
// import (
|
||||
// "os"
|
||||
|
||||
// "forge.cadoles.com/Cadoles/emissary/internal/client"
|
||||
// agentFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/agent/flag"
|
||||
// "forge.cadoles.com/Cadoles/emissary/internal/command/api/apierr"
|
||||
// clientFlag "forge.cadoles.com/Cadoles/emissary/internal/command/api/flag"
|
||||
// "forge.cadoles.com/Cadoles/emissary/internal/format"
|
||||
// "github.com/pkg/errors"
|
||||
// "github.com/urfave/cli/v2"
|
||||
// )
|
||||
|
||||
// func UpdateCommand() *cli.Command {
|
||||
// return &cli.Command{
|
||||
// Name: "update",
|
||||
// Usage: "Updata agent",
|
||||
// Flags: agentFlag.WithAgentFlags(
|
||||
// &cli.IntFlag{
|
||||
// Name: "status",
|
||||
// Usage: "Set `STATUS` to selected agent",
|
||||
// Value: -1,
|
||||
// },
|
||||
// &cli.StringFlag{
|
||||
// Name: "label",
|
||||
// Usage: "Set `LABEL` to selected agent",
|
||||
// Value: "",
|
||||
// },
|
||||
// ),
|
||||
// Action: func(ctx *cli.Context) error {
|
||||
// baseFlags := clientFlag.GetBaseFlags(ctx)
|
||||
|
||||
// token, err := clientFlag.GetToken(baseFlags)
|
||||
// if err != nil {
|
||||
// return errors.WithStack(apierr.Wrap(err))
|
||||
// }
|
||||
|
||||
// agentID, err := agentFlag.AssertAgentID(ctx)
|
||||
// if err != nil {
|
||||
// return errors.WithStack(err)
|
||||
// }
|
||||
|
||||
// options := make([]client.UpdateAgentOptionFunc, 0)
|
||||
|
||||
// status := ctx.Int("status")
|
||||
// if status != -1 {
|
||||
// options = append(options, client.WithAgentStatus(status))
|
||||
// }
|
||||
|
||||
// label := ctx.String("label")
|
||||
// if label != "" {
|
||||
// options = append(options, client.WithAgentLabel(label))
|
||||
// }
|
||||
|
||||
// client := client.New(baseFlags.ServerURL, client.WithToken(token))
|
||||
|
||||
// agent, err := client.UpdateAgent(ctx.Context, agentID, options...)
|
||||
// if err != nil {
|
||||
// return errors.WithStack(apierr.Wrap(err))
|
||||
// }
|
||||
|
||||
// hints := agentHints(baseFlags.OutputMode)
|
||||
|
||||
// if err := format.Write(baseFlags.Format, os.Stdout, hints, agent); err != nil {
|
||||
// return errors.WithStack(err)
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
// }
|
31
internal/command/client/proxy/util.go
Normal file
31
internal/command/client/proxy/util.go
Normal file
@ -0,0 +1,31 @@
|
||||
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("ID", "ID", table.WithCompactModeMaxColumnWidth(8)),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func proxyHints(outputMode format.OutputMode) format.Hints {
|
||||
return format.Hints{
|
||||
OutputMode: outputMode,
|
||||
Props: []format.Prop{
|
||||
format.NewProp("ID", "ID", table.WithCompactModeMaxColumnWidth(8)),
|
||||
format.NewProp("From", "From"),
|
||||
format.NewProp("To", "To"),
|
||||
format.NewProp("Weight", "Weight"),
|
||||
format.NewProp("CreatedAt", "CreatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
format.NewProp("UpdatedAt", "UpdatedAt", table.WithCompactModeMaxColumnWidth(20)),
|
||||
},
|
||||
}
|
||||
}
|
16
internal/command/client/root.go
Normal file
16
internal/command/client/root.go
Normal file
@ -0,0 +1,16 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/command/client/proxy"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Root() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "client",
|
||||
Usage: "Admin API related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
proxy.Root(),
|
||||
},
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user