Skip to content

Commit d7211d0

Browse files
Merge pull request #77 from StackVista/STAC-20950-commands-to-manage-ingestion-api-keys
STAC-20950 Commands to manage Ingestion Api Keys
2 parents bb96be5 + 17d44ac commit d7211d0

File tree

9 files changed

+370
-0
lines changed

9 files changed

+370
-0
lines changed

cmd/ingestionapikey.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/stackvista/stackstate-cli/cmd/ingestionapikey"
6+
"github.com/stackvista/stackstate-cli/internal/di"
7+
)
8+
9+
func IngestionApiKeyCommand(deps *di.Deps) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "ingestion-api-key",
12+
Short: "Manage Ingestion API Keys",
13+
Long: "Manage API Keys used by ingestion pipelines, means data (spans, metrics, logs an so on) send by STS Agent, OTel and so on.",
14+
}
15+
16+
cmd.AddCommand(ingestionapikey.CreateCommand(deps))
17+
cmd.AddCommand(ingestionapikey.ListCommand(deps))
18+
cmd.AddCommand(ingestionapikey.DeleteCommand(deps))
19+
return cmd
20+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package ingestionapikey
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/gookit/color"
8+
"github.com/spf13/cobra"
9+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
10+
"github.com/stackvista/stackstate-cli/internal/common"
11+
"github.com/stackvista/stackstate-cli/internal/di"
12+
)
13+
14+
const (
15+
DateFormat = "2006-01-02"
16+
)
17+
18+
type CreateArgs struct {
19+
Name string
20+
Expiration time.Time
21+
Description string
22+
}
23+
24+
func CreateCommand(deps *di.Deps) *cobra.Command {
25+
args := &CreateArgs{}
26+
cmd := &cobra.Command{
27+
Use: "create",
28+
Short: "Create a new Ingestion Api Key",
29+
Long: "Creates a token and then returns it in the response, the token can't be obtained any more after that so store it in the safe space.",
30+
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyGenerationCommand(args)),
31+
}
32+
33+
common.AddRequiredNameFlagVar(cmd, &args.Name, "Name of the API Key")
34+
cmd.Flags().TimeVar(&args.Expiration, "expiration", time.Time{}, []string{DateFormat}, "Expiration date of the API Key")
35+
cmd.Flags().StringVar(&args.Description, "description", "", "Optional description of the API Key")
36+
return cmd
37+
}
38+
39+
func RunIngestionApiKeyGenerationCommand(args *CreateArgs) di.CmdWithApiFn {
40+
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
41+
req := stackstate_api.GenerateIngestionApiKeyRequest{
42+
Name: args.Name,
43+
}
44+
45+
if len(args.Description) > 0 {
46+
req.Description = &args.Description
47+
}
48+
49+
if !args.Expiration.IsZero() {
50+
m := args.Expiration.UnixMilli()
51+
req.Expiration = &m
52+
}
53+
54+
ingestionApiKeyAPI := api.IngestionApiKeyApi.GenerateIngestionApiKey(cli.Context)
55+
56+
serviceToken, resp, err := ingestionApiKeyAPI.GenerateIngestionApiKeyRequest(req).Execute()
57+
if err != nil {
58+
return common.NewResponseError(err, resp)
59+
}
60+
61+
if cli.IsJson() {
62+
cli.Printer.PrintJson(map[string]interface{}{
63+
"ingestion-api-key": serviceToken,
64+
})
65+
} else {
66+
cli.Printer.Success(fmt.Sprintf("Ingestion API Key generated: %s\n", color.White.Render(serviceToken.ApiKey)))
67+
}
68+
69+
return nil
70+
}
71+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package ingestionapikey
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestIngestApiKeyGenerate(t *testing.T) {
12+
cli := di.NewMockDeps(t)
13+
cmd := CreateCommand(&cli.Deps)
14+
15+
cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = stackstate_api.GeneratedIngestionApiKeyResponse{
16+
Name: "test-token",
17+
ApiKey: "test-token-key",
18+
Expiration: int64p(1590105600000),
19+
Description: stringp("test-token-description"),
20+
}
21+
22+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token", "--description", "test-token-description", "--expiration", "2020-05-22")
23+
24+
checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token", stringp("test-token-description"), int64p(1590105600000))
25+
assert.Equal(t, []string{"Ingestion API Key generated: \x1b[37mtest-token-key\x1b[0m\n"}, *cli.MockPrinter.SuccessCalls)
26+
}
27+
28+
func TestIngestApiKeyGenerateOnlyRequriedFlags(t *testing.T) {
29+
cli := di.NewMockDeps(t)
30+
cmd := CreateCommand(&cli.Deps)
31+
32+
cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = stackstate_api.GeneratedIngestionApiKeyResponse{
33+
Name: "test-token2",
34+
ApiKey: "test-token2-key",
35+
}
36+
37+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token2")
38+
39+
checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token2", nil, nil)
40+
assert.Equal(t, []string{"Ingestion API Key generated: \x1b[37mtest-token2-key\x1b[0m\n"}, *cli.MockPrinter.SuccessCalls)
41+
}
42+
43+
func TestIngestApiKeyGenerateJSON(t *testing.T) {
44+
cli := di.NewMockDeps(t)
45+
cmd := CreateCommand(&cli.Deps)
46+
47+
r := &stackstate_api.GeneratedIngestionApiKeyResponse{
48+
Name: "test-token",
49+
ApiKey: "test-token-key",
50+
Expiration: int64p(1590105600000),
51+
Description: stringp("test-token-description"),
52+
}
53+
54+
cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = *r
55+
56+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token", "--description", "test-token-description", "--expiration", "2020-05-22", "-o", "json")
57+
58+
checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token", stringp("test-token-description"), int64p(1590105600000))
59+
assert.Equal(t,
60+
[]map[string]interface{}{{
61+
"ingestion-api-key": r,
62+
}},
63+
*cli.MockPrinter.PrintJsonCalls,
64+
)
65+
assert.False(t, cli.MockPrinter.HasNonJsonCalls)
66+
}
67+
68+
func int64p(i int64) *int64 {
69+
return &i
70+
}
71+
72+
func stringp(i string) *string {
73+
return &i
74+
}
75+
76+
func checkCreateCall(t *testing.T, calls *[]stackstate_api.GenerateIngestionApiKeyCall, name string, description *string, expiration *int64) {
77+
assert.Len(t, *calls, 1)
78+
79+
call := (*calls)[0]
80+
assert.Equal(t, name, call.PgenerateIngestionApiKeyRequest.Name)
81+
82+
if description != nil {
83+
assert.Equal(t, *description, *call.PgenerateIngestionApiKeyRequest.Description)
84+
} else {
85+
assert.Nil(t, call.PgenerateIngestionApiKeyRequest.Description)
86+
}
87+
88+
if expiration != nil {
89+
assert.Equal(t, *expiration, *call.PgenerateIngestionApiKeyRequest.Expiration)
90+
} else {
91+
assert.Nil(t, call.PgenerateIngestionApiKeyRequest.Expiration)
92+
}
93+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ingestionapikey
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+
ID int64
14+
}
15+
16+
func DeleteCommand(deps *di.Deps) *cobra.Command {
17+
args := &DeleteArgs{}
18+
cmd := &cobra.Command{
19+
Use: "delete",
20+
Short: "Delete an Ingestion Api Key",
21+
Long: "Deleted key can't be used by sources, so all ingestion pipelines for that key will fail.",
22+
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyDeleteCommand(args)),
23+
}
24+
25+
common.AddRequiredIDFlagVar(cmd, &args.ID, "ID of the Ingestion Api Key to delete")
26+
27+
return cmd
28+
}
29+
30+
func RunIngestionApiKeyDeleteCommand(args *DeleteArgs) di.CmdWithApiFn {
31+
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
32+
resp, err := api.IngestionApiKeyApi.DeleteIngestionApiKey(cli.Context, args.ID).Execute()
33+
if err != nil {
34+
return common.NewResponseError(err, resp)
35+
}
36+
37+
if cli.IsJson() {
38+
cli.Printer.PrintJson(map[string]interface{}{
39+
"deleted-ingestion-api-key": args.ID,
40+
})
41+
} else {
42+
cli.Printer.Success(fmt.Sprintf("Ingestion Api Key deleted: %d", args.ID))
43+
}
44+
45+
return nil
46+
}
47+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package ingestionapikey
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/internal/di"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestDeleteShouldFailOnNonIntID(t *testing.T) {
11+
cli := di.NewMockDeps(t)
12+
cmd := DeleteCommand(&cli.Deps)
13+
14+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--id", "foo")
15+
16+
assert.NotNil(t, err)
17+
assert.Contains(t, err.Error(), "invalid argument \"foo\" for \"-i, --id\"")
18+
}
19+
20+
func TestDelete(t *testing.T) {
21+
cli := di.NewMockDeps(t)
22+
cmd := DeleteCommand(&cli.Deps)
23+
24+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--id", "1")
25+
26+
assert.Len(t, *cli.MockClient.ApiMocks.IngestionApiKeyApi.DeleteIngestionApiKeyCalls, 1)
27+
assert.Equal(t, int64(1), (*cli.MockClient.ApiMocks.IngestionApiKeyApi.DeleteIngestionApiKeyCalls)[0].PingestionApiKeyId)
28+
29+
assert.Equal(t, []string{"Ingestion Api Key deleted: 1"}, *cli.MockPrinter.SuccessCalls)
30+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package ingestionapikey
2+
3+
import (
4+
"sort"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
9+
"github.com/stackvista/stackstate-cli/internal/common"
10+
"github.com/stackvista/stackstate-cli/internal/di"
11+
"github.com/stackvista/stackstate-cli/internal/printer"
12+
)
13+
14+
func ListCommand(deps *di.Deps) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "list",
17+
Short: "List Ingestion Api Keys",
18+
Long: "Returns only metadata without a key itself.",
19+
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyListCommand),
20+
}
21+
22+
return cmd
23+
}
24+
25+
func RunIngestionApiKeyListCommand(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
26+
ingestionApiKeys, resp, err := api.IngestionApiKeyApi.GetIngestionApiKeys(cli.Context).Execute()
27+
if err != nil {
28+
return common.NewResponseError(err, resp)
29+
}
30+
31+
sort.SliceStable(ingestionApiKeys, func(i, j int) bool {
32+
return ingestionApiKeys[i].Name < ingestionApiKeys[j].Name
33+
})
34+
35+
if cli.IsJson() {
36+
cli.Printer.PrintJson(map[string]interface{}{
37+
"ingestion-api-keys": ingestionApiKeys,
38+
})
39+
} else {
40+
data := make([][]interface{}, 0)
41+
for _, ingestionApiKey := range ingestionApiKeys {
42+
sid := ingestionApiKey.Id
43+
exp := ""
44+
if ingestionApiKey.Expiration != nil {
45+
exp = time.UnixMilli(*ingestionApiKey.Expiration).Format(DateFormat)
46+
}
47+
data = append(data, []interface{}{sid, ingestionApiKey.Name, exp, ingestionApiKey.Description})
48+
}
49+
50+
cli.Printer.Table(printer.TableData{
51+
Header: []string{"id", "name", "expiration", "description"},
52+
Data: data,
53+
MissingTableDataMsg: printer.NotFoundMsg{Types: "ingestion api keys"},
54+
})
55+
}
56+
57+
return nil
58+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package ingestionapikey
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
"github.com/stackvista/stackstate-cli/internal/printer"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestIngestionApiKeyList(t *testing.T) {
13+
cli := di.NewMockDeps(t)
14+
cmd := ListCommand(&cli.Deps)
15+
key1desc := "main key"
16+
17+
cli.MockClient.ApiMocks.IngestionApiKeyApi.GetIngestionApiKeysResponse.Result = []stackstate_api.IngestionApiKey{
18+
{
19+
Id: 1,
20+
Name: "key1",
21+
Description: &key1desc,
22+
Expiration: int64p(1590105600000),
23+
},
24+
{
25+
Id: 2,
26+
Name: "key2",
27+
Description: nil,
28+
Expiration: nil,
29+
},
30+
}
31+
32+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd)
33+
34+
tableData := []printer.TableData{
35+
{
36+
Header: []string{"id", "name", "expiration", "description"},
37+
Data: [][]interface{}{
38+
{int64(1), "key1", "2020-05-22", &key1desc},
39+
{int64(2), "key2", "", (*string)(nil)},
40+
},
41+
MissingTableDataMsg: printer.NotFoundMsg{Types: "ingestion api keys"},
42+
},
43+
}
44+
45+
assert.Equal(t, tableData, *cli.MockPrinter.TableCalls)
46+
}

cmd/sts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func STSCommand(cli *di.Deps) *cobra.Command {
2828
cmd.AddCommand(RbacCommand(cli))
2929
cmd.AddCommand(TopicCommand(cli))
3030
cmd.AddCommand(TopologySyncCommand(cli))
31+
cmd.AddCommand(IngestionApiKeyCommand(cli))
3132

3233
return cmd
3334
}

0 commit comments

Comments
 (0)