Skip to content

Commit

Permalink
feat(cli): refactor list formatter for better resource manager support (
Browse files Browse the repository at this point in the history
  • Loading branch information
schoren committed Jun 28, 2023
1 parent d3bdecd commit e6a6d5e
Show file tree
Hide file tree
Showing 26 changed files with 665 additions and 127 deletions.
149 changes: 146 additions & 3 deletions cli/cmd/config.go
Expand Up @@ -3,13 +3,17 @@ package cmd
import (
"context"
"fmt"
"net/http"
"os"
"time"

"github.com/Jeffail/gabs/v2"
"github.com/kubeshop/tracetest/cli/actions"
"github.com/kubeshop/tracetest/cli/analytics"
"github.com/kubeshop/tracetest/cli/config"
"github.com/kubeshop/tracetest/cli/formatters"
"github.com/kubeshop/tracetest/cli/parameters"
"github.com/kubeshop/tracetest/cli/pkg/resourcemanager"
"github.com/kubeshop/tracetest/cli/utils"
"github.com/spf13/cobra"
"go.uber.org/zap"
Expand Down Expand Up @@ -44,6 +48,8 @@ func SkipVersionMismatchCheck() setupOption {
}
}

var resources = resourcemanager.NewRegistry()

func setupCommand(options ...setupOption) func(cmd *cobra.Command, args []string) {
config := setupConfig{
shouldValidateConfig: true,
Expand All @@ -60,10 +66,146 @@ func setupCommand(options ...setupOption) func(cmd *cobra.Command, args []string
overrideConfig()
setupVersion()

baseOptions := []actions.ResourceArgsOption{actions.WithLogger(cliLogger), actions.WithConfig(cliConfig)}
extraHeaders := http.Header{}
extraHeaders.Set("x-client-id", analytics.ClientID())
extraHeaders.Set("x-source", "cli")

httpClient := resourcemanager.NewHTTPClient(cliConfig.URL(), extraHeaders)

resources.Register(
resourcemanager.NewClient(
httpClient,
"config", "configs",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "ANALYTICS ENABLED", Path: "spec.analyticsEnabled"},
},
},
),
)

// TODO: remove this client from here when we migrate tests to the resource manager
openapiClient := utils.GetAPIClient(cliConfig)
resources.Register(
resourcemanager.NewClient(
httpClient,
"analyzer", "analyzers",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "ENABLED", Path: "spec.enabled"},
{Header: "MINIMUM SCORE", Path: "spec.minimumScore"},
},
},
),
)

resources.Register(
resourcemanager.NewClient(
httpClient,
"pollingprofile", "pollingprofiles",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "STRATEGY", Path: "spec.strategy"},
},
},
),
)

resources.Register(
resourcemanager.NewClient(
httpClient,
"demo", "demos",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "TYPE", Path: "spec.type"},
{Header: "ENABLED", Path: "spec.enabled"},
},
},
),
)

resources.Register(
resourcemanager.NewClient(
httpClient,
"datastore", "datastores",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "DEFAULT", Path: "spec.default"},
},
ItemModifier: func(item *gabs.Container) error {
isDefault := item.Path("spec.default").Data().(bool)
if !isDefault {
item.SetP("", "spec.default")
} else {
item.SetP("*", "spec.default")
}
return nil
},
},
),
)

resources.Register(
resourcemanager.NewClient(
httpClient,
"environment", "environments",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "DESCRIPTION", Path: "spec.description"},
},
},
),
)

resources.Register(
resourcemanager.NewClient(
httpClient,
"transaction", "transactions",
resourcemanager.TableConfig{
Cells: []resourcemanager.TableCellConfig{
{Header: "ID", Path: "spec.id"},
{Header: "NAME", Path: "spec.name"},
{Header: "VERSION", Path: "spec.version"},
{Header: "STEPS", Path: "spec.summary.steps"},
{Header: "RUNS", Path: "spec.summary.runs"},
{Header: "LAST RUN TIME", Path: "spec.summary.lastRun.time"},
{Header: "LAST RUN SUCCESSES", Path: "spec.summary.lastRun.passes"},
{Header: "LAST RUN FAILURES", Path: "spec.summary.lastRun.fails"},
},
ItemModifier: func(item *gabs.Container) error {
// set spec.summary.steps to the number of steps in the transaction
item.SetP(len(item.Path("spec.steps").Children()), "spec.summary.steps")

// if lastRun.time is not empty, show it in a nicer format
lastRunTime := item.Path("spec.summary.lastRun.time").Data().(string)
if lastRunTime != "" {
date, err := time.Parse(time.RFC3339, lastRunTime)
if err != nil {
return fmt.Errorf("failed to parse last run time: %s", err)
}
if date.IsZero() {
item.SetP("", "spec.summary.lastRun.time")
} else {
item.SetP(date.Format(time.DateTime), "spec.summary.lastRun.time")
}
}
return nil
},
},
),
)

baseOptions := []actions.ResourceArgsOption{actions.WithLogger(cliLogger), actions.WithConfig(cliConfig)}

configOptions := append(
baseOptions,
Expand Down Expand Up @@ -113,6 +255,7 @@ func setupCommand(options ...setupOption) func(cmd *cobra.Command, args []string
environmentActions := actions.NewEnvironmentsActions(environmentOptions...)
resourceRegistry.Register(environmentActions)

openapiClient := utils.GetAPIClient(cliConfig)
transactionOptions := append(
baseOptions,
actions.WithClient(utils.GetResourceAPIClient("transactions", cliConfig)),
Expand Down
26 changes: 11 additions & 15 deletions cli/cmd/list_cmd.go
Expand Up @@ -5,13 +5,12 @@ import (
"fmt"
"strings"

"github.com/kubeshop/tracetest/cli/formatters"
"github.com/kubeshop/tracetest/cli/parameters"
"github.com/kubeshop/tracetest/cli/utils"
"github.com/kubeshop/tracetest/cli/pkg/resourcemanager"
"github.com/spf13/cobra"
)

var listParams = &parameters.ListParams{}
var listParams = parameters.ListParams{}

var listCmd = &cobra.Command{
GroupID: cmdGroupResources.ID,
Expand All @@ -23,28 +22,25 @@ var listCmd = &cobra.Command{
resourceType := args[0]
ctx := context.Background()

resourceActions, err := resourceRegistry.Get(resourceType)
resourceClient, err := resources.Get(resourceType)
if err != nil {
return "", err
}

listArgs := utils.ListArgs{
resultFormat, err := resourcemanager.Formats.Get(output)
if err != nil {
return "", err
}

lp := resourcemanager.ListOption{
Take: listParams.Take,
Skip: listParams.Skip,
SortDirection: listParams.SortDirection,
SortBy: listParams.SortBy,
SortDirection: listParams.SortDirection,
All: listParams.All,
}

resource, err := resourceActions.List(ctx, listArgs)
if err != nil {
return "", err
}

resourceFormatter := resourceActions.Formatter()
formatter := formatters.BuildFormatter(output, formatters.Pretty, resourceFormatter)

result, err := formatter.FormatList(resource)
result, err := resourceClient.List(ctx, lp, resultFormat)
if err != nil {
return "", err
}
Expand Down
22 changes: 14 additions & 8 deletions cli/cmd/middleware.go
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"os"

"github.com/kubeshop/tracetest/cli/parameters"
"github.com/spf13/cobra"
)

Expand All @@ -20,7 +19,7 @@ func WithResultHandler(runFn RunFn) CobraRunFn {
fmt.Fprintf(os.Stderr, `
Version
%s
An error ocurred when executing the command
%s
Expand All @@ -35,16 +34,19 @@ An error ocurred when executing the command
}
}

func WithParamsHandler(params ...parameters.Params) MiddlewareWrapper {
func WithParamsHandler(validators ...Validator) MiddlewareWrapper {
return func(runFn RunFn) RunFn {
return func(cmd *cobra.Command, args []string) (string, error) {
errors := parameters.ValidateParams(cmd, args, params...)
errors := make([]error, 0)

for _, validator := range validators {
errors = append(errors, validator.Validate(cmd, args)...)
}

if len(errors) > 0 {
errorText := `The following errors occurred when validating the flags:`
errorText := "The following errors occurred when validating the flags:\n"
for _, err := range errors {
errorText += fmt.Sprintf(`
[%s] %s`, err.Parameter, err.Message)
errorText += err.Error() + "\n"
}

return "", fmt.Errorf(errorText)
Expand All @@ -55,7 +57,11 @@ func WithParamsHandler(params ...parameters.Params) MiddlewareWrapper {
}
}

func WithResourceMiddleware(runFn RunFn, params ...parameters.Params) CobraRunFn {
type Validator interface {
Validate(cmd *cobra.Command, args []string) []error
}

func WithResourceMiddleware(runFn RunFn, params ...Validator) CobraRunFn {
params = append(params, resourceParams)
return WithResultHandler(WithParamsHandler(params...)(runFn))
}
1 change: 1 addition & 0 deletions cli/go.mod
Expand Up @@ -3,6 +3,7 @@ module github.com/kubeshop/tracetest/cli
go 1.20

require (
github.com/Jeffail/gabs/v2 v2.7.0
github.com/alexeyco/simpletable v1.0.0
github.com/compose-spec/compose-go v1.5.1
github.com/cucumber/ci-environment/go v0.0.0-20220915001957-711b1c82415f
Expand Down
2 changes: 2 additions & 0 deletions cli/go.sum
Expand Up @@ -43,6 +43,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
Expand Down
10 changes: 4 additions & 6 deletions cli/parameters/configure.go
Expand Up @@ -11,21 +11,19 @@ type ConfigureParams struct {
Global bool
}

var _ Params = &ConfigureParams{}

func (p *ConfigureParams) Validate(cmd *cobra.Command, args []string) []ParamError {
var errors []ParamError
func (p *ConfigureParams) Validate(cmd *cobra.Command, args []string) []error {
var errors []error

if cmd.Flags().Lookup("endpoint").Changed {
if p.Endpoint == "" {
errors = append(errors, ParamError{
errors = append(errors, paramError{
Parameter: "endpoint",
Message: "endpoint cannot be empty",
})
} else {
_, err := url.Parse(p.Endpoint)
if err != nil {
errors = append(errors, ParamError{
errors = append(errors, paramError{
Parameter: "endpoint",
Message: "endpoint is not a valid URL",
})
Expand Down
12 changes: 5 additions & 7 deletions cli/parameters/installer.go
Expand Up @@ -26,27 +26,25 @@ type InstallerParams struct {
KubernetesContext string
}

var _ Params = &InstallerParams{}

func (p *InstallerParams) Validate(cmd *cobra.Command, args []string) []ParamError {
errors := make([]ParamError, 0)
func (p *InstallerParams) Validate(cmd *cobra.Command, args []string) []paramError {
errors := make([]paramError, 0)

if cmd.Flags().Lookup("run-environment").Changed && slices.Contains(AllowedRunEnvironments, p.RunEnvironment) {
errors = append(errors, ParamError{
errors = append(errors, paramError{
Parameter: "run-environment",
Message: "run-environment must be one of 'none', 'docker' or 'kubernetes'",
})
}

if cmd.Flags().Lookup("mode").Changed && slices.Contains(AllowedInstallationMode, p.InstallationMode) {
errors = append(errors, ParamError{
errors = append(errors, paramError{
Parameter: "mode",
Message: "mode must be one of 'not-chosen', 'with-demo' or 'just-tracetest'",
})
}

if cmd.Flags().Lookup("kubernetes-context").Changed && p.KubernetesContext == "" {
errors = append(errors, ParamError{
errors = append(errors, paramError{
Parameter: "kubernetes-context",
Message: "kubernetes-context cannot be empty",
})
Expand Down
18 changes: 4 additions & 14 deletions cli/parameters/parameters.go
@@ -1,22 +1,12 @@
package parameters

import "github.com/spf13/cobra"
import "fmt"

type ParamError struct {
type paramError struct {
Parameter string
Message string
}

type Params interface {
Validate(cmd *cobra.Command, args []string) []ParamError
}

func ValidateParams(cmd *cobra.Command, args []string, params ...Params) []ParamError {
errors := make([]ParamError, 0)

for _, param := range params {
errors = append(errors, param.Validate(cmd, args)...)
}

return errors
func (pe paramError) Error() string {
return fmt.Sprintf(`[%s] %s`, pe.Parameter, pe.Message)
}

0 comments on commit e6a6d5e

Please sign in to comment.