package rollouts import ( "context" "errors" "time" ) var ( ErrInvalidGateState = errors.New("invalid gate state") ) type GateState bool const ( Open GateState = true Closed GateState = false ) type GateApproval string const ( GateApproved GateApproval = "approved" GatePending GateApproval = "pending" GateDenied GateApproval = "denied" ) // RolloutGate is a ... type RolloutGate interface { IsOpen() bool } var _ RolloutGate = &TimerGate{} var _ RolloutGate = &ApprovalGate{} var _ RolloutGate = &ScheduleGate{} // TimerGate is a rollout graph gate that pauses execution for a specified duration type TimerGate struct { BaseNode `json:",inline"` // StartTime is the time that graph execution began. Delay is added as an offset and compared to the current time to // determine the state of the gate, ie open or closed StartTime time.Time `json:"startTime"` // TODO(dk185217): figure out and document, test, what unit (ns, ms, etc) of time this needs to be // Delay is amount of time that the TimerGate should pause execution for Delay time.Duration `json:"delayMinutes"` // time is an injectable implementation for getting the current time. Mostly for testability, a default // implementation that wraps time.Now() will be used if it is not overwritten time TimeSource } func NewTimerGate(key NodeKey, start time.Time, delay time.Duration, ts TimeSource) *TimerGate { return &TimerGate{ BaseNode: BaseNode{Key: key, Label: "TimerGate", State: Pending}, StartTime: start, Delay: delay, time: ts, } } // Validate ... func (g *TimerGate) Validate() error { if g.GetKey() == "" { return ErrInvalidGateState } return nil } // IsOpen returns true if ... func (g *TimerGate) IsOpen() bool { now := g.time.Now() openAt := g.StartTime.Add(g.Delay) isTimeElapsed := now.After(openAt) return isTimeElapsed } // ScheduleGate is a rollout graph gate that pauses execution until a specified time type ScheduleGate struct { BaseNode `json:",inline"` // ScheduleTime is the time that the gate should open. Ie, the time until which execution is paused ScheduleTime time.Time // time is an injectable implementation for getting the current time. Mostly for testability, a default // implementation that wraps time.Now() will be used if it is not overwritten time TimeSource } func NewScheduleGate(key NodeKey, schedule time.Time, ts TimeSource) *ScheduleGate { return &ScheduleGate{ BaseNode: BaseNode{Key: key, Label: "ScheduleGate", State: Pending}, ScheduleTime: schedule, time: ts, } } // IsOpen returns true if ... func (g *ScheduleGate) IsOpen() bool { return time.Now().After(g.ScheduleTime) } // ApprovalGate ... type ApprovalGate struct { BaseNode `json:",inline"` GateState `json:"gate_state"` // approvalChecker ApprovalSource } // TODO(dk185217): Define approval inputs: // - what principal (user/group/role?) is required to approve // - any extra config: how many approvers? other config? type ApprovalSource func(context.Context) (bool, error) func NewApprovalGate(key NodeKey) *ApprovalGate { return &ApprovalGate{ BaseNode: BaseNode{Key: key, Label: "ApprovalGate", State: Pending}, GateState: Closed, } } func (g *ApprovalGate) IsOpen() bool { return bool(g.GateState) }