From f537f7edd3cedfd372678f1f29dd7fb07edd22d4 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Mon, 24 Nov 2025 22:52:47 -0800 Subject: [PATCH] chore: init force deploy --- apps/workspace-engine/oapi/openapi.json | 15 +++++++++++++++ apps/workspace-engine/oapi/spec/main.jsonnet | 3 ++- .../oapi/spec/schemas/release-targets.jsonnet | 12 ++++++++++++ .../pkg/events/handler/redeploy/redeploy.go | 9 +++++++++ apps/workspace-engine/pkg/oapi/oapi.gen.go | 6 ++++++ .../releasemanager/deployment/planner.go | 16 ++++++++++++++-- .../releasemanager/deployment_orchestrator.go | 1 + .../pkg/workspace/releasemanager/manager.go | 10 ++++++++++ .../pkg/workspace/releasemanager/opts.go | 7 +++++++ 9 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 apps/workspace-engine/oapi/spec/schemas/release-targets.jsonnet diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index 071781483..f356aec4e 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -1172,6 +1172,21 @@ ], "type": "object" }, + "ReleaseTargetForceDeployEvent": { + "properties": { + "releaseTarget": { + "$ref": "#/components/schemas/ReleaseTarget" + }, + "version": { + "$ref": "#/components/schemas/DeploymentVersion" + } + }, + "required": [ + "releaseTarget", + "version" + ], + "type": "object" + }, "ReleaseTargetState": { "properties": { "currentRelease": { diff --git a/apps/workspace-engine/oapi/spec/main.jsonnet b/apps/workspace-engine/oapi/spec/main.jsonnet index c073d915f..366708507 100644 --- a/apps/workspace-engine/oapi/spec/main.jsonnet +++ b/apps/workspace-engine/oapi/spec/main.jsonnet @@ -37,6 +37,7 @@ (import 'schemas/relationship.jsonnet') + (import 'schemas/jobs.jsonnet') + (import 'schemas/deployments.jsonnet') + - (import 'schemas/verification.jsonnet'), + (import 'schemas/verification.jsonnet') + + (import 'schemas/release-targets.jsonnet'), }, } diff --git a/apps/workspace-engine/oapi/spec/schemas/release-targets.jsonnet b/apps/workspace-engine/oapi/spec/schemas/release-targets.jsonnet new file mode 100644 index 000000000..0860bd61c --- /dev/null +++ b/apps/workspace-engine/oapi/spec/schemas/release-targets.jsonnet @@ -0,0 +1,12 @@ +local openapi = import '../lib/openapi.libsonnet'; + +{ + ReleaseTargetForceDeployEvent: { + type: 'object', + required: ['releaseTarget', 'version'], + properties: { + releaseTarget: openapi.schemaRef('ReleaseTarget'), + version: openapi.schemaRef('DeploymentVersion'), + }, + }, +} diff --git a/apps/workspace-engine/pkg/events/handler/redeploy/redeploy.go b/apps/workspace-engine/pkg/events/handler/redeploy/redeploy.go index 382ad6329..2c78312cb 100644 --- a/apps/workspace-engine/pkg/events/handler/redeploy/redeploy.go +++ b/apps/workspace-engine/pkg/events/handler/redeploy/redeploy.go @@ -27,3 +27,12 @@ func HandleReleaseTargetDeploy(ctx context.Context, ws *workspace.Workspace, eve return nil } + +func HandleReleaseTargetForceDeploy(ctx context.Context, ws *workspace.Workspace, event handler.RawEvent) error { + releaseTargetForceDeployEvent := &oapi.ReleaseTargetForceDeployEvent{} + if err := json.Unmarshal(event.Data, releaseTargetForceDeployEvent); err != nil { + return err + } + + return ws.ReleaseManager().ForceDeploy(ctx, &releaseTargetForceDeployEvent.ReleaseTarget, &releaseTargetForceDeployEvent.Version) +} diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index 6d1e397ee..1cf2c53ce 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -572,6 +572,12 @@ type ReleaseTarget struct { ResourceId string `json:"resourceId"` } +// ReleaseTargetForceDeployEvent defines model for ReleaseTargetForceDeployEvent. +type ReleaseTargetForceDeployEvent struct { + ReleaseTarget ReleaseTarget `json:"releaseTarget"` + Version DeploymentVersion `json:"version"` +} + // ReleaseTargetState defines model for ReleaseTargetState. type ReleaseTargetState struct { CurrentRelease *Release `json:"currentRelease,omitempty"` diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go index f722d97cb..e3387f339 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/deployment/planner.go @@ -58,6 +58,7 @@ type planDeploymentConfig struct { resourceRelatedEntities map[string][]*oapi.EntityRelation recorder *trace.ReconcileTarget earliestVersionForEvaluation *oapi.DeploymentVersion + forceDeployVersion *oapi.DeploymentVersion } func WithResourceRelatedEntities(entities map[string][]*oapi.EntityRelation) planDeploymentOptions { @@ -78,6 +79,12 @@ func WithVersionAndNewer(version *oapi.DeploymentVersion) planDeploymentOptions } } +func WithForceDeployVersion(version *oapi.DeploymentVersion) planDeploymentOptions { + return func(cfg *planDeploymentConfig) { + cfg.forceDeployVersion = version + } +} + // Returns: // - *oapi.Release: The desired release to deploy // - nil: No deployable release (no versions or all blocked by policies) @@ -126,9 +133,14 @@ func (p *Planner) PlanDeployment(ctx context.Context, releaseTarget *oapi.Releas return nil, nil } + deployableVersion := cfg.forceDeployVersion + // Step 2: Find first version that passes user-defined policies - span.AddEvent("Step 2: Finding deployable version") - deployableVersion := p.findDeployableVersion(ctx, candidateVersions, releaseTarget, planning) + if deployableVersion == nil { + span.AddEvent("Step 2: Finding deployable version") + deployableVersion = p.findDeployableVersion(ctx, candidateVersions, releaseTarget, planning) + } + if deployableVersion == nil { span.AddEvent("No deployable version found (blocked by policies)") span.SetAttributes( diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go b/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go index e0779ca85..6578e8b6c 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/deployment_orchestrator.go @@ -94,6 +94,7 @@ func (o *DeploymentOrchestrator) Reconcile( deployment.WithResourceRelatedEntities(options.resourceRelationships), deployment.WithTraceRecorder(recorder), deployment.WithVersionAndNewer(options.earliestVersionForEvaluation), + deployment.WithForceDeployVersion(options.forceDeployVersion), ) if err != nil { span.RecordError(err) diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/manager.go b/apps/workspace-engine/pkg/workspace/releasemanager/manager.go index 05670ab60..70905c719 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/manager.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/manager.go @@ -285,6 +285,16 @@ func (m *Manager) Redeploy(ctx context.Context, releaseTarget *oapi.ReleaseTarge WithTrigger(trace.TriggerManual)) } +func (m *Manager) ForceDeploy(ctx context.Context, releaseTarget *oapi.ReleaseTarget, version *oapi.DeploymentVersion) error { + ctx, span := tracer.Start(ctx, "ForceDeploy") + defer span.End() + + return m.ReconcileTarget(ctx, releaseTarget, + WithSkipEligibilityCheck(true), + WithTrigger(trace.TriggerManual), + WithForceDeployVersion(version)) +} + // reconcileTargetWithRelationships is like ReconcileTarget but accepts pre-computed resource relationships. // This is an optimization to avoid recomputing relationships for multiple release targets that share the same resource. // After reconciliation completes, it caches the computed state for use by other APIs. diff --git a/apps/workspace-engine/pkg/workspace/releasemanager/opts.go b/apps/workspace-engine/pkg/workspace/releasemanager/opts.go index 19e4af215..7027160e8 100644 --- a/apps/workspace-engine/pkg/workspace/releasemanager/opts.go +++ b/apps/workspace-engine/pkg/workspace/releasemanager/opts.go @@ -12,6 +12,7 @@ type options struct { trigger trace.TriggerReason resourceRelationships map[string][]*oapi.EntityRelation earliestVersionForEvaluation *oapi.DeploymentVersion + forceDeployVersion *oapi.DeploymentVersion // StateCache options bypassCache bool @@ -42,6 +43,12 @@ func WithVersionAndNewer(version *oapi.DeploymentVersion) Option { } } +func WithForceDeployVersion(version *oapi.DeploymentVersion) Option { + return func(opts *options) { + opts.forceDeployVersion = version + } +} + // StateCache options func WithResourceRelationships(relationships map[string][]*oapi.EntityRelation) Option {