Skip to content

Commit 7485f81

Browse files
authored
Merge pull request #129 from StackVista/stac-24049
STAC-24049: add crud commands for otel component|relation mappings
2 parents 3fc36f6 + f368b02 commit 7485f81

387 files changed

Lines changed: 3016 additions & 1294 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/dashboard/dashboard_describe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func RunDashboardDescribeCommand(args *DescribeArgs) di.CmdWithApiFn {
7676
} else {
7777
if cli.IsJson() {
7878
cli.Printer.PrintJson(map[string]interface{}{
79-
"data": data,
79+
"data": dashboard,
8080
"format": "json",
8181
})
8282
} else {

cmd/otelcomponentmapping.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ func OtelComponentMappingCommand(deps *di.Deps) *cobra.Command {
1515

1616
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingListCommand(deps))
1717
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingStatusCommand(deps))
18+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingDescribeCommand(deps))
19+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingDeleteCommand(deps))
20+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingEditCommand(deps))
21+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingApplyCommand(deps))
1822

1923
return cmd
2024
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/spf13/cobra"
10+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
11+
"github.com/stackvista/stackstate-cli/internal/common"
12+
"github.com/stackvista/stackstate-cli/internal/di"
13+
"sigs.k8s.io/kustomize/kyaml/yaml"
14+
)
15+
16+
type ApplyArgs struct {
17+
File string
18+
}
19+
20+
func OtelComponentMappingApplyCommand(deps *di.Deps) *cobra.Command {
21+
args := &ApplyArgs{}
22+
cmd := &cobra.Command{
23+
Use: "apply --file FILE",
24+
Short: "Create or edit an OTel Component Mapping from YAML",
25+
Long: "Create or edit a OTel Component Mapping from YAML file.",
26+
Example: `# create a new OTel component mapping from a YAML file
27+
sts otel-component-mapping apply --file new-component-mapping.yaml
28+
29+
# update an existing mapping
30+
sts otel-component-mapping apply --file updated-component-mapping.yaml`,
31+
RunE: deps.CmdRunEWithApi(RunApplyComponentMappingCommand(args)),
32+
}
33+
34+
common.AddRequiredFileFlagVar(cmd, &args.File, "Path to a .yaml file with the mapping definition")
35+
36+
return cmd
37+
}
38+
39+
func RunApplyComponentMappingCommand(args *ApplyArgs) di.CmdWithApiFn {
40+
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
41+
fileBytes, err := os.ReadFile(args.File)
42+
if err != nil {
43+
return common.NewReadFileError(err, args.File)
44+
}
45+
46+
ext := strings.ToLower(filepath.Ext(args.File))
47+
if ext != ".yaml" {
48+
return common.NewCLIArgParseError(fmt.Errorf("unsupported file type: %s. Only .yaml files are supported", ext))
49+
}
50+
51+
return applyYAMLOtelComponentMapping(cli, api, fileBytes)
52+
}
53+
}
54+
55+
func applyYAMLOtelComponentMapping(cli *di.Deps, api *stackstate_api.APIClient, fileBytes []byte) common.CLIError {
56+
var mapping stackstate_api.OtelComponentMapping
57+
if err := yaml.Unmarshal(fileBytes, &mapping); err != nil {
58+
return common.NewCLIArgParseError(fmt.Errorf("failed to parse YAML: %v", err))
59+
}
60+
61+
reqObj := stackstate_api.UpsertOtelComponentMappingsRequest{
62+
Identifier: mapping.Identifier,
63+
Name: mapping.Name,
64+
Description: mapping.Description,
65+
Input: mapping.Input,
66+
Output: mapping.Output,
67+
Vars: mapping.Vars,
68+
ExpireAfter: mapping.ExpireAfter,
69+
}
70+
upserted, resp, err := api.OtelMappingApi.UpsertOtelComponentMappings(cli.Context).UpsertOtelComponentMappingsRequest(reqObj).Execute()
71+
if err != nil {
72+
return common.NewResponseError(err, resp)
73+
}
74+
if cli.IsJson() {
75+
cli.Printer.PrintJson(map[string]interface{}{"otel_component_mapping": upserted})
76+
} else {
77+
cli.Printer.Success(fmt.Sprintf("OTel component mapping upserted successfully! Identifier: %s, Name: %s", mapping.GetIdentifier(), mapping.GetName()))
78+
}
79+
return nil
80+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package otelcomponentmapping_test
2+
3+
import (
4+
"errors"
5+
"os"
6+
"testing"
7+
8+
"github.com/stackvista/stackstate-cli/cmd/otelcomponentmapping"
9+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
10+
"github.com/stackvista/stackstate-cli/internal/di"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"gopkg.in/yaml.v2"
14+
)
15+
16+
func createTempYamlFile(t *testing.T, content string) string {
17+
t.Helper()
18+
tmpfile, err := os.CreateTemp(os.TempDir(), "test_componentmapping_*.yaml")
19+
if err != nil {
20+
panic(err)
21+
}
22+
23+
_, err = tmpfile.Write([]byte(content))
24+
assert.NoError(t, err)
25+
tmpfile.Close()
26+
return tmpfile.Name()
27+
}
28+
29+
func toOtelMappingItem(m stackstate_api.OtelComponentMapping) stackstate_api.OtelMappingItem {
30+
return stackstate_api.OtelMappingItem{
31+
Name: m.Name,
32+
Identifier: &m.Identifier,
33+
}
34+
}
35+
36+
func TestOtelComponentMappingApply_SuccessYaml(t *testing.T) {
37+
cli := di.NewMockDeps(t)
38+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
39+
mapping := stackstate_api.OtelComponentMapping{
40+
Name: "my-mapping",
41+
Identifier: "urn:otel:component:foo",
42+
}
43+
yamlContent, err := yaml.Marshal(mapping)
44+
assert.NoError(t, err)
45+
tmpFile := createTempYamlFile(t, string(yamlContent))
46+
defer os.Remove(tmpFile)
47+
48+
want := toOtelMappingItem(mapping)
49+
cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsResponse.Result = want
50+
51+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", tmpFile)
52+
53+
assert.Len(t, *cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsCalls, 1)
54+
upsertCall := (*cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsCalls)[0]
55+
assert.Equal(t, mapping.Identifier, upsertCall.PupsertOtelComponentMappingsRequest.Identifier)
56+
assert.Equal(t, mapping.Name, upsertCall.PupsertOtelComponentMappingsRequest.Name)
57+
}
58+
59+
func TestOtelComponentMappingApply_SuccessJson(t *testing.T) {
60+
cli := di.NewMockDeps(t)
61+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
62+
mapping := stackstate_api.OtelComponentMapping{
63+
Name: "my-mapping",
64+
Identifier: "urn:otel:component:foo",
65+
}
66+
yamlContent, err := yaml.Marshal(mapping)
67+
assert.NoError(t, err)
68+
tmpFile := createTempYamlFile(t, string(yamlContent))
69+
defer os.Remove(tmpFile)
70+
71+
want := toOtelMappingItem(mapping)
72+
cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsResponse.Result = want
73+
74+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", tmpFile, "-o", "json")
75+
76+
assert.Len(t, *cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsCalls, 1)
77+
78+
output := *cli.MockPrinter.PrintJsonCalls
79+
assert.Len(t, output, 1)
80+
val, ok := output[0]["otel_component_mapping"]
81+
assert.True(t, ok)
82+
actualPtr, ok := val.(*stackstate_api.OtelMappingItem)
83+
if ok {
84+
assert.Equal(t, &want, actualPtr)
85+
} else {
86+
actualVal, ok := val.(stackstate_api.OtelMappingItem)
87+
require.True(t, ok, "PrintJsonCalls type is not OtelMappingItem or *OtelMappingItem")
88+
assert.Equal(t, want, actualVal)
89+
}
90+
}
91+
92+
func TestOtelComponentMappingApply_FileNotFound(t *testing.T) {
93+
cli := di.NewMockDeps(t)
94+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
95+
badPath := "/not/a/real/path.yaml"
96+
require.Panics(t, func() {
97+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", badPath)
98+
}, "expected panic for missing file during apply command execution")
99+
}
100+
101+
func TestOtelComponentMappingApply_WrongExtension(t *testing.T) {
102+
cli := di.NewMockDeps(t)
103+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
104+
tmpFile := createTempYamlFile(t, "irrelevant-content")
105+
newPath := tmpFile + ".txt"
106+
err := os.Rename(tmpFile, newPath)
107+
assert.NoError(t, err)
108+
defer os.Remove(newPath)
109+
require.Panics(t, func() {
110+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", newPath)
111+
}, "expected panic for wrong extension during apply command execution")
112+
}
113+
114+
func TestOtelComponentMappingApply_InvalidYaml(t *testing.T) {
115+
cli := di.NewMockDeps(t)
116+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
117+
tmpFile := createTempYamlFile(t, "not: [valid, yaml,,,")
118+
defer os.Remove(tmpFile)
119+
require.Panics(t, func() {
120+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", tmpFile)
121+
}, "expected panic for invalid YAML during apply command execution")
122+
}
123+
124+
func TestOtelComponentMappingApply_ApiError(t *testing.T) {
125+
cli := di.NewMockDeps(t)
126+
cmd := otelcomponentmapping.OtelComponentMappingApplyCommand(&cli.Deps)
127+
mapping := stackstate_api.OtelComponentMapping{
128+
Name: "my-mapping",
129+
Identifier: "urn:otel:component:foo",
130+
}
131+
yamlContent, err := yaml.Marshal(mapping)
132+
assert.NoError(t, err)
133+
tmpFile := createTempYamlFile(t, string(yamlContent))
134+
defer os.Remove(tmpFile)
135+
136+
cli.MockClient.ApiMocks.OtelMappingApi.UpsertOtelComponentMappingsResponse.Error = errors.New("api failure")
137+
138+
require.Panics(t, func() {
139+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-f", tmpFile)
140+
}, "expected panic for API error during apply command execution")
141+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
8+
"github.com/stackvista/stackstate-cli/internal/common"
9+
"github.com/stackvista/stackstate-cli/internal/di"
10+
)
11+
12+
type DeleteArgs struct {
13+
Identifier string
14+
}
15+
16+
func OtelComponentMappingDeleteCommand(deps *di.Deps) *cobra.Command {
17+
args := &DeleteArgs{}
18+
cmd := &cobra.Command{
19+
Use: "delete --identifier URN",
20+
Short: "Delete an OTel Component Mapping by identifier (URN)",
21+
Long: "Delete an OTel Component Mapping by identifier (URN)",
22+
Example: `# delete a component mapping by identifier
23+
sts otel-component-mapping delete --identifier urn:stackpack:stackpack-name:shared:otel-component-mapping:service`,
24+
RunE: deps.CmdRunEWithApi(RunDeleteComponentMappingCommand(args)),
25+
}
26+
27+
common.AddRequiredIdentifierFlagVar(cmd, &args.Identifier, "Identifier (URN) of the Component Mapping to delete")
28+
29+
return cmd
30+
}
31+
32+
func RunDeleteComponentMappingCommand(args *DeleteArgs) di.CmdWithApiFn {
33+
return func(
34+
cmd *cobra.Command,
35+
cli *di.Deps,
36+
api *stackstate_api.APIClient,
37+
serverInfo *stackstate_api.ServerInfo,
38+
) common.CLIError {
39+
resp, err := api.OtelMappingApi.DeleteOtelComponentMapping(cli.Context, args.Identifier).Execute()
40+
if err != nil {
41+
return common.NewResponseError(err, resp)
42+
}
43+
44+
result := map[string]interface{}{"deleted_identifier": args.Identifier}
45+
if cli.IsJson() {
46+
cli.Printer.PrintJson(result)
47+
} else {
48+
cli.Printer.Success(fmt.Sprintf("OTel Component Mapping deleted: %s", args.Identifier))
49+
}
50+
51+
return nil
52+
}
53+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package otelcomponentmapping_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/cmd/otelcomponentmapping"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestOtelComponentMappingDelete_Success(t *testing.T) {
12+
cli := di.NewMockDeps(t)
13+
cmd := otelcomponentmapping.OtelComponentMappingDeleteCommand(&cli.Deps)
14+
mockUrn := "urn:otel:component:delete-test"
15+
16+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--identifier", mockUrn)
17+
18+
assert.Nil(t, err)
19+
assert.Len(t, *cli.MockPrinter.SuccessCalls, 1)
20+
assert.Contains(t, (*cli.MockPrinter.SuccessCalls)[0], mockUrn)
21+
}
22+
23+
func TestOtelComponentMappingDelete_MissingIdentifier(t *testing.T) {
24+
cli := di.NewMockDeps(t)
25+
cmd := otelcomponentmapping.OtelComponentMappingDeleteCommand(&cli.Deps)
26+
27+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd)
28+
29+
assert.NotNil(t, err)
30+
assert.Contains(t, err.Error(), "required flag(s) \"identifier\" not set")
31+
}
32+
33+
func TestOtelComponentMappingDelete_ApiError(t *testing.T) {
34+
cli := di.NewMockDeps(t)
35+
cmd := otelcomponentmapping.OtelComponentMappingDeleteCommand(&cli.Deps)
36+
mockUrn := "urn:otel:component:delete-error"
37+
38+
cli.MockClient.ApiMocks.OtelMappingApi.DeleteOtelComponentMappingResponse.Error = assert.AnError
39+
40+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--identifier", mockUrn)
41+
assert.NotNil(t, err)
42+
}
43+
44+
func TestOtelComponentMappingDelete_SuccessJsonOutput(t *testing.T) {
45+
cli := di.NewMockDeps(t)
46+
cmd := otelcomponentmapping.OtelComponentMappingDeleteCommand(&cli.Deps)
47+
mockUrn := "urn:otel:component:delete-test-json"
48+
49+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--identifier", mockUrn, "--output", "json")
50+
51+
assert.Nil(t, err)
52+
expected := []map[string]interface{}{
53+
{"deleted_identifier": mockUrn},
54+
}
55+
assert.Equal(t, expected, *cli.MockPrinter.PrintJsonCalls)
56+
assert.False(t, cli.MockPrinter.HasNonJsonCalls)
57+
}

0 commit comments

Comments
 (0)