From 194b0bfccd8bc8dedbec62bf057a58a698bb870d Mon Sep 17 00:00:00 2001 From: Marko Bevc Date: Mon, 29 Jun 2026 11:26:24 +0100 Subject: [PATCH 1/2] feat(get): add default-org to fetch user default organization Adds `kosli get default-org` which calls `GET /api/v2/user/default-org` to retrieve the default organization for the currently authenticated user. Supports table (default) and json output. --- cmd/kosli/get.go | 1 + cmd/kosli/getDefaultOrg.go | 91 +++++++++++++++++++++++++++++++++ cmd/kosli/getDefaultOrg_test.go | 74 +++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 cmd/kosli/getDefaultOrg.go create mode 100644 cmd/kosli/getDefaultOrg_test.go diff --git a/cmd/kosli/get.go b/cmd/kosli/get.go index cb31a4ce1..a330c9c99 100644 --- a/cmd/kosli/get.go +++ b/cmd/kosli/get.go @@ -29,6 +29,7 @@ func newGetCmd(out io.Writer) *cobra.Command { newGetAttestationCmd(out), newGetRepoCmd(out), newGetServiceAccountCmd(out), + newGetDefaultOrgCmd(out), ) return cmd } diff --git a/cmd/kosli/getDefaultOrg.go b/cmd/kosli/getDefaultOrg.go new file mode 100644 index 000000000..a4f549ca1 --- /dev/null +++ b/cmd/kosli/getDefaultOrg.go @@ -0,0 +1,91 @@ +package main + +import ( + "encoding/json" + "io" + "net/http" + "net/url" + + "github.com/kosli-dev/cli/internal/output" + "github.com/kosli-dev/cli/internal/requests" + "github.com/spf13/cobra" +) + +const getDefaultOrgShortDesc = `Get the default organization for the current user.` + +const getDefaultOrgLongDesc = getDefaultOrgShortDesc + ` +The default organization is used by Kosli Web UI when logging in.` + +const getDefaultOrgExample = ` +# get the default organization for the current user: +kosli get default-org \ + --api-token yourAPIToken +` + +type getDefaultOrgOptions struct { + output string +} + +// defaultOrg models the response of GET api/v2/user/default-org. +type defaultOrg struct { + DefaultOrgName string `json:"default_org_name"` +} + +func newGetDefaultOrgCmd(out io.Writer) *cobra.Command { + o := new(getDefaultOrgOptions) + cmd := &cobra.Command{ + Use: "default-org", + Short: getDefaultOrgShortDesc, + Long: getDefaultOrgLongDesc, + Example: getDefaultOrgExample, + Args: cobra.NoArgs, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := RequireGlobalFlags(global, []string{"ApiToken"}); err != nil { + return ErrorBeforePrintingUsage(cmd, err.Error()) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return o.run(out) + }, + } + + cmd.Flags().StringVarP(&o.output, "output", "o", "table", outputFlag) + + return cmd +} + +func (o *getDefaultOrgOptions) run(out io.Writer) error { + url, err := url.JoinPath(global.Host, "api/v2/user/default-org") + if err != nil { + return err + } + + reqParams := &requests.RequestParams{ + Method: http.MethodGet, + URL: url, + Token: global.ApiToken, + } + response, err := kosliClient.Do(reqParams) + if err != nil { + return err + } + + return output.FormattedPrint(response.Body, o.output, out, 0, + map[string]output.FormatOutputFunc{ + "table": printDefaultOrgAsTable, + "json": output.PrintJson, + }) +} + +// printDefaultOrgAsTable renders the default organization name. +func printDefaultOrgAsTable(raw string, out io.Writer, page int) error { + var org defaultOrg + if err := json.Unmarshal([]byte(raw), &org); err != nil { + return err + } + + rows := []string{"Default organization:\t" + org.DefaultOrgName} + tabFormattedPrint(out, []string{}, rows) + return nil +} diff --git a/cmd/kosli/getDefaultOrg_test.go b/cmd/kosli/getDefaultOrg_test.go new file mode 100644 index 000000000..ffe1cffcf --- /dev/null +++ b/cmd/kosli/getDefaultOrg_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "bytes" + "fmt" + "testing" + + "github.com/maxcnunes/httpfake" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +func TestPrintDefaultOrgAsTable(t *testing.T) { + raw := `{"default_org_name":"test-org"}` + + var buf bytes.Buffer + err := printDefaultOrgAsTable(raw, &buf, 0) + require.NoError(t, err) + + out := buf.String() + for _, want := range []string{"Default organization:", "test-org"} { + require.Contains(t, out, want) + } +} + +type GetDefaultOrgCommandTestSuite struct { + suite.Suite + defaultKosliArguments string +} + +func (suite *GetDefaultOrgCommandTestSuite) SetupTest() { + global = &GlobalOpts{ + ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", + Host: "http://localhost:8001", + } + suite.defaultKosliArguments = fmt.Sprintf(" --host %s --api-token %s", global.Host, global.ApiToken) +} + +func (suite *GetDefaultOrgCommandTestSuite) TestGetDefaultOrgCmd() { + fake := httpfake.New() + defer fake.Close() + fake.NewHandler(). + Get("/api/v2/user/default-org"). + Reply(200). + BodyString(`{"default_org_name":"test-org"}`) + + args := fmt.Sprintf(" --host %s --api-token %s", fake.Server.URL, global.ApiToken) + tests := []cmdTestCase{ + { + wantError: false, + name: "get default-org prints the org name as a table", + cmd: "get default-org" + args, + goldenRegex: `Default organization:\s+test-org`, + }, + { + wantError: false, + name: "get default-org supports --output json", + cmd: "get default-org --output json" + args, + goldenRegex: `(?s)"default_org_name":\s*"test-org"`, + }, + { + wantError: true, + name: "get default-org fails when an argument is provided", + cmd: "get default-org extra-arg" + args, + golden: "Error: unknown command \"extra-arg\" for \"kosli get default-org\"\n", + }, + } + + runTestCmd(suite.T(), tests) +} + +func TestGetDefaultOrgCommandTestSuite(t *testing.T) { + suite.Run(t, new(GetDefaultOrgCommandTestSuite)) +} From 77e6c21959b37ecdd23f087b0b35d2a2cb81de28 Mon Sep 17 00:00:00 2001 From: Marko Bevc Date: Mon, 29 Jun 2026 12:45:49 +0100 Subject: [PATCH 2/2] chore: expand default-org description and provide better output on non-set default org --- cmd/kosli/getDefaultOrg.go | 8 ++++++-- cmd/kosli/getDefaultOrg_test.go | 9 +++++++++ cmd/kosli/updateDefaultOrg.go | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd/kosli/getDefaultOrg.go b/cmd/kosli/getDefaultOrg.go index a4f549ca1..e89f4e114 100644 --- a/cmd/kosli/getDefaultOrg.go +++ b/cmd/kosli/getDefaultOrg.go @@ -14,7 +14,7 @@ import ( const getDefaultOrgShortDesc = `Get the default organization for the current user.` const getDefaultOrgLongDesc = getDefaultOrgShortDesc + ` -The default organization is used by Kosli Web UI when logging in.` +The default organization is the one selected by default in the Kosli Web UI when you log in.` const getDefaultOrgExample = ` # get the default organization for the current user: @@ -85,7 +85,11 @@ func printDefaultOrgAsTable(raw string, out io.Writer, page int) error { return err } - rows := []string{"Default organization:\t" + org.DefaultOrgName} + name := org.DefaultOrgName + if name == "" { + name = "(none set)" + } + rows := []string{"Default organization:\t" + name} tabFormattedPrint(out, []string{}, rows) return nil } diff --git a/cmd/kosli/getDefaultOrg_test.go b/cmd/kosli/getDefaultOrg_test.go index ffe1cffcf..f4ff2f720 100644 --- a/cmd/kosli/getDefaultOrg_test.go +++ b/cmd/kosli/getDefaultOrg_test.go @@ -23,6 +23,15 @@ func TestPrintDefaultOrgAsTable(t *testing.T) { } } +func TestPrintDefaultOrgAsTableNoneSet(t *testing.T) { + raw := `{"default_org_name":""}` + + var buf bytes.Buffer + err := printDefaultOrgAsTable(raw, &buf, 0) + require.NoError(t, err) + require.Regexp(t, `Default organization:\s+\(none set\)`, buf.String()) +} + type GetDefaultOrgCommandTestSuite struct { suite.Suite defaultKosliArguments string diff --git a/cmd/kosli/updateDefaultOrg.go b/cmd/kosli/updateDefaultOrg.go index eba1ab28f..7a26bd59a 100644 --- a/cmd/kosli/updateDefaultOrg.go +++ b/cmd/kosli/updateDefaultOrg.go @@ -12,7 +12,7 @@ import ( const updateDefaultOrgShortDesc = `Set the default organization for the current user.` const updateDefaultOrgLongDesc = updateDefaultOrgShortDesc + ` -The default organization is used by Kosli Web UI when logging in. +The default organization is the one selected by default in the Kosli Web UI when you log in. ` const updateDefaultOrgExample = `