Compare commits

...

5 Commits

15 changed files with 186 additions and 114 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SwaggerSettings">
<option name="defaultPreviewType" value="SWAGGER_UI" />
</component>
</project>

View File

@ -2,10 +2,12 @@ GO := go
PKG_BASE := $(shell head -n 1 go.mod | awk '{print $$2}') PKG_BASE := $(shell head -n 1 go.mod | awk '{print $$2}')
BUILD_TIME := $(shell date -u '+%Y%m%d-%I%M%S') BUILD_TIME := $(shell date -u '+%Y%m%d-%I%M%S')
VERSION := $(shell cat VERSION)+$(shell git rev-parse --short HEAD) VERSION := $(shell cat VERSION)
GIT_COMMIT := $(shell git rev-parse HEAD)
LDFLAGS += -X $(PKG_BASE)/cmd.BuildTime=$(BUILD_TIME) LDFLAGS += -X $(PKG_BASE)/cmd.BuildTime=$(BUILD_TIME)
LDFLAGS += -X $(PKG_BASE)/cmd.Version=$(VERSION) LDFLAGS += -X $(PKG_BASE)/cmd.Version=$(VERSION)
LDFLAGS += -X $(PKG_BASE)/cmd.GitCommit=$(GIT_COMMIT)
LDFLAGS += -X $(PKG_BASE)/cmd.SentryDSN=$(shell cat dsn.txt) LDFLAGS += -X $(PKG_BASE)/cmd.SentryDSN=$(shell cat dsn.txt)
LDFLAGS += -s -w LDFLAGS += -s -w

View File

@ -39,20 +39,26 @@ var App = &cli.App{
var ( var (
BuildTime string BuildTime string
Version string Version string
GitCommit string
SentryDSN string SentryDSN string
) )
func init() { func init() {
if BuildTime == "" { if BuildTime == "" {
BuildTime = "2022-09-06-01-00-00" // First Commit
BuildTime = "20220907-153437"
} }
App.Compiled = getBuildTime() App.Compiled = getBuildTime()
if Version == "" { if Version == "" {
Version = "0.0.0+None" Version = "0.0.0"
} }
App.Version = Version App.Version = Version
if GitCommit == "" {
GitCommit = "out-of-tree"
}
if SentryDSN != "" { if SentryDSN != "" {
setupSentry() setupSentry()
} }
@ -73,7 +79,7 @@ func setupSentry() {
EnableTracing: true, EnableTracing: true,
TracesSampleRate: 1.0, TracesSampleRate: 1.0,
SendDefaultPII: true, SendDefaultPII: true,
Release: Version, Release: GitCommit,
}) })
if err != nil { if err != nil {
log.Fatalf("sentry.Init: %s", err) log.Fatalf("sentry.Init: %s", err)

View File

@ -37,10 +37,10 @@ func main() {
Action: wrap(appServer.RunServer), Action: wrap(appServer.RunServer),
}, },
{ {
Name: "init", Name: "migrate",
Aliases: []string{"i"}, Aliases: []string{"m"},
Usage: "init database", Usage: "migrate database",
Action: wrap(appServer.RunServerInit), Action: wrap(appServer.RunServerMigrate),
}, },
{ {
Name: "runner", Name: "runner",
@ -94,10 +94,13 @@ func prepareServices(c *cli.Context) *do.Injector {
func wrap(f func(i *do.Injector) error) func(*cli.Context) error { func wrap(f func(i *do.Injector) error) func(*cli.Context) error {
return func(c *cli.Context) error { return func(c *cli.Context) error {
defer func() { defer func() {
if r := recover(); r != nil { if cmd.SentryDSN != "" {
sentry.CaptureException(r.(error)) // only recover when sentry is enabled
sentry.Flush(time.Second * 2) if r := recover(); r != nil {
slog.Printf("Panic Captured: %v", r) sentry.CaptureException(r.(error))
sentry.Flush(time.Second * 2)
slog.Printf("Panic Captured: %v", r)
}
} }
}() }()

View File

@ -23,7 +23,7 @@ import (
"time" "time"
) )
func RunServerInit(i *do.Injector) error { func RunServerMigrate(i *do.Injector) error {
slog := do.MustInvoke[log.Service](i).GetLogger("app.server") slog := do.MustInvoke[log.Service](i).GetLogger("app.server")
// Migrate and shutdown database // Migrate and shutdown database

View File

@ -1,6 +1,7 @@
package log package log
import ( import (
"git.0x7f.app/WOJ/woj-server/cmd"
"git.0x7f.app/WOJ/woj-server/internal/misc/config" "git.0x7f.app/WOJ/woj-server/internal/misc/config"
"git.0x7f.app/WOJ/woj-server/pkg/utils" "git.0x7f.app/WOJ/woj-server/pkg/utils"
"github.com/TheZeroSlave/zapsentry" "github.com/TheZeroSlave/zapsentry"
@ -48,7 +49,9 @@ func NewService(i *do.Injector) (Service, error) {
return nil, err return nil, err
} }
srv.logger = attachSentry(srv.logger) if cmd.SentryDSN != "" {
srv.logger = attachSentry(srv.logger)
}
return srv, nil return srv, nil
} }

View File

@ -7,11 +7,13 @@ import (
"git.0x7f.app/WOJ/woj-server/internal/misc/config" "git.0x7f.app/WOJ/woj-server/internal/misc/config"
"git.0x7f.app/WOJ/woj-server/internal/misc/log" "git.0x7f.app/WOJ/woj-server/internal/misc/log"
"git.0x7f.app/WOJ/woj-server/internal/model" "git.0x7f.app/WOJ/woj-server/internal/model"
"git.0x7f.app/WOJ/woj-server/pkg/utils"
"github.com/samber/do" "github.com/samber/do"
"go.uber.org/zap" "go.uber.org/zap"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/schema" "gorm.io/gorm/schema"
"hash/fnv"
"moul.io/zapgorm2" "moul.io/zapgorm2"
"time" "time"
) )
@ -108,11 +110,34 @@ func (s *service) setup(conf *model.Config) {
func (s *service) migrateDatabase() { func (s *service) migrateDatabase() {
s.log.Info("Auto Migrating database...") s.log.Info("Auto Migrating database...")
// Running AutoMigrate concurrently on the same model fails with various race conditions
// https://github.com/go-gorm/gorm/pull/6680
// https://github.com/go-gorm/postgres/pull/224
// Obtain a lock to prevent concurrent AutoMigrate
lockID := func(s string) int64 {
h := fnv.New64a()
_, err := h.Write([]byte(s))
return utils.If(err != nil, int64(0x4242AA55), int64(h.Sum64()))
}("gorm:migrator")
s.err = s.db.Exec("SELECT pg_advisory_lock(?)", lockID).Error
if s.err != nil {
s.log.Error("Failed to obtain lock", zap.Error(s.err))
return
}
_ = s.db.AutoMigrate(&model.User{}) _ = s.db.AutoMigrate(&model.User{})
_ = s.db.AutoMigrate(&model.Problem{}) _ = s.db.AutoMigrate(&model.Problem{})
_ = s.db.AutoMigrate(&model.ProblemVersion{}) _ = s.db.AutoMigrate(&model.ProblemVersion{})
_ = s.db.AutoMigrate(&model.Submission{}) _ = s.db.AutoMigrate(&model.Submission{})
_ = s.db.AutoMigrate(&model.Status{}) _ = s.db.AutoMigrate(&model.Status{})
s.err = s.db.Exec("SELECT pg_advisory_unlock(?)", lockID).Error
if s.err != nil {
s.log.Error("Failed to release lock", zap.Error(s.err))
}
} }
func (s *service) checkAlive(retry int) (*sql.DB, error) { func (s *service) checkAlive(retry int) (*sql.DB, error) {

View File

@ -10,7 +10,7 @@ import (
) )
func (s *service) EnsureDeps(force bool) e.Status { func (s *service) EnsureDeps(force bool) e.Status {
mark := filepath.Join(Prefix, ".mark.container") mark := filepath.Join(Prefix, ".mark.image")
if force { if force {
_ = os.Remove(mark) _ = os.Remove(mark)

View File

@ -79,7 +79,7 @@ func (s *service) initRouters(conf *model.Config, injector *do.Injector) *gin.En
r.Use(cors.New(cors.Config{ r.Use(cors.New(cors.Config{
AllowAllOrigins: true, AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "OPTIONS"}, AllowMethods: []string{"GET", "POST", "PUT", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"}, AllowHeaders: []string{"Authorization", "Origin", "Content-Length", "Content-Type"},
AllowCredentials: true, AllowCredentials: true,
})) }))

View File

@ -1,16 +1,5 @@
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: cache-config
labels:
app: cache
data:
redis.conf: |
requirepass YeT_An0tHeR_VeRy-S3cUr3^PaSsWoRd
---
apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
namespace: woj namespace: woj
@ -45,22 +34,23 @@ spec:
spec: spec:
containers: containers:
- name: cache - name: cache
image: docker.io/library/redis:7-alpine image: docker.io/bitnami/redis:7.2
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 6379 - containerPort: 6379
env:
- name: REDIS_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: REDIS_PASSWORD
volumeMounts: volumeMounts:
- name: cache-vol - name: cache-vol
mountPath: /data mountPath: /data
- name: cache-config
mountPath: /etc/redis/
volumes: volumes:
- name: cache-vol - name: cache-vol
persistentVolumeClaim: persistentVolumeClaim:
claimName: cache-pvc claimName: cache-pvc
- name: cache-config
configMap:
name: cache-config
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: shared-config
data:
POSTGRES_USER: "woj"
POSTGRES_PASSWORD: "A_VeRy-S3cUr3^PaSsWoRd"
POSTGRES_DB: "woj"
MINIO_ROOT_USER: "A_VeRy_CoMpLeX_AcCeSs_KeY"
MINIO_ROOT_PASSWORD: "A_VeRy_CoMpLeX_ScReT_KeY"
REDIS_PASSWORD: "YeT_An0tHeR_VeRy-S3cUr3^PaSsWoRd"

View File

@ -1,17 +1,5 @@
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: db-config
labels:
app: db
data:
POSTGRES_USER: "woj"
POSTGRES_PASSWORD: "A_VeRy-S3cUr3^PaSsWoRd"
POSTGRES_DB: "woj"
---
apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
namespace: woj namespace: woj
@ -50,9 +38,22 @@ spec:
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 5432 - containerPort: 5432
envFrom: env:
- configMapRef: - name: POSTGRES_USER
name: db-config valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_PASSWORD
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_DB
volumeMounts: volumeMounts:
- name: db-vol - name: db-vol
mountPath: /var/lib/postgresql/data mountPath: /var/lib/postgresql/data

View File

@ -1,26 +1,5 @@
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: runner-config
labels:
app: runner
data:
DATABASE_HOST: "db-service.woj.svc.cluster.local"
DATABASE_USER: "woj"
DATABASE_PASSWORD: "A_VeRy-S3cUr3^PaSsWoRd"
DATABASE_NAME: "woj"
REDIS_ADDRESS: "cache-service.woj.svc.cluster.local"
REDIS_PASSWORD: "YeT_An0tHeR_VeRy-S3cUr3^PaSsWoRd"
STORAGE_ENDPOINT: "storage-service.woj.svc.cluster.local:9000"
STORAGE_ACCESS_KEY: "A_VeRy_CoMpLeX_AcCeSs_KeY"
STORAGE_SECRET_KEY: "A_VeRy_CoMpLeX_ScReT_KeY"
STORAGE_BUCKET: "woj"
---
apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
namespace: woj namespace: woj
@ -60,9 +39,28 @@ spec:
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
args: args:
- runner - runner
envFrom: env:
- configMapRef: - name: REDIS_ADDRESS
name: runner-config value: "cache-service.woj.svc.cluster.local"
- name: REDIS_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: REDIS_PASSWORD
- name: STORAGE_ENDPOINT
value: "storage-service.woj.svc.cluster.local:9000"
- name: STORAGE_ACCESS_KEY
valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_USER
- name: STORAGE_SECRET_KEY
valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_PASSWORD
- name: STORAGE_BUCKET
value: "woj"
securityContext: securityContext:
privileged: true privileged: true
volumeMounts: volumeMounts:

View File

@ -1,25 +1,4 @@
--- ---
apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: server-config
labels:
app: server
data:
DATABASE_HOST: "db-service.woj.svc.cluster.local"
DATABASE_USER: "woj"
DATABASE_PASSWORD: "A_VeRy-S3cUr3^PaSsWoRd"
DATABASE_NAME: "woj"
REDIS_ADDRESS: "cache-service.woj.svc.cluster.local"
REDIS_PASSWORD: "YeT_An0tHeR_VeRy-S3cUr3^PaSsWoRd"
STORAGE_ENDPOINT: "storage-service.woj.svc.cluster.local:9000"
STORAGE_ACCESS_KEY: "A_VeRy_CoMpLeX_AcCeSs_KeY"
STORAGE_SECRET_KEY: "A_VeRy_CoMpLeX_ScReT_KeY"
STORAGE_BUCKET: "woj"
---
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@ -45,18 +24,69 @@ spec:
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
args: args:
- init - init
envFrom: env:
- configMapRef: - name: DATABASE_HOST
name: server-config value: "db-service.woj.svc.cluster.local"
- name: DATABASE_USER
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_USER
- name: DATABASE_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_PASSWORD
- name: DATABASE_NAME
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_DB
containers: containers:
- name: server - name: server
image: git.0x7f.app/woj/woj-server:1.1.0 image: git.0x7f.app/woj/woj-server:1.1.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
args: args:
- server - server
envFrom: env:
- configMapRef: - name: DATABASE_HOST
name: server-config value: "db-service.woj.svc.cluster.local"
- name: DATABASE_USER
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_USER
- name: DATABASE_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_PASSWORD
- name: DATABASE_NAME
valueFrom:
configMapKeyRef:
name: shared-config
key: POSTGRES_DB
- name: REDIS_ADDRESS
value: "cache-service.woj.svc.cluster.local"
- name: REDIS_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: REDIS_PASSWORD
- name: STORAGE_ENDPOINT
value: "storage-service.woj.svc.cluster.local:9000"
- name: STORAGE_ACCESS_KEY
valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_USER
- name: STORAGE_SECRET_KEY
valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_PASSWORD
- name: STORAGE_BUCKET
value: "woj"
ports: ports:
- containerPort: 8000 - containerPort: 8000
--- ---

View File

@ -1,17 +1,5 @@
--- ---
apiVersion: v1 apiVersion: v1
kind: ConfigMap
metadata:
namespace: woj
name: storage-config
labels:
app: storage
data:
MINIO_ROOT_USER: "A_VeRy_CoMpLeX_AcCeSs_KeY"
MINIO_ROOT_PASSWORD: "A_VeRy_CoMpLeX_ScReT_KeY"
MINIO_VOLUMES: "/data"
---
apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
namespace: woj namespace: woj
@ -56,9 +44,17 @@ spec:
ports: ports:
- containerPort: 9000 - containerPort: 9000
- containerPort: 9001 - containerPort: 9001
envFrom: env:
- configMapRef: - name: MINIO_ROOT_USER
name: storage-config valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_USER
- name: MINIO_ROOT_PASSWORD
valueFrom:
configMapKeyRef:
name: shared-config
key: MINIO_ROOT_PASSWORD
volumeMounts: volumeMounts:
- name: storage-vol - name: storage-vol
mountPath: /data mountPath: /data