diff --git a/apps/workspace-engine/svc/http/server/openapi/workflows/setters.go b/apps/workspace-engine/svc/http/server/openapi/workflows/setters.go index fdd92e0cb..8bc2cd40c 100644 --- a/apps/workspace-engine/svc/http/server/openapi/workflows/setters.go +++ b/apps/workspace-engine/svc/http/server/openapi/workflows/setters.go @@ -107,6 +107,28 @@ func mergeWorkflowJobAgentConfig( return oapi.DeepMergeConfigs(runnerConfig, perJobConfig) } +// buildJobDispatchContext returns a per-job copy of the base dispatch context +// with the resolved JobAgent and merged JobAgentConfig populated. Dispatchers +// (argo-workflow, argo-cd, github, …) read these fields off DispatchContext, +// not off the Job row, so they must be set here before the context is +// persisted alongside the job. +func buildJobDispatchContext( + base *oapi.DispatchContext, + runner db.JobAgent, + mergedConfig oapi.JobAgentConfig, +) *oapi.DispatchContext { + out := *base + out.JobAgent = oapi.JobAgent{ + Id: runner.ID.String(), + WorkspaceId: runner.WorkspaceID.String(), + Name: runner.Name, + Type: runner.Type, + Config: runner.Config, + } + out.JobAgentConfig = mergedConfig + return &out +} + func (s *PostgresSetter) dispatchJobForAgent( ctx context.Context, queries *db.Queries, @@ -138,7 +160,8 @@ func (s *PostgresSetter) dispatchJobForAgent( if err != nil { return fmt.Errorf("marshal job agent config: %w", err) } - dispatchContextJSON, err := json.Marshal(dispatchContext) + jobDispatchContext := buildJobDispatchContext(dispatchContext, runner, mergedConfig) + dispatchContextJSON, err := json.Marshal(jobDispatchContext) if err != nil { return fmt.Errorf("marshal dispatch context: %w", err) } diff --git a/apps/workspace-engine/svc/http/server/openapi/workflows/workflows_test.go b/apps/workspace-engine/svc/http/server/openapi/workflows/workflows_test.go index 23b9a8ee0..7f5df5488 100644 --- a/apps/workspace-engine/svc/http/server/openapi/workflows/workflows_test.go +++ b/apps/workspace-engine/svc/http/server/openapi/workflows/workflows_test.go @@ -3,8 +3,10 @@ package workflows import ( "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "workspace-engine/pkg/db" "workspace-engine/pkg/oapi" ) @@ -203,6 +205,52 @@ func TestMergeWorkflowJobAgentConfig_NilInputs(t *testing.T) { assert.Equal(t, "spec", merged["template"]) } +func TestBuildJobDispatchContext_PopulatesAgentAndMergedConfig(t *testing.T) { + workflow := &oapi.Workflow{Id: uuid.New().String()} + inputs := map[string]any{"env": "prod"} + base := &oapi.DispatchContext{Workflow: workflow, Inputs: &inputs} + + runnerID := uuid.New() + workspaceID := uuid.New() + runner := db.JobAgent{ + ID: runnerID, + WorkspaceID: workspaceID, + Name: "argo-runner", + Type: "argo-workflow", + Config: oapi.JobAgentConfig{"serverUrl": "https://argo.example", "apiKey": "secret"}, + } + merged := oapi.JobAgentConfig{ + "serverUrl": "https://argo.example", + "apiKey": "secret", + "template": "tmpl", + "name": "deploy", + } + + got := buildJobDispatchContext(base, runner, merged) + + assert.Equal(t, "https://argo.example", got.JobAgentConfig["serverUrl"]) + assert.Equal(t, "secret", got.JobAgentConfig["apiKey"]) + assert.Equal(t, "tmpl", got.JobAgentConfig["template"]) + assert.Equal(t, runnerID.String(), got.JobAgent.Id) + assert.Equal(t, "argo-workflow", got.JobAgent.Type) + assert.Equal(t, workspaceID.String(), got.JobAgent.WorkspaceId) + assert.Equal(t, workflow, got.Workflow) + require.NotNil(t, got.Inputs) + assert.Equal(t, "prod", (*got.Inputs)["env"]) +} + +func TestBuildJobDispatchContext_DoesNotMutateBase(t *testing.T) { + base := &oapi.DispatchContext{Workflow: &oapi.Workflow{Id: uuid.New().String()}} + runner := db.JobAgent{ID: uuid.New(), WorkspaceID: uuid.New(), Type: "argo-workflow"} + merged := oapi.JobAgentConfig{"serverUrl": "https://argo.example"} + + _ = buildJobDispatchContext(base, runner, merged) + + assert.Empty(t, base.JobAgentConfig) + assert.Empty(t, base.JobAgent.Id) + assert.Empty(t, base.JobAgent.Type) +} + func TestResolveInputs_ExtraProvidedInputsPassThrough(t *testing.T) { workflow := &oapi.Workflow{ Inputs: []oapi.WorkflowInput{