549 lines
13 KiB
Markdown
549 lines
13 KiB
Markdown
# Webhook Server на Go
|
|
|
|
## 1. Обзор проекта
|
|
|
|
### 1.1 Назначение
|
|
Webhook сервер на Go для автоматической сборки и деплоя Hugo сайта при изменениях в Git репозитории.
|
|
|
|
### 1.2 Ключевые функции
|
|
- Обработка webhook от Git (GitHub, GitLab, Gitea)
|
|
- Автоматическое клонирование/обновление репозитория
|
|
- Запуск Hugo сборки
|
|
- Валидация и безопасность
|
|
- Логирование и мониторинг
|
|
- Обработка ошибок и retry логика
|
|
|
|
## 2. Архитектура
|
|
|
|
### 2.1 Компонентная диаграмма
|
|
|
|
```mermaid
|
|
graph TB
|
|
A[Git Webhook] --> B[Webhook Handler]
|
|
B --> C[Validator]
|
|
C --> D[Git Client]
|
|
D --> E[Repository Manager]
|
|
E --> F[Hugo Builder]
|
|
F --> G[File System]
|
|
G --> H[Nginx Reload]
|
|
|
|
subgraph "Webhook Server"
|
|
B
|
|
C
|
|
D
|
|
E
|
|
F
|
|
end
|
|
|
|
subgraph "External"
|
|
A
|
|
G
|
|
H
|
|
end
|
|
```
|
|
|
|
### 2.2 Структура проекта
|
|
|
|
```
|
|
webhook-server/
|
|
├── cmd/
|
|
│ └── server/
|
|
│ └── main.go # Точка входа приложения
|
|
├── internal/
|
|
│ ├── handler/
|
|
│ │ ├── webhook.go # Обработчик webhook
|
|
│ │ └── health.go # Health check endpoint
|
|
│ ├── builder/
|
|
│ │ ├── hugo.go # Hugo сборка
|
|
│ │ └── git.go # Git операции
|
|
│ ├── config/
|
|
│ │ └── config.go # Конфигурация
|
|
│ ├── validator/
|
|
│ │ └── webhook.go # Валидация webhook
|
|
│ └── logger/
|
|
│ └── logger.go # Структурированное логирование
|
|
├── pkg/
|
|
│ ├── git/
|
|
│ │ └── client.go # Git клиент
|
|
│ └── metrics/
|
|
│ └── prometheus.go # Метрики Prometheus
|
|
├── configs/
|
|
│ ├── config.yaml # Конфигурация
|
|
│ └── nginx.conf # Nginx конфигурация
|
|
├── scripts/
|
|
│ ├── build.sh # Скрипт сборки
|
|
│ └── deploy.sh # Скрипт деплоя
|
|
├── go.mod
|
|
├── go.sum
|
|
├── Dockerfile
|
|
├── docker-compose.yml
|
|
└── README.md
|
|
```
|
|
|
|
## 3. Техническая реализация
|
|
|
|
### 3.1 Основные зависимости
|
|
|
|
```go
|
|
// go.mod
|
|
module webhook-server
|
|
|
|
go 1.21
|
|
|
|
require (
|
|
github.com/gin-gonic/gin v1.9.1
|
|
github.com/go-git/go-git/v5 v5.8.1
|
|
github.com/prometheus/client_golang v1.17.0
|
|
github.com/sirupsen/logrus v1.9.3
|
|
github.com/spf13/viper v1.17.0
|
|
gopkg.in/go-playground/webhooks.v5 v5.17.0
|
|
)
|
|
```
|
|
|
|
### 3.2 Конфигурация
|
|
|
|
```yaml
|
|
# configs/config.yaml
|
|
server:
|
|
port: 8080
|
|
host: "0.0.0.0"
|
|
|
|
git:
|
|
repository: "https://github.com/user/second-mind"
|
|
branch: "main"
|
|
webhook_secret: "your-webhook-secret"
|
|
ssh_key_path: "/root/.ssh/id_rsa"
|
|
|
|
hugo:
|
|
source_path: "/app/hugo-site"
|
|
output_path: "/var/www/html"
|
|
config_file: "config.toml"
|
|
base_url: "https://aepif.ru"
|
|
|
|
nginx:
|
|
config_path: "/etc/nginx/nginx.conf"
|
|
reload_command: "nginx -s reload"
|
|
|
|
logging:
|
|
level: "info"
|
|
format: "json"
|
|
|
|
metrics:
|
|
enabled: true
|
|
port: 9090
|
|
```
|
|
|
|
### 3.3 Основные компоненты
|
|
|
|
#### Webhook Handler
|
|
```go
|
|
// internal/handler/webhook.go
|
|
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type WebhookHandler struct {
|
|
gitClient *git.Client
|
|
hugoBuilder *builder.HugoBuilder
|
|
validator *validator.WebhookValidator
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
func (h *WebhookHandler) HandleWebhook(c *gin.Context) {
|
|
// Валидация webhook
|
|
if err := h.validator.Validate(c.Request); err != nil {
|
|
h.logger.WithError(err).Error("Webhook validation failed")
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Асинхронная обработка
|
|
go h.processWebhook()
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "processing"})
|
|
}
|
|
|
|
func (h *WebhookHandler) processWebhook() {
|
|
// Клонирование/обновление репозитория
|
|
if err := h.gitClient.Pull(); err != nil {
|
|
h.logger.WithError(err).Error("Git pull failed")
|
|
return
|
|
}
|
|
|
|
// Hugo сборка
|
|
if err := h.hugoBuilder.Build(); err != nil {
|
|
h.logger.WithError(err).Error("Hugo build failed")
|
|
return
|
|
}
|
|
|
|
// Перезагрузка Nginx
|
|
if err := h.reloadNginx(); err != nil {
|
|
h.logger.WithError(err).Error("Nginx reload failed")
|
|
return
|
|
}
|
|
|
|
h.logger.Info("Deployment completed successfully")
|
|
}
|
|
```
|
|
|
|
#### Hugo Builder
|
|
```go
|
|
// internal/builder/hugo.go
|
|
package builder
|
|
|
|
import (
|
|
"os/exec"
|
|
"path/filepath"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type HugoBuilder struct {
|
|
sourcePath string
|
|
outputPath string
|
|
configFile string
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
func (h *HugoBuilder) Build() error {
|
|
h.logger.Info("Starting Hugo build")
|
|
|
|
cmd := exec.Command("hugo",
|
|
"--source", h.sourcePath,
|
|
"--destination", h.outputPath,
|
|
"--config", filepath.Join(h.sourcePath, h.configFile),
|
|
"--minify",
|
|
"--cleanDestinationDir",
|
|
)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
h.logger.WithError(err).WithField("output", string(output)).Error("Hugo build failed")
|
|
return err
|
|
}
|
|
|
|
h.logger.WithField("output", string(output)).Info("Hugo build completed")
|
|
return nil
|
|
}
|
|
```
|
|
|
|
#### Git Client
|
|
```go
|
|
// pkg/git/client.go
|
|
package git
|
|
|
|
import (
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type Client struct {
|
|
repositoryPath string
|
|
remoteURL string
|
|
branch string
|
|
logger *logrus.Logger
|
|
}
|
|
|
|
func (c *Client) Pull() error {
|
|
c.logger.Info("Starting git pull")
|
|
|
|
repo, err := git.PlainOpen(c.repositoryPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
worktree, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = worktree.Pull(&git.PullOptions{
|
|
RemoteName: "origin",
|
|
BranchName: plumbing.NewBranchReferenceName(c.branch),
|
|
})
|
|
|
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
|
c.logger.WithError(err).Error("Git pull failed")
|
|
return err
|
|
}
|
|
|
|
c.logger.Info("Git pull completed")
|
|
return nil
|
|
}
|
|
```
|
|
|
|
### 3.4 Метрики и мониторинг
|
|
|
|
```go
|
|
// pkg/metrics/prometheus.go
|
|
package metrics
|
|
|
|
import (
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
|
)
|
|
|
|
var (
|
|
BuildDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
|
Name: "hugo_build_duration_seconds",
|
|
Help: "Duration of Hugo build in seconds",
|
|
})
|
|
|
|
BuildSuccess = promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "hugo_build_success_total",
|
|
Help: "Total number of successful builds",
|
|
})
|
|
|
|
BuildFailures = promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "hugo_build_failures_total",
|
|
Help: "Total number of failed builds",
|
|
})
|
|
|
|
WebhookRequests = promauto.NewCounter(prometheus.CounterOpts{
|
|
Name: "webhook_requests_total",
|
|
Help: "Total number of webhook requests",
|
|
})
|
|
)
|
|
```
|
|
|
|
## 4. Docker контейнеризация
|
|
|
|
### 4.1 Dockerfile
|
|
|
|
```dockerfile
|
|
# Многоэтапная сборка
|
|
FROM golang:1.21-alpine AS builder
|
|
|
|
# Установка зависимостей
|
|
RUN apk add --no-cache git ca-certificates tzdata
|
|
|
|
WORKDIR /app
|
|
|
|
# Копирование go.mod и go.sum
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
|
|
# Копирование исходного кода
|
|
COPY . .
|
|
|
|
# Сборка приложения
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
|
|
|
|
# Финальный образ
|
|
FROM alpine:latest
|
|
|
|
# Установка необходимых пакетов
|
|
RUN apk --no-cache add ca-certificates tzdata hugo git openssh-client nginx
|
|
|
|
# Создание пользователя
|
|
RUN addgroup -g 1000 -S appgroup && \
|
|
adduser -u 1000 -S appuser -G appgroup
|
|
|
|
WORKDIR /app
|
|
|
|
# Копирование бинарника
|
|
COPY --from=builder /app/main .
|
|
|
|
# Копирование конфигурации
|
|
COPY configs/ /app/configs/
|
|
|
|
# Создание директорий
|
|
RUN mkdir -p /var/www/html /root/.ssh && \
|
|
chown -R appuser:appgroup /app /var/www/html
|
|
|
|
# Переключение на пользователя
|
|
USER appuser
|
|
|
|
EXPOSE 8080 9090
|
|
|
|
CMD ["./main"]
|
|
```
|
|
|
|
### 4.2 Docker Compose
|
|
|
|
```yaml
|
|
# docker-compose.yml
|
|
version: '3.8'
|
|
|
|
services:
|
|
webhook-server:
|
|
build: .
|
|
ports:
|
|
- "8080:8080"
|
|
- "9090:9090"
|
|
volumes:
|
|
- ./configs:/app/configs
|
|
- /var/www/html:/var/www/html
|
|
- ~/.ssh:/root/.ssh:ro
|
|
environment:
|
|
- CONFIG_PATH=/app/configs/config.yaml
|
|
restart: unless-stopped
|
|
networks:
|
|
- webhook-network
|
|
|
|
nginx:
|
|
image: nginx:alpine
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- /var/www/html:/usr/share/nginx/html
|
|
- ./configs/nginx.conf:/etc/nginx/nginx.conf
|
|
depends_on:
|
|
- webhook-server
|
|
restart: unless-stopped
|
|
networks:
|
|
- webhook-network
|
|
|
|
networks:
|
|
webhook-network:
|
|
driver: bridge
|
|
```
|
|
|
|
## 5. Безопасность
|
|
|
|
### 5.1 Webhook валидация
|
|
- Проверка подписи webhook
|
|
- Валидация payload
|
|
- Rate limiting
|
|
- IP whitelist
|
|
|
|
### 5.2 Git безопасность
|
|
- SSH ключи для аутентификации
|
|
- Проверка подписи коммитов
|
|
- Ограничение доступа к репозиторию
|
|
|
|
### 5.3 Системная безопасность
|
|
- Запуск от непривилегированного пользователя
|
|
- Минимальные права доступа
|
|
- Регулярные обновления зависимостей
|
|
|
|
## 6. Мониторинг и логирование
|
|
|
|
### 6.1 Структурированное логирование
|
|
```go
|
|
// internal/logger/logger.go
|
|
func SetupLogger(level, format string) *logrus.Logger {
|
|
logger := logrus.New()
|
|
|
|
// Уровень логирования
|
|
if level, err := logrus.ParseLevel(level); err == nil {
|
|
logger.SetLevel(level)
|
|
}
|
|
|
|
// Формат логирования
|
|
if format == "json" {
|
|
logger.SetFormatter(&logrus.JSONFormatter{})
|
|
}
|
|
|
|
return logger
|
|
}
|
|
```
|
|
|
|
### 6.2 Health Check
|
|
```go
|
|
// internal/handler/health.go
|
|
func (h *HealthHandler) HealthCheck(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "healthy",
|
|
"timestamp": time.Now().Unix(),
|
|
"version": "1.0.0",
|
|
})
|
|
}
|
|
```
|
|
|
|
## 7. Развертывание
|
|
|
|
### 7.1 Скрипт сборки
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/build.sh
|
|
|
|
set -e
|
|
|
|
echo "Building webhook server..."
|
|
|
|
# Сборка Docker образа
|
|
docker build -t webhook-server:latest .
|
|
|
|
echo "Build completed successfully"
|
|
```
|
|
|
|
### 7.2 Скрипт деплоя
|
|
```bash
|
|
#!/bin/bash
|
|
# scripts/deploy.sh
|
|
|
|
set -e
|
|
|
|
echo "Deploying webhook server..."
|
|
|
|
# Остановка существующих контейнеров
|
|
docker-compose down
|
|
|
|
# Запуск новых контейнеров
|
|
docker-compose up -d
|
|
|
|
echo "Deployment completed successfully"
|
|
```
|
|
|
|
## 8. Тестирование
|
|
|
|
### 8.1 Unit тесты
|
|
```go
|
|
// internal/handler/webhook_test.go
|
|
func TestWebhookHandler_HandleWebhook(t *testing.T) {
|
|
// Тесты валидации webhook
|
|
// Тесты обработки ошибок
|
|
// Тесты успешной обработки
|
|
}
|
|
```
|
|
|
|
### 8.2 Integration тесты
|
|
```go
|
|
// tests/integration_test.go
|
|
func TestFullDeploymentFlow(t *testing.T) {
|
|
// Тест полного цикла деплоя
|
|
// Тест обработки ошибок
|
|
// Тест метрик
|
|
}
|
|
```
|
|
|
|
## 9. Документация API
|
|
|
|
### 9.1 Endpoints
|
|
|
|
**POST /webhook**
|
|
- Обработка webhook от Git
|
|
- Валидация подписи
|
|
- Асинхронная обработка
|
|
|
|
**GET /health**
|
|
- Health check endpoint
|
|
- Статус сервиса
|
|
- Версия приложения
|
|
|
|
**GET /metrics**
|
|
- Prometheus метрики
|
|
- Метрики сборки
|
|
- Метрики webhook
|
|
|
|
### 9.2 Примеры запросов
|
|
|
|
```bash
|
|
# Health check
|
|
curl http://localhost:8080/health
|
|
|
|
# Метрики
|
|
curl http://localhost:9090/metrics
|
|
|
|
# Webhook (пример)
|
|
curl -X POST http://localhost:8080/webhook \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-GitHub-Event: push" \
|
|
-d '{"ref":"refs/heads/main"}'
|
|
``` |