Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/kosli/attestCustom.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func newAttestCustomCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is now repeated in 13 RunE bodies. It follows the existing repoURLExplicit/repoNameExplicit pattern, so it's consistent — but the failure mode is new and silent: a future attest command that embeds CommonAttestationOptions and forgets this line still compiles, still passes tests, and quietly downgrades an explicit --commit from a hard error to a warning. Nothing catches it.

addAttestationFlags(cmd, o.CommonAttestationOptions, ...) already receives both cmd and o, so it could own the assignment for all three flags — e.g. by chaining a PreRunE there, or by stashing o.flags = cmd.Flags() and reading Changed at use-site. Worth doing here or in a follow-up, and worth a note in .claude/skills/new-command/ either way, since that skill scaffolds new attest commands.

return o.run(args)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestDecision.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ func newAttestDecisionCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestGeneric.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func newAttestGenericCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/kosli/attestJira.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ func newAttestJiraCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down Expand Up @@ -292,6 +293,10 @@ func (o *attestJiraOptions) run(args []string) error {
return err
}

if o.payload.Commit == nil {
return fmt.Errorf("failed to get commit info, which is required to search for Jira issue keys. Pass --commit and point --repo-root at a repository containing it")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the nil deref (line 304 dereferences o.payload.Commit.Sha1), but the advice doesn't fit this command: commit is in RequireFlags at line 267, so a user reaching this line either passed --commit already or is in CI where it was defaulted. "Pass --commit" reads as though they forgot it.

The attest pr * copy in pullrequest.go:42 has the same wording and the same RequireFlags situation. Something closer to the cause would help both:

Suggested change
return fmt.Errorf("failed to get commit info, which is required to search for Jira issue keys. Pass --commit and point --repo-root at a repository containing it")
return fmt.Errorf("--commit %s could not be resolved in the git repository at --repo-root %s, and the commit is required to search for Jira issue keys", o.commitSHA, o.srcRepoRoot)

Also worth noting: whenever this guard passes, lines 300-305 immediately redo the gitview.New + GetCommitInfoFromCommitSHA that resolveCommitInfo just did (with different redaction args). Pre-existing, but now that the lookup is centralised it's a visible duplicate.

}

gv, err := gitview.New(o.srcRepoRoot)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestJunit.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ func newAttestJunitCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestOverride.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func newAttestOverrideCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestPRAzure.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func newAttestAzurePRCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
o.retriever = azUtils.NewAzureConfig(azureFlagsValues.Token,
azureFlagsValues.OrgUrl, azureFlagsValues.Project, o.repoName)
return o.run(args)
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestPRBitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func newAttestBitbucketPRCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
o.getRetriever().(*bbUtils.Config).Repository = o.repoName
return o.run(args)
},
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestPRGithub.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func newAttestGithubPRCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
o.retriever = ghUtils.NewGithubRetrieverFunc(githubFlagsValues.Token, githubFlagsValues.BaseURL,
githubFlagsValues.Org, o.repoName, global.Debug)
return o.run(args)
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestPRGitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func newAttestGitlabPRCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
// GitlabConfig.Repository is the short project name (CI_PROJECT_NAME);
// combined with Org (CI_PROJECT_NAMESPACE) it forms the API ProjectID.
// This is separate from repo_info.name, which uses the full CI_PROJECT_PATH.
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestSnyk.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func newAttestSnykCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
1 change: 1 addition & 0 deletions cmd/kosli/attestSonar.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func newAttestSonarCmd(out io.Writer) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
o.repoURLExplicit = cmd.Flags().Changed("repo-url")
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down
29 changes: 22 additions & 7 deletions cmd/kosli/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type CommonAttestationOptions struct {
repoProvider string
repoURLExplicit bool
repoNameExplicit bool
commitSHAExplicit bool
}

func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestationPayload) error {
Expand All @@ -80,15 +81,10 @@ func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestation
}

if o.commitSHA != "" {
gv, err := gitview.New(o.srcRepoRoot)
payload.Commit, err = resolveCommitInfo(o.srcRepoRoot, o.commitSHA, o.commitSHAExplicit, o.redactedCommitInfo)
if err != nil {
return fmt.Errorf("failed to get commit info. %s", err)
return err
}
commitInfo, err := gv.GetCommitInfoFromCommitSHA(o.commitSHA, false, o.redactedCommitInfo)
if err != nil {
return fmt.Errorf("failed to get commit info. %s", err)
}
payload.Commit = &commitInfo.BasicCommitInfo
}

payload.GitRepoInfo, err = getGitRepoInfoFromEnvironment()
Expand Down Expand Up @@ -117,6 +113,25 @@ func (o *CommonAttestationOptions) run(args []string, payload *CommonAttestation
return err
}

// resolveCommitInfo returns nil when git cannot supply the commit info and the
// commit was not asked for explicitly, so a CI-defaulted --commit does not fail
// the command in a job with no checked-out repository (#6094).
func resolveCommitInfo(srcRepoRoot, commitSHA string, explicit bool, redactedCommitInfo []string) (*gitview.BasicCommitInfo, error) {
gv, err := gitview.New(srcRepoRoot)
if err == nil {
var commitInfo *gitview.CommitInfo
commitInfo, err = gv.GetCommitInfoFromCommitSHA(commitSHA, false, redactedCommitInfo)
if err == nil {
return &commitInfo.BasicCommitInfo, nil
}
}
if explicit {
return nil, fmt.Errorf("failed to get commit info. %s", err)
}
logger.Warn("attesting without commit info: --commit defaulted to %s from the CI environment, but %s. Point --repo-root at a repository containing that commit to attach it.", commitSHA, err.Error())
Comment on lines +116 to +131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things about the soft-fail branch:

1. The warning is the only signal a user gets that commit info is now missing. In CI it competes with everything else on stderr, and the consequence isn't stated: the --commit help text itself says a commit is required "when reporting an attestation for an artifact before reporting it to Kosli", so an attestation that quietly loses its commit can fail to bind to an artifact later, at a point far from this warning. Worth naming that consequence here.

2. A wrong --repo-root is still swallowed when --commit came from CI. The PR reasons that an explicit --commit protects against a misconfigured --repo-root, but the reverse case — CI-defaulted --commit plus an explicitly passed, wrong --repo-root — takes the warn path. The user did explicitly ask for that repo. Since cmd.Flags().Changed(...) is already the idiom here, repoRootExplicit could hard-fail that case; that keeps the fix scoped to "no repository was asked for at all", which is the actual #6094 report.

Minor: #6094 in the doc comment resolves to kosli-dev/cli#6094 on GitHub. Use kosli-dev/server#6094.

return nil, nil
}

// mergeGitRepoInfo applies flag overrides onto base (which may be nil) and
// returns nil if ID, Name, or URL is still empty after merging, so that the
// field is omitted from the JSON payload.
Expand Down
9 changes: 3 additions & 6 deletions cmd/kosli/beginTrail.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type beginTrailOptions struct {
repoURL string
repoProvider string
repoNameExplicit bool
commitSHAExplicit bool
}

type TrailPayload struct {
Expand Down Expand Up @@ -85,6 +86,7 @@ func newBeginTrailCmd(out io.Writer) *cobra.Command {
},
RunE: func(cmd *cobra.Command, args []string) error {
o.repoNameExplicit = cmd.Flags().Changed("repository")
o.commitSHAExplicit = cmd.Flags().Changed("commit")
return o.run(args)
},
}
Expand Down Expand Up @@ -128,15 +130,10 @@ func (o *beginTrailOptions) run(args []string) error {
}

if o.commitSHA != "" {
gv, err := gitview.New(o.srcRepoRoot)
o.payload.Commit, err = resolveCommitInfo(o.srcRepoRoot, o.commitSHA, o.commitSHAExplicit, o.redactedCommitInfo)
if err != nil {
return err
}
commitInfo, err := gv.GetCommitInfoFromCommitSHA(o.commitSHA, false, o.redactedCommitInfo)
if err != nil {
return err
}
o.payload.Commit = &commitInfo.BasicCommitInfo
}

base, err := getGitRepoInfoFromEnvironment()
Expand Down
126 changes: 126 additions & 0 deletions cmd/kosli/commitInfoResolution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main

import (
"fmt"
"testing"

"github.com/go-git/go-git/v5"
"github.com/stretchr/testify/suite"
)

// CommitInfoResolutionTestSuite guards that a --commit which was defaulted from
// the CI environment does not fail the command when git cannot supply its info,
// while an explicitly passed --commit still does.
//
// The production trigger (a CI-defaulted --commit in a job with no checked-out
// repository) cannot be reproduced through the command harness, because
// DefaultValue returns "" whenever KOSLI_TESTS is set. resolveCommitInfo is
// therefore exercised directly, and the command cases below guard only that
// each command assigns commitSHAExplicit.
Comment on lines +14 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stated limitation isn't quite true — the harness can produce a CI-defaulted --commit. DefaultValue reads KOSLI_TESTS at flag-construction time, which happens inside executeCommandCnewRootCmd, so a test can unset it around the call. cli_utils_test.go:305-322 already does exactly this unset-and-restore dance for the same reason.

That makes an end-to-end case possible, and it is the one behaviour nothing currently covers — that the command exits 0 and emits the warning:

value, inTests := os.LookupEnv("KOSLI_TESTS")
require.NoError(suite.T(), os.Unsetenv("KOSLI_TESTS"))
defer func() { if inTests { os.Setenv("KOSLI_TESTS", value) } }()
suite.T().Setenv("GITHUB_RUN_NUMBER", "1")   // WhichCI() -> github
suite.T().Setenv("GITHUB_SHA", suite.headHash)
// attest generic ... --repo-root testdata, no --commit
// expect: no error, stderr contains "attesting without commit info"

Without it, the warning text — the only thing the user sees on the new path — is unasserted, and nothing catches a regression that turns the soft-fail back into an error at the command level. restoreLogger (cli_utils_test.go:1334) is available if you'd rather capture the warning at the unit level instead.

Nit: resolveCommitInfo lives in attestation.go, which already has an attestation_test.go; a commitInfoResolution_test.go with no matching production file breaks the file-pairing convention.

type CommitInfoResolutionTestSuite struct {
suite.Suite
headHash string
defaultKosliArguments string
}

func (suite *CommitInfoResolutionTestSuite) SetupTest() {
repo, err := git.PlainOpen("../..")
suite.Require().NoError(err)
head, err := repo.Head()
suite.Require().NoError(err)
suite.headHash = head.Hash().String()

global = &GlobalOpts{
ApiToken: "DRY_RUN",
Org: "test-org",
Host: "http://localhost:8001",
DryRun: true,
}
suite.defaultKosliArguments = " --dry-run --host http://localhost:8001 --org test-org --api-token DRY_RUN"
}

func (suite *CommitInfoResolutionTestSuite) TestResolveCommitInfoWithoutRepository() {
const noRepo = "testdata"

info, err := resolveCommitInfo(noRepo, suite.headHash, false, []string{})
suite.Require().NoError(err, "a CI-defaulted commit must not fail when there is no repository")
suite.Nil(info)

_, err = resolveCommitInfo(noRepo, suite.headHash, true, []string{})
suite.Require().Error(err, "an explicit --commit must still fail when there is no repository")
suite.Contains(err.Error(), "repository does not exist")
}

func (suite *CommitInfoResolutionTestSuite) TestResolveCommitInfoWithUnresolvableCommit() {
// A well-formed SHA that is not in this repository, as in a shallow clone.
const absentSHA = "0d4c1e1b7f5c2a9e8b3d6f0a1c4e7b2d5a8f3c60"

info, err := resolveCommitInfo("../..", absentSHA, false, []string{})
suite.Require().NoError(err, "a CI-defaulted commit must not fail when it cannot be resolved")
suite.Nil(info)

_, err = resolveCommitInfo("../..", absentSHA, true, []string{})
suite.Require().Error(err, "an explicit --commit must still fail when it cannot be resolved")
}

func (suite *CommitInfoResolutionTestSuite) TestResolveCommitInfoSucceeds() {
info, err := resolveCommitInfo("../..", suite.headHash, false, []string{})
suite.Require().NoError(err)
suite.Require().NotNil(info)
suite.Equal(suite.headHash, info.Sha1)
}

func (suite *CommitInfoResolutionTestSuite) TestExplicitCommitWiring() {
tests := []cmdTestCase{
{
wantError: true,
name: "attest generic: an explicit --commit fails when --repo-root has no repository",
cmd: fmt.Sprintf("attest generic --fingerprint 7509e5bda0c762d2bac7f90d758b5b2263fa01ccbc542ab5e3df163be08e6ca9 --name foo --flow f --trail t --commit %s --repo-root testdata%s", suite.headHash, suite.defaultKosliArguments),
goldenRegex: "Error: failed to get commit info\\. .*repository does not exist\n",
},
{
wantError: true,
name: "begin trail: an explicit --commit fails when --repo-root has no repository",
cmd: fmt.Sprintf("begin trail t --flow f --commit %s --repo-root testdata%s", suite.headHash, suite.defaultKosliArguments),
goldenRegex: "Error: failed to get commit info\\. .*repository does not exist\n",
},
}
runTestCmd(suite.T(), tests)
}

// commitRequiredOptions builds the shared attestation options for a command run
// whose --commit came from the CI default and cannot be resolved, which is the
// only way payload.Commit reaches these commands as nil.
func (suite *CommitInfoResolutionTestSuite) commitRequiredOptions() *CommonAttestationOptions {
return &CommonAttestationOptions{
fingerprintOptions: &fingerprintOptions{},
attestationNameTemplate: "foo",
flowName: "f",
trailName: "t",
commitSHA: suite.headHash,
srcRepoRoot: "testdata",
commitSHAExplicit: false,
}
}

func (suite *CommitInfoResolutionTestSuite) TestCommandsNeedingCommitReportIt() {
pr := &attestPROptions{
CommonAttestationOptions: suite.commitRequiredOptions(),
payload: PRAttestationPayload{CommonAttestationPayload: &CommonAttestationPayload{}},
}
err := pr.run([]string{})
suite.Require().Error(err)
suite.Contains(err.Error(), "required to find pull requests")

jira := &attestJiraOptions{
CommonAttestationOptions: suite.commitRequiredOptions(),
payload: JiraAttestationPayload{CommonAttestationPayload: &CommonAttestationPayload{}},
}
err = jira.run([]string{})
suite.Require().Error(err)
suite.Contains(err.Error(), "required to search for Jira issue keys")
}

func TestCommitInfoResolutionTestSuite(t *testing.T) {
suite.Run(t, new(CommitInfoResolutionTestSuite))
}
4 changes: 4 additions & 0 deletions cmd/kosli/pullrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func (o *attestPROptions) run(args []string) error {
return err
}

if o.payload.Commit == nil {
return fmt.Errorf("failed to get commit info, which is required to find pull requests. Pass --commit and point --repo-root at a repository containing it")
}

label := ""
o.payload.GitProvider, label = o.getRetriever().ProviderAndLabel()

Expand Down
4 changes: 2 additions & 2 deletions cmd/kosli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file,
intervalFlag = "[optional] Expression to define specified snapshots range."
showUnchangedArtifactsFlag = "[defaulted] Show the unchanged artifacts present in both snapshots within the diff output."
attestationFingerprintFlag = "[conditional] The SHA256 fingerprint of the artifact to attach the attestation to. Only required if the attestation is for an artifact and --artifact-type and artifact name/path are not used."
attestationCommitFlag = "[conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: https://docs.kosli.com/integrations/ci_cd )."
attestationCommitFlag = "[conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: https://docs.kosli.com/integrations/ci_cd ). When it is defaulted from the CI environment and no git repository is available at --repo-root, the attestation is sent without commit info."
attestationRedactCommitInfoFlag = "[optional] The list of commit info to be redacted before sending to Kosli. Allowed values are one or more of [author, message, branch]."
attestationOriginUrlFlag = "[optional] The url pointing to where the attestation came from or is related. (defaulted to the CI url in some CIs: https://docs.kosli.com/integrations/ci_cd/#defaulted-kosli-command-flags-from-ci-variables )."
attestationNameFlag = "The name of the attestation as declared in the flow or trail yaml template."
Expand All @@ -269,7 +269,7 @@ The ^.kosli_ignore^ will be treated as part of the artifact like any other file,
uploadJunitResultsFlag = "[defaulted] Whether to upload the provided Junit results directory as an attachment to Kosli or not."
uploadSnykResultsFlag = "[defaulted] Whether to upload the provided Snyk results file as an attachment to Kosli or not."
attestationAssertFlag = "[optional] Exit with non-zero code if the attestation is non-compliant"
beginTrailCommitFlag = "[defaulted] The git commit from which the trail is begun. (defaulted in some CIs: https://docs.kosli.com/integrations/ci_cd, otherwise defaults to HEAD )."
beginTrailCommitFlag = "[defaulted] The git commit from which the trail is begun. (defaulted in some CIs: https://docs.kosli.com/integrations/ci_cd, otherwise unset ). When it is defaulted from the CI environment and no git repository is available at --repo-root, the trail is begun without commit info."
attachmentsFlag = "[optional] The comma-separated list of paths of attachments for the reported attestation. Attachments can be files or directories. All attachments are compressed and uploaded to Kosli's evidence vault."
externalFingerprintFlag = "[optional] A SHA256 fingerprint of an external attachment represented by --external-url. The format is label=fingerprint (labels cannot contain '.' or '='). This flag can be set multiple times. There must be an external url with a matching label for each external fingerprint."
externalURLFlag = "[optional] Add labeled reference URL for an external resource. The format is label=url (labels cannot contain '.' or '='). This flag can be set multiple times. If the resource is a file or dir, you can optionally add its fingerprint via --external-fingerprint"
Expand Down
2 changes: 1 addition & 1 deletion cmd/kosli/testdata/output/docs/mintlify/snyk.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ In other CI systems, set them explicitly to capture repository metadata.
| `--annotate` | stringToString | [optional] Annotate the attestation with data using key=value. |
| `-t`, `--artifact-type` | string | The type of the artifact to calculate its SHA256 fingerprint. One of: [oci, docker, file, dir]. Only required if you want Kosli to calculate the fingerprint for you (i.e. when you don't specify '`--fingerprint`' on commands that allow it). |
| `--attachments` | strings | [optional] The comma-separated list of paths of attachments for the reported attestation. Attachments can be files or directories. All attachments are compressed and uploaded to Kosli's evidence vault. |
| `-g`, `--commit` | string | [conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: [docs](/integrations/ci_cd) ). |
| `-g`, `--commit` | string | [conditional] The git commit for which the attestation is associated to. Becomes required when reporting an attestation for an artifact before reporting it to Kosli. (defaulted in some CIs: [docs](/integrations/ci_cd) ). When it is defaulted from the CI environment and no git repository is available at `--repo-root`, the attestation is sent without commit info. |
| `--description` | string | [optional] attestation description |
| `-D`, `--dry-run` | bool | [optional] Run in dry-run mode. When enabled, no data is sent to Kosli and the CLI exits with 0 exit code regardless of any errors. |
| `-x`, `--exclude` | strings | [optional] The comma separated list of directories and files to exclude from fingerprinting. Can take glob patterns. Only applicable for `--artifact-type` dir. |
Expand Down
9 changes: 7 additions & 2 deletions internal/testHelpers/testHelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ func GithubPRNumber() int {
}

func CloneGitRepo(url, cloneTo string) (*git.Repository, error) {
// Resolved for the same reason as in InitializeGitRepo below.
resolvedCloneTo, err := filepath.EvalSymlinks(cloneTo)
if err != nil {
return nil, err
}
// the repo worktree filesystem. It has to be osfs so that we can give it a path
Comment on lines +56 to 61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnosis is right and matches InitializeGitRepo below. One behavioural note: EvalSymlinks errors on a path that doesn't exist, so CloneGitRepo now requires cloneTo to be pre-created — previously osfs/git.Clone would create it. Both current callers (attestPRGitlab_test.go:44, attestPRBitbucket_test.go:42) os.MkdirTemp first, so nothing breaks today; it's just an unstated precondition on a shared helper. A line in the comment saying cloneTo must exist would save the next caller a confusing no such file or directory from a clone helper.

fs := osfs.New(cloneTo)
fs := osfs.New(resolvedCloneTo)
// the filesystem for git database
storerFS := osfs.New(filepath.Join(cloneTo, ".git"))
storerFS := osfs.New(filepath.Join(resolvedCloneTo, ".git"))
storer := filesystem.NewStorage(storerFS, cache.NewObjectLRUDefault())
return git.Clone(storer, fs, &git.CloneOptions{URL: url})
}
Expand Down
Loading