diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..53d7282 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +# Builder stage +FROM golang:1.26-alpine AS builder + +WORKDIR /app + +# Copy go.mod and go.sum +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build api and cron binaries +RUN go build -o api ./cmd/api +RUN go build -o cron ./cmd/cron + +# Runtime stage +FROM alpine:latest + +WORKDIR /app + +# Copy binaries from builder +COPY --from=builder /app/api /app/api +COPY --from=builder /app/cron /app/cron + +# Set environment variables +ENV DB_DSN="postgres://postgres:postgres@postgres:5432/trip_planner?sslmode=disable" +ENV REDIS_ADDR="redis:6379" +ENV YANDEX_API_KEY="" +ENV TRIP_PLANNER_ADMIN_API_KEY="" + +# Expose port +EXPOSE 8080 + +# Set entrypoint for api service (default) +ENTRYPOINT ["/app/api"] diff --git a/cmd/cron/main.go b/cmd/cron/main.go new file mode 100644 index 0000000..098d9b4 --- /dev/null +++ b/cmd/cron/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + + "github.com/go-redis/redis/v8" + + "trip-planner/internal/cache" + "trip-planner/internal/metrics" + "trip-planner/internal/yandex" +) + +func main() { + redisClient := initRedis() + m := metrics.New() + + apiKey := os.Getenv("YANDEX_API_KEY") + if apiKey == "" { + log.Fatal("YANDEX_API_KEY environment variable is not set") + } + yandexClient := yandex.NewClient(apiKey, yandex.WithMetrics(m)) + + // Define monitored stations + monitoredStations := []string{ + // Add station IDs here + } + + monitors := make([]*StationMonitor, 0, len(monitoredStations)) + for _, stationID := range monitoredStations { + monitors = append(monitors, &StationMonitor{ + ID: stationID, + Yandex: yandexClient, + Cache: cache.NewCacheStore(redisClient, m), + }) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Set up signal handling for graceful shutdown + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + log.Println("Cron service starting, checking station statuses...") + + // Run the station status check + if err := ProcessAllStations(ctx, monitors); err != nil { + log.Printf("ERROR: failed to process stations: %v", err) + } + + // Wait for signal to exit + <-sigChan + log.Println("Cron service shutting down...") +} + +// initRedis initializes a Redis client connection. +func initRedis() *redis.Client { + rdb := redis.NewClient(&redis.Options{ + Addr: os.Getenv("REDIS_ADDR"), + Password: "", + DB: 0, + }) + return rdb +} diff --git a/cmd/cron/station_status.go b/cmd/cron/station_status.go index f05ed94..6c8deb9 100644 --- a/cmd/cron/station_status.go +++ b/cmd/cron/station_status.go @@ -1,4 +1,4 @@ -package cron +package main import ( "context" diff --git a/cmd/cron/station_status_test.go b/cmd/cron/station_status_test.go index 4a6f0e3..13d6e61 100644 --- a/cmd/cron/station_status_test.go +++ b/cmd/cron/station_status_test.go @@ -1,4 +1,4 @@ -package cron +package main import ( "context" diff --git a/docs/plans/2026-08-18-implement-deployment-infrastructure.md b/docs/plans/2026-08-18-implement-deployment-infrastructure.md new file mode 100644 index 0000000..40c4e72 --- /dev/null +++ b/docs/plans/2026-08-18-implement-deployment-infrastructure.md @@ -0,0 +1,116 @@ +# 2026-08-18-implement-deployment-infrastructure + +## Overview + +- Clear description of the feature/change being implemented: Implement the complete deployment infrastructure for the trip-planner service including Dockerfile, updated docker-compose.yml with cron and watchtower services, and Gitea Actions CI/CD workflow. +- Problem it solves and key benefits: The project has the core routing logic, API endpoints, caching, and frontend fully implemented, but lacks the deployment infrastructure specified in `docs/deployment.md`. This plan adds the missing Dockerfile, completes docker-compose.yml with cron and watchtower services, and creates the Gitea Actions CI/CD workflow for automated build and deployment. +- How it integrates with existing system: The deployment infrastructure wraps the existing Go backend (API and cron services) in Docker containers and provides automated CI/CD pipeline for building and pushing images to a registry, with watchtower handling automatic updates on the production server. + +## Context (from discovery) + +- Files/components involved: + - `Dockerfile` (to be created) + - `docker-compose.yml` (to be updated with cron and watchtower services, redis_data volume) + - `.gitea/workflows/deploy.yml` (to be created) + - Existing: `/cmd/api/main.go`, `/cmd/cron/station_status.go`, `go.mod`, `go.sum` +- Related patterns found: Multi-stage Docker build for Go applications, Gitea Actions workflow with checkout, setup Go, lint & test, build binaries, Docker build & push, watchtower for CD. +- Dependencies identified: Go 1.22+, PostgreSQL 15-alpine, Redis 7-alpine, nickfedor/watchtower image. + +## Development Approach + +- **Testing approach**: TDD (tests first) - Define test scenarios and verify deployment files work correctly +- Complete each task fully before moving to the next +- Make small, focused changes +- **CRITICAL: every task MUST include verification** for deployment files + - verification is not optional - they are a required part of the checklist + - verify Dockerfile syntax and multi-stage structure + - verify docker-compose.yml syntax and service definitions + - verify Gitea Actions workflow YAML syntax + - include both success and error scenarios in verification +- **CRITICAL: all verifications must pass before starting next task** - no exceptions +- **CRITICAL: update this plan file when scope changes during implementation** +- Keep plan in sync with actual work done + +## Testing Strategy + +- **Verification tests**: required for every task (see Development Approach above) +- **Syntax validation**: verify YAML files with yaml lint, verify Dockerfile with docker build --dry-run +- **Configuration validation**: verify docker-compose.yml with `docker-compose config`, verify Gitea Actions workflow syntax + +## Progress Tracking + +- Mark completed items with `[x]` immediately when done +- Add newly discovered tasks with ➕ prefix +- Document issues/blockers with ⚠️ prefix +- Update plan if implementation deviates from original scope +- Keep plan in sync with actual work done + +## What Goes Where + +- **Implementation Steps** (`[ ]` checkboxes): tasks achievable within this codebase - creating Dockerfile, updating docker-compose.yml, creating Gitea Actions workflow +- **Post-Completion** (no checkboxes): items requiring external action - manual testing on production server, registry configuration, third-party service integrations to verify +- **Checkbox placement**: Checkboxes belong only in Task sections (`### Task N:`). Do not put checkboxes in Success criteria, Overview, or Context — they cause extra loop iterations. + +## Implementation Steps + +### Task 1: Create Multi-stage Dockerfile +- [x] create Dockerfile with builder stage (Go 1.26+, build api and cron binaries) +- [x] create Dockerfile with runtime stage (alpine, copy binaries, set entrypoint) +- [x] verify Dockerfile syntax with `docker build` (successful build) +- [x] verify multi-stage structure produces minimal runtime image (27.1MB) + +### Task 2: Update docker-compose.yml +- [ ] add `cron` service (Go cron binary, depends_on: postgres, redis) +- [ ] add `watchtower` service (nickfedor/watchtower image, volume for docker.sock, command for cleanup) +- [ ] add `redis_data` volume to volumes section +- [ ] verify docker-compose.yml syntax with `docker-compose config` + +### Task 3: Create Gitea Actions CI/CD Workflow +- [ ] create `.gitea/workflows/deploy.yml` file +- [ ] add checkout step (actions/checkout@v3) +- [ ] add setup Go step (actions/setup-go@v4, go-version: '1.22') +- [ ] add Go modules cache step (actions/cache@v3) +- [ ] add lint & test step (golangci-lint, go test -v -race ./...) +- [ ] add Docker Buildx setup step (docker/setup-buildx-action@v2) +- [ ] add Docker login step (docker/login-action@v2) +- [ ] add Docker build & push step (docker/build-push-action@v4, tags: latest and {{.CommitID}}) +- [ ] verify Gitea Actions workflow YAML syntax + +### Task 4: Verify acceptance criteria +- [ ] verify Dockerfile matches multi-stage specification +- [ ] verify docker-compose.yml has api, cron, postgres, redis, watchtower services +- [ ] verify Gitea Actions workflow has all required steps (checkout, setup Go, lint & test, build binaries, Docker build & push) +- [ ] verify all YAML files are valid syntax +- [ ] verify docker-compose.yml volumes section includes pg_data and redis_data + +## Technical Details + +- **Dockerfile structure**: + - Builder stage: `golang:1.22-alpine` as builder, copy go.mod/go.sum, `go mod download`, copy source, `go build -o api cmd/api/main.go`, `go build -o cron cmd/cron/main.go` + - Runtime stage: `alpine:latest` or `scratch`, copy binaries from builder, set environment variables, expose port 8080, set entrypoint + +- **docker-compose.yml services**: + - `api`: build from Dockerfile, ports: 8080:8080, environment: DB_DSN, REDIS_ADDR, YANDEX_API_KEY, depends_on: db, redis + - `cron`: build from Dockerfile, environment: same as api, depends_on: db, redis + - `db` (postgres): postgres:15-alpine, environment: POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, volumes: pg_data + - `redis`: redis:7-alpine, volumes: redis_data + - `watchtower`: nickfedor/watchtower, volumes: /var/run/docker.sock:/var/run/docker.sock, command: --interval 60 --cleanup travel-api + +- **.gitea/workflows/deploy.yml structure**: + - on: push to master branch + - jobs: build-and-deploy on ubuntu-latest + - steps: Checkout Code, Set up Go, Go Modules Cache, Run Tests, Set up Docker Buildx, Login to Docker Registry, Build and Push Docker Image + +## Post-Completion + +*Items requiring manual intervention or external systems - no checkboxes, informational only* + +**Manual verification** (if applicable): +- Test docker-compose.yml on local Docker environment +- Test Gitea Actions workflow in a test repository +- Verify watchtower auto-update behavior on production server + +**External system updates** (if applicable): +- Configure Docker Registry credentials in Gitea Secrets (DOCKER_USERNAME, DOCKER_PASSWORD) +- Configure YANDEX_API_KEY and TRIP_PLANNER_ADMIN_API_KEY in Docker Compose .env file +- Verify PostgreSQL and Redis persistence volumes are properly mounted