Заставить Gitlab повторить попытку веб-хука в случае неудачи с Go - PullRequest
1 голос
/ 18 апреля 2020

Я хочу следить за всеми событиями в проекте Gitlab и сохранять их во внешнем сервисе. Для этого я использую Gitlab Webhooks. В Go я создал небольшой локальный HTTP-сервер, который прослушивает POST-файлы Gitlab и перенаправляет их во внешний сервис. Hooks содержит всю необходимую мне информацию, поэтому кажется, что эта архитектура в порядке:

Gitlab > HTTPServer > External Service.

Моя проблема в том, что когда внешний сервис не работает, я не могу заставить Gitlab повторять неудавшиеся запросы , Как указано в документации :

  • GitLab игнорирует код состояния HTTP, возвращаемый вашей конечной точкой.
  • Ваша конечная точка должна ВСЕГДА верните правильный HTTP-ответ. Если вы этого не сделаете, то GitLab сочтет, что перехват произошел сбой, и повторите попытку.

Очень удивительно, что у Gitlab нет надлежащего способа запросить повторную попытку веб-крюка. Я должен явно вернуть неверный http ответ. Более того, я не могу найти конечную точку API, чтобы вывести список всех неудачных веб-соединений и запросить повторную отправку.

Вопрос: Как явно вернуть неверный HTTP-ответ со стандартной библиотекой net / http, чтобы заставить Gitlab повторить попытку Webhooks

1 Ответ

1 голос
/ 18 апреля 2020

Как написано в комментариях, веб-крючок - это просто уведомление о том, что произошло событие, и потенциально некоторые данные отправляются, как правило, в виде JSON данных.

Вы несете ответственность за сохранение самого события и данные, которые вы хотите / должны обработать, были отправлены вместе с ним. Ниже вы найдете пример с комментариями. Обратите внимание, что это не включает добавочные откаты, но их легко добавить:

package main

import (
    "encoding/json"
    "flag"
    "io"
    "log"
    "net/http"
    "os"
    "path/filepath"

    "github.com/joncrlsn/dque"
)

var (
    bind        string
    queueDir    string
    segmentSize int
)

// You might want to add request headers and stuff
type webhookContent struct {
    Foo string
    Bar int
}

func init() {
    flag.StringVar(&bind, "bind", ":8080", "The address to bind to")
    flag.StringVar(&queueDir, "path", "./queue", "path to store the queue in")
    flag.IntVar(&segmentSize, "size", 50, "number of entries for the queue")
}

// The "webserver" component
func runserver(q *dque.DQue) {

    http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
        // A new decoder for each call, as we want to have a new LimitReader
        // for each call. This is a simple, albeit a bit crude method to prevent
        // accidental or malicious overload of your server.
        dec := json.NewDecoder(io.LimitReader(r.Body, 4096))

        defer r.Body.Close()

        c := &webhookContent{}
        if err := dec.Decode(c); err != nil {
            log.Printf("reading body: %s", err)
            http.Error(w, "internal error", http.StatusInternalServerError)
            return
        }

        // When the content is successfully decoded, we can persist it into
        // our queue.
        if err := q.Enqueue(c); err != nil {
            log.Printf("enqueueing webhook data: %s", err)
            // PROPER ERROR HANDLING IS MISSING HERE
        }
    })

    http.ListenAndServe(bind, nil)
}

func main() {
    flag.Parse()

    var (
        q   *dque.DQue
        err error
    )

    if !dirExists(queueDir) {
        if err = os.MkdirAll(queueDir, 0750); err != nil {
            log.Fatalf("creating queue dir: %s", err)
        }
    }

    if !dirExists(filepath.Join(queueDir, "webhooks")) {
        q, err = dque.New("webhooks", queueDir, segmentSize, func() interface{} { return &webhookContent{} })
    } else {
        q, err = dque.Open("webhooks", queueDir, segmentSize, func() interface{} { return &webhookContent{} })
    }

    if err != nil {
        log.Fatalf("setting up queue: %s", err)
    }

    defer q.Close()

    go runserver(q)

    var (
        // Placeholder during event loop
        i interface{}
        // Payload
        w *webhookContent
        // Did the type assertion succeed
        ok bool
    )

    for {
        // We peek only. The semantic of this is that
        // you can already access the next item in the queue
        // without removing it from the queue and "mark" it as read.
        // We use PeekBlock since we want to wait for an item in the
        // queue to be available.
        if i, err = q.PeekBlock(); err != nil {
            // If we can not peek, something is SERIOUSLY wrong.
            log.Fatalf("reading from queue: %s", err)
        }

        if w, ok = i.(*webhookContent); !ok {
            // If the type assertion fails, something is seriously wrong, too.
            log.Fatalf("reading from queue: %s", err)
        }

        if err = doSomethingUseful(w); err != nil {
            log.Printf("Something went wrong: %s", err)
            log.Println("I strongly suggest entering an incremental backoff!")
            continue
        }

        // We did something useful, so we can dequeue the item we just processed from the queue.
        q.Dequeue()
    }

}

func doSomethingUseful(w *webhookContent) error {
    log.Printf("Instead of this log message, you can do something useful with: %#v", w)
    return nil
}

func dirExists(path string) bool {
    fileInfo, err := os.Stat(path)
    if err == nil {
        return fileInfo.IsDir()
    }
    return false
}

Теперь, когда вы делаете что-то вроде:

$ curl -X POST --data '{"Foo":"Baz","Bar":42}' http://localhost:8080/webhook

, вы должны получить запись в журнале, например

2020/04/18 11:34:23 Instead of this log message, you can do something useful with: &main.webhookContent{Foo:"Baz", Bar:42}
...