Documentation
¶
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ControlFlowStageReconciler ¶
type ControlFlowStageReconciler struct {
// contains filtered or unexported fields
}
func NewControlFlowStageReconciler ¶
func NewControlFlowStageReconciler( cfg ReconcilerConfig, ) *ControlFlowStageReconciler
NewControlFlowStageReconciler returns a new control flow Stage reconciler. After creating the reconciler, call SetupWithManager to register it with a controller manager.
func (*ControlFlowStageReconciler) Reconcile ¶
func (r *ControlFlowStageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)
Reconcile reconciles the given control flow Stage.
func (*ControlFlowStageReconciler) SetupWithManager ¶
func (r *ControlFlowStageReconciler) SetupWithManager( ctx context.Context, mgr ctrl.Manager, sharedIndexer client.FieldIndexer, ) error
SetupWithManager sets up the control flow Stage reconciler with the given controller manager. It registers the reconciler with the manager and sets up watches on the required objects.
type FleetStageReconciler ¶
type FleetStageReconciler struct {
// contains filtered or unexported fields
}
func NewFleetStageReconciler ¶
func NewFleetStageReconciler( cfg ReconcilerConfig, credentialsDB credentials.Database, ) *FleetStageReconciler
NewFleetStageReconciler creates a new Stages reconciler.
func (*FleetStageReconciler) SetupWithManager ¶
func (r *FleetStageReconciler) SetupWithManager( ctx context.Context, kargoMgr ctrl.Manager, _ ctrl.Manager, sharedIndexer client.FieldIndexer, ) error
SetupWithManager sets up the Stage reconciler with the given controller manager. It registers the reconciler with the manager and sets up watches on the required objects.
Example ¶
The reconciler as it is today: every source is a Kubernetes kind.
What changes against the controller-runtime version:
- For(&Stage{}) plus three WithEventFilter calls become one kube.Kind on Stages carrying the same predicates. WithEventFilter only ever applied to the For source, so nothing is lost by attaching them there.
- Each c.Watch(source.Kind(...)) becomes a kube.Kind(...) in Sources with the arguments unchanged. The handlers in event_handlers.go already implement controller-runtime's typed handler interface.
- The controller is built and handed to the manager, and Reconcile returns reconciler.Result. The adapter below exists only so this example compiles against today's signature.
var (
ctx context.Context
kargoMgr ctrl.Manager
sharedIndexer client.FieldIndexer
cfg ReconcilerConfig
credentialsDB credentials.Database
)
r := NewFleetStageReconciler(cfg, credentialsDB)
r.client = kargoMgr.GetClient()
logger := logging.LoggerFromContext(ctx)
// Indexes are set up exactly as today; one is shown.
if err := sharedIndexer.IndexField(
ctx,
&kargoapi.PromotionRequest{},
indexer.PromotionRequestsByStageField,
indexer.PromotionRequestsByStage,
); err != nil {
return
}
sources := []reconciler.Source[reconcile.Request]{
// The primary kind. This is For(&Stage{}) and the WithEventFilter
// predicates, which only ever applied to it.
kube.Kind(
kargoMgr.GetCache(),
client.Object(&kargoapi.Stage{}),
&handler.EnqueueRequestForObject{},
controller.ResponsibleFor[client.Object]{
IsDefaultController: cfg.IsDefaultController,
ShardName: cfg.ShardName,
},
intpredicate.IgnoreDelete[client.Object]{},
predicate.And(
IsTargetAwareStage(true),
IsControlFlowStage(false),
predicate.Or(
predicate.GenerationChangedPredicate{},
kargo.RefreshRequested{},
kargo.ReverifyRequested{},
kargo.VerificationAbortRequested{},
),
),
),
// PromotionRequests whose phase changed, mapped to the owning Stage.
kube.Kind(
kargoMgr.GetCache(),
&kargoapi.PromotionRequest{},
handler.TypedEnqueueRequestForOwner[*kargoapi.PromotionRequest](
kargoMgr.GetScheme(),
kargoMgr.GetRESTMapper(),
&kargoapi.Stage{},
handler.OnlyControllerOwner(),
),
kargo.NewPromotionRequestPhaseChangedPredicate(logger),
),
// Freight, three ways, each with the handler it has today.
kube.Kind(
kargoMgr.GetCache(),
&kargoapi.Freight{},
&downstreamStageEnqueuer[*kargoapi.Freight]{kargoClient: kargoMgr.GetClient()},
),
kube.Kind(
kargoMgr.GetCache(),
&kargoapi.Freight{},
&stageEnqueuerForApprovedFreight[*kargoapi.Freight]{kargoClient: kargoMgr.GetClient()},
),
kube.Kind(
kargoMgr.GetCache(),
&kargoapi.Freight{},
&warehouseStageEnqueuer[*kargoapi.Freight]{kargoClient: kargoMgr.GetClient()},
),
}
if cfg.RolloutsIntegrationEnabled {
sources = append(sources, kube.Kind(
kargoMgr.GetCache(),
&rolloutsapi.AnalysisRun{},
&stageEnqueuerForAnalysisRuns[*rolloutsapi.AnalysisRun]{kargoClient: kargoMgr.GetClient()},
))
}
b := reconciler.New[reconcile.Request]("fleet_stage").Workers(cfg.MaxConcurrentFleetReconciles)
for _, src := range sources {
b = b.Watch(src)
}
c, err := b.Func(func(ctx context.Context, req reconcile.Request) (reconciler.Result, error) {
// In a real port Reconcile returns reconciler.Result itself and this
// adapter disappears; the body of Reconcile does not change.
res, err := r.Reconcile(ctx, req)
return reconciler.Result{RequeueAfter: res.RequeueAfter}, err
})
if err != nil {
return
}
_ = kargoMgr.Add(kube.Runnable(c))
Example (DatabaseRequests) ¶
The same reconciler once PromotionRequests live in the database.
Only the PromotionRequest source changes. The kube.Kind on PromotionRequests is replaced by a NATS subject carrying the rows the API server and the request reconciler commit, mapped to the Stage each one belongs to, with the phase-changed predicate expressed over rows. A periodic list of Stages with open requests covers lost events. The Stage source, the Freight sources, the indexes on Freight, and Reconcile are untouched. Inside Reconcile, syncPromotionRequests reads the requests from the store instead of the cache, which is a change to that function, not to the controller.
var (
kargoMgr ctrl.Manager
cfg ReconcilerConfig
conn *natsgo.Conn
// listStagesWithOpenRequests returns every Stage with a non-terminal
// PromotionRequest, from the store.
listStagesWithOpenRequests func(context.Context) ([]reconcile.Request, error)
stageSource reconciler.Source[reconcile.Request]
freightSources []reconciler.Source[reconcile.Request]
reconcileFn reconciler.Func[reconcile.Request]
)
// Declared once beside the store in the real code.
requestEvents := nats.NewTopic[uuid.UUID, promotionRequestRow]("kargo.promotionrequests")
sources := []reconciler.Source[reconcile.Request]{
// Event sources first, so they are subscribed while the resync lists.
nats.Mapped(
conn,
requestEvents,
func(_ context.Context, e nats.Event[uuid.UUID, promotionRequestRow]) []reconcile.Request {
row := e.New
if row == nil {
row = e.Old
}
return []reconcile.Request{{
NamespacedName: client.ObjectKey{Namespace: row.Project, Name: row.Stage},
}}
},
).
// The counterpart of kargo.PromotionRequestPhaseChanged.
OnUpdate(func(old, updated promotionRequestRow) bool { return old.Phase != updated.Phase }),
stageSource,
}
sources = append(sources, freightSources...)
// Stages with requests in flight, in case an event was lost.
sources = append(sources, list.New(listStagesWithOpenRequests).Every(2*time.Minute))
b := reconciler.New[reconcile.Request]("fleet_stage").Workers(cfg.MaxConcurrentFleetReconciles)
for _, src := range sources {
b = b.Watch(src)
}
c, err := b.Func(reconcileFn)
if err != nil {
return
}
_ = kargoMgr.Add(kube.Runnable(c))
Example (TargetEvents) ¶
Listening to Targets from the fleet Stage reconciler.
Targets are database-native, so there is no Kubernetes kind to watch. The API server publishes a Target event on every committed write, and the Stage controller maps each event to the Stages that govern the Target: the target-aware Stages in the Target's Project whose selectors match its labels. Both the old and the new labels are checked, because a label change can move a Target from one Stage to another and both need to re-render. Updates that touch neither labels nor params are dropped, so a status write to the Target does not wake every Stage that governs it.
var (
kargoMgr ctrl.Manager
conn *natsgo.Conn
)
// Declared once beside the store in the real code.
targetEvents := nats.NewTopic[targetKey, targetRow]("kargo.targets")
c := kargoMgr.GetClient()
targetsToStages := nats.Mapped(
conn,
targetEvents,
func(ctx context.Context, e nats.Event[targetKey, targetRow]) []reconcile.Request {
stages := &kargoapi.StageList{}
if err := c.List(ctx, stages, client.InNamespace(e.Key.Project)); err != nil {
logging.LoggerFromContext(ctx).Error(err, "error listing Stages for Target event")
return nil
}
var reqs []reconcile.Request
for i := range stages.Items {
stage := &stages.Items[i]
selectors, err := api.TargetSelectorsForStage(stage)
if err != nil || len(selectors) == 0 {
continue
}
governedBefore := e.Old != nil && api.AnySelectorMatches(selectors, e.Old.Labels)
governedAfter := e.New != nil && api.AnySelectorMatches(selectors, e.New.Labels)
if governedBefore || governedAfter {
reqs = append(reqs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(stage)})
}
}
return reqs
},
).OnUpdate(func(old, updated targetRow) bool {
return !maps.Equal(old.Labels, updated.Labels) || !bytes.Equal(old.Params, updated.Params)
})
// Added to the fleet Stage controller alongside its Kubernetes sources.
_ = reconciler.New[reconcile.Request]("fleet_stage").Watch(targetsToStages)
type IsControlFlowStage ¶
type IsControlFlowStage bool
IsControlFlowStage is a predicate that filters out Stages based on whether they are control flow Stages or not.
func (IsControlFlowStage) Create ¶
func (s IsControlFlowStage) Create(e event.CreateEvent) bool
func (IsControlFlowStage) Delete ¶
func (s IsControlFlowStage) Delete(event.DeleteEvent) bool
func (IsControlFlowStage) Generic ¶
func (s IsControlFlowStage) Generic(event.GenericEvent) bool
func (IsControlFlowStage) Update ¶
func (s IsControlFlowStage) Update(e event.UpdateEvent) bool
type IsTargetAwareStage ¶
type IsTargetAwareStage bool
IsTargetAwareStage is a predicate that filters out Stages based on whether they have targets configured (fleet stages).
func (IsTargetAwareStage) Create ¶
func (s IsTargetAwareStage) Create(e event.CreateEvent) bool
func (IsTargetAwareStage) Delete ¶
func (s IsTargetAwareStage) Delete(event.DeleteEvent) bool
func (IsTargetAwareStage) Generic ¶
func (s IsTargetAwareStage) Generic(event.GenericEvent) bool
func (IsTargetAwareStage) Update ¶
func (s IsTargetAwareStage) Update(e event.UpdateEvent) bool
type ReconcilerConfig ¶
type ReconcilerConfig struct {
IsDefaultController bool `envconfig:"IS_DEFAULT_CONTROLLER"`
ShardName string `envconfig:"SHARD_NAME"`
RolloutsIntegrationEnabled bool `envconfig:"ROLLOUTS_INTEGRATION_ENABLED"`
RolloutsControllerInstanceID string `envconfig:"ROLLOUTS_CONTROLLER_INSTANCE_ID"`
MaxConcurrentControlFlowReconciles int `envconfig:"MAX_CONCURRENT_CONTROL_FLOW_RECONCILES" default:"4"`
MaxConcurrentReconciles int `envconfig:"MAX_CONCURRENT_STAGE_RECONCILES" default:"4"`
MaxConcurrentFleetReconciles int `envconfig:"MAX_CONCURRENT_FLEET_STAGE_RECONCILES" default:"4"`
}
ReconcilerConfig represents configuration for the stage reconciler.
func ReconcilerConfigFromEnv ¶
func ReconcilerConfigFromEnv() ReconcilerConfig
ReconcilerConfigFromEnv returns a new ReconcilerConfig populated from the environment variables.
func (ReconcilerConfig) Name ¶
func (c ReconcilerConfig) Name() string
Name returns the name of the Stage controller.
type RegularStageReconciler ¶
type RegularStageReconciler struct {
// contains filtered or unexported fields
}
func NewRegularStageReconciler ¶
func NewRegularStageReconciler( cfg ReconcilerConfig, credentialsDB credentials.Database, healthChecker health.AggregatingChecker, ) *RegularStageReconciler
NewRegularStageReconciler creates a new Stages reconciler.
func (*RegularStageReconciler) SetupWithManager ¶
func (r *RegularStageReconciler) SetupWithManager( ctx context.Context, kargoMgr, argocdMgr ctrl.Manager, sharedIndexer client.FieldIndexer, ) error
SetupWithManager sets up the Stage reconciler with the given controller manager. It registers the reconciler with the manager and sets up watches on the required objects.