From e016bbd4ac37445acc1bcd5e6fec2840f68938e2 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Wed, 8 Apr 2026 14:26:24 -0700 Subject: [PATCH] chore: jobAgentSelector is required field in deployment schema --- apps/api/openapi/openapi.json | 1 + apps/api/openapi/schemas/deployments.jsonnet | 2 +- apps/api/src/types/openapi.ts | 2 +- apps/web/app/api/openapi.ts | 89 ++++++++++++++++++- apps/workspace-engine/oapi/openapi.json | 1 + .../oapi/spec/schemas/deployments.jsonnet | 2 +- apps/workspace-engine/pkg/db/convert.go | 12 ++- apps/workspace-engine/pkg/oapi/oapi.gen.go | 2 +- .../controllers/deploymentplan/controller.go | 4 +- .../deploymentplan/controller_test.go | 5 +- .../svc/controllers/jobdispatch/reconcile.go | 4 +- .../controllers/jobdispatch/reconcile_test.go | 2 +- .../controllers/jobeligibility/reconcile.go | 15 ++-- .../jobeligibility/reconcile_test.go | 17 ++-- .../test/controllers/harness/pipeline.go | 2 +- packages/workspace-engine-sdk/src/schema.ts | 2 +- 16 files changed, 121 insertions(+), 41 deletions(-) diff --git a/apps/api/openapi/openapi.json b/apps/api/openapi/openapi.json index 8d3204edd..e2c470b91 100644 --- a/apps/api/openapi/openapi.json +++ b/apps/api/openapi/openapi.json @@ -490,6 +490,7 @@ "id", "name", "slug", + "jobAgentSelector", "jobAgentConfig" ], "type": "object" diff --git a/apps/api/openapi/schemas/deployments.jsonnet b/apps/api/openapi/schemas/deployments.jsonnet index e515bf3dc..73fecfa8d 100644 --- a/apps/api/openapi/schemas/deployments.jsonnet +++ b/apps/api/openapi/schemas/deployments.jsonnet @@ -59,7 +59,7 @@ local jobAgentConfig = { Deployment: { type: 'object', - required: ['id', 'name', 'slug', 'jobAgentConfig'], + required: ['id', 'name', 'slug', 'jobAgentSelector', 'jobAgentConfig'], properties: { id: { type: 'string' }, name: { type: 'string' }, diff --git a/apps/api/src/types/openapi.ts b/apps/api/src/types/openapi.ts index 8bddb5dd9..907e01fff 100644 --- a/apps/api/src/types/openapi.ts +++ b/apps/api/src/types/openapi.ts @@ -1225,7 +1225,7 @@ export interface components { }; jobAgentId?: string; /** @description CEL expression to match job agents */ - jobAgentSelector?: string; + jobAgentSelector: string; jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; diff --git a/apps/web/app/api/openapi.ts b/apps/web/app/api/openapi.ts index ad9fa24ef..907e01fff 100644 --- a/apps/web/app/api/openapi.ts +++ b/apps/web/app/api/openapi.ts @@ -801,6 +801,26 @@ export interface paths { patch: operations["requestResourceVariablesUpdate"]; trace?: never; }; + "/v1/workspaces/{workspaceId}/resources/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Search resources + * @description Returns a paginated list of resources matching the given filters. + */ + post: operations["searchResources"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/v1/workspaces/{workspaceId}/resources/{resourceIdentifier}/release-targets/deployment/{deploymentId}": { parameters: { query?: never; @@ -1205,7 +1225,7 @@ export interface components { }; jobAgentId?: string; /** @description CEL expression to match job agents */ - jobAgentSelector?: string; + jobAgentSelector: string; jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; @@ -1529,6 +1549,29 @@ export interface components { release: components["schemas"]["Release"]; resource?: components["schemas"]["Resource"]; }; + ListResourcesFilters: { + identifiers?: string[]; + kinds?: string[]; + /** @default 500 */ + limit: number; + /** @description Exact metadata key/value matches */ + metadata?: { + [key: string]: string; + }; + /** @default 0 */ + offset: number; + /** + * @default asc + * @enum {string} + */ + order: "asc" | "desc"; + providerIds?: string[]; + /** @description Text search on name or identifier */ + query?: string; + /** @enum {string} */ + sortBy?: "createdAt" | "updatedAt" | "name" | "kind"; + versions?: string[]; + }; LiteralValue: components["schemas"]["BooleanValue"] | components["schemas"]["NumberValue"] | components["schemas"]["IntegerValue"] | components["schemas"]["StringValue"] | components["schemas"]["ObjectValue"] | components["schemas"]["NullValue"]; MetricProvider: components["schemas"]["HTTPMetricProvider"] | components["schemas"]["SleepMetricProvider"] | components["schemas"]["DatadogMetricProvider"] | components["schemas"]["PrometheusMetricProvider"] | components["schemas"]["TerraformCloudRunMetricProvider"]; /** @enum {boolean} */ @@ -5138,6 +5181,50 @@ export interface operations { }; }; }; + searchResources: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ListResourcesFilters"]; + }; + }; + responses: { + /** @description Matching resources */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["Resource"][]; + /** @description Maximum number of items returned */ + limit: number; + /** @description Number of items skipped */ + offset: number; + /** @description Total number of items available */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; getReleaseTargetForResourceInDeployment: { parameters: { query?: never; diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index f123338a8..a9dfd94b4 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -266,6 +266,7 @@ "id", "name", "slug", + "jobAgentSelector", "jobAgentConfig", "metadata" ], diff --git a/apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet b/apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet index 4bbc08ec0..c68e6af21 100644 --- a/apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/deployments.jsonnet @@ -3,7 +3,7 @@ local openapi = import '../lib/openapi.libsonnet'; { Deployment: { type: 'object', - required: ['id', 'name', 'slug', 'jobAgentConfig', 'metadata'], + required: ['id', 'name', 'slug', 'jobAgentSelector', 'jobAgentConfig', 'metadata'], properties: { id: { type: 'string' }, name: { type: 'string' }, diff --git a/apps/workspace-engine/pkg/db/convert.go b/apps/workspace-engine/pkg/db/convert.go index d1788e8c8..c355e9fc5 100644 --- a/apps/workspace-engine/pkg/db/convert.go +++ b/apps/workspace-engine/pkg/db/convert.go @@ -10,17 +10,15 @@ import ( func ToOapiDeployment(row Deployment) *oapi.Deployment { d := &oapi.Deployment{ - Id: row.ID.String(), - Name: row.Name, - Metadata: row.Metadata, - JobAgentConfig: oapi.JobAgentConfig(row.JobAgentConfig), + Id: row.ID.String(), + Name: row.Name, + Metadata: row.Metadata, + JobAgentConfig: oapi.JobAgentConfig(row.JobAgentConfig), + JobAgentSelector: row.JobAgentSelector, } if row.Description != "" { d.Description = &row.Description } - if row.JobAgentSelector != "" { - d.JobAgentSelector = &row.JobAgentSelector - } return d } diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index b7319cbf4..37e8bff85 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -310,7 +310,7 @@ type Deployment struct { JobAgentId *string `json:"jobAgentId,omitempty"` // JobAgentSelector CEL expression to match job agents - JobAgentSelector *string `json:"jobAgentSelector,omitempty"` + JobAgentSelector string `json:"jobAgentSelector"` JobAgents *[]DeploymentJobAgent `json:"jobAgents,omitempty"` Metadata map[string]string `json:"metadata"` Name string `json:"name"` diff --git a/apps/workspace-engine/svc/controllers/deploymentplan/controller.go b/apps/workspace-engine/svc/controllers/deploymentplan/controller.go index 7c48138e1..e315a39f2 100644 --- a/apps/workspace-engine/svc/controllers/deploymentplan/controller.go +++ b/apps/workspace-engine/svc/controllers/deploymentplan/controller.go @@ -67,7 +67,7 @@ func (c *Controller) Process(ctx context.Context, item reconcile.Item) (reconcil return reconcile.Result{}, fmt.Errorf("get deployment: %w", err) } - if deployment.JobAgentSelector == nil || *deployment.JobAgentSelector == "" { + if deployment.JobAgentSelector == "" { if err := c.setter.CompletePlan(ctx, planID); err != nil { return reconcile.Result{}, fmt.Errorf("mark plan completed: %w", err) } @@ -79,7 +79,7 @@ func (c *Controller) Process(ctx context.Context, item reconcile.Item) (reconcil return reconcile.Result{}, fmt.Errorf("list job agents: %w", err) } - matchedAgents, err := selector.MatchJobAgents(ctx, *deployment.JobAgentSelector, allAgents) + matchedAgents, err := selector.MatchJobAgents(ctx, deployment.JobAgentSelector, allAgents) if err != nil { return reconcile.Result{}, fmt.Errorf("match job agents: %w", err) } diff --git a/apps/workspace-engine/svc/controllers/deploymentplan/controller_test.go b/apps/workspace-engine/svc/controllers/deploymentplan/controller_test.go index e35aeddc0..e4cc9f3ee 100644 --- a/apps/workspace-engine/svc/controllers/deploymentplan/controller_test.go +++ b/apps/workspace-engine/svc/controllers/deploymentplan/controller_test.go @@ -218,7 +218,7 @@ func testDeployment(hasSelector bool) *oapi.Deployment { } if hasSelector { sel := "true" - dep.JobAgentSelector = &sel + dep.JobAgentSelector = sel } return dep } @@ -300,8 +300,7 @@ func TestProcess_NoAgents_CompletesPlan(t *testing.T) { func TestProcess_EmptySelector_CompletesPlan(t *testing.T) { dep := testDeployment(false) - emptySel := "" - dep.JobAgentSelector = &emptySel + dep.JobAgentSelector = "" getter := &mockGetter{plan: testPlan(), deployment: dep} setter := &mockSetter{} diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/reconcile.go b/apps/workspace-engine/svc/controllers/jobdispatch/reconcile.go index dd33d8ea2..07454067d 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/reconcile.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/reconcile.go @@ -65,7 +65,7 @@ func getJobAgents( return nil, err } - if deployment.JobAgentSelector == nil || *deployment.JobAgentSelector == "" { + if deployment.JobAgentSelector == "" { return nil, fmt.Errorf("deployment job agent selector is empty") } @@ -74,7 +74,7 @@ func getJobAgents( return nil, fmt.Errorf("list job agents: %w", err) } - matched, err := selector.MatchJobAgents(ctx, *deployment.JobAgentSelector, allAgents) + matched, err := selector.MatchJobAgents(ctx, deployment.JobAgentSelector, allAgents) if err != nil { return nil, fmt.Errorf("match job agents: %w", err) } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/reconcile_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/reconcile_test.go index 4b6719901..1e8d50b7f 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/reconcile_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/reconcile_test.go @@ -196,7 +196,7 @@ func testDeployment() *oapi.Deployment { Slug: "test-deployment", Metadata: map[string]string{}, JobAgentConfig: oapi.JobAgentConfig{}, - JobAgentSelector: &sel, + JobAgentSelector: sel, } } diff --git a/apps/workspace-engine/svc/controllers/jobeligibility/reconcile.go b/apps/workspace-engine/svc/controllers/jobeligibility/reconcile.go index ae3c23e45..91106e5db 100644 --- a/apps/workspace-engine/svc/controllers/jobeligibility/reconcile.go +++ b/apps/workspace-engine/svc/controllers/jobeligibility/reconcile.go @@ -200,16 +200,11 @@ func (r *reconciler) buildAndDispatchJob(ctx context.Context) error { span.SetAttributes(attribute.String("deployment.id", deployment.Id)) span.SetAttributes(attribute.String("deployment.name", deployment.Name)) + span.SetAttributes( + attribute.String("deployment.job_agent_selector", deployment.JobAgentSelector), + ) - selectorIsNil := deployment.JobAgentSelector == nil - span.SetAttributes(attribute.Bool("deployment.job_agent_selector_nil", selectorIsNil)) - if !selectorIsNil { - span.SetAttributes( - attribute.String("deployment.job_agent_selector", *deployment.JobAgentSelector), - ) - } - - if selectorIsNil || *deployment.JobAgentSelector == "" { + if deployment.JobAgentSelector == "" { msg := fmt.Sprintf("No job agents configured for deployment '%s'", deployment.Name) span.AddEvent(msg) return r.createFailureJob(ctx, oapi.JobStatusInvalidJobAgent, msg) @@ -221,7 +216,7 @@ func (r *reconciler) buildAndDispatchJob(ctx context.Context) error { } span.SetAttributes(attribute.Int("workspace_agents.count", len(allAgents))) - matchedAgents, err := selector.MatchJobAgents(ctx, *deployment.JobAgentSelector, allAgents) + matchedAgents, err := selector.MatchJobAgents(ctx, deployment.JobAgentSelector, allAgents) if err != nil { return recordErr(span, "match job agents", err) } diff --git a/apps/workspace-engine/svc/controllers/jobeligibility/reconcile_test.go b/apps/workspace-engine/svc/controllers/jobeligibility/reconcile_test.go index 4e135bb07..01cdbe5a0 100644 --- a/apps/workspace-engine/svc/controllers/jobeligibility/reconcile_test.go +++ b/apps/workspace-engine/svc/controllers/jobeligibility/reconcile_test.go @@ -216,7 +216,7 @@ func testDeployment(rt *ReleaseTarget) *oapi.Deployment { Slug: "test-deployment", Metadata: map[string]string{}, JobAgentConfig: oapi.JobAgentConfig{}, - JobAgentSelector: &sel, + JobAgentSelector: sel, } } @@ -1160,8 +1160,7 @@ func TestReconcile_NoJobAgentSelector_CreatesFailureJob(t *testing.T) { rt := testRT() release := testRelease(rt) getter, setter := setupHappyPath(rt, release) - emptySel := "" - getter.deployment.JobAgentSelector = &emptySel + getter.deployment.JobAgentSelector = "" _, err := Reconcile(context.Background(), rt.WorkspaceID.String(), getter, setter, rt) require.NoError(t, err) @@ -1174,7 +1173,7 @@ func TestReconcile_NilJobAgentSelector_CreatesFailureJob(t *testing.T) { rt := testRT() release := testRelease(rt) getter, setter := setupHappyPath(rt, release) - getter.deployment.JobAgentSelector = nil + getter.deployment.JobAgentSelector = "" _, err := Reconcile(context.Background(), rt.WorkspaceID.String(), getter, setter, rt) require.NoError(t, err) @@ -1479,7 +1478,7 @@ func TestReconcile_JobAgentConfig_DeepMergesThreeLevels(t *testing.T) { Name: "test-deployment", Slug: "test-deployment", Metadata: map[string]string{}, - JobAgentSelector: &sel, + JobAgentSelector: sel, JobAgentConfig: oapi.JobAgentConfig{ "workflowId": float64(456), "timeout": float64(60), @@ -1563,7 +1562,7 @@ func TestReconcile_SelectorMatchesSpecificAgent(t *testing.T) { sel := fmt.Sprintf(`jobAgent.id == "%s"`, target.Id) deployment := testDeployment(rt) - deployment.JobAgentSelector = &sel + deployment.JobAgentSelector = sel getter := &mockGetter{ rtExists: true, @@ -1620,7 +1619,7 @@ func TestReconcile_SelectorByType_MatchesMultiple(t *testing.T) { sel := `jobAgent.type == "argo-cd"` deployment := testDeployment(rt) - deployment.JobAgentSelector = &sel + deployment.JobAgentSelector = sel getter := &mockGetter{ rtExists: true, @@ -1670,7 +1669,7 @@ func TestReconcile_SelectorFalse_NoAgentsMatched(t *testing.T) { agent := testAgent() sel := "false" deployment := testDeployment(rt) - deployment.JobAgentSelector = &sel + deployment.JobAgentSelector = sel getter := &mockGetter{ rtExists: true, @@ -1721,7 +1720,7 @@ func TestReconcile_SelectorByName_MatchesSingle(t *testing.T) { sel := `jobAgent.name == "prod-deployer"` deployment := testDeployment(rt) - deployment.JobAgentSelector = &sel + deployment.JobAgentSelector = sel getter := &mockGetter{ rtExists: true, diff --git a/apps/workspace-engine/test/controllers/harness/pipeline.go b/apps/workspace-engine/test/controllers/harness/pipeline.go index 0bc800545..e49984654 100644 --- a/apps/workspace-engine/test/controllers/harness/pipeline.go +++ b/apps/workspace-engine/test/controllers/harness/pipeline.go @@ -159,7 +159,7 @@ func NewTestPipeline(t *testing.T, opts ...PipelineOption) *TestPipeline { Deployment: &oapi.Deployment{ Id: sc.DeploymentID.String(), Name: sc.DeploymentName, - JobAgentSelector: &sel, + JobAgentSelector: sel, JobAgentConfig: oapi.JobAgentConfig{}, }, } diff --git a/packages/workspace-engine-sdk/src/schema.ts b/packages/workspace-engine-sdk/src/schema.ts index 47a58f4a5..b3ee02bd8 100644 --- a/packages/workspace-engine-sdk/src/schema.ts +++ b/packages/workspace-engine-sdk/src/schema.ts @@ -242,7 +242,7 @@ export interface components { jobAgentConfig: components["schemas"]["JobAgentConfig"]; jobAgentId?: string; /** @description CEL expression to match job agents */ - jobAgentSelector?: string; + jobAgentSelector: string; jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata: { [key: string]: string;