Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ func (o *Orchestrator) OnJobStatusChange(
return nil // Don't fail job update on policy lookup failure
}

if len(policies) == 0 {
span.SetAttributes(attribute.Int("policy_count", 0))
return nil // No policies apply
}

span.SetAttributes(attribute.Int("policy_count", len(policies)))

// Build action context with pre-fetched policies for efficiency and consistency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -349,97 +349,6 @@ func TestOrchestrator_OnJobStatusChange_ReleaseNotFound(t *testing.T) {
assert.False(t, mockAct.executeCalled)
}

func TestOrchestrator_OnJobStatusChange_NoPolicies(t *testing.T) {
ctx := context.Background()
s := newTestStore()
orchestrator := action.NewOrchestrator(s)

mockAct := &mockAction{
name: "test-action",
shouldExecute: true,
}
orchestrator.RegisterAction(mockAct)

// Create release but don't create matching policy
systemId := uuid.New().String()
system := &oapi.System{
Id: systemId,
Name: "test-system",
}
_ = s.Systems.Upsert(ctx, system)

resourceId := uuid.New().String()
resource := &oapi.Resource{
Id: resourceId,
Name: "test-resource",
Kind: "kubernetes",
Identifier: "test-res-1",
CreatedAt: time.Now(),
}
_, _ = s.Resources.Upsert(ctx, resource)

environmentId := uuid.New().String()
environment := &oapi.Environment{
Id: environmentId,
Name: "test-env",
SystemId: systemId,
}
selector := &oapi.Selector{}
_ = selector.FromCelSelector(oapi.CelSelector{Cel: "true"})
environment.ResourceSelector = selector
_ = s.Environments.Upsert(ctx, environment)

deploymentId := uuid.New().String()
deployment := &oapi.Deployment{
Id: deploymentId,
Name: "test-deployment",
Slug: "test-deployment",
SystemId: systemId,
}
deploymentSelector := &oapi.Selector{}
_ = deploymentSelector.FromCelSelector(oapi.CelSelector{Cel: "true"})
deployment.ResourceSelector = deploymentSelector
_ = s.Deployments.Upsert(ctx, deployment)

versionId := uuid.New().String()
version := &oapi.DeploymentVersion{
Id: versionId,
Tag: "v1.0.0",
DeploymentId: deploymentId,
CreatedAt: time.Now(),
}
s.DeploymentVersions.Upsert(ctx, versionId, version)

releaseTarget := &oapi.ReleaseTarget{
ResourceId: resourceId,
EnvironmentId: environmentId,
DeploymentId: deploymentId,
}
_ = s.ReleaseTargets.Upsert(ctx, releaseTarget)

release := &oapi.Release{
ReleaseTarget: *releaseTarget,
Version: *version,
Variables: map[string]oapi.LiteralValue{},
}
_ = s.Releases.Upsert(ctx, release)

// No policy created, so no policies should apply

job := &oapi.Job{
Id: uuid.New().String(),
ReleaseId: release.ID(),
Status: oapi.JobStatusSuccessful,
CreatedAt: time.Now(),
}

err := orchestrator.OnJobStatusChange(ctx, job, oapi.JobStatusPending)
require.NoError(t, err)

// Action should not be executed because no policies apply
assert.False(t, mockAct.executeCalled)
}

func TestOrchestrator_OnJobStatusChange_ActionError(t *testing.T) {
ctx := context.Background()
s := newTestStore()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package deploymentdependency

import (
"context"
"fmt"
"workspace-engine/pkg/oapi"
"workspace-engine/pkg/workspace/releasemanager/action"
"workspace-engine/pkg/workspace/store"
)

type ReconcileFn func(ctx context.Context, target *oapi.ReleaseTarget) error

type DeploymentDependencyAction struct {
store *store.Store
reconcileFn ReconcileFn
}

func NewDeploymentDependencyAction(store *store.Store, reconcileFn ReconcileFn) *DeploymentDependencyAction {
return &DeploymentDependencyAction{
store: store,
reconcileFn: reconcileFn,
}
}

func (d *DeploymentDependencyAction) Name() string {
return "deploymentdependency"
}

func (d *DeploymentDependencyAction) Execute(ctx context.Context, trigger action.ActionTrigger, context action.ActionContext) error {
if trigger != action.TriggerJobSuccess {
return nil
}

resourceId := context.Release.ReleaseTarget.ResourceId

resourceTargets := d.store.ReleaseTargets.GetForResource(ctx, resourceId)

targetsToReconcile := make([]*oapi.ReleaseTarget, 0)

for _, target := range resourceTargets {
if target.Key() == context.Release.ReleaseTarget.Key() {
continue
}

policies, err := d.store.ReleaseTargets.GetPolicies(ctx, target)
if err != nil {
return fmt.Errorf("failed to get policies for release target: %s", target.Key())
}

var hasDeploymentDependencyRule bool
for _, policy := range policies {
for _, rule := range policy.Rules {
if rule.DeploymentDependency != nil {
targetsToReconcile = append(targetsToReconcile, target)
hasDeploymentDependencyRule = true
break
}
}

if hasDeploymentDependencyRule {
break
}
}
}

if len(targetsToReconcile) == 0 {
return nil
}

for _, target := range targetsToReconcile {
if err := d.reconcileFn(ctx, target); err != nil {
return fmt.Errorf("failed to reconcile target: %s", target.Key())
}
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const (
TriggerPolicyUpdated TriggerReason = "policy.updated" // Policy configuration changed
TriggerVariablesUpdated TriggerReason = "variables.updated" // Deployment or resource variables changed
TriggerJobAgentUpdated TriggerReason = "jobagent.updated" // Job agent configuration changed
TriggerJobSuccess TriggerReason = "job.success" // Job was successful
TriggerManual TriggerReason = "manual" // Manually triggered (e.g., force redeploy)
TriggerFirstBoot TriggerReason = "first_boot" // Initial workspace startup
)
Expand Down
12 changes: 11 additions & 1 deletion apps/workspace-engine/pkg/workspace/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ package workspace

import (
"context"
"workspace-engine/pkg/oapi"
"workspace-engine/pkg/statechange"
"workspace-engine/pkg/workspace/releasemanager"
"workspace-engine/pkg/workspace/releasemanager/action"
verificationaction "workspace-engine/pkg/workspace/releasemanager/action/verification"
"workspace-engine/pkg/workspace/releasemanager/policy/evaluator/deploymentdependency"
"workspace-engine/pkg/workspace/releasemanager/trace"
"workspace-engine/pkg/workspace/releasemanager/trace/spanstore"
"workspace-engine/pkg/workspace/store"
)
Expand Down Expand Up @@ -34,7 +37,14 @@ func New(ctx context.Context, id string, options ...WorkspaceOption) *Workspace
verificationaction.NewVerificationAction(
ws.releasemanager.VerificationManager(),
),
)
).RegisterAction(
deploymentdependency.NewDeploymentDependencyAction(
s,
func(ctx context.Context, target *oapi.ReleaseTarget) error {
return ws.releasemanager.ReconcileTarget(ctx, target, releasemanager.WithTrigger(trace.TriggerJobSuccess))
},
),
)

return ws
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,21 +99,20 @@ func TestEngine_PolicyDeploymentDependency(t *testing.T) {
assert.Equal(t, 1, len(vpcJobs), "expected 1 vpc job")

vpcJob := vpcJobs[0]
vpcJob.Status = oapi.JobStatusSuccessful
vpcJobCopy := *vpcJob
vpcJobCopy.Status = oapi.JobStatusSuccessful
completedAt := time.Now()
vpcJob.CompletedAt = &completedAt
vpcJobCopy.CompletedAt = &completedAt
jobUpdateEvent := &oapi.JobUpdateEvent{
Id: &vpcJob.Id,
Job: *vpcJob,
Id: &vpcJobCopy.Id,
Job: vpcJobCopy,
FieldsToUpdate: &[]oapi.JobUpdateEventFieldsToUpdate{
oapi.JobUpdateEventFieldsToUpdateStatus,
oapi.JobUpdateEventFieldsToUpdateCompletedAt,
},
}
engine.PushEvent(ctx, handler.JobUpdate, jobUpdateEvent)

engine.PushEvent(ctx, handler.WorkspaceTick, nil)

clusterJobs = getAgentJobsSortedByNewest(engine, jobAgentClusterID)
assert.Equal(t, 1, len(clusterJobs), "expected 1 cluster job")
}
Loading