diff --git a/cmd/enterprise-portal/BUILD.bazel b/cmd/enterprise-portal/BUILD.bazel new file mode 100644 index 00000000000..7beb6c97c1c --- /dev/null +++ b/cmd/enterprise-portal/BUILD.bazel @@ -0,0 +1,62 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("//dev:oci_defs.bzl", "image_repository", "oci_image", "oci_push", "oci_tarball") +load("@rules_pkg//:pkg.bzl", "pkg_tar") +load("@container_structure_test//:defs.bzl", "container_structure_test") + +go_library( + name = "enterprise-portal_lib", + srcs = ["main.go"], + importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal", + visibility = ["//visibility:private"], + deps = [ + "//cmd/enterprise-portal/service", + "//lib/managedservicesplatform/runtime", + ], +) + +go_binary( + name = "enterprise-portal", + embed = [":enterprise-portal_lib"], + visibility = ["//visibility:public"], +) + +pkg_tar( + name = "tar_enterprise-portal", + srcs = [":enterprise-portal"], +) + +oci_image( + name = "image", + base = "//wolfi-images/sourcegraph-base:base_image", + entrypoint = [ + "/sbin/tini", + "--", + "/enterprise-portal", + ], + tars = [":tar_enterprise-portal"], + user = "sourcegraph", +) + +oci_tarball( + name = "image_tarball", + image = ":image", + repo_tags = ["enterprise-portal: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("enterprise-portal"), +) diff --git a/cmd/enterprise-portal/CODENOTIFY b/cmd/enterprise-portal/CODENOTIFY new file mode 100644 index 00000000000..98a4a599e73 --- /dev/null +++ b/cmd/enterprise-portal/CODENOTIFY @@ -0,0 +1,3 @@ +# See https://github.com/sourcegraph/codenotify for documentation. + +**/* @sourcegraph/core-services diff --git a/cmd/enterprise-portal/README.md b/cmd/enterprise-portal/README.md new file mode 100644 index 00000000000..015051c872f --- /dev/null +++ b/cmd/enterprise-portal/README.md @@ -0,0 +1,3 @@ +# enterprise-portal + +**WIP** - refer to [RFC 885 Sourcegraph Enterprise Portal (go/enterprise-portal)](https://docs.google.com/document/d/1tiaW1IVKm_YSSYhH-z7Q8sv4HSO_YJ_Uu6eYDjX7uU4/edit#heading=h.tdaxc5h34u7q) for more details. diff --git a/cmd/enterprise-portal/image_test.yaml b/cmd/enterprise-portal/image_test.yaml new file mode 100644 index 00000000000..f7ef64ffdfb --- /dev/null +++ b/cmd/enterprise-portal/image_test.yaml @@ -0,0 +1,15 @@ +schemaVersion: "2.0.0" + +commandTests: + - name: "binary is runnable" + command: "/enterprise-portal" + 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/enterprise-portal/internal/dotcomdb/BUILD.bazel b/cmd/enterprise-portal/internal/dotcomdb/BUILD.bazel new file mode 100644 index 00000000000..dc1a7eccc23 --- /dev/null +++ b/cmd/enterprise-portal/internal/dotcomdb/BUILD.bazel @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "dotcomdb", + srcs = ["dotcomdb.go"], + importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/dotcomdb", + visibility = ["//cmd/enterprise-portal:__subpackages__"], + deps = [ + "//lib/errors", + "@com_github_jackc_pgx_v5//:pgx", + ], +) diff --git a/cmd/enterprise-portal/internal/dotcomdb/dotcomdb.go b/cmd/enterprise-portal/internal/dotcomdb/dotcomdb.go new file mode 100644 index 00000000000..7aa53985576 --- /dev/null +++ b/cmd/enterprise-portal/internal/dotcomdb/dotcomdb.go @@ -0,0 +1,32 @@ +package dotcomdb + +import ( + "context" + + "github.com/jackc/pgx/v5" + + "github.com/sourcegraph/sourcegraph/lib/errors" +) + +type Database struct { + conn *pgx.Conn +} + +// NewDatabase wraps a direct connection to the Sourcegraph.com database. It +// ONLY executes read queries, so the connection can (and should) be +// authenticated by a read-only user. +func NewDatabase(conn *pgx.Conn) *Database { + return &Database{conn: conn} +} + +func (d *Database) Ping(ctx context.Context) error { + if err := d.conn.Ping(ctx); err != nil { + return errors.Wrap(err, "sqlDB.PingContext") + } + if _, err := d.conn.Exec(ctx, "SELECT current_user;"); err != nil { + return errors.Wrap(err, "sqlDB.Exec SELECT current_user") + } + return nil +} + +func (d *Database) Close(ctx context.Context) error { return d.conn.Close(ctx) } diff --git a/cmd/enterprise-portal/main.go b/cmd/enterprise-portal/main.go new file mode 100644 index 00000000000..4fc2801e805 --- /dev/null +++ b/cmd/enterprise-portal/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/service" + "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime" +) + +func main() { + runtime.Start(&service.Service{}) +} diff --git a/cmd/enterprise-portal/service/BUILD.bazel b/cmd/enterprise-portal/service/BUILD.bazel new file mode 100644 index 00000000000..a760e54c0df --- /dev/null +++ b/cmd/enterprise-portal/service/BUILD.bazel @@ -0,0 +1,25 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "service", + srcs = [ + "config.go", + "dotcomdb.go", + "service.go", + "state.go", + ], + importpath = "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/service", + visibility = ["//visibility:public"], + deps = [ + "//cmd/enterprise-portal/internal/dotcomdb", + "//internal/httpserver", + "//internal/trace/policy", + "//internal/version", + "//lib/background", + "//lib/errors", + "//lib/managedservicesplatform/cloudsql", + "//lib/managedservicesplatform/runtime", + "@com_github_jackc_pgx_v5//:pgx", + "@com_github_sourcegraph_log//:log", + ], +) diff --git a/cmd/enterprise-portal/service/config.go b/cmd/enterprise-portal/service/config.go new file mode 100644 index 00000000000..45e964d5a1e --- /dev/null +++ b/cmd/enterprise-portal/service/config.go @@ -0,0 +1,26 @@ +package service + +import ( + "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/cloudsql" + "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime" +) + +// Config is the configuration for the Enterprise Portal. +type Config struct { + DotComDB struct { + cloudsql.ConnConfig + + PGDSNOverride *string + } +} + +func (c *Config) Load(env *runtime.Env) { + c.DotComDB.ConnConfig = cloudsql.ConnConfig{ + ConnectionName: env.GetOptional("DOTCOM_CLOUDSQL_CONNECTION_NAME", + "Sourcegraph.com Cloud SQL connection name"), + User: env.GetOptional("DOTCOM_CLOUDSQL_USER", "Sourcegraph.com Cloud SQL user"), + Database: env.Get("DOTCOM_CLOUDSQL_DATABASE", "sourcegraph", "Sourcegraph.com database"), + } + c.DotComDB.PGDSNOverride = env.GetOptional("DOTCOM_PGDSN_OVERRIDE", + "For local dev: custom PostgreSQL DSN, overrides DOTCOM_CLOUDSQL_* options") +} diff --git a/cmd/enterprise-portal/service/dotcomdb.go b/cmd/enterprise-portal/service/dotcomdb.go new file mode 100644 index 00000000000..2cd05239be5 --- /dev/null +++ b/cmd/enterprise-portal/service/dotcomdb.go @@ -0,0 +1,32 @@ +package service + +import ( + "context" + + "github.com/jackc/pgx/v5" + + "github.com/sourcegraph/sourcegraph/cmd/enterprise-portal/internal/dotcomdb" + "github.com/sourcegraph/sourcegraph/lib/errors" + "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/cloudsql" +) + +func newDotComDBConn(ctx context.Context, config Config) (*dotcomdb.Database, error) { + if config.DotComDB.PGDSNOverride != nil { + config, err := pgx.ParseConfig(*config.DotComDB.PGDSNOverride) + if err != nil { + return nil, errors.Wrap(err, "rendered PGDSN is invalid") + } + conn, err := pgx.ConnectConfig(ctx, config) + if err != nil { + return nil, err + } + return dotcomdb.NewDatabase(conn), nil + } + + // Use IAM auth to connect to the Cloud SQL database. + conn, err := cloudsql.Connect(ctx, config.DotComDB.ConnConfig) + if err != nil { + return nil, errors.Wrap(err, "contract.GetPostgreSQLDB") + } + return dotcomdb.NewDatabase(conn), nil +} diff --git a/cmd/enterprise-portal/service/service.go b/cmd/enterprise-portal/service/service.go new file mode 100644 index 00000000000..ff7df7ba50a --- /dev/null +++ b/cmd/enterprise-portal/service/service.go @@ -0,0 +1,55 @@ +package service + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/sourcegraph/log" + + "github.com/sourcegraph/sourcegraph/internal/httpserver" + "github.com/sourcegraph/sourcegraph/internal/trace/policy" + "github.com/sourcegraph/sourcegraph/internal/version" + "github.com/sourcegraph/sourcegraph/lib/background" + "github.com/sourcegraph/sourcegraph/lib/errors" + "github.com/sourcegraph/sourcegraph/lib/managedservicesplatform/runtime" +) + +// Service is the implementation of the Enterprise Portal service. +type Service struct{} + +var _ runtime.Service[Config] = (*Service)(nil) + +func (Service) Name() string { return "enterprise-portal" } +func (Service) Version() string { return version.Version() } + +func (Service) Initialize(ctx context.Context, logger log.Logger, contract runtime.Contract, config Config) (background.Routine, error) { + // We use Sourcegraph tracing code, so explicitly configure a trace policy + policy.SetTracePolicy(policy.TraceAll) + + dotcomDB, err := newDotComDBConn(ctx, config) + if err != nil { + return nil, errors.Wrap(err, "newDotComDBConn") + } + defer dotcomDB.Close(context.Background()) + + // Simple test for now, move elsewhere later + if err := dotcomDB.Ping(context.Background()); err != nil { + return nil, errors.Wrap(err, "dotcomDB.Ping") + } + + httpServer := http.NewServeMux() + contract.Diagnostics.RegisterDiagnosticsHandlers(httpServer, serviceState{}) + + listenAddr := fmt.Sprintf(":%d", contract.Port) + server := httpserver.NewFromAddr( + listenAddr, + &http.Server{ + ReadTimeout: 2 * time.Minute, + WriteTimeout: 2 * time.Minute, + Handler: httpServer, + }, + ) + return server, nil +} diff --git a/cmd/enterprise-portal/service/state.go b/cmd/enterprise-portal/service/state.go new file mode 100644 index 00000000000..60d32ce9f4d --- /dev/null +++ b/cmd/enterprise-portal/service/state.go @@ -0,0 +1,10 @@ +package service + +import ( + "context" + "net/url" +) + +type serviceState struct{} + +func (s serviceState) Healthy(_ context.Context, _ url.Values) error { return nil } diff --git a/go.mod b/go.mod index fd7253e2222..72e8c453dd0 100644 --- a/go.mod +++ b/go.mod @@ -271,6 +271,7 @@ require ( github.com/hashicorp/terraform-cdk-go/cdktf v0.17.3 github.com/invopop/jsonschema v0.12.0 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa + github.com/jackc/pgx/v5 v5.5.4 github.com/jomei/notionapi v1.13.0 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 github.com/mitchellh/hashstructure/v2 v2.0.2 @@ -379,7 +380,6 @@ require ( github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgx/v5 v5.5.4 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/knadh/koanf/v2 v2.0.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect