Documentation
¶
Overview ¶
Package gitlab implements provider.Provider for GitLab.com and self-hosted GitLab instances. See ADR-0020 for the implementation choices (hand-rolled HTTP client, bare-token webhook verification, etc.).
Index ¶
- Variables
- func LoadGlabToken(host string) (token string, err error)
- func RefreshGlabToken(host string) (token string, err error)
- type AuthMode
- type Config
- type Provider
- func (p *Provider) AuthenticatedUser(ctx context.Context) (provider.User, error)
- func (p *Provider) CloseMR(ctx context.Context, projectID string, mrIID int) error
- func (p *Provider) CreateMR(ctx context.Context, projectID string, draft provider.MRDraft) (provider.MR, error)
- func (p *Provider) DeregisterWebhook(ctx context.Context, projectID, webhookID string) error
- func (p *Provider) GetMRState(ctx context.Context, projectID string, mrIID int) (provider.MRState, error)
- func (p *Provider) IsBot(u provider.User) bool
- func (p *Provider) ListNotesSince(ctx context.Context, projectID string, mrIID int, since provider.NoteCursor) ([]provider.NotePoll, error)
- func (p *Provider) Name() string
- func (p *Provider) NormaliseEvent(headers http.Header, body []byte) (provider.Event, error)
- func (p *Provider) PostComment(ctx context.Context, projectID string, mrIID int, body string) error
- func (p *Provider) ReactToNote(ctx context.Context, projectID string, mrIID int, noteID int64, ...) error
- func (p *Provider) RegisterWebhook(ctx context.Context, projectID, callbackURL, secret string, ...) (string, error)
- func (p *Provider) ReplyToDiscussion(ctx context.Context, projectID string, mrIID int, discussionID string, ...) error
- func (p *Provider) ResolveDiscussion(ctx context.Context, projectID string, mrIID int, discussionID string) error
- func (p *Provider) RetryPipelineJob(ctx context.Context, projectID string, jobID int64) error
- func (p *Provider) UpdateMRDescription(ctx context.Context, projectID string, mrIID int, description string) error
- func (p *Provider) UpdateMRTitle(ctx context.Context, projectID string, mrIID int, title string) error
- func (p *Provider) VerifySignature(headers http.Header, _ []byte, secret string) bool
Constants ¶
This section is empty.
Variables ¶
var ErrGlabNotConfigured = errors.New("glab: no token configured for host")
ErrGlabNotConfigured signals that the glab config file is missing the requested host, or doesn't exist at all. Caller can fall through to an env-var PAT.
Functions ¶
func LoadGlabToken ¶
LoadGlabToken reads the OAuth token from the `glab` CLI's config file — the same token `glab auth status` reports. Returns it as a Bearer token (AuthMode=AuthBearer) so callers can build a Provider that piggybacks on the user's interactive `glab auth login`. ErrGlabNotConfigured if the file or the gitlab.com section is missing.
Useful for spike / personal-laptop deployments where the user has already done `glab auth login` and doesn't want to mint a separate PAT. Production deployments should still use a service-account PAT via env.
func RefreshGlabToken ¶
RefreshGlabToken forces `glab` to run its own internal access-token refresh (via the refresh token it manages, stored alongside the access token in its config file) before reading whatever token ends up on disk via LoadGlabToken.
glab's OAuth access token is short-lived and only refreshed lazily, when something actually invokes glab — reading the config file directly (LoadGlabToken alone) can return a genuinely expired access token if nothing has triggered glab's own refresh recently, even though `glab auth status` would report a healthy login the moment it's run (ADR-0065). `glab api user` is used as the poke: a real authenticated API call, confirmed (by direct testing) to trigger glab's refresh-if-needed logic before it succeeds.
The poke is best-effort: if it fails (glab not on PATH, a genuine re-login requirement, a transient network blip), this doesn't return early — LoadGlabToken still runs and its result (or lack of one) is what the caller ultimately sees, same as if RefreshGlabToken hadn't poked at all. A failed poke isn't proof the token is unusable; a successful poke doesn't guarantee LoadGlabToken succeeds either (e.g. the host section could still be missing) — this only maximises the chance the token on disk is current when read.
Types ¶
type AuthMode ¶
type AuthMode int
AuthMode picks the HTTP header GitLab uses to authenticate. PATs go in PRIVATE-TOKEN; OAuth tokens (e.g. from `glab` config) go in Authorization: Bearer.
type Config ¶
type Config struct {
BaseURL string // defaults to https://gitlab.com
// Token is a static personal/project/group access token. Required
// unless TokenSource is set. PATs don't expire on their own the way an
// OAuth access token does, so a static string is fine here.
Token string
// TokenSource, if set, takes precedence over Token and is called to
// resolve the bearer/PAT value fresh on every request instead of
// caching one at construction time. Use this for tokens that can go
// stale behind the Provider's back — e.g. `glab auth login`'s OAuth
// access token, which `glab` itself transparently refreshes in its own
// config file (see LoadGlabToken and ADR-0063): a Provider built with a
// one-time Token snapshot would keep using an expired access token
// forever, since it has no way to notice `glab` refreshed a new one.
TokenSource func() (string, error)
AuthMode AuthMode // defaults to AuthPAT (the v1 behaviour)
Timeout time.Duration // defaults to 30s per request
}
Config wires a Provider.
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider is the GitLab implementation of provider.Provider.
func New ¶
New constructs a Provider. Returns an error if neither Token nor TokenSource is set so callers fail fast at daemon start rather than discovering at first API call.
func (*Provider) AuthenticatedUser ¶
AuthenticatedUser → GET /api/v4/user.
func (*Provider) CloseMR ¶
CloseMR → PUT /api/v4/projects/:id/merge_requests/:iid with state_event=close.
func (*Provider) CreateMR ¶
func (p *Provider) CreateMR(ctx context.Context, projectID string, draft provider.MRDraft) (provider.MR, error)
CreateMR → POST /api/v4/projects/:id/merge_requests. GitLab signals Draft MRs via a "Draft: " title prefix (the modern replacement for "WIP:"); we add it here when the caller asks for one.
func (*Provider) DeregisterWebhook ¶
DeregisterWebhook → DELETE /api/v4/projects/:id/hooks/:hook_id. Idempotent (404 treated as success — already gone).
func (*Provider) GetMRState ¶
func (p *Provider) GetMRState(ctx context.Context, projectID string, mrIID int) (provider.MRState, error)
GetMRState → GET /api/v4/projects/:id/merge_requests/:iid. Returns the MR's current `state` field ("opened" | "closed" | "merged" | "locked") plus `has_conflicts`, used by the poller to detect lifecycle transitions and merge conflicts from the same request.
func (*Provider) IsBot ¶
IsBot inspects the user.bot field set by GitLab on bot accounts. Some long-lived integrations (Danger, sonar) use regular accounts; the caller can layer name-pattern matching on top if needed.
func (*Provider) ListNotesSince ¶
func (p *Provider) ListNotesSince(ctx context.Context, projectID string, mrIID int, since provider.NoteCursor) ([]provider.NotePoll, error)
ListNotesSince → GET /api/v4/projects/:id/merge_requests/:iid/discussions. Returns notes whose `id` exceeds the watermark (i.e. arrived since the last poll). The poller stores the highest id seen on AgentState.
This sources from /discussions rather than the flat /notes endpoint: GitLab's /notes response only populates discussion_id on plain top-level notes — inline diff comments (DiffNote) come back with an empty discussion_id there. /discussions nests every note (diff or plain) under its owning discussion object, whose id is authoritative for both, so a reply to an inline review comment threads instead of always falling back to a new top-level comment.
func (*Provider) NormaliseEvent ¶
NormaliseEvent decodes a GitLab webhook POST into provider.Event. Routes by the X-Gitlab-Event header; returns provider.ErrIgnore for event kinds we didn't subscribe to (a project hook fires for everything the project enables, not just our subscription).
func (*Provider) PostComment ¶
PostComment → POST /api/v4/projects/:id/merge_requests/:iid/notes.
func (*Provider) ReactToNote ¶
func (p *Provider) ReactToNote(ctx context.Context, projectID string, mrIID int, noteID int64, _, emoji string) error
ReactToNote → POST /api/v4/projects/:id/merge_requests/:iid/notes/:note_id/award_emoji. GitLab has a single notes endpoint (see streamNote), so stream is unused — kept in the signature for parity with GitHub, which needs it to pick an endpoint. See ADR-0050.
func (*Provider) RegisterWebhook ¶
func (p *Provider) RegisterWebhook(ctx context.Context, projectID, callbackURL, secret string, events []provider.EventKind) (string, error)
RegisterWebhook → POST /api/v4/projects/:id/hooks. Event flags map onto GitLab's webhook event toggles. Idempotency is the caller's job (workflow state tracks WebhookID); GitLab will happily create duplicate hooks.
func (*Provider) ReplyToDiscussion ¶
func (p *Provider) ReplyToDiscussion(ctx context.Context, projectID string, mrIID int, discussionID string, body string) error
ReplyToDiscussion → POST /api/v4/projects/:id/merge_requests/:iid/discussions/:discussion_id/notes. Posts within the existing thread rather than as a new top-level note, so the reply shows up nested under the comment it addresses.
func (*Provider) ResolveDiscussion ¶
func (p *Provider) ResolveDiscussion(ctx context.Context, projectID string, mrIID int, discussionID string) error
ResolveDiscussion → PUT /api/v4/projects/:id/merge_requests/:iid/discussions/:discussion_id?resolved=true. Marks the thread as resolved (collapsed in the UI) — called after the agent successfully pushes a change addressing a reviewer comment. Empty discussionID is a no-op so callers don't need to guard.
func (*Provider) RetryPipelineJob ¶
RetryPipelineJob → POST /api/v4/projects/:id/jobs/:job_id/retry. Used by the deterministic CI-flake-retry path; the agent isn't involved.
func (*Provider) UpdateMRDescription ¶
func (p *Provider) UpdateMRDescription(ctx context.Context, projectID string, mrIID int, description string) error
UpdateMRDescription → PUT /api/v4/projects/:id/merge_requests/:iid.
func (*Provider) UpdateMRTitle ¶
func (p *Provider) UpdateMRTitle(ctx context.Context, projectID string, mrIID int, title string) error
UpdateMRTitle → PUT /api/v4/projects/:id/merge_requests/:iid.
func (*Provider) VerifySignature ¶
VerifySignature checks the X-Gitlab-Token header against the registered secret using constant-time comparison. GitLab does *not* use HMAC — the header is the bare token. The token comparison is the only auth we have for inbound webhooks, so do it carefully. See ADR-0020.