diff --git a/apps/api/openapi/openapi.json b/apps/api/openapi/openapi.json index 6551e5800..6611b8c7d 100644 --- a/apps/api/openapi/openapi.json +++ b/apps/api/openapi/openapi.json @@ -1000,13 +1000,6 @@ "minimum": 0, "type": "integer" }, - "minimumSockTimeMinutes": { - "deprecated": true, - "description": "Use minimumSoakTimeMinutes instead. Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed", - "format": "int32", - "minimum": 0, - "type": "integer" - }, "minimumSuccessPercentage": { "default": 100, "format": "float", diff --git a/apps/api/openapi/schemas/policies.jsonnet b/apps/api/openapi/schemas/policies.jsonnet index 31570a799..a75bc30fd 100644 --- a/apps/api/openapi/schemas/policies.jsonnet +++ b/apps/api/openapi/schemas/policies.jsonnet @@ -168,14 +168,6 @@ local openapi = import '../lib/openapi.libsonnet'; minimumSuccessPercentage: { type: 'number', format: 'float', minimum: 0, maximum: 100, default: 100 }, successStatuses: { type: 'array', items: openapi.schemaRef('JobStatus') }, - minimumSockTimeMinutes: { - type: 'integer', - format: 'int32', - minimum: 0, - deprecated: true, - description: 'Use minimumSoakTimeMinutes instead. Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed', - }, - minimumSoakTimeMinutes: { type: 'integer', format: 'int32', diff --git a/apps/api/src/routes/v1/workspaces/policies.ts b/apps/api/src/routes/v1/workspaces/policies.ts index a538c5860..76a51a37a 100644 --- a/apps/api/src/routes/v1/workspaces/policies.ts +++ b/apps/api/src/routes/v1/workspaces/policies.ts @@ -1,5 +1,5 @@ -import type { Tx } from "@ctrlplane/db"; import type { AsyncTypedHandler } from "@/types/api.js"; +import type { Tx } from "@ctrlplane/db"; import { ApiError, asyncHandler } from "@/types/api.js"; import { Router } from "express"; import { v4 as uuidv4 } from "uuid"; @@ -7,8 +7,8 @@ import { z } from "zod"; import { and, count, eq } from "@ctrlplane/db"; import { db } from "@ctrlplane/db/client"; -import * as schema from "@ctrlplane/db/schema"; import { enqueueAllReleaseTargetsDesiredVersion } from "@ctrlplane/db/reconcilers"; +import * as schema from "@ctrlplane/db/schema"; const deleteAllRulesForPolicy = async (tx: Tx, policyId: string) => { await tx @@ -43,7 +43,6 @@ const deleteAllRulesForPolicy = async (tx: Tx, policyId: string) => { .where(eq(schema.policyRuleVersionSelector.policyId, policyId)); }; - const insertPolicyRules = async (tx: Tx, policyId: string, rules: any[]) => { for (const rule of rules) { const ruleId: string = rule.id ?? uuidv4(); @@ -80,8 +79,7 @@ const insertPolicyRules = async (tx: Tx, policyId: string, rules: any[]) => { rule.environmentProgression.dependsOnEnvironmentSelector, maximumAgeHours: rule.environmentProgression.maximumAgeHours, minimumSoakTimeMinutes: - rule.environmentProgression.minimumSoakTimeMinutes ?? - rule.environmentProgression.minimumSockTimeMinutes, + rule.environmentProgression.minimumSoakTimeMinutes, minimumSuccessPercentage: rule.environmentProgression.minimumSuccessPercentage, successStatuses: rule.environmentProgression.successStatuses, @@ -193,7 +191,6 @@ const formatPolicy = (p: PolicyRow) => { ...(r.maximumAgeHours != null && { maximumAgeHours: r.maximumAgeHours, }), - minimumSockTimeMinutes: r.minimumSoakTimeMinutes, minimumSoakTimeMinutes: r.minimumSoakTimeMinutes, minimumSuccessPercentage: r.minimumSuccessPercentage, ...(r.successStatuses != null && { diff --git a/apps/api/src/types/openapi.ts b/apps/api/src/types/openapi.ts index f369a722e..07730889a 100644 --- a/apps/api/src/types/openapi.ts +++ b/apps/api/src/types/openapi.ts @@ -1405,12 +1405,6 @@ export interface components { * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed. Defaults to 0 if not provided. */ minimumSoakTimeMinutes?: number; - /** - * Format: int32 - * @deprecated - * @description Use minimumSoakTimeMinutes instead. Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed - */ - minimumSockTimeMinutes?: number; /** * Format: float * @default 100 diff --git a/apps/web/app/api/openapi.ts b/apps/web/app/api/openapi.ts index 907e01fff..07730889a 100644 --- a/apps/web/app/api/openapi.ts +++ b/apps/web/app/api/openapi.ts @@ -1063,10 +1063,8 @@ export interface components { jobAgentConfig?: { [key: string]: unknown; }; - jobAgentId?: string; - /** @description CEL expression to match job agents. Defaults to jobAgent.id == "" if not provided. */ + /** @description CEL expression to match job agents */ jobAgentSelector?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; }; @@ -1223,10 +1221,8 @@ export interface components { jobAgentConfig: { [key: string]: unknown; }; - jobAgentId?: string; /** @description CEL expression to match job agents */ jobAgentSelector: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; }; @@ -1240,15 +1236,9 @@ export interface components { systems: components["schemas"]["System"][]; }; DeploymentDependencyRule: { - /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ + /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. The expression can reference both deployment properties (deployment.id, deployment.name, deployment.slug, deployment.metadata) and the currently deployed version properties (version.id, version.tag, version.name, version.status, version.metadata, version.createdAt). For example: deployment.name == 'db-migration' && version.tag.startsWith('v2.'). */ dependsOn: string; }; - DeploymentJobAgent: { - config: components["schemas"]["JobAgentConfig"]; - ref: string; - /** @description CEL expression to determine if the job agent should be used */ - selector: string; - }; DeploymentPlan: { id: string; /** @enum {string} */ @@ -1264,17 +1254,25 @@ export interface components { unsupported?: number; }; DeploymentPlanTarget: { - /** @description Hash of the rendered output for change detection */ - contentHash?: string; - /** @description Full rendered output of the currently deployed state */ - current?: string; environmentId: string; environmentName: string; - hasChanges?: boolean | null; - /** @description Full rendered output of the proposed version */ - proposed?: string; + /** @description True if any result for this target has changes */ + hasChanges: boolean; resourceId: string; resourceName: string; + results: components["schemas"]["DeploymentPlanTargetResult"][]; + }; + DeploymentPlanTargetResult: { + /** @description Hash of the rendered output for change detection */ + contentHash: string; + /** @description Full rendered output of the currently deployed state */ + current: string; + hasChanges: boolean; + id: string; + /** @description Agent message (e.g. error explanation or summary) */ + message: string; + /** @description Full rendered output of the proposed version */ + proposed: string; /** @enum {string} */ status: "computing" | "completed" | "errored" | "unsupported"; }; @@ -1404,10 +1402,9 @@ export interface components { maximumAgeHours?: number; /** * Format: int32 - * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed - * @default 0 + * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed. Defaults to 0 if not provided. */ - minimumSockTimeMinutes: number; + minimumSoakTimeMinutes?: number; /** * Format: float * @default 100 @@ -1961,10 +1958,8 @@ export interface components { jobAgentConfig?: { [key: string]: unknown; }; - jobAgentId?: string; - /** @description CEL expression to match job agents. Defaults to jobAgent.id == "" if not provided. */ + /** @description CEL expression to match job agents */ jobAgentSelector?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; }; diff --git a/docs/policies/environment-progression.mdx b/docs/policies/environment-progression.mdx index 057fac45e..a5bd54b5a 100644 --- a/docs/policies/environment-progression.mdx +++ b/docs/policies/environment-progression.mdx @@ -187,7 +187,7 @@ resource "ctrlplane_policy" "production_soak_requirement" { environment_progression { depends_on_environment_selector = "environment.name == 'staging'" - minimum_sock_time_minutes = 60 + minimum_soak_time_minutes = 60 } } ``` @@ -280,7 +280,7 @@ resource "ctrlplane_policy" "production_full_gate" { environment_progression { depends_on_environment_selector = "environment.name == 'staging'" minimum_success_percentage = 100 - minimum_sock_time_minutes = 30 + minimum_soak_time_minutes = 30 maximum_age_hours = 48 } diff --git a/docs/policies/overview.mdx b/docs/policies/overview.mdx index 413ba8434..5a6babb20 100644 --- a/docs/policies/overview.mdx +++ b/docs/policies/overview.mdx @@ -332,7 +332,7 @@ resource "ctrlplane_policy" "production_policy" { environment_progression { depends_on_environment_selector = "environment.name == 'staging'" - minimum_sock_time_minutes = 30 + minimum_soak_time_minutes = 30 } gradual_rollout { diff --git a/e2e/api/schema.ts b/e2e/api/schema.ts index ad9fa24ef..6d74bcba8 100644 --- a/e2e/api/schema.ts +++ b/e2e/api/schema.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; @@ -1043,10 +1063,8 @@ export interface components { jobAgentConfig?: { [key: string]: unknown; }; - jobAgentId?: string; - /** @description CEL expression to match job agents. Defaults to jobAgent.id == "" if not provided. */ + /** @description CEL expression to match job agents */ jobAgentSelector?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; }; @@ -1203,10 +1221,8 @@ export interface components { jobAgentConfig: { [key: string]: unknown; }; - jobAgentId?: string; /** @description CEL expression to match job agents */ - jobAgentSelector?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; + jobAgentSelector: string; metadata?: { [key: string]: string; }; @@ -1220,15 +1236,9 @@ export interface components { systems: components["schemas"]["System"][]; }; DeploymentDependencyRule: { - /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ + /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. The expression can reference both deployment properties (deployment.id, deployment.name, deployment.slug, deployment.metadata) and the currently deployed version properties (version.id, version.tag, version.name, version.status, version.metadata, version.createdAt). For example: deployment.name == 'db-migration' && version.tag.startsWith('v2.'). */ dependsOn: string; }; - DeploymentJobAgent: { - config: components["schemas"]["JobAgentConfig"]; - ref: string; - /** @description CEL expression to determine if the job agent should be used */ - selector: string; - }; DeploymentPlan: { id: string; /** @enum {string} */ @@ -1244,17 +1254,25 @@ export interface components { unsupported?: number; }; DeploymentPlanTarget: { - /** @description Hash of the rendered output for change detection */ - contentHash?: string; - /** @description Full rendered output of the currently deployed state */ - current?: string; environmentId: string; environmentName: string; - hasChanges?: boolean | null; - /** @description Full rendered output of the proposed version */ - proposed?: string; + /** @description True if any result for this target has changes */ + hasChanges: boolean; resourceId: string; resourceName: string; + results: components["schemas"]["DeploymentPlanTargetResult"][]; + }; + DeploymentPlanTargetResult: { + /** @description Hash of the rendered output for change detection */ + contentHash: string; + /** @description Full rendered output of the currently deployed state */ + current: string; + hasChanges: boolean; + id: string; + /** @description Agent message (e.g. error explanation or summary) */ + message: string; + /** @description Full rendered output of the proposed version */ + proposed: string; /** @enum {string} */ status: "computing" | "completed" | "errored" | "unsupported"; }; @@ -1384,10 +1402,10 @@ export interface components { maximumAgeHours?: number; /** * Format: int32 - * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed - * @default 0 + * @deprecated + * @description Minimum time to wait after the depends on environment is in a success state before the current environment can be deployed. Defaults to 0 if not provided. */ - minimumSockTimeMinutes: number; + minimumSoakTimeMinutes?: number; /** * Format: float * @default 100 @@ -1529,6 +1547,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} */ @@ -1918,10 +1959,8 @@ export interface components { jobAgentConfig?: { [key: string]: unknown; }; - jobAgentId?: string; - /** @description CEL expression to match job agents. Defaults to jobAgent.id == "" if not provided. */ + /** @description CEL expression to match job agents */ jobAgentSelector?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; metadata?: { [key: string]: string; }; @@ -5138,6 +5177,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/packages/trpc/src/routes/policies.ts b/packages/trpc/src/routes/policies.ts index 75e9a383d..69d5e151a 100644 --- a/packages/trpc/src/routes/policies.ts +++ b/packages/trpc/src/routes/policies.ts @@ -195,7 +195,6 @@ export const policiesRouter = router({ const ep = rule.environmentProgression as { dependsOnEnvironmentSelector: string; maximumAgeHours?: number; - minimumSockTimeMinutes?: number; minimumSoakTimeMinutes?: number; minimumSuccessPercentage?: number; successStatuses?: string[]; @@ -205,8 +204,7 @@ export const policiesRouter = router({ policyId, dependsOnEnvironmentSelector: ep.dependsOnEnvironmentSelector, maximumAgeHours: ep.maximumAgeHours, - minimumSoakTimeMinutes: - ep.minimumSoakTimeMinutes ?? ep.minimumSockTimeMinutes, + minimumSoakTimeMinutes: ep.minimumSoakTimeMinutes, minimumSuccessPercentage: ep.minimumSuccessPercentage, successStatuses: ep.successStatuses, });