diff --git a/cmd/pings/BUILD.bazel b/cmd/pings/BUILD.bazel new file mode 100644 index 00000000000..fd9607091a3 --- /dev/null +++ b/cmd/pings/BUILD.bazel @@ -0,0 +1,72 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("@rules_oci//oci:defs.bzl", "oci_image", "oci_push", "oci_tarball") +load("@rules_pkg//:pkg.bzl", "pkg_tar") +load("@container_structure_test//:defs.bzl", "container_structure_test") +load("//dev:oci_defs.bzl", "image_repository") + +go_library( + name = "pings_lib", + srcs = ["main.go"], + importpath = "github.com/sourcegraph/sourcegraph/cmd/pings", + visibility = ["//visibility:private"], + deps = [ + "//cmd/pings/shared", + "//internal/conf", + "//internal/env", + "//internal/sanitycheck", + "//internal/service/svcmain", + "@com_github_getsentry_sentry_go//:sentry-go", + "@com_github_sourcegraph_log//:log", + ], +) + +go_binary( + name = "pings", + embed = [":pings_lib"], + visibility = ["//visibility:public"], + x_defs = { + "github.com/sourcegraph/sourcegraph/internal/version.version": "{STABLE_VERSION}", + "github.com/sourcegraph/sourcegraph/internal/version.timestamp": "{VERSION_TIMESTAMP}", + }, +) + +pkg_tar( + name = "tar_pings", + srcs = [":pings"], +) + +oci_image( + name = "image", + base = "@wolfi_base", + entrypoint = [ + "/sbin/tini", + "--", + "/pings", + ], + tars = [":tar_pings"], + user = "sourcegraph", +) + +oci_tarball( + name = "image_tarball", + image = ":image", + repo_tags = ["pings:candidate"], +) + +container_structure_test( + name = "image_test", + timeout = "short", + configs = ["image_test.yaml"], + driver = "docker", + image = ":image", + tags = [ + "exclusive", + "requires-network", + ], +) + +oci_push( + name = "candidate_push", + image = ":image", + repository = image_repository("pings"), +) diff --git a/cmd/pings/image_test.yaml b/cmd/pings/image_test.yaml new file mode 100644 index 00000000000..e64672e7e56 --- /dev/null +++ b/cmd/pings/image_test.yaml @@ -0,0 +1,15 @@ +schemaVersion: "2.0.0" + +commandTests: + - name: "binary is runnable" + command: "/pings" + envVars: + - key: "SANITY_CHECK" + value: "true" + + - name: "not running as root" + command: "/usr/bin/id" + args: + - -u + excludedOutput: ["^0"] + exitCode: 0 diff --git a/cmd/pings/main.go b/cmd/pings/main.go new file mode 100644 index 00000000000..2ce89ec2c65 --- /dev/null +++ b/cmd/pings/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/getsentry/sentry-go" + "github.com/sourcegraph/log" + + "github.com/sourcegraph/sourcegraph/cmd/pings/shared" + "github.com/sourcegraph/sourcegraph/internal/conf" + "github.com/sourcegraph/sourcegraph/internal/env" + "github.com/sourcegraph/sourcegraph/internal/sanitycheck" + "github.com/sourcegraph/sourcegraph/internal/service/svcmain" +) + +var sentryDSN = env.Get("PINGS_SENTRY_DSN", "", "Sentry DSN") + +func main() { + sanitycheck.Pass() + svcmain.SingleServiceMainWithoutConf(shared.Service, svcmain.Config{}, svcmain.OutOfBandConfiguration{ + Logging: func() conf.LogSinksSource { + if sentryDSN == "" { + return nil + } + + return conf.NewStaticLogsSinksSource(log.SinksConfig{ + Sentry: &log.SentrySink{ + ClientOptions: sentry.ClientOptions{ + Dsn: sentryDSN, + }, + }, + }) + }(), + Tracing: nil, + }) +} diff --git a/cmd/pings/shared/BUILD.bazel b/cmd/pings/shared/BUILD.bazel new file mode 100644 index 00000000000..4648ddaad3c --- /dev/null +++ b/cmd/pings/shared/BUILD.bazel @@ -0,0 +1,26 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "shared", + srcs = [ + "config.go", + "main.go", + "service.go", + ], + importpath = "github.com/sourcegraph/sourcegraph/cmd/pings/shared", + visibility = ["//visibility:public"], + deps = [ + "//internal/debugserver", + "//internal/env", + "//internal/goroutine", + "//internal/httpserver", + "//internal/observation", + "//internal/pubsub", + "//internal/service", + "//internal/updatecheck", + "//internal/version", + "//lib/errors", + "@com_github_gorilla_mux//:mux", + "@com_github_sourcegraph_log//:log", + ], +) diff --git a/cmd/pings/shared/config.go b/cmd/pings/shared/config.go new file mode 100644 index 00000000000..f6a86c5157b --- /dev/null +++ b/cmd/pings/shared/config.go @@ -0,0 +1,22 @@ +package shared + +import ( + "github.com/sourcegraph/sourcegraph/internal/env" +) + +type Config struct { + env.BaseConfig + + Address string + + PubSub struct { + ProjectID string + TopicID string + } +} + +func (c *Config) Load() { + c.Address = c.Get("PINGS_ADDR", ":10086", "Address to serve Ping service on.") + c.PubSub.ProjectID = c.Get("PINGS_PUBSUB_PROJECT_ID", "", "The project ID for the Pub/Sub.") + c.PubSub.TopicID = c.Get("PINGS_PUBSUB_TOPIC_ID", "", "The topic ID for the Pub/Sub.") +} diff --git a/cmd/pings/shared/main.go b/cmd/pings/shared/main.go new file mode 100644 index 00000000000..232a3f4d3ba --- /dev/null +++ b/cmd/pings/shared/main.go @@ -0,0 +1,77 @@ +package shared + +import ( + "context" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/sourcegraph/log" + + "github.com/sourcegraph/sourcegraph/internal/goroutine" + "github.com/sourcegraph/sourcegraph/internal/httpserver" + "github.com/sourcegraph/sourcegraph/internal/observation" + "github.com/sourcegraph/sourcegraph/internal/pubsub" + "github.com/sourcegraph/sourcegraph/internal/service" + "github.com/sourcegraph/sourcegraph/internal/updatecheck" + "github.com/sourcegraph/sourcegraph/internal/version" + "github.com/sourcegraph/sourcegraph/lib/errors" +) + +func Main(ctx context.Context, obctx *observation.Context, ready service.ReadyFunc, config *Config) error { + // Initialize our server + serverHandler, err := newServerHandler(obctx.Logger, config) + if err != nil { + return errors.Errorf("create server handler: %v", err) + } + server := httpserver.NewFromAddr( + config.Address, + &http.Server{ + ReadTimeout: 75 * time.Second, + WriteTimeout: 10 * time.Minute, + Handler: serverHandler, + }, + ) + + // Mark health server as ready and go! + ready() + obctx.Logger.Info("service ready", log.String("address", config.Address)) + + // Block until done + goroutine.MonitorBackgroundRoutines(ctx, server) + return nil +} + +func newServerHandler(logger log.Logger, config *Config) (http.Handler, error) { + r := mux.NewRouter() + + r.Path("/-/version").Methods(http.MethodGet).HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(version.Version())) + }) + + pubsubClient, err := pubsub.NewTopicClient(config.PubSub.ProjectID, config.PubSub.TopicID) + if err != nil { + return nil, errors.Errorf("create Pub/Sub client: %v", err) + } + r.Path("/-/healthz").Methods(http.MethodGet).HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + var errs error + if err = pubsubClient.Ping(context.Background()); err != nil { + errs = errors.Append(errs, errors.Errorf("Pub/Sub client: %v", err)) + } + if errs != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(errs.Error())) + return + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(http.StatusText(http.StatusOK))) + }) + r.Path("/updates"). + Methods(http.MethodGet, http.MethodPost). + HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + updatecheck.HandlePingRequest(logger, pubsubClient, w, r) + }) + return r, nil +} diff --git a/cmd/pings/shared/service.go b/cmd/pings/shared/service.go new file mode 100644 index 00000000000..04f955dfaca --- /dev/null +++ b/cmd/pings/shared/service.go @@ -0,0 +1,27 @@ +package shared + +import ( + "context" + + "github.com/sourcegraph/sourcegraph/internal/debugserver" + "github.com/sourcegraph/sourcegraph/internal/env" + "github.com/sourcegraph/sourcegraph/internal/observation" + "github.com/sourcegraph/sourcegraph/internal/service" +) + +// Service is the shared ping service. +var Service service.Service = svc{} + +type svc struct{} + +func (svc) Name() string { return "pings" } + +func (svc) Configure() (env.Config, []debugserver.Endpoint) { + c := &Config{} + c.Load() + return c, []debugserver.Endpoint{} +} + +func (svc) Start(ctx context.Context, observationCtx *observation.Context, ready service.ReadyFunc, config env.Config) error { + return Main(ctx, observationCtx, ready, config.(*Config)) +} diff --git a/dev/check/go-dbconn-import.sh b/dev/check/go-dbconn-import.sh index 8375f539067..5d1559ca93b 100755 --- a/dev/check/go-dbconn-import.sh +++ b/dev/check/go-dbconn-import.sh @@ -27,6 +27,8 @@ allowed_prefix=( github.com/sourcegraph/sourcegraph/cmd/symbols # Transitively depends on zoekt package which imports but does not use DB github.com/sourcegraph/sourcegraph/cmd/searcher + # Transitively depends on updatecheck package which imports but does not use DB + github.com/sourcegraph/sourcegraph/cmd/pings # Main entrypoints for running all services, so they must be allowed to import it. github.com/sourcegraph/sourcegraph/cmd/sourcegraph-oss github.com/sourcegraph/sourcegraph/enterprise/cmd/sourcegraph diff --git a/doc/dev/background-information/sg/reference.md b/doc/dev/background-information/sg/reference.md index e2f44111370..43103b055b3 100644 --- a/doc/dev/background-information/sg/reference.md +++ b/doc/dev/background-information/sg/reference.md @@ -123,6 +123,7 @@ Available commands in `sg.config.yaml`: * monitoring-generator * multiqueue-executor * otel-collector: OpenTelemetry collector +* pings * postgres_exporter * prometheus * qdrant diff --git a/internal/updatecheck/BUILD.bazel b/internal/updatecheck/BUILD.bazel index c59341f2d85..6df38f54da0 100644 --- a/internal/updatecheck/BUILD.bazel +++ b/internal/updatecheck/BUILD.bazel @@ -10,7 +10,7 @@ go_library( "handler.go", ], importpath = "github.com/sourcegraph/sourcegraph/internal/updatecheck", - visibility = ["//cmd/frontend:__subpackages__"], + visibility = ["//:__subpackages__"], deps = [ "//cmd/frontend/envvar", "//cmd/frontend/globals", diff --git a/internal/updatecheck/handler.go b/internal/updatecheck/handler.go index 8cc77b58b0d..e5e772a85de 100644 --- a/internal/updatecheck/handler.go +++ b/internal/updatecheck/handler.go @@ -84,13 +84,13 @@ func HandlerWithLog(logger log.Logger) (http.HandlerFunc, error) { } } return func(w http.ResponseWriter, r *http.Request) { - handler(logger, pubsubClient, w, r) + HandlePingRequest(logger, pubsubClient, w, r) }, nil } -// handler is an HTTP handler that responds with information about software updates -// for Sourcegraph. -func handler(logger log.Logger, pubsubClient pubsub.TopicClient, w http.ResponseWriter, r *http.Request) { +// HandlePingRequest handles the ping requests and responds with information +// about software updates for Sourcegraph. +func HandlePingRequest(logger log.Logger, pubsubClient pubsub.TopicClient, w http.ResponseWriter, r *http.Request) { requestCounter.Inc() pr, err := readPingRequest(r) diff --git a/sg.config.yaml b/sg.config.yaml index 0965b35f3dc..a5fbc04310b 100644 --- a/sg.config.yaml +++ b/sg.config.yaml @@ -316,6 +316,23 @@ commands: - internal - cmd/cody-gateway + pings: + cmd: | + .bin/pings + install: | + if [ -n "$DELVE" ]; then + export GCFLAGS='-N -l' + fi + + go build -gcflags="$GCFLAGS" -o .bin/pings github.com/sourcegraph/sourcegraph/cmd/pings + checkBinary: .bin/pings + env: + SRC_LOG_LEVEL: info + watch: + - lib + - internal + - cmd/pings + searcher: cmd: .bin/searcher install: |