Skip to content

Commit 19254fd

Browse files
CU-2458: Display server error messages for failed uploads
1 parent 436da91 commit 19254fd

File tree

2 files changed

+66
-70
lines changed

2 files changed

+66
-70
lines changed

cmd/src/code_intel_upload.go

Lines changed: 66 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
56
"encoding/base64"
67
"encoding/json"
78
"flag"
89
"fmt"
910
"io"
11+
"net/http"
1012
"net/url"
1113
"os"
1214
"path/filepath"
@@ -15,7 +17,6 @@ import (
1517

1618
"github.com/pkg/browser"
1719

18-
"github.com/sourcegraph/sourcegraph/lib/accesstoken"
1920
"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
2021
"github.com/sourcegraph/sourcegraph/lib/errors"
2122
"github.com/sourcegraph/sourcegraph/lib/output"
@@ -88,18 +89,19 @@ func handleCodeIntelUpload(args []string) error {
8889
}
8990
}
9091
if err != nil {
91-
return handleUploadError(cfg.AccessToken, err)
92+
return handleUploadError(err, "")
9293
}
9394

9495
client := api.NewClient(api.ClientOpts{
9596
Out: io.Discard,
9697
Flags: codeintelUploadFlags.apiFlags,
9798
})
9899

100+
wrappedClient := &errorCapturingClient{inner: client}
99101
uploadOptions := codeintelUploadOptions(out, isSCIPAvailable)
100-
uploadID, err := upload.UploadIndex(ctx, codeintelUploadFlags.file, client, uploadOptions)
102+
uploadID, err := upload.UploadIndex(ctx, codeintelUploadFlags.file, wrappedClient, uploadOptions)
101103
if err != nil {
102-
return handleUploadError(uploadOptions.SourcegraphInstanceOptions.AccessToken, err)
104+
return handleUploadError(err, wrappedClient.lastErrorBody)
103105
}
104106

105107
uploadURL, err := makeCodeIntelUploadURL(uploadID)
@@ -232,15 +234,62 @@ func (e errorWithHint) Error() string {
232234
return fmt.Sprintf("%s\n\n%s\n", e.err, e.hint)
233235
}
234236

237+
// errorCapturingClient wraps an upload.Client to capture the response body
238+
// from error responses (401, 403, etc.). The pinned lib version discards the
239+
// body for 401 responses, so we capture it here before the lib sees it.
240+
type errorCapturingClient struct {
241+
inner upload.Client
242+
lastErrorBody string
243+
}
244+
245+
func (c *errorCapturingClient) Do(req *http.Request) (*http.Response, error) {
246+
resp, err := c.inner.Do(req)
247+
if err != nil {
248+
return resp, err
249+
}
250+
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
251+
body, readErr := io.ReadAll(resp.Body)
252+
resp.Body.Close()
253+
if readErr == nil && len(bytes.TrimSpace(body)) > 0 && !bytes.HasPrefix(bytes.TrimSpace(body), []byte{'<'}) {
254+
c.lastErrorBody = string(bytes.TrimSpace(body))
255+
}
256+
// Replace body so the lib can still read it
257+
resp.Body = io.NopCloser(bytes.NewReader(body))
258+
}
259+
return resp, nil
260+
}
261+
235262
// handleUploadError writes the given error to the given output. If the
236263
// given output object is nil then the error will be written to standard out.
237264
//
238265
// This method returns the error that should be passed back up to the runner.
239-
func handleUploadError(accessToken string, err error) error {
266+
func handleUploadError(err error, serverMessage string) error {
240267
if errors.Is(err, upload.ErrUnauthorized) {
241-
err = attachHintsForAuthorizationError(accessToken, err)
268+
if serverMessage != "" {
269+
// The server provided a specific error message (e.g. "must provide gitlab_token").
270+
// Show it directly so the user knows what went wrong.
271+
err = errorWithHint{
272+
err: errors.Newf("upload rejected by server: %s", serverMessage),
273+
hint: codeHostTokenHint(),
274+
}
275+
} else {
276+
// Generic 401 with no server message — likely a missing or invalid Sourcegraph access token.
277+
err = errorWithHint{
278+
err: errors.New("upload failed: unauthorized (check your Sourcegraph access token)"),
279+
hint: "To create a Sourcegraph access token, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token.",
280+
}
281+
}
282+
} else if strings.Contains(err.Error(), "unexpected status code: 403") {
283+
// TODO: replace with errors.Is(err, upload.ErrForbidden) once lib snapshot is updated.
284+
displayMsg := "upload failed: forbidden"
285+
if serverMessage != "" {
286+
displayMsg = fmt.Sprintf("upload rejected by server: %s", serverMessage)
287+
}
288+
err = errorWithHint{
289+
err: errors.New(displayMsg),
290+
hint: codeHostTokenHint(),
291+
}
242292
}
243-
244293
if codeintelUploadFlags.ignoreUploadFailures {
245294
// Report but don't return the error
246295
fmt.Println(err.Error())
@@ -250,76 +299,23 @@ func handleUploadError(accessToken string, err error) error {
250299
return err
251300
}
252301

253-
func attachHintsForAuthorizationError(accessToken string, originalError error) error {
254-
var actionableHints []string
255-
256-
likelyTokenError := accessToken == ""
257-
if _, parseErr := accesstoken.ParsePersonalAccessToken(accessToken); accessToken != "" && parseErr != nil {
258-
likelyTokenError = true
259-
actionableHints = append(actionableHints,
260-
"However, the provided access token does not match expected format; was it truncated?",
261-
"Typically the access token looks like sgp_<40 hex chars> or sgp_<instance-id>_<40 hex chars>.")
302+
// codeHostTokenHint returns documentation links relevant to the upload failure.
303+
// Always includes the general upload docs, and adds code-host-specific token
304+
// documentation if the user supplied a code host token flag.
305+
func codeHostTokenHint() string {
306+
hint := "For more details on uploading SCIP indexes, see https://sourcegraph.com/docs/cli/references/code-intel/upload."
307+
if codeintelUploadFlags.gitHubToken != "" {
308+
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."
262309
}
263-
264-
if likelyTokenError {
265-
return errorWithHint{err: originalError, hint: strings.Join(mergeStringSlices(
266-
[]string{"A Sourcegraph access token must be provided via SRC_ACCESS_TOKEN for uploading SCIP/LSIF data."},
267-
actionableHints,
268-
[]string{"For more details, see https://sourcegraph.com/docs/cli/how-tos/creating_an_access_token."},
269-
), "\n")}
310+
if codeintelUploadFlags.gitLabToken != "" {
311+
hint += "\nIf the issue is related to your GitLab token, see https://docs.gitlab.com/user/profile/personal_access_tokens/."
270312
}
271-
272-
needsGitHubToken := strings.HasPrefix(codeintelUploadFlags.repo, "github.com")
273-
needsGitLabToken := strings.HasPrefix(codeintelUploadFlags.repo, "gitlab.com")
274-
275-
if needsGitHubToken {
276-
if codeintelUploadFlags.gitHubToken != "" {
277-
actionableHints = append(actionableHints,
278-
fmt.Sprintf("The supplied -github-token does not indicate that you have collaborator access to %s.", codeintelUploadFlags.repo),
279-
"Please check the value of the supplied token and its permissions on the code host and try again.",
280-
)
281-
} else {
282-
actionableHints = append(actionableHints,
283-
fmt.Sprintf("Please retry your request with a -github-token=XXX with collaborator access to %s.", codeintelUploadFlags.repo),
284-
"This token will be used to check with the code host that the uploading user has write access to the target repository.",
285-
)
286-
}
287-
} else if needsGitLabToken {
288-
if codeintelUploadFlags.gitLabToken != "" {
289-
actionableHints = append(actionableHints,
290-
fmt.Sprintf("The supplied -gitlab-token does not indicate that you have write access to %s.", codeintelUploadFlags.repo),
291-
"Please check the value of the supplied token and its permissions on the code host and try again.",
292-
)
293-
} else {
294-
actionableHints = append(actionableHints,
295-
fmt.Sprintf("Please retry your request with a -gitlab-token=XXX with write access to %s.", codeintelUploadFlags.repo),
296-
"This token will be used to check with the code host that the uploading user has write access to the target repository.",
297-
)
298-
}
299-
} else {
300-
actionableHints = append(actionableHints,
301-
"Verification is supported for the following code hosts: github.com, gitlab.com.",
302-
"Please request support for additional code host verification at https://github.com/sourcegraph/sourcegraph/issues/4967.",
303-
)
304-
}
305-
306-
return errorWithHint{err: originalError, hint: strings.Join(mergeStringSlices(
307-
[]string{"This Sourcegraph instance has enforced auth for SCIP/LSIF uploads."},
308-
actionableHints,
309-
[]string{"For more details, see https://docs.sourcegraph.com/cli/references/code-intel/upload."},
310-
), "\n")}
313+
return hint
311314
}
312315

313316
// emergencyOutput creates a default Output object writing to standard out.
314317
func emergencyOutput() *output.Output {
315318
return output.NewOutput(os.Stdout, output.OutputOpts{})
316319
}
317320

318-
func mergeStringSlices(ss ...[]string) []string {
319-
var combined []string
320-
for _, s := range ss {
321-
combined = append(combined, s...)
322-
}
323321

324-
return combined
325-
}

src

83.9 MB
Binary file not shown.

0 commit comments

Comments
 (0)