webhook

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

@index Repository and branch allowlist rules for webhook-triggered sync.

@index Authentication helpers for webhook-driven git access.

@index Clone URL resolution for webhook-triggered repository sync.

@index Repository locking and clone-or-pull operations for webhook sync.

@index HTTP webhook intake for repository sync dispatch.

@index Deduplicating work queue for webhook-driven repository sync.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrSyncQueueFull         = errors.New("sync queue full")
	ErrSyncQueueShuttingDown = errors.New("sync queue shutting down")
)
View Source
var ErrRepoLockTimeout = errors.New("repo lock timeout")

@intent signal that a per-repository lock could not be acquired within the configured wait window.

Functions

func AllowRuleOwners

func AllowRuleOwners(rules []RepoRule) []string

AllowRuleOwners returns the positive repository owner prefixes present in allow rules. @intent let server startup warn when repo-name namespace extraction is used with multi-owner webhook allowlists. @domainRule deny rules are ignored because they only narrow the positive admission surface.

func AllowRulesSpanMultipleOwners

func AllowRulesSpanMultipleOwners(rules []RepoRule) (bool, []string)

AllowRulesSpanMultipleOwners reports whether webhook rules admit repositories under more than one owner prefix. @intent identify configurations that can collide under the current repo-name namespace strategy.

func CloneOrPull

func CloneOrPull(ctx context.Context, repoURL, repoRoot, namespace string, auth transport.AuthMethod) error

CloneOrPull syncs the repository namespace to the default remote branch. @intent give webhook handlers a branch-agnostic entry point for standard repo refresh. @param auth is the git transport auth method to use when the remote requires credentials. @sideEffect may clone or hard-reset the target repository checkout on disk.

func CloneOrPullBranch

func CloneOrPullBranch(ctx context.Context, repoURL, repoRoot, namespace, branch string, auth transport.AuthMethod) error

CloneOrPullBranch ensures the namespace checkout exists and matches the requested branch head. @intent reuse the same repo sync path for first clone and subsequent updates. @sideEffect creates or hard-resets the namespace checkout on disk. @param branch is optional; when empty the repository's current HEAD branch is used during sync. @ensures the checkout at RepoDir(repoRoot, namespace) matches the requested remote branch head on success.

func CloneOrPullBranchLocked

func CloneOrPullBranchLocked(ctx context.Context, locker *RepoLocker, repoURL, repoRoot, repoFullName, namespace, branch string, auth transport.AuthMethod) error

CloneOrPullBranchLocked wraps branch sync with repository locking. @intent prevent overlapping webhook deliveries from cloning or resetting the same checkout simultaneously. @ensures branch sync runs under repository locking even when caller passes a nil locker.

func ExtractNamespace

func ExtractNamespace(repoFullName string) string

ExtractNamespace derives a filesystem-safe namespace from a repository full name. @intent keep repo-backed namespaces predictable when organizations contain nested path segments. @param repoFullName is the full repository name, typically org/repo. @return returns the repository portion after the first slash, with any remaining slashes replaced by dashes.

func GenerateAppJWT

func GenerateAppJWT(appID int64, privateKeyPEM []byte) (string, error)

GenerateAppJWT signs a short-lived GitHub App JWT from the configured private key. @intent mint the app identity token needed to exchange for installation-scoped repository access. @requires privateKeyPEM must decode to an RSA PKCS#1 private key. @ensures returns an RS256 JWT whose iss claim matches appID and whose exp claim is set to now+10 minutes. @domainRule issued-at is backdated by 60 seconds to tolerate small clock skew.

func IsNonRetryable

func IsNonRetryable(err error) bool

IsNonRetryable reports whether a sync error should bypass queue retries. @intent let retry logic stop early when a failure is known to be permanent for the current payload. @param err is the sync failure returned by the queue handler. @return returns true when err unwraps to nonRetryableError.

func NonRetryable

func NonRetryable(err error) error

@intent wrap permanent sync failures so queue retry logic can short-circuit them.

func NormalizeBranchRef

func NormalizeBranchRef(ref string) (string, bool)

NormalizeBranchRef extracts the branch name from a refs/heads/* git ref. @intent ignore tag and non-branch webhook refs before repository policy evaluation. @param ref is the raw git reference from the webhook payload. @return returns the branch name and true only for refs/heads/* values.

func RepoDir

func RepoDir(repoRoot, namespace string) string

RepoDir maps a namespace to the checkout directory used for repository sync. @intent keep namespace naming stable across clone, pull, and downstream build steps. @return returns the repository checkout path rooted under repoRoot.

func ResolveCloneURL

func ResolveCloneURL(repoFullName, payloadCloneURL string, baseURLs []string, allowPayload bool) (string, error)

ResolveCloneURL selects a safe clone target from configured base URLs or the payload. @intent keep repository sync deterministic and prevent untrusted webhook payloads from choosing arbitrary clone endpoints unless explicitly allowed.

Types

type GitAuth

type GitAuth struct {
	SSHKeyPath   string
	SSHKeyData   []byte
	SSHPassword  string
	AppID        int64
	AppKey       []byte
	InstallToken string
}

@intent bundle the supported git authentication inputs (SSH key, GitHub App credentials, install token) for webhook clone and fetch.

func (*GitAuth) Resolve

func (a *GitAuth) Resolve() (transport.AuthMethod, error)

Resolve picks the first configured git authentication strategy. @intent allow webhook sync to switch between SSH and GitHub App token auth without branching at call sites. @domainRule prefer SSHKeyPath first, then inline SSHKeyData, then InstallToken, and return nil auth if none are configured. @sideEffect reads the SSH private key file when SSHKeyPath is used. @ensures returns the first usable transport.AuthMethod for the configured credentials.

type QueueConfig

type QueueConfig struct {
	RetryConfig
	ShutdownTimeout time.Duration
	MaxTrackedRepos int
}

@intent configure queue retry policy and memory bounds when constructing a SyncQueue.

type RepoFilter

type RepoFilter struct {
	// contains filtered or unexported fields
}

RepoFilter holds the ordered rule list used to decide repo and branch admission. @intent provide a single matcher whose result depends on rule declaration order, where later matching rules override earlier ones.

func NewRepoFilter

func NewRepoFilter(patterns []string) *RepoFilter

NewRepoFilter expands simple string patterns into webhook repo rules. @intent keep CLI-style allowlist configuration compatible with the richer rule matcher. @param patterns is the ordered allow/deny rule list in compact string form. @ensures returns a filter whose rule order matches the caller input order.

func NewRepoFilterFromRules

func NewRepoFilterFromRules(rules []RepoRule) *RepoFilter

NewRepoFilterFromRules compiles repository and branch rules into a matcher. @intent centralize Atlantis-style repo filtering so webhook dispatch can make one consistent allow decision. @param rules is the ordered repo rule list whose later matches override earlier ones. @ensures returns a filter that preserves the source rule order for evaluation.

func (*RepoFilter) IsAllowed

func (f *RepoFilter) IsAllowed(repoFullName string) bool

IsAllowed reports whether a repository matches the configured allow and deny rules. @intent let callers gate repository-level sync before looking at branch-specific restrictions. @domainRule rules are evaluated in declaration order and the last matching rule wins; a later allow rule overrides an earlier deny match and vice versa. @domainRule a repository with no matching rule is denied (default-deny).

func (*RepoFilter) IsAllowedBranch

func (f *RepoFilter) IsAllowedBranch(repoFullName, branch string) bool

IsAllowedBranch reports whether a repository and branch pair should be processed. @intent combine repo allowlist and branch policy so webhook handlers can skip unsupported pushes cheaply. @domainRule with no rules configured the result is always false (default-deny). @domainRule rules are evaluated in declaration order; each later matching rule replaces the prior decision, so a negate rule clears any previous allow and a subsequent allow rule re-enables the repo. @domainRule when a non-negate rule matches but specifies no branches, the built-in defaults ("main", "master") are used.

func (*RepoFilter) IsAllowedRef

func (f *RepoFilter) IsAllowedRef(repoFullName, ref string) bool

IsAllowedRef resolves a git ref to a branch and applies branch allowlist rules. @intent reject non-branch webhook refs before they can enter the sync pipeline. @return returns false for non-branch refs and otherwise defers to branch policy evaluation.

type RepoLocker

type RepoLocker struct {
	// contains filtered or unexported fields
}

@intent keep repository-scoped git operations serialized across concurrent webhook deliveries.

func NewRepoLocker

func NewRepoLocker(timeout time.Duration) *RepoLocker

NewRepoLocker creates a per-repository lock coordinator with a bounded wait time. @intent serialize concurrent webhook sync for the same repo so git operations do not corrupt the working tree. @param timeout bounds how long callers wait before ErrRepoLockTimeout is returned. @ensures non-positive timeout falls back to 30 seconds.

func (*RepoLocker) WithLock

func (l *RepoLocker) WithLock(ctx context.Context, lockRoot, repoFullName string, fn func(context.Context) error) error

WithLock runs a sync operation while holding both in-process and filesystem repo locks. @intent coordinate webhook workers across goroutines and processes before touching a repository checkout. @sideEffect creates and removes filesystem lock files under the repo root. @param lockRoot is the filesystem root under which lock files are created. @param repoFullName is the repository key used for in-memory and filesystem lock scoping. @ensures fn runs at most once concurrently per repository across cooperating workers/processes.

type RepoRule

type RepoRule struct {
	Pattern  string
	Branches []string
}

RepoRule is the user-facing config form pairing a repo pattern with optional branch globs. @intent expose a stable shape for CLI/YAML config without leaking the internal compiled rule layout.

func ParseRepoRule

func ParseRepoRule(s string) RepoRule

ParseRepoRule decodes a repo rule string with optional comma-separated branch patterns. @intent preserve compact CLI config while still supporting per-repository branch restrictions. @param s follows pattern or pattern:branch1,branch2 syntax. @return returns a RepoRule with branch restrictions only when the colon form is present.

type RepoStats

type RepoStats struct {
	Repo            string    `json:"repo"`
	Branch          string    `json:"branch"`
	Queued          bool      `json:"queued"`
	Processing      bool      `json:"processing"`
	EnqueuedAt      time.Time `json:"enqueued_at,omitempty"`
	ProcessingAt    time.Time `json:"processing_at,omitempty"`
	LastSuccessTime time.Time `json:"last_success_time,omitempty"`
	LastErrorTime   time.Time `json:"last_error_time,omitempty"`
	LastError       string    `json:"last_error,omitempty"`
}

@intent expose per-repository queue state so operators can inspect backlog and failure hotspots.

type RetryConfig

type RetryConfig struct {
	// MaxAttempts is the total number of attempts (1 = no retry). Default: 3.
	MaxAttempts int
	// BaseDelay is the initial backoff duration. Default: 1s.
	BaseDelay time.Duration
	// MaxDelay caps the backoff duration. Default: 30s.
	MaxDelay time.Duration
}

RetryConfig configures exponential backoff retry for sync handlers. @intent tune retry backoff so transient webhook sync failures can recover without overwhelming the remote.

type SyncFunc

type SyncFunc func(ctx context.Context, repoFullName, cloneURL, branch string) error

@intent define the callback signature webhook intake invokes to trigger repository sync.

type SyncHandlerFunc

type SyncHandlerFunc func(ctx context.Context, repoFullName, cloneURL, branch string) error

@intent share the same sync callback shape between direct handler wiring and queue-based processing.

type SyncQueue

type SyncQueue struct {
	// contains filtered or unexported fields
}

@intent coordinate deduplicated per-repository sync execution across a worker pool.

func NewSyncQueue

func NewSyncQueue(workers int, handler SyncHandlerFunc) *SyncQueue

NewSyncQueue creates a queue with background workers and default lifecycle settings. @intent provide the smallest constructor for production webhook dispatch. @param workers is the number of background workers to start. @param handler processes one deduplicated repository sync payload. @sideEffect starts background worker goroutines immediately.

func NewSyncQueueWithConfig

func NewSyncQueueWithConfig(ctx context.Context, workers int, handler SyncHandlerFunc, cfg QueueConfig) *SyncQueue

NewSyncQueueWithConfig creates a deduplicating repo work queue and starts its workers. @intent coalesce bursty webhook pushes per repository while still allowing different repos to sync concurrently. @param cfg controls retry behavior and how many repositories may be tracked at once. @sideEffect starts worker goroutines and allocates queue state maps. @ensures non-positive MaxAttempts becomes 1, non-positive ShutdownTimeout becomes 30s, and non-positive MaxTrackedRepos becomes 1024.

func NewSyncQueueWithContext

func NewSyncQueueWithContext(ctx context.Context, workers int, handler SyncHandlerFunc) *SyncQueue

NewSyncQueueWithContext binds queue workers to a parent lifecycle context. @intent allow server shutdown to cancel retries and worker waits cleanly. @param ctx controls worker lifetime and retry cancellation. @sideEffect starts background worker goroutines immediately.

func NewSyncQueueWithOptions

func NewSyncQueueWithOptions(ctx context.Context, workers int, handler SyncHandlerFunc, retry RetryConfig) *SyncQueue

NewSyncQueueWithOptions applies retry tuning while keeping default queue limits. @intent expose backoff customization without forcing every caller to build a full queue config. @param retry overrides exponential backoff behavior for sync retries. @ensures queue memory bounds still use default MaxTrackedRepos when not specified.

func (*SyncQueue) Add

func (q *SyncQueue) Add(ctx context.Context, repoFullName, cloneURL, branch string) error

Add records the latest payload for a repository and enqueues it if needed. @intent collapse repeated push events into one queued sync while preserving the newest branch and clone data. @mutates SyncQueue.queue, SyncQueue.dirty, SyncQueue.payloads. @param repoFullName identifies the deduplication key for queueing. @return returns ErrSyncQueueFull when a new repository would exceed the bounded tracked-repo set and ErrSyncQueueShuttingDown after graceful shutdown begins. @domainRule repeated events for the same repository replace payload data instead of enqueueing duplicate work.

func (*SyncQueue) Shutdown

func (q *SyncQueue) Shutdown()

Shutdown stops accepting new work and waits for workers to finish or time out. @intent give the server a bounded, graceful shutdown path for in-flight webhook sync. @sideEffect flips queue shutdown state, wakes waiting workers, and may record a timeout failure.

func (*SyncQueue) Stats

func (q *SyncQueue) Stats() SyncQueueStats

Stats snapshots queue health and recent repository activity for operators. @intent expose enough queue state to diagnose backlog, failures, and hot repositories. @return returns a point-in-time snapshot without mutating queue execution state.

type SyncQueueStats

type SyncQueueStats struct {
	Queued              int           `json:"queued"`
	Dirty               int           `json:"dirty"`
	Processing          int           `json:"processing"`
	TrackedRepos        int           `json:"tracked_repos"`
	MaxTrackedRepos     int           `json:"max_tracked_repos"`
	QueueFullTotal      int64         `json:"queue_full_total"`
	FailureTotal        int64         `json:"failure_total"`
	LastError           string        `json:"last_error,omitempty"`
	LastErrorTime       time.Time     `json:"last_error_time,omitempty"`
	OldestQueuedAge     time.Duration `json:"oldest_queued_age"`
	OldestProcessingAge time.Duration `json:"oldest_processing_age"`
	LastSuccessTime     time.Time     `json:"last_success_time,omitempty"`
	Shutdown            bool          `json:"shutdown"`
	RecentRepos         []RepoStats   `json:"recent_repos,omitempty"`
}

@intent summarize queue-wide health and recent repository activity for observability.

type WebhookHandler

type WebhookHandler struct {
	// contains filtered or unexported fields
}

@intent bundle webhook validation policy and dispatch dependencies into one reusable HTTP handler.

func NewWebhookHandler

func NewWebhookHandler(secret []byte, filter *RepoFilter, onSync SyncFunc) *WebhookHandler

NewWebhookHandler wires a webhook handler from the common secret/filter/sync callback inputs. @intent keep the default construction path small while routing all configuration through the shared config builder. @param secret is the shared webhook secret used for signature validation. @param filter decides which repo and branch combinations are eligible for sync. @param onSync dispatches the validated sync request. @ensures returns a handler configured with the default secure validation path.

func NewWebhookHandlerWithConfig

func NewWebhookHandlerWithConfig(cfg WebhookHandlerConfig) *WebhookHandler

NewWebhookHandlerWithConfig assembles webhook validation and clone URL policy into one handler. @intent make webhook intake configurable without duplicating constructor logic across CLI and tests. @param cfg carries webhook secret, filtering, sync callback, and clone URL policy. @ensures returns a handler whose clone base URLs preserve config ordering with CloneBaseURL first when provided.

func NewWebhookHandlerWithOptions

func NewWebhookHandlerWithOptions(secret []byte, filter *RepoFilter, onSync SyncFunc, insecure bool) *WebhookHandler

NewWebhookHandlerWithOptions builds a handler with the legacy option-style constructor. @intent preserve older call sites while the config-based constructor owns the actual assembly logic. @param insecure allows payload delivery without signature validation when true. @ensures returns a handler configured equivalently to the legacy constructor inputs.

func (*WebhookHandler) ServeHTTP

func (h *WebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP validates a webhook push event and dispatches repository sync when it passes policy checks. @intent turn GitHub or Gitea push deliveries into safe, filtered sync requests for the build pipeline. @sideEffect reads the request body and invokes the configured sync callback. @domainRule only signed push events for allowed repository/branch pairs are dispatched. @ensures writes an HTTP status describing acceptance, rejection, or sync backpressure for the delivery.

type WebhookHandlerConfig

type WebhookHandlerConfig struct {
	Secret        []byte
	Filter        *RepoFilter
	OnSync        SyncFunc
	Insecure      bool
	CloneBaseURL  string
	CloneBaseURLs []string
}

@intent carry all constructor options for webhook validation, clone URL policy, and sync dispatch.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL