diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2c6fc94 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,154 @@ +name: CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: linkkeeper_test + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + + - name: Install dependencies + run: | + go mod download + go mod tidy + + - name: Run go fmt + run: | + unformatted=$(gofmt -s -l $(find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' 2>/dev/null)) + if [ -n "$unformatted" ]; then + echo "Please run 'go fmt ./...'" + echo "$unformatted" + exit 1 + fi + + - name: Run go vet + run: go vet ./... + + - name: Run tests + env: + POSTGRES_DSN: "postgres://postgres:postgres@localhost:5432/linkkeeper_test?sslmode=disable" + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella + fail_ci_if_error: false + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: latest + args: --timeout=5m + + build: + name: Build + runs-on: ubuntu-latest + needs: [test, lint] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + + - name: Build api-service + run: go build -v -o bin/api-service ./cmd/api-service + + - name: Build user-service + run: go build -v -o bin/user-service ./cmd/user-service + + - name: Build bot-service + run: go build -v -o bin/bot-service ./cmd/bot-service + + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: [test, lint] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build api-service image + uses: docker/build-push-action@v5 + with: + context: . + file: ./build/api-service/Dockerfile + push: false + tags: linkkeeper-api-service:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build user-service image + uses: docker/build-push-action@v5 + with: + context: . + file: ./build/user-service/Dockerfile + push: false + tags: linkkeeper-user-service:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build bot-service image + uses: docker/build-push-action@v5 + with: + context: . + file: ./build/bot-service/Dockerfile + push: false + tags: linkkeeper-bot-service:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..bed39fb --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,65 @@ +linters-settings: + govet: + enable: + - shadow + gocyclo: + min-complexity: 20 + dupl: + threshold: 150 + goconst: + min-len: 3 + min-occurrences: 3 + misspell: + locale: US + lll: + line-length: 140 + gocritic: + enabled-tags: + - diagnostic + - performance + disabled-checks: + - hugeParam + - ifElseChain + +linters: + disable-all: true + enable: + - bodyclose + - errcheck + - gocyclo + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - misspell + - noctx + - staticcheck + - stylecheck + - typecheck + - unconvert + - unused + - whitespace + +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck + - gocyclo + - path: cmd/ + linters: + - errcheck + - path: internal/.*/repository\.go + linters: + - dupl + - path: internal/.*/usecase\.go + linters: + - dupl + +run: + timeout: 5m + tests: false + skip-dirs: + - vendor + - frontend diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..94d10f3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: check-case-conflict + - id: detect-private-key + + - repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-fmt + - id: go-vet + - id: go-mod-tidy + - id: go-unit-tests + args: [-timeout=30s, -short] + + - repo: local + hooks: + - id: go-test + name: go test + entry: go test -v -race -short ./... + language: system + pass_filenames: false + always_run: true diff --git a/ARCHITECTURE_TESTING.md b/ARCHITECTURE_TESTING.md new file mode 100644 index 0000000..289ec78 --- /dev/null +++ b/ARCHITECTURE_TESTING.md @@ -0,0 +1,187 @@ +# Тестирование и архитектура в GitHub Actions + +## Краткий ответ: НЕТ, отдельная сборка с указанием архитектуры НЕ нужна + +### Почему не нужно? + +1. **`go test` автоматически компилирует для текущей платформы** + - GitHub Actions runner (`ubuntu-latest`) работает на `linux/amd64` + - `go test` автоматически компилирует тесты для `linux/amd64` + - Тесты запускаются на той же архитектуре, что и runner + +2. **Текущая конфигурация правильная:** +```yaml +- name: Run tests + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... +``` + +3. **Go автоматически определяет:** + - Операционную систему (Linux) + - Архитектуру (amd64) + - Компилирует и запускает соответственно + +## Когда МОЖЕТ понадобиться указать архитектуру? + +### 1. Кросс-платформенное тестирование + +Если нужно тестировать на разных платформах: + +```yaml +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + go-version: ['1.23'] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - run: go test ./... +``` + +### 2. Тестирование на разных архитектурах (ARM, x86) + +```yaml +jobs: + test: + strategy: + matrix: + arch: [amd64, arm64] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: '1.23' + - run: | + GOARCH=${{ matrix.arch }} go test ./... +``` + +### 3. Сборка бинарников для разных платформ + +Для сборки (не тестов) может понадобиться: + +```yaml +- name: Build for multiple platforms + run: | + GOOS=linux GOARCH=amd64 go build -o bin/api-service-linux-amd64 ./cmd/api-service + GOOS=linux GOARCH=arm64 go build -o bin/api-service-linux-arm64 ./cmd/api-service + GOOS=windows GOARCH=amd64 go build -o bin/api-service-windows-amd64.exe ./cmd/api-service +``` + +## Текущая конфигурация (оптимальная для большинства случаев) + +```yaml +- name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.23' + cache: true + +- name: Run tests + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... +``` + +**Это работает потому что:** +- ✅ `ubuntu-latest` = Linux amd64 +- ✅ `go test` автоматически компилирует для Linux amd64 +- ✅ Тесты запускаются нативно (быстро) +- ✅ Race detector работает корректно +- ✅ Coverage собирается правильно + +## Сравнение подходов + +### Текущий подход (рекомендуется) +```yaml +go test ./... +``` +- ✅ Просто и быстро +- ✅ Автоматическая компиляция для текущей платформы +- ✅ Нативная производительность +- ✅ Подходит для 99% проектов + +### Явное указание архитектуры (избыточно) +```yaml +GOOS=linux GOARCH=amd64 go test ./... +``` +- ⚠️ Избыточно (то же самое, что и без указания) +- ⚠️ Может быть медленнее (если нужна кросс-компиляция) +- ✅ Нужно только для кросс-платформенного тестирования + +## Когда добавить multi-arch тестирование? + +Добавьте только если: +1. ✅ Проект должен работать на разных платформах (Windows, macOS, Linux) +2. ✅ Есть платформо-специфичный код (syscalls, файловые пути) +3. ✅ Нужно тестировать на ARM (например, для Docker на ARM) +4. ✅ Требования проекта/компании + +## Рекомендации + +### Для вашего проекта (LinkKeeper) + +**Текущая конфигурация идеальна:** +- ✅ Тесты запускаются на Linux amd64 (стандарт для серверов) +- ✅ Быстро и эффективно +- ✅ Покрывает основную целевую платформу +- ✅ Race detector работает корректно + +**Не нужно добавлять:** +- ❌ Явное указание GOOS/GOARCH (избыточно) +- ❌ Multi-arch тестирование (если не требуется) +- ❌ Дополнительные сборки (только для тестов) + +### Если понадобится расширить + +Добавьте matrix strategy только если: +- Нужно тестировать на Windows/macOS +- Нужна поддержка ARM +- Есть платформо-специфичный код + +## Пример расширенной конфигурации (если понадобится) + +```yaml +jobs: + test: + strategy: + matrix: + include: + - os: ubuntu-latest + go-version: '1.23' + - os: windows-latest + go-version: '1.23' + - os: macos-latest + go-version: '1.23' + runs-on: ${{ matrix.os }} + services: + postgres: + image: postgres:16 + # ... (только для Linux) + if: matrix.os == 'ubuntu-latest' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: Run tests + run: go test -v -race ./... + # PostgreSQL только на Linux + if: matrix.os == 'ubuntu-latest' +``` + +## Вывод + +**Для вашего проекта:** +- ✅ Текущая конфигурация правильная +- ✅ Не нужно указывать архитектуру явно +- ✅ `go test` автоматически работает корректно +- ✅ Все тесты запускаются нативно на Linux amd64 + +**Добавляйте multi-arch только если:** +- Требуется поддержка других платформ +- Есть специфичные требования проекта diff --git a/CI_CD.md b/CI_CD.md new file mode 100644 index 0000000..16583fe --- /dev/null +++ b/CI_CD.md @@ -0,0 +1,348 @@ +# CI/CD Documentation + +This document describes the Continuous Integration and Continuous Deployment setup for the LinkKeeper project. + +## GitHub Actions Workflows + +### Main CI Pipeline (`.github/workflows/ci.yml`) + +The CI pipeline runs automatically on: +- Push to `main` and `develop` branches +- Pull requests to `main` and `develop` branches + +#### Pipeline Jobs + +##### 1. Test Job +- **Environment**: Ubuntu latest with PostgreSQL 16 +- **Steps**: + - Checkout code + - Set up Go 1.23 + - Install dependencies (including test libraries) + - Run `go fmt` check + - Run `go vet` static analysis + - Run all tests with race detector and coverage + - Upload coverage report to Codecov + +##### 2. Lint Job +- **Environment**: Ubuntu latest +- **Steps**: + - Checkout code + - Set up Go 1.23 + - Run golangci-lint with 5-minute timeout + +##### 3. Build Job +- **Dependencies**: Requires Test and Lint jobs to pass +- **Steps**: + - Checkout code + - Set up Go 1.23 + - Build all three services (api-service, user-service, bot-service) + +##### 4. Docker Job +- **Dependencies**: Requires Test and Lint jobs to pass +- **Trigger**: Only on push to `main` branch +- **Steps**: + - Checkout code + - Set up Docker Buildx + - Build Docker images for all services + - Uses GitHub Actions cache for faster builds + +## Linter Configuration + +### golangci-lint (`.golangci.yml`) + +Enabled linters: +- `bodyclose` - Checks HTTP response body is closed +- `errcheck` - Checks for unchecked errors +- `govet` - Go vet analysis +- `ineffassign` - Detects ineffectual assignments +- `staticcheck` - Static analysis checks +- `gosec` - Security checks +- `gocritic` - Comprehensive Go code analysis +- `misspell` - Spell checking +- `dupl` - Code duplication detection +- `gocyclo` - Cyclomatic complexity +- And more... + +### Settings: +- Line length limit: 140 characters +- Minimum complexity for warning: 15 +- Timeout: 5 minutes + +## Pre-commit Hooks + +### Setup (`.pre-commit-config.yaml`) + +Install hooks: +```bash +task hooks:install +# or +make install-hooks +# or +pre-commit install +``` + +### Hooks: +1. **pre-commit/pre-commit-hooks**: + - Remove trailing whitespace + - Fix end of files + - Check YAML syntax + - Detect large files + - Check for merge conflicts + - Detect private keys + +2. **dnephin/pre-commit-golang**: + - Run `go fmt` + - Run `go vet` + - Run `go mod tidy` + - Run unit tests (with 30s timeout, short mode) + +3. **Local hooks**: + - Run full test suite with race detector + +## Running CI Checks Locally + +### Quick Check +```bash +task ci:local +``` + +This will run: +1. Code formatting (`go fmt`) +2. Linting (`go vet`, `golangci-lint`) +3. All tests with coverage + +### Individual Steps + +```bash +# Format code +task fmt + +# Run linter +task lint + +# Run tests +task test + +# Run tests with coverage +task test:coverage + +# Run only unit tests +task test:unit + +# Run only integration tests +task test:integration +``` + +### Using Makefile + +```bash +# Run linter +make lint + +# Run tests +make test + +# Run tests with coverage +make test-coverage + +# Format code +make fmt +``` + +## Environment Variables + +### Required for CI/CD: + +#### GitHub Secrets +- `CODECOV_TOKEN` - (Optional) For Codecov integration + +### Local Development +- `POSTGRES_DSN` - Database connection string +- `TELEGRAM_TOKEN` - Telegram bot token +- `HTTP_ADDR` - Service HTTP address + +## Docker Build Strategy + +### Multi-stage Builds +All services use multi-stage Docker builds: +1. **Build stage**: Use `golang:latest` with full toolchain +2. **Runtime stage**: Use `debian:bookworm-slim` for smaller images + +### Build Caching +- GitHub Actions uses build cache to speed up builds +- Cache keys based on Go modules hash + +### Building Locally + +```bash +# Build specific service +docker build -f build/api-service/Dockerfile -t api-service . +docker build -f build/user-service/Dockerfile -t user-service . +docker build -f build/bot-service/Dockerfile -t bot-service . + +# Build all services +docker-compose build + +# Build with task +task docker:build +``` + +## Branch Strategy + +### Main Branches +- `main` - Production-ready code + - Full CI pipeline + Docker builds + - Should always be stable + - Protected branch (requires PR reviews) + +- `develop` - Development branch + - Full CI pipeline + - Integration of features + - Can be unstable + +### Feature Branches +- `feature/*` - New features +- `bugfix/*` - Bug fixes +- `hotfix/*` - Urgent production fixes + +### Workflow +1. Create feature branch from `develop` +2. Make changes and commit +3. Push and create PR to `develop` +4. CI checks run automatically +5. After review and CI pass, merge to `develop` +6. Periodically merge `develop` to `main` + +## Quality Gates + +### Before Merge +- ✅ All tests pass +- ✅ No linter warnings +- ✅ Code coverage maintained +- ✅ Code review approved +- ✅ No merge conflicts + +### Pre-commit (Local) +- ✅ Code formatted +- ✅ `go vet` passes +- ✅ Unit tests pass + +## Monitoring and Alerts + +### Current Setup +- GitHub Actions notifications +- Codecov coverage reports + +### Future Enhancements +- [ ] Slack/Discord notifications +- [ ] Performance regression detection +- [ ] Security vulnerability scanning +- [ ] Dependency update notifications +- [ ] Deploy previews for frontend + +## Deployment (Planned) + +### Future CD Pipeline +1. **Staging Deployment** + - Automatic deployment on merge to `develop` + - Deploy to staging environment + - Run smoke tests + +2. **Production Deployment** + - Manual approval required + - Deploy on merge to `main` + - Blue-green deployment + - Automatic rollback on failure + +3. **Container Registry** + - Push images to Docker Hub/GitHub Container Registry + - Tag with version/commit SHA + - Keep last 10 versions + +## Troubleshooting + +### CI Fails but Local Passes + +```bash +# Clean cache +go clean -cache -testcache + +# Run with same flags as CI +go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + +# Check formatting +gofmt -s -l . + +# Run linter +golangci-lint run --timeout=5m +``` + +### Docker Build Fails + +```bash +# Clear Docker cache +docker system prune -a + +# Build without cache +docker-compose build --no-cache + +# Check Dockerfile syntax +docker build --check -f build/api-service/Dockerfile . +``` + +### Pre-commit Hooks Fail + +```bash +# Update hooks +pre-commit autoupdate + +# Run manually +pre-commit run --all-files + +# Skip hooks (emergency only) +git commit --no-verify +``` + +## Best Practices + +1. **Always run tests locally before pushing** +2. **Keep commits small and focused** +3. **Write meaningful commit messages** (use Conventional Commits) +4. **Don't skip CI checks** (only in emergencies) +5. **Monitor CI failures** and fix quickly +6. **Keep dependencies updated** regularly +7. **Add tests for bug fixes** before fixing +8. **Review CI logs** when tests fail + +## Commit Message Format + +Use Conventional Commits: + +``` +(): + + + +