Skip to content

Commit

Permalink
Merge pull request #77 from StackVista/STAC-20950-commands-to-manage-…
Browse files Browse the repository at this point in the history
…ingestion-api-keys

STAC-20950 Commands to manage Ingestion Api Keys
  • Loading branch information
LukaszMarchewka authored Mar 18, 2024
2 parents bb96be5 + 17d44ac commit d7211d0
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 0 deletions.
20 changes: 20 additions & 0 deletions cmd/ingestionapikey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/stackvista/stackstate-cli/cmd/ingestionapikey"
"github.com/stackvista/stackstate-cli/internal/di"
)

func IngestionApiKeyCommand(deps *di.Deps) *cobra.Command {
cmd := &cobra.Command{
Use: "ingestion-api-key",
Short: "Manage Ingestion API Keys",
Long: "Manage API Keys used by ingestion pipelines, means data (spans, metrics, logs an so on) send by STS Agent, OTel and so on.",
}

cmd.AddCommand(ingestionapikey.CreateCommand(deps))
cmd.AddCommand(ingestionapikey.ListCommand(deps))
cmd.AddCommand(ingestionapikey.DeleteCommand(deps))
return cmd
}
71 changes: 71 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package ingestionapikey

import (
"fmt"
"time"

"github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
"github.com/stackvista/stackstate-cli/internal/common"
"github.com/stackvista/stackstate-cli/internal/di"
)

const (
DateFormat = "2006-01-02"
)

type CreateArgs struct {
Name string
Expiration time.Time
Description string
}

func CreateCommand(deps *di.Deps) *cobra.Command {
args := &CreateArgs{}
cmd := &cobra.Command{
Use: "create",
Short: "Create a new Ingestion Api Key",
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.",
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyGenerationCommand(args)),
}

common.AddRequiredNameFlagVar(cmd, &args.Name, "Name of the API Key")
cmd.Flags().TimeVar(&args.Expiration, "expiration", time.Time{}, []string{DateFormat}, "Expiration date of the API Key")
cmd.Flags().StringVar(&args.Description, "description", "", "Optional description of the API Key")
return cmd
}

func RunIngestionApiKeyGenerationCommand(args *CreateArgs) di.CmdWithApiFn {
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
req := stackstate_api.GenerateIngestionApiKeyRequest{
Name: args.Name,
}

if len(args.Description) > 0 {
req.Description = &args.Description
}

if !args.Expiration.IsZero() {
m := args.Expiration.UnixMilli()
req.Expiration = &m
}

ingestionApiKeyAPI := api.IngestionApiKeyApi.GenerateIngestionApiKey(cli.Context)

serviceToken, resp, err := ingestionApiKeyAPI.GenerateIngestionApiKeyRequest(req).Execute()
if err != nil {
return common.NewResponseError(err, resp)
}

if cli.IsJson() {
cli.Printer.PrintJson(map[string]interface{}{
"ingestion-api-key": serviceToken,
})
} else {
cli.Printer.Success(fmt.Sprintf("Ingestion API Key generated: %s\n", color.White.Render(serviceToken.ApiKey)))
}

return nil
}
}
93 changes: 93 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ingestionapikey

import (
"testing"

"github.com/stackvista/stackstate-cli/generated/stackstate_api"
"github.com/stackvista/stackstate-cli/internal/di"
"github.com/stretchr/testify/assert"
)

func TestIngestApiKeyGenerate(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := CreateCommand(&cli.Deps)

cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = stackstate_api.GeneratedIngestionApiKeyResponse{
Name: "test-token",
ApiKey: "test-token-key",
Expiration: int64p(1590105600000),
Description: stringp("test-token-description"),
}

di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token", "--description", "test-token-description", "--expiration", "2020-05-22")

checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token", stringp("test-token-description"), int64p(1590105600000))
assert.Equal(t, []string{"Ingestion API Key generated: \x1b[37mtest-token-key\x1b[0m\n"}, *cli.MockPrinter.SuccessCalls)
}

func TestIngestApiKeyGenerateOnlyRequriedFlags(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := CreateCommand(&cli.Deps)

cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = stackstate_api.GeneratedIngestionApiKeyResponse{
Name: "test-token2",
ApiKey: "test-token2-key",
}

di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token2")

checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token2", nil, nil)
assert.Equal(t, []string{"Ingestion API Key generated: \x1b[37mtest-token2-key\x1b[0m\n"}, *cli.MockPrinter.SuccessCalls)
}

func TestIngestApiKeyGenerateJSON(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := CreateCommand(&cli.Deps)

r := &stackstate_api.GeneratedIngestionApiKeyResponse{
Name: "test-token",
ApiKey: "test-token-key",
Expiration: int64p(1590105600000),
Description: stringp("test-token-description"),
}

cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyResponse.Result = *r

di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--name", "test-token", "--description", "test-token-description", "--expiration", "2020-05-22", "-o", "json")

checkCreateCall(t, cli.MockClient.ApiMocks.IngestionApiKeyApi.GenerateIngestionApiKeyCalls, "test-token", stringp("test-token-description"), int64p(1590105600000))
assert.Equal(t,
[]map[string]interface{}{{
"ingestion-api-key": r,
}},
*cli.MockPrinter.PrintJsonCalls,
)
assert.False(t, cli.MockPrinter.HasNonJsonCalls)
}

func int64p(i int64) *int64 {
return &i
}

func stringp(i string) *string {
return &i
}

func checkCreateCall(t *testing.T, calls *[]stackstate_api.GenerateIngestionApiKeyCall, name string, description *string, expiration *int64) {
assert.Len(t, *calls, 1)

call := (*calls)[0]
assert.Equal(t, name, call.PgenerateIngestionApiKeyRequest.Name)

if description != nil {
assert.Equal(t, *description, *call.PgenerateIngestionApiKeyRequest.Description)
} else {
assert.Nil(t, call.PgenerateIngestionApiKeyRequest.Description)
}

if expiration != nil {
assert.Equal(t, *expiration, *call.PgenerateIngestionApiKeyRequest.Expiration)
} else {
assert.Nil(t, call.PgenerateIngestionApiKeyRequest.Expiration)
}
}
47 changes: 47 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ingestionapikey

import (
"fmt"

"github.com/spf13/cobra"
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
"github.com/stackvista/stackstate-cli/internal/common"
"github.com/stackvista/stackstate-cli/internal/di"
)

type DeleteArgs struct {
ID int64
}

func DeleteCommand(deps *di.Deps) *cobra.Command {
args := &DeleteArgs{}
cmd := &cobra.Command{
Use: "delete",
Short: "Delete an Ingestion Api Key",
Long: "Deleted key can't be used by sources, so all ingestion pipelines for that key will fail.",
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyDeleteCommand(args)),
}

common.AddRequiredIDFlagVar(cmd, &args.ID, "ID of the Ingestion Api Key to delete")

return cmd
}

func RunIngestionApiKeyDeleteCommand(args *DeleteArgs) di.CmdWithApiFn {
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
resp, err := api.IngestionApiKeyApi.DeleteIngestionApiKey(cli.Context, args.ID).Execute()
if err != nil {
return common.NewResponseError(err, resp)
}

if cli.IsJson() {
cli.Printer.PrintJson(map[string]interface{}{
"deleted-ingestion-api-key": args.ID,
})
} else {
cli.Printer.Success(fmt.Sprintf("Ingestion Api Key deleted: %d", args.ID))
}

return nil
}
}
30 changes: 30 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ingestionapikey

import (
"testing"

"github.com/stackvista/stackstate-cli/internal/di"
"github.com/stretchr/testify/assert"
)

func TestDeleteShouldFailOnNonIntID(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := DeleteCommand(&cli.Deps)

_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--id", "foo")

assert.NotNil(t, err)
assert.Contains(t, err.Error(), "invalid argument \"foo\" for \"-i, --id\"")
}

func TestDelete(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := DeleteCommand(&cli.Deps)

di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--id", "1")

assert.Len(t, *cli.MockClient.ApiMocks.IngestionApiKeyApi.DeleteIngestionApiKeyCalls, 1)
assert.Equal(t, int64(1), (*cli.MockClient.ApiMocks.IngestionApiKeyApi.DeleteIngestionApiKeyCalls)[0].PingestionApiKeyId)

assert.Equal(t, []string{"Ingestion Api Key deleted: 1"}, *cli.MockPrinter.SuccessCalls)
}
58 changes: 58 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package ingestionapikey

import (
"sort"
"time"

"github.com/spf13/cobra"
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
"github.com/stackvista/stackstate-cli/internal/common"
"github.com/stackvista/stackstate-cli/internal/di"
"github.com/stackvista/stackstate-cli/internal/printer"
)

func ListCommand(deps *di.Deps) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List Ingestion Api Keys",
Long: "Returns only metadata without a key itself.",
RunE: deps.CmdRunEWithApi(RunIngestionApiKeyListCommand),
}

return cmd
}

func RunIngestionApiKeyListCommand(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
ingestionApiKeys, resp, err := api.IngestionApiKeyApi.GetIngestionApiKeys(cli.Context).Execute()
if err != nil {
return common.NewResponseError(err, resp)
}

sort.SliceStable(ingestionApiKeys, func(i, j int) bool {
return ingestionApiKeys[i].Name < ingestionApiKeys[j].Name
})

if cli.IsJson() {
cli.Printer.PrintJson(map[string]interface{}{
"ingestion-api-keys": ingestionApiKeys,
})
} else {
data := make([][]interface{}, 0)
for _, ingestionApiKey := range ingestionApiKeys {
sid := ingestionApiKey.Id
exp := ""
if ingestionApiKey.Expiration != nil {
exp = time.UnixMilli(*ingestionApiKey.Expiration).Format(DateFormat)
}
data = append(data, []interface{}{sid, ingestionApiKey.Name, exp, ingestionApiKey.Description})
}

cli.Printer.Table(printer.TableData{
Header: []string{"id", "name", "expiration", "description"},
Data: data,
MissingTableDataMsg: printer.NotFoundMsg{Types: "ingestion api keys"},
})
}

return nil
}
46 changes: 46 additions & 0 deletions cmd/ingestionapikey/ingestionapikey_list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ingestionapikey

import (
"testing"

"github.com/stackvista/stackstate-cli/generated/stackstate_api"
"github.com/stackvista/stackstate-cli/internal/di"
"github.com/stackvista/stackstate-cli/internal/printer"
"github.com/stretchr/testify/assert"
)

func TestIngestionApiKeyList(t *testing.T) {
cli := di.NewMockDeps(t)
cmd := ListCommand(&cli.Deps)
key1desc := "main key"

cli.MockClient.ApiMocks.IngestionApiKeyApi.GetIngestionApiKeysResponse.Result = []stackstate_api.IngestionApiKey{
{
Id: 1,
Name: "key1",
Description: &key1desc,
Expiration: int64p(1590105600000),
},
{
Id: 2,
Name: "key2",
Description: nil,
Expiration: nil,
},
}

di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd)

tableData := []printer.TableData{
{
Header: []string{"id", "name", "expiration", "description"},
Data: [][]interface{}{
{int64(1), "key1", "2020-05-22", &key1desc},
{int64(2), "key2", "", (*string)(nil)},
},
MissingTableDataMsg: printer.NotFoundMsg{Types: "ingestion api keys"},
},
}

assert.Equal(t, tableData, *cli.MockPrinter.TableCalls)
}
1 change: 1 addition & 0 deletions cmd/sts.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func STSCommand(cli *di.Deps) *cobra.Command {
cmd.AddCommand(RbacCommand(cli))
cmd.AddCommand(TopicCommand(cli))
cmd.AddCommand(TopologySyncCommand(cli))
cmd.AddCommand(IngestionApiKeyCommand(cli))

return cmd
}
Loading

0 comments on commit d7211d0

Please sign in to comment.