diff --git a/cmd/frontend/auth/BUILD.bazel b/cmd/frontend/auth/BUILD.bazel index 7c464da428d..164eb238b24 100644 --- a/cmd/frontend/auth/BUILD.bazel +++ b/cmd/frontend/auth/BUILD.bazel @@ -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", diff --git a/cmd/frontend/auth/reset_password.go b/cmd/frontend/auth/reset_password.go index d1ff154b2ae..06d700cece9 100644 --- a/cmd/frontend/auth/reset_password.go +++ b/cmd/frontend/auth/reset_password.go @@ -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 } diff --git a/cmd/frontend/backend/BUILD.bazel b/cmd/frontend/backend/BUILD.bazel index a978febf8ad..629b59a26f3 100644 --- a/cmd/frontend/backend/BUILD.bazel +++ b/cmd/frontend/backend/BUILD.bazel @@ -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", diff --git a/cmd/frontend/backend/user_emails.go b/cmd/frontend/backend/user_emails.go index 89247642e50..b0542e556d4 100644 --- a/cmd/frontend/backend/user_emails.go +++ b/cmd/frontend/backend/user_emails.go @@ -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, }, }) } diff --git a/cmd/frontend/external/globals/BUILD.bazel b/cmd/frontend/external/globals/BUILD.bazel deleted file mode 100644 index 2307a09d848..00000000000 --- a/cmd/frontend/external/globals/BUILD.bazel +++ /dev/null @@ -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"], -) diff --git a/cmd/frontend/external/globals/globals.go b/cmd/frontend/external/globals/globals.go deleted file mode 100644 index 67980615753..00000000000 --- a/cmd/frontend/external/globals/globals.go +++ /dev/null @@ -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() -} diff --git a/cmd/frontend/globals/BUILD.bazel b/cmd/frontend/globals/BUILD.bazel index b0c1d600f33..6ac1f944750 100644 --- a/cmd/frontend/globals/BUILD.bazel +++ b/cmd/frontend/globals/BUILD.bazel @@ -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"], ) diff --git a/cmd/frontend/globals/globals.go b/cmd/frontend/globals/globals.go index 176c86d78bf..5e222d736f3 100644 --- a/cmd/frontend/globals/globals.go +++ b/cmd/frontend/globals/globals.go @@ -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. // diff --git a/cmd/frontend/graphqlbackend/git_tree_entry.go b/cmd/frontend/graphqlbackend/git_tree_entry.go index 199f2db864e..07a55831ae1 100644 --- a/cmd/frontend/graphqlbackend/git_tree_entry.go +++ b/cmd/frontend/graphqlbackend/git_tree_entry.go @@ -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() diff --git a/cmd/frontend/graphqlbackend/org_invitations.go b/cmd/frontend/graphqlbackend/org_invitations.go index 993919d1f91..58f9183b6c2 100644 --- a/cmd/frontend/graphqlbackend/org_invitations.go +++ b/cmd/frontend/graphqlbackend/org_invitations.go @@ -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), }, diff --git a/cmd/frontend/graphqlbackend/users_randomize_password.go b/cmd/frontend/graphqlbackend/users_randomize_password.go index 2dd5f0bcd2c..a4ba01cc2ce 100644 --- a/cmd/frontend/graphqlbackend/users_randomize_password.go +++ b/cmd/frontend/graphqlbackend/users_randomize_password.go @@ -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 } diff --git a/cmd/frontend/internal/app/BUILD.bazel b/cmd/frontend/internal/app/BUILD.bazel index 57e729b4be2..5b29e51d243 100644 --- a/cmd/frontend/internal/app/BUILD.bazel +++ b/cmd/frontend/internal/app/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/app/app.go b/cmd/frontend/internal/app/app.go index d5bd3fc1514..e6518602663 100644 --- a/cmd/frontend/internal/app/app.go +++ b/cmd/frontend/internal/app/app.go @@ -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") diff --git a/cmd/frontend/internal/app/jscontext/jscontext.go b/cmd/frontend/internal/app/jscontext/jscontext.go index 5e69cb199e0..6aed8825afe 100644 --- a/cmd/frontend/internal/app/jscontext/jscontext.go +++ b/cmd/frontend/internal/app/jscontext/jscontext.go @@ -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(), diff --git a/cmd/frontend/internal/app/opensearch.go b/cmd/frontend/internal/app/opensearch.go index 2d8842128d1..5f109329033 100644 --- a/cmd/frontend/internal/app/opensearch.go +++ b/cmd/frontend/internal/app/opensearch.go @@ -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, diff --git a/cmd/frontend/internal/auth/oauth/BUILD.bazel b/cmd/frontend/internal/auth/oauth/BUILD.bazel index 840e9ac20e2..3c939a5bd03 100644 --- a/cmd/frontend/internal/auth/oauth/BUILD.bazel +++ b/cmd/frontend/internal/auth/oauth/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/auth/oauth/provider.go b/cmd/frontend/internal/auth/oauth/provider.go index 6c01ae26492..6d125058ebb 100644 --- a/cmd/frontend/internal/auth/oauth/provider.go +++ b/cmd/frontend/internal/auth/oauth/provider.go @@ -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? diff --git a/cmd/frontend/internal/auth/openidconnect/BUILD.bazel b/cmd/frontend/internal/auth/openidconnect/BUILD.bazel index 3d10d52b94c..9df5ba8d20d 100644 --- a/cmd/frontend/internal/auth/openidconnect/BUILD.bazel +++ b/cmd/frontend/internal/auth/openidconnect/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/auth/openidconnect/provider.go b/cmd/frontend/internal/auth/openidconnect/provider.go index 7e0d0d994bd..105fc100599 100644 --- a/cmd/frontend/internal/auth/openidconnect/provider.go +++ b/cmd/frontend/internal/auth/openidconnect/provider.go @@ -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(), diff --git a/cmd/frontend/internal/auth/scim/BUILD.bazel b/cmd/frontend/internal/auth/scim/BUILD.bazel index c02a536fed5..108d50ef780 100644 --- a/cmd/frontend/internal/auth/scim/BUILD.bazel +++ b/cmd/frontend/internal/auth/scim/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/auth/scim/user_service.go b/cmd/frontend/internal/auth/scim/user_service.go index 22c45d1a5f6..af52e315058 100644 --- a/cmd/frontend/internal/auth/scim/user_service.go +++ b/cmd/frontend/internal/auth/scim/user_service.go @@ -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() diff --git a/cmd/frontend/internal/auth/userpasswd/BUILD.bazel b/cmd/frontend/internal/auth/userpasswd/BUILD.bazel index 7ffa5e9a73d..aac4777ee78 100644 --- a/cmd/frontend/internal/auth/userpasswd/BUILD.bazel +++ b/cmd/frontend/internal/auth/userpasswd/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/auth/userpasswd/lockout.go b/cmd/frontend/internal/auth/userpasswd/lockout.go index fe76d9ff29b..a39b69df829 100644 --- a/cmd/frontend/internal/auth/userpasswd/lockout.go +++ b/cmd/frontend/internal/auth/userpasswd/lockout.go @@ -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 diff --git a/cmd/frontend/internal/auth/userpasswd/reset_password.go b/cmd/frontend/internal/auth/userpasswd/reset_password.go index 238063dbd3d..8bf0e186948 100644 --- a/cmd/frontend/internal/auth/userpasswd/reset_password.go +++ b/cmd/frontend/internal/auth/userpasswd/reset_password.go @@ -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, }, }) } diff --git a/cmd/frontend/internal/auth/userpasswd/set_password.go b/cmd/frontend/internal/auth/userpasswd/set_password.go index 614fe417230..c0e7c4087bc 100644 --- a/cmd/frontend/internal/auth/userpasswd/set_password.go +++ b/cmd/frontend/internal/auth/userpasswd/set_password.go @@ -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 diff --git a/cmd/frontend/internal/batches/resolvers/BUILD.bazel b/cmd/frontend/internal/batches/resolvers/BUILD.bazel index 765bab3779e..0670061aa81 100644 --- a/cmd/frontend/internal/batches/resolvers/BUILD.bazel +++ b/cmd/frontend/internal/batches/resolvers/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/batches/resolvers/credential.go b/cmd/frontend/internal/batches/resolvers/credential.go index 935ec4079ab..c3a214c6cbb 100644 --- a/cmd/frontend/internal/batches/resolvers/credential.go +++ b/cmd/frontend/internal/batches/resolvers/credential.go @@ -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 } diff --git a/cmd/frontend/internal/batches/resolvers/credential_test.go b/cmd/frontend/internal/batches/resolvers/credential_test.go index 1a5f08ce315..ac9e3b4801a 100644 --- a/cmd/frontend/internal/batches/resolvers/credential_test.go +++ b/cmd/frontend/internal/batches/resolvers/credential_test.go @@ -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) diff --git a/cmd/frontend/internal/cli/middleware/BUILD.bazel b/cmd/frontend/internal/cli/middleware/BUILD.bazel index f1629c727c1..c4f07f88071 100644 --- a/cmd/frontend/internal/cli/middleware/BUILD.bazel +++ b/cmd/frontend/internal/cli/middleware/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/cli/middleware/goimportpath.go b/cmd/frontend/internal/cli/middleware/goimportpath.go index 6e5ff6da7b2..c219a0d5996 100644 --- a/cmd/frontend/internal/cli/middleware/goimportpath.go +++ b/cmd/frontend/internal/cli/middleware/goimportpath.go @@ -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] diff --git a/cmd/frontend/internal/cli/serve_cmd.go b/cmd/frontend/internal/cli/serve_cmd.go index a2b3a880669..eefdecb6ef0 100644 --- a/cmd/frontend/internal/cli/serve_cmd.go +++ b/cmd/frontend/internal/cli/serve_cmd.go @@ -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...) diff --git a/cmd/frontend/internal/repos/webhooks/BUILD.bazel b/cmd/frontend/internal/repos/webhooks/BUILD.bazel index f344d33974f..fea21694dcb 100644 --- a/cmd/frontend/internal/repos/webhooks/BUILD.bazel +++ b/cmd/frontend/internal/repos/webhooks/BUILD.bazel @@ -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", diff --git a/cmd/frontend/internal/repos/webhooks/handlers_test.go b/cmd/frontend/internal/repos/webhooks/handlers_test.go index 47ad611233e..36aec66f411 100644 --- a/cmd/frontend/internal/repos/webhooks/handlers_test.go +++ b/cmd/frontend/internal/repos/webhooks/handlers_test.go @@ -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) diff --git a/cmd/repo-updater/shared/BUILD.bazel b/cmd/repo-updater/shared/BUILD.bazel index 2aed43be5b2..6544e13e3f8 100644 --- a/cmd/repo-updater/shared/BUILD.bazel +++ b/cmd/repo-updater/shared/BUILD.bazel @@ -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", diff --git a/cmd/repo-updater/shared/main.go b/cmd/repo-updater/shared/main.go index b3a0c4f8257..bf785b376b8 100644 --- a/cmd/repo-updater/shared/main.go +++ b/cmd/repo-updater/shared/main.go @@ -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{ diff --git a/internal/conf/BUILD.bazel b/internal/conf/BUILD.bazel index 3dc3da493aa..89ebfb77672 100644 --- a/internal/conf/BUILD.bazel +++ b/internal/conf/BUILD.bazel @@ -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", ], ) diff --git a/internal/conf/computed.go b/internal/conf/computed.go index b40aac84040..de53e1f5831 100644 --- a/internal/conf/computed.go +++ b/internal/conf/computed.go @@ -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 } diff --git a/internal/conf/computed_test.go b/internal/conf/computed_test.go index df1ad220b00..4360f46c1d9 100644 --- a/internal/conf/computed_test.go +++ b/internal/conf/computed_test.go @@ -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() + }) +} diff --git a/internal/deviceid/BUILD.bazel b/internal/deviceid/BUILD.bazel index e6e1f615913..3bb904d4b11 100644 --- a/internal/deviceid/BUILD.bazel +++ b/internal/deviceid/BUILD.bazel @@ -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", ], diff --git a/internal/deviceid/middleware.go b/internal/deviceid/middleware.go index 30768f95801..9fa4048096b 100644 --- a/internal/deviceid/middleware.go +++ b/internal/deviceid/middleware.go @@ -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, }) }