Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Fixes issues from code review of resilience implementation:
- Wire saga system in main.go (SagaRepository, SagaExecutor, SagaHandler)
- Fix CompletedSteps() to include skipped steps for dependency resolution
- Fix reverse loop bug in saga compensation (use standard swap pattern)
- Add circuit breaker state change callbacks for Prometheus metrics
Phase 1 (Build Resilience):
- Add failure:retry to all component Kaniko build steps
- Add preflight registry health check before builds
- Add services-deployed sync point to decouple docs from critical path
Phase 2 (API Resilience):
- Add pipeline retry endpoint (POST /projects/{id}/pipelines/{number}/retry)
- Wire circuit breakers with metrics callbacks
- Add /health/circuits endpoint for circuit breaker status
Phase 3 (Saga Engine):
- Full domain model (Saga, SagaStep, RetryPolicy, BackoffType)
- PostgreSQL saga repository with CRUD and step management
- Saga executor with retry, compensation, skip step support
- Saga API handlers with CRUD and control operations
Phase 4 (Observability):
- Add saga metrics (total, step_duration, retry, circuit_breaker_state)
- Add logging fields (saga_id, saga_name, step_name)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
188 lines
6.5 KiB
Go
188 lines
6.5 KiB
Go
package domain
|
|
|
|
import (
|
|
"time"
|
|
)
|
|
|
|
// WebhookID is a strongly-typed identifier for webhooks.
|
|
type WebhookID string
|
|
|
|
// String returns the webhook ID as a string.
|
|
func (id WebhookID) String() string {
|
|
return string(id)
|
|
}
|
|
|
|
// WebhookEventType represents the type of event that triggers a webhook.
|
|
type WebhookEventType string
|
|
|
|
// Available webhook event types.
|
|
const (
|
|
WebhookEventCommandStarted WebhookEventType = "command.started"
|
|
WebhookEventCommandCompleted WebhookEventType = "command.completed"
|
|
WebhookEventCommandFailed WebhookEventType = "command.failed"
|
|
WebhookEventPodReady WebhookEventType = "pod.ready"
|
|
WebhookEventPodFailed WebhookEventType = "pod.failed"
|
|
|
|
// Saga events
|
|
WebhookEventSagaStarted WebhookEventType = "saga.started"
|
|
WebhookEventSagaStepStarted WebhookEventType = "saga.step.started"
|
|
WebhookEventSagaStepCompleted WebhookEventType = "saga.step.completed"
|
|
WebhookEventSagaStepFailed WebhookEventType = "saga.step.failed"
|
|
WebhookEventSagaStepRetrying WebhookEventType = "saga.step.retrying"
|
|
WebhookEventSagaCompleted WebhookEventType = "saga.completed"
|
|
WebhookEventSagaFailed WebhookEventType = "saga.failed"
|
|
WebhookEventSagaCompensating WebhookEventType = "saga.compensating"
|
|
WebhookEventSagaCompensated WebhookEventType = "saga.compensated"
|
|
)
|
|
|
|
// AllWebhookEventTypes lists all valid webhook event types.
|
|
var AllWebhookEventTypes = []WebhookEventType{
|
|
WebhookEventCommandStarted,
|
|
WebhookEventCommandCompleted,
|
|
WebhookEventCommandFailed,
|
|
WebhookEventPodReady,
|
|
WebhookEventPodFailed,
|
|
WebhookEventSagaStarted,
|
|
WebhookEventSagaStepStarted,
|
|
WebhookEventSagaStepCompleted,
|
|
WebhookEventSagaStepFailed,
|
|
WebhookEventSagaStepRetrying,
|
|
WebhookEventSagaCompleted,
|
|
WebhookEventSagaFailed,
|
|
WebhookEventSagaCompensating,
|
|
WebhookEventSagaCompensated,
|
|
}
|
|
|
|
// IsValid checks if a webhook event type is valid.
|
|
func (t WebhookEventType) IsValid() bool {
|
|
for _, valid := range AllWebhookEventTypes {
|
|
if t == valid {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// String returns the event type as a string.
|
|
func (t WebhookEventType) String() string {
|
|
return string(t)
|
|
}
|
|
|
|
// Webhook represents a webhook subscription for a project.
|
|
type Webhook struct {
|
|
ID WebhookID `json:"id"`
|
|
ProjectID string `json:"project_id"`
|
|
URL string `json:"url"`
|
|
Secret string `json:"-"` // Never expose secret in JSON responses
|
|
Events []WebhookEventType `json:"events"`
|
|
Enabled bool `json:"enabled"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// HasSecret returns true if the webhook has a signing secret configured.
|
|
func (w *Webhook) HasSecret() bool {
|
|
return w.Secret != ""
|
|
}
|
|
|
|
// SubscribesToEvent checks if the webhook subscribes to the given event type.
|
|
func (w *Webhook) SubscribesToEvent(eventType WebhookEventType) bool {
|
|
for _, e := range w.Events {
|
|
if e == eventType {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// WebhookDeliveryID is a strongly-typed identifier for webhook deliveries.
|
|
type WebhookDeliveryID string
|
|
|
|
// String returns the delivery ID as a string.
|
|
func (id WebhookDeliveryID) String() string {
|
|
return string(id)
|
|
}
|
|
|
|
// WebhookDelivery represents a single webhook delivery attempt.
|
|
type WebhookDelivery struct {
|
|
ID WebhookDeliveryID `json:"id"`
|
|
WebhookID WebhookID `json:"webhook_id"`
|
|
EventType WebhookEventType `json:"event_type"`
|
|
Payload string `json:"payload"` // JSON payload that was sent
|
|
ResponseStatus int `json:"response_status,omitempty"`
|
|
ResponseBody string `json:"response_body,omitempty"`
|
|
DeliveredAt time.Time `json:"delivered_at"`
|
|
Success bool `json:"success"`
|
|
RetryCount int `json:"retry_count"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
}
|
|
|
|
// WebhookEvent represents an event to be dispatched to webhooks.
|
|
type WebhookEvent struct {
|
|
Type WebhookEventType `json:"type"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
ProjectID string `json:"project_id"`
|
|
Data any `json:"data"`
|
|
}
|
|
|
|
// WebhookPayload is the structure sent to webhook endpoints.
|
|
type WebhookPayload struct {
|
|
ID string `json:"id"` // Unique delivery ID
|
|
Event WebhookEventType `json:"event"` // Event type
|
|
Timestamp time.Time `json:"timestamp"` // When the event occurred
|
|
ProjectID string `json:"project_id"` // Project this event relates to
|
|
Data any `json:"data"` // Event-specific data
|
|
}
|
|
|
|
// CommandEventData is the data structure for command-related webhook events.
|
|
type CommandEventData struct {
|
|
CommandID string `json:"command_id"`
|
|
CommandType CommandType `json:"command_type"`
|
|
ProjectID string `json:"project_id"`
|
|
StartedAt time.Time `json:"started_at,omitempty"`
|
|
CompletedAt time.Time `json:"completed_at,omitempty"`
|
|
ExitCode int `json:"exit_code,omitempty"`
|
|
DurationMs int64 `json:"duration_ms,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// PodEventData is the data structure for pod-related webhook events.
|
|
type PodEventData struct {
|
|
PodName string `json:"pod_name"`
|
|
ProjectID string `json:"project_id"`
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// SagaEventData is the data structure for saga-related webhook events.
|
|
type SagaEventData struct {
|
|
SagaID string `json:"saga_id"`
|
|
SagaName string `json:"saga_name"`
|
|
Status string `json:"status"`
|
|
StepName string `json:"step_name,omitempty"`
|
|
StepStatus string `json:"step_status,omitempty"`
|
|
RetryCount int `json:"retry_count,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// WebhookFilters contains filter options for listing webhook deliveries.
|
|
type WebhookDeliveryFilters struct {
|
|
EventType *WebhookEventType // Filter by event type
|
|
Success *bool // Filter by success status
|
|
Limit int // Max results (default 100)
|
|
Offset int // For pagination
|
|
}
|
|
|
|
// DefaultWebhookDeliveryFilters returns sensible defaults.
|
|
func DefaultWebhookDeliveryFilters() *WebhookDeliveryFilters {
|
|
return &WebhookDeliveryFilters{
|
|
Limit: 100,
|
|
Offset: 0,
|
|
}
|
|
}
|
|
|
|
// Webhook-related errors are defined in errors.go for centralized error definitions.
|