diff --git a/cmd/kosli/logEnvironment.go b/cmd/kosli/logEnvironment.go index b88db66be..3d3422dc9 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" @@ -45,12 +46,26 @@ 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 + +# 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 { listOptions reverse bool interval string + repos []string } func newLogEnvironmentCmd(out io.Writer) *cobra.Command { @@ -76,6 +91,7 @@ func newLogEnvironmentCmd(out io.Writer) *cobra.Command { } cmd.Flags().StringVarP(&o.interval, "interval", "i", "", intervalFlag) + cmd.Flags().StringSliceVar(&o.repos, "repo", []string{}, repoNameFlag) addListFlags(cmd, &o.listOptions) cmd.Flags().BoolVar(&o.reverse, "reverse", false, reverseFlag) @@ -92,12 +108,29 @@ 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", - global.Host, global.Org, envName, o.pageNumber, o.pageLimit, url.QueryEscape(interval), o.reverse) + u, err := url.Parse(global.Host) + if err != nil { + 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)) + 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 != "" { + q.Add("repo_name", repo) + } + } + u.RawQuery = q.Encode() reqParams := &requests.RequestParams{ Method: http.MethodGet, - URL: url, + URL: u.String(), Token: global.ApiToken, } response, err := kosliClient.Do(reqParams) diff --git a/cmd/kosli/logEnvironment_test.go b/cmd/kosli/logEnvironment_test.go index 5a6dbec6b..f8b181cd6 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,18 @@ type LogEnvironmentCommandTestSuite struct { eventsEnvName string firstArtifactPath string secondArtifactPath string + flowName string + fingerprint string + secondFingerprint string + repoName string + secondRepoName 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 +36,32 @@ 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) + + 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.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()) } @@ -105,6 +138,20 @@ 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, + }, + }, + { + name: "listing events with several --repo flags filters by all provided repos", + cmd: fmt.Sprintf(`log env %s --repo %s --repo %s %s --debug`, suite.eventsEnvName, suite.secondRepoName, suite.repoName, suite.defaultKosliArguments), + additionalConfig: listSnapshotsTestConfig{ + reportToEnv: true, + }, + }, } for _, t := range tests { 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