From 30cc2e6e636aedce66272bcfa8c68c61ac23b65c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Feb 2026 10:24:02 +0100 Subject: [PATCH 01/10] add get repo command --- cmd/kosli/get.go | 1 + cmd/kosli/getRepo.go | 104 ++++++++++++++++++++++++++++++++++++++ cmd/kosli/getRepo_test.go | 86 +++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 cmd/kosli/getRepo.go create mode 100644 cmd/kosli/getRepo_test.go diff --git a/cmd/kosli/get.go b/cmd/kosli/get.go index 8ce43bafc..dd5905ab3 100644 --- a/cmd/kosli/get.go +++ b/cmd/kosli/get.go @@ -27,6 +27,7 @@ func newGetCmd(out io.Writer) *cobra.Command { newGetPolicyCmd(out), newGetAttestationTypeCmd(out), newGetAttestationCmd(out), + newGetRepoCmd(out), ) return cmd } diff --git a/cmd/kosli/getRepo.go b/cmd/kosli/getRepo.go new file mode 100644 index 000000000..528c59cb8 --- /dev/null +++ b/cmd/kosli/getRepo.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + neturl "net/url" + + "github.com/kosli-dev/cli/internal/output" + "github.com/kosli-dev/cli/internal/requests" + "github.com/spf13/cobra" +) + +const getRepoShortDesc = `Get a repo for an org.` + +const getRepoLongDesc = getRepoShortDesc + ` +The name of the repo is specified as an argument (e.g. "my-org/my-repo").` + +const getRepoExample = ` +# get a repo +kosli get repo my-org/my-repo \ + --api-token yourAPIToken \ + --org KosliOrgName` + +type getRepoOptions struct { + output string +} + +func newGetRepoCmd(out io.Writer) *cobra.Command { + o := new(getRepoOptions) + cmd := &cobra.Command{ + Use: "repo REPO-NAME", + Hidden: true, + Short: getRepoShortDesc, + Long: getRepoLongDesc, + Example: getRepoExample, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + err := RequireGlobalFlags(global, []string{"Org", "ApiToken"}) + if err != nil { + return ErrorBeforePrintingUsage(cmd, err.Error()) + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return o.run(out, args) + }, + } + + cmd.Flags().StringVarP(&o.output, "output", "o", "table", outputFlag) + + return cmd +} + +func (o *getRepoOptions) run(out io.Writer, args []string) error { + reqURL := fmt.Sprintf("%s/api/v2/repos/%s?name=%s", global.Host, global.Org, neturl.QueryEscape(args[0])) + + reqParams := &requests.RequestParams{ + Method: http.MethodGet, + URL: reqURL, + 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": printRepoAsTable, + "json": output.PrintJson, + }) +} + +func printRepoAsTable(raw string, out io.Writer, page int) error { + var response struct { + Embedded struct { + Repos []map[string]any `json:"repos"` + } `json:"_embedded"` + } + + err := json.Unmarshal([]byte(raw), &response) + if err != nil { + return err + } + + repos := response.Embedded.Repos + if len(repos) == 0 { + logger.Info("Repo was not found.") + return nil + } + + repo := repos[0] + rows := []string{ + fmt.Sprintf("Name:\t%s", repo["name"]), + fmt.Sprintf("URL:\t%s", repo["url"]), + fmt.Sprintf("Provider:\t%s", repo["provider"]), + fmt.Sprintf("Latest Activity:\t%s", repo["latest_activity"]), + } + + tabFormattedPrint(out, []string{}, rows) + return nil +} diff --git a/cmd/kosli/getRepo_test.go b/cmd/kosli/getRepo_test.go new file mode 100644 index 000000000..024996b2a --- /dev/null +++ b/cmd/kosli/getRepo_test.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/suite" +) + +// Define the suite, and absorb the built-in basic suite +// functionality from testify - including a T() method which +// returns the current testing context +type GetRepoCommandTestSuite struct { + suite.Suite + defaultKosliArguments string + acmeOrgKosliArguments string +} + +func (suite *GetRepoCommandTestSuite) SetupTest() { + global = &GlobalOpts{ + ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", + Org: "docs-cmd-test-user", + Host: "http://localhost:8001", + } + suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) + + global.Org = "acme-org" + global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" + suite.acmeOrgKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) + CreateFlowWithTemplate("get-repo", "testdata/valid_template.yml", suite.T()) + SetEnvVars(map[string]string{ + "GITHUB_RUN_NUMBER": "1234", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_REPOSITORY": "kosli-dev/cli", + "GITHUB_REPOSITORY_ID": "1234567890", + }, suite.T()) + BeginTrail("trail-name", "get-repo", "", suite.T()) +} + +func (suite *GetRepoCommandTestSuite) TearDownTest() { + UnSetEnvVars(map[string]string{ + "GITHUB_RUN_NUMBER": "", + "GITHUB_SERVER_URL": "", + "GITHUB_REPOSITORY": "", + "GITHUB_REPOSITORY_ID": "", + }, suite.T()) +} + +func (suite *GetRepoCommandTestSuite) TestGetRepoCmd() { + tests := []cmdTestCase{ + { + name: "01-getting a non-existing repo returns not-found message", + cmd: fmt.Sprintf(`get repo non-existing/repo %s`, suite.defaultKosliArguments), + golden: "Repo was not found.\n", + }, + { + name: "02-getting an existing repo works", + cmd: fmt.Sprintf(`get repo kosli-dev/cli %s`, suite.acmeOrgKosliArguments), + }, + { + name: "03-getting an existing repo with --output json works", + cmd: fmt.Sprintf(`get repo kosli-dev/cli --output json %s`, suite.acmeOrgKosliArguments), + goldenJson: []jsonCheck{{"_embedded.repos", "non-empty"}}, + }, + { + wantError: true, + name: "04-providing no argument fails", + cmd: fmt.Sprintf(`get repo %s`, suite.defaultKosliArguments), + golden: "Error: accepts 1 arg(s), received 0\n", + }, + { + wantError: true, + name: "05-providing more than one argument fails", + cmd: fmt.Sprintf(`get repo foo bar %s`, suite.defaultKosliArguments), + golden: "Error: accepts 1 arg(s), received 2\n", + }, + } + + runTestCmd(suite.T(), tests) +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestGetRepoCommandTestSuite(t *testing.T) { + suite.Run(t, new(GetRepoCommandTestSuite)) +} From 748e1b55ce3f40df7d89144164072dfda41ce0b2 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Feb 2026 12:45:18 +0100 Subject: [PATCH 02/10] add provider and repo-id filters to get and list repo commands --- cmd/kosli/getRepo.go | 38 ++++++++++++++++++++++++++++++++++--- cmd/kosli/getRepo_test.go | 23 ++++++++++++++++++++-- cmd/kosli/listRepos.go | 27 ++++++++++++++++++++++---- cmd/kosli/listRepos_test.go | 25 ++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/cmd/kosli/getRepo.go b/cmd/kosli/getRepo.go index 528c59cb8..e86469490 100644 --- a/cmd/kosli/getRepo.go +++ b/cmd/kosli/getRepo.go @@ -15,16 +15,26 @@ import ( const getRepoShortDesc = `Get a repo for an org.` const getRepoLongDesc = getRepoShortDesc + ` -The name of the repo is specified as an argument (e.g. "my-org/my-repo").` +The name of the repo is specified as an argument (e.g. "my-org/my-repo"). +Use --provider or --repo-id to narrow down the result when multiple repos +match the given name.` const getRepoExample = ` # get a repo kosli get repo my-org/my-repo \ + --api-token yourAPIToken \ + --org KosliOrgName + +# get a repo filtering by provider +kosli get repo my-org/my-repo \ + --provider github \ --api-token yourAPIToken \ --org KosliOrgName` type getRepoOptions struct { - output string + output string + provider string + repoID string } func newGetRepoCmd(out io.Writer) *cobra.Command { @@ -49,12 +59,22 @@ func newGetRepoCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.output, "output", "o", "table", outputFlag) + cmd.Flags().StringVar(&o.provider, "provider", "", "[optional] The VCS provider to filter repos by (e.g. github, gitlab).") + cmd.Flags().StringVar(&o.repoID, "repo-id", "", "[optional] The external repo ID to filter repos by.") return cmd } func (o *getRepoOptions) run(out io.Writer, args []string) error { - reqURL := fmt.Sprintf("%s/api/v2/repos/%s?name=%s", global.Host, global.Org, neturl.QueryEscape(args[0])) + params := neturl.Values{} + params.Set("name", args[0]) + if o.provider != "" { + params.Set("provider", o.provider) + } + if o.repoID != "" { + params.Set("repo_id", o.repoID) + } + reqURL := fmt.Sprintf("%s/api/v2/repos/%s?%s", global.Host, global.Org, params.Encode()) reqParams := &requests.RequestParams{ Method: http.MethodGet, @@ -66,6 +86,18 @@ func (o *getRepoOptions) run(out io.Writer, args []string) error { return err } + var parsed struct { + Embedded struct { + Repos []map[string]any `json:"repos"` + } `json:"_embedded"` + } + if err := json.Unmarshal([]byte(response.Body), &parsed); err != nil { + return err + } + if len(parsed.Embedded.Repos) > 1 { + return fmt.Errorf("found %d repos matching %q. Use --provider or --repo-id to narrow down the search", len(parsed.Embedded.Repos), args[0]) + } + return output.FormattedPrint(response.Body, o.output, out, 0, map[string]output.FormatOutputFunc{ "table": printRepoAsTable, diff --git a/cmd/kosli/getRepo_test.go b/cmd/kosli/getRepo_test.go index 024996b2a..e2c77e4cf 100644 --- a/cmd/kosli/getRepo_test.go +++ b/cmd/kosli/getRepo_test.go @@ -62,15 +62,34 @@ func (suite *GetRepoCommandTestSuite) TestGetRepoCmd() { cmd: fmt.Sprintf(`get repo kosli-dev/cli --output json %s`, suite.acmeOrgKosliArguments), goldenJson: []jsonCheck{{"_embedded.repos", "non-empty"}}, }, + { + name: "04-getting an existing repo with matching --provider works", + cmd: fmt.Sprintf(`get repo kosli-dev/cli --provider github %s`, suite.acmeOrgKosliArguments), + }, + { + name: "05-getting an existing repo with matching --provider and --output json works", + cmd: fmt.Sprintf(`get repo kosli-dev/cli --provider github --output json %s`, suite.acmeOrgKosliArguments), + goldenJson: []jsonCheck{{"_embedded.repos", "non-empty"}}, + }, + { + name: "06-getting a repo with a non-matching --provider returns not-found message", + cmd: fmt.Sprintf(`get repo kosli-dev/cli --provider gitlab %s`, suite.acmeOrgKosliArguments), + golden: "Repo was not found.\n", + }, + { + name: "07-getting a repo with a non-matching --repo-id returns not-found message", + cmd: fmt.Sprintf(`get repo kosli-dev/cli --repo-id non-existing-id %s`, suite.acmeOrgKosliArguments), + golden: "Repo was not found.\n", + }, { wantError: true, - name: "04-providing no argument fails", + name: "08-providing no argument fails", cmd: fmt.Sprintf(`get repo %s`, suite.defaultKosliArguments), golden: "Error: accepts 1 arg(s), received 0\n", }, { wantError: true, - name: "05-providing more than one argument fails", + name: "09-providing more than one argument fails", cmd: fmt.Sprintf(`get repo foo bar %s`, suite.defaultKosliArguments), golden: "Error: accepts 1 arg(s), received 2\n", }, diff --git a/cmd/kosli/listRepos.go b/cmd/kosli/listRepos.go index db81d91b6..c505b2563 100644 --- a/cmd/kosli/listRepos.go +++ b/cmd/kosli/listRepos.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + neturl "net/url" "github.com/kosli-dev/cli/internal/output" "github.com/kosli-dev/cli/internal/requests" @@ -15,6 +16,9 @@ const listReposDesc = `List repos for an org.` type listReposOptions struct { listOptions + name string + provider string + repoID string } func newListReposCmd(out io.Writer) *cobra.Command { @@ -38,16 +42,31 @@ func newListReposCmd(out io.Writer) *cobra.Command { } addListFlags(cmd, &o.listOptions) + cmd.Flags().StringVar(&o.name, "name", "", "[optional] The repo name to filter by.") + cmd.Flags().StringVar(&o.provider, "provider", "", "[optional] The VCS provider to filter repos by (e.g. github, gitlab).") + cmd.Flags().StringVar(&o.repoID, "repo-id", "", "[optional] The external repo ID to filter repos by.") return cmd } func (o *listReposOptions) run(out io.Writer) error { - url := fmt.Sprintf("%s/api/v2/repos/%s?page=%d&per_page=%d", global.Host, global.Org, o.pageNumber, o.pageLimit) + params := neturl.Values{} + params.Set("page", fmt.Sprintf("%d", o.pageNumber)) + params.Set("per_page", fmt.Sprintf("%d", o.pageLimit)) + if o.name != "" { + params.Set("name", o.name) + } + if o.provider != "" { + params.Set("provider", o.provider) + } + if o.repoID != "" { + params.Set("repo_id", o.repoID) + } + reqURL := fmt.Sprintf("%s/api/v2/repos/%s?%s", global.Host, global.Org, params.Encode()) reqParams := &requests.RequestParams{ Method: http.MethodGet, - URL: url, + URL: reqURL, Token: global.ApiToken, } response, err := kosliClient.Do(reqParams) @@ -81,10 +100,10 @@ func printReposListAsTable(raw string, out io.Writer, page int) error { return nil } - header := []string{"NAME", "URL", "LAST_ACTIVITY"} + header := []string{"NAME", "URL", "PROVIDER", "LAST_ACTIVITY"} rows := []string{} for _, repo := range repos { - row := fmt.Sprintf("%s\t%s\t%s", repo["name"], repo["url"], repo["latest_activity"]) + row := fmt.Sprintf("%s\t%s\t%s\t%s", repo["name"], repo["url"], repo["provider"], repo["latest_activity"]) rows = append(rows, row) } tabFormattedPrint(out, header, rows) diff --git a/cmd/kosli/listRepos_test.go b/cmd/kosli/listRepos_test.go index d3379b551..614230c56 100644 --- a/cmd/kosli/listRepos_test.go +++ b/cmd/kosli/listRepos_test.go @@ -93,6 +93,31 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { cmd: fmt.Sprintf(`list repos --page-limit 15 --page 2 %s`, suite.defaultKosliArguments), golden: "", }, + { + name: "09-listing repos with --name filter works", + cmd: fmt.Sprintf(`list repos --name kosli-dev/cli %s`, suite.acmeOrgKosliArguments), + goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli Trail Started at.*", + }, + { + name: "10-listing repos with --name filter and --output json works", + cmd: fmt.Sprintf(`list repos --name kosli-dev/cli --output json %s`, suite.acmeOrgKosliArguments), + goldenJson: []jsonCheck{{"_embedded.repos", "non-empty"}}, + }, + { + name: "11-listing repos with --provider filter works", + cmd: fmt.Sprintf(`list repos --provider github %s`, suite.acmeOrgKosliArguments), + goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli Trail Started at.*", + }, + { + name: "12-listing repos with non-matching --provider returns no repos message", + cmd: fmt.Sprintf(`list repos --provider gitlab %s`, suite.acmeOrgKosliArguments), + golden: "No repos were found.\n", + }, + { + name: "13-listing repos with non-matching --repo-id returns no repos message", + cmd: fmt.Sprintf(`list repos --repo-id non-existing-id %s`, suite.acmeOrgKosliArguments), + golden: "No repos were found.\n", + }, } runTestCmd(suite.T(), tests) From 9909566b3a2ac9d07a787fb0178fdc920022c884 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 20 Feb 2026 12:45:35 +0100 Subject: [PATCH 03/10] correct comment --- internal/requests/requests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 4f648ebed..d6830d5ff 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -320,7 +320,7 @@ func (c *Client) PayloadOutput(req *http.Request, jsonFields map[string]any, mes func customCheckRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { // Get the default retry policy for errors and certain status codes. - // It will retry on 5xx, 429 and some special cases + // It will retry on 5xx, 409 and some special cases shouldRetry, retryErr := retryablehttp.DefaultRetryPolicy(ctx, resp, err) if retryErr != nil { return false, retryErr From a060ecbe345ca972fc758e09c28168445639da73 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 10:06:59 +0100 Subject: [PATCH 04/10] fix linting issue --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fba865b94..cb97a9952 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ARG ALPINE_VERSION="3.21" ### Go Builder ### -FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} as builder +FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS builder RUN apk add --update --no-cache git bash make ca-certificates From b8b03b3ea9769fff2400cc921597eddda292418a Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 10:07:25 +0100 Subject: [PATCH 05/10] change the org used in list repos test --- cmd/kosli/listRepos_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kosli/listRepos_test.go b/cmd/kosli/listRepos_test.go index 614230c56..233a283b5 100644 --- a/cmd/kosli/listRepos_test.go +++ b/cmd/kosli/listRepos_test.go @@ -24,7 +24,7 @@ func (suite *ListReposCommandTestSuite) SetupTest() { } suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) - global.Org = "acme-org" + global.Org = "acme-org-shared" global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" suite.acmeOrgKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) CreateFlowWithTemplate("list-repos", "testdata/valid_template.yml", suite.T()) From c6bf9360e5cea9754e0de3f6f310d921052f7944 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 10:10:35 +0100 Subject: [PATCH 06/10] update indirect dep cloudflare/circl to fix a vulnerability --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ef1302157..c94a94b0d 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect github.com/containers/ocicrypt v1.2.1 // indirect github.com/containers/storage v1.57.2 // indirect diff --git a/go.sum b/go.sum index b5965735d..a33f1d1f6 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containers/image/v5 v5.34.3 h1:/cMgfyA4Y7ILH7nzWP/kqpkE5Df35Ek4bp5ZPvJOVmI= From 7d92025e86df14b0752bdf1734a659b83851a9c7 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 10:22:17 +0100 Subject: [PATCH 07/10] add missing provider in the expected output --- cmd/kosli/listRepos_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kosli/listRepos_test.go b/cmd/kosli/listRepos_test.go index 233a283b5..9ca36765a 100644 --- a/cmd/kosli/listRepos_test.go +++ b/cmd/kosli/listRepos_test.go @@ -57,7 +57,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "02-listing repos works when there are no repos", cmd: fmt.Sprintf(`list repos %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", }, { name: "03-listing repos with --output json works when there are repos", @@ -96,7 +96,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "09-listing repos with --name filter works", cmd: fmt.Sprintf(`list repos --name kosli-dev/cli %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", }, { name: "10-listing repos with --name filter and --output json works", @@ -106,7 +106,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "11-listing repos with --provider filter works", cmd: fmt.Sprintf(`list repos --provider github %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", }, { name: "12-listing repos with non-matching --provider returns no repos message", From b9f15a003c273180d7c3a546aafcc67cb93ecd85 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 10:38:11 +0100 Subject: [PATCH 08/10] fix whitespace mismatching --- cmd/kosli/listRepos_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kosli/listRepos_test.go b/cmd/kosli/listRepos_test.go index 9ca36765a..8c2200af1 100644 --- a/cmd/kosli/listRepos_test.go +++ b/cmd/kosli/listRepos_test.go @@ -57,7 +57,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "02-listing repos works when there are no repos", cmd: fmt.Sprintf(`list repos %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli.*https://github.com/kosli-dev/cli.*github.*Trail Started at.*", }, { name: "03-listing repos with --output json works when there are repos", @@ -96,7 +96,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "09-listing repos with --name filter works", cmd: fmt.Sprintf(`list repos --name kosli-dev/cli %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli.*https://github.com/kosli-dev/cli.*github.*Trail Started at.*", }, { name: "10-listing repos with --name filter and --output json works", @@ -106,7 +106,7 @@ func (suite *ListReposCommandTestSuite) TestListReposCmd() { { name: "11-listing repos with --provider filter works", cmd: fmt.Sprintf(`list repos --provider github %s`, suite.acmeOrgKosliArguments), - goldenRegex: ".*\nkosli-dev/cli https://github.com/kosli-dev/cli github Trail Started at.*", + goldenRegex: ".*\nkosli-dev/cli.*https://github.com/kosli-dev/cli.*github.*Trail Started at.*", }, { name: "12-listing repos with non-matching --provider returns no repos message", From b7a3d10571d40125f7ad0b02880ba97042072204 Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Thu, 26 Feb 2026 11:25:48 +0100 Subject: [PATCH 09/10] ensure get repos tests use a different org --- cmd/kosli/getRepo_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/kosli/getRepo_test.go b/cmd/kosli/getRepo_test.go index e2c77e4cf..6554fb7e7 100644 --- a/cmd/kosli/getRepo_test.go +++ b/cmd/kosli/getRepo_test.go @@ -24,8 +24,8 @@ func (suite *GetRepoCommandTestSuite) SetupTest() { } suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) - global.Org = "acme-org" - global.ApiToken = "v3OWZiYWu9G2IMQStYg9BcPQUQ88lJNNnTJTNq8jfvmkR1C5wVpHSs7F00JcB5i6OGeUzrKt3CwRq7ndcN4TTfMeo8ASVJ5NdHpZT7DkfRfiFvm8s7GbsIHh2PtiQJYs2UoN13T8DblV5C4oKb6-yWH73h67OhotPlKfVKazR-c" + global.Org = "iu-org-shared" + global.ApiToken = "qM9u2_grv6pJLbACwsMMMT5LIQy82tQj2k1zjZnlXti1smnFaGwCKW4jzk0La7ae9RrSYvEwCXSsXknD6YZqd-onLaaIUUKtEn6-B6yh53vWIe9EC5u85FCbKZjFbaicp_d0Me0Zcqq_KcCgrAZRX9xggl_pBb2oaCsNdllqNjk" suite.acmeOrgKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) CreateFlowWithTemplate("get-repo", "testdata/valid_template.yml", suite.T()) SetEnvVars(map[string]string{ From 376f5128bb55e56b2b5476de27ec478b696ace6c Mon Sep 17 00:00:00 2001 From: Sami Alajrami Date: Fri, 27 Feb 2026 10:42:11 +0100 Subject: [PATCH 10/10] update comments for clarity --- internal/requests/requests.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index d6830d5ff..c444afb9a 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -320,7 +320,7 @@ func (c *Client) PayloadOutput(req *http.Request, jsonFields map[string]any, mes func customCheckRetry(ctx context.Context, resp *http.Response, err error) (bool, error) { // Get the default retry policy for errors and certain status codes. - // It will retry on 5xx, 409 and some special cases + // It will retry on 5xx, 429 (rate limit) and we add 409 (lock conflict) via a custom check. shouldRetry, retryErr := retryablehttp.DefaultRetryPolicy(ctx, resp, err) if retryErr != nil { return false, retryErr @@ -328,7 +328,7 @@ func customCheckRetry(ctx context.Context, resp *http.Response, err error) (bool if shouldRetry { return true, nil } - // The sever gives 409 if we have a lock conflict. + // The server gives 409 if we have a lock conflict. if resp != nil && resp.StatusCode == 409 { return true, nil }