diff --git a/docs/stackit_postgresflex_instance_clone.md b/docs/stackit_postgresflex_instance_clone.md index 64acbc209..cbc9f1e9b 100644 --- a/docs/stackit_postgresflex_instance_clone.md +++ b/docs/stackit_postgresflex_instance_clone.md @@ -27,7 +27,7 @@ stackit postgresflex instance clone INSTANCE_ID [flags] ``` -h, --help Help for "stackit postgresflex instance clone" - --recovery-timestamp string Recovery timestamp for the instance, specified in UTC time following the format, e.g. 2024-03-12T09:28:00+00:00 + --recovery-timestamp string Recovery timestamp for the instance, in a date-time with the RFC3339 layout format, e.g. 2024-01-01T00:00:00Z --storage-class string Storage class. If not specified, storage class from the existing instance will be used. --storage-size int Storage size (in GB). If not specified, storage size from the existing instance will be used. ``` diff --git a/docs/stackit_project_list.md b/docs/stackit_project_list.md index 388768532..f0fdf46af 100644 --- a/docs/stackit_project_list.md +++ b/docs/stackit_project_list.md @@ -13,6 +13,9 @@ stackit project list [flags] ### Examples ``` + List all STACKIT projects that authenticated user is a member of + $ stackit project list + List all STACKIT projects that are children of a specific parent $ stackit project list --parent-id xxx diff --git a/docs/stackit_project_member_list.md b/docs/stackit_project_member_list.md index 6761cb374..43f371879 100644 --- a/docs/stackit_project_member_list.md +++ b/docs/stackit_project_member_list.md @@ -14,13 +14,13 @@ stackit project member list [flags] ``` List all members of a project - $ stackit project role list --project-id xxx + $ stackit project member list --project-id xxx List all members of a project, sorted by role - $ stackit project role list --project-id xxx --sort-by role + $ stackit project member list --project-id xxx --sort-by role List up to 10 members of a project - $ stackit project role list --project-id xxx --limit 10 + $ stackit project member list --project-id xxx --limit 10 ``` ### Options diff --git a/internal/cmd/dns/record-set/describe/describe.go b/internal/cmd/dns/record-set/describe/describe.go index 46c8546bc..4d9349f02 100644 --- a/internal/cmd/dns/record-set/describe/describe.go +++ b/internal/cmd/dns/record-set/describe/describe.go @@ -103,10 +103,9 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *dns.APIClie func outputResult(cmd *cobra.Command, outputFormat string, recordSet *dns.RecordSet) error { switch outputFormat { case globalflags.PrettyOutputFormat: - records := *recordSet.Records - recordsData := []string{} - for i := range records { - recordsData = append(recordsData, *records[i].Content) + recordsData := make([]string, 0, len(*recordSet.Records)) + for _, r := range *recordSet.Records { + recordsData = append(recordsData, *r.Content) } recordsDataJoin := strings.Join(recordsData, ",") diff --git a/internal/cmd/dns/record-set/list/list.go b/internal/cmd/dns/record-set/list/list.go index 95a195e44..a551abdef 100644 --- a/internal/cmd/dns/record-set/list/list.go +++ b/internal/cmd/dns/record-set/list/list.go @@ -232,10 +232,15 @@ func outputResult(cmd *cobra.Command, outputFormat string, recordSets []dns.Reco return nil default: table := tables.NewTable() - table.SetHeader("ID", "NAME", "STATUS", "TTL", "TYPE") + table.SetHeader("ID", "NAME", "STATUS", "TTL", "TYPE", "RECORD DATA") for i := range recordSets { rs := recordSets[i] - table.AddRow(*rs.Id, *rs.Name, *rs.State, *rs.Ttl, *rs.Type) + recordData := make([]string, 0, len(*rs.Records)) + for _, r := range *rs.Records { + recordData = append(recordData, *r.Content) + } + recordDataJoin := strings.Join(recordData, ", ") + table.AddRow(*rs.Id, *rs.Name, *rs.State, *rs.Ttl, *rs.Type, recordDataJoin) } err := table.Display(cmd) if err != nil { diff --git a/internal/cmd/logme/plans/plans.go b/internal/cmd/logme/plans/plans.go index f7c46d3b9..8bf3fd69d 100644 --- a/internal/cmd/logme/plans/plans.go +++ b/internal/cmd/logme/plans/plans.go @@ -127,16 +127,16 @@ func outputResult(cmd *cobra.Command, outputFormat string, plans []logme.Offerin return nil default: table := tables.NewTable() - table.SetHeader("OFFERING NAME", "ID", "NAME", "DESCRIPTION") + table.SetHeader("OFFERING NAME", "VERSION", "ID", "NAME", "DESCRIPTION") for i := range plans { o := plans[i] for j := range *o.Plans { p := (*o.Plans)[j] - table.AddRow(*o.Name, *p.Id, *p.Name, *p.Description) + table.AddRow(*o.Name, *o.Version, *p.Id, *p.Name, *p.Description) } table.AddSeparator() } - table.EnableAutoMergeOnColumns(1) + table.EnableAutoMergeOnColumns(1, 2) err := table.Display(cmd) if err != nil { return fmt.Errorf("render table: %w", err) diff --git a/internal/cmd/mariadb/plans/plans.go b/internal/cmd/mariadb/plans/plans.go index 7e400f7f8..f7135658e 100644 --- a/internal/cmd/mariadb/plans/plans.go +++ b/internal/cmd/mariadb/plans/plans.go @@ -127,16 +127,16 @@ func outputResult(cmd *cobra.Command, outputFormat string, plans []mariadb.Offer return nil default: table := tables.NewTable() - table.SetHeader("OFFERING NAME", "ID", "NAME", "DESCRIPTION") + table.SetHeader("OFFERING NAME", "VERSION", "ID", "NAME", "DESCRIPTION") for i := range plans { o := plans[i] for j := range *o.Plans { p := (*o.Plans)[j] - table.AddRow(*o.Name, *p.Id, *p.Name, *p.Description) + table.AddRow(*o.Name, *o.Version, *p.Id, *p.Name, *p.Description) } table.AddSeparator() } - table.EnableAutoMergeOnColumns(1) + table.EnableAutoMergeOnColumns(1, 2) err := table.Display(cmd) if err != nil { return fmt.Errorf("render table: %w", err) diff --git a/internal/cmd/opensearch/plans/plans.go b/internal/cmd/opensearch/plans/plans.go index db7932d0b..e46bbb888 100644 --- a/internal/cmd/opensearch/plans/plans.go +++ b/internal/cmd/opensearch/plans/plans.go @@ -127,16 +127,16 @@ func outputResult(cmd *cobra.Command, outputFormat string, plans []opensearch.Of return nil default: table := tables.NewTable() - table.SetHeader("OFFERING NAME", "ID", "NAME", "DESCRIPTION") + table.SetHeader("OFFERING NAME", "VERSION", "ID", "NAME", "DESCRIPTION") for i := range plans { o := plans[i] for j := range *o.Plans { p := (*o.Plans)[j] - table.AddRow(*o.Name, *p.Id, *p.Name, *p.Description) + table.AddRow(*o.Name, *o.Version, *p.Id, *p.Name, *p.Description) } table.AddSeparator() } - table.EnableAutoMergeOnColumns(1) + table.EnableAutoMergeOnColumns(1, 2) err := table.Display(cmd) if err != nil { return fmt.Errorf("render table: %w", err) diff --git a/internal/cmd/project/list/list.go b/internal/cmd/project/list/list.go index 6743cd337..20fd35013 100644 --- a/internal/cmd/project/list/list.go +++ b/internal/cmd/project/list/list.go @@ -7,6 +7,7 @@ import ( "time" "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" @@ -47,6 +48,9 @@ func NewCmd() *cobra.Command { Long: "Lists all STACKIT projects that match certain criteria.", Args: args.NoArgs, Example: examples.Build( + examples.NewExample( + `List all STACKIT projects that the authenticated user is a member of`, + "$ stackit project list"), examples.NewExample( `List all STACKIT projects that are children of a specific parent`, "$ stackit project list --parent-id xxx"), @@ -94,9 +98,6 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(creationTimeAfterFlag, "", "Filter by creation timestamp, in a date-time with the RFC3339 layout format, e.g. 2023-01-01T00:00:00Z. The list of projects that were created after the given timestamp will be shown") cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list") cmd.Flags().Int64(pageSizeFlag, pageSizeDefault, "Number of items fetched in each API call. Does not affect the number of items in the command output") - - // At least one of parent-id, project-id-like or member flag must be provided - cmd.MarkFlagsOneRequired(parentIdFlag, projectIdLikeFlag, memberFlag) } func parseInput(cmd *cobra.Command) (*inputModel, error) { @@ -137,7 +138,7 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) { }, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient resourceManagerClient, offset int) resourcemanager.ApiListProjectsRequest { +func buildRequest(ctx context.Context, model *inputModel, apiClient resourceManagerClient, offset int) (resourcemanager.ApiListProjectsRequest, error) { req := apiClient.ListProjects(ctx) if model.ParentId != nil { req = req.ContainerParentId(*model.ParentId) @@ -151,9 +152,17 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient resourceMana if model.CreationTimeAfter != nil { req = req.CreationTimeStart(*model.CreationTimeAfter) } + + if model.ParentId == nil && model.ProjectIdLike == nil && model.Member == nil { + email, err := auth.GetAuthField(auth.USER_EMAIL) + if err != nil { + return req, fmt.Errorf("get email of authenticated user: %w", err) + } + req = req.Member(email) + } req = req.Limit(float32(model.PageSize)) req = req.Offset(float32(offset)) - return req + return req, nil } type resourceManagerClient interface { @@ -169,7 +178,10 @@ func fetchProjects(ctx context.Context, model *inputModel, apiClient resourceMan projects := []resourcemanager.ProjectResponse{} for { // Call API - req := buildRequest(ctx, model, apiClient, offset) + req, err := buildRequest(ctx, model, apiClient, offset) + if err != nil { + return nil, fmt.Errorf("build list projects request: %w", err) + } resp, err := req.Execute() if err != nil { return nil, fmt.Errorf("get projects: %w", err) diff --git a/internal/cmd/project/list/list_test.go b/internal/cmd/project/list/list_test.go index 8bbd3b70d..1a4ca5755 100644 --- a/internal/cmd/project/list/list_test.go +++ b/internal/cmd/project/list/list_test.go @@ -9,8 +9,10 @@ import ( "testing" "time" + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/zalando/go-keyring" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -139,15 +141,12 @@ func TestParseInput(t *testing.T) { { description: "no values", flagValues: map[string]string{}, - isValid: false, - }, - { - description: "none of required fields provided", - flagValues: fixtureFlagValues(func(flagValues map[string]string) { - delete(flagValues, parentIdFlag) - delete(flagValues, memberFlag) + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.ParentId = nil + model.Member = nil + model.CreationTimeAfter = nil }), - isValid: false, }, { description: "projectIdLike invalid", @@ -258,6 +257,17 @@ func TestParseInput(t *testing.T) { } func TestBuildRequest(t *testing.T) { + keyring.MockInit() + err := auth.SetAuthField(auth.USER_EMAIL, "test@test.com") + if err != nil { + t.Fatalf("Failed to set auth user email: %v", err) + } + + authUserEmail, err := auth.GetAuthField(auth.USER_EMAIL) + if err != nil { + t.Fatalf("Failed to get auth user email: %v", err) + } + tests := []struct { description string model *inputModel @@ -278,12 +288,12 @@ func TestBuildRequest(t *testing.T) { expectedRequest: fixtureRequest().Offset(10), }, { - description: "required fields only", + description: "fetch email from auth user", model: &inputModel{ PageSize: pageSizeDefault, }, offset: 1, - expectedRequest: testClient.ListProjects(testCtx).Offset(1).Limit(pageSizeDefault), + expectedRequest: testClient.ListProjects(testCtx).Offset(1).Limit(pageSizeDefault).Member(authUserEmail), }, { description: "projectIdLike set", @@ -299,7 +309,10 @@ func TestBuildRequest(t *testing.T) { if tt.projectIdLike != nil { tt.model.ProjectIdLike = tt.projectIdLike } - request := buildRequest(testCtx, tt.model, testClient, tt.offset) + request, err := buildRequest(testCtx, tt.model, testClient, tt.offset) + if err != nil { + t.Fatalf("Failed to build request: %v", err) + } diff := cmp.Diff(request, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), diff --git a/internal/cmd/project/member/list/list.go b/internal/cmd/project/member/list/list.go index 1979785f2..5337048e4 100644 --- a/internal/cmd/project/member/list/list.go +++ b/internal/cmd/project/member/list/list.go @@ -44,13 +44,13 @@ func NewCmd() *cobra.Command { Example: examples.Build( examples.NewExample( `List all members of a project`, - "$ stackit project role list --project-id xxx"), + "$ stackit project member list --project-id xxx"), examples.NewExample( `List all members of a project, sorted by role`, - "$ stackit project role list --project-id xxx --sort-by role"), + "$ stackit project member list --project-id xxx --sort-by role"), examples.NewExample( `List up to 10 members of a project`, - "$ stackit project role list --project-id xxx --limit 10"), + "$ stackit project member list --project-id xxx --limit 10"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() diff --git a/internal/cmd/rabbitmq/plans/plans.go b/internal/cmd/rabbitmq/plans/plans.go index 773e6679b..fc26d00f0 100644 --- a/internal/cmd/rabbitmq/plans/plans.go +++ b/internal/cmd/rabbitmq/plans/plans.go @@ -127,16 +127,16 @@ func outputResult(cmd *cobra.Command, outputFormat string, plans []rabbitmq.Offe return nil default: table := tables.NewTable() - table.SetHeader("OFFERING NAME", "ID", "NAME", "DESCRIPTION") + table.SetHeader("OFFERING NAME", "VERSION", "ID", "NAME", "DESCRIPTION") for i := range plans { o := plans[i] for j := range *o.Plans { p := (*o.Plans)[j] - table.AddRow(*o.Name, *p.Id, *p.Name, *p.Description) + table.AddRow(*o.Name, *o.Version, *p.Id, *p.Name, *p.Description) } table.AddSeparator() } - table.EnableAutoMergeOnColumns(1) + table.EnableAutoMergeOnColumns(1, 2) err := table.Display(cmd) if err != nil { return fmt.Errorf("render table: %w", err) diff --git a/internal/cmd/redis/plans/plans.go b/internal/cmd/redis/plans/plans.go index 86232c1fb..4aadf5fdd 100644 --- a/internal/cmd/redis/plans/plans.go +++ b/internal/cmd/redis/plans/plans.go @@ -127,16 +127,16 @@ func outputResult(cmd *cobra.Command, outputFormat string, plans []redis.Offerin return nil default: table := tables.NewTable() - table.SetHeader("OFFERING NAME", "ID", "NAME", "DESCRIPTION") + table.SetHeader("OFFERING NAME", "VERSION", "ID", "NAME", "DESCRIPTION") for i := range plans { o := plans[i] for j := range *o.Plans { p := (*o.Plans)[j] - table.AddRow(*o.Name, *p.Id, *p.Name, *p.Description) + table.AddRow(*o.Name, *o.Version, *p.Id, *p.Name, *p.Description) } table.AddSeparator() } - table.EnableAutoMergeOnColumns(1) + table.EnableAutoMergeOnColumns(1, 2) err := table.Display(cmd) if err != nil { return fmt.Errorf("render table: %w", err)