Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
60fbc27
chore(go module): added amqp 0.9.1 client
belyaevEDU Aug 29, 2026
f6406a1
refactor(repository): added a module 'storage'
belyaevEDU Aug 29, 2026
996075a
fix(cmd/server): fixed import
belyaevEDU Aug 29, 2026
bbd2457
feat(port): defined the CodeExecutor interface
belyaevEDU Aug 30, 2026
eb453a8
feat(domain/task): added structs for execution requests and results
belyaevEDU Aug 30, 2026
23d39b3
feat/refactor(config): added 'philharmonic' config, split configs int…
belyaevEDU Aug 30, 2026
48fc6f1
feat(config): loading the separate configs into one struct
belyaevEDU Aug 30, 2026
82096a6
fix(main): fixed config referencing
belyaevEDU Aug 30, 2026
ed00a2a
chore: go mod tidy
belyaevEDU Aug 30, 2026
26b8038
refactor(config): split 'app' and 'philharmonic' configs again
belyaevEDU Aug 30, 2026
881055c
fix(main): updated config-related references
belyaevEDU Aug 30, 2026
8f46553
chore: updated .env.example to reflect new env vars
belyaevEDU Aug 30, 2026
da5220c
feat(service): added a client for philharmonic
belyaevEDU Aug 31, 2026
405713e
feat(config): added queue config
belyaevEDU Aug 31, 2026
cefd43b
feat(task): added struct for rabbitmq payload
belyaevEDU Aug 31, 2026
3c0346e
feat(port): added interfaces and a type for producer->consumer intera…
belyaevEDU Aug 31, 2026
36d16f3
feat(repository): added queue package with basic connection functions
belyaevEDU Aug 31, 2026
280397a
feat(repository/queue): added a publisher implementation
belyaevEDU Aug 31, 2026
6204112
feat(repository/queue): added a consumer implementation
belyaevEDU Aug 31, 2026
16ad7b3
docs(.env): updated .env.example
belyaevEDU Aug 31, 2026
b108b02
chore: updated go module info
belyaevEDU Aug 31, 2026
9f39414
refactor: removed double of TaskRequest
belyaevEDU Sep 1, 2026
a0a725c
feat(service/philharmonic): added image prewarming
belyaevEDU Sep 1, 2026
de6c508
feat: added migrations
belyaevEDU Sep 1, 2026
4b3df4b
feat(config): added postgres
belyaevEDU Sep 1, 2026
d9a52f5
feat(config): added redis
belyaevEDU Sep 1, 2026
0a81d19
feat(repository): added postgres package - repository struct and open…
belyaevEDU Sep 2, 2026
551156b
feat(repository/postgres): added Migrate function
belyaevEDU Sep 2, 2026
871ce4e
fix(migrations): added a NOT NULL constraint to tasks' translator field
belyaevEDU Sep 2, 2026
cfa95c3
feat(domain/task): added translator field to task, submission struct
belyaevEDU Sep 2, 2026
73315cd
feat(repository/postgres): implemented port.taskrepository
belyaevEDU Sep 2, 2026
f0c0a65
feat(repository/postgres): implemented port.userrepository
belyaevEDU Sep 2, 2026
ba88399
fix(repository): removed ram storage package
belyaevEDU Sep 2, 2026
896facd
style: updated taskrepository method names to be userrepository-alike
belyaevEDU Sep 2, 2026
753c085
feat(repository): added redis
belyaevEDU Sep 2, 2026
fa97289
fix(service/task): using publisher, adhering to the new fields
belyaevEDU Sep 2, 2026
453deb6
feat(controller/handlers/task): updated Create handler to submit tasks
belyaevEDU Sep 2, 2026
36773f9
fix(cmd/server): updated server
belyaevEDU Sep 2, 2026
db51867
feat(cmd): added processor
belyaevEDU Sep 2, 2026
a61d4bc
fix(config/app): removed unnecessary fields
belyaevEDU Sep 2, 2026
0945083
fix(cmd/processor): reworded comment
belyaevEDU Sep 2, 2026
e4248f9
docs(swagger): regenerated
belyaevEDU Sep 2, 2026
32a5ef4
build(dockerfile): added migrations to main dockerfile, separate dock…
belyaevEDU Sep 4, 2026
57c0012
build(dockerfile): base image and dep version pinning
belyaevEDU Sep 4, 2026
c530aae
chore: updated go package
belyaevEDU Sep 4, 2026
e1bc33e
chore: added sql-migrate cli config (forgor)
belyaevEDU Sep 4, 2026
bc09609
fix(test): now testing codeprocessor
belyaevEDU Sep 4, 2026
575a45d
build(dockerfile.processor): pinned runtime image by digest
belyaevEDU Sep 4, 2026
af74482
feat: added sandbox image
belyaevEDU Sep 4, 2026
19bec29
build(compose): updated docker compose
belyaevEDU Sep 4, 2026
62c329c
ci: added ci
belyaevEDU Sep 4, 2026
731b687
ci: e2e now runs on PR trigger, sandbox image publishing fixed
belyaevEDU Sep 4, 2026
45652c8
build(compose): fixed postgres volume bind
belyaevEDU Sep 4, 2026
9353069
ci: rewrote debug logs to avoid 'file not found' errors
belyaevEDU Sep 4, 2026
47797c3
ci(e2e): fixed connection retries
belyaevEDU Sep 4, 2026
ba11c0b
feat(service/philharmonic): using new security options
belyaevEDU Sep 4, 2026
2a7263c
style(service/philharmonic): dropped tags
belyaevEDU Sep 4, 2026
e724f22
ci(e2e): updated philharmonic version
belyaevEDU Sep 4, 2026
f6b212c
chore(.dockerignore): added sandbox, dbconfig, .python-version (pyenv…
belyaevEDU Sep 4, 2026
7713125
chore(.dockerignore): forgot that sandbox is built within the context…
belyaevEDU Sep 4, 2026
9f187de
build: split COPY . .
belyaevEDU Sep 4, 2026
f2b2e47
build: removed .dockerignore
belyaevEDU Sep 4, 2026
8895f82
feat(service): added a metrics-instrumented executor
belyaevEDU Sep 4, 2026
a73fdcc
feat(config): added a metrics config
belyaevEDU Sep 4, 2026
2a7a72d
feat(controller): added a metrics server
belyaevEDU Sep 4, 2026
2203e36
feat(controller/router): instrumented chi with chi's metrics package
belyaevEDU Sep 4, 2026
8f03509
feat(cmd/server): loading metrics config, starting metrics server
belyaevEDU Sep 4, 2026
aac0f61
feat(cmd/processor): loading metrics config, instrumenting executor, …
belyaevEDU Sep 4, 2026
4143b07
docs(.env): updated .env.example
belyaevEDU Sep 4, 2026
3b29345
build: exposing metrics server ports
belyaevEDU Sep 4, 2026
75b0230
chore: updated go module
belyaevEDU Sep 4, 2026
e2aea3a
ci(e2e): fixed port conflict
belyaevEDU Sep 4, 2026
46f5786
fix(port): passing in contexts
belyaevEDU Sep 7, 2026
b52d1d1
fix(repository): accepting and using passed contexts instead of conte…
belyaevEDU Sep 7, 2026
7c84a69
fix(service/user): accepting and passing context
belyaevEDU Sep 7, 2026
093fd90
fix(service/task): using prev. passed context, accepting and passing …
belyaevEDU Sep 7, 2026
d3355df
fix(controller/handlers): passing request contexts
belyaevEDU Sep 7, 2026
d97484d
fix(cmd/processor): passing context to db method
belyaevEDU Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions .dockerignore

This file was deleted.

50 changes: 49 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
## docker compose

POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=remote_code
POSTGRES_PORT=5432

REDIS_PORT=6379

RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
RABBITMQ_PORT=5672

HTTP_PORT=8000
METRICS_PORT=9100 # server / producer
PROCESSOR_METRICS_PORT=9101 # consumer

## runtime - not injected into containers, set via compose

HTTP_ADDR=:8000
TASK_PROCESSING_TIME=2s
SHUTDOWN_TIMEOUT=10s

METRICS_ADDR=:9100
METRICS_SHUTDOWN_TIMEOUT=5s

DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${POSTGRES_PORT}/${POSTGRES_DB}?sslmode=disable
MIGRATIONS_DIR=migrations

REDIS_ADDR=localhost:${REDIS_PORT}
REDIS_PASSWORD=
REDIS_DB=0
SESSION_TTL=168h

RABBITMQ_URL=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@localhost:${RABBITMQ_PORT}/
TASK_QUEUE_NAME=tasks
TASK_QUEUE_PREFETCH=1
TASK_QUEUE_RECONNECT_DELAY=1s

PHILHARMONIC_URL=http://localhost:5555
# empty means manager runs without auth
PHILHARMONIC_TOKEN=
PHILHARMONIC_IMAGE=sandbox:latest
PHILHARMONIC_TASK_TIMEOUT=30s
PHILHARMONIC_POLL_INTERVAL=1s
# unset means PHILHARMONIC_TASK_TIMEOUT + 15s
PHILHARMONIC_POLL_TIMEOUT=
PHILHARMONIC_CPU=0.5
# bytes
PHILHARMONIC_MEMORY=268435456
# CI publishes the sandbox to GHCR on every push to main
PHILHARMONIC_IMAGE=ghcr.io/belyaevedu/backend-final/sandbox:latest
127 changes: 127 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
lint:
name: vet & lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: vet
run: go vet ./...

- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.11.3

publish-sandbox:
name: publish sandbox image to ghcr
# push to main only basically
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- id: repo
name: lowercase repo name by ghcr requirement
run: echo "name=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"

- name: login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: build and push
uses: docker/build-push-action@v6
with:
context: .
file: sandbox/Dockerfile
push: true
tags: |
ghcr.io/${{ steps.repo.outputs.name }}/sandbox:latest
ghcr.io/${{ steps.repo.outputs.name }}/sandbox:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

e2e:
name: e2e (compose + philharmonic + pytest)
runs-on: ubuntu-latest
needs: lint
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/remote_code?sslmode=disable
REDIS_ADDR: localhost:6379
RABBITMQ_URL: amqp://guest:guest@localhost:5672/
PHILHARMONIC_URL: http://localhost:5555
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: infra
run: docker compose up -d --wait postgres redis rabbitmq

- name: build sandbox image
run: docker build -t sandbox:latest -f sandbox/Dockerfile .

- name: start philharmonic manager + worker on the runner
run: |
git clone --depth 1 --branch v0.1.1 https://github.com/belyaevedu/philharmonic /tmp/philharmonic
(cd /tmp/philharmonic && CGO_ENABLED=0 go build -o /tmp/phrm .)

/tmp/phrm manager --host 127.0.0.1 --port 5555 --workers 127.0.0.1:5556 > /tmp/phrm-manager.log 2>&1 &
/tmp/phrm worker --host 127.0.0.1 --port 5556 --name dev-worker > /tmp/phrm-worker.log 2>&1 &

# wait until the manager reads the worker's stats
for i in $(seq 1 30); do
curl -sf localhost:5555/nodes | grep -q '"Cores":[1-9]' && break
sleep 1
done
curl -sf localhost:5555/nodes | grep -q '"Cores":[1-9]'

- name: build & run server and processor
run: |
go build -o /tmp/server ./cmd/server
go build -o /tmp/processor ./cmd/processor
/tmp/server > /tmp/server.log 2>&1 &
METRICS_ADDR=:9101 /tmp/processor > /tmp/processor.log 2>&1 &
# any HTTP answer (even 401/404) means the server is serving.
# it only serves after the migrations are done
curl -s -o /dev/null --retry 30 --retry-delay 1 --retry-all-errors \
localhost:8000/status/00000000-0000-0000-0000-000000000000

- name: pytest
run: |
python -m pip install pytest requests
python -m pytest tests/hw2.py tests/hw3.py -v

- name: debug logs
if: failure()
run: |
docker compose ps
docker compose logs --tail 30 postgres redis rabbitmq || true
for f in /tmp/server.log /tmp/processor.log /tmp/phrm-manager.log /tmp/phrm-worker.log; do
if [ -f "$f" ]; then echo "== $f =="; tail -n 50 "$f"; fi
done

- name: teardown
if: always()
run: docker compose down -v
14 changes: 9 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
FROM golang:1.26-alpine AS builder
FROM golang:1.26-alpine@sha256:ce864e7223ac17b1775e6fd0b4c0db580c2eb50e7953a427916379e4b92a1628 AS builder

WORKDIR /src

RUN apk add --no-cache git ca-certificates
RUN apk add --no-cache git~=2.54.0-r0 ca-certificates~=20260611-r0

COPY go.mod go.sum ./
RUN go mod download

COPY . .
COPY cmd ./cmd
COPY internal ./internal
COPY migrations ./migrations

RUN CGO_ENABLED=0 GOOS=linux \
go build -trimpath -ldflags="-s -w" -o /out/server ./cmd/server

FROM gcr.io/distroless/static-debian12:nonroot AS runtime
FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab AS runtime

WORKDIR /
WORKDIR /app

COPY --from=builder /out/server /app/server
COPY --from=builder /src/migrations /app/migrations

EXPOSE 8000
EXPOSE 9100

USER 65532:65532

Expand Down
24 changes: 24 additions & 0 deletions Dockerfile.processor
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM golang:1.26-alpine@sha256:ce864e7223ac17b1775e6fd0b4c0db580c2eb50e7953a427916379e4b92a1628 AS builder

WORKDIR /src

RUN apk add --no-cache git~=2.54.0-r0 ca-certificates~=20260611-r0

COPY go.mod go.sum ./
RUN go mod download

COPY cmd ./cmd
COPY internal ./internal

RUN CGO_ENABLED=0 GOOS=linux \
go build -trimpath -ldflags="-s -w" -o /out/processor ./cmd/processor

FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab AS runtime

WORKDIR /app

EXPOSE 9100

COPY --from=builder /out/processor /app/processor

ENTRYPOINT ["/app/processor"]
86 changes: 86 additions & 0 deletions cmd/processor/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"context"
"fmt"
"log"
"os/signal"
"syscall"
"time"

"github.com/belyaevedu/remote-code-service/internal/config"
"github.com/belyaevedu/remote-code-service/internal/controller"
"github.com/belyaevedu/remote-code-service/internal/domain"
"github.com/belyaevedu/remote-code-service/internal/repository/postgres"
"github.com/belyaevedu/remote-code-service/internal/repository/queue"
"github.com/belyaevedu/remote-code-service/internal/service"
)

func main() {
dbCfg, err := config.LoadDBConfig()
if err != nil {
log.Fatalf("invalid db config: %v", err)
}
queueCfg, err := config.LoadQueueConfig()
if err != nil {
log.Fatalf("invalid queue config: %v", err)
}
philCfg, err := config.LoadPhilharmonicConfig()
if err != nil {
log.Fatalf("invalid philharmonic config: %v", err)
}
metricsCfg, err := config.LoadMetricsConfig()
if err != nil {
log.Fatalf("invalid metrics config: %v", err)
}

ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

db, err := postgres.New(ctx, dbCfg)
if err != nil {
log.Fatalf("postgres: %v", err)
}
defer db.Close()

if err := db.WaitReady(ctx); err != nil {
log.Fatalf("waiting for schema: %v", err)
}

phrmExecutor := service.NewPhilharmonicExecutor(philCfg)

// pulling the sandbox image on every worker before starting to process
prewarmCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
if err := phrmExecutor.PreWarm(prewarmCtx); err != nil {
log.Printf("processor: sandbox pre-warm failed: %v (continuing)", err)
} else {
log.Printf("processor: sandbox image %q pre-warmed on workers", philCfg.SandboxImage)
}
cancel()

executor := service.NewInstrumentedExecutor(phrmExecutor)

consumer := queue.NewConsumer(queueCfg)

metricsServer := controller.NewMetricsServer(metricsCfg.Addr, metricsCfg.ShutdownTimeout)
go func() {
if err := metricsServer.Start(ctx); err != nil {
log.Fatalf("metrics server exited with error: %v", err)
}
}()

handle := func(ctx context.Context, msg domain.TaskMessage) error {
result, err := executor.Execute(ctx, msg)
if err != nil {
return fmt.Errorf("execute task %s: %w", msg.TaskID, err)
}

return db.SaveTaskResult(ctx, msg.TaskID, &domain.Result{Output: result.Output})
}

log.Printf("processor: consuming queue %q", queueCfg.Queue)
if err := consumer.Consume(ctx, handle); err != nil {
log.Fatalf("consumer exited with error: %v", err)
}
log.Printf("processor stopped gracefully")
}
Loading
Loading