feat: backend service

This commit is contained in:
Ilia Denisov
2026-05-06 10:14:55 +03:00
committed by GitHub
parent 3e2622757e
commit f446c6a2ac
1486 changed files with 49720 additions and 266401 deletions
+181
View File
@@ -0,0 +1,181 @@
package testenv
import (
"context"
"fmt"
"path/filepath"
"testing"
"time"
"github.com/google/uuid"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/mount"
"github.com/testcontainers/testcontainers-go"
tcnetwork "github.com/testcontainers/testcontainers-go/network"
"github.com/testcontainers/testcontainers-go/wait"
)
// BackendContainer wraps a running galaxy/backend:integration
// container reachable from the host (HTTPHost, GRPCPushHost) and
// from the shared Docker network at the alias "backend".
type BackendContainer struct {
Container testcontainers.Container
HTTPHost string
HTTPPort int
HTTPURL string
GRPCHost string
GRPCPort int
GRPCURL string
// AdminUser/AdminPassword are the bootstrap admin credentials this
// container started with. Tests that exercise the admin surface
// reuse them directly.
AdminUser string
AdminPassword string
}
// BackendOptions tunes a backend container before it boots.
type BackendOptions struct {
NetworkAlias string
NetworkName string
PostgresDSN string
MailpitHost string
MailpitPort int
GeoIPHostPath string
AdminEmail string
Extra map[string]string
}
// StartBackend boots galaxy/backend:integration with the supplied
// options.
func StartBackend(t *testing.T, opts BackendOptions) *BackendContainer {
t.Helper()
EnsureBackendImage(t)
if opts.NetworkAlias == "" {
opts.NetworkAlias = "backend"
}
if opts.AdminEmail == "" {
opts.AdminEmail = "admin@galaxy.test"
}
geoIPInContainer := "/var/lib/galaxy/geoip.mmdb"
// Use a unique daemon-side path for each test so concurrent
// runs cannot collide. Docker creates the source directory at
// container start because BindOptions.CreateMountpoint=true.
stateRoot := "/tmp/galaxy-state-" + uuid.NewString()
env := map[string]string{
"BACKEND_HTTP_LISTEN_ADDR": ":8080",
"BACKEND_GRPC_PUSH_LISTEN_ADDR": ":8081",
"BACKEND_LOGGING_LEVEL": "info",
"BACKEND_POSTGRES_DSN": opts.PostgresDSN,
"BACKEND_SMTP_HOST": opts.MailpitHost,
"BACKEND_SMTP_PORT": fmt.Sprintf("%d", opts.MailpitPort),
"BACKEND_SMTP_FROM": "galaxy-backend@galaxy.test",
"BACKEND_SMTP_TLS_MODE": "none",
"BACKEND_DOCKER_NETWORK": opts.NetworkName,
"BACKEND_GAME_STATE_ROOT": stateRoot,
"BACKEND_ADMIN_BOOTSTRAP_USER": "bootstrap",
"BACKEND_ADMIN_BOOTSTRAP_PASSWORD": "bootstrap-secret",
"BACKEND_GEOIP_DB_PATH": geoIPInContainer,
"BACKEND_OTEL_TRACES_EXPORTER": "none",
"BACKEND_OTEL_METRICS_EXPORTER": "none",
"BACKEND_NOTIFICATION_ADMIN_EMAIL": opts.AdminEmail,
"BACKEND_AUTH_CHALLENGE_THROTTLE_MAX": "100",
"BACKEND_MAIL_WORKER_INTERVAL": "500ms",
"BACKEND_NOTIFICATION_WORKER_INTERVAL": "500ms",
}
for k, v := range opts.Extra {
env[k] = v
}
dockerSocket := DockerSocketPath()
req := testcontainers.ContainerRequest{
Image: BackendImage,
ExposedPorts: []string{"8080/tcp", "8081/tcp"},
Env: env,
WaitingFor: wait.ForHTTP("/healthz").
WithPort("8080/tcp").
WithStartupTimeout(60 * time.Second),
Files: []testcontainers.ContainerFile{
{
HostFilePath: opts.GeoIPHostPath,
ContainerFilePath: geoIPInContainer,
FileMode: 0o644,
},
},
HostConfigModifier: func(hc *container.HostConfig) {
hc.Binds = append(hc.Binds, dockerSocket+":/var/run/docker.sock")
// Bind a unique daemon-side directory at the same path
// inside the backend container. CreateMountpoint=true
// asks the daemon to create the source directory if it
// is missing, so we do not need a second container just
// to mkdir on the daemon host. Per-game subdirectories
// are created by backend's runtime via os.MkdirAll
// before each engine container start.
hc.Mounts = append(hc.Mounts, mount.Mount{
Type: mount.TypeBind,
Source: stateRoot,
Target: stateRoot,
BindOptions: &mount.BindOptions{
CreateMountpoint: true,
},
})
},
// The distroless `nonroot` user (uid 65532) cannot reach the
// Docker daemon socket that backend mounts to manage engine
// containers. In integration tests we run as root so the
// dockerclient.EnsureNetwork startup probe succeeds; the
// production deployment will rely on a docker-socket-proxy
// sidecar (see ARCHITECTURE.md §13).
User: "0:0",
}
gcr := &testcontainers.GenericContainerRequest{ContainerRequest: req}
if opts.NetworkName != "" {
_ = tcnetwork.WithNetwork([]string{opts.NetworkAlias}, &testcontainers.DockerNetwork{Name: opts.NetworkName}).Customize(gcr)
}
gcr.Started = true
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
container, err := testcontainers.GenericContainer(ctx, *gcr)
if err != nil {
t.Fatalf("start backend container: %v", err)
}
t.Cleanup(func() {
if err := testcontainers.TerminateContainer(container); err != nil {
t.Logf("terminate backend: %v", err)
}
})
host, err := container.Host(ctx)
if err != nil {
t.Fatalf("backend host: %v", err)
}
httpPort, err := container.MappedPort(ctx, "8080/tcp")
if err != nil {
t.Fatalf("backend http port: %v", err)
}
grpcPort, err := container.MappedPort(ctx, "8081/tcp")
if err != nil {
t.Fatalf("backend grpc port: %v", err)
}
return &BackendContainer{
Container: container,
HTTPHost: host,
HTTPPort: int(httpPort.Num()),
HTTPURL: fmt.Sprintf("http://%s:%d", host, httpPort.Num()),
GRPCHost: host,
GRPCPort: int(grpcPort.Num()),
GRPCURL: fmt.Sprintf("%s:%d", host, grpcPort.Num()),
AdminUser: env["BACKEND_ADMIN_BOOTSTRAP_USER"],
AdminPassword: env["BACKEND_ADMIN_BOOTSTRAP_PASSWORD"],
}
}
// _ keeps filepath imported even when only the network helper grows
// here later.
var _ = filepath.Separator