diff --git a/apps/workspace-engine/pkg/events/handler/deploymentversion/deploymentversion.go b/apps/workspace-engine/pkg/events/handler/deploymentversion/deploymentversion.go index 4d010c9f3..69ef2038e 100644 --- a/apps/workspace-engine/pkg/events/handler/deploymentversion/deploymentversion.go +++ b/apps/workspace-engine/pkg/events/handler/deploymentversion/deploymentversion.go @@ -40,7 +40,8 @@ func HandleDeploymentVersionCreated( } ws.ReleaseManager().ReconcileTargets(ctx, releaseTargets, - releasemanager.WithTrigger(trace.TriggerVersionCreated)) + releasemanager.WithTrigger(trace.TriggerVersionCreated), + releasemanager.WithEarliestVersionForEvaluation(deploymentVersion)) return nil } @@ -68,7 +69,8 @@ func HandleDeploymentVersionUpdated( return err } ws.ReleaseManager().ReconcileTargets(ctx, releaseTargets, - releasemanager.WithTrigger(trace.TriggerVersionCreated)) + releasemanager.WithTrigger(trace.TriggerVersionCreated), + releasemanager.WithEarliestVersionForEvaluation(deploymentVersion)) return nil } diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go index 6a2859635..4c487387c 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go @@ -56,8 +56,9 @@ func NewPlanner( type planDeploymentOptions func(*planDeploymentConfig) type planDeploymentConfig struct { - resourceRelatedEntities map[string][]*oapi.EntityRelation - recorder *trace.ReconcileTarget + resourceRelatedEntities map[string][]*oapi.EntityRelation + recorder *trace.ReconcileTarget + earliestVersionForEvaluation *oapi.DeploymentVersion } func WithResourceRelatedEntities(entities map[string][]*oapi.EntityRelation) planDeploymentOptions { @@ -72,6 +73,12 @@ func WithTraceRecorder(recorder *trace.ReconcileTarget) planDeploymentOptions { } } +func WithEarliestVersionForEvaluation(version *oapi.DeploymentVersion) planDeploymentOptions { + return func(cfg *planDeploymentConfig) { + cfg.earliestVersionForEvaluation = version + } +} + // Returns: // - *oapi.Release: The desired release to deploy // - nil: No deployable release (no versions or all blocked by policies) @@ -104,7 +111,7 @@ func (p *Planner) PlanDeployment(ctx context.Context, releaseTarget *oapi.Releas // Step 1: Get candidate versions (sorted newest to oldest) span.AddEvent("Step 1: Getting candidate versions") - candidateVersions := p.versionManager.GetCandidateVersions(ctx, releaseTarget) + candidateVersions := p.versionManager.GetCandidateVersions(ctx, releaseTarget, cfg.earliestVersionForEvaluation) span.SetAttributes(attribute.Int("candidate_versions.count", len(candidateVersions))) if len(candidateVersions) == 0 { diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go b/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go index e23a56c9c..cdaec98be 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go @@ -93,6 +93,7 @@ func (o *DeploymentOrchestrator) Reconcile( releaseTarget, deployment.WithResourceRelatedEntities(options.resourceRelationships), deployment.WithTraceRecorder(recorder), + deployment.WithEarliestVersionForEvaluation(options.earliestVersionForEvaluation), ) if err != nil { span.RecordError(err) diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/opts.go b/apps/workspace-engine/pkg/workspace/releasemanager/opts.go index 0de3b6f01..34ac9a6e1 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/opts.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/opts.go @@ -8,9 +8,10 @@ import ( // options is a unified options struct for all releasemanager methods type options struct { // ReleaseManager options - skipEligibilityCheck bool - trigger trace.TriggerReason - resourceRelationships map[string][]*oapi.EntityRelation + skipEligibilityCheck bool + trigger trace.TriggerReason + resourceRelationships map[string][]*oapi.EntityRelation + earliestVersionForEvaluation *oapi.DeploymentVersion // StateCache options bypassCache bool @@ -35,6 +36,12 @@ func WithTrigger(trigger trace.TriggerReason) Option { } } +func WithEarliestVersionForEvaluation(version *oapi.DeploymentVersion) Option { + return func(opts *options) { + opts.earliestVersionForEvaluation = version + } +} + // StateCache options func WithResourceRelationships(relationships map[string][]*oapi.EntityRelation) Option { diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/versions/manager.go b/apps/workspace-engine/pkg/workspace/releasemanager/versions/manager.go index e485accec..c07f9fcc8 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/versions/manager.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/versions/manager.go @@ -25,7 +25,7 @@ func New(store *store.Store) *Manager { // GetCandidateVersions returns all versions for a deployment, sorted newest to oldest. // The caller is responsible for filtering based on policies or other criteria. -func (m *Manager) GetCandidateVersions(ctx context.Context, releaseTarget *oapi.ReleaseTarget) []*oapi.DeploymentVersion { +func (m *Manager) GetCandidateVersions(ctx context.Context, releaseTarget *oapi.ReleaseTarget, earliestVersionForEvaluation *oapi.DeploymentVersion) []*oapi.DeploymentVersion { _, span := tracer.Start(ctx, "GetCandidateVersions", trace.WithAttributes( attribute.String("deployment.id", releaseTarget.DeploymentId), @@ -70,5 +70,17 @@ func (m *Manager) GetCandidateVersions(ctx context.Context, releaseTarget *oapi. ) } - return filtered + if earliestVersionForEvaluation == nil { + return filtered + } + + laterThanEarliestVersion := []*oapi.DeploymentVersion{} + for _, version := range filtered { + if version.CreatedAt.Before(earliestVersionForEvaluation.CreatedAt) { + break + } + + laterThanEarliestVersion = append(laterThanEarliestVersion, version) + } + return laterThanEarliestVersion } diff --git a/apps/workspace-engine/test/e2e/engine_environment_progression_soak_test.go b/apps/workspace-engine/test/e2e/engine_environment_progression_soak_test.go index 47b980772..5719739d7 100644 --- a/apps/workspace-engine/test/e2e/engine_environment_progression_soak_test.go +++ b/apps/workspace-engine/test/e2e/engine_environment_progression_soak_test.go @@ -663,9 +663,11 @@ func TestEngine_EnvironmentProgression_MultipleVersions(t *testing.T) { engine.PushEvent(ctx, handler.JobUpdate, v2StagingJob) // Trigger policy re-evaluation - engine.PushEvent(ctx, handler.DeploymentVersionUpdate, version2) time.Sleep(100 * time.Millisecond) + // send a workspace tick to trigger reconciliation + engine.PushEvent(ctx, handler.WorkspaceTick, nil) + // v2.0.0 production job should NOT be created // (even though v1.0.0 has past soak time, v2.0.0's own soak time hasn't elapsed) jobs = engine.Workspace().Jobs().Items()