From e6a00ca9f0f5b1fb7082b7a285b2a0def9832f45 Mon Sep 17 00:00:00 2001 From: AlexBelyan Date: Tue, 19 May 2026 23:27:37 +0300 Subject: [PATCH] Fixed deploy pipeline --- .gitea/workflows/deploy.yaml | 29 ++++++-- infra/docker/application/Dockerfile | 15 ++-- infra/webhook/Dockerfile | 6 +- infra/webhook/go.mod | 4 +- infra/webhook/go.sum | 1 - infra/webhook/main.go | 110 ++++++++++++++++++++-------- 6 files changed, 108 insertions(+), 57 deletions(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 202a898..da7d988 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: https://gitea.com/actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -25,17 +25,30 @@ jobs: - name: Build and push postgres image uses: docker/build-push-action@v5 - if: | - contains(github.event.commits[0].modified, 'infra/docker/postgres-pg-cron') || - contains(github.event.commits[0].added, 'infra/docker/postgres-pg-cron') +# if: | +# contains(github.event.commits[0].modified, 'infra/docker/postgres-pg-cron') || +# contains(github.event.commits[0].added, 'infra/docker/postgres-pg-cron') with: context: . file: infra/docker/postgres-pg-cron/Dockerfile push: true tags: | ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub-postgres:latest - cache-from: type=gha - cache-to: type=gha,mode=max + ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} + cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache + cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max + + - name: Build and push webhook image + uses: docker/build-push-action@v5 + with: + context: . + file: infra/webhook/Dockerfile + push: true + tags: | + ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub-webhook:latest + ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} + cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache + cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max - name: Build and push app image uses: docker/build-push-action@v5 @@ -46,8 +59,8 @@ jobs: tags: | ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:latest ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache + cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max - name: Trigger deploy run: | diff --git a/infra/docker/application/Dockerfile b/infra/docker/application/Dockerfile index 0f62880..21f83bf 100644 --- a/infra/docker/application/Dockerfile +++ b/infra/docker/application/Dockerfile @@ -14,7 +14,7 @@ RUN npm run build # ================================ # Stage 2: сборка Go # ================================ -FROM golang:1.25-alpine AS backend +FROM golang:1.26-bookworm AS backend WORKDIR /app @@ -34,15 +34,10 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o server ./src/ # ================================ # Stage 3: финальный образ # ================================ -FROM alpine:3.19 +FROM scratch -# нужен для корректной работы TLS и временных зон -RUN apk add --no-cache ca-certificates tzdata - -WORKDIR /app - -COPY --from=backend /app/server ./server - -EXPOSE 8080 +COPY --from=backend /app/server /server +COPY --from=backend /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=backend /usr/share/zoneinfo /usr/share/zoneinfo ENTRYPOINT ["./server"] \ No newline at end of file diff --git a/infra/webhook/Dockerfile b/infra/webhook/Dockerfile index fea7be5..9f76a90 100644 --- a/infra/webhook/Dockerfile +++ b/infra/webhook/Dockerfile @@ -1,12 +1,12 @@ -FROM golang:1.25-alpine AS builder +FROM golang:1.26-bookworm AS backend WORKDIR /app COPY infra/webhook/ . RUN go mod download && \ CGO_ENABLED=0 GOOS=linux go build -o webhook . -FROM alpine:3.19 -RUN apk add --no-cache docker-cli ca-certificates +FROM scratch COPY --from=builder /app/webhook /webhook +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ EXPOSE 9001 ENTRYPOINT ["/webhook"] \ No newline at end of file diff --git a/infra/webhook/go.mod b/infra/webhook/go.mod index a3261a0..e4ca30e 100644 --- a/infra/webhook/go.mod +++ b/infra/webhook/go.mod @@ -1,5 +1,3 @@ module webhook -go 1.25 - -require github.com/docker/docker v26.1.0+incompatible \ No newline at end of file +go 1.25.0 diff --git a/infra/webhook/go.sum b/infra/webhook/go.sum index e358dc9..e69de29 100644 --- a/infra/webhook/go.sum +++ b/infra/webhook/go.sum @@ -1 +0,0 @@ -github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= diff --git a/infra/webhook/main.go b/infra/webhook/main.go index 50c05a0..4734ddb 100644 --- a/infra/webhook/main.go +++ b/infra/webhook/main.go @@ -3,79 +3,125 @@ package main import ( "context" "encoding/json" + "fmt" + "io" "log" + "net" "net/http" + "net/url" "os" - "os/exec" - - "github.com/docker/docker/client" ) +func newDockerClient() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", "/var/run/docker.sock") + }, + }, + } +} + +func getImageName(containerName string) (string, error) { + client := newDockerClient() + resp, err := client.Get("http://localhost/containers/" + containerName + "/json") + if err != nil { + return "", err + } + defer resp.Body.Close() + + var result struct { + Config struct { + Image string `json:"Image"` + } `json:"Config"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", err + } + return result.Config.Image, nil +} + +func pullImage(imageName string) error { + client := newDockerClient() + resp, err := client.Post( + "http://localhost/images/create?fromImage="+url.QueryEscape(imageName), + "application/json", + nil, + ) + if err != nil { + return err + } + defer resp.Body.Close() + // читаем до конца чтобы pull завершился полностью + io.Copy(io.Discard, resp.Body) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("pull failed with status: %d", resp.StatusCode) + } + return nil +} + +func restartContainer(containerName string) error { + client := newDockerClient() + resp, err := client.Post( + "http://localhost/containers/"+containerName+"/restart", + "application/json", + nil, + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("restart failed with status: %d", resp.StatusCode) + } + return nil +} + func main() { secret := os.Getenv("WEBHOOK_SECRET") - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - log.Fatal("failed to create docker client:", err) - } - defer cli.Close() - http.HandleFunc("/deploy", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - if r.Header.Get("X-Webhook-Secret") != secret { http.Error(w, "unauthorized", http.StatusUnauthorized) return } - // имя контейнера берём из query параметра containerName := r.URL.Query().Get("container") if containerName == "" { http.Error(w, "container parameter is required", http.StatusBadRequest) return } - ctx := context.Background() - - // получаем инфо о контейнере чтобы узнать имя образа - inspect, err := cli.ContainerInspect(ctx, containerName) + imageName, err := getImageName(containerName) if err != nil { log.Printf("failed to inspect container %s: %v", containerName, err) http.Error(w, "container not found", http.StatusNotFound) return } - - imageName := inspect.Config.Image log.Printf("Container: %s, Image: %s", containerName, imageName) - // тянем новый образ - log.Println("Pulling new image...") - pull := exec.Command("docker", "pull", imageName) - pull.Stdout = os.Stdout - pull.Stderr = os.Stderr - if err := pull.Run(); err != nil { - log.Println("pull failed:", err) + log.Printf("Pulling image %s...", imageName) + if err := pullImage(imageName); err != nil { + log.Printf("pull failed: %v", err) http.Error(w, "pull failed", http.StatusInternalServerError) return } - // перезапускаем контейнер log.Printf("Restarting container %s...", containerName) - restart := exec.Command("docker", "restart", containerName) - restart.Stdout = os.Stdout - restart.Stderr = os.Stderr - if err := restart.Run(); err != nil { - log.Println("restart failed:", err) + if err := restartContainer(containerName); err != nil { + log.Printf("restart failed: %v", err) http.Error(w, "restart failed", http.StatusInternalServerError) return } log.Printf("Deploy of %s completed", containerName) - w.WriteHeader(http.StatusOK) - + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "ok", "container": containerName,