Documentation
¶
Overview ¶
Package pr provides core domain types and business logic for pull request lifecycle management. It defines the state machine that governs PR progression through discovery, qualification, merge, and deployment phases. This package is independent of external APIs and frameworks, representing pure domain logic that can be used across different layers of the application.
The state machine enforces strict transitions to ensure PRs follow the correct lifecycle:
- Discovery: PRs are discovered and labeled
- Qualification: PRs are validated against deployment criteria
- Merge: Qualified PRs are merged to the target branch
- Deployment: Merged PRs are deployed and monitored for sync status
States are persisted as GitHub labels with the format "cd/state:<state-name>". This allows the operator to be stateless while maintaining PR lifecycle state.
Package pr provides pull request domain types and state machine logic.
Index ¶
- Constants
- func IsValidTransition(from, to State) bool
- func ValidateTransition(from, to State) error
- type PullRequest
- type State
- type StateTransition
- func (st *StateTransition) Actor() string
- func (st *StateTransition) Equals(other *StateTransition) bool
- func (st *StateTransition) FromState() State
- func (st *StateTransition) GetMetadata(key string) string
- func (st *StateTransition) IsDegradationTransition() bool
- func (st *StateTransition) IsProgressTransition() bool
- func (st *StateTransition) IsRecoveryTransition() bool
- func (st *StateTransition) Metadata() map[string]string
- func (st *StateTransition) Reason() string
- func (st *StateTransition) String() string
- func (st *StateTransition) Timestamp() time.Time
- func (st *StateTransition) ToState() State
- func (st *StateTransition) WithActor(actor string) *StateTransition
- func (st *StateTransition) WithMetadata(key, value string) *StateTransition
- func (st *StateTransition) WithTimestamp(timestamp time.Time) *StateTransition
- type Transition
- type TransitionError
Constants ¶
const LabelPrefix = "cd/state:"
LabelPrefix is the namespace for all cd-operator labels to avoid conflicts. All state labels use this prefix followed by the state name.
Variables ¶
This section is empty.
Functions ¶
func IsValidTransition ¶
IsValidTransition checks if a state transition is allowed. Returns true if the transition is valid, false otherwise. Invalid source states are only allowed if transitioning to StateDiscovered (recovery).
func ValidateTransition ¶
ValidateTransition validates a proposed state transition and returns an error if invalid. This function should be called before attempting state transitions to fail fast.
Types ¶
type PullRequest ¶
type PullRequest struct {
// Number is the PR number (unique within owner/repo)
Number int
// Owner is the repository owner (organization or user)
Owner string
// Repo is the repository name
Repo string
// State is the current lifecycle state of the PR
State State
// HeadSHA is the commit SHA at the head of the PR branch
HeadSHA string
// Title is the PR title
Title string
// BaseBranch is the target branch for the PR (usually main/master)
BaseBranch string
// CreatedAt is when the PR was created
CreatedAt time.Time
// UpdatedAt is when the PR was last updated
UpdatedAt time.Time
// Mergeable indicates if GitHub considers the PR mergeable.
// nil means the mergeable state is unknown/being calculated.
// This is a pointer because GitHub's API returns null during calculation.
Mergeable *bool
// MergeableState is GitHub's detailed mergeable state.
// Possible values: "clean", "unstable", "dirty", "blocked", "behind", "unknown"
// This provides more context than the binary Mergeable field.
MergeableState string
}
PullRequest is the aggregate root representing a pull request in the cd-operator domain. It encapsulates all PR-related data and behavior, enforcing business rules and state transitions. This is a pure domain entity with no infrastructure dependencies (no GitHub, ArgoCD, or Kubernetes types).
The PullRequest aggregate enforces:
- State machine transitions (via TransitionTo)
- Immutability of core identity (Number, Owner, Repo)
- Valid state values
- Business rules for qualification, merge, and deployment
This type is designed to be persisted via repositories (ports pattern) and manipulated only through its domain methods to maintain invariants.
func NewPullRequest ¶
func NewPullRequest(number int, owner, repo string, state State, headSHA string) (*PullRequest, error)
NewPullRequest creates a new PullRequest with required fields and validates invariants. Returns an error if validation fails.
Validation rules:
- Number must be positive
- Owner and Repo must not be empty
- State must be valid (checked via State.IsValid())
- HeadSHA must not be empty
Optional fields (Title, BaseBranch, timestamps, Mergeable) can be set after construction.
func (*PullRequest) CanTransitionTo ¶
func (pr *PullRequest) CanTransitionTo(target State) bool
CanTransitionTo checks if the PR can transition to the target state. This validates the transition against the state machine rules without mutating the PR. Returns true if the transition is valid, false otherwise.
This method is useful for:
- Pre-flight checks before attempting a transition
- UI/API validation to show available actions
- Testing transition logic without side effects
func (*PullRequest) IsDeployed ¶
func (pr *PullRequest) IsDeployed() bool
IsDeployed returns true if the PR is in the StateSynced state. This indicates the PR has been merged and successfully deployed to all clusters. Note: Drifted/unhealthy states do not count as "deployed" because they indicate issues.
func (*PullRequest) IsMerged ¶
func (pr *PullRequest) IsMerged() bool
IsMerged returns true if the PR is in the StateMerged state or beyond. "Beyond" means any post-merge state: deploying, synced, drifted, unhealthy. This is useful for checking if a PR has been integrated into the base branch.
func (*PullRequest) IsQualified ¶
func (pr *PullRequest) IsQualified() bool
IsQualified returns true if the PR is in the StateQualified state. This indicates the PR has passed all qualification checks and is ready for merge.
func (*PullRequest) String ¶
func (pr *PullRequest) String() string
String returns a human-readable identifier for the PR. Format: "owner/repo#number" Example: "grhili/cd-operator#123"
This is useful for logging, debugging, and displaying PR references.
func (*PullRequest) TransitionTo ¶
func (pr *PullRequest) TransitionTo(target State) error
TransitionTo attempts to transition the PR to the target state. Returns an error if the transition is not allowed by the state machine.
This method:
- Validates the transition is allowed (via IsValidTransition)
- Updates the State field if valid
- Returns a TransitionError if invalid
The caller is responsible for persisting the state change (via repository). This keeps the domain layer independent of infrastructure concerns.
Example usage:
if err := pr.TransitionTo(StateQualified); err != nil {
return fmt.Errorf("failed to qualify PR: %w", err)
}
prRepo.Save(ctx, pr) // Persist the state change
type State ¶
type State string
State represents a PR lifecycle state in the cd-operator state machine. States are mutually exclusive - a PR can only be in one state at a time. States are persisted as GitHub labels to enable stateless operator design.
const ( // StateDiscovered indicates the PR has been discovered by the operator StateDiscovered State = "discovered" // StateQualifying indicates the PR is being validated against qualification rules StateQualifying State = "qualifying" // StateQualified indicates the PR has passed all qualification checks StateQualified State = "qualified" // StateMerging indicates the PR is being merged StateMerging State = "merging" // StateMerged indicates the PR has been successfully merged StateMerged State = "merged" // StateDeploying indicates the merged PR is being deployed to clusters StateDeploying State = "deploying" // StateSynced indicates the deployment is complete and synchronized StateSynced State = "synced" // StateFailed indicates the PR failed at some stage (qualification, merge, deployment) StateFailed State = "failed" // StateDrifted indicates the deployment has drifted from the expected state StateDrifted State = "drifted" // StateUnhealthy indicates the application is unhealthy after deployment StateUnhealthy State = "unhealthy" // StateTestTriggered indicates external tests have been triggered StateTestTriggered State = "test-triggered" // StateTestRunning indicates external tests are currently running StateTestRunning State = "test-running" // StateTestPassed indicates all external tests passed StateTestPassed State = "test-passed" // StateTestFailed indicates one or more external tests failed StateTestFailed State = "test-failed" // StatePromoting indicates promotion to next environment is in progress StatePromoting State = "promoting" // StateUnknown represents an unknown state (used for error handling) StateUnknown State = "unknown" )
func AllStates ¶
func AllStates() []State
AllStates returns all valid states in the state machine. Used for validation and testing. StateUnknown is intentionally excluded as it represents an invalid/error state.
func NextStates ¶
NextStates returns all valid next states from a given state. Used for UI/CLI to show available state transitions.
func StateFromLabel ¶
StateFromLabel extracts a state from a GitHub label name. Returns StateUnknown if the label is not a cd-operator state label. Example: "cd/state:discovered" -> StateDiscovered
func (State) IsValid ¶
IsValid checks if the state is a valid state in the state machine. StateUnknown returns false as it represents an invalid/error state.
type StateTransition ¶
type StateTransition struct {
// contains filtered or unexported fields
}
StateTransition represents a state transition that occurred during PR processing. This is a value object that captures the details of a state change for audit, logging, and debugging purposes. Unlike StateTransitionEvent (which is published to event handlers), this is used within the pipeline processing flow.
StateTransition is immutable once created and provides a historical record of how and why a PR moved between states.
func NewStateTransition ¶
func NewStateTransition(fromState, toState State, reason string) (*StateTransition, error)
NewStateTransition creates a new state transition value object. The transition is validated to ensure it represents a valid state change. Returns an error if the transition is invalid according to the state machine rules.
func NewStateTransitionUnchecked ¶
func NewStateTransitionUnchecked(fromState, toState State, reason string) *StateTransition
NewStateTransitionUnchecked creates a state transition without validation. This should only be used for recording historical transitions or when validation has already been performed. Use NewStateTransition for normal cases.
func (*StateTransition) Actor ¶
func (st *StateTransition) Actor() string
Actor returns who or what triggered the transition.
func (*StateTransition) Equals ¶
func (st *StateTransition) Equals(other *StateTransition) bool
Equals checks if two state transitions are equivalent. Compares states, reason, and actor, but not timestamp (since timestamp can vary).
func (*StateTransition) FromState ¶
func (st *StateTransition) FromState() State
FromState returns the previous state.
func (*StateTransition) GetMetadata ¶
func (st *StateTransition) GetMetadata(key string) string
GetMetadata retrieves a specific metadata value by key. Returns empty string if the key doesn't exist.
func (*StateTransition) IsDegradationTransition ¶
func (st *StateTransition) IsDegradationTransition() bool
IsDegradationTransition returns true if this transition represents a degradation from a healthy state to an unhealthy one (e.g., synced -> drifted).
func (*StateTransition) IsProgressTransition ¶
func (st *StateTransition) IsProgressTransition() bool
IsProgressTransition returns true if this transition represents forward progress in the PR lifecycle (e.g., discovered -> qualifying -> qualified -> merged -> synced). Returns false for backward transitions (e.g., synced -> drifted) or recovery transitions.
func (*StateTransition) IsRecoveryTransition ¶
func (st *StateTransition) IsRecoveryTransition() bool
IsRecoveryTransition returns true if this transition represents a recovery from a failed or degraded state (e.g., failed -> discovered, drifted -> synced).
func (*StateTransition) Metadata ¶
func (st *StateTransition) Metadata() map[string]string
Metadata returns a copy of the metadata map to maintain immutability.
func (*StateTransition) Reason ¶
func (st *StateTransition) Reason() string
Reason returns why the transition occurred.
func (*StateTransition) String ¶
func (st *StateTransition) String() string
String returns a human-readable string representation of the transition.
func (*StateTransition) Timestamp ¶
func (st *StateTransition) Timestamp() time.Time
Timestamp returns when the transition occurred.
func (*StateTransition) ToState ¶
func (st *StateTransition) ToState() State
ToState returns the new state.
func (*StateTransition) WithActor ¶
func (st *StateTransition) WithActor(actor string) *StateTransition
WithActor sets the actor who triggered the transition. Returns a new StateTransition to maintain immutability.
func (*StateTransition) WithMetadata ¶
func (st *StateTransition) WithMetadata(key, value string) *StateTransition
WithMetadata adds metadata to the transition. Returns a new StateTransition to maintain immutability.
func (*StateTransition) WithTimestamp ¶
func (st *StateTransition) WithTimestamp(timestamp time.Time) *StateTransition
WithTimestamp creates a transition with a specific timestamp. This is useful for recording historical transitions. Returns a new StateTransition.
type Transition ¶
type Transition struct {
// From is the source state (StateUnknown means any state)
From State
// To is the target state
To State
// Description explains when this transition should occur
Description string
}
Transition represents a state transition in the PR lifecycle. Each transition defines valid source states, target state, and conditions. This forms the edges of the state machine graph.
func ValidTransitions ¶
func ValidTransitions(from State) []Transition
ValidTransitions returns all valid transitions from a given state. Returns empty slice if no valid transitions exist. Includes wildcard transitions (From=StateUnknown).
type TransitionError ¶
TransitionError represents an error that occurs during state transition. It wraps the underlying error with transition context for better debugging.
func NewTransitionError ¶
func NewTransitionError(from, to State, err error) *TransitionError
NewTransitionError creates a new transition error.
func (*TransitionError) Error ¶
func (e *TransitionError) Error() string
Error implements the error interface.
func (*TransitionError) Unwrap ¶
func (e *TransitionError) Unwrap() error
Unwrap allows error wrapping for use with errors.Is and errors.As.