This commit is contained in:
Dax McDonald 2024-08-14 19:34:08 +00:00
commit 652ec406d6
5 changed files with 56 additions and 11 deletions

View File

@ -256,6 +256,7 @@ go_library(
"//internal/actor",
"//internal/adminanalytics",
"//internal/api",
"//internal/audit",
"//internal/auth",
"//internal/authz",
"//internal/authz/permssync",
@ -283,6 +284,7 @@ go_library(
"//internal/embeddings/background/repo",
"//internal/encryption",
"//internal/encryption/keyring",
"//internal/endpoint",
"//internal/env",
"//internal/errcode",
"//internal/executor",
@ -383,6 +385,8 @@ go_library(
"@com_github_sourcegraph_zoekt//query",
"@com_github_stretchr_testify//require",
"@com_github_throttled_throttled_v2//:throttled",
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
"@io_k8s_apimachinery//pkg/types",
"@io_opentelemetry_go_otel//:otel",
"@io_opentelemetry_go_otel//attribute",
"@org_golang_x_text//unicode/norm",

View File

@ -175,7 +175,7 @@ func (r *siteResolver) SettingsURL() *string { return strptr("/site-admin/global
func (r *siteResolver) CanReloadSite(ctx context.Context) bool {
err := auth.CheckCurrentUserIsSiteAdmin(ctx, r.db)
return canReloadSite && err == nil
return isGoremanSite && err == nil
}
func (r *siteResolver) BuildVersion() string { return version.Version() }

View File

@ -2,29 +2,70 @@ package graphqlbackend
import (
"context"
"fmt"
"time"
"github.com/inconshreveable/log15" //nolint:logging // TODO move all logging to sourcegraph/log
"github.com/sourcegraph/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"github.com/sourcegraph/sourcegraph/cmd/frontend/internal/processrestart"
"github.com/sourcegraph/sourcegraph/internal/actor"
"github.com/sourcegraph/sourcegraph/internal/audit"
"github.com/sourcegraph/sourcegraph/internal/auth"
"github.com/sourcegraph/sourcegraph/internal/cloud"
"github.com/sourcegraph/sourcegraph/internal/endpoint"
"github.com/sourcegraph/sourcegraph/internal/env"
"github.com/sourcegraph/sourcegraph/lib/errors"
)
// canReloadSite is whether the current site can be reloaded via the API. Currently
// only goreman-managed sites can be reloaded. Callers must also check if the actor
// is an admin before actually reloading the site.
var canReloadSite = processrestart.CanRestart()
var isGoremanSite = processrestart.CanRestart()
// SRC_ENABLE_FRONTEND_SITE_RELOAD is env
var enableFrontendSiteReload = env.MustGetBool("SRC_ENABLE_FRONTEND_SITE_RELOAD", true, "Enables the /reload-site endpoint for reloading the site")
func (r *schemaResolver) ReloadSite(ctx context.Context) (*EmptyResponse, error) {
// 🚨 SECURITY: Reloading the site is an interruptive action, so only admins
// 🚨 SECURITY: Reloading the site is an disruptive action, so only admins
// may do it.
if err := auth.CheckCurrentUserIsSiteAdmin(ctx, r.db); err != nil {
return nil, err
}
if !canReloadSite {
if cloud.SiteConfig().SourcegraphOperatorAuthProviderEnabled() && enableFrontendSiteReload {
// use k8s client to restart the frontend deployment rollout
client, err := endpoint.LoadClient()
if err != nil {
return nil, err
}
ns := endpoint.Namespace(r.logger)
data := fmt.Sprintf(`{"spec": {"template": {"metadata": {"annotations": {"kubectl.kubernetes.io/restartedAt": "%s", "kubernetes.io/change-cause: "frontend restart requested"}}}}}`, time.Now().Format("20060102150405"))
deploymentClient := client.AppsV1().Deployments(ns)
currentUser, err := CurrentUser(ctx, r.db)
if err != nil {
return nil, err
}
audit.Log(ctx, r.logger, audit.Record{
Entity: "GraphQL",
Action: "frontend-reload",
Fields: []log.Field{
log.String("currentUser", currentUser.user.Username),
},
})
r.logger.Info("Restarting k8s deployment")
_, err = deploymentClient.Patch(ctx, "sourcegraph-frontend",
k8stypes.StrategicMergePatchType, []byte(data), metav1.PatchOptions{})
if err != nil {
return nil, err
}
return &EmptyResponse{}, nil
}
if !isGoremanSite {
return nil, errors.New("reloading site is not supported")
}

View File

@ -49,7 +49,7 @@ type endpoints struct {
// New creates a new Map for the URL specifier.
//
// If the scheme is prefixed with "k8s+", one URL is expected and the format is
// expected to match e.g. k8s+http://service.namespace:port/path. namespace,
// expected to match e.g. k8s+http://service.namespace:port/path. Namespace,
// port and path are optional. URLs of this form will consistently hash among
// the endpoints for the Kubernetes service. The values returned by Get will
// look like http://endpoint:port/path.

View File

@ -27,7 +27,7 @@ func K8S(logger log.Logger, urlspec string) *Map {
logger = logger.Scoped("k8s")
return &Map{
urlspec: urlspec,
discofunk: k8sDiscovery(logger, urlspec, namespace(logger), loadClient),
discofunk: k8sDiscovery(logger, urlspec, Namespace(logger), LoadClient),
}
}
@ -189,10 +189,10 @@ func parseURL(rawurl string) (*k8sURL, error) {
}, nil
}
// namespace returns the namespace the pod is currently running in
// this is done because the k8s client we previously used set the namespace
// Namespace returns the Namespace the pod is currently running in
// this is done because the k8s client we previously used set the Namespace
// when the client was created, the official k8s client does not
func namespace(logger log.Logger) string {
func Namespace(logger log.Logger) string {
logger = logger.Scoped("namespace")
const filename = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
data, err := os.ReadFile(filename)
@ -209,7 +209,7 @@ func namespace(logger log.Logger) string {
return ns
}
func loadClient() (client *kubernetes.Clientset, err error) {
func LoadClient() (client *kubernetes.Clientset, err error) {
// Uncomment below to test against a real cluster. This is only important
// when you are changing how we interact with the k8s API and you want to
// test against the real thing.
@ -226,7 +226,7 @@ func loadClient() (client *kubernetes.Clientset, err error) {
}
clientConfig := clientcmd.NewDefaultClientConfig(*c, nil)
config, err = clientConfig.ClientConfig()
namespace = "prod"
Namespace = "prod"
*/
config, err := rest.InClusterConfig()