-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #26 from stackitcloud/hs/ske-credentials
Implement `ske credential rotate/describe`
- Loading branch information
Showing
6 changed files
with
596 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.