From 735003edf7d0b0a02e145faf8125b18529a2be52 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 11:36:33 +0100 Subject: [PATCH 1/7] Add repo flag to log env command --- cmd/kosli/logEnvironment.go | 15 +++++++++++++-- cmd/kosli/logEnvironment_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index b88db66be..e83324448 100644 --- a/cmd/kosli/logEnvironment.go +++ b/cmd/kosli/logEnvironment.go @@ -45,12 +45,19 @@ kosli log environment yourEnvironmentName \ --api-token yourAPIToken \ --org yourOrgName \ --output json + +# list events for an environment filtered by repo: +kosli log environment yourEnvironmentName \ + --repo yourOrg/yourRepo \ + --api-token yourAPIToken \ + --org yourOrgName ` type logEnvironmentOptions struct { listOptions reverse bool interval string + repo string } func newLogEnvironmentCmd(out io.Writer) *cobra.Command { @@ -76,6 +83,7 @@ func newLogEnvironmentCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.interval, "interval", "i", "", intervalFlag) + cmd.Flags().StringVar(&o.repo, "repo", "", repoNameFlag) addListFlags(cmd, &o.listOptions) cmd.Flags().BoolVar(&o.reverse, "reverse", false, reverseFlag) @@ -92,12 +100,15 @@ func (o *logEnvironmentOptions) run(out io.Writer, args []string) error { // events func (o *logEnvironmentOptions) getEnvironmentEvents(out io.Writer, envName, interval string) error { - url := fmt.Sprintf("%s/api/v2/environments/%s/%s/events?page=%d&per_page=%d&interval=%s&reverse=%t", + eventsURL := fmt.Sprintf("%s/api/v2/environments/%s/%s/events?page=%d&per_page=%d&interval=%s&reverse=%t", global.Host, global.Org, envName, o.pageNumber, o.pageLimit, url.QueryEscape(interval), o.reverse) + if o.repo != "" { + eventsURL = eventsURL + "&repo_name=" + url.QueryEscape(o.repo) + } reqParams := &requests.RequestParams{ Method: http.MethodGet, - URL: url, + URL: eventsURL, Token: global.ApiToken, } response, err := kosliClient.Do(reqParams) diff --git a/cmd/kosli/logEnvironment_test.go b/cmd/kosli/logEnvironment_test.go index 5a6dbec6b..2510d6972 100644 --- a/cmd/kosli/logEnvironment_test.go +++ b/cmd/kosli/logEnvironment_test.go @@ -4,6 +4,7 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -16,12 +17,17 @@ type LogEnvironmentCommandTestSuite struct { eventsEnvName string firstArtifactPath string secondArtifactPath string + flowName string + artifactName string + fingerprint string + repoName string } func (suite *LogEnvironmentCommandTestSuite) SetupTest() { suite.eventsEnvName = "list-events-env" suite.firstArtifactPath = "testdata/report.xml" suite.secondArtifactPath = "testdata/file1" + suite.flowName = "list-events-flow" global = &GlobalOpts{ ApiToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6ImNkNzg4OTg5In0.e8i_lA_QrEhFncb05Xw6E_tkCHU9QfcY4OLTVUCHffY", @@ -29,6 +35,23 @@ func (suite *LogEnvironmentCommandTestSuite) SetupTest() { Host: "http://localhost:8001", } suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) + + suite.artifactName = "arti" + CreateFlow(suite.flowName, suite.T()) + fingerprintOptions := &fingerprintOptions{ + artifactType: "file", + } + var err error + suite.fingerprint, err = GetSha256Digest(suite.firstArtifactPath, fingerprintOptions, logger) + require.NoError(suite.T(), err) + suite.repoName = "kosli/dev" + SetEnvVars(map[string]string{ + "GITHUB_RUN_NUMBER": "1234", + "GITHUB_SERVER_URL": "https://github.com", + "GITHUB_REPOSITORY": suite.repoName, + "GITHUB_REPOSITORY_ID": "1234567890", + }, suite.T()) + CreateArtifactOnTrail(suite.flowName, "trail-1", "backend", suite.fingerprint, suite.artifactName, suite.T()) CreateEnv(global.Org, suite.eventsEnvName, "server", suite.T()) } @@ -105,6 +128,13 @@ func (suite *LogEnvironmentCommandTestSuite) TestLogEnvironmentCmd() { reportToEnv: true, }, }, + { + name: "listing events with --repo returns events filtered by repo", + cmd: fmt.Sprintf(`log env %s --repo %s %s`, suite.eventsEnvName, suite.repoName, suite.defaultKosliArguments), + additionalConfig: listSnapshotsTestConfig{ + reportToEnv: true, + }, + }, } for _, t := range tests { From ffd925b6de4a95fc33ec5870cd08be9acb89253c Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 15:15:51 +0100 Subject: [PATCH 2/7] Add repo flag to log environment command --- cmd/kosli/logEnvironment.go | 10 ++++++---- cmd/kosli/logEnvironment_test.go | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index e83324448..cf7011071 100644 --- a/cmd/kosli/logEnvironment.go +++ b/cmd/kosli/logEnvironment.go @@ -57,7 +57,7 @@ type logEnvironmentOptions struct { listOptions reverse bool interval string - repo string + repos []string } func newLogEnvironmentCmd(out io.Writer) *cobra.Command { @@ -83,7 +83,7 @@ func newLogEnvironmentCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.interval, "interval", "i", "", intervalFlag) - cmd.Flags().StringVar(&o.repo, "repo", "", repoNameFlag) + cmd.Flags().StringSliceVar(&o.repos, "repo", []string{}, repoNameFlag) addListFlags(cmd, &o.listOptions) cmd.Flags().BoolVar(&o.reverse, "reverse", false, reverseFlag) @@ -102,8 +102,10 @@ func (o *logEnvironmentOptions) run(out io.Writer, args []string) error { func (o *logEnvironmentOptions) getEnvironmentEvents(out io.Writer, envName, interval string) error { eventsURL := fmt.Sprintf("%s/api/v2/environments/%s/%s/events?page=%d&per_page=%d&interval=%s&reverse=%t", global.Host, global.Org, envName, o.pageNumber, o.pageLimit, url.QueryEscape(interval), o.reverse) - if o.repo != "" { - eventsURL = eventsURL + "&repo_name=" + url.QueryEscape(o.repo) + for _, repo := range o.repos { + if repo != "" { + eventsURL = eventsURL + "&repo_name=" + url.QueryEscape(repo) + } } reqParams := &requests.RequestParams{ diff --git a/cmd/kosli/logEnvironment_test.go b/cmd/kosli/logEnvironment_test.go index 2510d6972..6508e47bd 100644 --- a/cmd/kosli/logEnvironment_test.go +++ b/cmd/kosli/logEnvironment_test.go @@ -135,6 +135,13 @@ func (suite *LogEnvironmentCommandTestSuite) TestLogEnvironmentCmd() { reportToEnv: true, }, }, + { + name: "listing events with several --repo flags filters by all provided repos", + cmd: fmt.Sprintf(`log env %s --repo other/repo --repo %s %s --debug`, suite.eventsEnvName, suite.repoName, suite.defaultKosliArguments), + additionalConfig: listSnapshotsTestConfig{ + reportToEnv: true, + }, + }, } for _, t := range tests { From 5f89ca1cfb360085e784ea8ec9f691995fc1af75 Mon Sep 17 00:00:00 2001 From: SimonC Date: Mon, 9 Feb 2026 15:25:08 +0100 Subject: [PATCH 3/7] Update cmd/kosli/logEnvironment.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/kosli/logEnvironment.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index cf7011071..5aff23028 100644 --- a/cmd/kosli/logEnvironment.go +++ b/cmd/kosli/logEnvironment.go @@ -51,6 +51,13 @@ kosli log environment yourEnvironmentName \ --repo yourOrg/yourRepo \ --api-token yourAPIToken \ --org yourOrgName + +# list events for an environment filtered by multiple repos: +kosli log environment yourEnvironmentName \ + --repo yourOrg/yourRepo1 \ + --repo yourOrg/yourRepo2 \ + --api-token yourAPIToken \ + --org yourOrgName ` type logEnvironmentOptions struct { From 4c69c43ee52a8e05cde9771311a1afdecdc7833f Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 15:27:51 +0100 Subject: [PATCH 4/7] Add note that repoNameFlag is optional --- cmd/kosli/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kosli/root.go b/cmd/kosli/root.go index b81054ba5..9fef06b60 100644 --- a/cmd/kosli/root.go +++ b/cmd/kosli/root.go @@ -261,7 +261,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file, getAttestationTrailNameFlag = "[conditional] The name of the Kosli trail for the attestation. Cannot be used together with --fingerprint or --attestation-id." getAttestationFlowNameFlag = "[conditional] The name of the Kosli flow for the attestation. Required if ATTESTATION-NAME provided. Cannot be used together with --attestation-id." attestationIDFlag = "[conditional] The unique identifier of the attestation to retrieve. Cannot be used together with ATTESTATION-NAME." - repoNameFlag = "The name of a git repo as it is registered in Kosli. e.g kosli-dev/cli" + repoNameFlag = "[optional] The name of a git repo as it is registered in Kosli. e.g kosli-dev/cli" ) var global *GlobalOpts From 5b6803316620b63a23adbb1df9f956b1e1a01e0d Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 15:48:59 +0100 Subject: [PATCH 5/7] Update how url is built --- cmd/kosli/logEnvironment.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index 5aff23028..f230a3a86 100644 --- a/cmd/kosli/logEnvironment.go +++ b/cmd/kosli/logEnvironment.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "net/url" + "strconv" "github.com/kosli-dev/cli/internal/output" "github.com/kosli-dev/cli/internal/requests" @@ -107,17 +108,26 @@ func (o *logEnvironmentOptions) run(out io.Writer, args []string) error { // events func (o *logEnvironmentOptions) getEnvironmentEvents(out io.Writer, envName, interval string) error { - eventsURL := fmt.Sprintf("%s/api/v2/environments/%s/%s/events?page=%d&per_page=%d&interval=%s&reverse=%t", - global.Host, global.Org, envName, o.pageNumber, o.pageLimit, url.QueryEscape(interval), o.reverse) + baseURL := fmt.Sprintf("%s/api/v2/environments/%s/%s/events", global.Host, global.Org, envName) + u, err := url.Parse(baseURL) + if err != nil { + return fmt.Errorf("failed to parse events URL: %w", err) + } + q := u.Query() + q.Set("page", strconv.Itoa(o.pageNumber)) + q.Set("per_page", strconv.Itoa(o.pageLimit)) + q.Set("interval", interval) + q.Set("reverse", strconv.FormatBool(o.reverse)) for _, repo := range o.repos { if repo != "" { - eventsURL = eventsURL + "&repo_name=" + url.QueryEscape(repo) + q.Add("repo_name", repo) } } + u.RawQuery = q.Encode() reqParams := &requests.RequestParams{ Method: http.MethodGet, - URL: eventsURL, + URL: u.String(), Token: global.ApiToken, } response, err := kosliClient.Do(reqParams) From 794f791b01ab0f0bdbe1e290f158ab8281192ced Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 16:19:02 +0100 Subject: [PATCH 6/7] Refactor url building to use JoinPath instead of string formatting --- cmd/kosli/logEnvironment.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index f230a3a86..3d3422dc9 100644 --- a/cmd/kosli/logEnvironment.go +++ b/cmd/kosli/logEnvironment.go @@ -108,10 +108,13 @@ func (o *logEnvironmentOptions) run(out io.Writer, args []string) error { // events func (o *logEnvironmentOptions) getEnvironmentEvents(out io.Writer, envName, interval string) error { - baseURL := fmt.Sprintf("%s/api/v2/environments/%s/%s/events", global.Host, global.Org, envName) - u, err := url.Parse(baseURL) + u, err := url.Parse(global.Host) if err != nil { - return fmt.Errorf("failed to parse events URL: %w", err) + return fmt.Errorf("failed to parse host URL: %w", err) + } + u.Path, err = url.JoinPath(u.Path, "/api/v2/environments", global.Org, envName, "events") + if err != nil { + return fmt.Errorf("failed to join URL path: %w", err) } q := u.Query() q.Set("page", strconv.Itoa(o.pageNumber)) From 109ead3d476d41505d3eefa2aeea9d4a8c2a1009 Mon Sep 17 00:00:00 2001 From: Simon Castagna Date: Mon, 9 Feb 2026 16:50:22 +0100 Subject: [PATCH 7/7] Fix integration test to create repo for second artifact --- cmd/kosli/logEnvironment_test.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/kosli/logEnvironment_test.go b/cmd/kosli/logEnvironment_test.go index 6508e47bd..f8b181cd6 100644 --- a/cmd/kosli/logEnvironment_test.go +++ b/cmd/kosli/logEnvironment_test.go @@ -18,9 +18,10 @@ type LogEnvironmentCommandTestSuite struct { firstArtifactPath string secondArtifactPath string flowName string - artifactName string fingerprint string + secondFingerprint string repoName string + secondRepoName string } func (suite *LogEnvironmentCommandTestSuite) SetupTest() { @@ -36,7 +37,6 @@ func (suite *LogEnvironmentCommandTestSuite) SetupTest() { } suite.defaultKosliArguments = fmt.Sprintf(" --host %s --org %s --api-token %s", global.Host, global.Org, global.ApiToken) - suite.artifactName = "arti" CreateFlow(suite.flowName, suite.T()) fingerprintOptions := &fingerprintOptions{ artifactType: "file", @@ -51,7 +51,17 @@ func (suite *LogEnvironmentCommandTestSuite) SetupTest() { "GITHUB_REPOSITORY": suite.repoName, "GITHUB_REPOSITORY_ID": "1234567890", }, suite.T()) - CreateArtifactOnTrail(suite.flowName, "trail-1", "backend", suite.fingerprint, suite.artifactName, suite.T()) + CreateArtifactOnTrail(suite.flowName, "trail-1", "backend", suite.fingerprint, suite.firstArtifactPath, suite.T()) + + suite.secondRepoName = "other/repo" + SetEnvVars(map[string]string{ + "GITHUB_RUN_NUMBER": "5678", + "GITHUB_REPOSITORY": suite.secondRepoName, + "GITHUB_REPOSITORY_ID": "1234567891", + }, suite.T()) + suite.secondFingerprint, err = GetSha256Digest(suite.secondArtifactPath, fingerprintOptions, logger) + require.NoError(suite.T(), err) + CreateArtifactOnTrail(suite.flowName, "trail-2", "frontend", suite.secondFingerprint, suite.secondArtifactPath, suite.T()) CreateEnv(global.Org, suite.eventsEnvName, "server", suite.T()) } @@ -137,7 +147,7 @@ func (suite *LogEnvironmentCommandTestSuite) TestLogEnvironmentCmd() { }, { name: "listing events with several --repo flags filters by all provided repos", - cmd: fmt.Sprintf(`log env %s --repo other/repo --repo %s %s --debug`, suite.eventsEnvName, suite.repoName, suite.defaultKosliArguments), + cmd: fmt.Sprintf(`log env %s --repo %s --repo %s %s --debug`, suite.eventsEnvName, suite.secondRepoName, suite.repoName, suite.defaultKosliArguments), additionalConfig: listSnapshotsTestConfig{ reportToEnv: true, },