Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ske credential rotate/describe #26

Merged
merged 9 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions internal/cmd/ske/credential/credential.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package credential

import (
"fmt"

"github.com/stackitcloud/stackit-cli/internal/cmd/ske/credential/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/ske/credential/rotate"

"github.com/spf13/cobra"
)

func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "credential",
Short: "Provides functionality for SKE credentials",
Long: "Provides functionality for SKE credentials",
Example: fmt.Sprintf("%s\n%s", describe.NewCmd().Example, rotate.NewCmd().Example),
}
addSubcommands(cmd)
return cmd
}

func addSubcommands(cmd *cobra.Command) {
cmd.AddCommand(describe.NewCmd())
cmd.AddCommand(rotate.NewCmd())
}
109 changes: 109 additions & 0 deletions internal/cmd/ske/credential/describe/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package describe

import (
"context"
"encoding/json"
"fmt"

"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)

const (
clusterNameFlag = "cluster-name"
)

type flagModel struct {
*globalflags.GlobalFlagModel
ClusterName string
}

func NewCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "describe",
Short: "Get details of the credential associated to a SKE cluster",
Long: "Get details of the credential associated to a SKE cluster",
Example: `$ stackit ske credential describe --project-id xxx --cluster-name xxx`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseFlags(cmd)
if err != nil {
return err
}

// Configure API client
apiClient, err := client.ConfigureClient(cmd)
if err != nil {
return fmt.Errorf("authentication failed, please run \"stackit auth login\" or \"stackit auth activate-service-account\"")
}

// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("describe SKE credential: %w", err)
}

return outputResult(cmd, model.OutputFormat, resp)
},
}
configureFlags(cmd)
return cmd
}

func configureFlags(cmd *cobra.Command) {
cmd.Flags().String(clusterNameFlag, "", "Cluster name")

err := utils.MarkFlagsRequired(cmd, clusterNameFlag)
cobra.CheckErr(err)
}

func parseFlags(cmd *cobra.Command) (*flagModel, error) {
globalFlags := globalflags.Parse(cmd)
if globalFlags.ProjectId == "" {
return nil, fmt.Errorf("project ID not set")
}

clusterName := utils.FlagToStringValue(cmd, clusterNameFlag)
if clusterName == "" {
return nil, fmt.Errorf("cluster name can't be empty")
}

return &flagModel{
GlobalFlagModel: globalFlags,
ClusterName: clusterName,
}, nil
}

func buildRequest(ctx context.Context, model *flagModel, apiClient *ske.APIClient) ske.ApiGetCredentialsRequest {
req := apiClient.GetCredentials(ctx, model.ProjectId, model.ClusterName)
return req
}

func outputResult(cmd *cobra.Command, outputFormat string, credential *ske.CredentialsResponse) error {
switch outputFormat {
case globalflags.TableOutputFormat:
table := tables.NewTable()
table.SetHeader("SERVER", "TOKEN")
table.AddRow(*credential.Server, *credential.Token)
err := table.Render(cmd)
if err != nil {
return fmt.Errorf("render table: %w", err)
}

return nil
default:
details, err := json.MarshalIndent(credential, "", " ")
if err != nil {
return fmt.Errorf("marshal PostgreSQL credential: %w", err)
}
cmd.Println(string(details))

return nil
}
}
183 changes: 183 additions & 0 deletions internal/cmd/ske/credential/describe/describe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package describe

import (
"context"
"testing"

"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/ske"
)

var projectIdFlag = globalflags.ProjectIdFlag

type testCtxKey struct{}

var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &ske.APIClient{}
var testProjectId = uuid.NewString()

func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
clusterNameFlag: "cluster",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}

func fixtureFlagModel(mods ...func(model *flagModel)) *flagModel {
model := &flagModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
},
ClusterName: "cluster",
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *ske.ApiGetCredentialsRequest)) ske.ApiGetCredentialsRequest {
request := testClient.GetCredentials(testCtx, testProjectId, "cluster")
for _, mod := range mods {
mod(&request)
}
return request
}

func TestParseFlags(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *flagModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureFlagModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "cluster name missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, clusterNameFlag)
}),
isValid: false,
},
{
description: "cluster name invalid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[clusterNameFlag] = ""
}),
isValid: false,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
cmd := &cobra.Command{}
configureFlags(cmd)
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}

for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}

err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}

model, err := parseFlags(cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing flags: %v", err)
}

if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}

func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *flagModel
expectedRequest ske.ApiGetCredentialsRequest
}{
{
description: "base",
model: fixtureFlagModel(),
expectedRequest: fixtureRequest(),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
Loading