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
4 changes: 2 additions & 2 deletions cmd/src/batch_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) (err error
if err != nil {
return execUI.CreatingBatchSpecError(lr.MaxUnlicensedChangesets, err)
}
previewURL := cfg.Endpoint + url
previewURL := cfg.endpointURL.JoinPath(url).String()
execUI.CreatingBatchSpecSuccess(previewURL)

hasWorkspaceFiles := false
Expand Down Expand Up @@ -567,7 +567,7 @@ func executeBatchSpec(ctx context.Context, opts executeBatchSpecOpts) (err error
if err != nil {
return err
}
execUI.ApplyingBatchSpecSuccess(cfg.Endpoint + batch.URL)
execUI.ApplyingBatchSpecSuccess(cfg.endpointURL.JoinPath(batch.URL).String())

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/src/batch_remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Examples:

executionURL := fmt.Sprintf(
"%s/%s/batch-changes/%s/executions/%s",
strings.TrimSuffix(cfg.Endpoint, "/"),
cfg.endpointURL,
strings.TrimPrefix(namespace.URL, "/"),
batchChangeName,
batchSpecID,
Expand Down
2 changes: 1 addition & 1 deletion cmd/src/batch_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Examples:
Max: max,
RepoCount: len(repos),
Repos: repos,
SourcegraphEndpoint: cfg.Endpoint,
SourcegraphEndpoint: cfg.endpointURL.String(),
}); err != nil {
return err
}
Expand Down
27 changes: 9 additions & 18 deletions cmd/src/code_intel_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"flag"
"fmt"
"io"
"net/url"
"os"
"strings"
"time"
Expand Down Expand Up @@ -87,10 +86,7 @@ func handleCodeIntelUpload(args []string) error {
return handleUploadError(uploadOptions.SourcegraphInstanceOptions.AccessToken, err)
}

uploadURL, err := makeCodeIntelUploadURL(uploadID)
if err != nil {
return err
}
uploadURL := makeCodeIntelUploadURL(uploadID)

if codeintelUploadFlags.json {
serialized, err := json.Marshal(map[string]any{
Expand Down Expand Up @@ -132,7 +128,7 @@ func codeintelUploadOptions(out *output.Output) upload.UploadOptions {
associatedIndexID = &codeintelUploadFlags.associatedIndexID
}

cfg.AdditionalHeaders["Content-Type"] = "application/x-protobuf+scip"
cfg.additionalHeaders["Content-Type"] = "application/x-protobuf+scip"

logger := upload.NewRequestLogger(
os.Stdout,
Expand All @@ -153,9 +149,9 @@ func codeintelUploadOptions(out *output.Output) upload.UploadOptions {
AssociatedIndexID: associatedIndexID,
},
SourcegraphInstanceOptions: upload.SourcegraphInstanceOptions{
SourcegraphURL: cfg.Endpoint,
AccessToken: cfg.AccessToken,
AdditionalHeaders: cfg.AdditionalHeaders,
SourcegraphURL: cfg.endpointURL.String(),
AccessToken: cfg.accessToken,
AdditionalHeaders: cfg.additionalHeaders,
MaxRetries: 5,
RetryInterval: time.Second,
Path: codeintelUploadFlags.uploadRoute,
Expand Down Expand Up @@ -191,16 +187,11 @@ func printInferredArguments(out *output.Output) {

// makeCodeIntelUploadURL constructs a URL to the upload with the given internal identifier.
// The base of the URL is constructed from the configured Sourcegraph instance.
func makeCodeIntelUploadURL(uploadID int) (string, error) {
url, err := url.Parse(cfg.Endpoint)
if err != nil {
return "", err
}

func makeCodeIntelUploadURL(uploadID int) string {
u := *cfg.endpointURL
Copy link
Contributor Author

Choose a reason for hiding this comment

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

coy by dereference makes a shallow copy - affects User mostly/only - should we include a comment to that effect here?

graphqlID := base64.URLEncoding.EncodeToString(fmt.Appendf(nil, `SCIPUpload:%d`, uploadID))
url.Path = codeintelUploadFlags.repo + "/-/code-intelligence/uploads/" + graphqlID
url.User = nil
return url.String(), nil
u.Path = codeintelUploadFlags.repo + "/-/code-intelligence/uploads/" + graphqlID
return u.String()
}

type errorWithHint struct {
Expand Down
2 changes: 1 addition & 1 deletion cmd/src/debug_compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Examples:
return errors.Wrap(err, "failed to get containers for subcommand with err")
}
// Safety check user knows what they are targeting with this debug command
log.Printf("This command will archive docker-cli data for %d containers\n SRC_ENDPOINT: %v\n Output filename: %v", len(containers), cfg.Endpoint, base)
log.Printf("This command will archive docker-cli data for %d containers\n SRC_ENDPOINT: %v\n Output filename: %v", len(containers), cfg.endpointURL, base)
if verified, _ := verify("Do you want to start writing to an archive?"); !verified {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/src/debug_kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Examples:
return errors.Wrapf(err, "failed to get current-context")
}
// Safety check user knows what they've targeted with this command
log.Printf("Archiving kubectl data for %d pods\n SRC_ENDPOINT: %v\n Context: %s Namespace: %v\n Output filename: %v", len(pods.Items), cfg.Endpoint, kubectx, namespace, base)
log.Printf("Archiving kubectl data for %d pods\n SRC_ENDPOINT: %v\n Context: %s Namespace: %v\n Output filename: %v", len(pods.Items), cfg.endpointURL, kubectx, namespace, base)
if verified, _ := verify("Do you want to start writing to an archive?"); !verified {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/src/debug_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Examples:
defer zw.Close()

// Safety check user knows what they are targeting with this debug command
log.Printf("This command will archive docker-cli data for container: %s\n SRC_ENDPOINT: %s\n Output filename: %s", container, cfg.Endpoint, base)
log.Printf("This command will archive docker-cli data for container: %s\n SRC_ENDPOINT: %s\n Output filename: %s", container, cfg.endpointURL, base)
if verified, _ := verify("Do you want to start writing to an archive?"); !verified {
return nil
}
Expand Down
43 changes: 19 additions & 24 deletions cmd/src/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,21 @@ Examples:
if err := flagSet.Parse(args); err != nil {
return err
}
endpoint := cfg.Endpoint

if flagSet.NArg() >= 1 {
endpoint = flagSet.Arg(0)
}
if endpoint == "" {
return cmderrors.Usage("expected exactly one argument: the Sourcegraph URL, or SRC_ENDPOINT to be set")
arg := flagSet.Arg(0)
parsed, err := parseEndpoint(arg)
if err != nil {
return cmderrors.Usage(fmt.Sprintf("invalid endpoint URL: %s", arg))
}
if parsed.String() != cfg.endpointURL.String() {
return cmderrors.Usage(fmt.Sprintf("endpoint argument %s conflicts with configured endpoint %s", parsed, cfg.endpointURL))
}
Comment on lines +54 to +56
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The existence of this check calls into question the command-line parameter - SRC_ENDPOINT (or the value from the config file) is the source[1] of truth, forcing the user to put the same value on the command line.
Adding to the confusion is that if there is nothing in the config file or in SRC_ENDPOINT, cfg.endpointURL defaults to https://sourcegraph.com, which is probably wrong pretty much all of the time, and still invalidates anything different on the command line.
I kept the error condition and message (although I did change the semantics by changing from an exit error to a usage error) because I didn't want to rip that rug out from under anyone at this point.
Thoughts?

[1] no pun intended, but I do like it

}

client := cfg.apiClient(apiFlags, io.Discard)

return loginCmd(context.Background(), cfg, client, endpoint, os.Stdout)
return loginCmd(context.Background(), cfg, client, os.Stdout)
}

commands = append(commands, &command{
Expand All @@ -64,9 +68,7 @@ Examples:
})
}

func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg string, out io.Writer) error {
endpointArg = cleanEndpoint(endpointArg)

func loginCmd(ctx context.Context, cfg *config, client api.Client, out io.Writer) error {
printProblem := func(problem string) {
fmt.Fprintf(out, "❌ Problem: %s\n", problem)
}
Expand All @@ -77,23 +79,16 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
export SRC_ACCESS_TOKEN=(your access token)
To verify that it's working, run the login command again.
`, endpointArg, endpointArg)
`, cfg.endpointURL, cfg.endpointURL)

if cfg.ConfigFilePath != "" {
if cfg.configFilePath != "" {
fmt.Fprintln(out)
fmt.Fprintf(out, "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove %s. See https://github.com/sourcegraph/src-cli#readme for more information.\n", cfg.ConfigFilePath)
fmt.Fprintf(out, "⚠️ Warning: Configuring src with a JSON file is deprecated. Please migrate to using the env vars SRC_ENDPOINT, SRC_ACCESS_TOKEN, and SRC_PROXY instead, and then remove %s. See https://github.com/sourcegraph/src-cli#readme for more information.\n", cfg.configFilePath)
Copy link
Contributor Author

@peterguy peterguy Mar 7, 2026

Choose a reason for hiding this comment

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

The config file has been deprecated for a long time, but if it ever is finally removed, the ability to set additional headers will go also. Do we need to keep the ability to set additional headers around? If so, we should probably introduce yet another environment variable for those and delay the config file removal. If not, let's complete the config file deprecation!

}

noToken := cfg.AccessToken == ""
endpointConflict := endpointArg != cfg.Endpoint
if noToken || endpointConflict {
if cfg.accessToken == "" {
fmt.Fprintln(out)
switch {
case noToken:
printProblem("No access token is configured.")
case endpointConflict:
printProblem(fmt.Sprintf("The configured endpoint is %s, not %s.", cfg.Endpoint, endpointArg))
}
printProblem("No access token is configured.")
fmt.Fprintln(out, createAccessTokenMessage)
return cmderrors.ExitCode1
}
Expand All @@ -107,7 +102,7 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
if strings.HasPrefix(err.Error(), "error: 401 Unauthorized") || strings.HasPrefix(err.Error(), "error: 403 Forbidden") {
printProblem("Invalid access token.")
} else {
printProblem(fmt.Sprintf("Error communicating with %s: %s", endpointArg, err))
printProblem(fmt.Sprintf("Error communicating with %s: %s", cfg.endpointURL, err))
}
fmt.Fprintln(out, createAccessTokenMessage)
fmt.Fprintln(out, " (If you need to supply custom HTTP request headers, see information about SRC_HEADER_* and SRC_HEADERS env vars at https://github.com/sourcegraph/src-cli/blob/main/AUTH_PROXY.md)")
Expand All @@ -117,11 +112,11 @@ func loginCmd(ctx context.Context, cfg *config, client api.Client, endpointArg s
if result.CurrentUser == nil {
// This should never happen; we verified there is an access token, so there should always be
// a user.
printProblem(fmt.Sprintf("Unable to determine user on %s.", endpointArg))
printProblem(fmt.Sprintf("Unable to determine user on %s.", cfg.endpointURL))
return cmderrors.ExitCode1
}
fmt.Fprintln(out)
fmt.Fprintf(out, "✔️ Authenticated as %s on %s\n", result.CurrentUser.Username, endpointArg)
fmt.Fprintf(out, "✔️ Authenticated as %s on %s\n", result.CurrentUser.Username, cfg.endpointURL)
fmt.Fprintln(out)
return nil
}
34 changes: 13 additions & 21 deletions cmd/src/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,25 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/sourcegraph/src-cli/internal/cmderrors"
)

func TestLogin(t *testing.T) {
check := func(t *testing.T, cfg *config, endpointArg string) (output string, err error) {
check := func(t *testing.T, cfg *config) (output string, err error) {
t.Helper()

var out bytes.Buffer
err = loginCmd(context.Background(), cfg, cfg.apiClient(nil, io.Discard), endpointArg, &out)
err = loginCmd(context.Background(), cfg, cfg.apiClient(nil, io.Discard), &out)
return strings.TrimSpace(out.String()), err
}

t.Run("different endpoint in config vs. arg", func(t *testing.T) {
out, err := check(t, &config{Endpoint: "https://example.com"}, "https://sourcegraph.example.com")
if err != cmderrors.ExitCode1 {
t.Fatal(err)
}
wantOut := "❌ Problem: No access token is configured.\n\n🛠 To fix: Create an access token by going to https://sourcegraph.example.com/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=https://sourcegraph.example.com\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again."
if out != wantOut {
t.Errorf("got output %q, want %q", out, wantOut)
}
})

t.Run("no access token", func(t *testing.T) {
out, err := check(t, &config{Endpoint: "https://example.com"}, "https://sourcegraph.example.com")
endpoint := &url.URL{Scheme: "https", Host: "sourcegraph.example.com"}
out, err := check(t, &config{endpointURL: endpoint})
if err != cmderrors.ExitCode1 {
t.Fatal(err)
}
Expand All @@ -45,7 +36,8 @@ func TestLogin(t *testing.T) {
})

t.Run("warning when using config file", func(t *testing.T) {
out, err := check(t, &config{Endpoint: "https://example.com", ConfigFilePath: "f"}, "https://example.com")
endpoint := &url.URL{Scheme: "https", Host: "example.com"}
out, err := check(t, &config{endpointURL: endpoint, configFilePath: "f"})
if err != cmderrors.ExitCode1 {
t.Fatal(err)
}
Expand All @@ -62,13 +54,13 @@ func TestLogin(t *testing.T) {
}))
defer s.Close()

endpoint := s.URL
out, err := check(t, &config{Endpoint: endpoint, AccessToken: "x"}, endpoint)
u, _ := url.ParseRequestURI(s.URL)
out, err := check(t, &config{endpointURL: u, accessToken: "x"})
if err != cmderrors.ExitCode1 {
t.Fatal(err)
}
wantOut := "❌ Problem: Invalid access token.\n\n🛠 To fix: Create an access token by going to $ENDPOINT/user/settings/tokens, then set the following environment variables in your terminal:\n\n export SRC_ENDPOINT=$ENDPOINT\n export SRC_ACCESS_TOKEN=(your access token)\n\n To verify that it's working, run the login command again.\n\n (If you need to supply custom HTTP request headers, see information about SRC_HEADER_* and SRC_HEADERS env vars at https://github.com/sourcegraph/src-cli/blob/main/AUTH_PROXY.md)"
wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", endpoint)
wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", s.URL)
if out != wantOut {
t.Errorf("got output %q, want %q", out, wantOut)
}
Expand All @@ -81,13 +73,13 @@ func TestLogin(t *testing.T) {
}))
defer s.Close()

endpoint := s.URL
out, err := check(t, &config{Endpoint: endpoint, AccessToken: "x"}, endpoint)
u, _ := url.ParseRequestURI(s.URL)
out, err := check(t, &config{endpointURL: u, accessToken: "x"})
if err != nil {
t.Fatal(err)
}
wantOut := "✔️ Authenticated as alice on $ENDPOINT"
wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", endpoint)
wantOut = strings.ReplaceAll(wantOut, "$ENDPOINT", s.URL)
if out != wantOut {
t.Errorf("got output %q, want %q", out, wantOut)
}
Expand Down
Loading
Loading