package main import ( "context" "encoding/json" "fmt" "io" "log" "net" "net/http" "net/url" "os" ) 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") 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 } containerName := r.URL.Query().Get("container") if containerName == "" { http.Error(w, "container parameter is required", http.StatusBadRequest) return } 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 } log.Printf("Container: %s, Image: %s", containerName, imageName) 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) 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.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "ok", "container": containerName, "image": imageName, }) }) log.Println("Webhook server listening on :9001") log.Fatal(http.ListenAndServe(":9001", nil)) }