chore: Remove global externalURL watcher (#64058)

This removes the need to register a global URL watcher. Instead, the
conf package now returns a cached version, and also makes a copy of it
so it's impossible to accidentally modify it.

This makes it safe in non-cmd/frontend packages to use this.

Test plan: All tests are still passing.
This commit is contained in:
Erik Seliger 2024-07-31 03:43:28 +02:00 committed by GitHub
parent 56467e3c48
commit 35dcea121f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 160 additions and 158 deletions

View File

@ -15,7 +15,6 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cmd/frontend/backend",
"//cmd/frontend/globals",
"//cmd/frontend/internal/app/router",
"//cmd/frontend/internal/app/ui/router",
"//cmd/frontend/internal/auth/session",

View File

@ -6,7 +6,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/auth/userpasswd"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/database"
@ -41,6 +40,6 @@ func ResetPasswordURL(ctx context.Context, db database.DB, logger log.Logger, us
return nil, errors.Wrap(err, msg)
}
ru := globals.ExternalURL().ResolveReference(resetURL).String()
ru := conf.ExternalURLParsed().ResolveReference(resetURL).String()
return &ru, nil
}

View File

@ -21,7 +21,6 @@ go_library(
importpath = "github.com/sourcegraph/sourcegraph/cmd/frontend/backend",
visibility = ["//visibility:public"],
deps = [
"//cmd/frontend/globals",
"//cmd/frontend/internal/app/router",
"//cmd/frontend/internal/inventory",
"//internal/actor",

View File

@ -9,7 +9,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/router"
"github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/auth"
@ -385,7 +384,7 @@ func (e *userEmails) SendUserEmailOnFieldUpdate(ctx context.Context, id int32, c
Email: email,
Change: change,
Username: usr.Username,
Host: globals.ExternalURL().Host,
Host: conf.ExternalURLParsed().Host,
},
})
}
@ -432,7 +431,7 @@ func (e *userEmails) SendUserEmailOnAccessTokenChange(ctx context.Context, id in
Email: email,
TokenName: tokenName,
Username: usr.Username,
Host: globals.ExternalURL().Host,
Host: conf.ExternalURLParsed().Host,
},
})
}
@ -572,11 +571,11 @@ func SendUserEmailVerificationEmail(ctx context.Context, username, email, code s
Host string
}{
Username: username,
URL: globals.ExternalURL().ResolveReference(&url.URL{
URL: conf.ExternalURLParsed().ResolveReference(&url.URL{
Path: verifyEmailPath.Path,
RawQuery: q.Encode(),
}).String(),
Host: globals.ExternalURL().Host,
Host: conf.ExternalURLParsed().Host,
},
})
}

View File

@ -1,9 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "globals",
srcs = ["globals.go"],
importpath = "github.com/sourcegraph/sourcegraph/cmd/frontend/external/globals",
visibility = ["//visibility:public"],
deps = ["//cmd/frontend/globals"],
)

View File

@ -1,13 +0,0 @@
// Package globals exports symbols from frontend/globals. See the parent
// package godoc for more information.
package globals
import (
"net/url"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
)
func ExternalURL() *url.URL {
return globals.ExternalURL()
}

View File

@ -5,8 +5,5 @@ go_library(
srcs = ["globals.go"],
importpath = "github.com/sourcegraph/sourcegraph/cmd/frontend/globals",
visibility = ["//visibility:public"],
deps = [
"//internal/conf",
"@com_github_inconshreveable_log15//:log15",
],
deps = ["//internal/conf"],
)

View File

@ -2,69 +2,9 @@
package globals
import (
"net/url"
"reflect"
"sync"
"sync/atomic"
"github.com/inconshreveable/log15" //nolint:logging // TODO move all logging to sourcegraph/log //nolint:go
"github.com/sourcegraph/sourcegraph/internal/conf"
)
var defaultExternalURL = &url.URL{
Scheme: "http",
Host: "example.com",
}
var externalURL = func() atomic.Value {
var v atomic.Value
v.Store(defaultExternalURL)
return v
}()
var watchExternalURLOnce sync.Once
// WatchExternalURL watches for changes in the `externalURL` site configuration
// so that changes are reflected in what is returned by the ExternalURL function.
func WatchExternalURL() {
watchExternalURLOnce.Do(func() {
conf.Watch(func() {
after := defaultExternalURL
if val := conf.Get().ExternalURL; val != "" {
var err error
if after, err = url.Parse(val); err != nil {
log15.Error("globals.ExternalURL", "value", val, "error", err)
return
}
}
if before := ExternalURL(); !reflect.DeepEqual(before, after) {
SetExternalURL(after)
if before.Host != "example.com" {
log15.Info(
"globals.ExternalURL",
"updated", true,
"before", before,
"after", after,
)
}
}
})
})
}
// ExternalURL returns the fully-resolved, externally accessible frontend URL.
// Callers must not mutate the returned pointer.
func ExternalURL() *url.URL {
return externalURL.Load().(*url.URL)
}
// SetExternalURL sets the fully-resolved, externally accessible frontend URL.
func SetExternalURL(u *url.URL) {
externalURL.Store(u)
}
// ConfigurationServerFrontendOnly provides the contents of the site configuration
// to other services and manages modifications to it.
//

View File

@ -16,7 +16,6 @@ import (
"github.com/inconshreveable/log15" //nolint:logging // TODO move all logging to sourcegraph/log
"go.opentelemetry.io/otel/attribute"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend/externallink"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/cloneurls"
"github.com/sourcegraph/sourcegraph/internal/api"
@ -418,7 +417,7 @@ func (r *GitTreeEntryResolver) ExternalURLs(ctx context.Context) ([]*externallin
}
func (r *GitTreeEntryResolver) RawZipArchiveURL() string {
return globals.ExternalURL().ResolveReference(&url.URL{
return conf.ExternalURLParsed().ResolveReference(&url.URL{
Path: path.Join(r.Repository().URL(), "-/raw/", r.Path()),
RawQuery: "format=zip",
}).String()

View File

@ -14,7 +14,6 @@ import (
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
sgactor "github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/auth"
"github.com/sourcegraph/sourcegraph/internal/authz/permssync"
@ -305,7 +304,7 @@ func orgInvitationURLLegacy(org *types.Org, relative bool) string {
if relative {
return path
}
return globals.ExternalURL().ResolveReference(&url.URL{Path: path}).String()
return conf.ExternalURLParsed().ResolveReference(&url.URL{Path: path}).String()
}
func orgInvitationURL(invitation database.OrgInvitation, relative bool) (string, error) {
@ -320,7 +319,7 @@ func orgInvitationURL(invitation database.OrgInvitation, relative bool) (string,
if relative {
return path, nil
}
return globals.ExternalURL().ResolveReference(&url.URL{Path: path}).String(), nil
return conf.ExternalURLParsed().ResolveReference(&url.URL{Path: path}).String(), nil
}
func createInvitationJWT(orgID int32, invitationID int64, senderID int32, expiryTime time.Time) (string, error) {
@ -331,7 +330,7 @@ func createInvitationJWT(orgID int32, invitationID int64, senderID int32, expiry
token := jwt.NewWithClaims(jwt.SigningMethodHS512, &orgInvitationClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: globals.ExternalURL().String(),
Issuer: conf.ExternalURLParsed().String(),
ExpiresAt: jwt.NewNumericDate(expiryTime),
Subject: strconv.FormatInt(int64(orgID), 10),
},

View File

@ -9,7 +9,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/auth/userpasswd"
"github.com/sourcegraph/sourcegraph/internal/auth"
"github.com/sourcegraph/sourcegraph/internal/conf"
@ -27,7 +26,7 @@ func (r *randomizeUserPasswordResult) ResetPasswordURL() *string {
if r.resetURL == nil {
return nil
}
urlStr := globals.ExternalURL().ResolveReference(r.resetURL).String()
urlStr := conf.ExternalURLParsed().ResolveReference(r.resetURL).String()
return &urlStr
}

View File

@ -22,7 +22,6 @@ go_library(
deps = [
"//cmd/frontend/auth",
"//cmd/frontend/backend",
"//cmd/frontend/globals",
"//cmd/frontend/internal/app/assetsutil",
"//cmd/frontend/internal/app/debugproxies",
"//cmd/frontend/internal/app/errorutil",

View File

@ -5,7 +5,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/errorutil"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/router"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/app/ui"
@ -24,7 +23,7 @@ import (
// and sets the actor in the request context.
func NewHandler(db database.DB, logger log.Logger) http.Handler {
session.SetSessionStore(session.NewRedisStore(func() bool {
return globals.ExternalURL().Scheme == "https"
return conf.ExternalURLParsed().Scheme == "https"
}))
logger = logger.Scoped("appHandler")

View File

@ -276,7 +276,7 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
a := sgactor.FromContext(ctx)
headers := make(map[string]string)
headers["x-sourcegraph-client"] = globals.ExternalURL().String()
headers["x-sourcegraph-client"] = conf.ExternalURLParsed().String()
headers["X-Requested-With"] = "Sourcegraph" // required for httpapi to use cookie auth
// Propagate Cache-Control no-cache and max-age=0 directives
@ -381,7 +381,7 @@ func NewJSContextFromRequest(req *http.Request, db database.DB) JSContext {
// authentication above, but do not include e.g. hard-coded secrets about
// the server instance here as they would be sent to anonymous users.
context := JSContext{
ExternalURL: globals.ExternalURL().String(),
ExternalURL: conf.ExternalURLParsed().String(),
XHRHeaders: headers,
UserAgentIsBot: isBot(req.UserAgent()),
AssetsRoot: assetsutil.URL("").String(),

View File

@ -7,7 +7,7 @@ import (
"github.com/inconshreveable/log15" //nolint:logging // TODO move all logging to sourcegraph/log
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
)
var openSearchDescription = template.Must(template.New("").Parse(`
@ -26,7 +26,7 @@ func openSearch(w http.ResponseWriter, r *http.Request) {
BaseURL string
SearchURL string
}
externalURL := globals.ExternalURL()
externalURL := conf.ExternalURLParsed()
externalURLStr := externalURL.String()
data := vars{
BaseURL: externalURLStr,

View File

@ -15,7 +15,6 @@ go_library(
deps = [
"//cmd/frontend/auth",
"//cmd/frontend/external/session",
"//cmd/frontend/globals",
"//cmd/frontend/hubspot",
"//cmd/frontend/internal/auth/providers",
"//internal/actor",

View File

@ -14,8 +14,8 @@ import (
"golang.org/x/oauth2"
"github.com/sourcegraph/sourcegraph/cmd/frontend/external/session"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/auth/providers"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/extsvc"
"github.com/sourcegraph/sourcegraph/internal/extsvc/azuredevops"
"github.com/sourcegraph/sourcegraph/internal/extsvc/bitbucketcloud"
@ -263,7 +263,7 @@ func canRedirect(redirect string) bool {
return false
}
// if we have a non-relative url, make sure it's the same host as the sourcegraph instance
if redirectURL.Host != "" && redirectURL.Host != globals.ExternalURL().Host {
if redirectURL.Host != "" && redirectURL.Host != conf.ExternalURLParsed().Host {
return false
}
// TODO: do we want to exclude any internal paths here?

View File

@ -14,7 +14,6 @@ go_library(
visibility = ["//cmd/frontend:__subpackages__"],
deps = [
"//cmd/frontend/auth",
"//cmd/frontend/external/globals",
"//cmd/frontend/external/session",
"//cmd/frontend/hubspot",
"//cmd/frontend/hubspot/hubspotutil",

View File

@ -11,8 +11,8 @@ import (
"github.com/coreos/go-oidc"
"golang.org/x/oauth2"
"github.com/sourcegraph/sourcegraph/cmd/frontend/external/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/auth/providers"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/extsvc"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/schema"
@ -128,7 +128,7 @@ func (p *Provider) oauth2Config() *oauth2.Config {
// It would be nice if this was "/.auth/openidconnect/callback" not "/.auth/callback", but
// many instances have the "/.auth/callback" value hardcoded in their external auth
// provider, so we can't change it easily
RedirectURL: globals.ExternalURL().
RedirectURL: conf.ExternalURLParsed().
ResolveReference(&url.URL{Path: p.callbackUrl}).
String(),

View File

@ -20,7 +20,6 @@ go_library(
visibility = ["//cmd/frontend:__subpackages__"],
deps = [
"//cmd/frontend/auth",
"//cmd/frontend/globals",
"//cmd/frontend/internal/auth/scim/filter",
"//cmd/frontend/internal/auth/userpasswd",
"//internal/authz",

View File

@ -13,7 +13,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/auth"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/authz"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/database"
@ -249,7 +248,7 @@ func (u *UserSCIMService) Create(ctx context.Context, attributes scim.ResourceAt
// Attempt to send emails in the background.
goroutine.Go(func() {
_ = sendPasswordResetEmail(u.getLogger(), u.db, user, primaryEmail)
_ = sendWelcomeEmail(primaryEmail, globals.ExternalURL().String(), u.getLogger())
_ = sendWelcomeEmail(primaryEmail, conf.ExternalURLParsed().String(), u.getLogger())
})
now := time.Now()

View File

@ -22,7 +22,6 @@ go_library(
visibility = ["//:__subpackages__"],
deps = [
"//cmd/frontend/backend",
"//cmd/frontend/globals",
"//cmd/frontend/hubspot",
"//cmd/frontend/hubspot/hubspotutil",
"//cmd/frontend/internal/auth/providers",

View File

@ -10,7 +10,6 @@ import (
"github.com/golang-jwt/jwt/v4"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/rcache"
"github.com/sourcegraph/sourcegraph/internal/redispool"
@ -124,7 +123,7 @@ func (s *lockoutStore) GenerateUnlockAccountURL(userID int32) (string, string, e
token := jwt.NewWithClaims(jwt.SigningMethodHS512, &unlockAccountClaims{
RegisteredClaims: jwt.RegisteredClaims{
Issuer: globals.ExternalURL().String(),
Issuer: conf.ExternalURLParsed().String(),
ExpiresAt: jwt.NewNumericDate(expiryTime),
Subject: strconv.FormatInt(int64(userID), 10),
},
@ -145,7 +144,7 @@ func (s *lockoutStore) GenerateUnlockAccountURL(userID int32) (string, string, e
path := fmt.Sprintf("/unlock-account/%s", tokenString)
return globals.ExternalURL().ResolveReference(&url.URL{Path: path}).String(), tokenString, nil
return conf.ExternalURLParsed().ResolveReference(&url.URL{Path: path}).String(), tokenString, nil
}
// take site config link expiry into account as well when setting unlock expiry

View File

@ -9,7 +9,6 @@ import (
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/cookie"
@ -30,8 +29,8 @@ func SendResetPasswordURLEmail(ctx context.Context, email, username string, rese
Template: emailTemplate,
Data: SetPasswordEmailTemplateData{
Username: username,
URL: globals.ExternalURL().ResolveReference(resetURL).String(),
Host: globals.ExternalURL().Host,
URL: conf.ExternalURLParsed().ResolveReference(resetURL).String(),
Host: conf.ExternalURLParsed().Host,
},
})
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/txemail"
@ -24,7 +23,7 @@ func HandleSetPasswordEmail(ctx context.Context, db database.DB, id int32, usern
return "", errors.Wrap(err, "make password reset URL")
}
shareableResetURL := globals.ExternalURL().ResolveReference(resetURL).String()
shareableResetURL := conf.ExternalURLParsed().ResolveReference(resetURL).String()
emailedResetURL := shareableResetURL
if !emailVerified {
@ -32,7 +31,7 @@ func HandleSetPasswordEmail(ctx context.Context, db database.DB, id int32, usern
if err != nil {
return shareableResetURL, errors.Wrap(err, "attach email verification")
}
emailedResetURL = globals.ExternalURL().ResolveReference(newURL).String()
emailedResetURL = conf.ExternalURLParsed().ResolveReference(newURL).String()
}
// Configure the template
@ -47,7 +46,7 @@ func HandleSetPasswordEmail(ctx context.Context, db database.DB, id int32, usern
Data: SetPasswordEmailTemplateData{
Username: username,
URL: emailedResetURL,
Host: globals.ExternalURL().Host,
Host: conf.ExternalURLParsed().Host,
},
}); err != nil {
return shareableResetURL, err

View File

@ -40,7 +40,6 @@ go_library(
visibility = ["//cmd/frontend:__subpackages__"],
deps = [
"//cmd/frontend/enterprise",
"//cmd/frontend/globals",
"//cmd/frontend/graphqlbackend",
"//cmd/frontend/graphqlbackend/externallink",
"//cmd/frontend/graphqlbackend/graphqlutil",
@ -131,7 +130,6 @@ go_test(
],
deps = [
"//cmd/frontend/backend",
"//cmd/frontend/globals",
"//cmd/frontend/graphqlbackend",
"//cmd/frontend/graphqlbackend/externallink",
"//cmd/frontend/internal/batches/resolvers/apitest",

View File

@ -3,22 +3,21 @@ package resolvers
import (
"context"
"fmt"
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/githubapp"
"strconv"
"strings"
ghauth "github.com/sourcegraph/sourcegraph/internal/github_apps/auth"
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/githubapp"
btypes "github.com/sourcegraph/sourcegraph/internal/batches/types"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/extsvc"
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
ghauth "github.com/sourcegraph/sourcegraph/internal/github_apps/auth"
ghastore "github.com/sourcegraph/sourcegraph/internal/github_apps/store"
"github.com/sourcegraph/sourcegraph/internal/gqlutil"
"github.com/sourcegraph/sourcegraph/internal/types"
@ -67,7 +66,7 @@ func unmarshalBatchChangesCredentialID(id graphql.ID) (credentialID int64, isSit
}
func commentSSHKey(ssh auth.AuthenticatorWithSSH) string {
url := globals.ExternalURL()
url := conf.ExternalURLParsed()
if url != nil && url.Host != "" {
return strings.TrimRight(ssh.SSHPublicKey(), "\n") + " Sourcegraph " + url.Host
}

View File

@ -6,7 +6,7 @@ import (
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/extsvc/auth"
)
@ -67,7 +67,7 @@ func TestUnmarshalBatchChangesCredentialID(t *testing.T) {
func TestCommentSSHKey(t *testing.T) {
publicKey := "public\n"
sshKey := commentSSHKey(&auth.BasicAuthWithSSH{BasicAuth: auth.BasicAuth{Username: "foo", Password: "bar"}, PrivateKey: "private", PublicKey: publicKey, Passphrase: "pass"})
expectedKey := "public Sourcegraph " + globals.ExternalURL().Host
expectedKey := "public Sourcegraph " + conf.ExternalURLParsed().Host
if sshKey != expectedKey {
t.Errorf("found wrong ssh key: want=%q, have=%q", expectedKey, sshKey)

View File

@ -14,11 +14,11 @@ go_library(
importpath = "github.com/sourcegraph/sourcegraph/cmd/frontend/internal/cli/middleware",
visibility = ["//cmd/frontend:__subpackages__"],
deps = [
"//cmd/frontend/globals",
"//cmd/frontend/internal/app/router",
"//cmd/frontend/internal/app/ui",
"//cmd/frontend/internal/app/ui/router",
"//internal/actor",
"//internal/conf",
"//internal/dotcom",
"//internal/env",
"//internal/trace",

View File

@ -7,7 +7,7 @@ import (
"path"
"strings"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/trace"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
@ -61,7 +61,7 @@ func SourcegraphComGoGetHandler(next http.Handler) http.Handler {
// It's a vanity import path that maps to "github.com/{sourcegraph,sqs}/*" clone URLs.
pathElements := strings.Split(req.URL.Path[1:], "/")
if len(pathElements) >= 2 && (pathElements[0] == "sourcegraph" || pathElements[0] == "sqs") {
host := globals.ExternalURL().Host
host := conf.ExternalURLParsed().Host
user := pathElements[0]
repo := pathElements[1]

View File

@ -204,8 +204,6 @@ func Main(ctx context.Context, observationCtx *observation.Context, ready servic
return err
}
globals.WatchExternalURL()
// Single shot
goroutine.Go(func() { bg.CheckRedisCacheEvictionPolicy() })
goroutine.Go(func() { bg.UpdatePermissions(ctx, logger, db) })
@ -296,7 +294,7 @@ func Main(ctx context.Context, observationCtx *observation.Context, ready servic
infos[i] = providerInfo{
ServiceType: p.ServiceType(),
ServiceID: p.ServiceID(),
ExternalServiceURL: fmt.Sprintf("%s/site-admin/external-services/%s", globals.ExternalURL(), relay.MarshalID("ExternalService", id)),
ExternalServiceURL: fmt.Sprintf("%s/site-admin/external-services/%s", conf.ExternalURLParsed(), relay.MarshalID("ExternalService", id)),
}
}
@ -313,7 +311,7 @@ func Main(ctx context.Context, observationCtx *observation.Context, ready servic
// This is not a log entry and is usually disabled
println(fmt.Sprintf("\n\n%s\n\n", logoColor))
}
logger.Info(fmt.Sprintf("✱ Sourcegraph is ready at: %s", globals.ExternalURL()))
logger.Info(fmt.Sprintf("✱ Sourcegraph is ready at: %s", conf.ExternalURLParsed()))
ready()
return goroutine.MonitorBackgroundRoutines(context.Background(), routines...)

View File

@ -39,9 +39,9 @@ go_test(
"requires-network",
],
deps = [
"//cmd/frontend/external/globals",
"//cmd/frontend/webhooks",
"//internal/api",
"//internal/conf",
"//internal/database",
"//internal/database/dbmocks",
"//internal/database/dbtest",

View File

@ -22,9 +22,9 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/sourcegraph/sourcegraph/cmd/frontend/external/globals"
"github.com/sourcegraph/sourcegraph/cmd/frontend/webhooks"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/internal/database/dbmocks"
"github.com/sourcegraph/sourcegraph/internal/database/dbtest"
@ -158,7 +158,7 @@ func TestGitHubHandler(t *testing.T) {
t.Fatal(err)
}
targetURL := fmt.Sprintf("%s/github-webhooks", globals.ExternalURL())
targetURL := fmt.Sprintf("%s/github-webhooks", conf.ExternalURLParsed())
req, err := http.NewRequest("POST", targetURL, bytes.NewReader(payload))
if err != nil {
t.Fatal(err)

View File

@ -13,7 +13,6 @@ go_library(
tags = [TAG_PLATFORM_SOURCE],
visibility = ["//visibility:public"],
deps = [
"//cmd/frontend/globals",
"//cmd/repo-updater/internal/gitserver",
"//cmd/repo-updater/internal/purge",
"//cmd/repo-updater/internal/repoupdater",

View File

@ -17,7 +17,6 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/sourcegraph/log"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
repogitserver "github.com/sourcegraph/sourcegraph/cmd/repo-updater/internal/gitserver"
"github.com/sourcegraph/sourcegraph/cmd/repo-updater/internal/purge"
"github.com/sourcegraph/sourcegraph/cmd/repo-updater/internal/repoupdater"
@ -118,7 +117,6 @@ func Main(ctx context.Context, observationCtx *observation.Context, ready servic
server.ChangesetSyncRegistry = syncRegistry
}
go globals.WatchExternalURL()
go watchSyncer(ctx, logger, syncer, updateScheduler, server.ChangesetSyncRegistry)
routines := []goroutine.BackgroundRoutine{

View File

@ -51,6 +51,7 @@ go_library(
"@com_github_xeipuuv_gojsonschema//:gojsonschema",
"@org_golang_google_grpc//codes",
"@org_golang_google_grpc//status",
"@org_uber_go_atomic//:atomic",
],
)

View File

@ -2,12 +2,15 @@ package conf
import (
"encoding/hex"
"log" //nolint:logging // TODO move all logging to sourcegraph/log
stdlog "log" //nolint:logging // TODO move all logging to sourcegraph/log
"net/url"
"slices"
"strings"
"time"
"github.com/hashicorp/cronexpr"
"github.com/sourcegraph/log"
"go.uber.org/atomic"
"github.com/sourcegraph/sourcegraph/internal/completions/client/anthropic"
"github.com/sourcegraph/sourcegraph/internal/completions/client/google"
@ -28,7 +31,7 @@ const CodyGatewayProdEndpoint = "https://cody-gateway.sourcegraph.com"
func init() {
deployType := deploy.Type()
if !deploy.IsValidDeployType(deployType) {
log.Fatalf("The 'DEPLOY_TYPE' environment variable is invalid. Expected one of: %q, %q, %q, %q, %q, %q. Got: %q", deploy.Kubernetes, deploy.DockerCompose, deploy.PureDocker, deploy.SingleDocker, deploy.Dev, deploy.Helm, deployType)
stdlog.Fatalf("The 'DEPLOY_TYPE' environment variable is invalid. Expected one of: %q, %q, %q, %q, %q, %q. Got: %q", deploy.Kubernetes, deploy.DockerCompose, deploy.PureDocker, deploy.SingleDocker, deploy.Dev, deploy.Helm, deployType)
}
confdefaults.Default = defaultConfigForDeployment()
@ -434,6 +437,62 @@ func ProductResearchPageEnabled() bool {
return true
}
type lastParsedURLValue struct {
url *url.URL
confValue string
}
var lastParsedURL *atomic.Pointer[lastParsedURLValue] = atomic.NewPointer(&lastParsedURLValue{
url: &url.URL{
Scheme: "http",
Host: "example.com",
},
confValue: "",
})
// ExternalURLParsed returns a parsed version of conf.ExternalURL().
// This function is thread-safe and returns a copy of the cached parsed version
// of the URL, so it is also safe to mutate.
func ExternalURLParsed() *url.URL {
// Note that we do NOT use a mutex here, and instead let callers parse the URL
// simultaneously during the short period where the URL was changed but the new
// parsed value has not yet been cached.
// This eliminates the need to acquire a mutex entirely, and avoids potential costly
// locking if many requests are served concurrently.
urlString := ExternalURL()
lastParsed := lastParsedURL.Load()
clonedURL := cloneURL(lastParsed.url)
if lastParsed.confValue != urlString {
parsed, err := url.Parse(urlString)
if err != nil {
log.Scoped("conf.ExternalURL").Error("failed to parse external URL", log.Error(err), log.String("externalURL", urlString))
return clonedURL
}
lastParsed = &lastParsedURLValue{
url: parsed,
confValue: urlString,
}
lastParsedURL.Store(lastParsed)
clonedURL = cloneURL(parsed)
}
return clonedURL
}
// cloneURL returns a copy of the URL. It is safe to mutate the returned URL.
// This is copied from net/http/clone.go
func cloneURL(u *url.URL) *url.URL {
if u == nil {
return nil
}
u2 := new(url.URL)
*u2 = *u
if u.User != nil {
u2.User = new(url.Userinfo)
*u2.User = *u.User
}
return u2
}
func ExternalURL() string {
return Get().ExternalURL
}

View File

@ -2,6 +2,7 @@ package conf
import (
"fmt"
"sync"
"testing"
"time"
@ -1356,3 +1357,54 @@ func TestAccessTokensExpirationOptions(t *testing.T) {
})
}
}
func TestExternalURLParsed(t *testing.T) {
t.Run("Result is mutable", func(t *testing.T) {
Mock(&Unified{SiteConfiguration: schema.SiteConfiguration{
ExternalURL: "https://sourcegraph.com",
}})
t.Cleanup(func() { Mock(nil) })
u := ExternalURLParsed()
u.Scheme = "http"
assert.Equal(t, "http://sourcegraph.com", u.String())
u2 := ExternalURLParsed()
assert.Equal(t, "https://sourcegraph.com", u2.String())
Mock(&Unified{SiteConfiguration: schema.SiteConfiguration{
ExternalURL: "https://sourcegraph.sourcegraph.com",
}})
assert.Equal(t, "http://sourcegraph.com", u.String())
assert.Equal(t, "https://sourcegraph.com", u2.String())
})
t.Run("Concurrent access and updates are memory safe", func(t *testing.T) {
Mock(&Unified{SiteConfiguration: schema.SiteConfiguration{
ExternalURL: "https://host-0.sourcegraph.com",
}})
t.Cleanup(func() { Mock(nil) })
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := range 1000 {
Mock(&Unified{SiteConfiguration: schema.SiteConfiguration{
ExternalURL: fmt.Sprintf("https://host-%d.sourcegraph.com", i),
}})
// Allow some time for synchronization.
time.Sleep(time.Millisecond)
}
}()
go func() {
defer wg.Done()
for range 1000 {
u := ExternalURLParsed()
assert.Contains(t, u.Host, ".sourcegraph.com")
// Allow some time for synchronization.
time.Sleep(time.Millisecond)
}
}()
wg.Wait()
})
}

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/sourcegraph/sourcegraph/internal/deviceid",
visibility = ["//:__subpackages__"],
deps = [
"//cmd/frontend/globals",
"//internal/conf",
"//internal/cookie",
"@com_github_google_uuid//:uuid",
],

View File

@ -7,7 +7,7 @@ import (
"github.com/google/uuid"
"github.com/sourcegraph/sourcegraph/cmd/frontend/globals"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/cookie"
)
@ -22,7 +22,7 @@ func Middleware(next http.Handler) http.Handler {
Name: "sourcegraphDeviceId",
Value: newDeviceId.String(),
Expires: time.Now().AddDate(1, 0, 0),
Secure: globals.ExternalURL().Scheme == "https",
Secure: conf.ExternalURLParsed().Scheme == "https",
Domain: r.URL.Host,
})
}