Skip to content

Commit 66f7186

Browse files
chore: dependencies trigger downstream target reconciles (#731)
1 parent 8f556d2 commit 66f7186

File tree

6 files changed

+94
-103
lines changed

6 files changed

+94
-103
lines changed

apps/workspace-engine/pkg/workspace/releasemanager/action/orchestrator.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,6 @@ func (o *Orchestrator) OnJobStatusChange(
7676
return nil // Don't fail job update on policy lookup failure
7777
}
7878

79-
if len(policies) == 0 {
80-
span.SetAttributes(attribute.Int("policy_count", 0))
81-
return nil // No policies apply
82-
}
83-
8479
span.SetAttributes(attribute.Int("policy_count", len(policies)))
8580

8681
// Build action context with pre-fetched policies for efficiency and consistency

apps/workspace-engine/pkg/workspace/releasemanager/action/orchestrator_test.go

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -349,97 +349,6 @@ func TestOrchestrator_OnJobStatusChange_ReleaseNotFound(t *testing.T) {
349349
assert.False(t, mockAct.executeCalled)
350350
}
351351

352-
func TestOrchestrator_OnJobStatusChange_NoPolicies(t *testing.T) {
353-
ctx := context.Background()
354-
s := newTestStore()
355-
orchestrator := action.NewOrchestrator(s)
356-
357-
mockAct := &mockAction{
358-
name: "test-action",
359-
shouldExecute: true,
360-
}
361-
orchestrator.RegisterAction(mockAct)
362-
363-
// Create release but don't create matching policy
364-
systemId := uuid.New().String()
365-
system := &oapi.System{
366-
Id: systemId,
367-
Name: "test-system",
368-
}
369-
_ = s.Systems.Upsert(ctx, system)
370-
371-
resourceId := uuid.New().String()
372-
resource := &oapi.Resource{
373-
Id: resourceId,
374-
Name: "test-resource",
375-
Kind: "kubernetes",
376-
Identifier: "test-res-1",
377-
CreatedAt: time.Now(),
378-
}
379-
_, _ = s.Resources.Upsert(ctx, resource)
380-
381-
environmentId := uuid.New().String()
382-
environment := &oapi.Environment{
383-
Id: environmentId,
384-
Name: "test-env",
385-
SystemId: systemId,
386-
}
387-
selector := &oapi.Selector{}
388-
_ = selector.FromCelSelector(oapi.CelSelector{Cel: "true"})
389-
environment.ResourceSelector = selector
390-
_ = s.Environments.Upsert(ctx, environment)
391-
392-
deploymentId := uuid.New().String()
393-
deployment := &oapi.Deployment{
394-
Id: deploymentId,
395-
Name: "test-deployment",
396-
Slug: "test-deployment",
397-
SystemId: systemId,
398-
}
399-
deploymentSelector := &oapi.Selector{}
400-
_ = deploymentSelector.FromCelSelector(oapi.CelSelector{Cel: "true"})
401-
deployment.ResourceSelector = deploymentSelector
402-
_ = s.Deployments.Upsert(ctx, deployment)
403-
404-
versionId := uuid.New().String()
405-
version := &oapi.DeploymentVersion{
406-
Id: versionId,
407-
Tag: "v1.0.0",
408-
DeploymentId: deploymentId,
409-
CreatedAt: time.Now(),
410-
}
411-
s.DeploymentVersions.Upsert(ctx, versionId, version)
412-
413-
releaseTarget := &oapi.ReleaseTarget{
414-
ResourceId: resourceId,
415-
EnvironmentId: environmentId,
416-
DeploymentId: deploymentId,
417-
}
418-
_ = s.ReleaseTargets.Upsert(ctx, releaseTarget)
419-
420-
release := &oapi.Release{
421-
ReleaseTarget: *releaseTarget,
422-
Version: *version,
423-
Variables: map[string]oapi.LiteralValue{},
424-
}
425-
_ = s.Releases.Upsert(ctx, release)
426-
427-
// No policy created, so no policies should apply
428-
429-
job := &oapi.Job{
430-
Id: uuid.New().String(),
431-
ReleaseId: release.ID(),
432-
Status: oapi.JobStatusSuccessful,
433-
CreatedAt: time.Now(),
434-
}
435-
436-
err := orchestrator.OnJobStatusChange(ctx, job, oapi.JobStatusPending)
437-
require.NoError(t, err)
438-
439-
// Action should not be executed because no policies apply
440-
assert.False(t, mockAct.executeCalled)
441-
}
442-
443352
func TestOrchestrator_OnJobStatusChange_ActionError(t *testing.T) {
444353
ctx := context.Background()
445354
s := newTestStore()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package deploymentdependency
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"workspace-engine/pkg/oapi"
7+
"workspace-engine/pkg/workspace/releasemanager/action"
8+
"workspace-engine/pkg/workspace/store"
9+
)
10+
11+
type ReconcileFn func(ctx context.Context, target *oapi.ReleaseTarget) error
12+
13+
type DeploymentDependencyAction struct {
14+
store *store.Store
15+
reconcileFn ReconcileFn
16+
}
17+
18+
func NewDeploymentDependencyAction(store *store.Store, reconcileFn ReconcileFn) *DeploymentDependencyAction {
19+
return &DeploymentDependencyAction{
20+
store: store,
21+
reconcileFn: reconcileFn,
22+
}
23+
}
24+
25+
func (d *DeploymentDependencyAction) Name() string {
26+
return "deploymentdependency"
27+
}
28+
29+
func (d *DeploymentDependencyAction) Execute(ctx context.Context, trigger action.ActionTrigger, context action.ActionContext) error {
30+
if trigger != action.TriggerJobSuccess {
31+
return nil
32+
}
33+
34+
resourceId := context.Release.ReleaseTarget.ResourceId
35+
36+
resourceTargets := d.store.ReleaseTargets.GetForResource(ctx, resourceId)
37+
38+
targetsToReconcile := make([]*oapi.ReleaseTarget, 0)
39+
40+
for _, target := range resourceTargets {
41+
if target.Key() == context.Release.ReleaseTarget.Key() {
42+
continue
43+
}
44+
45+
policies, err := d.store.ReleaseTargets.GetPolicies(ctx, target)
46+
if err != nil {
47+
return fmt.Errorf("failed to get policies for release target: %s", target.Key())
48+
}
49+
50+
var hasDeploymentDependencyRule bool
51+
for _, policy := range policies {
52+
for _, rule := range policy.Rules {
53+
if rule.DeploymentDependency != nil {
54+
targetsToReconcile = append(targetsToReconcile, target)
55+
hasDeploymentDependencyRule = true
56+
break
57+
}
58+
}
59+
60+
if hasDeploymentDependencyRule {
61+
break
62+
}
63+
}
64+
}
65+
66+
if len(targetsToReconcile) == 0 {
67+
return nil
68+
}
69+
70+
for _, target := range targetsToReconcile {
71+
if err := d.reconcileFn(ctx, target); err != nil {
72+
return fmt.Errorf("failed to reconcile target: %s", target.Key())
73+
}
74+
}
75+
76+
return nil
77+
}

apps/workspace-engine/pkg/workspace/releasemanager/trace/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const (
9191
TriggerPolicyUpdated TriggerReason = "policy.updated" // Policy configuration changed
9292
TriggerVariablesUpdated TriggerReason = "variables.updated" // Deployment or resource variables changed
9393
TriggerJobAgentUpdated TriggerReason = "jobagent.updated" // Job agent configuration changed
94+
TriggerJobSuccess TriggerReason = "job.success" // Job was successful
9495
TriggerManual TriggerReason = "manual" // Manually triggered (e.g., force redeploy)
9596
TriggerFirstBoot TriggerReason = "first_boot" // Initial workspace startup
9697
)

apps/workspace-engine/pkg/workspace/workspace.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package workspace
22

33
import (
44
"context"
5+
"workspace-engine/pkg/oapi"
56
"workspace-engine/pkg/statechange"
67
"workspace-engine/pkg/workspace/releasemanager"
78
"workspace-engine/pkg/workspace/releasemanager/action"
89
verificationaction "workspace-engine/pkg/workspace/releasemanager/action/verification"
10+
"workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentdependency"
11+
"workspace-engine/pkg/workspace/releasemanager/trace"
912
"workspace-engine/pkg/workspace/releasemanager/trace/spanstore"
1013
"workspace-engine/pkg/workspace/store"
1114
)
@@ -34,7 +37,14 @@ func New(ctx context.Context, id string, options ...WorkspaceOption) *Workspace
3437
verificationaction.NewVerificationAction(
3538
ws.releasemanager.VerificationManager(),
3639
),
37-
)
40+
).RegisterAction(
41+
deploymentdependency.NewDeploymentDependencyAction(
42+
s,
43+
func(ctx context.Context, target *oapi.ReleaseTarget) error {
44+
return ws.releasemanager.ReconcileTarget(ctx, target, releasemanager.WithTrigger(trace.TriggerJobSuccess))
45+
},
46+
),
47+
)
3848

3949
return ws
4050
}

apps/workspace-engine/test/e2e/engine_policy_deployment_dependency_test.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,20 @@ func TestEngine_PolicyDeploymentDependency(t *testing.T) {
9999
assert.Equal(t, 1, len(vpcJobs), "expected 1 vpc job")
100100

101101
vpcJob := vpcJobs[0]
102-
vpcJob.Status = oapi.JobStatusSuccessful
102+
vpcJobCopy := *vpcJob
103+
vpcJobCopy.Status = oapi.JobStatusSuccessful
103104
completedAt := time.Now()
104-
vpcJob.CompletedAt = &completedAt
105+
vpcJobCopy.CompletedAt = &completedAt
105106
jobUpdateEvent := &oapi.JobUpdateEvent{
106-
Id: &vpcJob.Id,
107-
Job: *vpcJob,
107+
Id: &vpcJobCopy.Id,
108+
Job: vpcJobCopy,
108109
FieldsToUpdate: &[]oapi.JobUpdateEventFieldsToUpdate{
109110
oapi.JobUpdateEventFieldsToUpdateStatus,
110111
oapi.JobUpdateEventFieldsToUpdateCompletedAt,
111112
},
112113
}
113114
engine.PushEvent(ctx, handler.JobUpdate, jobUpdateEvent)
114115

115-
engine.PushEvent(ctx, handler.WorkspaceTick, nil)
116-
117116
clusterJobs = getAgentJobsSortedByNewest(engine, jobAgentClusterID)
118117
assert.Equal(t, 1, len(clusterJobs), "expected 1 cluster job")
119118
}

0 commit comments

Comments
 (0)