pr

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2026 License: GPL-3.0 Imports: 2 Imported by: 0

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

View Source
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

func IsValidTransition(from, to State) bool

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

func ValidateTransition(from, to State) error

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:

  1. Validates the transition is allowed (via IsValidTransition)
  2. Updates the State field if valid
  3. 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

func NextStates(from State) []State

NextStates returns all valid next states from a given state. Used for UI/CLI to show available state transitions.

func StateFromLabel

func StateFromLabel(label string) State

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

func (s State) IsValid() bool

IsValid checks if the state is a valid state in the state machine. StateUnknown returns false as it represents an invalid/error state.

func (State) String

func (s State) String() string

String returns the string representation of the state.

func (State) ToLabel

func (s State) ToLabel() string

ToLabel converts a state to its corresponding GitHub label name. Example: StateDiscovered -> "cd/state:discovered"

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

type TransitionError struct {
	From State
	To   State
	Err  error
}

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.

Jump to

Keyboard shortcuts

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