From 91036564ea4014f36917a6cb0875104d1e4f3754 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 15:07:34 +0100 Subject: [PATCH 01/10] feat(resourcemanager): List and describe organizations relates to STACKITCLI-325 --- .../cmd/organization/describe/describe.go | 115 +++++++++++ .../organization/describe/describe_test.go | 190 +++++++++++++++++ internal/cmd/organization/list/list.go | 143 +++++++++++++ internal/cmd/organization/list/list_test.go | 191 ++++++++++++++++++ internal/cmd/organization/organization.go | 4 + 5 files changed, 643 insertions(+) create mode 100644 internal/cmd/organization/describe/describe.go create mode 100644 internal/cmd/organization/describe/describe_test.go create mode 100644 internal/cmd/organization/list/list.go create mode 100644 internal/cmd/organization/list/list_test.go diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go new file mode 100644 index 000000000..0c5463d16 --- /dev/null +++ b/internal/cmd/organization/describe/describe.go @@ -0,0 +1,115 @@ +package describe + +import ( + "context" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +const ( + organizationIdArg = "ORGANIZATION_ID" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + OrganizationId string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Short: "Show a organization", + Long: "Show a organization.", + // the arg can be the organization uuid or the container id, which is not a uuid, so no validation needed + Args: args.SingleArg(organizationIdArg, nil), + Example: examples.Build( + examples.NewExample( + `Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"`, + "$ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + ), + examples.NewExample( + `Describe the organization with the container id "foo-bar-organization"`, + "$ stackit organization describe foo-bar-organization", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return err + } + + return outputResult(params.Printer, model.OutputFormat, resp) + }, + } + return cmd +} + +func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { + organizationId := inputArgs[0] + globalFlags := globalflags.Parse(p, cmd) + + model := inputModel{ + GlobalFlagModel: globalFlags, + OrganizationId: organizationId, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiGetOrganizationRequest { + req := apiClient.GetOrganization(ctx, model.OrganizationId) + return req +} + +func outputResult(p *print.Printer, outputFormat string, organization *resourcemanager.OrganizationResponse) error { + return p.OutputResult(outputFormat, organization, func() error { + if organization == nil { + p.Error("show organization: empty response") + return nil + } + + table := tables.NewTable() + + table.AddRow("ORGANIZATION ID", utils.PtrString(organization.OrganizationId)) + table.AddSeparator() + table.AddRow("NAME", utils.PtrString(organization.Name)) + table.AddSeparator() + table.AddRow("CONTAINER ID", utils.PtrString(organization.ContainerId)) + table.AddSeparator() + table.AddRow("STATUS", utils.PtrString(organization.LifecycleState)) + table.AddSeparator() + table.AddRow("CREATION TIME", utils.PtrString(organization.CreationTime)) + table.AddSeparator() + table.AddRow("UPDATE TIME", utils.PtrString(organization.UpdateTime)) + table.AddSeparator() + table.AddRow("LABELS", utils.JoinStringMap(utils.PtrValue(organization.Labels), ": ", ", ")) + + p.Outputln(table.Render()) + return nil + }) +} diff --git a/internal/cmd/organization/describe/describe_test.go b/internal/cmd/organization/describe/describe_test.go new file mode 100644 index 000000000..00d1d2598 --- /dev/null +++ b/internal/cmd/organization/describe/describe_test.go @@ -0,0 +1,190 @@ +package describe + +import ( + "context" + "testing" + "time" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &resourcemanager.APIClient{} + +var ( + testOrganizationId = uuid.NewString() +) + +func fixtureArgValues(mods ...func(argValues []string)) []string { + argValues := []string{ + testOrganizationId, + } + for _, mod := range mods { + mod(argValues) + } + return argValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + }, + OrganizationId: testOrganizationId, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *resourcemanager.ApiGetOrganizationRequest)) resourcemanager.ApiGetOrganizationRequest { + request := testClient.GetOrganization(testCtx, testOrganizationId) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + argValues: fixtureArgValues(), + isValid: true, + expectedModel: fixtureInputModel(), + }, + { + description: "uuid as example for an organization id", + argValues: []string{"12345678-90ab-cdef-1234-1234567890ab"}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.OrganizationId = "12345678-90ab-cdef-1234-1234567890ab" + }), + }, + { + description: "non uuid string as example for a container id", + argValues: []string{"foo-bar-organization"}, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.OrganizationId = "foo-bar-organization" + }), + }, + { + description: "no args", + argValues: []string{}, + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest resourcemanager.ApiGetOrganizationRequest + }{ + { + description: "base", + model: fixtureInputModel(), + 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) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + organization *resourcemanager.OrganizationResponse + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "nil pointer as organization", + args: args{ + organization: nil, + }, + wantErr: false, + }, + { + name: "empty organization", + args: args{ + organization: utils.Ptr(resourcemanager.OrganizationResponse{}), + }, + wantErr: false, + }, + { + name: "full response", + args: args{ + organization: utils.Ptr(resourcemanager.OrganizationResponse{ + OrganizationId: utils.Ptr(uuid.NewString()), + Name: utils.Ptr("foo bar"), + LifecycleState: utils.Ptr(resourcemanager.LIFECYCLESTATE_ACTIVE), + ContainerId: utils.Ptr("foo-bar-organization"), + CreationTime: utils.Ptr(time.Now()), + UpdateTime: utils.Ptr(time.Now()), + Labels: utils.Ptr(map[string]string{ + "foo": "true", + "bar": "false", + }), + }), + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.organization); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go new file mode 100644 index 000000000..d94a525b8 --- /dev/null +++ b/internal/cmd/organization/list/list.go @@ -0,0 +1,143 @@ +package list + +import ( + "context" + "fmt" + + "github.com/stackitcloud/stackit-cli/internal/pkg/auth" + "github.com/stackitcloud/stackit-cli/internal/pkg/config" + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-cli/internal/pkg/args" + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/examples" + "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" + "github.com/stackitcloud/stackit-cli/internal/pkg/tables" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +const ( + limitFlag = "limit" +) + +type inputModel struct { + *globalflags.GlobalFlagModel + Limit *int64 + Member string +} + +func NewCmd(params *types.CmdParams) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "Lists all organizations", + Long: "Lists all organizations.", + Args: args.NoArgs, + Example: examples.Build( + examples.NewExample( + `Lists organizations for your user`, + "$ stackit organization list", + ), + examples.NewExample( + `Lists organizations for the user with the email foo@bar`, + "$ stackit organization list --member foo@bar", + ), + examples.NewExample( + `Lists the first 10 organizations`, + "$ stackit organization list --limit 10", + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + model, err := parseInput(params.Printer, cmd, args) + if err != nil { + return err + } + + model.Member = auth.GetProfileEmail(config.DefaultProfileName) + + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + if err != nil { + return err + } + + // Call API + req := buildRequest(ctx, model, apiClient) + resp, err := req.Execute() + if err != nil { + return fmt.Errorf("list organizations: %w", err) + } + + if resp == nil { + params.Printer.Info("list organizations: empty response") + return nil + } + + return outputResult(params.Printer, model.OutputFormat, utils.PtrValue(resp.Items)) + }, + } + configureFlags(cmd) + return cmd +} + +func configureFlags(cmd *cobra.Command) { + cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list (default 50)") +} + +func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { + globalFlags := globalflags.Parse(p, cmd) + + limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) + if limit != nil && *limit < 1 { + return nil, &errors.FlagValidationError{ + Flag: limitFlag, + Details: "must be greater than 0", + } + } + + model := inputModel{ + GlobalFlagModel: globalFlags, + Limit: limit, + } + + p.DebugInputModel(model) + return &model, nil +} + +func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) resourcemanager.ApiListOrganizationsRequest { + req := apiClient.ListOrganizations(ctx) + req = req.Member(model.Member) + if model.Limit != nil { + req = req.Limit(float32(*model.Limit)) + } + return req +} + +func outputResult(p *print.Printer, outputFormat string, organizations []resourcemanager.ListOrganizationsResponseItemsInner) error { + return p.OutputResult(outputFormat, organizations, func() error { + if len(organizations) == 0 { + p.Outputln("No organizations found") + return nil + } + + table := tables.NewTable() + table.SetHeader("ID", "NAME", "CONTAINER ID") + + for _, organization := range organizations { + table.AddRow( + utils.PtrString(organization.OrganizationId), + utils.PtrString(organization.Name), + utils.PtrString(organization.ContainerId), + ) + table.AddSeparator() + } + + p.Outputln(table.Render()) + return nil + }) +} diff --git a/internal/cmd/organization/list/list_test.go b/internal/cmd/organization/list/list_test.go new file mode 100644 index 000000000..ec54c69ea --- /dev/null +++ b/internal/cmd/organization/list/list_test.go @@ -0,0 +1,191 @@ +package list + +import ( + "context" + "strconv" + "testing" + + "github.com/stackitcloud/stackit-cli/internal/pkg/types" + + "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stackitcloud/stackit-sdk-go/services/resourcemanager" +) + +type testCtxKey struct{} + +var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo") +var testClient = &resourcemanager.APIClient{} + +const ( + testEmail = "foo@bar" + testLimit = 10 +) + +func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { + flagValues := map[string]string{ + limitFlag: strconv.Itoa(int(testLimit)), + } + for _, mod := range mods { + mod(flagValues) + } + return flagValues +} + +func fixtureInputModel(mods ...func(model *inputModel)) *inputModel { + model := &inputModel{ + GlobalFlagModel: &globalflags.GlobalFlagModel{ + Verbosity: globalflags.VerbosityDefault, + }, + Limit: utils.Ptr(int64(testLimit)), + Member: testEmail, + } + for _, mod := range mods { + mod(model) + } + return model +} + +func fixtureRequest(mods ...func(request *resourcemanager.ApiListOrganizationsRequest)) resourcemanager.ApiListOrganizationsRequest { + request := testClient.ListOrganizations(testCtx) + request = request.Limit(testLimit) + request = request.Member(testEmail) + for _, mod := range mods { + mod(&request) + } + return request +} + +func TestParseInput(t *testing.T) { + tests := []struct { + description string + argValues []string + flagValues map[string]string + isValid bool + expectedModel *inputModel + }{ + { + description: "base", + flagValues: fixtureFlagValues(), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + // model.Member is set by the Run function afterwards + model.Member = "" + }), + }, + { + description: "no limit", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, limitFlag) + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + // model.Member is set by the Run function afterwards + model.Member = "" + model.Limit = nil + }), + }, + { + description: "limit invalid", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "invalid" + }), + isValid: false, + }, + { + description: "limit invalid 2", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + flagValues[limitFlag] = "0" + }), + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid) + }) + } +} + +func TestBuildRequest(t *testing.T) { + tests := []struct { + description string + model *inputModel + expectedRequest resourcemanager.ApiListOrganizationsRequest + }{ + { + description: "base", + model: fixtureInputModel(), + expectedRequest: fixtureRequest(), + }, + { + description: "empty input model", + model: fixtureInputModel(func(model *inputModel) { + model.Member = "" + model.Limit = nil + }), + expectedRequest: testClient.ListOrganizations(testCtx).Member(""), + }, + } + + 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) + } + }) + } +} + +func TestOutputResult(t *testing.T) { + type args struct { + outputFormat string + organizations []resourcemanager.ListOrganizationsResponseItemsInner + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "empty", + args: args{}, + wantErr: false, + }, + { + name: "empty organizations slice", + args: args{ + organizations: []resourcemanager.ListOrganizationsResponseItemsInner{}, + }, + wantErr: false, + }, + { + name: "empty organization in organizations slice", + args: args{ + organizations: []resourcemanager.ListOrganizationsResponseItemsInner{{}}, + }, + wantErr: false, + }, + } + p := print.NewPrinter() + p.Cmd = NewCmd(&types.CmdParams{Printer: p}) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := outputResult(p, tt.args.outputFormat, tt.args.organizations); (err != nil) != tt.wantErr { + t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/cmd/organization/organization.go b/internal/cmd/organization/organization.go index 7a68177b7..adc9fa8e8 100644 --- a/internal/cmd/organization/organization.go +++ b/internal/cmd/organization/organization.go @@ -5,6 +5,8 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/types" + "github.com/stackitcloud/stackit-cli/internal/cmd/organization/describe" + "github.com/stackitcloud/stackit-cli/internal/cmd/organization/list" "github.com/stackitcloud/stackit-cli/internal/cmd/organization/member" "github.com/stackitcloud/stackit-cli/internal/cmd/organization/role" "github.com/stackitcloud/stackit-cli/internal/pkg/args" @@ -31,4 +33,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func addSubcommands(cmd *cobra.Command, params *types.CmdParams) { cmd.AddCommand(member.NewCmd(params)) cmd.AddCommand(role.NewCmd(params)) + cmd.AddCommand(list.NewCmd(params)) + cmd.AddCommand(describe.NewCmd(params)) } From f4cc9c22e47befbf6b3c3188e50356fc30a0dbf6 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 15:11:44 +0100 Subject: [PATCH 02/10] generated docs --- docs/stackit_organization.md | 2 ++ docs/stackit_organization_describe.md | 43 ++++++++++++++++++++++++ docs/stackit_organization_list.md | 47 +++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 docs/stackit_organization_describe.md create mode 100644 docs/stackit_organization_list.md diff --git a/docs/stackit_organization.md b/docs/stackit_organization.md index f1bbaedde..2d58a4997 100644 --- a/docs/stackit_organization.md +++ b/docs/stackit_organization.md @@ -31,6 +31,8 @@ stackit organization [flags] ### SEE ALSO * [stackit](./stackit.md) - Manage STACKIT resources using the command line +* [stackit organization describe](./stackit_organization_describe.md) - Show a organization +* [stackit organization list](./stackit_organization_list.md) - Lists all organizations * [stackit organization member](./stackit_organization_member.md) - Manages organization members * [stackit organization role](./stackit_organization_role.md) - Manages organization roles diff --git a/docs/stackit_organization_describe.md b/docs/stackit_organization_describe.md new file mode 100644 index 000000000..230c8777b --- /dev/null +++ b/docs/stackit_organization_describe.md @@ -0,0 +1,43 @@ +## stackit organization describe + +Show a organization + +### Synopsis + +Show a organization. + +``` +stackit organization describe [flags] +``` + +### Examples + +``` + Describe the organization with the organization uuid "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + $ stackit organization describe xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + + Describe the organization with the container id "foo-bar-organization" + $ stackit organization describe foo-bar-organization +``` + +### Options + +``` + -h, --help Help for "stackit organization describe" +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit organization](./stackit_organization.md) - Manages organizations + diff --git a/docs/stackit_organization_list.md b/docs/stackit_organization_list.md new file mode 100644 index 000000000..e986fa5a4 --- /dev/null +++ b/docs/stackit_organization_list.md @@ -0,0 +1,47 @@ +## stackit organization list + +Lists all organizations + +### Synopsis + +Lists all organizations. + +``` +stackit organization list [flags] +``` + +### Examples + +``` + Lists organizations for your user + $ stackit organization list + + Lists organizations for the user with the email foo@bar + $ stackit organization list --member foo@bar + + Lists the first 10 organizations + $ stackit organization list --limit 10 +``` + +### Options + +``` + -h, --help Help for "stackit organization list" + --limit int Maximum number of entries to list (default 50) +``` + +### Options inherited from parent commands + +``` + -y, --assume-yes If set, skips all confirmation prompts + --async If set, runs the command asynchronously + -o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"] + -p, --project-id string Project ID + --region string Target region for region-specific requests + --verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info") +``` + +### SEE ALSO + +* [stackit organization](./stackit_organization.md) - Manages organizations + From 5bf48d06ce5894ca23d78dca2c0ecd099383a8b6 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 15:39:43 +0100 Subject: [PATCH 03/10] changed to outputf --- internal/cmd/organization/describe/describe.go | 2 +- internal/cmd/organization/list/list.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go index 0c5463d16..45fbda86f 100644 --- a/internal/cmd/organization/describe/describe.go +++ b/internal/cmd/organization/describe/describe.go @@ -89,7 +89,7 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *resourceman func outputResult(p *print.Printer, outputFormat string, organization *resourcemanager.OrganizationResponse) error { return p.OutputResult(outputFormat, organization, func() error { if organization == nil { - p.Error("show organization: empty response") + p.Outputln("show organization: empty response") return nil } diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go index d94a525b8..b41047a1a 100644 --- a/internal/cmd/organization/list/list.go +++ b/internal/cmd/organization/list/list.go @@ -2,7 +2,6 @@ package list import ( "context" - "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/config" @@ -70,11 +69,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { req := buildRequest(ctx, model, apiClient) resp, err := req.Execute() if err != nil { - return fmt.Errorf("list organizations: %w", err) + return err } if resp == nil { - params.Printer.Info("list organizations: empty response") + params.Printer.Outputln("list organizations: empty response") return nil } From 52ddff8b3d4e28cd2f47a6725ccd5fe988130b9c Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 15:49:49 +0100 Subject: [PATCH 04/10] table.render -> table.display --- internal/cmd/organization/describe/describe.go | 6 +++++- internal/cmd/organization/list/list.go | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go index 45fbda86f..80714c2e9 100644 --- a/internal/cmd/organization/describe/describe.go +++ b/internal/cmd/organization/describe/describe.go @@ -2,6 +2,7 @@ package describe import ( "context" + "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/types" @@ -109,7 +110,10 @@ func outputResult(p *print.Printer, outputFormat string, organization *resourcem table.AddSeparator() table.AddRow("LABELS", utils.JoinStringMap(utils.PtrValue(organization.Labels), ": ", ", ")) - p.Outputln(table.Render()) + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } return nil }) } diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go index b41047a1a..bf5b3ceda 100644 --- a/internal/cmd/organization/list/list.go +++ b/internal/cmd/organization/list/list.go @@ -2,6 +2,7 @@ package list import ( "context" + "fmt" "github.com/stackitcloud/stackit-cli/internal/pkg/auth" "github.com/stackitcloud/stackit-cli/internal/pkg/config" @@ -73,8 +74,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } if resp == nil { - params.Printer.Outputln("list organizations: empty response") - return nil + return fmt.Errorf("list organizations: empty response") } return outputResult(params.Printer, model.OutputFormat, utils.PtrValue(resp.Items)) @@ -136,7 +136,10 @@ func outputResult(p *print.Printer, outputFormat string, organizations []resourc table.AddSeparator() } - p.Outputln(table.Render()) + err := table.Display(p) + if err != nil { + return fmt.Errorf("render table: %w", err) + } return nil }) } From ffd1351138c5732ca72d8fee975087ad52b17abf Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 15:51:15 +0100 Subject: [PATCH 05/10] updated examples --- docs/stackit_organization_list.md | 3 --- internal/cmd/organization/list/list.go | 4 ---- 2 files changed, 7 deletions(-) diff --git a/docs/stackit_organization_list.md b/docs/stackit_organization_list.md index e986fa5a4..a258807ba 100644 --- a/docs/stackit_organization_list.md +++ b/docs/stackit_organization_list.md @@ -16,9 +16,6 @@ stackit organization list [flags] Lists organizations for your user $ stackit organization list - Lists organizations for the user with the email foo@bar - $ stackit organization list --member foo@bar - Lists the first 10 organizations $ stackit organization list --limit 10 ``` diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go index bf5b3ceda..494d38d89 100644 --- a/internal/cmd/organization/list/list.go +++ b/internal/cmd/organization/list/list.go @@ -42,10 +42,6 @@ func NewCmd(params *types.CmdParams) *cobra.Command { `Lists organizations for your user`, "$ stackit organization list", ), - examples.NewExample( - `Lists organizations for the user with the email foo@bar`, - "$ stackit organization list --member foo@bar", - ), examples.NewExample( `Lists the first 10 organizations`, "$ stackit organization list --limit 10", From c6a020c01eb8f616653c52d31c7b6fb9d18b2e4b Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 16:02:54 +0100 Subject: [PATCH 06/10] select current profile and not default --- internal/cmd/organization/list/list.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go index 494d38d89..86090d5ce 100644 --- a/internal/cmd/organization/list/list.go +++ b/internal/cmd/organization/list/list.go @@ -54,7 +54,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command { return err } - model.Member = auth.GetProfileEmail(config.DefaultProfileName) + activeProfile, err := config.GetProfile() + if err != nil { + return fmt.Errorf("get profile: %w", err) + } + model.Member = auth.GetProfileEmail(activeProfile) // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) From f7884d2f1ae3d308bc8f61b7bb264147417b5dd2 Mon Sep 17 00:00:00 2001 From: Manuel Vaas <34416897+Manuelvaas@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:09:38 +0100 Subject: [PATCH 07/10] Update internal/cmd/organization/describe/describe.go Co-authored-by: cgoetz-inovex --- internal/cmd/organization/describe/describe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go index 80714c2e9..1b9fe3eab 100644 --- a/internal/cmd/organization/describe/describe.go +++ b/internal/cmd/organization/describe/describe.go @@ -30,7 +30,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "describe", Short: "Show a organization", - Long: "Show a organization.", + Long: "Show an organization.", // the arg can be the organization uuid or the container id, which is not a uuid, so no validation needed Args: args.SingleArg(organizationIdArg, nil), Example: examples.Build( From b8dcb2634f9593710b8b2911c6141ae3ac111e85 Mon Sep 17 00:00:00 2001 From: Manuel Vaas <34416897+Manuelvaas@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:09:48 +0100 Subject: [PATCH 08/10] Update internal/cmd/organization/describe/describe.go Co-authored-by: cgoetz-inovex --- internal/cmd/organization/describe/describe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/organization/describe/describe.go b/internal/cmd/organization/describe/describe.go index 1b9fe3eab..d17179c8e 100644 --- a/internal/cmd/organization/describe/describe.go +++ b/internal/cmd/organization/describe/describe.go @@ -29,7 +29,7 @@ type inputModel struct { func NewCmd(params *types.CmdParams) *cobra.Command { cmd := &cobra.Command{ Use: "describe", - Short: "Show a organization", + Short: "Show an organization", Long: "Show an organization.", // the arg can be the organization uuid or the container id, which is not a uuid, so no validation needed Args: args.SingleArg(organizationIdArg, nil), From c0177f33e8ae1cb88b52ff6839dea5758ac21740 Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 17:10:59 +0100 Subject: [PATCH 09/10] generated docs --- docs/stackit_organization.md | 2 +- docs/stackit_organization_describe.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/stackit_organization.md b/docs/stackit_organization.md index 2d58a4997..ad124b975 100644 --- a/docs/stackit_organization.md +++ b/docs/stackit_organization.md @@ -31,7 +31,7 @@ stackit organization [flags] ### SEE ALSO * [stackit](./stackit.md) - Manage STACKIT resources using the command line -* [stackit organization describe](./stackit_organization_describe.md) - Show a organization +* [stackit organization describe](./stackit_organization_describe.md) - Show an organization * [stackit organization list](./stackit_organization_list.md) - Lists all organizations * [stackit organization member](./stackit_organization_member.md) - Manages organization members * [stackit organization role](./stackit_organization_role.md) - Manages organization roles diff --git a/docs/stackit_organization_describe.md b/docs/stackit_organization_describe.md index 230c8777b..795cf38e5 100644 --- a/docs/stackit_organization_describe.md +++ b/docs/stackit_organization_describe.md @@ -1,10 +1,10 @@ ## stackit organization describe -Show a organization +Show an organization ### Synopsis -Show a organization. +Show an organization. ``` stackit organization describe [flags] From 0e5218e6b1454552c2fdcb839aca820beea6fa5c Mon Sep 17 00:00:00 2001 From: Manuel Vaas Date: Tue, 24 Feb 2026 17:17:34 +0100 Subject: [PATCH 10/10] check that limit is maximum 100 --- internal/cmd/organization/list/list.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/cmd/organization/list/list.go b/internal/cmd/organization/list/list.go index 86090d5ce..07a85c10b 100644 --- a/internal/cmd/organization/list/list.go +++ b/internal/cmd/organization/list/list.go @@ -92,10 +92,10 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, globalFlags := globalflags.Parse(p, cmd) limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) - if limit != nil && *limit < 1 { + if limit != nil && (*limit < 1 || *limit > 100) { return nil, &errors.FlagValidationError{ Flag: limitFlag, - Details: "must be greater than 0", + Details: "must be between 0 and 100", } }