internal/callback/callback.go (90 lines of code) (raw):
// Package callback provides functionality for sending operation status callbacks to GitLab.
// It implements mechanisms for reporting success or failure of operations (particularly indexing)
// back to GitLab via internal API calls. The package handles constructing appropriate payloads,
// including repository statistics when needed, and manages the HTTP communication with configurable
// timeouts and authentication.
package callback
import (
"context"
"fmt"
"log/slog"
"net/http"
"time"
internal_api "gitlab.com/gitlab-org/gitlab-zoekt-indexer/internal/api"
"gitlab.com/gitlab-org/gitlab-zoekt-indexer/internal/disk_stats"
)
type CallbackFunc struct {
OnSuccess func(CallbackParams)
OnFailure func(CallbackParams, error)
}
type CallbackAPI struct {
GitlabURL string
NodeUUID string
Secret []byte
Client *http.Client
}
type CallbackParams struct {
Name string `json:"name"`
RailsPayload any `json:"payload"`
}
// AdditionalPayload might get new fields in the future when there will be new callback actions. Right now we add only RepoStats for the index action
type AdditionalPayload struct {
RepoStats *disk_stats.RepoStats `json:"repo_stats,omitempty"`
}
type CallbackBody struct {
Name string `json:"name"`
Success bool `json:"success"`
Payload any `json:"payload"`
AdditionalPayload *AdditionalPayload `json:"additional_payload,omitempty"`
Error string `json:"error,omitempty"`
}
const (
apiPath = "callback"
timeout = time.Minute
)
func NewCallbackAPI(gitlabURL, nodeUUID string, secret []byte, client *http.Client) (CallbackAPI, error) {
return CallbackAPI{
GitlabURL: gitlabURL,
NodeUUID: nodeUUID,
Secret: secret,
Client: client,
}, nil
}
func (c CallbackAPI) SendSuccess(ctx context.Context, callbackParams CallbackParams, indexDir string, repoID uint32) {
payload := buildPayload(callbackParams, indexDir, repoID, true)
c.sendCallback(ctx, payload)
}
func (c CallbackAPI) SendFailure(ctx context.Context, callbackParams CallbackParams, indexDir string, repoID uint32, failureReason error) {
payload := buildPayload(callbackParams, indexDir, repoID, false)
payload.Error = failureReason.Error()
c.sendCallback(ctx, payload)
}
func buildPayload(callbackParams CallbackParams, indexDir string, repoID uint32, success bool) CallbackBody {
railsPayload := callbackParams.RailsPayload // Rails payload
var additionalPayload AdditionalPayload
if callbackParams.Name == "index" {
repoStats := disk_stats.GetFileSizeAndCount(indexDir, fmt.Sprintf("%d_*.zoekt", repoID))
additionalPayload.RepoStats = &repoStats
}
return CallbackBody{
Name: callbackParams.Name,
Success: success,
Payload: railsPayload,
AdditionalPayload: &additionalPayload,
}
}
func (c CallbackAPI) sendCallback(ctx context.Context, payload CallbackBody) {
apiParams := internal_api.InternalAPIRequestParams{
Path: apiPath,
BodyParams: payload,
GitlabURL: c.GitlabURL,
NodeUUID: c.NodeUUID,
Secret: c.Secret,
}
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := internal_api.NewRequest(ctx, apiParams)
slog.Debug("sending callback request", "payload", payload)
if err != nil {
slog.Error("error while creating a new request", "err", err)
return
}
_, errRequest := c.Client.Do(req) //nolint:bodyclose
if errRequest != nil {
slog.Error("error while creating a new request", "err", errRequest)
}
}