Skip to content

Commit abcc8c7

Browse files
committed
add exit code 2 on subject resolution error
1 parent 34d79ad commit abcc8c7

File tree

3 files changed

+138
-12
lines changed

3 files changed

+138
-12
lines changed

evidence/cli/flags.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ var flagsMap = map[string]components.Flag{
7373
providerId: components.NewStringFlag(providerId, "Provider ID for the evidence.", func(f *components.StringFlag) { f.Mandatory = false }),
7474
publicKeys: components.NewStringFlag(publicKeys, "Array of paths to public keys for signatures verification with \";\" separator. Supported keys: 'ecdsa','rsa' and 'ed25519'.", func(f *components.StringFlag) { f.Mandatory = false }),
7575
useArtifactoryKeys: components.NewBoolFlag(useArtifactoryKeys, "Use Artifactory keys for verification. When enabled, the verify command retrieves keys from Artifactory.", func(f *components.BoolFlag) { f.DefaultValue = false }),
76-
sigstoreBundle: components.NewStringFlag(sigstoreBundle, "Path to a Sigstore bundle file with a pre-signed DSSE envelope. Incompatible with --"+key+", --"+keyAlias+", --"+predicate+", --"+predicateType+" and --"+subjectSha256+".", func(f *components.StringFlag) { f.Mandatory = false }),
76+
sigstoreBundle: components.NewStringFlag(sigstoreBundle, "Path to a Sigstore bundle file with a pre-signed DSSE envelope. Incompatible with --"+key+", --"+keyAlias+", --"+predicate+", --"+predicateType+" and --"+subjectSha256+". If subject is omitted, it will be extracted from the DSSE when possible. Returns exit code 2 if subject resolution fails.", func(f *components.StringFlag) { f.Mandatory = false }),
7777
}
7878

7979
var commandFlags = map[string][]string{

evidence/create/create_custom.go

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package create
22

33
import (
44
"encoding/json"
5+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
56
clientLog "github.com/jfrog/jfrog-client-go/utils/log"
67
"github.com/sigstore/sigstore-go/pkg/bundle"
78
"regexp"
@@ -15,9 +16,10 @@ import (
1516

1617
type createEvidenceCustom struct {
1718
createEvidenceBase
18-
subjectRepoPath string
19-
subjectSha256 string
20-
sigstoreBundlePath string
19+
subjectRepoPath string
20+
subjectSha256 string
21+
sigstoreBundlePath string
22+
autoSubjectResolution bool
2123
}
2224

2325
func NewCreateEvidenceCustom(serverDetails *config.ServerDetails, predicateFilePath, predicateType, markdownFilePath, key, keyId, subjectRepoPath,
@@ -62,13 +64,13 @@ func (c *createEvidenceCustom) Run() error {
6264
return err
6365
}
6466

65-
err = validateSubject(c.subjectRepoPath)
67+
err = c.validateSubject()
6668
if err != nil {
6769
return err
6870
}
6971
err = c.uploadEvidence(evidencePayload, c.subjectRepoPath)
7072
if err != nil {
71-
err = handleSubjectNotFound(err, c.subjectRepoPath)
73+
err = c.handleSubjectNotFound(err)
7274
return err
7375
}
7476

@@ -82,6 +84,7 @@ func (c *createEvidenceCustom) processSigstoreBundle() ([]byte, error) {
8284
}
8385

8486
if c.subjectRepoPath == "" {
87+
c.autoSubjectResolution = true
8588
extractedSubject, err := c.extractSubjectFromBundle(sigstoreBundle)
8689
if err != nil {
8790
return nil, err
@@ -99,7 +102,7 @@ func (c *createEvidenceCustom) extractSubjectFromBundle(bundle *bundle.Bundle) (
99102
}
100103

101104
if subject == "" {
102-
return "", errorutils.CheckErrorf("Subject is not found in the sigstore bundle. Please ensure the bundle contains a valid subject.")
105+
return "", c.newSubjectError("Subject is not found in the sigstore bundle. Please ensure the bundle contains a valid subject.")
103106
} else {
104107
clientLog.Info("Subject " + subject + " is resolved from sigstore bundle.")
105108
}
@@ -116,19 +119,32 @@ func (c *createEvidenceCustom) createDSSEEnvelope() ([]byte, error) {
116119
return envelope, nil
117120
}
118121

119-
func validateSubject(subject string) error {
122+
func (c *createEvidenceCustom) validateSubject() error {
120123
// Pattern: must have at least one slash with non-empty sections
121-
if matched, _ := regexp.MatchString(`^[^/]+(/[^/]+)+$`, subject); !matched {
122-
return errorutils.CheckErrorf("Subject '%s' is invalid. Subject must be in format: <repo>/<path>/<name> or <repo>/<name>", subject)
124+
if matched, _ := regexp.MatchString(`^[^/]+(/[^/]+)+$`, c.subjectRepoPath); !matched {
125+
return c.newSubjectError("Subject '" + c.subjectRepoPath + "' is invalid. Subject must be in format: <repo>/<path>/<name> or <repo>/<name>")
123126
}
124127
return nil
125128
}
126129

127-
func handleSubjectNotFound(err error, subject string) error {
130+
func (c *createEvidenceCustom) handleSubjectNotFound(err error) error {
128131
errStr := err.Error()
129132
if strings.Contains(errStr, "404 Not Found") {
130133
clientLog.Debug("Server response error:", err.Error())
131-
return errorutils.CheckErrorf("Subject '%s' is not found. Please ensure the subject exists.", subject)
134+
return c.newSubjectError("Subject '" + c.subjectRepoPath + "' is not found. Please ensure the subject exists.")
132135
}
133136
return err
134137
}
138+
139+
// newSubjectError creates an error with ExitCodeFailNoOp (2) for subject-related failures
140+
// When auto subject resolution is enabled, this allows pipeline calls with gh attestation
141+
// sigstore bundle generation to skip command execution without breaking a pipeline
142+
func (c *createEvidenceCustom) newSubjectError(message string) error {
143+
if c.autoSubjectResolution {
144+
return coreutils.CliError{
145+
ExitCode: coreutils.ExitCodeFailNoOp,
146+
ErrorMsg: message,
147+
}
148+
}
149+
return errorutils.CheckErrorf(message)
150+
}

evidence/create/create_custom_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/jfrog/jfrog-cli-core/v2/utils/config"
11+
"github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
1112
"github.com/stretchr/testify/assert"
1213
)
1314

@@ -212,3 +213,112 @@ func TestCreateEvidenceCustom_SigstoreBundleWithSubjectPath(t *testing.T) {
212213
assert.Equal(t, bundlePath, custom.sigstoreBundlePath)
213214
assert.Equal(t, "provided-repo/provided-artifact", custom.subjectRepoPath)
214215
}
216+
217+
func TestCreateEvidenceCustom_NewSubjectError_GitHubActions(t *testing.T) {
218+
_ = os.Setenv("GITHUB_ACTIONS", "true")
219+
defer func() {
220+
_ = os.Unsetenv("GITHUB_ACTIONS")
221+
}()
222+
223+
serverDetails := &config.ServerDetails{
224+
Url: "https://test.jfrog.io",
225+
User: "test-user",
226+
AccessToken: "test-token",
227+
}
228+
229+
cmd := NewCreateEvidenceCustom(
230+
serverDetails,
231+
"predicate.json",
232+
"https://example.com/predicate/v1",
233+
"markdown.md",
234+
"key.pem",
235+
"key-alias",
236+
"test-repo/test-artifact",
237+
"abcd1234",
238+
"/path/to/sigstore-bundle.json",
239+
"test-provider",
240+
)
241+
242+
custom, ok := cmd.(*createEvidenceCustom)
243+
assert.True(t, ok, "cmd should be of type *createEvidenceCustom")
244+
245+
testMessage := "Test error message"
246+
err := custom.newSubjectError(testMessage)
247+
248+
assert.Error(t, err)
249+
cliErr, ok := err.(coreutils.CliError)
250+
assert.True(t, ok, "error should be of type CliError when running under GitHub Actions with sigstore bundle")
251+
assert.Equal(t, coreutils.ExitCodeFailNoOp, cliErr.ExitCode, "should return exit code 2 (ExitCodeFailNoOp)")
252+
assert.Equal(t, testMessage, cliErr.ErrorMsg, "error message should match")
253+
}
254+
255+
func TestCreateEvidenceCustom_NewSubjectError_RegularExecution(t *testing.T) {
256+
_ = os.Unsetenv("GITHUB_ACTIONS")
257+
258+
serverDetails := &config.ServerDetails{
259+
Url: "https://test.jfrog.io",
260+
User: "test-user",
261+
AccessToken: "test-token",
262+
}
263+
264+
cmd := NewCreateEvidenceCustom(
265+
serverDetails,
266+
"predicate.json",
267+
"https://example.com/predicate/v1",
268+
"markdown.md",
269+
"key.pem",
270+
"key-alias",
271+
"test-repo/test-artifact",
272+
"abcd1234",
273+
"/path/to/sigstore-bundle.json",
274+
"test-provider",
275+
)
276+
277+
custom, ok := cmd.(*createEvidenceCustom)
278+
assert.True(t, ok, "cmd should be of type *createEvidenceCustom")
279+
280+
testMessage := "Test error message"
281+
err := custom.newSubjectError(testMessage)
282+
283+
assert.Error(t, err)
284+
_, ok = err.(coreutils.CliError)
285+
assert.False(t, ok, "error should not be of type CliError when not running under GitHub Actions")
286+
assert.Contains(t, err.Error(), testMessage, "error message should contain the test message")
287+
}
288+
289+
func TestCreateEvidenceCustom_NewSubjectError_GitHubActionsWithoutSigstoreBundle(t *testing.T) {
290+
_ = os.Setenv("GITHUB_ACTIONS", "true")
291+
defer func() {
292+
_ = os.Unsetenv("GITHUB_ACTIONS")
293+
}()
294+
295+
serverDetails := &config.ServerDetails{
296+
Url: "https://test.jfrog.io",
297+
User: "test-user",
298+
AccessToken: "test-token",
299+
}
300+
301+
cmd := NewCreateEvidenceCustom(
302+
serverDetails,
303+
"predicate.json",
304+
"https://example.com/predicate/v1",
305+
"markdown.md",
306+
"key.pem",
307+
"key-alias",
308+
"test-repo/test-artifact",
309+
"abcd1234",
310+
"",
311+
"test-provider",
312+
)
313+
314+
custom, ok := cmd.(*createEvidenceCustom)
315+
assert.True(t, ok, "cmd should be of type *createEvidenceCustom")
316+
317+
testMessage := "Test error message"
318+
err := custom.newSubjectError(testMessage)
319+
320+
assert.Error(t, err)
321+
_, ok = err.(coreutils.CliError)
322+
assert.False(t, ok, "error should not be of type CliError when running under GitHub Actions but without sigstore bundle")
323+
assert.Contains(t, err.Error(), testMessage, "error message should contain the test message")
324+
}

0 commit comments

Comments
 (0)