From 57a9c71b48f8f0783d6096dc966fb3baa8553c61 Mon Sep 17 00:00:00 2001 From: bubbajoe Date: Sat, 11 May 2024 22:01:25 +0900 Subject: [PATCH] add tests for dgate cli and dgclient --- ...llection_commands.go => collection_cmd.go} | 2 +- .../{document_commands.go => document_cmd.go} | 2 +- .../{domain_commands.go => domain_cmd.go} | 2 +- .../{module_commands.go => module_cmd.go} | 2 +- ...namespace_commands.go => namespace_cmd.go} | 2 +- .../{route_commands.go => route_cmd.go} | 2 +- cmd/dgate-cli/commands/run_cmd.go | 113 ++++++ cmd/dgate-cli/commands/run_cmd_test.go | 374 ++++++++++++++++++ .../{secret_commands.go => secret_cmd.go} | 2 +- .../{service_commands.go => service_cmd.go} | 2 +- cmd/dgate-cli/main.go | 109 +---- pkg/dgclient/collection_client.go | 17 +- pkg/dgclient/dgclient.go | 131 +++--- pkg/dgclient/document_client.go | 18 +- pkg/dgclient/domain_client.go | 17 +- pkg/dgclient/module_client.go | 17 +- pkg/dgclient/namespace_client.go | 17 +- pkg/dgclient/route_client.go | 17 +- pkg/dgclient/route_client_test.go | 107 +++++ pkg/dgclient/secret_client.go | 17 +- pkg/dgclient/service_client.go | 15 +- 21 files changed, 783 insertions(+), 202 deletions(-) rename cmd/dgate-cli/commands/{collection_commands.go => collection_cmd.go} (96%) rename cmd/dgate-cli/commands/{document_commands.go => document_cmd.go} (97%) rename cmd/dgate-cli/commands/{domain_commands.go => domain_cmd.go} (96%) rename cmd/dgate-cli/commands/{module_commands.go => module_cmd.go} (96%) rename cmd/dgate-cli/commands/{namespace_commands.go => namespace_cmd.go} (96%) rename cmd/dgate-cli/commands/{route_commands.go => route_cmd.go} (96%) create mode 100644 cmd/dgate-cli/commands/run_cmd.go create mode 100644 cmd/dgate-cli/commands/run_cmd_test.go rename cmd/dgate-cli/commands/{secret_commands.go => secret_cmd.go} (96%) rename cmd/dgate-cli/commands/{service_commands.go => service_cmd.go} (96%) create mode 100644 pkg/dgclient/route_client_test.go diff --git a/cmd/dgate-cli/commands/collection_commands.go b/cmd/dgate-cli/commands/collection_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/collection_commands.go rename to cmd/dgate-cli/commands/collection_cmd.go index 37e190b..7f7686d 100644 --- a/cmd/dgate-cli/commands/collection_commands.go +++ b/cmd/dgate-cli/commands/collection_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func CollectionCommand(client *dgclient.DGateClient) *cli.Command { +func CollectionCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "collection", Aliases: []string{"col"}, diff --git a/cmd/dgate-cli/commands/document_commands.go b/cmd/dgate-cli/commands/document_cmd.go similarity index 97% rename from cmd/dgate-cli/commands/document_commands.go rename to cmd/dgate-cli/commands/document_cmd.go index 80e15af..6131d16 100644 --- a/cmd/dgate-cli/commands/document_commands.go +++ b/cmd/dgate-cli/commands/document_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func DocumentCommand(client *dgclient.DGateClient) *cli.Command { +func DocumentCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "document", Aliases: []string{"doc"}, diff --git a/cmd/dgate-cli/commands/domain_commands.go b/cmd/dgate-cli/commands/domain_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/domain_commands.go rename to cmd/dgate-cli/commands/domain_cmd.go index eeaa5ef..b5d41b1 100644 --- a/cmd/dgate-cli/commands/domain_commands.go +++ b/cmd/dgate-cli/commands/domain_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func DomainCommand(client *dgclient.DGateClient) *cli.Command { +func DomainCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "domain", Aliases: []string{"dom"}, diff --git a/cmd/dgate-cli/commands/module_commands.go b/cmd/dgate-cli/commands/module_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/module_commands.go rename to cmd/dgate-cli/commands/module_cmd.go index 9ac6f18..131df3d 100644 --- a/cmd/dgate-cli/commands/module_commands.go +++ b/cmd/dgate-cli/commands/module_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func ModuleCommand(client *dgclient.DGateClient) *cli.Command { +func ModuleCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "module", Aliases: []string{"mod"}, diff --git a/cmd/dgate-cli/commands/namespace_commands.go b/cmd/dgate-cli/commands/namespace_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/namespace_commands.go rename to cmd/dgate-cli/commands/namespace_cmd.go index da45c6f..9db5159 100644 --- a/cmd/dgate-cli/commands/namespace_commands.go +++ b/cmd/dgate-cli/commands/namespace_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func NamespaceCommand(client *dgclient.DGateClient) *cli.Command { +func NamespaceCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "namespace", Aliases: []string{"ns"}, diff --git a/cmd/dgate-cli/commands/route_commands.go b/cmd/dgate-cli/commands/route_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/route_commands.go rename to cmd/dgate-cli/commands/route_cmd.go index 8cdb10b..edbc794 100644 --- a/cmd/dgate-cli/commands/route_commands.go +++ b/cmd/dgate-cli/commands/route_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func RouteCommand(client *dgclient.DGateClient) *cli.Command { +func RouteCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "route", Aliases: []string{"rt"}, diff --git a/cmd/dgate-cli/commands/run_cmd.go b/cmd/dgate-cli/commands/run_cmd.go new file mode 100644 index 0000000..3a3f535 --- /dev/null +++ b/cmd/dgate-cli/commands/run_cmd.go @@ -0,0 +1,113 @@ +package commands + +import ( + "fmt" + "os" + "runtime" + "runtime/debug" + "strings" + + "github.com/dgate-io/dgate/pkg/dgclient" + "github.com/urfave/cli/v2" + "golang.org/x/term" +) + +func Run(client dgclient.DGateClient, version string) { + if buildInfo, ok := debug.ReadBuildInfo(); ok { + bv := buildInfo.Main.Version + if bv != "" && bv != "(devel)" { + version = buildInfo.Main.Version + } + } + + app := &cli.App{ + Name: "dgate-cli", + Usage: "a command line interface for DGate (API Gateway) Admin API", + Version: version, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "admin", + Value: "http://localhost:9080", + EnvVars: []string{"DGATE_ADMIN_API"}, + Usage: "the url for the file client", + }, + // only basic auth support for now + &cli.StringFlag{ + Name: "auth", + Aliases: []string{"a"}, + EnvVars: []string{"DGATE_ADMIN_AUTH"}, + Usage: "basic auth username:password; or just username for password prompt", + }, + &cli.BoolFlag{ + Name: "follow", + DefaultText: "false", + Aliases: []string{"f"}, + EnvVars: []string{"DGATE_FOLLOW_REDIRECTS"}, + Usage: "follows redirects, useful for raft leader changes", + }, + &cli.BoolFlag{ + Name: "verbose", + DefaultText: "false", + Aliases: []string{"V"}, + Usage: "enable verbose logging", + }, + }, + Before: func(ctx *cli.Context) (err error) { + var authOption dgclient.Options = func(dc dgclient.DGateClient) {} + if auth := ctx.String("auth"); strings.Contains(auth, ":") { + pair := strings.SplitN(ctx.String("auth"), ":", 2) + username := pair[0] + password := "" + if len(pair) > 1 { + password = pair[1] + } + authOption = dgclient.WithBasicAuth( + username, password, + ) + } else if auth != "" { + fmt.Printf("password for %s:", auth) + password, err := term.ReadPassword(0) + if err != nil { + return err + } + fmt.Print("\n") + authOption = dgclient.WithBasicAuth( + auth, string(password), + ) + } + return client.Init( + ctx.String("admin"), + authOption, + dgclient.WithFollowRedirect( + ctx.Bool("follow"), + ), + dgclient.WithUserAgent( + "DGate CLI "+version+ + ";os="+runtime.GOOS+ + ";arch="+runtime.GOARCH, + ), + dgclient.WithVerboseLogging( + ctx.Bool("verbose"), + ), + ) + }, + Action: func(ctx *cli.Context) error { + return ctx.App.Command("help").Run(ctx) + }, + Commands: []*cli.Command{ + NamespaceCommand(client), + ServiceCommand(client), + ModuleCommand(client), + RouteCommand(client), + DomainCommand(client), + CollectionCommand(client), + DocumentCommand(client), + SecretCommand(client), + }, + } + + if err := app.Run(os.Args); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/dgate-cli/commands/run_cmd_test.go b/cmd/dgate-cli/commands/run_cmd_test.go new file mode 100644 index 0000000..85cf0b0 --- /dev/null +++ b/cmd/dgate-cli/commands/run_cmd_test.go @@ -0,0 +1,374 @@ +package commands + +import ( + "os" + "strings" + "testing" + + "github.com/dgate-io/dgate/pkg/dgclient" + "github.com/dgate-io/dgate/pkg/spec" + "github.com/stretchr/testify/mock" +) + +const version = "test" + +func TestGenericCommands(t *testing.T) { + stdout := os.Stdout + os.Stdout = os.NewFile(0, os.DevNull) + defer func() { os.Stdout = stdout }() + + resources := []string{ + "namespace", "route", "service", + "module", "domain", "secret", + "collection", "document", + } + actions := []string{ + "get", "list", + "create", "delete", + } + + for _, resource := range resources { + for _, action := range actions { + os.Args = []string{ + "dgate-cli", + "--admin=localhost.com", + resource, action, + } + switch action { + case "delete", "get", "create": + if resource == "document" { + os.Args = append( + os.Args, + "id=test", + ) + } else { + os.Args = append(os.Args, "name=test") + } + } + if resource == "document" { + os.Args = append( + os.Args, + "collection=test", + ) + } + if action == "create" { + switch resource { + case "route": + os.Args = append(os.Args, "paths=/", "methods=GET") + case "service": + os.Args = append(os.Args, "urls=http://localhost.net") + case "module": + os.Args = append(os.Args, "payload=QUJD") + case "domain": + os.Args = append(os.Args, "patterns=*") + case "secret": + os.Args = append(os.Args, "data=123") + case "collection": + os.Args = append(os.Args, "schema:={}") + case "document": + os.Args = append(os.Args, "data:={}") + } + } + mockClient := new(mockDGClient) + funcName := firstUpper(action) + firstUpper(resource) + mockClient.On( + funcName, + mock.Anything, + mock.Anything, + mock.Anything, + ) + mockClient.On("Init", "localhost.com").Return(nil) + Run(mockClient, version) + } + } +} + +func firstUpper(s string) string { + if len(s) <= 1 { + return strings.ToUpper(s) + } + rs := []rune(s) + firstChar := strings.ToUpper(string(rs[0])) + return firstChar + string(rs[1:]) +} + +type mockDGClient struct { + mock.Mock +} + +var _ dgclient.DGateClient = &mockDGClient{} + +func (m *mockDGClient) Init(baseUrl string, opts ...dgclient.Options) error { + args := m.Called(baseUrl) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) BaseUrl() string { + args := m.Called() + return args.String(0) +} + +func (m *mockDGClient) GetRoute(name, namespace string) (*spec.Route, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Route), args.Error(1) +} + +func (m *mockDGClient) CreateRoute(rt *spec.Route) error { + args := m.Called(rt) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteRoute(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) ListRoute(namespace string) ([]*spec.Route, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Route), args.Error(1) +} + +func (m *mockDGClient) GetNamespace(name string) (*spec.Namespace, error) { + args := m.Called(name) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Namespace), args.Error(1) +} + +func (m *mockDGClient) CreateNamespace(ns *spec.Namespace) error { + args := m.Called(ns) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteNamespace(name string) error { + args := m.Called(name) + if len(args) == 0 { + return nil + } + return args.Error(0) +} +func (m *mockDGClient) ListNamespace() ([]*spec.Namespace, error) { + args := m.Called() + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Namespace), args.Error(1) +} + +func (m *mockDGClient) CreateSecret(sec *spec.Secret) error { + args := m.Called(sec) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteSecret(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) GetSecret(name, namespace string) (*spec.Secret, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Secret), args.Error(1) +} + +func (m *mockDGClient) ListSecret(namespace string) ([]*spec.Secret, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Secret), args.Error(1) +} +func (m *mockDGClient) GetService(name string, namespace string) (*spec.Service, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Service), args.Error(1) +} + +func (m *mockDGClient) CreateService(svc *spec.Service) error { + args := m.Called(svc) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteService(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) ListService(namespace string) ([]*spec.Service, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Service), args.Error(1) +} + +func (m *mockDGClient) GetModule(name, namespace string) (*spec.Module, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Module), args.Error(1) +} + +func (m *mockDGClient) CreateModule(mod *spec.Module) error { + args := m.Called(mod) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteModule(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) ListModule(namespace string) ([]*spec.Module, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Module), args.Error(1) +} + +func (m *mockDGClient) CreateDomain(domain *spec.Domain) error { + args := m.Called(domain) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteDomain(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) GetDomain(name, namespace string) (*spec.Domain, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Domain), args.Error(1) +} + +func (m *mockDGClient) ListDomain(namespace string) ([]*spec.Domain, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Domain), args.Error(1) +} + +func (m *mockDGClient) CreateCollection(svc *spec.Collection) error { + args := m.Called(svc) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteCollection(name, namespace string) error { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) ListCollection(namespace string) ([]*spec.Collection, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Collection), args.Error(1) +} + +func (m *mockDGClient) GetCollection(name, namespace string) (*spec.Collection, error) { + args := m.Called(name, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Collection), args.Error(1) +} + +func (m *mockDGClient) CreateDocument(doc *spec.Document) error { + args := m.Called(doc) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) DeleteDocument(id, collection, namespace string) error { + args := m.Called(id, collection, namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} + +func (m *mockDGClient) ListDocument(namespace, collection string) ([]*spec.Document, error) { + args := m.Called(namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].([]*spec.Document), args.Error(1) +} + +func (m *mockDGClient) GetDocument(id, collection, namespace string) (*spec.Document, error) { + args := m.Called(id, collection, namespace) + if len(args) == 0 { + return nil, nil + } + return args[0].(*spec.Document), args.Error(1) +} + +func (m *mockDGClient) DeleteAllDocument(namespace string, collection string) error { + args := m.Called(namespace) + if len(args) == 0 { + return nil + } + return args.Error(0) +} diff --git a/cmd/dgate-cli/commands/secret_commands.go b/cmd/dgate-cli/commands/secret_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/secret_commands.go rename to cmd/dgate-cli/commands/secret_cmd.go index 02afd3e..7fc9b93 100644 --- a/cmd/dgate-cli/commands/secret_commands.go +++ b/cmd/dgate-cli/commands/secret_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func SecretCommand(client *dgclient.DGateClient) *cli.Command { +func SecretCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "secret", Aliases: []string{"sec"}, diff --git a/cmd/dgate-cli/commands/service_commands.go b/cmd/dgate-cli/commands/service_cmd.go similarity index 96% rename from cmd/dgate-cli/commands/service_commands.go rename to cmd/dgate-cli/commands/service_cmd.go index 84bdda8..8e62752 100644 --- a/cmd/dgate-cli/commands/service_commands.go +++ b/cmd/dgate-cli/commands/service_cmd.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func ServiceCommand(client *dgclient.DGateClient) *cli.Command { +func ServiceCommand(client dgclient.DGateClient) *cli.Command { return &cli.Command{ Name: "service", Aliases: []string{"svc"}, diff --git a/cmd/dgate-cli/main.go b/cmd/dgate-cli/main.go index dd5456f..c17bb0b 100644 --- a/cmd/dgate-cli/main.go +++ b/cmd/dgate-cli/main.go @@ -1,118 +1,13 @@ package main import ( - "fmt" - "os" - "runtime" - "runtime/debug" - "strings" - "github.com/dgate-io/dgate/cmd/dgate-cli/commands" "github.com/dgate-io/dgate/pkg/dgclient" - "github.com/urfave/cli/v2" - "golang.org/x/term" ) var version string = "dev" -var client = &dgclient.DGateClient{} - func main() { - if buildInfo, ok := debug.ReadBuildInfo(); ok { - bv := buildInfo.Main.Version - if bv != "" && bv != "(devel)" { - version = buildInfo.Main.Version - } - } - - app := &cli.App{ - Name: "dgate-cli", - Usage: "a command line interface for DGate (API Gateway) Admin API", - Version: version, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "admin", - Value: "http://localhost:9080", - EnvVars: []string{"DGATE_ADMIN_API"}, - Usage: "the url for the file client", - }, - // only basic auth support for now - &cli.StringFlag{ - Name: "auth", - Aliases: []string{"a"}, - EnvVars: []string{"DGATE_ADMIN_AUTH"}, - Usage: "basic auth username:password; or just username for password prompt", - }, - &cli.BoolFlag{ - Name: "follow", - DefaultText: "false", - Aliases: []string{"f"}, - EnvVars: []string{"DGATE_FOLLOW_REDIRECTS"}, - Usage: "follows redirects, useful for raft leader changes", - }, - &cli.BoolFlag{ - Name: "verbose", - DefaultText: "false", - Aliases: []string{"V"}, - Usage: "enable verbose logging", - }, - }, - Before: func(ctx *cli.Context) (err error) { - var authOption dgclient.Options = func(dc *dgclient.DGateClient) {} - if auth := ctx.String("auth"); strings.Contains(auth, ":") { - pair := strings.SplitN(ctx.String("auth"), ":", 2) - username := pair[0] - password := "" - if len(pair) > 1 { - password = pair[1] - } - authOption = dgclient.WithBasicAuth( - username, password, - ) - } else if auth != "" { - fmt.Printf("password for %s:", auth) - password, err := term.ReadPassword(0) - if err != nil { - return err - } - fmt.Print("\n") - authOption = dgclient.WithBasicAuth( - auth, string(password), - ) - } - return client.Init( - ctx.String("admin"), - authOption, - dgclient.WithFollowRedirect( - ctx.Bool("follow"), - ), - dgclient.WithUserAgent( - "DGate CLI "+version+ - ";os="+runtime.GOOS+ - ";arch="+runtime.GOARCH, - ), - dgclient.WithVerboseLogging( - ctx.Bool("verbose"), - ), - ) - }, - Action: func(ctx *cli.Context) error { - return ctx.App.Command("help").Run(ctx) - }, - Commands: []*cli.Command{ - commands.NamespaceCommand(client), - commands.ServiceCommand(client), - commands.ModuleCommand(client), - commands.RouteCommand(client), - commands.DomainCommand(client), - commands.CollectionCommand(client), - commands.DocumentCommand(client), - commands.SecretCommand(client), - }, - } - - if err := app.Run(os.Args); err != nil { - fmt.Println(err) - os.Exit(1) - } + client := dgclient.NewDGateClient() + commands.Run(client, version) } diff --git a/pkg/dgclient/collection_client.go b/pkg/dgclient/collection_client.go index 2e4c6f1..9335442 100644 --- a/pkg/dgclient/collection_client.go +++ b/pkg/dgclient/collection_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetCollection(name, namespace string) (*spec.Collection, error) { +type DGateCollectionClient interface { + GetCollection(name, namespace string) (*spec.Collection, error) + CreateCollection(svc *spec.Collection) error + DeleteCollection(name, namespace string) error + ListCollection(namespace string) ([]*spec.Collection, error) +} + +var _ DGateCollectionClient = &dgateClient{} + +func (d *dgateClient) GetCollection(name, namespace string) (*spec.Collection, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +26,7 @@ func (d *DGateClient) GetCollection(name, namespace string) (*spec.Collection, e return commonGet[spec.Collection](d.client, uri) } -func (d *DGateClient) CreateCollection(svc *spec.Collection) error { +func (d *dgateClient) CreateCollection(svc *spec.Collection) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/collection") if err != nil { return err @@ -25,7 +34,7 @@ func (d *DGateClient) CreateCollection(svc *spec.Collection) error { return commonPut(d.client, uri, svc) } -func (d *DGateClient) DeleteCollection(name, namespace string) error { +func (d *dgateClient) DeleteCollection(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/collection") if err != nil { return err @@ -33,7 +42,7 @@ func (d *DGateClient) DeleteCollection(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListCollection(namespace string) ([]*spec.Collection, error) { +func (d *dgateClient) ListCollection(namespace string) ([]*spec.Collection, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() diff --git a/pkg/dgclient/dgclient.go b/pkg/dgclient/dgclient.go index c8aaa48..f73af5b 100644 --- a/pkg/dgclient/dgclient.go +++ b/pkg/dgclient/dgclient.go @@ -4,26 +4,39 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" ) -type DGateClient struct { +type DGateClient interface { + Init(baseUrl string, opts ...Options) error + BaseUrl() string + + DGateNamespaceClient + DGateModuleClient + DGateRouteClient + DGateServiceClient + DGateDomainClient + DGateCollectionClient + DGateDocumentClient + DGateSecretClient +} + +type dgateClient struct { client *http.Client baseUrl *url.URL } -type Options func(*DGateClient) +type Options func(DGateClient) -func NewDGateClient(baseUrl string, opts ...Options) (*DGateClient, error) { - dgc := &DGateClient{} - err := dgc.Init(baseUrl) - if err != nil { - return nil, err - } - return dgc, nil +func NewDGateClient() DGateClient { + return &dgateClient{} } -func (d *DGateClient) Init(baseUrl string, opts ...Options) error { +func (d *dgateClient) Init(baseUrl string, opts ...Options) error { + if !strings.Contains(baseUrl, "://") { + baseUrl = "http://" + baseUrl + } bUrl, err := url.Parse(baseUrl) if err != nil { return err @@ -44,13 +57,15 @@ func (d *DGateClient) Init(baseUrl string, opts ...Options) error { return nil } -func (d *DGateClient) BaseUrl() string { +func (d *dgateClient) BaseUrl() string { return d.baseUrl.String() } func WithHttpClient(client *http.Client) Options { - return func(d *DGateClient) { - d.client = client + return func(dc DGateClient) { + if d, ok := dc.(*dgateClient); ok { + d.client = client + } } } @@ -85,67 +100,75 @@ func (ct *customTransport) RoundTrip(req *http.Request) (*http.Response, error) func WithBasicAuth(username, password string) Options { if username == "" || password == "" { - return func(d *DGateClient) {} + return nil } - return func(d *DGateClient) { - if d.client.Transport == nil { - d.client.Transport = http.DefaultTransport - } - if ct, ok := d.client.Transport.(*customTransport); ok { - ct.Username = username - ct.Password = password - } else { - d.client.Transport = &customTransport{ - Username: username, - Password: password, - Transport: d.client.Transport, + return func(dc DGateClient) { + if d, ok := dc.(*dgateClient); ok { + if d.client.Transport == nil { + d.client.Transport = http.DefaultTransport + } + if ct, ok := d.client.Transport.(*customTransport); ok { + ct.Username = username + ct.Password = password + } else { + d.client.Transport = &customTransport{ + Username: username, + Password: password, + Transport: d.client.Transport, + } } } } } func WithFollowRedirect(follow bool) Options { - return func(d *DGateClient) { - if follow { - return - } - d.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse + return func(dc DGateClient) { + if d, ok := dc.(*dgateClient); ok { + if follow { + return + } + d.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + } } } } func WithUserAgent(ua string) Options { if ua == "" { - return func(dc *DGateClient) {} + return nil } - return func(d *DGateClient) { - if d.client.Transport == nil { - d.client.Transport = http.DefaultTransport - } - if ct, ok := d.client.Transport.(*customTransport); ok { - ct.UserAgent = ua - ct.Transport = http.DefaultTransport - } else { - d.client.Transport = &customTransport{ - UserAgent: ua, - Transport: d.client.Transport, + return func(dc DGateClient) { + if d, ok := dc.(*dgateClient); ok { + if d.client.Transport == nil { + d.client.Transport = http.DefaultTransport + } + if ct, ok := d.client.Transport.(*customTransport); ok { + ct.UserAgent = ua + ct.Transport = http.DefaultTransport + } else { + d.client.Transport = &customTransport{ + UserAgent: ua, + Transport: d.client.Transport, + } } } } } func WithVerboseLogging(on bool) Options { - return func(d *DGateClient) { - if d.client.Transport == nil { - d.client.Transport = http.DefaultTransport - } - if ct, ok := d.client.Transport.(*customTransport); ok { - ct.VerboseLog = on - } else { - d.client.Transport = &customTransport{ - VerboseLog: on, - Transport: d.client.Transport, + return func(dc DGateClient) { + if d, ok := dc.(*dgateClient); ok { + if d.client.Transport == nil { + d.client.Transport = http.DefaultTransport + } + if ct, ok := d.client.Transport.(*customTransport); ok { + ct.VerboseLog = on + } else { + d.client.Transport = &customTransport{ + VerboseLog: on, + Transport: d.client.Transport, + } } } } diff --git a/pkg/dgclient/document_client.go b/pkg/dgclient/document_client.go index 0be79be..3540555 100644 --- a/pkg/dgclient/document_client.go +++ b/pkg/dgclient/document_client.go @@ -6,7 +6,15 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetDocument(id, namespace, collection string) (*spec.Document, error) { +type DGateDocumentClient interface { + GetDocument(id, namespace, collection string) (*spec.Document, error) + CreateDocument(doc *spec.Document) error + DeleteDocument(id, namespace, collection string) error + DeleteAllDocument(namespace, collection string) error + ListDocument(namespace, collection string) ([]*spec.Document, error) +} + +func (d *dgateClient) GetDocument(id, namespace, collection string) (*spec.Document, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) query.Set("collection", collection) @@ -18,7 +26,7 @@ func (d *DGateClient) GetDocument(id, namespace, collection string) (*spec.Docum return commonGet[spec.Document](d.client, uri) } -func (d *DGateClient) CreateDocument(doc *spec.Document) error { +func (d *dgateClient) CreateDocument(doc *spec.Document) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/document") if err != nil { return err @@ -26,7 +34,7 @@ func (d *DGateClient) CreateDocument(doc *spec.Document) error { return commonPut(d.client, uri, doc) } -func (d *DGateClient) DeleteDocument(id, namespace, collection string) error { +func (d *dgateClient) DeleteDocument(id, namespace, collection string) error { query := d.baseUrl.Query() query.Set("namespace", namespace) query.Set("collection", collection) @@ -37,7 +45,7 @@ func (d *DGateClient) DeleteDocument(id, namespace, collection string) error { return basicDelete(d.client, uri, nil) } -func (d *DGateClient) DeleteAllDocument(namespace, collection string) error { +func (d *dgateClient) DeleteAllDocument(namespace, collection string) error { query := d.baseUrl.Query() query.Set("namespace", namespace) query.Set("collection", collection) @@ -48,7 +56,7 @@ func (d *DGateClient) DeleteAllDocument(namespace, collection string) error { return basicDelete(d.client, uri, nil) } -func (d *DGateClient) ListDocument(namespace, collection string) ([]*spec.Document, error) { +func (d *dgateClient) ListDocument(namespace, collection string) ([]*spec.Document, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) query.Set("collection", collection) diff --git a/pkg/dgclient/domain_client.go b/pkg/dgclient/domain_client.go index 145aa11..04cd50f 100644 --- a/pkg/dgclient/domain_client.go +++ b/pkg/dgclient/domain_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetDomain(name, namespace string) (*spec.Domain, error) { +type DGateDomainClient interface { + GetDomain(name, namespace string) (*spec.Domain, error) + CreateDomain(dom *spec.Domain) error + DeleteDomain(name, namespace string) error + ListDomain(namespace string) ([]*spec.Domain, error) +} + +var _ DGateDomainClient = &dgateClient{} + +func (d *dgateClient) GetDomain(name, namespace string) (*spec.Domain, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +26,7 @@ func (d *DGateClient) GetDomain(name, namespace string) (*spec.Domain, error) { return commonGet[spec.Domain](d.client, uri) } -func (d *DGateClient) CreateDomain(dm *spec.Domain) error { +func (d *dgateClient) CreateDomain(dm *spec.Domain) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/domain") if err != nil { return err @@ -25,7 +34,7 @@ func (d *DGateClient) CreateDomain(dm *spec.Domain) error { return commonPut(d.client, uri, dm) } -func (d *DGateClient) DeleteDomain(name, namespace string) error { +func (d *dgateClient) DeleteDomain(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/domain") if err != nil { return err @@ -33,7 +42,7 @@ func (d *DGateClient) DeleteDomain(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListDomain(namespace string) ([]*spec.Domain, error) { +func (d *dgateClient) ListDomain(namespace string) ([]*spec.Domain, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() diff --git a/pkg/dgclient/module_client.go b/pkg/dgclient/module_client.go index aa93b6a..8eb664f 100644 --- a/pkg/dgclient/module_client.go +++ b/pkg/dgclient/module_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetModule(name, namespace string) (*spec.Module, error) { +type DGateModuleClient interface { + GetModule(name, namespace string) (*spec.Module, error) + CreateModule(mod *spec.Module) error + DeleteModule(name, namespace string) error + ListModule(namespace string) ([]*spec.Module, error) +} + +var _ DGateModuleClient = &dgateClient{} + +func (d *dgateClient) GetModule(name, namespace string) (*spec.Module, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +26,7 @@ func (d *DGateClient) GetModule(name, namespace string) (*spec.Module, error) { return commonGet[spec.Module](d.client, uri) } -func (d *DGateClient) CreateModule(mod *spec.Module) error { +func (d *dgateClient) CreateModule(mod *spec.Module) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/module") if err != nil { return err @@ -25,7 +34,7 @@ func (d *DGateClient) CreateModule(mod *spec.Module) error { return commonPut(d.client, uri, mod) } -func (d *DGateClient) DeleteModule(name, namespace string) error { +func (d *dgateClient) DeleteModule(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/module") if err != nil { return err @@ -33,7 +42,7 @@ func (d *DGateClient) DeleteModule(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListModule(namespace string) ([]*spec.Module, error) { +func (d *dgateClient) ListModule(namespace string) ([]*spec.Module, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() diff --git a/pkg/dgclient/namespace_client.go b/pkg/dgclient/namespace_client.go index ed41387..ccfab3f 100644 --- a/pkg/dgclient/namespace_client.go +++ b/pkg/dgclient/namespace_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetNamespace(name string) (*spec.Namespace, error) { +type DGateNamespaceClient interface { + GetNamespace(name string) (*spec.Namespace, error) + CreateNamespace(ns *spec.Namespace) error + DeleteNamespace(name string) error + ListNamespace() ([]*spec.Namespace, error) +} + +var _ DGateNamespaceClient = &dgateClient{} + +func (d *dgateClient) GetNamespace(name string) (*spec.Namespace, error) { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/namespace", name) if err != nil { return nil, err @@ -14,7 +23,7 @@ func (d *DGateClient) GetNamespace(name string) (*spec.Namespace, error) { return commonGet[spec.Namespace](d.client, uri) } -func (d *DGateClient) CreateNamespace(ns *spec.Namespace) error { +func (d *dgateClient) CreateNamespace(ns *spec.Namespace) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/namespace") if err != nil { return err @@ -22,7 +31,7 @@ func (d *DGateClient) CreateNamespace(ns *spec.Namespace) error { return commonPut(d.client, uri, ns) } -func (d *DGateClient) DeleteNamespace(name string) error { +func (d *dgateClient) DeleteNamespace(name string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/namespace") if err != nil { return err @@ -30,7 +39,7 @@ func (d *DGateClient) DeleteNamespace(name string) error { return commonDelete(d.client, uri, name, "") } -func (d *DGateClient) ListNamespace() ([]*spec.Namespace, error) { +func (d *dgateClient) ListNamespace() ([]*spec.Namespace, error) { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/namespace") if err != nil { return nil, err diff --git a/pkg/dgclient/route_client.go b/pkg/dgclient/route_client.go index d0c38da..a6d0eb1 100644 --- a/pkg/dgclient/route_client.go +++ b/pkg/dgclient/route_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetRoute(name, namespace string) (*spec.Route, error) { +type DGateRouteClient interface { + GetRoute(name, namespace string) (*spec.Route, error) + CreateRoute(rt *spec.Route) error + DeleteRoute(name, namespace string) error + ListRoute(namespace string) ([]*spec.Route, error) +} + +var _ DGateRouteClient = &dgateClient{} + +func (d *dgateClient) GetRoute(name, namespace string) (*spec.Route, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +26,7 @@ func (d *DGateClient) GetRoute(name, namespace string) (*spec.Route, error) { return commonGet[spec.Route](d.client, uri) } -func (d *DGateClient) CreateRoute(rt *spec.Route) error { +func (d *dgateClient) CreateRoute(rt *spec.Route) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/route") if err != nil { return err @@ -25,7 +34,7 @@ func (d *DGateClient) CreateRoute(rt *spec.Route) error { return commonPut(d.client, uri, rt) } -func (d *DGateClient) DeleteRoute(name, namespace string) error { +func (d *dgateClient) DeleteRoute(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/route") if err != nil { return err @@ -33,7 +42,7 @@ func (d *DGateClient) DeleteRoute(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListRoute(namespace string) ([]*spec.Route, error) { +func (d *dgateClient) ListRoute(namespace string) ([]*spec.Route, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() diff --git a/pkg/dgclient/route_client_test.go b/pkg/dgclient/route_client_test.go new file mode 100644 index 0000000..6efff1c --- /dev/null +++ b/pkg/dgclient/route_client_test.go @@ -0,0 +1,107 @@ +package dgclient_test + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/dgate-io/dgate/pkg/dgclient" + "github.com/dgate-io/dgate/pkg/spec" + "github.com/stretchr/testify/assert" +) + +func TestDGClient_GetRoute(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/route/test", r.URL.Path) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(&dgclient.ResponseWrapper[*spec.Route]{ + Data: &spec.Route{ + Name: "test", + Paths: []string{"/"}, + Methods: []string{"GET"}, + }, + }) + })) + client := dgclient.NewDGateClient() + err := client.Init(server.URL) + if err != nil { + t.Fatal(err) + } + + route, err := client.GetRoute("test", "test") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, "test", route.Name) + assert.Equal(t, []string{"/"}, route.Paths) + assert.Equal(t, []string{"GET"}, route.Methods) +} + +func TestDGClient_CreateRoute(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/route", r.URL.Path) + w.WriteHeader(http.StatusCreated) + })) + client := dgclient.NewDGateClient() + err := client.Init(server.URL) + if err != nil { + t.Fatal(err) + } + + err = client.CreateRoute(&spec.Route{ + Name: "test", + Paths: []string{"/"}, + Methods: []string{"GET"}, + }) + if err != nil { + t.Fatal(err) + } +} + +func TestDGClient_DeleteRoute(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/route", r.URL.Path) + w.WriteHeader(http.StatusNoContent) + })) + client := dgclient.NewDGateClient() + err := client.Init(server.URL) + if err != nil { + t.Fatal(err) + } + + err = client.DeleteRoute("test", "test") + if err != nil { + t.Fatal(err) + } +} + +func TestDGClient_ListRoute(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/route", r.URL.Path) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(&dgclient.ResponseWrapper[[]*spec.Route]{ + Data: []*spec.Route{ + { + Name: "test", + Paths: []string{"/"}, + Methods: []string{"GET"}, + }, + }, + }) + })) + client := dgclient.NewDGateClient() + err := client.Init(server.URL) + if err != nil { + t.Fatal(err) + } + + routes, err := client.ListRoute("test") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(routes)) + assert.Equal(t, "test", routes[0].Name) + assert.Equal(t, []string{"/"}, routes[0].Paths) + assert.Equal(t, []string{"GET"}, routes[0].Methods) +} diff --git a/pkg/dgclient/secret_client.go b/pkg/dgclient/secret_client.go index b3d323f..bca98a6 100644 --- a/pkg/dgclient/secret_client.go +++ b/pkg/dgclient/secret_client.go @@ -6,7 +6,16 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetSecret(name, namespace string) (*spec.Secret, error) { +type DGateSecretClient interface { + GetSecret(name, namespace string) (*spec.Secret, error) + CreateSecret(svc *spec.Secret) error + DeleteSecret(name, namespace string) error + ListSecret(namespace string) ([]*spec.Secret, error) +} + +var _ DGateSecretClient = &dgateClient{} + +func (d *dgateClient) GetSecret(name, namespace string) (*spec.Secret, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +26,7 @@ func (d *DGateClient) GetSecret(name, namespace string) (*spec.Secret, error) { return commonGet[spec.Secret](d.client, uri) } -func (d *DGateClient) CreateSecret(sec *spec.Secret) error { +func (d *dgateClient) CreateSecret(sec *spec.Secret) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/secret") if err != nil { return err @@ -25,7 +34,7 @@ func (d *DGateClient) CreateSecret(sec *spec.Secret) error { return commonPut(d.client, uri, sec) } -func (d *DGateClient) DeleteSecret(name, namespace string) error { +func (d *dgateClient) DeleteSecret(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/secret") if err != nil { return err @@ -33,7 +42,7 @@ func (d *DGateClient) DeleteSecret(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListSecret(namespace string) ([]*spec.Secret, error) { +func (d *dgateClient) ListSecret(namespace string) ([]*spec.Secret, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() diff --git a/pkg/dgclient/service_client.go b/pkg/dgclient/service_client.go index 7187cf6..3848285 100644 --- a/pkg/dgclient/service_client.go +++ b/pkg/dgclient/service_client.go @@ -6,7 +6,14 @@ import ( "github.com/dgate-io/dgate/pkg/spec" ) -func (d *DGateClient) GetService(name, namespace string) (*spec.Service, error) { +type DGateServiceClient interface { + GetService(name, namespace string) (*spec.Service, error) + CreateService(svc *spec.Service) error + DeleteService(name, namespace string) error + ListService(namespace string) ([]*spec.Service, error) +} + +func (d *dgateClient) GetService(name, namespace string) (*spec.Service, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode() @@ -17,7 +24,7 @@ func (d *DGateClient) GetService(name, namespace string) (*spec.Service, error) return commonGet[spec.Service](d.client, uri) } -func (d *DGateClient) CreateService(svc *spec.Service) error { +func (d *dgateClient) CreateService(svc *spec.Service) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/service") if err != nil { return err @@ -25,7 +32,7 @@ func (d *DGateClient) CreateService(svc *spec.Service) error { return commonPut(d.client, uri, svc) } -func (d *DGateClient) DeleteService(name, namespace string) error { +func (d *dgateClient) DeleteService(name, namespace string) error { uri, err := url.JoinPath(d.baseUrl.String(), "/api/v1/service") if err != nil { return err @@ -33,7 +40,7 @@ func (d *DGateClient) DeleteService(name, namespace string) error { return commonDelete(d.client, uri, name, namespace) } -func (d *DGateClient) ListService(namespace string) ([]*spec.Service, error) { +func (d *dgateClient) ListService(namespace string) ([]*spec.Service, error) { query := d.baseUrl.Query() query.Set("namespace", namespace) d.baseUrl.RawQuery = query.Encode()