Skip to content

Commit 707f918

Browse files
authored
feature(CLI): Analyzer Resource Integration (#2726)
* feature(CLI): Analyzer Resource Integration * feature(CLI): including e2e tests * feature(CLI): including e2e tests * feature(CLI): including e2e tests * fixing comments * fixing test file names
1 parent 7f1e120 commit 707f918

File tree

19 files changed

+517
-9
lines changed

19 files changed

+517
-9
lines changed

cli/actions/analyzer.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package actions
2+
3+
import (
4+
"context"
5+
6+
"github.com/kubeshop/tracetest/cli/file"
7+
"github.com/kubeshop/tracetest/cli/openapi"
8+
"github.com/kubeshop/tracetest/cli/utils"
9+
"github.com/kubeshop/tracetest/server/model/yaml"
10+
)
11+
12+
type analyzerActions struct {
13+
resourceArgs
14+
}
15+
16+
var _ ResourceActions = &analyzerActions{}
17+
18+
func NewAnalyzerActions(options ...ResourceArgsOption) analyzerActions {
19+
args := NewResourceArgs(options...)
20+
21+
return analyzerActions{
22+
resourceArgs: args,
23+
}
24+
}
25+
26+
func (analyzerActions) FileType() yaml.FileType {
27+
return yaml.FileTypeAnalyzer
28+
}
29+
30+
func (analyzerActions) Name() string {
31+
return "analyzer"
32+
}
33+
34+
func (analyzer analyzerActions) GetID(file *file.File) (string, error) {
35+
resource, err := analyzer.formatter.ToStruct(file)
36+
if err != nil {
37+
return "", err
38+
}
39+
40+
return *resource.(openapi.LinterResource).Spec.Id, nil
41+
}
42+
43+
func (analyzer analyzerActions) Apply(ctx context.Context, fileContent file.File) (result *file.File, err error) {
44+
result, err = analyzer.resourceClient.Update(ctx, fileContent, currentConfigID)
45+
return result, err
46+
}
47+
48+
func (analyzer analyzerActions) Get(ctx context.Context, ID string) (*file.File, error) {
49+
return analyzer.resourceClient.Get(ctx, currentConfigID)
50+
}
51+
52+
func (analyzer analyzerActions) List(ctx context.Context, listArgs utils.ListArgs) (*file.File, error) {
53+
return analyzer.resourceClient.List(ctx, listArgs)
54+
}
55+
56+
func (analyzer analyzerActions) Delete(ctx context.Context, ID string) (string, error) {
57+
return "", ErrNotSupportedResourceAction
58+
}

cli/cmd/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ func setupCommand(options ...setupOption) func(cmd *cobra.Command, args []string
7373
configActions := actions.NewConfigActions(configOptions...)
7474
resourceRegistry.Register(configActions)
7575

76+
analyzerOptions := append(
77+
baseOptions,
78+
actions.WithClient(utils.GetResourceAPIClient("analyzers", cliConfig)),
79+
actions.WithFormatter(formatters.NewAnalyzerFormatter()),
80+
)
81+
analyzerActions := actions.NewAnalyzerActions(analyzerOptions...)
82+
resourceRegistry.Register(analyzerActions)
83+
7684
pollingOptions := append(
7785
baseOptions,
7886
actions.WithClient(utils.GetResourceAPIClient("pollingprofiles", cliConfig)),

cli/formatters/analyzer.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package formatters
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/alexeyco/simpletable"
7+
"github.com/goccy/go-yaml"
8+
"github.com/kubeshop/tracetest/cli/file"
9+
"github.com/kubeshop/tracetest/cli/openapi"
10+
)
11+
12+
type AnalyzerFormatter struct{}
13+
14+
var _ ResourceFormatter = AnalyzerFormatter{}
15+
16+
func NewAnalyzerFormatter() AnalyzerFormatter {
17+
return AnalyzerFormatter{}
18+
}
19+
20+
func (f AnalyzerFormatter) ToTable(file *file.File) (*simpletable.Header, *simpletable.Body, error) {
21+
rawConfig, err := f.ToStruct(file)
22+
if err != nil {
23+
return nil, nil, err
24+
}
25+
26+
linterResource := rawConfig.(openapi.LinterResource)
27+
row, err := f.getTableRow(linterResource)
28+
if err != nil {
29+
return nil, nil, err
30+
}
31+
32+
body := simpletable.Body{}
33+
body.Cells = [][]*simpletable.Cell{row}
34+
35+
return f.getTableHeader(), &body, nil
36+
}
37+
38+
func (f AnalyzerFormatter) ToListTable(file *file.File) (*simpletable.Header, *simpletable.Body, error) {
39+
rawConfigList, err := f.ToListStruct(file)
40+
if err != nil {
41+
return nil, nil, err
42+
}
43+
44+
body := simpletable.Body{}
45+
for _, rawConfig := range rawConfigList {
46+
linterResource := rawConfig.(openapi.LinterResource)
47+
row, err := f.getTableRow(linterResource)
48+
if err != nil {
49+
return nil, nil, err
50+
}
51+
52+
body.Cells = append(body.Cells, row)
53+
}
54+
55+
return f.getTableHeader(), &body, nil
56+
}
57+
58+
func (f AnalyzerFormatter) ToStruct(file *file.File) (interface{}, error) {
59+
var linterResource openapi.LinterResource
60+
err := yaml.Unmarshal([]byte(file.Contents()), &linterResource)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
return linterResource, nil
66+
}
67+
68+
func (f AnalyzerFormatter) ToListStruct(file *file.File) ([]interface{}, error) {
69+
var analyzerList openapi.LinterResourceList
70+
71+
err := yaml.Unmarshal([]byte(file.Contents()), &analyzerList)
72+
if err != nil {
73+
return nil, err
74+
}
75+
76+
items := make([]interface{}, len(analyzerList.Items))
77+
for i, item := range analyzerList.Items {
78+
items[i] = item
79+
}
80+
81+
return items, nil
82+
}
83+
84+
func (f AnalyzerFormatter) getTableHeader() *simpletable.Header {
85+
return &simpletable.Header{
86+
Cells: []*simpletable.Cell{
87+
{Text: "ID"},
88+
{Text: "NAME"},
89+
{Text: "ENABLED"},
90+
{Text: "MINIMUM SCORE"},
91+
},
92+
}
93+
}
94+
95+
func (f AnalyzerFormatter) getTableRow(t openapi.LinterResource) ([]*simpletable.Cell, error) {
96+
return []*simpletable.Cell{
97+
{Text: *t.Spec.Id},
98+
{Text: *t.Spec.Name},
99+
{Text: fmt.Sprintf("%t", *t.Spec.Enabled)},
100+
{Text: fmt.Sprintf("%d", *t.Spec.MinimumScore)},
101+
}, nil
102+
}

cli/parameters/resources.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type ResourceParams struct {
8787
}
8888

8989
var _ Params = &ResourceParams{}
90-
var ValidResources = []string{"config", "datastore", "demo", "environment", "pollingprofile", "transaction"}
90+
var ValidResources = []string{"config", "datastore", "demo", "environment", "pollingprofile", "transaction", "analyzer"}
9191

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

server/model/yaml/file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const (
2020
FileTypeConfig FileType = "Config"
2121
FileTypeDemo FileType = "Demo"
2222
FileTypePollingProfile FileType = "PollingProfile"
23+
FileTypeAnalyzer FileType = "Analyzer"
2324
)
2425

2526
type File struct {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package analyzer
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/kubeshop/tracetest/cli-e2etest/environment"
8+
"github.com/kubeshop/tracetest/cli-e2etest/helpers"
9+
"github.com/kubeshop/tracetest/cli-e2etest/testscenarios/types"
10+
"github.com/kubeshop/tracetest/cli-e2etest/tracetestcli"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestApplyAnalyzer(t *testing.T) {
15+
// instantiate require with testing helper
16+
require := require.New(t)
17+
18+
// setup isolated e2e environment
19+
env := environment.CreateAndStart(t)
20+
defer env.Close(t)
21+
22+
cliConfig := env.GetCLIConfigPath(t)
23+
24+
// Given I am a Tracetest CLI user
25+
// And I have my server recently created
26+
27+
// When I try to set up a new analyzer
28+
// Then it should be applied with success
29+
configPath := env.GetTestResourcePath(t, "new-analyzer")
30+
31+
result := tracetestcli.Exec(t, fmt.Sprintf("apply analyzer --file %s", configPath), tracetestcli.WithCLIConfig(cliConfig))
32+
helpers.RequireExitCodeEqual(t, result, 0)
33+
34+
// When I try to get a analyzer again
35+
// Then it should return the analyzer applied on the last step, with analytics disabled
36+
result = tracetestcli.Exec(t, "get analyzer --id current", tracetestcli.WithCLIConfig(cliConfig))
37+
helpers.RequireExitCodeEqual(t, result, 0)
38+
39+
analyzer := helpers.UnmarshalYAML[types.AnalyzerResource](t, result.StdOut)
40+
require.Equal("Analyzer", analyzer.Type)
41+
require.Equal("current", analyzer.Spec.Id)
42+
require.Equal("analyzer", analyzer.Spec.Name)
43+
require.True(analyzer.Spec.Enabled)
44+
require.Equal(analyzer.Spec.MinimumScore, 95)
45+
require.Len(analyzer.Spec.Plugins, 3)
46+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package analyzer
2+
3+
import (
4+
"testing"
5+
6+
"github.com/kubeshop/tracetest/cli-e2etest/environment"
7+
"github.com/kubeshop/tracetest/cli-e2etest/helpers"
8+
"github.com/kubeshop/tracetest/cli-e2etest/tracetestcli"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestDeleteAnalyzer(t *testing.T) {
13+
// instantiate require with testing helper
14+
require := require.New(t)
15+
16+
// setup isolated e2e environment
17+
env := environment.CreateAndStart(t)
18+
defer env.Close(t)
19+
20+
cliConfig := env.GetCLIConfigPath(t)
21+
22+
// Given I am a Tracetest CLI user
23+
// And I have my server recently created
24+
25+
// When I try to delete the analyzer
26+
// Then it should return a error message, showing that we cannot delete a analyzer
27+
result := tracetestcli.Exec(t, "delete analyzer --id current", tracetestcli.WithCLIConfig(cliConfig))
28+
helpers.RequireExitCodeEqual(t, result, 1)
29+
require.Contains(result.StdErr, "the specified resource type doesn't support the action")
30+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package analyzer
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/kubeshop/tracetest/cli-e2etest/environment"
9+
"github.com/kubeshop/tracetest/cli-e2etest/helpers"
10+
"github.com/kubeshop/tracetest/cli-e2etest/testscenarios/types"
11+
"github.com/kubeshop/tracetest/cli-e2etest/tracetestcli"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func addGetAnalyzerPreReqs(t *testing.T, env environment.Manager) {
16+
cliConfig := env.GetCLIConfigPath(t)
17+
18+
// When I try to set up a analyzer
19+
// Then it should be applied with success
20+
configPath := env.GetTestResourcePath(t, "new-analyzer")
21+
22+
result := tracetestcli.Exec(t, fmt.Sprintf("apply analyzer --file %s", configPath), tracetestcli.WithCLIConfig(cliConfig))
23+
helpers.RequireExitCodeEqual(t, result, 0)
24+
}
25+
26+
func TestGetAnalyzer(t *testing.T) {
27+
// instantiate require with testing helper
28+
require := require.New(t)
29+
30+
env := environment.CreateAndStart(t)
31+
defer env.Close(t)
32+
33+
cliConfig := env.GetCLIConfigPath(t)
34+
35+
t.Run("get with no analyzer initialized", func(t *testing.T) {
36+
// Given I am a Tracetest CLI user
37+
// And I have my server recently created
38+
// And no analyzer previously registered
39+
40+
// When I try to get a analyzer on yaml mode
41+
// Then it should print a YAML with the default analyzer
42+
result := tracetestcli.Exec(t, "get analyzer --id current --output yaml", tracetestcli.WithCLIConfig(cliConfig))
43+
require.Equal(0, result.ExitCode)
44+
45+
analyzer := helpers.UnmarshalYAML[types.AnalyzerResource](t, result.StdOut)
46+
require.Equal("Analyzer", analyzer.Type)
47+
require.Equal("current", analyzer.Spec.Id)
48+
require.Equal("analyzer", analyzer.Spec.Name)
49+
require.True(analyzer.Spec.Enabled)
50+
require.Equal(analyzer.Spec.MinimumScore, 0)
51+
require.Len(analyzer.Spec.Plugins, 3)
52+
})
53+
54+
addGetAnalyzerPreReqs(t, env)
55+
56+
t.Run("get with YAML format", func(t *testing.T) {
57+
// Given I am a Tracetest CLI user
58+
// And I have my server recently created
59+
// And I have a config already set
60+
61+
// When I try to get a config on yaml mode
62+
// Then it should print a YAML
63+
result := tracetestcli.Exec(t, "get analyzer --id current --output yaml", tracetestcli.WithCLIConfig(cliConfig))
64+
require.Equal(0, result.ExitCode)
65+
66+
analyzer := helpers.UnmarshalYAML[types.AnalyzerResource](t, result.StdOut)
67+
require.Equal("Analyzer", analyzer.Type)
68+
require.Equal("current", analyzer.Spec.Id)
69+
require.Equal("analyzer", analyzer.Spec.Name)
70+
require.True(analyzer.Spec.Enabled)
71+
require.Equal(analyzer.Spec.MinimumScore, 95)
72+
require.Len(analyzer.Spec.Plugins, 3)
73+
})
74+
75+
t.Run("get with JSON format", func(t *testing.T) {
76+
// Given I am a Tracetest CLI user
77+
// And I have my server recently created
78+
// And I have a analyzer already set
79+
80+
// When I try to get a analyzer on json mode
81+
// Then it should print a json
82+
result := tracetestcli.Exec(t, "get analyzer --id current --output json", tracetestcli.WithCLIConfig(cliConfig))
83+
helpers.RequireExitCodeEqual(t, result, 0)
84+
85+
analyzer := helpers.UnmarshalJSON[types.AnalyzerResource](t, result.StdOut)
86+
require.Equal("Analyzer", analyzer.Type)
87+
require.Equal("current", analyzer.Spec.Id)
88+
require.Equal("analyzer", analyzer.Spec.Name)
89+
require.True(analyzer.Spec.Enabled)
90+
require.Equal(analyzer.Spec.MinimumScore, 95)
91+
require.Len(analyzer.Spec.Plugins, 3)
92+
})
93+
94+
t.Run("get with pretty format", func(t *testing.T) {
95+
// Given I am a Tracetest CLI user
96+
// And I have my server recently created
97+
// And I have a analyzer already set
98+
99+
// When I try to get a analyzer on pretty mode
100+
// Then it should print a table with 4 lines printed: header, separator, a analyzer item and empty line
101+
result := tracetestcli.Exec(t, "get analyzer --id current --output pretty", tracetestcli.WithCLIConfig(cliConfig))
102+
helpers.RequireExitCodeEqual(t, result, 0)
103+
require.Contains(result.StdOut, "current")
104+
105+
lines := strings.Split(result.StdOut, "\n")
106+
require.Len(lines, 4)
107+
})
108+
}

0 commit comments

Comments
 (0)