Skip to content

Commit 97abae2

Browse files
CU-2458: Display server error messages for failed uploads
1 parent f404049 commit 97abae2

File tree

2 files changed

+63
-66
lines changed

2 files changed

+63
-66
lines changed

cmd/src/code_intel_upload.go

Lines changed: 59 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
"github.com/pkg/browser"
1616

17-
"github.com/sourcegraph/sourcegraph/lib/accesstoken"
1817
"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
1918
"github.com/sourcegraph/sourcegraph/lib/errors"
2019
"github.com/sourcegraph/sourcegraph/lib/output"
@@ -71,7 +70,7 @@ func handleCodeIntelUpload(args []string) error {
7170
}
7271
}
7372
if err != nil {
74-
return handleUploadError(cfg.AccessToken, err)
73+
return handleUploadError(err)
7574
}
7675

7776
client := cfg.apiClient(codeintelUploadFlags.apiFlags, io.Discard)
@@ -84,7 +83,7 @@ func handleCodeIntelUpload(args []string) error {
8483
uploadID, err = UploadUncompressedIndex(ctx, codeintelUploadFlags.file, client, uploadOptions)
8584
}
8685
if err != nil {
87-
return handleUploadError(uploadOptions.SourcegraphInstanceOptions.AccessToken, err)
86+
return handleUploadError(err)
8887
}
8988

9089
uploadURL, err := makeCodeIntelUploadURL(uploadID)
@@ -216,9 +215,30 @@ func (e errorWithHint) Error() string {
216215
// given output object is nil then the error will be written to standard out.
217216
//
218217
// This method returns the error that should be passed back up to the runner.
219-
func handleUploadError(accessToken string, err error) error {
220-
if errors.Is(err, upload.ErrUnauthorized) {
221-
err = attachHintsForAuthorizationError(accessToken, err)
218+
func handleUploadError(err error) error {
219+
if errors.Is(err, ErrUnauthorized) {
220+
serverMessage := serverMessageFromError(err)
221+
if serverMessage != "" {
222+
err = errorWithHint{
223+
err: errors.Newf("upload rejected by server: %s", serverMessage),
224+
hint: codeHostTokenHint(),
225+
}
226+
} else {
227+
err = errorWithHint{
228+
err: errors.New("upload failed: unauthorized (check your Sourcegraph access token)"),
229+
hint: "To create a Sourcegraph access token, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token.",
230+
}
231+
}
232+
} else if strings.Contains(err.Error(), "unexpected status code: 403") {
233+
serverMessage := serverMessageFrom403Error(err)
234+
displayMsg := "upload failed: forbidden"
235+
if serverMessage != "" {
236+
displayMsg = fmt.Sprintf("upload rejected by server: %s", serverMessage)
237+
}
238+
err = errorWithHint{
239+
err: errors.New(displayMsg),
240+
hint: codeHostTokenHint(),
241+
}
222242
}
223243

224244
if codeintelUploadFlags.ignoreUploadFailures {
@@ -230,76 +250,49 @@ func handleUploadError(accessToken string, err error) error {
230250
return err
231251
}
232252

233-
func attachHintsForAuthorizationError(accessToken string, originalError error) error {
234-
var actionableHints []string
235-
236-
likelyTokenError := accessToken == ""
237-
if _, parseErr := accesstoken.ParsePersonalAccessToken(accessToken); accessToken != "" && parseErr != nil {
238-
likelyTokenError = true
239-
actionableHints = append(actionableHints,
240-
"However, the provided access token does not match expected format; was it truncated?",
241-
"Typically the access token looks like sgp_<40 hex chars> or sgp_<instance-id>_<40 hex chars>.")
253+
// serverMessageFromError extracts the detail string from a wrapped ErrUnauthorized.
254+
// Returns "" if the error is the bare sentinel (no server message).
255+
func serverMessageFromError(err error) string {
256+
msg := err.Error()
257+
sentinel := ErrUnauthorized.Error()
258+
if msg == sentinel {
259+
return ""
242260
}
243-
244-
if likelyTokenError {
245-
return errorWithHint{err: originalError, hint: strings.Join(mergeStringSlices(
246-
[]string{"A Sourcegraph access token must be provided via SRC_ACCESS_TOKEN for uploading SCIP data."},
247-
actionableHints,
248-
[]string{"For more details, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token."},
249-
), "\n")}
261+
// errors.Wrap produces "detail: sentinel", so strip the suffix.
262+
if strings.HasSuffix(msg, ": "+sentinel) {
263+
return strings.TrimSuffix(msg, ": "+sentinel)
250264
}
265+
return ""
266+
}
251267

252-
needsGitHubToken := strings.HasPrefix(codeintelUploadFlags.repo, "github.com")
253-
needsGitLabToken := strings.HasPrefix(codeintelUploadFlags.repo, "gitlab.com")
254-
255-
if needsGitHubToken {
256-
if codeintelUploadFlags.gitHubToken != "" {
257-
actionableHints = append(actionableHints,
258-
fmt.Sprintf("The supplied -github-token does not indicate that you have collaborator access to %s.", codeintelUploadFlags.repo),
259-
"Please check the value of the supplied token and its permissions on the code host and try again.",
260-
)
261-
} else {
262-
actionableHints = append(actionableHints,
263-
fmt.Sprintf("Please retry your request with a -github-token=XXX with collaborator access to %s.", codeintelUploadFlags.repo),
264-
"This token will be used to check with the code host that the uploading user has write access to the target repository.",
265-
)
266-
}
267-
} else if needsGitLabToken {
268-
if codeintelUploadFlags.gitLabToken != "" {
269-
actionableHints = append(actionableHints,
270-
fmt.Sprintf("The supplied -gitlab-token does not indicate that you have write access to %s.", codeintelUploadFlags.repo),
271-
"Please check the value of the supplied token and its permissions on the code host and try again.",
272-
)
273-
} else {
274-
actionableHints = append(actionableHints,
275-
fmt.Sprintf("Please retry your request with a -gitlab-token=XXX with write access to %s.", codeintelUploadFlags.repo),
276-
"This token will be used to check with the code host that the uploading user has write access to the target repository.",
277-
)
278-
}
279-
} else {
280-
actionableHints = append(actionableHints,
281-
"Verification is supported for the following code hosts: github.com, gitlab.com.",
282-
"Please request support for additional code host verification at https://github.com/sourcegraph/sourcegraph/issues/4967.",
283-
)
268+
// serverMessageFrom403Error extracts the server body from a 403 error.
269+
// The vendored code formats these as "unexpected status code: 403 (body)".
270+
func serverMessageFrom403Error(err error) string {
271+
msg := err.Error()
272+
prefix := "unexpected status code: 403 ("
273+
if strings.HasPrefix(msg, prefix) && strings.HasSuffix(msg, ")") {
274+
return msg[len(prefix) : len(msg)-1]
284275
}
276+
return ""
277+
}
285278

286-
return errorWithHint{err: originalError, hint: strings.Join(mergeStringSlices(
287-
[]string{"This Sourcegraph instance has enforced auth for SCIP uploads."},
288-
actionableHints,
289-
[]string{"For more details, see https://docs.sourcegraph.com/cli/references/code-intel/upload."},
290-
), "\n")}
279+
// codeHostTokenHint returns documentation links relevant to the upload failure.
280+
// Always includes the general upload docs, and adds code-host-specific token
281+
// documentation if the user supplied a code host token flag.
282+
func codeHostTokenHint() string {
283+
hint := "For more details on uploading SCIP indexes, see https://sourcegraph.com/docs/cli/references/code-intel/upload."
284+
if codeintelUploadFlags.gitHubToken != "" {
285+
hint += "\nIf the issue is related to your GitHub token, see https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens."
286+
}
287+
if codeintelUploadFlags.gitLabToken != "" {
288+
hint += "\nIf the issue is related to your GitLab token, see https://docs.gitlab.com/user/profile/personal_access_tokens/."
289+
}
290+
return hint
291291
}
292292

293293
// emergencyOutput creates a default Output object writing to standard out.
294294
func emergencyOutput() *output.Output {
295295
return output.NewOutput(os.Stdout, output.OutputOpts{})
296296
}
297297

298-
func mergeStringSlices(ss ...[]string) []string {
299-
var combined []string
300-
for _, s := range ss {
301-
combined = append(combined, s...)
302-
}
303298

304-
return combined
305-
}

cmd/src/code_intel_upload_vendored.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,10 @@ func performRequest(ctx context.Context, req *http.Request, httpClient upload.Cl
420420
func decodeUploadPayload(resp *http.Response, body []byte, target *int) (bool, error) {
421421
if resp.StatusCode >= 300 {
422422
if resp.StatusCode == http.StatusUnauthorized {
423+
detail := string(bytes.TrimSpace(body))
424+
if detail != "" && !bytes.HasPrefix(bytes.TrimSpace(body), []byte{'<'}) {
425+
return false, errors.Wrap(ErrUnauthorized, detail)
426+
}
423427
return false, ErrUnauthorized
424428
}
425429

0 commit comments

Comments
 (0)