From 287c5287509e37e02aa169803dca6fe03cfecb3a Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 23 Mar 2026 09:52:26 -0400 Subject: [PATCH 01/36] feat: add argo workflow JobAgent * adds webhook endpoint in api for argo events --- apps/api/src/config.ts | 3 + apps/api/src/routes/argoworkflow/index.ts | 29 +++++++ apps/api/src/routes/argoworkflow/workflow.ts | 78 +++++++++++++++++++ apps/api/src/server.ts | 4 +- .../oapi/spec/schemas/jobs.jsonnet | 10 +++ 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 apps/api/src/routes/argoworkflow/index.ts create mode 100644 apps/api/src/routes/argoworkflow/workflow.ts diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts index e9fcbfe5b..6c7444fca 100644 --- a/apps/api/src/config.ts +++ b/apps/api/src/config.ts @@ -39,6 +39,9 @@ export const env = createEnv({ OTEL_SAMPLER_RATIO: z.number().optional().default(1), AZURE_APP_CLIENT_ID: z.string().optional(), + + ARGO_WORKFLOW_WEBHOOK_SECRET: z.string().optional(), + ARGO_WORKFLOW_HTTP_ENDPOINT: z.string().optional(), }, runtimeEnv: process.env, diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts new file mode 100644 index 000000000..0eda53e73 --- /dev/null +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -0,0 +1,29 @@ +import type { Request, Response } from "express"; +import { env } from "@/config.js"; +import { asyncHandler } from "@/types/api.js"; +import { Router } from "express"; + +import { handleArgoWorkflow } from "./workflow.js"; + +export const createArgoWorkflowRouter = (): Router => + Router().post("/webhook", asyncHandler(handleWebhookRequest)); + +const verifyRequest = async (req: Request): Promise => { + const authHeader = req.headers["authorization"]?.toString(); + if (authHeader == null) return false; + const secret = env.ARGO_WORKFLOW_WEBHOOK_SECRET; + return authHeader === secret; +}; + +const handleWebhookRequest = async (req: Request, res: Response) => { + const isVerified = await verifyRequest(req); + if (!isVerified) { + res.status(401).json({ message: "Unauthorized" }); + return; + } + + const payload = req.body; + console.log("handleArgoWorkflow payload:", JSON.stringify(payload, null, 2)); + await handleArgoWorkflow(payload); + res.status(200).send(); +}; diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts new file mode 100644 index 000000000..00b518bab --- /dev/null +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -0,0 +1,78 @@ +import { eq } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import { enqueueAllReleaseTargetsDesiredVersion } from "@ctrlplane/db/reconcilers"; +import * as schema from "@ctrlplane/db/schema"; +import { exitedStatus, JobStatus } from "@ctrlplane/validators/jobs"; + +interface ArgoWorkflowPayload { + workflowName: string; + namespace: string; + uid: string; + createdAt: string; + startedAt: string; + finishedAt: string | null; + phase: string; + eventType: string; +} + +const statusMap: Record = { + Succeeded: JobStatus.Successful, + Failed: JobStatus.Failure, + Running: JobStatus.InProgress, + Pending: JobStatus.Pending, +}; + +const extractUuid = (str: string) => { + const uuidRegex = + /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/; + const match = uuidRegex.exec(str); + return match ? match[0] : null; +}; + +export const mapTriggerToStatus = (trigger: string): JobStatus | null => + statusMap[trigger] ?? null; + +export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { + const { workflowName, uid, phase, startedAt, finishedAt } = payload; + + const jobId = uid; + if (jobId == null) return; + + const status = statusMap[phase] ?? null; + if (status == null) return; + + const isCompleted = exitedStatus.includes(status); + const completedAt = + isCompleted && finishedAt != null ? new Date(finishedAt) : null; + + const [updated] = await db + .update(schema.job) + .set({ + externalId: uid, + status, + startedAt: new Date(startedAt), + completedAt, + updatedAt: new Date(), + }) + .where(eq(schema.job.id, jobId)) + .returning(); + + if (updated == null) return; + + const result = await db + .select({ workspaceId: schema.deployment.workspaceId }) + .from(schema.releaseJob) + .innerJoin( + schema.release, + eq(schema.releaseJob.releaseId, schema.release.id), + ) + .innerJoin( + schema.deployment, + eq(schema.release.deploymentId, schema.deployment.id), + ) + .where(eq(schema.releaseJob.jobId, jobId)) + .then((rows) => rows[0] ?? null); + + if (result?.workspaceId == null) return; + enqueueAllReleaseTargetsDesiredVersion(db, result.workspaceId); +}; diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 6003c7eb2..3454039ad 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -16,6 +16,7 @@ import { auth } from "@ctrlplane/auth/server"; import { appRouter, createTRPCContext } from "@ctrlplane/trpc"; import swaggerDocument from "../openapi/openapi.json" with { type: "json" }; +import { createArgoWorkflowRouter } from "./routes/argoworkflow"; import { createGithubRouter } from "./routes/github/index.js"; import { createTfeRouter } from "./routes/tfe/index.js"; @@ -26,7 +27,7 @@ const specFile = join(__dirname, "../openapi/openapi.json"); const oapiValidatorMiddleware = OpenApiValidator.middleware({ apiSpec: specFile, validateRequests: true, - ignorePaths: /\/api\/(auth|trpc|github|tfe|ui|healthz)/, + ignorePaths: /\/api\/(auth|argo|trpc|github|tfe|ui|healthz)/, }); const trpcMiddleware = trpcExpress.createExpressMiddleware({ @@ -81,6 +82,7 @@ const app = express() .use("/api/v1", createV1Router()) .use("/api/github", createGithubRouter()) .use("/api/tfe", createTfeRouter()) + .use("/api/argo", createArgoWorkflowRouter()) .use("/api/trpc", trpcMiddleware) .use(errorHandler); diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 64437deb6..380680e06 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -155,6 +155,16 @@ local JobPropertyKeys = std.objectFields(Job.properties); }, }, + ArgoWorkflowJobAgentConfig: { + type: 'object', + required: ['uid', 'serverUrl', 'apiKey'], + properties: { + serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, + uid: { type: 'string', description: 'ArgoWorkflow job id ' }, + apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + }, + }, + TestRunnerJobAgentConfig: { type: 'object', properties: { From b1d2513847270f1b869592eeff08a85b7fcab9fc Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 23 Mar 2026 09:55:38 -0400 Subject: [PATCH 02/36] fix: update jsonnet for argo workflow --- apps/workspace-engine/oapi/openapi.json | 24 ++++++++++++++++++- .../oapi/spec/schemas/jobs.jsonnet | 6 ++--- apps/workspace-engine/pkg/oapi/oapi.gen.go | 12 ++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index efe7f1d4e..d81addf02 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -53,6 +53,18 @@ ], "type": "object" }, + "ArgoWorkflowJobAgentConfig": { + "properties": { + "name": { + "description": "ArgoWorkflow job name", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, "BasicResource": { "properties": { "id": { @@ -2233,13 +2245,23 @@ "token": { "description": "Terraform Cloud API token.", "type": "string" + }, + "triggerRunOnChange": { + "default": true, + "description": "Whether to create a TFC run on dispatch. When false, only the workspace and variables are synced.", + "type": "boolean" + }, + "webhookUrl": { + "description": "The ctrlplane API endpoint for TFC webhook notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook).", + "type": "string" } }, "required": [ "address", "organization", "token", - "template" + "template", + "webhookUrl" ], "type": "object" }, diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 380680e06..117d48a20 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -157,11 +157,9 @@ local JobPropertyKeys = std.objectFields(Job.properties); ArgoWorkflowJobAgentConfig: { type: 'object', - required: ['uid', 'serverUrl', 'apiKey'], + required: ['name'], properties: { - serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, - uid: { type: 'string', description: 'ArgoWorkflow job id ' }, - apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + name: { type: 'string', description: 'ArgoWorkflow job name' }, }, }, diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index 5a50bf0e9..2f69a216c 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -239,6 +239,12 @@ type ArgoCDJobAgentConfig struct { Template string `json:"template"` } +// ArgoWorkflowJobAgentConfig defines model for ArgoWorkflowJobAgentConfig. +type ArgoWorkflowJobAgentConfig struct { + // Name ArgoWorkflow job name + Name string `json:"name"` +} + // BasicResource defines model for BasicResource. type BasicResource struct { Id string `json:"id"` @@ -1094,6 +1100,12 @@ type TerraformCloudJobAgentConfig struct { // Token Terraform Cloud API token. Token string `json:"token"` + + // TriggerRunOnChange Whether to create a TFC run on dispatch. When false, only the workspace and variables are synced. + TriggerRunOnChange *bool `json:"triggerRunOnChange,omitempty"` + + // WebhookUrl The ctrlplane API endpoint for TFC webhook notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook). + WebhookUrl string `json:"webhookUrl"` } // TerraformCloudRunMetricProvider defines model for TerraformCloudRunMetricProvider. From ab2327569cea1c554ecba011b78ac8e1135411f6 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 23 Mar 2026 10:18:26 -0400 Subject: [PATCH 03/36] fix: bad typescript --- apps/api/src/routes/argoworkflow/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts index 0eda53e73..2309b1013 100644 --- a/apps/api/src/routes/argoworkflow/index.ts +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -8,7 +8,7 @@ import { handleArgoWorkflow } from "./workflow.js"; export const createArgoWorkflowRouter = (): Router => Router().post("/webhook", asyncHandler(handleWebhookRequest)); -const verifyRequest = async (req: Request): Promise => { +const verifyRequest = (req: Request): boolean => { const authHeader = req.headers["authorization"]?.toString(); if (authHeader == null) return false; const secret = env.ARGO_WORKFLOW_WEBHOOK_SECRET; @@ -16,7 +16,7 @@ const verifyRequest = async (req: Request): Promise => { }; const handleWebhookRequest = async (req: Request, res: Response) => { - const isVerified = await verifyRequest(req); + const isVerified = verifyRequest(req); if (!isVerified) { res.status(401).json({ message: "Unauthorized" }); return; From 1616248ef6c5afaf43e1f08a4ca95062fa0333db Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 23 Mar 2026 11:33:38 -0400 Subject: [PATCH 04/36] fix: same params as argocd --- apps/workspace-engine/oapi/openapi.json | 15 +++++++++++++++ .../oapi/spec/schemas/jobs.jsonnet | 6 +++++- apps/workspace-engine/pkg/oapi/oapi.gen.go | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index d81addf02..2a0fde485 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -55,12 +55,27 @@ }, "ArgoWorkflowJobAgentConfig": { "properties": { + "apiKey": { + "description": "ArgoWorkflow API token.", + "type": "string" + }, "name": { "description": "ArgoWorkflow job name", "type": "string" + }, + "serverUrl": { + "description": "ArgoWorkflow server address (host[:port] or URL).", + "type": "string" + }, + "template": { + "description": "ArgoWorkflow application template.", + "type": "string" } }, "required": [ + "serverUrl", + "apiKey", + "template", "name" ], "type": "object" diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 117d48a20..6cc260774 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -157,9 +157,13 @@ local JobPropertyKeys = std.objectFields(Job.properties); ArgoWorkflowJobAgentConfig: { type: 'object', - required: ['name'], + required: ['serverUrl', 'apiKey', 'template', 'name'], properties: { name: { type: 'string', description: 'ArgoWorkflow job name' }, + serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, + apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + template: { type: 'string', description: 'ArgoWorkflow application template.' }, + }, }, diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index 2f69a216c..3dd300370 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -241,8 +241,17 @@ type ArgoCDJobAgentConfig struct { // ArgoWorkflowJobAgentConfig defines model for ArgoWorkflowJobAgentConfig. type ArgoWorkflowJobAgentConfig struct { + // ApiKey ArgoWorkflow API token. + ApiKey string `json:"apiKey"` + // Name ArgoWorkflow job name Name string `json:"name"` + + // ServerUrl ArgoWorkflow server address (host[:port] or URL). + ServerUrl string `json:"serverUrl"` + + // Template ArgoWorkflow application template. + Template string `json:"template"` } // BasicResource defines model for BasicResource. From 40f6e4f84bc1b73d3db3c86976ad07eb9f9f3b20 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 23 Mar 2026 15:42:39 -0400 Subject: [PATCH 05/36] feat: continue adding argoworkflow jobagent --- apps/api/src/routes/argoworkflow/index.ts | 2 +- .../svc/controllers/jobdispatch/controller.go | 4 + .../jobagents/argo-workflow/workflow.go | 189 ++++++++++++++++++ .../argo-workflow/workflow_submitter.go | 162 +++++++++++++++ 4 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go create mode 100644 apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts index 2309b1013..1513193b1 100644 --- a/apps/api/src/routes/argoworkflow/index.ts +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -9,7 +9,7 @@ export const createArgoWorkflowRouter = (): Router => Router().post("/webhook", asyncHandler(handleWebhookRequest)); const verifyRequest = (req: Request): boolean => { - const authHeader = req.headers["authorization"]?.toString(); + const authHeader = req.headers.authorization?.toString(); if (authHeader == null) return false; const secret = env.ARGO_WORKFLOW_WEBHOOK_SECRET; return authHeader === secret; diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go index 735aa72e7..a61d1e487 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go @@ -17,6 +17,7 @@ import ( "workspace-engine/pkg/reconcile/postgres" "workspace-engine/svc/controllers/jobdispatch/jobagents" "workspace-engine/svc/controllers/jobdispatch/jobagents/argo" + argoworkflow "workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow" "workspace-engine/svc/controllers/jobdispatch/jobagents/github" "workspace-engine/svc/controllers/jobdispatch/jobagents/terraformcloud" "workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner" @@ -117,6 +118,9 @@ func New(workerID string, pgxPool *pgxpool.Pool) *reconcile.Worker { github.New(&github.GoGitHubWorkflowDispatcher{}, pgSetter), ) dispatcher.Register(terraformcloud.New(pgSetter)) + dispatcher.Register( + argoworkflow.New(&argoworkflow.GoWorkflowSubmitter{}, pgSetter), + ) maxConcurrency := config.GetMaxConcurrency(kind) log.Debug( diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go new file mode 100644 index 000000000..41707981b --- /dev/null +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -0,0 +1,189 @@ +package argo_workflows + +import ( + "bytes" + "context" + "fmt" + "regexp" + "strings" + + "workspace-engine/pkg/oapi" + "workspace-engine/pkg/templatefuncs" + "workspace-engine/svc/controllers/jobdispatch/jobagents/types" + + "github.com/goccy/go-yaml" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" +) + +var tracer = otel.Tracer("workspace-engine/jobagents/argo") + +// Workflow is a minimal representation of an Argo Workflows Workflow resource. +// We define this locally to avoid importing argo-workflows/v4, which conflicts +// with argo-cd/v2's transitive dependencies (docker/docker vs moby/moby rename). +type Workflow struct { + Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` + APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` + Metadata WorkflowMetadata `yaml:"metadata,omitempty" json:"metadata,omitempty"` + Spec interface{} `yaml:"spec,omitempty" json:"spec,omitempty"` +} + +type WorkflowMetadata struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` + Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` +} + +var _ types.Dispatchable = &ArgoWorkflow{} + +type Getter interface { + GetWorkflow(ctx context.Context, name string) (*Workflow, error) +} + +// Setter persists job status updates. +type Setter interface { + UpdateJob( + ctx context.Context, + jobID string, + status oapi.JobStatus, + message string, + metadata map[string]string, + ) error +} + +// WorkflowDeleter deletes an Argo Workflows Workflow resource. +type WorkflowDeleter interface { + DeleteWorkflow(ctx context.Context, serverAddr, apiKey, name string) error +} + +// WorkflowSubmitter submits an Argo Workflows Workflow to the server. +type WorkflowSubmitter interface { + SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *Workflow) error +} + +type ArgoWorkflow struct { + setter Setter + submitter WorkflowSubmitter +} + +func New(submitter WorkflowSubmitter, setter Setter) *ArgoWorkflow { + return &ArgoWorkflow{setter: setter, submitter: submitter} +} + +func (a *ArgoWorkflow) Type() string { + return "argo-workflow" +} + +func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { + dispatchCtx := job.DispatchContext + jobAgentConfig := dispatchCtx.JobAgentConfig + serverAddr, apiKey, template, err := ParseJobAgentConfig(jobAgentConfig) + if err != nil { + return fmt.Errorf("failed to parse job agent config: %w", err) + } + + wf, err := TemplateApplication(dispatchCtx, template) + if err != nil { + return fmt.Errorf("failed to generate workflow from template: %w", err) + } + + MakeApplicationK8sCompatible(wf) + + go func() { + parentSpanCtx := trace.SpanContextFromContext(ctx) + asyncCtx, span := tracer.Start(context.Background(), "ArgoWorkflow.AsyncDispatch", + trace.WithLinks(trace.Link{SpanContext: parentSpanCtx}), + ) + defer span.End() + + if err := a.submitter.SubmitWorkflow(asyncCtx, serverAddr, apiKey, wf); err != nil { + _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusFailure, + fmt.Sprintf("failed to submit workflow: %s", err.Error()), nil) + return + } + + metadata := BuildArgoLinks(serverAddr, wf) + _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusInProgress, "", metadata) + }() + + return nil +} + +// ParseJobAgentConfig extracts the required fields from an agent config. +func ParseJobAgentConfig( + config oapi.JobAgentConfig, +) (serverAddr, apiKey, template string, err error) { + serverAddr, ok := config["serverUrl"].(string) + if !ok { + return "", "", "", fmt.Errorf("serverUrl is required") + } + apiKey, ok = config["apiKey"].(string) + if !ok { + return "", "", "", fmt.Errorf("apiKey is required") + } + template, ok = config["template"].(string) + if !ok { + return "", "", "", fmt.Errorf("template is required") + } + if serverAddr == "" || apiKey == "" || template == "" { + return "", "", "", fmt.Errorf("missing required fields in job agent config") + } + return serverAddr, apiKey, template, nil +} + +// TemplateApplication renders the Argo Workflows Workflow YAML template using +// the dispatch context variables. +func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*Workflow, error) { + t, err := templatefuncs.Parse("argoWorkflowAgentConfig", tmpl) + if err != nil { + return nil, fmt.Errorf("failed to parse template: %w", err) + } + var buf bytes.Buffer + if err := t.Execute(&buf, ctx.Map()); err != nil { + return nil, fmt.Errorf("failed to execute template: %w", err) + } + + var workflow Workflow + if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { + return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) + } + return &workflow, nil +} + +// MakeApplicationK8sCompatible sanitises the workflow name and label +// values so they conform to Kubernetes naming rules. +func MakeApplicationK8sCompatible(wf *Workflow) { + wf.Metadata.Name = getK8sCompatibleName(wf.Metadata.Name) + if wf.Metadata.Labels != nil { + for key, value := range wf.Metadata.Labels { + wf.Metadata.Labels[key] = getK8sCompatibleName(value) + } + } +} + +func getK8sCompatibleName(name string) string { + cleaned := strings.ToLower(name) + k8sInvalidCharsRegex := regexp.MustCompile(`[^a-z0-9-]`) + cleaned = k8sInvalidCharsRegex.ReplaceAllString(cleaned, "-") + + if len(cleaned) > 63 { + cleaned = cleaned[:63] + } + cleaned = strings.Trim(cleaned, "-") + if cleaned == "" { + return "default" + } + + return cleaned +} + +// BuildArgoLinks builds the metadata map with an Argo Workflows URL. +func BuildArgoLinks(serverAddr string, wf *Workflow) map[string]string { + appURL := fmt.Sprintf("%s/workflows/%s/%s", serverAddr, wf.Metadata.Namespace, wf.Metadata.Name) + if !strings.HasPrefix(appURL, "https://") { + appURL = "https://" + appURL + } + return map[string]string{ + "ctrlplane/links": fmt.Sprintf(`{"Argo Workflow":"%s"}`, appURL), + } +} diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go new file mode 100644 index 000000000..2a090c02a --- /dev/null +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -0,0 +1,162 @@ +package argo_workflows + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "os" + + "github.com/avast/retry-go" + "github.com/charmbracelet/log" + "github.com/goccy/go-yaml" + + argoapiclient "github.com/argoproj/argo-workflows/v3/pkg/apiclient" + wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" + "sigs.k8s.io/yaml" +) + +type submitWorkflowRequest struct { + Workflow *Workflow `json:"workflow"` +} + +func submitThing() { + ctx := context.Background() + + b, err := os.ReadFile("workflow.yaml") + if err != nil { + panic(err) + } + + var wf wfv1.Workflow + if err := yaml.Unmarshal(b, &wf); err != nil { + panic(err) + } + + fmt.Printf("kind=%q\n", wf.Kind) + fmt.Printf("apiVersion=%q\n", wf.APIVersion) + fmt.Printf("metadata.name=%q\n", wf.Name) + fmt.Printf("metadata.generateName=%q\n", wf.GenerateName) + fmt.Printf("metadata.namespace=%q\n", wf.Namespace) + + ctx, apiClient, err := argoapiclient.NewClientFromOptsWithContext(ctx, argoapiclient.Opts{ + ArgoServerOpts: argoapiclient.ArgoServerOpts{ + URL: "localhost:2746", // host:port only + Secure: true, // HTTPS + HTTP1: true, // avoid gRPC/HTTP2 issues + InsecureSkipVerify: true, + }, + AuthSupplier: func() string { + return "" + }, + }) + if err != nil { + panic(err) + } + wfClient := apiClient.NewWorkflowServiceClient(ctx) + +} + +// GoWorkflowSubmitter is the production implementation of WorkflowSubmitter +// that calls the Argo Workflows REST API. +type GoWorkflowSubmitter struct{} + +func (s *GoWorkflowSubmitter) SubmitWorkflow( + ctx context.Context, + serverAddr, apiKey string, + wf *Workflow, +) error { + namespace := wf.Metadata.Namespace + if namespace == "" { + namespace = "default" + } + + url := fmt.Sprintf( + "%s/api/v1/workflows/%s", + strings.TrimRight(serverAddr, "/"), + namespace, + ) + jsonBody, err := yaml.YAMLToJSON(template) + if err != nil { + panic(err) + } + + client := &http.Client{ + Timeout: 20 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, // local dev only + }, + }, + } + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader()) + if err != nil { + panic(err) + } + req.Header.Set("Content-Type", "application/json") + if token != "" { + req.Header.Set("Authorization", "Bearer "+token) + } + + resp, err := client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + respBody, _ := io.ReadAll(resp.Body) + + return retry.Do( + func() error { + body, err := json.Marshal(submitWorkflowRequest{Workflow: wf}) + if err != nil { + return retry.Unrecoverable(fmt.Errorf("marshal workflow: %w", err)) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return retry.Unrecoverable(fmt.Errorf("create request: %w", err)) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+apiKey) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("submit workflow: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 300 { + respBody, _ := io.ReadAll(resp.Body) + errMsg := fmt.Sprintf("submit workflow: status %d: %s", resp.StatusCode, string(respBody)) + if isRetryableStatusCode(resp.StatusCode) { + return fmt.Errorf("%s", errMsg) + } + return retry.Unrecoverable(fmt.Errorf("%s", errMsg)) + } + + return nil + }, + retry.Attempts(5), + retry.Delay(1*time.Second), + retry.MaxDelay(10*time.Second), + retry.DelayType(retry.BackOffDelay), + retry.OnRetry(func(n uint, err error) { + log.Warn("Retrying Argo Workflow submission", + "attempt", n+1, + "error", err) + }), + retry.Context(ctx), + ) +} + +func isRetryableStatusCode(code int) bool { + return code == 502 || code == 503 || code == 504 +} From 55aac4133c6553aade1ef7120747f0d928c67098 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Tue, 24 Mar 2026 13:28:08 -0400 Subject: [PATCH 06/36] feat: continuing with argo workflow agent --- apps/api/src/routes/argoworkflow/workflow.ts | 4 +- apps/workspace-engine/go.mod | 62 ++- apps/workspace-engine/go.sum | 388 +++++++++++------- .../jobagents/argo-workflow/workflow.go | 50 +-- .../argo-workflow/workflow_submitter.go | 153 ++----- 5 files changed, 358 insertions(+), 299 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index 00b518bab..b782a60d5 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -35,7 +35,7 @@ export const mapTriggerToStatus = (trigger: string): JobStatus | null => export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { const { workflowName, uid, phase, startedAt, finishedAt } = payload; - const jobId = uid; + const jobId = extractUuid(workflowName); if (jobId == null) return; const status = statusMap[phase] ?? null; @@ -50,7 +50,7 @@ export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { .set({ externalId: uid, status, - startedAt: new Date(startedAt), + ...(startedAt ? { startedAt: new Date(startedAt) } : {}), completedAt, updatedAt: new Date(), }) diff --git a/apps/workspace-engine/go.mod b/apps/workspace-engine/go.mod index 8ba29da09..d8a2a15c2 100644 --- a/apps/workspace-engine/go.mod +++ b/apps/workspace-engine/go.mod @@ -1,10 +1,11 @@ module workspace-engine -go 1.25.5 +go 1.25.7 require ( github.com/Masterminds/sprig/v3 v3.3.0 github.com/argoproj/argo-cd/v3 v3.3.4 + github.com/argoproj/argo-workflows/v4 v4.0.3 github.com/avast/retry-go v2.7.0+incompatible github.com/charmbracelet/log v0.4.2 github.com/confluentinc/confluent-kafka-go/v2 v2.13.3 @@ -96,9 +97,9 @@ require ( go.uber.org/mock v0.6.0 // indirect golang.org/x/arch v0.20.0 // indirect golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.32.0 // indirect + golang.org/x/mod v0.33.0 // indirect golang.org/x/sync v0.19.0 - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/grpc v1.79.1 // indirect @@ -109,6 +110,7 @@ require ( cloud.google.com/go/compute/metadata v0.9.0 // indirect cyphar.com/go-pathrs v0.2.1 // indirect dario.cat/mergo v1.0.2 // indirect + filippo.io/edwards25519 v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect @@ -119,8 +121,9 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/argoproj/argo-events v1.9.6 // indirect github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627 // indirect - github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 // indirect + github.com/argoproj/pkg v0.13.7-0.20250123033407-65f2d4777bfd // indirect github.com/argoproj/pkg/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -140,26 +143,30 @@ require ( github.com/charmbracelet/x/term v0.2.1 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/colinmarc/hdfs/v2 v2.4.0 // indirect github.com/containerd/containerd/api v1.10.0 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect - github.com/creack/pty v1.1.24 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/doublerebel/bellows v0.0.0-20160303004610-f177d92a03d3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/evilmonkeyinc/jsonpath v0.8.1 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/expr-lang/expr v1.17.7 // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-git/go-git/v5 v5.14.0 // indirect + github.com/go-git/go-git/v5 v5.16.5 // indirect + github.com/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-openapi/swag/cmdutils v0.25.3 // indirect @@ -174,10 +181,12 @@ require ( github.com/go-openapi/swag/typeutils v0.25.3 // indirect github.com/go-openapi/swag/yamlutils v0.25.3 // indirect github.com/go-redis/cache/v9 v9.0.0 // indirect - github.com/gobwas/glob v0.2.3 // indirect + github.com/go-sql-driver/mysql v1.9.2 // indirect + github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -192,21 +201,32 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-slug v0.16.8 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgtype v1.14.4 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.18.3 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect + github.com/mattn/go-sqlite3 v1.14.28 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -216,6 +236,7 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -228,16 +249,22 @@ require ( github.com/prometheus/procfs v0.17.0 // indirect github.com/r3labs/diff/v3 v3.0.2 // indirect github.com/redis/go-redis/v9 v9.8.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/robfig/cron/v3 v3.0.2-0.20210106135023-bc59245fe10e // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/segmentio/fasthash v1.0.3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sethvargo/go-limiter v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.4 // indirect github.com/skeema/knownhosts v1.3.1 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cast v1.9.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/testcontainers/testcontainers-go/modules/compose v0.41.0 // indirect + github.com/upper/db/v4 v4.10.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect github.com/vmihailenco/go-tinylfu v0.2.2 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -246,16 +273,18 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.58.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect + golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -275,13 +304,18 @@ require ( k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/kubectl v0.34.0 // indirect k8s.io/kubernetes v1.34.2 // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect + modernc.org/libc v1.65.8 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.37.1 // indirect oras.land/oras-go/v2 v2.6.0 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c // indirect + zombiezen.com/go/sqlite v1.4.2 // indirect ) tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen diff --git a/apps/workspace-engine/go.sum b/apps/workspace-engine/go.sum index c61ac5c69..307256a44 100644 --- a/apps/workspace-engine/go.sum +++ b/apps/workspace-engine/go.sum @@ -2,13 +2,15 @@ cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= +filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= @@ -19,13 +21,6 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDo github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= @@ -39,6 +34,7 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= @@ -46,11 +42,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= @@ -65,17 +58,20 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7D github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/argoproj/argo-cd/v3 v3.3.4 h1:3dFCh1b8QaqHv78QZPaH59zdZnu6wRvUpQtXmklLfUY= github.com/argoproj/argo-cd/v3 v3.3.4/go.mod h1:RqLNOqYHufjRTFcKIpdf27c1zvrze+fS5I9VVoJJciE= +github.com/argoproj/argo-events v1.9.6 h1:tQTyUmMt0/4UI+9fbXrmK1/h9oalV7KBCC3YgPI7qz0= +github.com/argoproj/argo-events v1.9.6/go.mod h1:MkJI9UXTLnLOFX6LKo0rC1tnvWfLFzKkGigsdfu58SA= +github.com/argoproj/argo-workflows/v4 v4.0.3 h1:KksplD7dY6tYcyLxqL+Uh/1jNNyKsfx1N+Frw0uF38I= +github.com/argoproj/argo-workflows/v4 v4.0.3/go.mod h1:hxJyAXSEr8abuoUHTcQrnh/QNh7O4FRgzGOJFw0wGMo= github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627 h1:yntvA+uaFz62HRfWGGwlvs4ErdxoLQjCpDXufdEt2FI= github.com/argoproj/gitops-engine v0.7.1-0.20250908182407-97ad5b59a627/go.mod h1:yJ3t/GRn9Gx2LEyMrh9X0roL7zzVlk3nvuJt6G1o6jI= -github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo= -github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1/go.mod h1:CZHlkyAD1/+FbEn6cB2DQTj48IoLGvEYsWEvtzP3238= +github.com/argoproj/pkg v0.13.7-0.20250123033407-65f2d4777bfd h1:lGvauSky5XrqNhzzL078KqR/I+65/KNP5IcXqTEIZ5c= +github.com/argoproj/pkg v0.13.7-0.20250123033407-65f2d4777bfd/go.mod h1:UzNnTJT+8Fv5oc1LB2pcgXiUF+n9n+tulbaON2EBgJo= github.com/argoproj/pkg/v2 v2.0.1 h1:O/gCETzB/3+/hyFL/7d/VM/6pSOIRWIiBOTb2xqAHvc= github.com/argoproj/pkg/v2 v2.0.1/go.mod h1:sdifF6sUTx9ifs38ZaiNMRJuMpSCBB9GulHfbPgQeRE= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/avast/retry-go v2.7.0+incompatible h1:XaGnzl7gESAideSjr+I8Hki/JBi+Yb9baHlMRPeSC84= github.com/avast/retry-go v2.7.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -138,11 +134,14 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/colinmarc/hdfs/v2 v2.4.0 h1:v6R8oBx/Wu9fHpdPoJJjpGSUxo8NhHIwrwsfhFvU9W0= +github.com/colinmarc/hdfs/v2 v2.4.0/go.mod h1:0NAO+/3knbMx6+5pCv+Hcbaz4xn/Zzbn9+WIib2rKVI= github.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU= github.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg= github.com/confluentinc/confluent-kafka-go/v2 v2.13.3 h1:8HzftqswgcQDX/4WkUMDgC4mPmpq8ZrYIzS0Fm5BKWs= @@ -170,10 +169,12 @@ github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++ github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= @@ -185,7 +186,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38= github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -208,7 +208,8 @@ github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pM github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/doublerebel/bellows v0.0.0-20160303004610-f177d92a03d3 h1:7nllYTGLnq4CqBL27lV6oNfXzM2tJ2mrKF8E+aBXOV0= +github.com/doublerebel/bellows v0.0.0-20160303004610-f177d92a03d3/go.mod h1:v/MTKot4he5oRHGirOYGN4/hEOONNnWtDBLAzllSGMw= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= @@ -218,10 +219,8 @@ github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -230,18 +229,20 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/evilmonkeyinc/jsonpath v0.8.1 h1:W8K4t8u7aipkQE0hcTICGAdAN0Xph349LtjgSoofvVo= +github.com/evilmonkeyinc/jsonpath v0.8.1/go.mod h1:EQhs0ZsoD4uD56ZJbO30gMTfHLQ6DEa0/5rT5Ymy42s= github.com/exaring/otelpgx v0.9.3 h1:4yO02tXC7ZJZ+hcqcUkfxblYNCIFGVhpUWI0iw1TzPU= github.com/exaring/otelpgx v0.9.3/go.mod h1:R5/M5LWsPPBZc1SrRE5e0DiU48bI78C1/GPTWs6I66U= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= +github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -260,7 +261,6 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= @@ -278,14 +278,16 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= +github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= +github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= @@ -294,16 +296,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s= github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8= github.com/go-openapi/swag/cmdutils v0.25.3 h1:EIwGxN143JCThNHnqfqs85R8lJcJG06qjJRZp3VvjLI= @@ -346,6 +344,9 @@ github.com/go-playground/webhooks/v6 v6.4.0 h1:KLa6y7bD19N48rxJDHM0DpE3T4grV7GxM github.com/go-playground/webhooks/v6 v6.4.0/go.mod h1:5lBxopx+cAJiBI4+kyRbuHrEi+hYRDdRHuRR4Ya5Ums= github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI= +github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= +github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= @@ -354,17 +355,17 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe h1:zn8tqiUbec4wR94o7Qj3LZCAT6uGobhEgnDRg6isG5U= +github.com/gobwas/glob v0.2.4-0.20181002190808-e7a84e9525fe/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogits/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85 h1:04sojTxgYxu1L4Hn7Tgf7UVtIosVa6CuHtvNY+7T1K4= github.com/gogits/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= @@ -372,15 +373,12 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= @@ -394,8 +392,6 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= @@ -421,25 +417,22 @@ github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIq github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.1-0.20241114170450-2d3c2a9cc518 h1:UBg1xk+oAsIVbFuGg6hdfAm7EvCv3EL80vFxJNsslqw= github.com/google/uuid v1.6.1-0.20241114170450-2d3c2a9cc518/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= @@ -462,48 +455,103 @@ github.com/hashicorp/go-slug v0.16.8 h1:f4/sDZqRsxx006HrE6e9BE5xO9lWXydKhVoH6Kb0 github.com/hashicorp/go-slug v0.16.8/go.mod h1:hB4mUcVHl4RPu0205s0fwmB9i31MxQgeafGkko3FD+Y= github.com/hashicorp/go-tfe v1.97.0 h1:UmIUUPuWAVPKxTa9N5qTUWd7FblKbgLq+HBio1X7/Qc= github.com/hashicorp/go-tfe v1.97.0/go.mod h1:nmGZMS3pdU7gPPmoe1xYhzU9O2BmasV36XggDOSCDW0= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e h1:xwy/1T0cxHWaLx2MM0g4BlaQc1BXn/9835mPrBqwSPU= github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= +github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= +github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.4 h1:fKuNiCumbKTAIxQwXfB/nsrnkEI6bPJrrSiMKgbJ2j8= +github.com/jackc/pgtype v1.14.4/go.mod h1:aKeozOde08iifGosdJpz9MBZonJOUJxqNpPBcMJTlVA= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -513,23 +561,23 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -539,6 +587,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= @@ -549,20 +603,23 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.58/go.mod h1:NUDy4A4oXPq1l2yK6LTSvCEzAMeIcoz9lcj5dbzSrRE= -github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -608,8 +665,6 @@ github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFL github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= @@ -621,11 +676,12 @@ github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -637,10 +693,7 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= -github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= @@ -655,7 +708,6 @@ github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJo github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= @@ -687,6 +739,7 @@ github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -714,37 +767,49 @@ github.com/r3labs/diff/v3 v3.0.2/go.mod h1:Cy542hv0BAEmhDYWtGxXRQ4kqRsVIcEjG9gCh github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.2-0.20210106135023-bc59245fe10e h1:0xChnl3lhHiXbgSJKgChye0D+DvoItkOdkGcwelDXH0= github.com/robfig/cron/v3 v3.0.2-0.20210106135023-bc59245fe10e/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/secure-systems-lab/go-securesystemslib v0.9.1 h1:nZZaNz4DiERIQguNy0cL5qTdn9lR8XKHf4RUyG1Sx3g= github.com/secure-systems-lab/go-securesystemslib v0.9.1/go.mod h1:np53YzT0zXGMv6x4iEWc9Z59uR+x+ndLwCLqPYpLXVU= +github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sethvargo/go-limiter v1.0.0 h1:JqW13eWEMn0VFv86OKn8wiYJY/m250WoXdrjRV0kLe4= +github.com/sethvargo/go-limiter v1.0.0/go.mod h1:01b6tW25Ap+MeLYBuD4aHunMrJoNO5PVUFdS9rac3II= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI= github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sigstore/sigstore v1.10.4 h1:ytOmxMgLdcUed3w1SbbZOgcxqwMG61lh1TmZLN+WeZE= github.com/sigstore/sigstore v1.10.4/go.mod h1:tDiyrdOref3q6qJxm2G+JHghqfmvifB7hw+EReAfnbI= github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7 h1:94NLPmq4bxvdmslzcG670IOkrlS98CGpmob8cjpFHuI= github.com/sigstore/sigstore-go v1.1.4-0.20251124094504-b5fe07a5a7d7/go.mod h1:4r/PNX0G7uzkLpc3PSdYs5E2k4bWEJNXTK6kwAyw9TM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= @@ -755,14 +820,10 @@ github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -770,6 +831,8 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -783,7 +846,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= @@ -818,6 +880,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/upper/db/v4 v4.10.0 h1:u5fdqcFZAOwUZWtkS0ueQttecKcSpVF8qmBwZesS9nc= +github.com/upper/db/v4 v4.10.0/go.mod h1:s3qHxKIKvqZNZBG5jrAPufMUXqCBmMdIHa7buGfR+OU= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI= github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q= github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= @@ -839,13 +907,14 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= @@ -858,7 +927,6 @@ go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCu go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.42.0 h1:ZtfnDL+tUrs1F0Pzfwbg2d59Gru9NCH3bgSHBM6LDwU= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= @@ -869,6 +937,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4D go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= @@ -879,10 +949,21 @@ go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -891,35 +972,42 @@ go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= -golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0= +golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -928,12 +1016,14 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -947,19 +1037,16 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -968,24 +1055,30 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -993,7 +1086,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1003,9 +1095,10 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1014,14 +1107,13 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= @@ -1029,50 +1121,50 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= @@ -1104,6 +1196,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -1116,7 +1209,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -1125,21 +1217,18 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.17.8/go.mod h1:N++Llhs8kCixMUoCaXXAyMMPbo8dDVnh+IQ36xZV2/0= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= -k8s.io/apimachinery v0.17.8/go.mod h1:Lg8zZ5iC/O8UjCqW6DNhcQG2m4TdjF9kwG3891OWbbA= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= k8s.io/cli-runtime v0.34.0 h1:N2/rUlJg6TMEBgtQ3SDRJwa8XyKUizwjlOknT1mB2Cw= k8s.io/cli-runtime v0.34.0/go.mod h1:t/skRecS73Piv+J+FmWIQA2N2/rDjdYSQzEE67LUUs8= -k8s.io/client-go v0.17.8/go.mod h1:SJsDS64AAtt9VZyeaQMb4Ck5etCitZ/FwajWdzua5eY= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= @@ -1148,40 +1237,57 @@ k8s.io/component-helpers v0.34.0 h1:5T7P9XGMoUy1JDNKzHf0p/upYbeUf8ZaSf9jbx0QlIo= k8s.io/component-helpers v0.34.0/go.mod h1:kaOyl5tdtnymriYcVZg4uwDBe2d1wlIpXyDkt6sVnt4= k8s.io/controller-manager v0.34.0 h1:oCHoqS8dcFp7zDSu7HUvTpakq3isSxil3GprGGlJMsE= k8s.io/controller-manager v0.34.0/go.mod h1:XFto21U+Mm9BT8r/Jd5E4tHCGtwjKAUFOuDcqaj2VK0= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-aggregator v0.34.0 h1:XE4u+HOYkj0g44sblhTtPv+QyIIK7sJxrIlia0731kE= k8s.io/kube-aggregator v0.34.0/go.mod h1:GIUqdChXVC448Vp2Wgxf0m6fir7Xt3A2TAZcs2JNG1Y= -k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/kubectl v0.34.0 h1:NcXz4TPTaUwhiX4LU+6r6udrlm0NsVnSkP3R9t0dmxs= k8s.io/kubectl v0.34.0/go.mod h1:bmd0W5i+HuG7/p5sqicr0Li0rR2iIhXL0oUyLF3OjR4= k8s.io/kubernetes v1.34.2 h1:WQdDvYJazkmkwSncgNwGvVtaCt4TYXIU3wSMRgvp3MI= k8s.io/kubernetes v1.34.2/go.mod h1:m6pZk6a179pRo2wsTiCPORJ86iOEQmfIzUvtyEF8BwA= -k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= +k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8= +modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.65.8 h1:7PXRJai0TXZ8uNA3srsmYzmTyrLoHImV5QxHeni108Q= +modernc.org/libc v1.65.8/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= +modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c h1:RCkxmWwPjOw2O1RiDgBgI6tfISvB07jAh+GEztp7TWk= sigs.k8s.io/structured-merge-diff/v6 v6.3.1-0.20251003215857-446d8398e19c/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY= tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q= +zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo= +zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc= diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 41707981b..28f79e66c 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -11,33 +11,18 @@ import ( "workspace-engine/pkg/templatefuncs" "workspace-engine/svc/controllers/jobdispatch/jobagents/types" + wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) -var tracer = otel.Tracer("workspace-engine/jobagents/argo") +var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") -// Workflow is a minimal representation of an Argo Workflows Workflow resource. -// We define this locally to avoid importing argo-workflows/v4, which conflicts -// with argo-cd/v2's transitive dependencies (docker/docker vs moby/moby rename). -type Workflow struct { - Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` - APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` - Metadata WorkflowMetadata `yaml:"metadata,omitempty" json:"metadata,omitempty"` - Spec interface{} `yaml:"spec,omitempty" json:"spec,omitempty"` -} - -type WorkflowMetadata struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` - Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` -} - -var _ types.Dispatchable = &ArgoWorkflow{} +var _ types.Dispatchable = (*ArgoWorkflow)(nil) type Getter interface { - GetWorkflow(ctx context.Context, name string) (*Workflow, error) + GetWorkflow(ctx context.Context, name string) (*wfv1.Workflow, error) } // Setter persists job status updates. @@ -58,7 +43,7 @@ type WorkflowDeleter interface { // WorkflowSubmitter submits an Argo Workflows Workflow to the server. type WorkflowSubmitter interface { - SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *Workflow) error + SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *wfv1.Workflow) error } type ArgoWorkflow struct { @@ -76,8 +61,12 @@ func (a *ArgoWorkflow) Type() string { func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { dispatchCtx := job.DispatchContext + if dispatchCtx == nil { + return fmt.Errorf("job %s has no dispatch context", job.Id) + } jobAgentConfig := dispatchCtx.JobAgentConfig serverAddr, apiKey, template, err := ParseJobAgentConfig(jobAgentConfig) + fmt.Printf("jobConfig: %+v\n", jobAgentConfig) if err != nil { return fmt.Errorf("failed to parse job agent config: %w", err) } @@ -87,6 +76,7 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { return fmt.Errorf("failed to generate workflow from template: %w", err) } + wf.Name = job.Id MakeApplicationK8sCompatible(wf) go func() { @@ -125,7 +115,7 @@ func ParseJobAgentConfig( if !ok { return "", "", "", fmt.Errorf("template is required") } - if serverAddr == "" || apiKey == "" || template == "" { + if serverAddr == "" || template == "" { return "", "", "", fmt.Errorf("missing required fields in job agent config") } return serverAddr, apiKey, template, nil @@ -133,7 +123,7 @@ func ParseJobAgentConfig( // TemplateApplication renders the Argo Workflows Workflow YAML template using // the dispatch context variables. -func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*Workflow, error) { +func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*wfv1.Workflow, error) { t, err := templatefuncs.Parse("argoWorkflowAgentConfig", tmpl) if err != nil { return nil, fmt.Errorf("failed to parse template: %w", err) @@ -143,7 +133,7 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*Workflow, err return nil, fmt.Errorf("failed to execute template: %w", err) } - var workflow Workflow + var workflow wfv1.Workflow if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } @@ -152,11 +142,11 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*Workflow, err // MakeApplicationK8sCompatible sanitises the workflow name and label // values so they conform to Kubernetes naming rules. -func MakeApplicationK8sCompatible(wf *Workflow) { - wf.Metadata.Name = getK8sCompatibleName(wf.Metadata.Name) - if wf.Metadata.Labels != nil { - for key, value := range wf.Metadata.Labels { - wf.Metadata.Labels[key] = getK8sCompatibleName(value) +func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { + wf.Name = getK8sCompatibleName(wf.Name) + if wf.Labels != nil { + for key, value := range wf.Labels { + wf.Labels[key] = getK8sCompatibleName(value) } } } @@ -178,8 +168,8 @@ func getK8sCompatibleName(name string) string { } // BuildArgoLinks builds the metadata map with an Argo Workflows URL. -func BuildArgoLinks(serverAddr string, wf *Workflow) map[string]string { - appURL := fmt.Sprintf("%s/workflows/%s/%s", serverAddr, wf.Metadata.Namespace, wf.Metadata.Name) +func BuildArgoLinks(serverAddr string, wf *wfv1.Workflow) map[string]string { + appURL := fmt.Sprintf("%s/workflows/%s/%s", serverAddr, wf.Namespace, wf.Name) if !strings.HasPrefix(appURL, "https://") { appURL = "https://" + appURL } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go index 2a090c02a..3fd935723 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -1,147 +1,64 @@ package argo_workflows import ( - "bytes" "context" - "crypto/tls" - "encoding/json" "fmt" - "io" - "net/http" "strings" "time" - "os" - "github.com/avast/retry-go" "github.com/charmbracelet/log" - "github.com/goccy/go-yaml" - argoapiclient "github.com/argoproj/argo-workflows/v3/pkg/apiclient" - wfv1 "github.com/argoproj/argo-workflows/v3/pkg/apis/workflow/v1alpha1" - "sigs.k8s.io/yaml" + argoapiclient "github.com/argoproj/argo-workflows/v4/pkg/apiclient" + workflowpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/workflow" + wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" ) -type submitWorkflowRequest struct { - Workflow *Workflow `json:"workflow"` -} - -func submitThing() { - ctx := context.Background() - - b, err := os.ReadFile("workflow.yaml") - if err != nil { - panic(err) - } - - var wf wfv1.Workflow - if err := yaml.Unmarshal(b, &wf); err != nil { - panic(err) - } +// GoWorkflowSubmitter is the production implementation of WorkflowSubmitter +// that calls the Argo Workflows REST API. +type GoWorkflowSubmitter struct{} - fmt.Printf("kind=%q\n", wf.Kind) - fmt.Printf("apiVersion=%q\n", wf.APIVersion) - fmt.Printf("metadata.name=%q\n", wf.Name) - fmt.Printf("metadata.generateName=%q\n", wf.GenerateName) - fmt.Printf("metadata.namespace=%q\n", wf.Namespace) +func (s *GoWorkflowSubmitter) SubmitWorkflow( + ctx context.Context, + serverAddr, apiKey string, + wf *wfv1.Workflow, +) error { ctx, apiClient, err := argoapiclient.NewClientFromOptsWithContext(ctx, argoapiclient.Opts{ ArgoServerOpts: argoapiclient.ArgoServerOpts{ - URL: "localhost:2746", // host:port only - Secure: true, // HTTPS - HTTP1: true, // avoid gRPC/HTTP2 issues + URL: serverAddr, + Secure: true, + HTTP1: true, InsecureSkipVerify: true, }, AuthSupplier: func() string { - return "" + return apiKey }, }) if err != nil { - panic(err) + return fmt.Errorf("failed to create argo client: %w", err) } - wfClient := apiClient.NewWorkflowServiceClient(ctx) + fmt.Printf("serverAddr: %s, apiKey: %s\n", serverAddr, apiKey) -} - -// GoWorkflowSubmitter is the production implementation of WorkflowSubmitter -// that calls the Argo Workflows REST API. -type GoWorkflowSubmitter struct{} - -func (s *GoWorkflowSubmitter) SubmitWorkflow( - ctx context.Context, - serverAddr, apiKey string, - wf *Workflow, -) error { - namespace := wf.Metadata.Namespace + wfClient := apiClient.NewWorkflowServiceClient(ctx) + namespace := wf.Namespace if namespace == "" { namespace = "default" } - url := fmt.Sprintf( - "%s/api/v1/workflows/%s", - strings.TrimRight(serverAddr, "/"), - namespace, - ) - jsonBody, err := yaml.YAMLToJSON(template) - if err != nil { - panic(err) - } - - client := &http.Client{ - Timeout: 20 * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, // local dev only - }, - }, - } - - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader()) - if err != nil { - panic(err) - } - req.Header.Set("Content-Type", "application/json") - if token != "" { - req.Header.Set("Authorization", "Bearer "+token) - } - - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() - - respBody, _ := io.ReadAll(resp.Body) - return retry.Do( func() error { - body, err := json.Marshal(submitWorkflowRequest{Workflow: wf}) - if err != nil { - return retry.Unrecoverable(fmt.Errorf("marshal workflow: %w", err)) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) - if err != nil { - return retry.Unrecoverable(fmt.Errorf("create request: %w", err)) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+apiKey) + _, err := wfClient.CreateWorkflow(ctx, &workflowpkg.WorkflowCreateRequest{ + Namespace: namespace, + Workflow: wf, + }) - resp, err := http.DefaultClient.Do(req) if err != nil { - return fmt.Errorf("submit workflow: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode >= 300 { - respBody, _ := io.ReadAll(resp.Body) - errMsg := fmt.Sprintf("submit workflow: status %d: %s", resp.StatusCode, string(respBody)) - if isRetryableStatusCode(resp.StatusCode) { - return fmt.Errorf("%s", errMsg) + if isRetryableError(err) { + return err } - return retry.Unrecoverable(fmt.Errorf("%s", errMsg)) + return retry.Unrecoverable(err) } - return nil }, retry.Attempts(5), @@ -149,7 +66,7 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( retry.MaxDelay(10*time.Second), retry.DelayType(retry.BackOffDelay), retry.OnRetry(func(n uint, err error) { - log.Warn("Retrying Argo Workflow submission", + log.Warn("Retrying ArgoWorkflow submission", "attempt", n+1, "error", err) }), @@ -157,6 +74,18 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( ) } -func isRetryableStatusCode(code int) bool { - return code == 502 || code == 503 || code == 504 +func isRetryableError(err error) bool { + if err == nil { + return false + } + errStr := err.Error() + return strings.Contains(errStr, "502") || + strings.Contains(errStr, "503") || + strings.Contains(errStr, "504") || + strings.Contains(errStr, "connection refused") || + strings.Contains(errStr, "connection reset") || + strings.Contains(errStr, "timeout") || + strings.Contains(errStr, "temporarily unavailable") || + strings.Contains(errStr, "EOF") || + strings.Contains(errStr, "Unavailable") } From efc4a84d617c43a20924caeafb8e161d1fc27592 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Tue, 24 Mar 2026 14:50:33 -0400 Subject: [PATCH 07/36] {WIP} --- apps/api/src/routes/argoworkflow/workflow.ts | 10 +++++++++- packages/trpc/src/routes/deployments.ts | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index b782a60d5..7dff77b28 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -11,6 +11,7 @@ interface ArgoWorkflowPayload { createdAt: string; startedAt: string; finishedAt: string | null; + jobId: string | null; phase: string; eventType: string; } @@ -32,10 +33,17 @@ const extractUuid = (str: string) => { export const mapTriggerToStatus = (trigger: string): JobStatus | null => statusMap[trigger] ?? null; +export const getJobId = (payload: ArgoWorkflowPayload): string => { + if (payload.jobId != null && payload.jobId !== "") { + return payload.jobId; + } + return payload.workflowName; +}; + export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { const { workflowName, uid, phase, startedAt, finishedAt } = payload; - const jobId = extractUuid(workflowName); + const jobId = getJobId(payload); if (jobId == null) return; const status = statusMap[phase] ?? null; diff --git a/packages/trpc/src/routes/deployments.ts b/packages/trpc/src/routes/deployments.ts index 6737050ec..19f151dba 100644 --- a/packages/trpc/src/routes/deployments.ts +++ b/packages/trpc/src/routes/deployments.ts @@ -29,6 +29,10 @@ const deploymentTfeConfig = z.object({ template: z.string(), }); +const deploymentArgoWorkflowConfig = z.object({ + template: z.string(), +}); + const deploymentCustomConfig = z.object({}).passthrough(); const deploymentJobAgentConfig = z.union([ @@ -36,6 +40,7 @@ const deploymentJobAgentConfig = z.union([ deploymentArgoCdConfig, deploymentTfeConfig, deploymentCustomConfig, + deploymentArgoWorkflowConfig, ]); const getAgentsArrayWithLegacyAgent = ( @@ -507,6 +512,10 @@ export const deploymentsRouter = router({ const tfeResult = deploymentTfeConfig.safeParse(config); if (tfeResult.success) return { ...tfeResult.data, type: "tfe" as const }; + const argoWorkflowResult = + deploymentArgoWorkflowConfig.safeParse(config); + if (argoWorkflowResult.success) + return { ...argoWorkflowResult.data, type: "argo-workflow" as const }; return { ...config, type: "custom" as const }; }; From 59562301901428d1001f73b635aa9c874730a7e5 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Tue, 24 Mar 2026 15:05:54 -0400 Subject: [PATCH 08/36] feat: use generateName on workflow * to have some ability to have a normal name, also stores the job id on the workflow in a label so it's searchable in the workflow ui in argo workflows --- .../jobagents/argo-workflow/workflow.go | 21 +++++++++++++------ .../argo-workflow/workflow_submitter.go | 21 ++++++++++--------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 28f79e66c..a1a2c497d 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -43,7 +43,7 @@ type WorkflowDeleter interface { // WorkflowSubmitter submits an Argo Workflows Workflow to the server. type WorkflowSubmitter interface { - SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *wfv1.Workflow) error + SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *wfv1.Workflow) (*wfv1.Workflow, error) } type ArgoWorkflow struct { @@ -66,7 +66,6 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { } jobAgentConfig := dispatchCtx.JobAgentConfig serverAddr, apiKey, template, err := ParseJobAgentConfig(jobAgentConfig) - fmt.Printf("jobConfig: %+v\n", jobAgentConfig) if err != nil { return fmt.Errorf("failed to parse job agent config: %w", err) } @@ -76,7 +75,11 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { return fmt.Errorf("failed to generate workflow from template: %w", err) } - wf.Name = job.Id + if wf.Labels == nil { + wf.Labels = map[string]string{} + } + + wf.Labels["job-id"] = job.Id MakeApplicationK8sCompatible(wf) go func() { @@ -86,13 +89,14 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { ) defer span.End() - if err := a.submitter.SubmitWorkflow(asyncCtx, serverAddr, apiKey, wf); err != nil { + created, err := a.submitter.SubmitWorkflow(asyncCtx, serverAddr, apiKey, wf) + if err != nil { _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusFailure, fmt.Sprintf("failed to submit workflow: %s", err.Error()), nil) return } - metadata := BuildArgoLinks(serverAddr, wf) + metadata := BuildArgoLinks(serverAddr, created) _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusInProgress, "", metadata) }() @@ -143,7 +147,12 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*wfv1.Workflow // MakeApplicationK8sCompatible sanitises the workflow name and label // values so they conform to Kubernetes naming rules. func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { - wf.Name = getK8sCompatibleName(wf.Name) + if wf.Name != "" { + wf.Name = getK8sCompatibleName(wf.Name) + } + if wf.GenerateName != "" { + wf.GenerateName = getK8sCompatibleName(wf.GenerateName) + } if wf.Labels != nil { for key, value := range wf.Labels { wf.Labels[key] = getK8sCompatibleName(value) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go index 3fd935723..faefbddb8 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -22,7 +22,7 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( ctx context.Context, serverAddr, apiKey string, wf *wfv1.Workflow, -) error { +) (*wfv1.Workflow, error) { ctx, apiClient, err := argoapiclient.NewClientFromOptsWithContext(ctx, argoapiclient.Opts{ ArgoServerOpts: argoapiclient.ArgoServerOpts{ @@ -36,28 +36,28 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( }, }) if err != nil { - return fmt.Errorf("failed to create argo client: %w", err) + return nil, fmt.Errorf("failed to create argo client: %w", err) } - fmt.Printf("serverAddr: %s, apiKey: %s\n", serverAddr, apiKey) - wfClient := apiClient.NewWorkflowServiceClient(ctx) namespace := wf.Namespace if namespace == "" { namespace = "default" } - return retry.Do( + var created *wfv1.Workflow + err = retry.Do( func() error { - _, err := wfClient.CreateWorkflow(ctx, &workflowpkg.WorkflowCreateRequest{ + var createdErr error + created, createdErr = wfClient.CreateWorkflow(ctx, &workflowpkg.WorkflowCreateRequest{ Namespace: namespace, Workflow: wf, }) - if err != nil { - if isRetryableError(err) { - return err + if createdErr != nil { + if isRetryableError(createdErr) { + return createdErr } - return retry.Unrecoverable(err) + return retry.Unrecoverable(createdErr) } return nil }, @@ -72,6 +72,7 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( }), retry.Context(ctx), ) + return created, err } func isRetryableError(err error) bool { From 7a442bff37bddc01d581394a670fc8f6f45f556d Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Tue, 24 Mar 2026 16:46:31 -0400 Subject: [PATCH 09/36] test: add some tests * adds some tests for generating and sanitizing workflow names based upon if we're using the auto generate feature of the workflow --- .../jobagents/argo-workflow/workflow.go | 17 +- .../jobagents/argo-workflow/workflow_test.go | 419 ++++++++++++++++++ 2 files changed, 427 insertions(+), 9 deletions(-) create mode 100644 apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index a1a2c497d..84d940d86 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -148,31 +148,30 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*wfv1.Workflow // values so they conform to Kubernetes naming rules. func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { if wf.Name != "" { - wf.Name = getK8sCompatibleName(wf.Name) + wf.Name = getK8sCompatibleName(wf.Name, false) } if wf.GenerateName != "" { - wf.GenerateName = getK8sCompatibleName(wf.GenerateName) + wf.GenerateName = getK8sCompatibleName(wf.GenerateName, true) } if wf.Labels != nil { for key, value := range wf.Labels { - wf.Labels[key] = getK8sCompatibleName(value) + wf.Labels[key] = getK8sCompatibleName(value, false) } } } -func getK8sCompatibleName(name string) string { +func getK8sCompatibleName(name string, generated bool) string { cleaned := strings.ToLower(name) k8sInvalidCharsRegex := regexp.MustCompile(`[^a-z0-9-]`) cleaned = k8sInvalidCharsRegex.ReplaceAllString(cleaned, "-") - if len(cleaned) > 63 { cleaned = cleaned[:63] } - cleaned = strings.Trim(cleaned, "-") - if cleaned == "" { - return "default" + if !generated { + cleaned = strings.Trim(cleaned, "-") + } else { + cleaned = strings.TrimLeft(cleaned, "-") } - return cleaned } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go new file mode 100644 index 000000000..451ffbcac --- /dev/null +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -0,0 +1,419 @@ +package argo_workflows + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "workspace-engine/pkg/oapi" + + wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ----- Mocks ----- + +type mockSubmitter struct { + mu sync.Mutex + calls []submitCall + err error + result *wfv1.Workflow +} + +type submitCall struct { + ServerAddr string + APIKey string + Workflow *wfv1.Workflow +} + +func (m *mockSubmitter) SubmitWorkflow( + _ context.Context, + serverAddr, apiKey string, + wf *wfv1.Workflow, +) (*wfv1.Workflow, error) { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, submitCall{ServerAddr: serverAddr, APIKey: apiKey, Workflow: wf}) + if m.result != nil { + return m.result, m.err + } + // Echo back the submitted workflow by default. + return wf, m.err +} + +func (m *mockSubmitter) getCalls() []submitCall { + m.mu.Lock() + defer m.mu.Unlock() + out := make([]submitCall, len(m.calls)) + copy(out, m.calls) + return out +} + +type mockSetter struct { + mu sync.Mutex + calls []updateCall + err error +} + +type updateCall struct { + JobID string + Status oapi.JobStatus + Message string + Metadata map[string]string +} + +func (m *mockSetter) UpdateJob( + _ context.Context, + jobID string, + status oapi.JobStatus, + message string, + metadata map[string]string, +) error { + m.mu.Lock() + defer m.mu.Unlock() + m.calls = append(m.calls, updateCall{JobID: jobID, Status: status, Message: message, Metadata: metadata}) + return m.err +} + +func (m *mockSetter) getCalls() []updateCall { + m.mu.Lock() + defer m.mu.Unlock() + out := make([]updateCall, len(m.calls)) + copy(out, m.calls) + return out +} + +// ----- Helpers ----- + +const minimalWorkflowTemplate = ` +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: my-workflow- + namespace: argo +spec: + entrypoint: whalesay + templates: + - name: whalesay + container: + image: docker/whalesay + command: [cowsay] + args: ["hello world"] +` + +func validConfig() oapi.JobAgentConfig { + return oapi.JobAgentConfig{ + "serverUrl": "https://argo.example.com", + "apiKey": "secret-token", + "template": minimalWorkflowTemplate, + } +} + +func newTestJob(id string, cfg oapi.JobAgentConfig) *oapi.Job { + return &oapi.Job{ + Id: id, + Status: oapi.JobStatusPending, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Metadata: map[string]string{}, + JobAgentConfig: cfg, + DispatchContext: &oapi.DispatchContext{ + JobAgentConfig: cfg, + }, + } +} + +// ----- Type ----- + +func TestType(t *testing.T) { + a := New(&mockSubmitter{}, &mockSetter{}) + assert.Equal(t, "argo-workflow", a.Type()) +} + +// ----- Dispatch ----- + +func TestDispatch_Success_SubmitsWorkflow(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-1", validConfig()) + err := a.Dispatch(context.Background(), job) + require.NoError(t, err) + + assert.Eventually(t, func() bool { + return len(sub.getCalls()) == 1 + }, time.Second, 10*time.Millisecond) + + call := sub.getCalls()[0] + assert.Equal(t, "https://argo.example.com", call.ServerAddr) + assert.Equal(t, "secret-token", call.APIKey) + assert.NotNil(t, call.Workflow) +} + +func TestDispatch_Success_SetsJobInProgress(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-2", validConfig()) + err := a.Dispatch(context.Background(), job) + require.NoError(t, err) + + assert.Eventually(t, func() bool { + return len(setter.getCalls()) == 1 + }, time.Second, 10*time.Millisecond) + + call := setter.getCalls()[0] + assert.Equal(t, "job-2", call.JobID) + assert.Equal(t, oapi.JobStatusInProgress, call.Status) +} + +func TestDispatch_Success_MetadataContainsArgoLink(t *testing.T) { + created := &wfv1.Workflow{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-workflow-abc", + Namespace: "argo", + }, + } + sub := &mockSubmitter{result: created} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-3", validConfig()) + _ = a.Dispatch(context.Background(), job) + + assert.Eventually(t, func() bool { + return len(setter.getCalls()) == 1 + }, time.Second, 10*time.Millisecond) + + meta := setter.getCalls()[0].Metadata + require.Contains(t, meta, "ctrlplane/links") + assert.Contains(t, meta["ctrlplane/links"], "my-workflow-abc") +} + +func TestDispatch_SubmitFailure_SetsJobFailure(t *testing.T) { + sub := &mockSubmitter{err: fmt.Errorf("argo server unavailable")} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-4", validConfig()) + err := a.Dispatch(context.Background(), job) + require.NoError(t, err, "Dispatch itself should not error — failure is async") + + assert.Eventually(t, func() bool { + return len(setter.getCalls()) == 1 + }, time.Second, 10*time.Millisecond) + + call := setter.getCalls()[0] + assert.Equal(t, "job-4", call.JobID) + assert.Equal(t, oapi.JobStatusFailure, call.Status) + assert.Contains(t, call.Message, "argo server unavailable") +} + +func TestDispatch_NilDispatchContext_ReturnsError(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + job := &oapi.Job{Id: "job-5", DispatchContext: nil} + err := a.Dispatch(context.Background(), job) + + require.Error(t, err) + assert.Contains(t, err.Error(), "no dispatch context") +} + +func TestDispatch_InvalidConfig_ReturnsError(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-6", oapi.JobAgentConfig{}) + err := a.Dispatch(context.Background(), job) + + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse job agent config") + assert.Empty(t, sub.getCalls()) +} + +func TestDispatch_SetsJobIDLabel(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + job := newTestJob("job-7", validConfig()) + _ = a.Dispatch(context.Background(), job) + + assert.Eventually(t, func() bool { + return len(sub.getCalls()) == 1 + }, time.Second, 10*time.Millisecond) + + wf := sub.getCalls()[0].Workflow + assert.Equal(t, "job-7", wf.Labels["job-id"]) +} + +func TestDispatch_ConcurrentDispatches(t *testing.T) { + sub := &mockSubmitter{} + setter := &mockSetter{} + a := New(sub, setter) + + var wg sync.WaitGroup + for i := range 10 { + wg.Add(1) + go func(idx int) { + defer wg.Done() + job := newTestJob(fmt.Sprintf("job-%d", idx), validConfig()) + _ = a.Dispatch(context.Background(), job) + }(i) + } + wg.Wait() + + assert.Eventually(t, func() bool { + return len(sub.getCalls()) == 10 + }, 2*time.Second, 10*time.Millisecond) +} + +// ----- ParseJobAgentConfig ----- + +func TestParseJobAgentConfig_Valid(t *testing.T) { + serverAddr, apiKey, template, err := ParseJobAgentConfig(validConfig()) + require.NoError(t, err) + assert.Equal(t, "https://argo.example.com", serverAddr) + assert.Equal(t, "secret-token", apiKey) + assert.Equal(t, minimalWorkflowTemplate, template) +} + +func TestParseJobAgentConfig_MissingServerUrl(t *testing.T) { + cfg := validConfig() + delete(cfg, "serverUrl") + _, _, _, err := ParseJobAgentConfig(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "serverUrl") +} + +func TestParseJobAgentConfig_MissingApiKey(t *testing.T) { + cfg := validConfig() + delete(cfg, "apiKey") + _, _, _, err := ParseJobAgentConfig(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "apiKey") +} + +func TestParseJobAgentConfig_MissingTemplate(t *testing.T) { + cfg := validConfig() + delete(cfg, "template") + _, _, _, err := ParseJobAgentConfig(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "template") +} + +func TestParseJobAgentConfig_EmptyServerUrl(t *testing.T) { + cfg := validConfig() + cfg["serverUrl"] = "" + _, _, _, err := ParseJobAgentConfig(cfg) + require.Error(t, err) +} + +func TestParseJobAgentConfig_EmptyTemplate(t *testing.T) { + cfg := validConfig() + cfg["template"] = "" + _, _, _, err := ParseJobAgentConfig(cfg) + require.Error(t, err) +} + +// ----- MakeApplicationK8sCompatible ----- + +func TestMakeApplicationK8sCompatible_SanitisesName(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "My_Workflow 1!"}} + MakeApplicationK8sCompatible(wf) + assert.Equal(t, "my-workflow-1", wf.Name) +} + +func TestMakeApplicationK8sCompatible_SanitisesGenerateName(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{GenerateName: "My_Workflow-"}} + MakeApplicationK8sCompatible(wf) + assert.Equal(t, "my-workflow-", wf.GenerateName) +} + +func TestMakeApplicationK8sCompatible_SanitisesLabels(t *testing.T) { + wf := &wfv1.Workflow{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-name", + Labels: map[string]string{"env": "Prod/US"}, + }, + } + MakeApplicationK8sCompatible(wf) + assert.Equal(t, "prod-us", wf.Labels["env"]) +} + +func TestMakeApplicationK8sCompatible_NilLabels(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Labels: nil}} + assert.NotPanics(t, func() { MakeApplicationK8sCompatible(wf) }) +} + +func TestMakeApplicationK8sCompatible_TruncatesLongName(t *testing.T) { + long := "abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz" + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: long}} + MakeApplicationK8sCompatible(wf) + assert.LessOrEqual(t, len(wf.Name), 63) +} + +// ----- BuildArgoLinks ----- + +func TestBuildArgoLinks_WithHttpsPrefix(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Namespace: "argo"}} + links := BuildArgoLinks("https://argo.example.com", wf) + assert.Contains(t, links["ctrlplane/links"], "https://argo.example.com/workflows/argo/my-wf") +} + +func TestBuildArgoLinks_AddsHttpsWhenMissing(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Namespace: "argo"}} + links := BuildArgoLinks("argo.example.com", wf) + assert.True(t, len(links["ctrlplane/links"]) > 0) + assert.Contains(t, links["ctrlplane/links"], "https://") +} + +func TestBuildArgoLinks_ContainsWorkflowName(t *testing.T) { + wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "deploy-abc123", Namespace: "ci"}} + links := BuildArgoLinks("https://argo.internal", wf) + assert.Contains(t, links["ctrlplane/links"], "deploy-abc123") + assert.Contains(t, links["ctrlplane/links"], "ci") +} + +// ----- getK8sCompatibleName ----- + +func TestGetK8sCompatibleName(t *testing.T) { + type inputParams struct { + input string + generate bool + } + tests := []struct { + name string + input inputParams + expect string + }{ + {"lowercase passthrough", inputParams{"hello-world", false}, "hello-world"}, + {"uppercase lowercased", inputParams{"Hello-World", false}, "hello-world"}, + {"spaces replaced", inputParams{"hello world", false}, "hello-world"}, + {"underscores replaced", inputParams{"hello_world", false}, "hello-world"}, + {"slashes replaced", inputParams{"hello/world", false}, "hello-world"}, + {"leading dashes trimmed", inputParams{"--hello", false}, "hello"}, + {"trailing dashes trimmed", inputParams{"hello--", false}, "hello"}, + {"numbers preserved", inputParams{"deploy-v1-2", false}, "deploy-v1-2"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expect, getK8sCompatibleName(tt.input.input, tt.input.generate)) + }) + } +} + +func TestGetK8sCompatibleName_LongNameTruncatedTo63(t *testing.T) { + long := "a" + fmt.Sprintf("%0*d", 70, 0) // 71 chars + result := getK8sCompatibleName(long, false) + assert.LessOrEqual(t, len(result), 63) +} From f514d10236edb8a2177b768f5fcdbbe0a2fa1761 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Wed, 25 Mar 2026 16:02:31 -0400 Subject: [PATCH 10/36] feat: support workflowtemplate calls * based upon the job agent params we either tempalte an inline workflow and submit or we crate a workflow object that templates the workflowTemplate parameters and creates the workflow object to send to argo workflow to call the template by name --- apps/api/src/routes/argoworkflow/index.ts | 1 - apps/api/src/routes/argoworkflow/workflow.ts | 7 -- apps/workspace-engine/oapi/openapi.json | 12 ++- .../oapi/spec/schemas/jobs.jsonnet | 5 +- apps/workspace-engine/pkg/oapi/oapi.gen.go | 6 ++ .../jobagents/argo-workflow/workflow.go | 98 ++++++++++++++++--- .../jobagents/argo-workflow/workflow_test.go | 68 +++++++++++-- 7 files changed, 161 insertions(+), 36 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts index 1513193b1..d93b7fa92 100644 --- a/apps/api/src/routes/argoworkflow/index.ts +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -23,7 +23,6 @@ const handleWebhookRequest = async (req: Request, res: Response) => { } const payload = req.body; - console.log("handleArgoWorkflow payload:", JSON.stringify(payload, null, 2)); await handleArgoWorkflow(payload); res.status(200).send(); }; diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index 7dff77b28..2c30ea1e3 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -23,13 +23,6 @@ const statusMap: Record = { Pending: JobStatus.Pending, }; -const extractUuid = (str: string) => { - const uuidRegex = - /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/; - const match = uuidRegex.exec(str); - return match ? match[0] : null; -}; - export const mapTriggerToStatus = (trigger: string): JobStatus | null => statusMap[trigger] ?? null; diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index 2a0fde485..8b53ca6b3 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -59,10 +59,18 @@ "description": "ArgoWorkflow API token.", "type": "string" }, + "inline": { + "description": "If the template passed in is meant to trigger a workflow template", + "type": "boolean" + }, "name": { "description": "ArgoWorkflow job name", "type": "string" }, + "namespace": { + "description": "ArgoWorkflow workflowTemplate namespace", + "type": "string" + }, "serverUrl": { "description": "ArgoWorkflow server address (host[:port] or URL).", "type": "string" @@ -76,7 +84,9 @@ "serverUrl", "apiKey", "template", - "name" + "name", + "inline", + "namespace" ], "type": "object" }, diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 6cc260774..c5004e4b0 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -157,13 +157,14 @@ local JobPropertyKeys = std.objectFields(Job.properties); ArgoWorkflowJobAgentConfig: { type: 'object', - required: ['serverUrl', 'apiKey', 'template', 'name'], + required: ['serverUrl', 'apiKey', 'template', 'name', 'inline', 'namespace'], properties: { name: { type: 'string', description: 'ArgoWorkflow job name' }, + inline: { type: 'boolean', description: 'If the template passed in is meant to trigger a workflow template' }, serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, template: { type: 'string', description: 'ArgoWorkflow application template.' }, - + namespace: { type: 'string', description: 'ArgoWorkflow workflowTemplate namespace' }, }, }, diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index 3dd300370..c298c1b98 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -244,9 +244,15 @@ type ArgoWorkflowJobAgentConfig struct { // ApiKey ArgoWorkflow API token. ApiKey string `json:"apiKey"` + // Inline If the template passed in is meant to trigger a workflow template + Inline bool `json:"inline"` + // Name ArgoWorkflow job name Name string `json:"name"` + // Namespace ArgoWorkflow workflowTemplate namespace + Namespace string `json:"namespace"` + // ServerUrl ArgoWorkflow server address (host[:port] or URL). ServerUrl string `json:"serverUrl"` diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 84d940d86..dc983d785 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -3,6 +3,7 @@ package argo_workflows import ( "bytes" "context" + "encoding/json" "fmt" "regexp" "strings" @@ -15,12 +16,22 @@ import ( "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") var _ types.Dispatchable = (*ArgoWorkflow)(nil) +type WorkFlowJobAgentConfig struct { + serverAddr string + apiKey string + template string + name string + inline bool + namespace string +} + type Getter interface { GetWorkflow(ctx context.Context, name string) (*wfv1.Workflow, error) } @@ -65,12 +76,12 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { return fmt.Errorf("job %s has no dispatch context", job.Id) } jobAgentConfig := dispatchCtx.JobAgentConfig - serverAddr, apiKey, template, err := ParseJobAgentConfig(jobAgentConfig) + wfConfig, err := ParseJobAgentConfig(jobAgentConfig) if err != nil { return fmt.Errorf("failed to parse job agent config: %w", err) } - wf, err := TemplateApplication(dispatchCtx, template) + wf, err := TemplateApplication(dispatchCtx, wfConfig.template, wfConfig.inline, wfConfig.name, wfConfig.namespace) if err != nil { return fmt.Errorf("failed to generate workflow from template: %w", err) } @@ -89,14 +100,14 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { ) defer span.End() - created, err := a.submitter.SubmitWorkflow(asyncCtx, serverAddr, apiKey, wf) + created, err := a.submitter.SubmitWorkflow(asyncCtx, wfConfig.serverAddr, wfConfig.apiKey, wf) if err != nil { _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusFailure, fmt.Sprintf("failed to submit workflow: %s", err.Error()), nil) return } - metadata := BuildArgoLinks(serverAddr, created) + metadata := BuildArgoLinks(wfConfig.serverAddr, created) _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusInProgress, "", metadata) }() @@ -106,28 +117,49 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { // ParseJobAgentConfig extracts the required fields from an agent config. func ParseJobAgentConfig( config oapi.JobAgentConfig, -) (serverAddr, apiKey, template string, err error) { +) (*WorkFlowJobAgentConfig, error) { + wfT := new(WorkFlowJobAgentConfig) serverAddr, ok := config["serverUrl"].(string) if !ok { - return "", "", "", fmt.Errorf("serverUrl is required") + return wfT, fmt.Errorf("serverUrl is required") } - apiKey, ok = config["apiKey"].(string) + wfT.serverAddr = serverAddr + apiKey, ok := config["apiKey"].(string) if !ok { - return "", "", "", fmt.Errorf("apiKey is required") + return wfT, fmt.Errorf("apiKey is required") } - template, ok = config["template"].(string) + wfT.apiKey = apiKey + template, ok := config["template"].(string) + if !ok { + return wfT, fmt.Errorf("template is required") + } + wfT.template = template + + isInline, ok := config["inline"].(bool) if !ok { - return "", "", "", fmt.Errorf("template is required") + wfT.inline = false + } else { + wfT.inline = isInline + } + name, ok := config["name"].(string) + if !ok { + return wfT, fmt.Errorf("name is required") + } + wfT.name = name + namespace, _ := config["namespace"].(string) + if serverAddr == "" || template == "" || name == "" { + return wfT, fmt.Errorf("missing required fields in job agent config") } - if serverAddr == "" || template == "" { - return "", "", "", fmt.Errorf("missing required fields in job agent config") + if !isInline && namespace == "" { + return wfT, fmt.Errorf("when inline is false namespace must be set to trigger the correct workflow template") } - return serverAddr, apiKey, template, nil + wfT.namespace = namespace + return wfT, nil } // TemplateApplication renders the Argo Workflows Workflow YAML template using // the dispatch context variables. -func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*wfv1.Workflow, error) { +func TemplateApplication(ctx *oapi.DispatchContext, tmpl string, inline bool, name string, namespace string) (*wfv1.Workflow, error) { t, err := templatefuncs.Parse("argoWorkflowAgentConfig", tmpl) if err != nil { return nil, fmt.Errorf("failed to parse template: %w", err) @@ -138,12 +170,46 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string) (*wfv1.Workflow } var workflow wfv1.Workflow - if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { - return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) + if inline { + if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { + return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) + } + } else { + params := make(map[string]any) + if err := json.Unmarshal(buf.Bytes(), ¶ms); err != nil { + return nil, fmt.Errorf("failed to parse workflow template vars: %w", err) + } + workflow = *createWorkFlowTemplateCall(name, namespace, params) } + return &workflow, nil } +func createWorkFlowTemplateCall(name string, namespace string, params map[string]any) *wfv1.Workflow { + wf := &wfv1.Workflow{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("%s-", name), + }, + Spec: wfv1.WorkflowSpec{ + WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{ + Name: name, + }, + Arguments: wfv1.Arguments{ + Parameters: []wfv1.Parameter{}, + }, + }, + } + wf.Namespace = namespace + for key, val := range params { + p := wfv1.Parameter{ + Name: key, + Value: wfv1.AnyStringPtr(val), + } + wf.Spec.Arguments.Parameters = append(wf.Spec.Arguments.Parameters, p) + } + return wf +} + // MakeApplicationK8sCompatible sanitises the workflow name and label // values so they conform to Kubernetes naming rules. func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go index 451ffbcac..cc2a079d2 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -105,11 +105,25 @@ spec: args: ["hello world"] ` +const nonInlineWorkflowTemplate = ` +{{- $resourceName := .resource.name }} +{{- $resourceIdentifier := .resource.identifier }} +{{- $environmentName := .environment.name }} +{{- $repo := .release.version.tag }} +{ + "repo": "{{$repo}}", + "resource": "{{$resourceName}}", + "environment": "{{$environmentName}}" +} +` + func validConfig() oapi.JobAgentConfig { return oapi.JobAgentConfig{ "serverUrl": "https://argo.example.com", "apiKey": "secret-token", "template": minimalWorkflowTemplate, + "inline": true, + "name": "job-1", } } @@ -127,6 +141,42 @@ func newTestJob(id string, cfg oapi.JobAgentConfig) *oapi.Job { } } +// ----- TemplateApplication (non-inline) ----- + +func TestTemplateApplication_NonInline_RendersParamsAndCreatesTemplateRef(t *testing.T) { + tag := "v1.2.3" + ctx := &oapi.DispatchContext{ + Resource: &oapi.Resource{ + Name: "my-resource", + Identifier: "res-id-123", + }, + Environment: &oapi.Environment{ + Name: "production", + }, + Release: &oapi.Release{ + Version: oapi.DeploymentVersion{ + Tag: tag, + }, + }, + JobAgent: oapi.JobAgent{}, + JobAgentConfig: oapi.JobAgentConfig{}, + } + + wf, err := TemplateApplication(ctx, nonInlineWorkflowTemplate, false, "my-workflow", "default") + require.NoError(t, err) + assert.Equal(t, "my-workflow-", wf.GenerateName) + require.NotNil(t, wf.Spec.WorkflowTemplateRef) + assert.Equal(t, "my-workflow", wf.Spec.WorkflowTemplateRef.Name) + + params := make(map[string]string) + for _, p := range wf.Spec.Arguments.Parameters { + params[p.Name] = p.Value.String() + } + assert.Equal(t, tag, params["repo"]) + assert.Equal(t, "my-resource", params["resource"]) + assert.Equal(t, "production", params["environment"]) +} + // ----- Type ----- func TestType(t *testing.T) { @@ -280,17 +330,17 @@ func TestDispatch_ConcurrentDispatches(t *testing.T) { // ----- ParseJobAgentConfig ----- func TestParseJobAgentConfig_Valid(t *testing.T) { - serverAddr, apiKey, template, err := ParseJobAgentConfig(validConfig()) + c, err := ParseJobAgentConfig(validConfig()) require.NoError(t, err) - assert.Equal(t, "https://argo.example.com", serverAddr) - assert.Equal(t, "secret-token", apiKey) - assert.Equal(t, minimalWorkflowTemplate, template) + assert.Equal(t, "https://argo.example.com", c.serverAddr) + assert.Equal(t, "secret-token", c.apiKey) + assert.Equal(t, minimalWorkflowTemplate, c.template) } func TestParseJobAgentConfig_MissingServerUrl(t *testing.T) { cfg := validConfig() delete(cfg, "serverUrl") - _, _, _, err := ParseJobAgentConfig(cfg) + _, err := ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "serverUrl") } @@ -298,7 +348,7 @@ func TestParseJobAgentConfig_MissingServerUrl(t *testing.T) { func TestParseJobAgentConfig_MissingApiKey(t *testing.T) { cfg := validConfig() delete(cfg, "apiKey") - _, _, _, err := ParseJobAgentConfig(cfg) + _, err := ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "apiKey") } @@ -306,7 +356,7 @@ func TestParseJobAgentConfig_MissingApiKey(t *testing.T) { func TestParseJobAgentConfig_MissingTemplate(t *testing.T) { cfg := validConfig() delete(cfg, "template") - _, _, _, err := ParseJobAgentConfig(cfg) + _, err := ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "template") } @@ -314,14 +364,14 @@ func TestParseJobAgentConfig_MissingTemplate(t *testing.T) { func TestParseJobAgentConfig_EmptyServerUrl(t *testing.T) { cfg := validConfig() cfg["serverUrl"] = "" - _, _, _, err := ParseJobAgentConfig(cfg) + _, err := ParseJobAgentConfig(cfg) require.Error(t, err) } func TestParseJobAgentConfig_EmptyTemplate(t *testing.T) { cfg := validConfig() cfg["template"] = "" - _, _, _, err := ParseJobAgentConfig(cfg) + _, err := ParseJobAgentConfig(cfg) require.Error(t, err) } From d516cc4cf05b4816f95790db61e620206e6f1257 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Thu, 26 Mar 2026 10:24:08 -0400 Subject: [PATCH 11/36] wip --- .../jobagents/argo-workflow/workflow.go | 8 ++------ .../jobagents/argo-workflow/workflow_test.go | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index dc983d785..de6e0e232 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -135,12 +135,8 @@ func ParseJobAgentConfig( } wfT.template = template - isInline, ok := config["inline"].(bool) - if !ok { - wfT.inline = false - } else { - wfT.inline = isInline - } + isInline, _ := config["inline"].(bool) + wfT.inline = isInline name, ok := config["name"].(string) if !ok { return wfT, fmt.Errorf("name is required") diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go index cc2a079d2..fdb157c7a 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -90,6 +90,10 @@ func (m *mockSetter) getCalls() []updateCall { // ----- Helpers ----- const minimalWorkflowTemplate = ` +{{- $resourceIdentifier := .resource.identifier }} +{{- $environmentName := .environment.name }} +{{- $repo := .release.version.tag }} +--- apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: @@ -128,6 +132,7 @@ func validConfig() oapi.JobAgentConfig { } func newTestJob(id string, cfg oapi.JobAgentConfig) *oapi.Job { + tag := "v1.2.3" return &oapi.Job{ Id: id, Status: oapi.JobStatusPending, @@ -135,7 +140,21 @@ func newTestJob(id string, cfg oapi.JobAgentConfig) *oapi.Job { UpdatedAt: time.Now(), Metadata: map[string]string{}, JobAgentConfig: cfg, + DispatchContext: &oapi.DispatchContext{ + Resource: &oapi.Resource{ + Name: "my-resource", + Identifier: "res-id-123", + }, + Environment: &oapi.Environment{ + Name: "production", + }, + Release: &oapi.Release{ + Version: oapi.DeploymentVersion{ + Tag: tag, + }, + }, + JobAgent: oapi.JobAgent{}, JobAgentConfig: cfg, }, } From 79edb58d40fb053906f019a1f40f0998fc22d409 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Thu, 26 Mar 2026 15:56:52 -0400 Subject: [PATCH 12/36] fix: lint issues --- .../jobagents/argo-workflow/workflow.go | 73 +++++++++++------ .../jobagents/argo-workflow/workflow_test.go | 81 +++++++++++-------- 2 files changed, 97 insertions(+), 57 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index de6e0e232..2734df575 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -24,12 +24,12 @@ var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") var _ types.Dispatchable = (*ArgoWorkflow)(nil) type WorkFlowJobAgentConfig struct { - serverAddr string - apiKey string - template string - name string - inline bool - namespace string + ServerAddr string + ApiKey string + Template string + Name string + Inline bool + Namespace string } type Getter interface { @@ -54,7 +54,11 @@ type WorkflowDeleter interface { // WorkflowSubmitter submits an Argo Workflows Workflow to the server. type WorkflowSubmitter interface { - SubmitWorkflow(ctx context.Context, serverAddr, apiKey string, wf *wfv1.Workflow) (*wfv1.Workflow, error) + SubmitWorkflow( + ctx context.Context, + serverAddr, apiKey string, + wf *wfv1.Workflow, + ) (*wfv1.Workflow, error) } type ArgoWorkflow struct { @@ -81,7 +85,13 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { return fmt.Errorf("failed to parse job agent config: %w", err) } - wf, err := TemplateApplication(dispatchCtx, wfConfig.template, wfConfig.inline, wfConfig.name, wfConfig.namespace) + wf, err := TemplateApplication( + dispatchCtx, + wfConfig.Template, + wfConfig.Inline, + wfConfig.Name, + wfConfig.Namespace, + ) if err != nil { return fmt.Errorf("failed to generate workflow from template: %w", err) } @@ -100,14 +110,19 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { ) defer span.End() - created, err := a.submitter.SubmitWorkflow(asyncCtx, wfConfig.serverAddr, wfConfig.apiKey, wf) + created, err := a.submitter.SubmitWorkflow( + asyncCtx, + wfConfig.ServerAddr, + wfConfig.ApiKey, + wf, + ) if err != nil { _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusFailure, fmt.Sprintf("failed to submit workflow: %s", err.Error()), nil) return } - metadata := BuildArgoLinks(wfConfig.serverAddr, created) + metadata := BuildArgoLinks(wfConfig.ServerAddr, created) _ = a.setter.UpdateJob(asyncCtx, job.Id, oapi.JobStatusInProgress, "", metadata) }() @@ -123,39 +138,47 @@ func ParseJobAgentConfig( if !ok { return wfT, fmt.Errorf("serverUrl is required") } - wfT.serverAddr = serverAddr + wfT.ServerAddr = serverAddr apiKey, ok := config["apiKey"].(string) if !ok { return wfT, fmt.Errorf("apiKey is required") } - wfT.apiKey = apiKey + wfT.ApiKey = apiKey template, ok := config["template"].(string) if !ok { return wfT, fmt.Errorf("template is required") } - wfT.template = template + wfT.Template = template isInline, _ := config["inline"].(bool) - wfT.inline = isInline + wfT.Inline = isInline name, ok := config["name"].(string) if !ok { return wfT, fmt.Errorf("name is required") } - wfT.name = name + wfT.Name = name namespace, _ := config["namespace"].(string) if serverAddr == "" || template == "" || name == "" { return wfT, fmt.Errorf("missing required fields in job agent config") } if !isInline && namespace == "" { - return wfT, fmt.Errorf("when inline is false namespace must be set to trigger the correct workflow template") + return wfT, fmt.Errorf( + "when inline is false namespace must be set to trigger the correct workflow template", + ) } - wfT.namespace = namespace + wfT.Namespace = namespace return wfT, nil } // TemplateApplication renders the Argo Workflows Workflow YAML template using // the dispatch context variables. -func TemplateApplication(ctx *oapi.DispatchContext, tmpl string, inline bool, name string, namespace string) (*wfv1.Workflow, error) { +func TemplateApplication( + ctx *oapi.DispatchContext, + tmpl string, + inline bool, + name string, + namespace string, +) (*wfv1.Workflow, error) { t, err := templatefuncs.Parse("argoWorkflowAgentConfig", tmpl) if err != nil { return nil, fmt.Errorf("failed to parse template: %w", err) @@ -181,7 +204,11 @@ func TemplateApplication(ctx *oapi.DispatchContext, tmpl string, inline bool, na return &workflow, nil } -func createWorkFlowTemplateCall(name string, namespace string, params map[string]any) *wfv1.Workflow { +func createWorkFlowTemplateCall( + name string, + namespace string, + params map[string]any, +) *wfv1.Workflow { wf := &wfv1.Workflow{ ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("%s-", name), @@ -210,19 +237,19 @@ func createWorkFlowTemplateCall(name string, namespace string, params map[string // values so they conform to Kubernetes naming rules. func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { if wf.Name != "" { - wf.Name = getK8sCompatibleName(wf.Name, false) + wf.Name = GetK8sCompatibleName(wf.Name, false) } if wf.GenerateName != "" { - wf.GenerateName = getK8sCompatibleName(wf.GenerateName, true) + wf.GenerateName = GetK8sCompatibleName(wf.GenerateName, true) } if wf.Labels != nil { for key, value := range wf.Labels { - wf.Labels[key] = getK8sCompatibleName(value, false) + wf.Labels[key] = GetK8sCompatibleName(value, false) } } } -func getK8sCompatibleName(name string, generated bool) string { +func GetK8sCompatibleName(name string, generated bool) string { cleaned := strings.ToLower(name) k8sInvalidCharsRegex := regexp.MustCompile(`[^a-z0-9-]`) cleaned = k8sInvalidCharsRegex.ReplaceAllString(cleaned, "-") diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go index fdb157c7a..5ababcc9b 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -1,4 +1,4 @@ -package argo_workflows +package argo_workflows_test import ( "context" @@ -7,12 +7,12 @@ import ( "testing" "time" - "workspace-engine/pkg/oapi" - wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "workspace-engine/pkg/oapi" + argo_workflows "workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow" ) // ----- Mocks ----- @@ -75,7 +75,10 @@ func (m *mockSetter) UpdateJob( ) error { m.mu.Lock() defer m.mu.Unlock() - m.calls = append(m.calls, updateCall{JobID: jobID, Status: status, Message: message, Metadata: metadata}) + m.calls = append( + m.calls, + updateCall{JobID: jobID, Status: status, Message: message, Metadata: metadata}, + ) return m.err } @@ -181,7 +184,13 @@ func TestTemplateApplication_NonInline_RendersParamsAndCreatesTemplateRef(t *tes JobAgentConfig: oapi.JobAgentConfig{}, } - wf, err := TemplateApplication(ctx, nonInlineWorkflowTemplate, false, "my-workflow", "default") + wf, err := argo_workflows.TemplateApplication( + ctx, + nonInlineWorkflowTemplate, + false, + "my-workflow", + "default", + ) require.NoError(t, err) assert.Equal(t, "my-workflow-", wf.GenerateName) require.NotNil(t, wf.Spec.WorkflowTemplateRef) @@ -199,7 +208,7 @@ func TestTemplateApplication_NonInline_RendersParamsAndCreatesTemplateRef(t *tes // ----- Type ----- func TestType(t *testing.T) { - a := New(&mockSubmitter{}, &mockSetter{}) + a := argo_workflows.New(&mockSubmitter{}, &mockSetter{}) assert.Equal(t, "argo-workflow", a.Type()) } @@ -208,7 +217,7 @@ func TestType(t *testing.T) { func TestDispatch_Success_SubmitsWorkflow(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-1", validConfig()) err := a.Dispatch(context.Background(), job) @@ -227,7 +236,7 @@ func TestDispatch_Success_SubmitsWorkflow(t *testing.T) { func TestDispatch_Success_SetsJobInProgress(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-2", validConfig()) err := a.Dispatch(context.Background(), job) @@ -251,7 +260,7 @@ func TestDispatch_Success_MetadataContainsArgoLink(t *testing.T) { } sub := &mockSubmitter{result: created} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-3", validConfig()) _ = a.Dispatch(context.Background(), job) @@ -268,7 +277,7 @@ func TestDispatch_Success_MetadataContainsArgoLink(t *testing.T) { func TestDispatch_SubmitFailure_SetsJobFailure(t *testing.T) { sub := &mockSubmitter{err: fmt.Errorf("argo server unavailable")} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-4", validConfig()) err := a.Dispatch(context.Background(), job) @@ -287,7 +296,7 @@ func TestDispatch_SubmitFailure_SetsJobFailure(t *testing.T) { func TestDispatch_NilDispatchContext_ReturnsError(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := &oapi.Job{Id: "job-5", DispatchContext: nil} err := a.Dispatch(context.Background(), job) @@ -299,7 +308,7 @@ func TestDispatch_NilDispatchContext_ReturnsError(t *testing.T) { func TestDispatch_InvalidConfig_ReturnsError(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-6", oapi.JobAgentConfig{}) err := a.Dispatch(context.Background(), job) @@ -312,7 +321,7 @@ func TestDispatch_InvalidConfig_ReturnsError(t *testing.T) { func TestDispatch_SetsJobIDLabel(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) job := newTestJob("job-7", validConfig()) _ = a.Dispatch(context.Background(), job) @@ -328,7 +337,7 @@ func TestDispatch_SetsJobIDLabel(t *testing.T) { func TestDispatch_ConcurrentDispatches(t *testing.T) { sub := &mockSubmitter{} setter := &mockSetter{} - a := New(sub, setter) + a := argo_workflows.New(sub, setter) var wg sync.WaitGroup for i := range 10 { @@ -349,17 +358,17 @@ func TestDispatch_ConcurrentDispatches(t *testing.T) { // ----- ParseJobAgentConfig ----- func TestParseJobAgentConfig_Valid(t *testing.T) { - c, err := ParseJobAgentConfig(validConfig()) + c, err := argo_workflows.ParseJobAgentConfig(validConfig()) require.NoError(t, err) - assert.Equal(t, "https://argo.example.com", c.serverAddr) - assert.Equal(t, "secret-token", c.apiKey) - assert.Equal(t, minimalWorkflowTemplate, c.template) + assert.Equal(t, "https://argo.example.com", c.ServerAddr) + assert.Equal(t, "secret-token", c.ApiKey) + assert.Equal(t, minimalWorkflowTemplate, c.Template) } func TestParseJobAgentConfig_MissingServerUrl(t *testing.T) { cfg := validConfig() delete(cfg, "serverUrl") - _, err := ParseJobAgentConfig(cfg) + _, err := argo_workflows.ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "serverUrl") } @@ -367,7 +376,7 @@ func TestParseJobAgentConfig_MissingServerUrl(t *testing.T) { func TestParseJobAgentConfig_MissingApiKey(t *testing.T) { cfg := validConfig() delete(cfg, "apiKey") - _, err := ParseJobAgentConfig(cfg) + _, err := argo_workflows.ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "apiKey") } @@ -375,7 +384,7 @@ func TestParseJobAgentConfig_MissingApiKey(t *testing.T) { func TestParseJobAgentConfig_MissingTemplate(t *testing.T) { cfg := validConfig() delete(cfg, "template") - _, err := ParseJobAgentConfig(cfg) + _, err := argo_workflows.ParseJobAgentConfig(cfg) require.Error(t, err) assert.Contains(t, err.Error(), "template") } @@ -383,14 +392,14 @@ func TestParseJobAgentConfig_MissingTemplate(t *testing.T) { func TestParseJobAgentConfig_EmptyServerUrl(t *testing.T) { cfg := validConfig() cfg["serverUrl"] = "" - _, err := ParseJobAgentConfig(cfg) + _, err := argo_workflows.ParseJobAgentConfig(cfg) require.Error(t, err) } func TestParseJobAgentConfig_EmptyTemplate(t *testing.T) { cfg := validConfig() cfg["template"] = "" - _, err := ParseJobAgentConfig(cfg) + _, err := argo_workflows.ParseJobAgentConfig(cfg) require.Error(t, err) } @@ -398,13 +407,13 @@ func TestParseJobAgentConfig_EmptyTemplate(t *testing.T) { func TestMakeApplicationK8sCompatible_SanitisesName(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "My_Workflow 1!"}} - MakeApplicationK8sCompatible(wf) + argo_workflows.MakeApplicationK8sCompatible(wf) assert.Equal(t, "my-workflow-1", wf.Name) } func TestMakeApplicationK8sCompatible_SanitisesGenerateName(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{GenerateName: "My_Workflow-"}} - MakeApplicationK8sCompatible(wf) + argo_workflows.MakeApplicationK8sCompatible(wf) assert.Equal(t, "my-workflow-", wf.GenerateName) } @@ -415,19 +424,19 @@ func TestMakeApplicationK8sCompatible_SanitisesLabels(t *testing.T) { Labels: map[string]string{"env": "Prod/US"}, }, } - MakeApplicationK8sCompatible(wf) + argo_workflows.MakeApplicationK8sCompatible(wf) assert.Equal(t, "prod-us", wf.Labels["env"]) } func TestMakeApplicationK8sCompatible_NilLabels(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Labels: nil}} - assert.NotPanics(t, func() { MakeApplicationK8sCompatible(wf) }) + assert.NotPanics(t, func() { argo_workflows.MakeApplicationK8sCompatible(wf) }) } func TestMakeApplicationK8sCompatible_TruncatesLongName(t *testing.T) { long := "abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyz" wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: long}} - MakeApplicationK8sCompatible(wf) + argo_workflows.MakeApplicationK8sCompatible(wf) assert.LessOrEqual(t, len(wf.Name), 63) } @@ -435,20 +444,20 @@ func TestMakeApplicationK8sCompatible_TruncatesLongName(t *testing.T) { func TestBuildArgoLinks_WithHttpsPrefix(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Namespace: "argo"}} - links := BuildArgoLinks("https://argo.example.com", wf) + links := argo_workflows.BuildArgoLinks("https://argo.example.com", wf) assert.Contains(t, links["ctrlplane/links"], "https://argo.example.com/workflows/argo/my-wf") } func TestBuildArgoLinks_AddsHttpsWhenMissing(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Namespace: "argo"}} - links := BuildArgoLinks("argo.example.com", wf) - assert.True(t, len(links["ctrlplane/links"]) > 0) + links := argo_workflows.BuildArgoLinks("argo.example.com", wf) + assert.Positive(t, len(links["ctrlplane/links"])) assert.Contains(t, links["ctrlplane/links"], "https://") } func TestBuildArgoLinks_ContainsWorkflowName(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "deploy-abc123", Namespace: "ci"}} - links := BuildArgoLinks("https://argo.internal", wf) + links := argo_workflows.BuildArgoLinks("https://argo.internal", wf) assert.Contains(t, links["ctrlplane/links"], "deploy-abc123") assert.Contains(t, links["ctrlplane/links"], "ci") } @@ -476,13 +485,17 @@ func TestGetK8sCompatibleName(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expect, getK8sCompatibleName(tt.input.input, tt.input.generate)) + assert.Equal( + t, + tt.expect, + argo_workflows.GetK8sCompatibleName(tt.input.input, tt.input.generate), + ) }) } } func TestGetK8sCompatibleName_LongNameTruncatedTo63(t *testing.T) { long := "a" + fmt.Sprintf("%0*d", 70, 0) // 71 chars - result := getK8sCompatibleName(long, false) + result := argo_workflows.GetK8sCompatibleName(long, false) assert.LessOrEqual(t, len(result), 63) } From 39f4b259c6e7271838a83db0a93e68c977712fc9 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Thu, 26 Mar 2026 16:40:36 -0400 Subject: [PATCH 13/36] fix: more lint issues --- apps/api/src/routes/argoworkflow/workflow.ts | 8 +++++++- apps/api/src/server.ts | 2 +- .../jobdispatch/jobagents/argo-workflow/workflow.go | 7 +++---- .../jobagents/argo-workflow/workflow_submitter.go | 5 ++--- .../jobdispatch/jobagents/argo-workflow/workflow_test.go | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index 2c30ea1e3..eab21eef7 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -34,7 +34,13 @@ export const getJobId = (payload: ArgoWorkflowPayload): string => { }; export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { - const { workflowName, uid, phase, startedAt, finishedAt } = payload; + const { + workflowName: _workflowName, + uid, + phase, + startedAt, + finishedAt, + } = payload; const jobId = getJobId(payload); if (jobId == null) return; diff --git a/apps/api/src/server.ts b/apps/api/src/server.ts index 3454039ad..6b020c273 100644 --- a/apps/api/src/server.ts +++ b/apps/api/src/server.ts @@ -16,7 +16,7 @@ import { auth } from "@ctrlplane/auth/server"; import { appRouter, createTRPCContext } from "@ctrlplane/trpc"; import swaggerDocument from "../openapi/openapi.json" with { type: "json" }; -import { createArgoWorkflowRouter } from "./routes/argoworkflow"; +import { createArgoWorkflowRouter } from "./routes/argoworkflow/index.js"; import { createGithubRouter } from "./routes/github/index.js"; import { createTfeRouter } from "./routes/tfe/index.js"; diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 2734df575..a2a79c03f 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -8,15 +8,14 @@ import ( "regexp" "strings" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/templatefuncs" - "workspace-engine/svc/controllers/jobdispatch/jobagents/types" - wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "workspace-engine/pkg/oapi" + "workspace-engine/pkg/templatefuncs" + "workspace-engine/svc/controllers/jobdispatch/jobagents/types" ) var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go index faefbddb8..c4ff0f399 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -6,12 +6,11 @@ import ( "strings" "time" - "github.com/avast/retry-go" - "github.com/charmbracelet/log" - argoapiclient "github.com/argoproj/argo-workflows/v4/pkg/apiclient" workflowpkg "github.com/argoproj/argo-workflows/v4/pkg/apiclient/workflow" wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" + "github.com/avast/retry-go" + "github.com/charmbracelet/log" ) // GoWorkflowSubmitter is the production implementation of WorkflowSubmitter diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go index 5ababcc9b..3a7508398 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -451,7 +451,7 @@ func TestBuildArgoLinks_WithHttpsPrefix(t *testing.T) { func TestBuildArgoLinks_AddsHttpsWhenMissing(t *testing.T) { wf := &wfv1.Workflow{ObjectMeta: metav1.ObjectMeta{Name: "my-wf", Namespace: "argo"}} links := argo_workflows.BuildArgoLinks("argo.example.com", wf) - assert.Positive(t, len(links["ctrlplane/links"])) + assert.NotEmpty(t, links["ctrlplane/links"]) assert.Contains(t, links["ctrlplane/links"], "https://") } From f10661b555b1eb87b8959716728233a863796a5b Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Thu, 26 Mar 2026 17:07:42 -0400 Subject: [PATCH 14/36] fix: more linting issues --- apps/api/src/routes/argoworkflow/workflow.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index eab21eef7..da0638fc4 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -43,7 +43,6 @@ export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { } = payload; const jobId = getJobId(payload); - if (jobId == null) return; const status = statusMap[phase] ?? null; if (status == null) return; From 172f980efb602af12c965e8de8afab8bb52eabc5 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Fri, 27 Mar 2026 14:41:11 -0400 Subject: [PATCH 15/36] chore: update go.mod --- apps/workspace-engine/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workspace-engine/go.mod b/apps/workspace-engine/go.mod index d8a2a15c2..6c8f6f53e 100644 --- a/apps/workspace-engine/go.mod +++ b/apps/workspace-engine/go.mod @@ -35,6 +35,7 @@ require ( go.opentelemetry.io/otel/sdk v1.41.0 go.opentelemetry.io/otel/sdk/metric v1.41.0 go.opentelemetry.io/otel/trace v1.41.0 + k8s.io/apimachinery v0.34.1 sigs.k8s.io/yaml v1.6.0 ) @@ -292,7 +293,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.34.1 // indirect k8s.io/apiextensions-apiserver v0.34.0 // indirect - k8s.io/apimachinery v0.34.1 // indirect k8s.io/apiserver v0.34.0 // indirect k8s.io/cli-runtime v0.34.0 // indirect k8s.io/client-go v0.34.1 // indirect From a5513ed64feeeb0ce668ef075366795bde4fee4c Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Fri, 27 Mar 2026 15:17:25 -0400 Subject: [PATCH 16/36] docs: add argo-workflow docs --- .../job-agents/argo-workflows.mdx | 278 ++++++++++++++++++ docs/reference/glossary.mdx | 13 +- 2 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 docs/integrations/job-agents/argo-workflows.mdx diff --git a/docs/integrations/job-agents/argo-workflows.mdx b/docs/integrations/job-agents/argo-workflows.mdx new file mode 100644 index 000000000..4c267df9c --- /dev/null +++ b/docs/integrations/job-agents/argo-workflows.mdx @@ -0,0 +1,278 @@ +--- +title: "Argo Workflows" +description: "Execute deployments using Argo Workflows" +--- +The Argo Workflow job agent triggers workflow dispatch events to execute your +deployments. This is ideal for teams that use github for non git event triggered workflows. + + +```mermaid +sequenceDiagram + participant C as Ctrlplane + participant G as ArgoWorkflow API + participant W as Workflow + + C->>G: Dispatch workflow (job_id) + G->>W: Trigger workflow_dispatch + W->>W: Get job context from Ctrlplane + W->>W: Execute deployment + G->>C: Webhook: workflow_run event + C->>C: Update job status automatically +``` + +1. Ctrlplane creates a job and dispatches it to ArgoWorkflows +2. ArgoEvents triggers your workflow with +3. Your workflow fetches job context (version, environment, resource) +4. Your workflow executes the deployment +5. ArgoWorkflows sends a webhook event to Ctrlplane as the run progresses +6. Ctrlplane automatically updates the job status (in progress, successful, failure, etc.) + +## Prerequisites + +- ArgoWorkflows server with API access +- API token with application create/update permissions +- Network connectivity from Ctrlplane to ArgoWorkflows + +## Configuration + +### Job Agent Setup + +Create a job agent with type `argo-cd`: + +```yaml +type: JobAgent +name: argo-workflows +agentType: argo-workflow +``` + +### Deployment Configuration + +```yaml +type: Deployment +name: api-service +jobAgent: argo-workflow +jobAgentConfig: + serverUrl: argocd.example.com:443 + apiKey: "{{.variables.argoworkflow_token}}" + template: | + --- + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: go-build- + namespace: argo + spec: + entrypoint: main + templates: + - name: main + container: + image: alpine:3.19 + command: ["sh", "-c"] + args: ["echo hello"] +``` + +| Field | Required | Description | +| ----------- | --------------|--------------------------------------------------------------------------- | +| `serverUrl` | Yes | ArgoWorkflow server URL (host:port) | +| `apiKey` | Yes | ArgoWorkflow API token | +| `template` | Yes | Go template for ArgoWorkflow Application | +| `name` | Yes | Go template for ArgoWorkflow Application | +| `inline` | No | Boolean for if you're suppling an inline workflow ArgoWorkflow Application | +| `namespace` | If not Inline | If not supplying an inline template this is required | + +## Template Context + +The template has access to all job context. Variables are accessed using Go template +syntax: `{{.variable.property}}`. The job context is derived from the resources +returned by the deployment selector (and narrowed by the environment selector), +so the data available to the template reflects that specific target. + +### Top-Level Variables + +| Variable | Description | +| -------------- | ----------------------------------------------- | +| `.job` | Current job details | +| `.version` | Version being deployed (shortcut) | +| `.deployment` | User-defined deployment configuration and properties | +| `.environment` | User-defined environment you deploy into and its properties | +| `.resource` | User-defined resource you deploy against and its properties | +| `.variables` | Merged deployment variables (key-value strings) | + +### Resource Properties + +Each job invocation is tied to a specific resource instance returned by +your selector, so the `.resource` values can differ for every ArgoWorkflows +template invocation. + +| Property | Type | Description | +| ---------------------- | ----------------- | ------------------------------------- | +| `.resource.id` | string | Unique resource ID | +| `.resource.name` | string | Display name | +| `.resource.identifier` | string | Unique identifier within workspace | +| `.resource.kind` | string | Resource kind (e.g., KubernetesCluster) | +| `.resource.version` | string | Resource schema version | +| `.resource.config` | object | Arbitrary configuration data | +| `.resource.metadata` | map[string]string | Key-value metadata labels | +| `.resource.workspaceId`| string | Parent workspace ID | +| `.resource.providerId` | string | Resource provider ID (if any) | +| `.resource.createdAt` | timestamp | Creation timestamp | +| `.resource.updatedAt` | timestamp | Last update timestamp | +| `.resource.lockedAt` | timestamp | Lock timestamp (if locked) | + +### Deployment Properties + +Each job invocation is tied to a specific deployment, so `.deployment` values can differ +for every ArgoWorkflow template invocation. + +| Property | Type | Description | +| ----------------------------- | ----------------- | ---------------------------------- | +| `.deployment.id` | string | Unique deployment ID | +| `.deployment.name` | string | Display name | +| `.deployment.slug` | string | URL-friendly identifier | +| `.deployment.description` | string | Optional description | +| `.deployment.systemId` | string | Parent system ID | +| `.deployment.jobAgentId` | string | Associated job agent ID | +| `.deployment.jobAgentConfig` | object | Job agent configuration | +| `.deployment.resourceSelector`| object | Resource selector for targeting | + +### Environment Properties + +Each job invocation is tied to a specific environment, so `.environment` values can differ +for every ArgoWorkflow template invocation. + +| Property | Type | Description | +| ------------------------------- | --------- | ------------------------------- | +| `.environment.id` | string | Unique environment ID | +| `.environment.name` | string | Display name | +| `.environment.description` | string | Optional description | +| `.environment.systemId` | string | Parent system ID | +| `.environment.resourceSelector` | object | Resource selector | +| `.environment.createdAt` | timestamp | Creation timestamp | + +### Version Properties + +Access via `.version`: + +| Property | Type | Description | +| ---------------------- | ----------------- | -------------------------------- | +| `.version.id` | string | Unique version ID | +| `.version.tag` | string | Version tag (e.g., v1.2.3) | +| `.version.name` | string | Display name | +| `.version.message` | string | Optional commit/release message | +| `.version.status` | string | Version status | +| `.version.config` | object | Version-specific configuration | +| `.version.metadata` | map[string]string | Version metadata | +| `.version.jobAgentConfig`| object | Version-level job agent config | +| `.version.deploymentId`| string | Parent deployment ID | +| `.version.createdAt` | timestamp | Creation timestamp | + +### Job Properties + +| Property | Type | Description | +| ------------------- | ----------------- | ---------------------------------- | +| `.job.id` | string | Unique job ID | +| `.job.status` | string | Current status | +| `.job.message` | string | Status message | +| `.job.externalId` | string | External system reference | +| `.job.metadata` | map[string]string | Job metadata | +| `.job.jobAgentId` | string | Executing job agent ID | +| `.job.releaseId` | string | Associated release ID | +| `.job.createdAt` | timestamp | Creation timestamp | +| `.job.startedAt` | timestamp | Execution start timestamp | +| `.job.completedAt` | timestamp | Completion timestamp | +| `.job.updatedAt` | timestamp | Last update timestamp | + +### Variables + +The `.variables` map contains all resolved deployment variables as strings: + +```yaml +template: | + metadata: + annotations: + replicas: "{{.variables.replica_count}}" + db-host: "{{.variables.database_host}}" +``` + +### Accessing Nested Config + +Resource and version configs are arbitrary objects. Access nested properties: + +```yaml +template: | + spec: + destination: + server: {{.resource.config.cluster_url}} + namespace: {{.resource.config.namespaces.app}} + source: + helm: + parameters: + - name: image.tag + value: {{.version.config.imageTag}} +``` + +## Template Functions + +The template supports [Sprig](http://masterminds.github.io/sprig/) functions: + +```yaml +template: | + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + name: {{ .deployment.slug | lower | replace "_" "-" }} + labels: + version: {{ .version.tag | trunc 63 }} + deployed-at: {{ now | date "2006-01-02" }} +``` + +## Automatic Verification + +When an ArgoWorkflows job is created, Ctrlplane automatically listens for webhooks from the argo server + +- Application sync status is `Synced` +- Application health status is `Healthy` or `Progressing` + +## Example: Inline vs Triggering a Template + +```yaml +type: argo-workflow +name: api-service +jobAgent: argo-workflow +jobAgentConfig: + serverUrl: argoworkflows.example.com:443 + apiKey: "{{.variables.argoworkflow_token}}" + template: | + --- + apiVersion: argoproj.io/v1alpha1 + kind: Workflow + metadata: + generateName: go-build- + namespace: argo + spec: + entrypoint: main + templates: + - name: main + container: + image: alpine:3.19 + command: ["sh", "-c"] + args: ["echo hello"] +``` + +```json +type: argo-workflow +name: api-service +jobAgent: argo-workflow +jobAgentConfig: + serverUrl: argoworkflows.example.com:443 + apiKey: "{{.variables.argoworkflow_token}}" + inline: false + type: argo-workflow + namespace: mynamespace + template: | + { + "repo": "${variables.repo}}" + "branch": "${variables.branch}}" + } +``` + diff --git a/docs/reference/glossary.mdx b/docs/reference/glossary.mdx index 32b631a52..36b5a97ed 100644 --- a/docs/reference/glossary.mdx +++ b/docs/reference/glossary.mdx @@ -199,12 +199,13 @@ A **Job** is the actual deployment task executed by a Job Agent. A **Job Agent** is the executor that performs deployments. It bridges Ctrlplane to your infrastructure. -| Agent Type | What It Does | -| --------------- | --------------------------------- | -| GitHub Actions | Triggers workflow dispatch | -| ArgoCD | Creates/syncs ArgoCD Applications | -| Terraform Cloud | Creates workspaces, triggers runs | -| Kubernetes | Applies manifests directly | +| Agent Type | What It Does | +| --------------- | --------------------------------------| +| GitHub Actions | Triggers workflow dispatch | +| ArgoCD | Creates/syncs ArgoCD Applications | +| Terraform Cloud | Creates workspaces, triggers runs | +| Kubernetes | Applies manifests directly | +| ArgoWorkflows | Applies an inline workflow or template| ## Policy From 076226f9ace70857c6e5accc4f887e3193dcdb3e Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Fri, 27 Mar 2026 15:39:45 -0400 Subject: [PATCH 17/36] fix: add generated files * add generated files and update schema --- apps/relay/bin/relay | Bin 0 -> 661246 bytes apps/web/app/api/openapi.ts | 331 +- .../.claude/settings.local.json | 13 + apps/workspace-engine/oapi/openapi.json | 110 +- .../oapi/spec/schemas/jobs.jsonnet | 48 +- apps/workspace-engine/pkg/oapi/oapi.gen.go | 111 +- packages/workspace-engine-sdk/src/schema.ts | 2916 ++++++++--------- 7 files changed, 2001 insertions(+), 1528 deletions(-) create mode 100644 apps/relay/bin/relay create mode 100644 apps/workspace-engine/.claude/settings.local.json diff --git a/apps/relay/bin/relay b/apps/relay/bin/relay new file mode 100644 index 0000000000000000000000000000000000000000..90c6aab4e6abb46d36a8cca8e23c03f7b365ae14 GIT binary patch literal 661246 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

7nB?dPd*oBE6tKRuPsC;#meaGKhqg6GD== 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%0DVkWjcGDvv)Wb&%p=@v(xEM=kmZQ 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 export interface components { @@ -915,6 +979,13 @@ export interface components { /** @enum {string} */ ApprovalStatus: "approved" | "rejected"; BooleanValue: boolean; + CreateDeploymentPlanRequest: { + /** @description Arbitrary key-value metadata for the plan (e.g. GitHub PR links, CI run URLs) */ + metadata?: { + [key: string]: string; + }; + version: components["schemas"]["DeploymentPlanVersion"]; + }; CreateDeploymentRequest: { description?: string; jobAgentConfig?: { @@ -1093,6 +1164,50 @@ export interface components { /** @description CEL expression to determine if the job agent should be used */ selector: string; }; + DeploymentPlan: { + id: string; + /** @enum {string} */ + status: "computing" | "completed" | "failed"; + summary?: components["schemas"]["DeploymentPlanSummary"]; + targets: components["schemas"]["DeploymentPlanTarget"][]; + }; + DeploymentPlanSummary: { + changed: number; + errored: number; + total: number; + unchanged: number; + 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; + resourceId: string; + resourceName: string; + /** @enum {string} */ + status: "computing" | "completed" | "errored" | "unsupported"; + }; + DeploymentPlanVersion: { + config?: { + [key: string]: unknown; + }; + jobAgentConfig?: { + [key: string]: unknown; + }; + metadata?: { + [key: string]: string; + }; + /** @description Display name for the proposed version (defaults to tag if omitted) */ + name?: string; + /** @description Version tag for the proposed deployment (e.g. pr-123-abc123) */ + tag: string; + }; DeploymentRequestAccepted: { id: string; message: string; @@ -1815,6 +1930,20 @@ export interface components { }; name: string; }; + UpsertResourceRequest: { + config?: { + [key: string]: unknown; + }; + kind: string; + metadata?: { + [key: string]: string; + }; + name: string; + variables?: { + [key: string]: unknown; + }; + version: string; + }; UpsertSystemRequest: { description?: string; metadata?: { @@ -2862,6 +2991,107 @@ export interface operations { }; }; }; + createDeploymentPlan: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateDeploymentPlanRequest"]; + }; + }; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentPlan"]; + }; + }; + /** @description Accepted response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentPlan"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeploymentPlan: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the deployment */ + deploymentId: string; + /** @description ID of the deployment plan */ + planId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeploymentPlan"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; listDeploymentVariablesByDeployment: { parameters: { query?: { @@ -4554,6 +4784,53 @@ export interface operations { }; }; }; + upsertResourceByIdentifier: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Identifier of the resource */ + identifier: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpsertResourceRequest"]; + }; + }; + responses: { + /** @description Accepted response */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ResourceRequestAccepted"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; requestResourceDeletionByIdentifier: { parameters: { query?: never; @@ -5465,4 +5742,56 @@ export interface operations { }; }; }; + createWorkflowRun: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the workflow */ + workflowId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Input values for the workflow run. */ + inputs: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description Resource created successfully */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkflowRun"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; } diff --git a/apps/workspace-engine/.claude/settings.local.json b/apps/workspace-engine/.claude/settings.local.json new file mode 100644 index 000000000..ff37daf8a --- /dev/null +++ b/apps/workspace-engine/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(grep -E \"\\\\.\\(ts|sql|js\\)$\")", + "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, resource_selector AS deployment_resource_selector,\n job_agents\nFROM deployment;\n\")", + "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, resource_selector FROM environment;\n\")", + "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, kind, identifier, metadata FROM resource LIMIT 5;\n\")", + "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \":*)", + "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\\\\d reconcile_work_scope\")", + "Bash(grep -n DELETE /Users/mleone/Documents/repos/work/ctrlplane/apps/workspace-engine/pkg/reconcile/postgres/queries/*.sql)" + ] + } +} diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index e814a5e41..4a5a59dda 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -54,41 +54,85 @@ "type": "object" }, "ArgoWorkflowJobAgentConfig": { - "properties": { - "apiKey": { - "description": "ArgoWorkflow API token.", - "type": "string" - }, - "inline": { - "description": "If the template passed in is meant to trigger a workflow template", - "type": "boolean" - }, - "name": { - "description": "ArgoWorkflow job name", - "type": "string" - }, - "namespace": { - "description": "ArgoWorkflow workflowTemplate namespace", - "type": "string" - }, - "serverUrl": { - "description": "ArgoWorkflow server address (host[:port] or URL).", - "type": "string" + "oneOf": [ + { + "additionalProperties": false, + "description": "Inline workflow execution", + "properties": { + "apiKey": { + "description": "ArgoWorkflow API token.", + "type": "string" + }, + "inline": { + "description": "Execute inline workflow (defaults to false if omitted)", + "enum": [ + true + ], + "type": "boolean" + }, + "name": { + "description": "ArgoWorkflow job name", + "type": "string" + }, + "serverUrl": { + "description": "ArgoWorkflow server address (host[:port] or URL).", + "type": "string" + }, + "template": { + "description": "Inline workflow spec or template.", + "type": "string" + } + }, + "required": [ + "serverUrl", + "apiKey", + "template", + "name" + ], + "type": "object" }, - "template": { - "description": "ArgoWorkflow application template.", - "type": "string" + { + "additionalProperties": false, + "description": "WorkflowTemplate reference execution", + "properties": { + "apiKey": { + "description": "ArgoWorkflow API token.", + "type": "string" + }, + "inline": { + "description": "Use WorkflowTemplate reference (default mode)", + "enum": [ + false + ], + "type": "boolean" + }, + "name": { + "description": "ArgoWorkflow job name", + "type": "string" + }, + "namespace": { + "description": "WorkflowTemplate namespace", + "type": "string" + }, + "serverUrl": { + "description": "ArgoWorkflow server address (host[:port] or URL).", + "type": "string" + }, + "template": { + "description": "WorkflowTemplate name.", + "type": "string" + } + }, + "required": [ + "serverUrl", + "apiKey", + "template", + "name", + "namespace" + ], + "type": "object" } - }, - "required": [ - "serverUrl", - "apiKey", - "template", - "name", - "inline", - "namespace" - ], - "type": "object" + ] }, "BasicResource": { "properties": { diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index eb3656ff3..8cb2924f3 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -161,18 +161,44 @@ local JobPropertyKeys = std.objectFields(Job.properties); }, ArgoWorkflowJobAgentConfig: { - type: 'object', - required: ['serverUrl', 'apiKey', 'template', 'name', 'inline', 'namespace'], - properties: { - name: { type: 'string', description: 'ArgoWorkflow job name' }, - inline: { type: 'boolean', description: 'If the template passed in is meant to trigger a workflow template' }, - serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, - apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, - template: { type: 'string', description: 'ArgoWorkflow application template.' }, - namespace: { type: 'string', description: 'ArgoWorkflow workflowTemplate namespace' }, - }, + oneOf: [ + { + type: 'object', + description: 'Inline workflow execution', + required: ['serverUrl', 'apiKey', 'template', 'name'], + properties: { + name: { type: 'string', description: 'ArgoWorkflow job name' }, + inline: { + type: 'boolean', + enum: [true], + description: 'Execute inline workflow (defaults to false if omitted)', + }, + serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, + apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + template: { type: 'string', description: 'Inline workflow spec or template.' }, + }, + additionalProperties: false, + }, + { + type: 'object', + description: 'WorkflowTemplate reference execution', + required: ['serverUrl', 'apiKey', 'template', 'name', 'namespace'], + properties: { + name: { type: 'string', description: 'ArgoWorkflow job name' }, + inline: { + type: 'boolean', + enum: [false], + description: 'Use WorkflowTemplate reference (default mode)', + }, + serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, + apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + template: { type: 'string', description: 'WorkflowTemplate name.' }, + namespace: { type: 'string', description: 'WorkflowTemplate namespace' }, + }, + additionalProperties: false, + }, + ], }, - TestRunnerJobAgentConfig: { type: 'object', properties: { diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index b12ca8b9f..308e6ac62 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -21,6 +21,16 @@ const ( ApprovalStatusRejected ApprovalStatus = "rejected" ) +// Defines values for ArgoWorkflowJobAgentConfig0Inline. +const ( + ArgoWorkflowJobAgentConfig0InlineTrue ArgoWorkflowJobAgentConfig0Inline = true +) + +// Defines values for ArgoWorkflowJobAgentConfig1Inline. +const ( + False ArgoWorkflowJobAgentConfig1Inline = false +) + // Defines values for DatadogMetricProviderAggregator. const ( Area DatadogMetricProviderAggregator = "area" @@ -114,7 +124,7 @@ const ( // Defines values for NullValue. const ( - True NullValue = true + NullValueTrue NullValue = true ) // Defines values for PrometheusMetricProviderType. @@ -229,25 +239,54 @@ type ArgoCDJobAgentConfig struct { // ArgoWorkflowJobAgentConfig defines model for ArgoWorkflowJobAgentConfig. type ArgoWorkflowJobAgentConfig struct { + union json.RawMessage +} + +// ArgoWorkflowJobAgentConfig0 Inline workflow execution +type ArgoWorkflowJobAgentConfig0 struct { // ApiKey ArgoWorkflow API token. ApiKey string `json:"apiKey"` - // Inline If the template passed in is meant to trigger a workflow template - Inline bool `json:"inline"` + // Inline Execute inline workflow (defaults to false if omitted) + Inline *ArgoWorkflowJobAgentConfig0Inline `json:"inline,omitempty"` // Name ArgoWorkflow job name Name string `json:"name"` - // Namespace ArgoWorkflow workflowTemplate namespace + // ServerUrl ArgoWorkflow server address (host[:port] or URL). + ServerUrl string `json:"serverUrl"` + + // Template Inline workflow spec or template. + Template string `json:"template"` +} + +// ArgoWorkflowJobAgentConfig0Inline Execute inline workflow (defaults to false if omitted) +type ArgoWorkflowJobAgentConfig0Inline bool + +// ArgoWorkflowJobAgentConfig1 WorkflowTemplate reference execution +type ArgoWorkflowJobAgentConfig1 struct { + // ApiKey ArgoWorkflow API token. + ApiKey string `json:"apiKey"` + + // Inline Use WorkflowTemplate reference (default mode) + Inline *ArgoWorkflowJobAgentConfig1Inline `json:"inline,omitempty"` + + // Name ArgoWorkflow job name + Name string `json:"name"` + + // Namespace WorkflowTemplate namespace Namespace string `json:"namespace"` // ServerUrl ArgoWorkflow server address (host[:port] or URL). ServerUrl string `json:"serverUrl"` - // Template ArgoWorkflow application template. + // Template WorkflowTemplate name. Template string `json:"template"` } +// ArgoWorkflowJobAgentConfig1Inline Use WorkflowTemplate reference (default mode) +type ArgoWorkflowJobAgentConfig1Inline bool + // BasicResource defines model for BasicResource. type BasicResource struct { Id string `json:"id"` @@ -1472,6 +1511,68 @@ type QueryResourcesJSONRequestBody QueryResourcesJSONBody // CreateWorkflowRunJSONRequestBody defines body for CreateWorkflowRun for application/json ContentType. type CreateWorkflowRunJSONRequestBody CreateWorkflowRunJSONBody +// AsArgoWorkflowJobAgentConfig0 returns the union data inside the ArgoWorkflowJobAgentConfig as a ArgoWorkflowJobAgentConfig0 +func (t ArgoWorkflowJobAgentConfig) AsArgoWorkflowJobAgentConfig0() (ArgoWorkflowJobAgentConfig0, error) { + var body ArgoWorkflowJobAgentConfig0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromArgoWorkflowJobAgentConfig0 overwrites any union data inside the ArgoWorkflowJobAgentConfig as the provided ArgoWorkflowJobAgentConfig0 +func (t *ArgoWorkflowJobAgentConfig) FromArgoWorkflowJobAgentConfig0(v ArgoWorkflowJobAgentConfig0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeArgoWorkflowJobAgentConfig0 performs a merge with any union data inside the ArgoWorkflowJobAgentConfig, using the provided ArgoWorkflowJobAgentConfig0 +func (t *ArgoWorkflowJobAgentConfig) MergeArgoWorkflowJobAgentConfig0(v ArgoWorkflowJobAgentConfig0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsArgoWorkflowJobAgentConfig1 returns the union data inside the ArgoWorkflowJobAgentConfig as a ArgoWorkflowJobAgentConfig1 +func (t ArgoWorkflowJobAgentConfig) AsArgoWorkflowJobAgentConfig1() (ArgoWorkflowJobAgentConfig1, error) { + var body ArgoWorkflowJobAgentConfig1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromArgoWorkflowJobAgentConfig1 overwrites any union data inside the ArgoWorkflowJobAgentConfig as the provided ArgoWorkflowJobAgentConfig1 +func (t *ArgoWorkflowJobAgentConfig) FromArgoWorkflowJobAgentConfig1(v ArgoWorkflowJobAgentConfig1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeArgoWorkflowJobAgentConfig1 performs a merge with any union data inside the ArgoWorkflowJobAgentConfig, using the provided ArgoWorkflowJobAgentConfig1 +func (t *ArgoWorkflowJobAgentConfig) MergeArgoWorkflowJobAgentConfig1(v ArgoWorkflowJobAgentConfig1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ArgoWorkflowJobAgentConfig) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ArgoWorkflowJobAgentConfig) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // AsJobUpdateEvent0 returns the union data inside the JobUpdateEvent as a JobUpdateEvent0 func (t JobUpdateEvent) AsJobUpdateEvent0() (JobUpdateEvent0, error) { var body JobUpdateEvent0 diff --git a/packages/workspace-engine-sdk/src/schema.ts b/packages/workspace-engine-sdk/src/schema.ts index a2fe77b44..84666c6a1 100644 --- a/packages/workspace-engine-sdk/src/schema.ts +++ b/packages/workspace-engine-sdk/src/schema.ts @@ -4,1516 +4,1476 @@ */ export interface paths { - "/v1/deployments/{deploymentId}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List release targets for a deployment */ - get: operations["listReleaseTargets"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/jobs/{jobId}/verification-status": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get aggregate verification status for a job */ - get: operations["getJobVerificationStatus"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/validate/resource-selector": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Validate a resource selector */ - post: operations["validateResourceSelector"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/aggregates": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Compute resource aggregate - * @description Filters resources by a CEL expression and groups them by specified properties, returning counts per group. - */ - post: operations["computeAggergate"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/query": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Query resources with CEL expression - * @description Returns paginated resources that match the provided CEL expression. Use the "resource" variable in your expression to access resource properties. - */ - post: operations["queryResources"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/workflows/{workflowId}/runs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Create a workflow run - * @description Creates a new run for the specified workflow with the provided inputs. - */ - post: operations["createWorkflowRun"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; -} -export type webhooks = Record; -export interface components { - schemas: { - AnyApprovalRule: { - /** Format: int32 */ - minApprovals: number; - }; - /** @enum {string} */ - ApprovalStatus: "approved" | "rejected"; - ArgoCDJobAgentConfig: { - /** @description ArgoCD API token. */ - apiKey: string; - /** @description ArgoCD server address (host[:port] or URL). */ - serverUrl: string; - /** @description ArgoCD application template. */ - template: string; - }; - BasicResource: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - workspaceId: string; - }; - BooleanValue: boolean; - CelMatcher: { - cel: string; - }; - DatadogMetricProvider: { - /** - * @description Datadog aggregator - * @default last - * @enum {string} - */ - aggregator: - | "avg" - | "min" - | "max" - | "sum" - | "last" - | "percentile" - | "mean" - | "l2norm" - | "area"; - /** - * @description Datadog API key (supports Go templates for variable references) - * @example {{.variables.dd_api_key}} - */ - apiKey: string; - /** - * @description Datadog Application key (supports Go templates for variable references) - * @example {{.variables.dd_app_key}} - */ - appKey: string; - /** @description Datadog formula (supports Go templates) */ - formula?: string; - /** - * Format: int64 - * @example 30 - */ - intervalSeconds?: number; - /** - * @description Datadog metrics queries (supports Go templates) - * @example { - * "q": "sum:requests.error.rate{service:{{.resource.name}}}" - * } - */ - queries: { - [key: string]: string; - }; - /** - * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) - * @default datadoghq.com - */ - site: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "datadog"; - }; - DeployDecision: { - policyResults: components["schemas"]["PolicyEvaluation"][]; - }; - Deployment: { - description?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; - metadata: { - [key: string]: string; - }; - name: string; - /** @description CEL expression to determine if the deployment should be used */ - resourceSelector?: string; - slug: string; - }; - DeploymentAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - }; - DeploymentDependencyRule: { - /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ - dependsOn: string; - }; - DeploymentJobAgent: { - config: components["schemas"]["JobAgentConfig"]; - ref: string; - /** @description CEL expression to determine if the job agent should be used */ - selector: string; - }; - DeploymentVariable: { - defaultValue?: components["schemas"]["LiteralValue"]; - deploymentId: string; - description?: string; - id: string; - key: string; - }; - DeploymentVariableValue: { - deploymentVariableId: string; - id: string; - /** Format: int64 */ - priority: number; - /** @description CEL expression to determine if the deployment variable value should be used */ - resourceSelector?: string; - value: components["schemas"]["Value"]; - }; - DeploymentVariableWithValues: { - values: components["schemas"]["DeploymentVariableValue"][]; - variable: components["schemas"]["DeploymentVariable"]; - }; - DeploymentVersion: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - deploymentId: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - message?: string; - metadata: { - [key: string]: string; - }; - name: string; - status: components["schemas"]["DeploymentVersionStatus"]; - tag: string; - }; - /** @enum {string} */ - DeploymentVersionStatus: - | "unspecified" - | "building" - | "ready" - | "failed" - | "rejected" - | "paused"; - DeploymentWindowRule: { - /** - * @description If true, deployments are only allowed during the window. If false, deployments are blocked during the window (deny window) - * @default true - */ - allowWindow: boolean; - /** - * Format: int32 - * @description Duration of each deployment window in minutes - */ - durationMinutes: number; - /** @description RFC 5545 recurrence rule defining when deployment windows start (e.g., FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9) */ - rrule: string; - /** @description IANA timezone for the rrule (e.g., America/New_York). Defaults to UTC if not specified */ - timezone?: string; - }; - DeploymentWithVariablesAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - variables: components["schemas"]["DeploymentVariableWithValues"][]; - }; - DispatchContext: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - /** @description Resolved input values for the workflow run. */ - inputs?: { - [key: string]: unknown; - }; - jobAgent: components["schemas"]["JobAgent"]; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - release?: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - variables?: { - [key: string]: components["schemas"]["LiteralValue"]; - }; - version?: components["schemas"]["DeploymentVersion"]; - workflow?: components["schemas"]["Workflow"]; - workflowJob?: components["schemas"]["WorkflowJob"]; - workflowRun?: components["schemas"]["WorkflowRun"]; - }; - EntityRelation: { - direction: components["schemas"]["RelationDirection"]; - entity: components["schemas"]["RelatableEntity"]; - /** @description ID of the related entity */ - entityId: string; - entityType: components["schemas"]["RelatableEntityType"]; - rule: components["schemas"]["RelationshipRule"]; - }; - Environment: { - /** Format: date-time */ - createdAt: string; - description?: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - /** @description CEL expression to determine if the environment should be used */ - resourceSelector?: string; - workspaceId: string; - }; - EnvironmentProgressionRule: { - /** @description CEL expression to determine if the environment progression rule should be used */ - dependsOnEnvironmentSelector: string; - /** - * Format: int32 - * @description Maximum age of dependency deployment before blocking progression (prevents stale promotions) - */ - 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 - */ - minimumSockTimeMinutes: number; - /** - * Format: float - * @default 100 - */ - minimumSuccessPercentage: number; - successStatuses?: components["schemas"]["JobStatus"][]; - }; - EnvironmentSummary: { - id: string; - name: string; - }; - EnvironmentWithSystems: components["schemas"]["Environment"] & { - systems: components["schemas"]["System"][]; - }; - ErrorResponse: { - /** @example Workspace not found */ - error?: string; - }; - EvaluateReleaseTargetRequest: { - releaseTarget: components["schemas"]["ReleaseTarget"]; - version: components["schemas"]["DeploymentVersion"]; - }; - EvaluationScope: { - environmentId?: string; - versionId?: string; - }; - GithubEntity: { - installationId: number; - slug: string; - }; - GithubJobAgentConfig: { - /** - * Format: int - * @description GitHub app installation ID. - */ - installationId: number; - /** @description GitHub repository owner. */ - owner: string; - /** @description Git ref to run the workflow on (defaults to "main" if omitted). */ - ref?: string; - /** @description GitHub repository name. */ - repo: string; - /** - * Format: int64 - * @description GitHub Actions workflow ID. - */ - workflowId: number; - }; - GradualRolloutRule: { - /** - * @description Strategy for scheduling deployments to release targets. "linear": Each target is deployed at a fixed interval of timeScaleInterval seconds. "linear-normalized": Deployments are spaced evenly so that the last target is scheduled at or before timeScaleInterval seconds. See rolloutType algorithm documentation for details. - * @enum {string} - */ - rolloutType: "linear" | "linear-normalized"; - /** - * Format: int32 - * @description Base time interval in seconds used to compute the delay between deployments to release targets. - */ - timeScaleInterval: number; - }; - HTTPMetricProvider: { - /** @description Request body (supports Go templates) */ - body?: string; - /** @description HTTP headers (values support Go templates) */ - headers?: { - [key: string]: string; - }; - /** - * @description HTTP method - * @default GET - * @enum {string} - */ - method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - /** - * @description Request timeout (duration string, e.g., "30s") - * @default 30s - */ - timeout: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "http"; - /** - * @description HTTP endpoint URL (supports Go templates) - * @example http://{{ .resource.name }}.{{ .environment.name }}/health - */ - url: string; - }; - IntegerValue: number; - Job: { - /** Format: date-time */ - completedAt?: string; - /** Format: date-time */ - createdAt: string; - dispatchContext?: components["schemas"]["DispatchContext"]; - externalId?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId: string; - message?: string; - metadata: { - [key: string]: string; - }; - releaseId: string; - /** Format: date-time */ - startedAt?: string; - status: components["schemas"]["JobStatus"]; - traceToken?: string; - /** Format: date-time */ - updatedAt: string; - workflowJobId: string; - }; - JobAgent: { - config: components["schemas"]["JobAgentConfig"]; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - type: string; - workspaceId: string; - }; - JobAgentConfig: { - [key: string]: unknown; - }; - /** @enum {string} */ - JobStatus: - | "cancelled" - | "skipped" - | "inProgress" - | "actionRequired" - | "pending" - | "failure" - | "invalidJobAgent" - | "invalidIntegration" - | "externalRunNotFound" - | "successful"; - JobSummary: { - id: string; - /** @description External links extracted from job metadata */ - links?: { - [key: string]: string; - }; - message?: string; - status: components["schemas"]["JobStatus"]; - verifications: components["schemas"]["JobVerification"][]; - }; - JobUpdateEvent: { - agentId?: string; - externalId?: string; - fieldsToUpdate?: ( - | "completedAt" - | "createdAt" - | "dispatchContext" - | "externalId" - | "id" - | "jobAgentConfig" - | "jobAgentId" - | "message" - | "metadata" - | "releaseId" - | "startedAt" - | "status" - | "traceToken" - | "updatedAt" - | "workflowJobId" - )[]; - id?: string; - job: components["schemas"]["Job"]; - } & (unknown | unknown); - JobVerification: { - /** - * Format: date-time - * @description When verification was created - */ - createdAt: string; - id: string; - jobId: string; - /** @description Summary message of verification result */ - message?: string; - /** @description Metrics associated with this verification */ - metrics: components["schemas"]["VerificationMetricStatus"][]; - }; - /** @enum {string} */ - JobVerificationStatus: "running" | "passed" | "failed" | "cancelled"; - JobWithRelease: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - job: components["schemas"]["Job"]; - release: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - }; - JobWithVerifications: { - job: components["schemas"]["Job"]; - verifications: components["schemas"]["JobVerification"][]; - }; - 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} */ - NullValue: true; - NumberValue: number; - ObjectValue: { - object: { - [key: string]: unknown; - }; - }; - Policy: { - createdAt: string; - description?: string; - enabled: boolean; - id: string; - /** @description Arbitrary metadata for the policy (record) */ - metadata: { - [key: string]: string; - }; - name: string; - priority: number; - rules: components["schemas"]["PolicyRule"][]; - /** @description CEL expression for matching release targets. Use "true" to match all targets. */ - selector: string; - workspaceId: string; - }; - PolicyEvaluation: { - policy?: components["schemas"]["Policy"]; - ruleResults: components["schemas"]["RuleEvaluation"][]; - summary?: string; - }; - PolicyRule: { - anyApproval?: components["schemas"]["AnyApprovalRule"]; - createdAt: string; - deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; - deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; - environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; - gradualRollout?: components["schemas"]["GradualRolloutRule"]; - id: string; - policyId: string; - retry?: components["schemas"]["RetryRule"]; - rollback?: components["schemas"]["RollbackRule"]; - verification?: components["schemas"]["VerificationRule"]; - versionCooldown?: components["schemas"]["VersionCooldownRule"]; - versionSelector?: components["schemas"]["VersionSelectorRule"]; - }; - PolicySkip: { - /** - * Format: date-time - * @description When this skip was created - */ - createdAt: string; - /** @description User ID who created this skip */ - createdBy: string; - /** @description Environment this skip applies to. If null, applies to all environments. */ - environmentId?: string; - /** - * Format: date-time - * @description When this skip expires. If null, skip never expires. - */ - expiresAt?: string; - /** @description Unique identifier for the skip */ - id: string; - /** @description Required reason for why this skip is needed (e.g., incident ticket, emergency situation) */ - reason: string; - /** @description Resource this skip applies to. If null, applies to all resources (in the environment if specified, or globally). */ - resourceId?: string; - /** @description Rule ID this skip applies to */ - ruleId: string; - /** @description Deployment version this skip applies to */ - versionId: string; - /** @description Workspace this skip belongs to */ - workspaceId: string; - }; - PrometheusMetricProvider: { - /** - * @description Prometheus server address (supports Go templates) - * @example http://prometheus.example.com:9090 - */ - address: string; - /** @description Authentication configuration for Prometheus */ - authentication?: { - /** - * @description Bearer token for authentication (supports Go templates for variable references) - * @example {{.variables.prometheus_token}} - */ - bearerToken?: string; - /** @description OAuth2 client credentials flow */ - oauth2?: { - /** @description OAuth2 client ID (supports Go templates) */ - clientId: string; - /** @description OAuth2 client secret (supports Go templates) */ - clientSecret: string; - /** @description OAuth2 scopes */ - scopes?: string[]; - /** @description Token endpoint URL */ - tokenUrl: string; - }; - }; - /** @description Additional HTTP headers for the Prometheus request (values support Go templates) */ - headers?: { - /** @example X-Scope-OrgID */ - key: string; - /** @example tenant_a */ - value: string; - }[]; - /** - * @description Skip TLS certificate verification - * @default false - */ - insecure: boolean; - /** - * @description PromQL query expression (supports Go templates) - * @example sum(irate(istio_requests_total{reporter="source",destination_service=~"{{.resource.name}}",response_code!~"5.*"}[5m])) - */ - query: string; - /** @description If provided, a range query (/api/v1/query_range) is used instead of an instant query (/api/v1/query) */ - rangeQuery?: { + "/v1/deployments/{deploymentId}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List release targets for a deployment */ + get: operations["listReleaseTargets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/jobs/{jobId}/verification-status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get aggregate verification status for a job */ + get: operations["getJobVerificationStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/validate/resource-selector": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Validate a resource selector */ + post: operations["validateResourceSelector"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/aggregates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; /** - * @description How far back from now for the query end, as a Prometheus duration (e.g., "0s" for now, "1m" for 1 minute ago). Defaults to "0s" (now) if unset. - * @example 0s + * Compute resource aggregate + * @description Filters resources by a CEL expression and groups them by specified properties, returning counts per group. */ - end?: string; + post: operations["computeAggergate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/resources/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; /** - * @description How far back from now to start the query, as a Prometheus duration (e.g., "5m", "1h"). Defaults to 10 * step if unset. - * @example 5m + * Query resources with CEL expression + * @description Returns paginated resources that match the provided CEL expression. Use the "resource" variable in your expression to access resource properties. */ - start?: string; + post: operations["queryResources"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/workspaces/{workspaceId}/workflows/{workflowId}/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; /** - * @description Query resolution step width as a Prometheus duration (e.g., "15s", "1m", "500ms") - * @example 1m + * Create a workflow run + * @description Creates a new run for the specified workflow with the provided inputs. */ - step: string; - }; - /** - * Format: int64 - * @description Query timeout in seconds - * @example 30 - */ - timeout?: number; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "prometheus"; - }; - PropertiesMatcher: { - properties: components["schemas"]["PropertyMatcher"][]; - }; - PropertyMatcher: { - fromProperty: string[]; - /** @enum {string} */ - operator: - | "equals" - | "notEquals" - | "contains" - | "startsWith" - | "endsWith" - | "regex"; - toProperty: string[]; - }; - ReferenceValue: { - path: string[]; - reference: string; + post: operations["createWorkflowRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - RelatableEntity: - | components["schemas"]["Deployment"] - | components["schemas"]["Environment"] - | components["schemas"]["Resource"]; - /** @enum {string} */ - RelatableEntityType: "deployment" | "environment" | "resource"; - /** @enum {string} */ - RelationDirection: "from" | "to"; - RelationshipRule: { - description?: string; - /** @description CEL expression to determine if the relationship rule should be used */ - fromSelector?: string; - fromType: components["schemas"]["RelatableEntityType"]; - id: string; - matcher: - | components["schemas"]["CelMatcher"] - | components["schemas"]["PropertiesMatcher"]; - metadata: { - [key: string]: string; - }; - name: string; - reference: string; - relationshipType: string; - /** @description CEL expression to determine if the relationship rule should be used */ - toSelector?: string; - toType: components["schemas"]["RelatableEntityType"]; - workspaceId: string; - }; - Release: { - createdAt: string; - encryptedVariables: string[]; - /** Format: uuid */ - id: string; - releaseTarget: components["schemas"]["ReleaseTarget"]; - variables: { - [key: string]: components["schemas"]["LiteralValue"]; - }; - version: components["schemas"]["DeploymentVersion"]; - }; - ReleaseTarget: { - deploymentId: string; - environmentId: string; - resourceId: string; - }; - ReleaseTargetAndState: { - releaseTarget: components["schemas"]["ReleaseTarget"]; - state: components["schemas"]["ReleaseTargetState"]; - }; - ReleaseTargetItem: { - currentVersion?: { - id: string; - name: string; - tag: string; - } | null; - desiredVersion?: { - id: string; - name: string; - tag: string; - } | null; - environment: { - id: string; - name: string; - }; - latestJob?: { - /** Format: date-time */ - completedAt?: string; - /** Format: date-time */ - createdAt: string; - id: string; - message?: string; - metadata: { - [key: string]: string; - }; - status: components["schemas"]["JobStatus"]; - verifications: { - id: string; - jobId: string; - metrics: { - count: number; - failureCondition: string | null; - failureThreshold: number | null; +} +export type webhooks = Record; +export interface components { + schemas: { + AnyApprovalRule: { + /** Format: int32 */ + minApprovals: number; + }; + /** @enum {string} */ + ApprovalStatus: "approved" | "rejected"; + ArgoCDJobAgentConfig: { + /** @description ArgoCD API token. */ + apiKey: string; + /** @description ArgoCD server address (host[:port] or URL). */ + serverUrl: string; + /** @description ArgoCD application template. */ + template: string; + }; + ArgoWorkflowJobAgentConfig: { + /** @description ArgoWorkflow API token. */ + apiKey: string; + /** + * @description Execute inline workflow (defaults to false if omitted) + * @enum {boolean} + */ + inline?: true; + /** @description ArgoWorkflow job name */ + name: string; + /** @description ArgoWorkflow server address (host[:port] or URL). */ + serverUrl: string; + /** @description Inline workflow spec or template. */ + template: string; + } | { + /** @description ArgoWorkflow API token. */ + apiKey: string; + /** + * @description Use WorkflowTemplate reference (default mode) + * @enum {boolean} + */ + inline?: false; + /** @description ArgoWorkflow job name */ + name: string; + /** @description WorkflowTemplate namespace */ + namespace: string; + /** @description ArgoWorkflow server address (host[:port] or URL). */ + serverUrl: string; + /** @description WorkflowTemplate name. */ + template: string; + }; + BasicResource: { id: string; - jobId?: string; + identifier: string; + kind: string; name: string; - policyRuleVerificationMetricId?: string; - provider: { - [key: string]: unknown; + version: string; + workspaceId: string; + }; + BooleanValue: boolean; + CelMatcher: { + cel: string; + }; + DatadogMetricProvider: { + /** + * @description Datadog aggregator + * @default last + * @enum {string} + */ + aggregator: "avg" | "min" | "max" | "sum" | "last" | "percentile" | "mean" | "l2norm" | "area"; + /** + * @description Datadog API key (supports Go templates for variable references) + * @example {{.variables.dd_api_key}} + */ + apiKey: string; + /** + * @description Datadog Application key (supports Go templates for variable references) + * @example {{.variables.dd_app_key}} + */ + appKey: string; + /** @description Datadog formula (supports Go templates) */ + formula?: string; + /** + * Format: int64 + * @example 30 + */ + intervalSeconds?: number; + /** + * @description Datadog metrics queries (supports Go templates) + * @example { + * "q": "sum:requests.error.rate{service:{{.resource.name}}}" + * } + */ + queries: { + [key: string]: string; }; - successCondition: string; - successThreshold: number | null; - }[]; - }[]; - } | null; - releaseTarget: { - deploymentId: string; - environmentId: string; - resourceId: string; - }; - resource: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - }; - }; - ReleaseTargetPreview: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - system: components["schemas"]["System"]; - }; - ReleaseTargetState: { - currentRelease?: components["schemas"]["Release"]; - desiredRelease?: components["schemas"]["Release"]; - latestJob?: components["schemas"]["JobWithVerifications"]; - }; - ReleaseTargetSummary: { - currentVersion?: components["schemas"]["VersionSummary"]; - desiredVersion?: components["schemas"]["VersionSummary"]; - environment: components["schemas"]["EnvironmentSummary"]; - latestJob?: components["schemas"]["JobSummary"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["ResourceSummary"]; - }; - ReleaseTargetWithState: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["Resource"]; - state: components["schemas"]["ReleaseTargetState"]; - }; - ResolvedPolicy: { - environmentIds: string[]; - policy: components["schemas"]["Policy"]; - releaseTargets: components["schemas"]["ReleaseTarget"][]; - }; - Resource: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - deletedAt?: string; - id: string; - identifier: string; - kind: string; - /** Format: date-time */ - lockedAt?: string; - metadata: { - [key: string]: string; - }; - name: string; - providerId?: string; - /** Format: date-time */ - updatedAt?: string; - version: string; - workspaceId: string; - }; - ResourcePreviewRequest: { - config: { - [key: string]: unknown; - }; - identifier: string; - kind: string; - metadata: { - [key: string]: string; - }; - name: string; - version: string; - }; - ResourceProvider: { - /** Format: date-time */ - createdAt: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - /** Format: uuid */ - workspaceId: string; - }; - ResourceSummary: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - }; - ResourceVariable: { - key: string; - resourceId: string; - value: components["schemas"]["Value"]; - }; - ResourceVariablesBulkUpdateEvent: { - resourceId: string; - variables: { - [key: string]: unknown; - }; - }; - RetryRule: { - /** - * Format: int32 - * @description Minimum seconds to wait between retry attempts. If null, retries are allowed immediately after job completion. - */ - backoffSeconds?: number; - /** - * @description Backoff strategy: "linear" uses constant backoffSeconds delay, "exponential" doubles the delay with each retry (backoffSeconds * 2^(attempt-1)). - * @default linear - * @enum {string} - */ - backoffStrategy: "linear" | "exponential"; - /** - * Format: int32 - * @description Maximum backoff time in seconds (cap for exponential backoff). If null, no maximum is enforced. - */ - maxBackoffSeconds?: number; - /** - * Format: int32 - * @description Maximum number of retries allowed. 0 means no retries (1 attempt total), 3 means up to 4 attempts (1 initial + 3 retries). - */ - maxRetries: number; - /** @description Job statuses that count toward the retry limit. If null or empty, defaults to ["failure", "invalidIntegration", "invalidJobAgent"] for maxRetries > 0, or ["failure", "invalidIntegration", "invalidJobAgent", "successful"] for maxRetries = 0. Cancelled and skipped jobs never count by default (allows redeployment after cancellation). Example: ["failure", "cancelled"] will only count failed/cancelled jobs. */ - retryOnStatuses?: components["schemas"]["JobStatus"][]; - }; - RollbackRule: { - /** @description Job statuses that will trigger a rollback */ - onJobStatuses?: components["schemas"]["JobStatus"][]; - /** - * @description If true, a release target will be rolled back if the verification fails - * @default false - */ - onVerificationFailure: boolean; - }; - RuleEvaluation: { - /** @description Whether the rule requires an action (e.g., approval, wait) */ - actionRequired: boolean; - /** - * @description Type of action required - * @enum {string} - */ - actionType?: "approval" | "wait"; - /** @description Whether the rule allows the deployment */ - allowed: boolean; - /** @description Additional details about the rule evaluation */ - details: { - [key: string]: unknown; - }; - /** @description Human-readable explanation of the rule result */ - message: string; - /** - * Format: date-time - * @description The time when this rule should be re-evaluated (e.g., when soak time will be complete, when gradual rollout schedule is due) - */ - nextEvaluationTime?: string; - /** @description The ID of the rule that was evaluated */ - ruleId: string; - /** - * Format: date-time - * @description The time when the rule requirement was satisfied (e.g., when approvals were met, soak time completed) - */ - satisfiedAt?: string; - }; - SensitiveValue: { - valueHash: string; - }; - SleepMetricProvider: { - /** - * Format: int32 - * @example 30 - */ - durationSeconds: number; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "sleep"; - }; - StringValue: string; - System: { - description?: string; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - workspaceId: string; - }; - SystemDeploymentLink: { - deploymentId: string; - systemId: string; - }; - SystemEnvironmentLink: { - environmentId: string; - systemId: string; - }; - TerraformCloudJobAgentConfig: { - /** @description Terraform Cloud address (e.g. https://app.terraform.io). */ - address: string; - /** @description Terraform Cloud organization name. */ - organization: string; - /** @description Terraform Cloud workspace template. */ - template: string; - /** @description Terraform Cloud API token. */ - token: string; - /** - * @description Whether to create a TFC run on dispatch. When false, only the workspace and variables are synced. - * @default true - */ - triggerRunOnChange: boolean; - /** @description The ctrlplane API endpoint for TFC webhook notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook). */ - webhookUrl: string; - }; - TerraformCloudRunMetricProvider: { - /** - * @description Terraform Cloud address - * @example https://app.terraform.io - */ - address: string; - /** - * @description Terraform Cloud run ID - * @example run-1234567890 - */ - runId: string; - /** - * @description Terraform Cloud token - * @example {{.variables.terraform_cloud_token}} - */ - token: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "terraformCloudRun"; - }; - TestRunnerJobAgentConfig: { - /** - * Format: int - * @description Delay in seconds before resolving the job. - */ - delaySeconds?: number; - /** @description Optional message to include in the job output. */ - message?: string; - /** @description Final status to set (e.g. "successful", "failure"). */ - status?: string; - }; - UserApprovalRecord: { - createdAt: string; - environmentId: string; - reason?: string; - status: components["schemas"]["ApprovalStatus"]; - userId: string; - versionId: string; - }; - Value: - | components["schemas"]["LiteralValue"] - | components["schemas"]["ReferenceValue"] - | components["schemas"]["SensitiveValue"]; - VerificationMeasurement: { - /** @description Raw measurement data */ - data: { - [key: string]: unknown; - }; - /** - * Format: date-time - * @description When measurement was taken - */ - measuredAt: string; - /** @description Measurement result message */ - message?: string; - status: components["schemas"]["VerificationMeasurementStatus"]; - }; - /** - * @description Status of a verification measurement - * @enum {string} - */ - VerificationMeasurementStatus: "passed" | "failed" | "inconclusive"; - VerificationMetricSpec: { - /** @description Number of measurements to take */ - count: number; - /** - * @description CEL expression to evaluate measurement failure (e.g., "result.statusCode == 500"), if not provided, a failure is just the opposite of the success condition - * @example result.statusCode == 500 - */ - failureCondition?: string; - /** - * @description Stop after this many consecutive failures (0 = no limit) - * @default 0 - */ - failureThreshold: number; - /** - * Format: int32 - * @description Interval between measurements in seconds - * @example 30 - */ - intervalSeconds: number; - /** @description Name of the verification metric */ - name: string; - provider: components["schemas"]["MetricProvider"]; - /** - * @description CEL expression to evaluate measurement success (e.g., "result.statusCode == 200") - * @example result.statusCode == 200 - */ - successCondition: string; - /** - * @description Minimum number of consecutive successful measurements required to consider the metric successful - * @example 0 - */ - successThreshold?: number; - }; - VerificationMetricStatus: components["schemas"]["VerificationMetricSpec"] & { - id: string; - /** @description Individual verification measurements taken for this metric */ - measurements: components["schemas"]["VerificationMeasurement"][]; - }; - VerificationRule: { - /** @description Metrics to verify */ - metrics: components["schemas"]["VerificationMetricSpec"][]; - /** - * @description When to trigger verification - * @default jobSuccess - * @enum {string} - */ - triggerOn: "jobCreated" | "jobStarted" | "jobSuccess" | "jobFailure"; - }; - VersionCooldownRule: { - /** - * Format: int32 - * @description Minimum time in seconds that must pass since the currently deployed (or in-progress) version was created before allowing another deployment. This enables batching of frequent upstream releases into periodic deployments. - */ - intervalSeconds: number; - }; - VersionSelectorRule: { - /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ - description?: string; - /** @description CEL expression to determine if the version selector should be used */ - selector: string; - }; - VersionSummary: { - id: string; - name: string; - tag: string; - }; - Workflow: { - id: string; - inputs: components["schemas"]["WorkflowInput"][]; - jobs: components["schemas"]["WorkflowJobAgent"][]; - name: string; - }; - WorkflowArrayInput: components["schemas"]["WorkflowManualArrayInput"]; - WorkflowBooleanInput: { - default?: boolean; - key: string; - /** @enum {string} */ - type: "boolean"; - }; - WorkflowInput: - | components["schemas"]["WorkflowStringInput"] - | components["schemas"]["WorkflowNumberInput"] - | components["schemas"]["WorkflowBooleanInput"] - | components["schemas"]["WorkflowArrayInput"] - | components["schemas"]["WorkflowObjectInput"]; - WorkflowJob: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - index: number; - /** @description Reference to the job agent */ - ref: string; - workflowRunId: string; - }; - WorkflowJobAgent: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - name: string; - /** @description Reference to the job agent */ - ref: string; - /** @description CEL expression to determine if the job agent should dispatch a job */ - selector: string; - }; - WorkflowManualArrayInput: { - default?: { - [key: string]: unknown; - }[]; - key: string; - /** @enum {string} */ - type: "array"; - }; - WorkflowNumberInput: { - default?: number; - key: string; - /** @enum {string} */ - type: "number"; - }; - WorkflowObjectInput: { - default?: { - [key: string]: unknown; - }; - key: string; - /** @enum {string} */ - type: "object"; - }; - WorkflowRun: { - id: string; - inputs: { - [key: string]: unknown; - }; - workflowId: string; - }; - WorkflowStringInput: { - default?: string; - key: string; - /** @enum {string} */ - type: "string"; - }; - }; - responses: never; - parameters: { - /** @description Type of the entity (deployment, environment, or resource) */ - relatableEntityType: components["schemas"]["RelatableEntityType"]; - }; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - listReleaseTargets: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of release targets for the deployment */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetItem"][]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobVerificationStatus: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the job */ - jobId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Aggregate verification status for the job */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { /** - * @description Aggregate verification status + * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) + * @default datadoghq.com + */ + site: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) * @enum {string} */ - status: "passed" | "running" | "failed" | ""; - }; + type: "datadog"; }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; + DeployDecision: { + policyResults: components["schemas"]["PolicyEvaluation"][]; }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; + Deployment: { + description?: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + jobAgentId?: string; + jobAgents?: components["schemas"]["DeploymentJobAgent"][]; + metadata: { + [key: string]: string; + }; + name: string; + /** @description CEL expression to determine if the deployment should be used */ + resourceSelector?: string; + slug: string; }; - }; - }; - }; - validateResourceSelector: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: { - content: { - "application/json": { - /** @description CEL expression to validate. */ - resourceSelector: string; + DeploymentAndSystems: { + deployment: components["schemas"]["Deployment"]; + systems: components["schemas"]["System"][]; }; - }; - }; - responses: { - /** @description The validated resource selector */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - errors: string[]; - valid: boolean; - }; - }; - }; - }; - }; - computeAggergate: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @description CEL expression to filter resources. Defaults to "true" (all resources). */ - filter?: string; - groupBy?: { - /** @description Label for this grouping */ + DeploymentDependencyRule: { + /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ + dependsOn: string; + }; + DeploymentJobAgent: { + config: components["schemas"]["JobAgentConfig"]; + ref: string; + /** @description CEL expression to determine if the job agent should be used */ + selector: string; + }; + DeploymentVariable: { + defaultValue?: components["schemas"]["LiteralValue"]; + deploymentId: string; + description?: string; + id: string; + key: string; + }; + DeploymentVariableValue: { + deploymentVariableId: string; + id: string; + /** Format: int64 */ + priority: number; + /** @description CEL expression to determine if the deployment variable value should be used */ + resourceSelector?: string; + value: components["schemas"]["Value"]; + }; + DeploymentVariableWithValues: { + values: components["schemas"]["DeploymentVariableValue"][]; + variable: components["schemas"]["DeploymentVariable"]; + }; + DeploymentVersion: { + config: { + [key: string]: unknown; + }; + /** Format: date-time */ + createdAt: string; + deploymentId: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + message?: string; + metadata: { + [key: string]: string; + }; name: string; - /** @description Dot-path property to group by (e.g. kind, metadata.region) */ - property: string; - }[]; + status: components["schemas"]["DeploymentVersionStatus"]; + tag: string; }; - }; - }; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - groups: { - /** @description Number of resources in this group */ - count: number; - /** @description Map of grouping name to its value for this bucket */ - key: { + /** @enum {string} */ + DeploymentVersionStatus: "unspecified" | "building" | "ready" | "failed" | "rejected" | "paused"; + DeploymentWindowRule: { + /** + * @description If true, deployments are only allowed during the window. If false, deployments are blocked during the window (deny window) + * @default true + */ + allowWindow: boolean; + /** + * Format: int32 + * @description Duration of each deployment window in minutes + */ + durationMinutes: number; + /** @description RFC 5545 recurrence rule defining when deployment windows start (e.g., FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9) */ + rrule: string; + /** @description IANA timezone for the rrule (e.g., America/New_York). Defaults to UTC if not specified */ + timezone?: string; + }; + DeploymentWithVariablesAndSystems: { + deployment: components["schemas"]["Deployment"]; + systems: components["schemas"]["System"][]; + variables: components["schemas"]["DeploymentVariableWithValues"][]; + }; + DispatchContext: { + deployment?: components["schemas"]["Deployment"]; + environment?: components["schemas"]["Environment"]; + /** @description Resolved input values for the workflow run. */ + inputs?: { + [key: string]: unknown; + }; + jobAgent: components["schemas"]["JobAgent"]; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + release?: components["schemas"]["Release"]; + resource?: components["schemas"]["Resource"]; + variables?: { + [key: string]: components["schemas"]["LiteralValue"]; + }; + version?: components["schemas"]["DeploymentVersion"]; + workflow?: components["schemas"]["Workflow"]; + workflowJob?: components["schemas"]["WorkflowJob"]; + workflowRun?: components["schemas"]["WorkflowRun"]; + }; + EntityRelation: { + direction: components["schemas"]["RelationDirection"]; + entity: components["schemas"]["RelatableEntity"]; + /** @description ID of the related entity */ + entityId: string; + entityType: components["schemas"]["RelatableEntityType"]; + rule: components["schemas"]["RelationshipRule"]; + }; + Environment: { + /** Format: date-time */ + createdAt: string; + description?: string; + id: string; + metadata: { + [key: string]: string; + }; + name: string; + /** @description CEL expression to determine if the environment should be used */ + resourceSelector?: string; + workspaceId: string; + }; + EnvironmentProgressionRule: { + /** @description CEL expression to determine if the environment progression rule should be used */ + dependsOnEnvironmentSelector: string; + /** + * Format: int32 + * @description Maximum age of dependency deployment before blocking progression (prevents stale promotions) + */ + 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 + */ + minimumSockTimeMinutes: number; + /** + * Format: float + * @default 100 + */ + minimumSuccessPercentage: number; + successStatuses?: components["schemas"]["JobStatus"][]; + }; + EnvironmentSummary: { + id: string; + name: string; + }; + EnvironmentWithSystems: components["schemas"]["Environment"] & { + systems: components["schemas"]["System"][]; + }; + ErrorResponse: { + /** @example Workspace not found */ + error?: string; + }; + EvaluateReleaseTargetRequest: { + releaseTarget: components["schemas"]["ReleaseTarget"]; + version: components["schemas"]["DeploymentVersion"]; + }; + EvaluationScope: { + environmentId?: string; + versionId?: string; + }; + GithubEntity: { + installationId: number; + slug: string; + }; + GithubJobAgentConfig: { + /** + * Format: int + * @description GitHub app installation ID. + */ + installationId: number; + /** @description GitHub repository owner. */ + owner: string; + /** @description Git ref to run the workflow on (defaults to "main" if omitted). */ + ref?: string; + /** @description GitHub repository name. */ + repo: string; + /** + * Format: int64 + * @description GitHub Actions workflow ID. + */ + workflowId: number; + }; + GradualRolloutRule: { + /** + * @description Strategy for scheduling deployments to release targets. "linear": Each target is deployed at a fixed interval of timeScaleInterval seconds. "linear-normalized": Deployments are spaced evenly so that the last target is scheduled at or before timeScaleInterval seconds. See rolloutType algorithm documentation for details. + * @enum {string} + */ + rolloutType: "linear" | "linear-normalized"; + /** + * Format: int32 + * @description Base time interval in seconds used to compute the delay between deployments to release targets. + */ + timeScaleInterval: number; + }; + HTTPMetricProvider: { + /** @description Request body (supports Go templates) */ + body?: string; + /** @description HTTP headers (values support Go templates) */ + headers?: { [key: string]: string; - }; + }; + /** + * @description HTTP method + * @default GET + * @enum {string} + */ + method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; + /** + * @description Request timeout (duration string, e.g., "30s") + * @default 30s + */ + timeout: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "http"; + /** + * @description HTTP endpoint URL (supports Go templates) + * @example http://{{ .resource.name }}.{{ .environment.name }}/health + */ + url: string; + }; + IntegerValue: number; + Job: { + /** Format: date-time */ + completedAt?: string; + /** Format: date-time */ + createdAt: string; + dispatchContext?: components["schemas"]["DispatchContext"]; + externalId?: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + jobAgentId: string; + message?: string; + metadata: { + [key: string]: string; + }; + releaseId: string; + /** Format: date-time */ + startedAt?: string; + status: components["schemas"]["JobStatus"]; + traceToken?: string; + /** Format: date-time */ + updatedAt: string; + workflowJobId: string; + }; + JobAgent: { + config: components["schemas"]["JobAgentConfig"]; + id: string; + metadata?: { + [key: string]: string; + }; + name: string; + type: string; + workspaceId: string; + }; + JobAgentConfig: { + [key: string]: unknown; + }; + /** @enum {string} */ + JobStatus: "cancelled" | "skipped" | "inProgress" | "actionRequired" | "pending" | "failure" | "invalidJobAgent" | "invalidIntegration" | "externalRunNotFound" | "successful"; + JobSummary: { + id: string; + /** @description External links extracted from job metadata */ + links?: { + [key: string]: string; + }; + message?: string; + status: components["schemas"]["JobStatus"]; + verifications: components["schemas"]["JobVerification"][]; + }; + JobUpdateEvent: { + agentId?: string; + externalId?: string; + fieldsToUpdate?: ("completedAt" | "createdAt" | "dispatchContext" | "externalId" | "id" | "jobAgentConfig" | "jobAgentId" | "message" | "metadata" | "releaseId" | "startedAt" | "status" | "traceToken" | "updatedAt" | "workflowJobId")[]; + id?: string; + job: components["schemas"]["Job"]; + } & (unknown | unknown); + JobVerification: { + /** + * Format: date-time + * @description When verification was created + */ + createdAt: string; + id: string; + jobId: string; + /** @description Summary message of verification result */ + message?: string; + /** @description Metrics associated with this verification */ + metrics: components["schemas"]["VerificationMetricStatus"][]; + }; + /** @enum {string} */ + JobVerificationStatus: "running" | "passed" | "failed" | "cancelled"; + JobWithRelease: { + deployment?: components["schemas"]["Deployment"]; + environment?: components["schemas"]["Environment"]; + job: components["schemas"]["Job"]; + release: components["schemas"]["Release"]; + resource?: components["schemas"]["Resource"]; + }; + JobWithVerifications: { + job: components["schemas"]["Job"]; + verifications: components["schemas"]["JobVerification"][]; + }; + 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} */ + NullValue: true; + NumberValue: number; + ObjectValue: { + object: { + [key: string]: unknown; + }; + }; + Policy: { + createdAt: string; + description?: string; + enabled: boolean; + id: string; + /** @description Arbitrary metadata for the policy (record) */ + metadata: { + [key: string]: string; + }; + name: string; + priority: number; + rules: components["schemas"]["PolicyRule"][]; + /** @description CEL expression for matching release targets. Use "true" to match all targets. */ + selector: string; + workspaceId: string; + }; + PolicyEvaluation: { + policy?: components["schemas"]["Policy"]; + ruleResults: components["schemas"]["RuleEvaluation"][]; + summary?: string; + }; + PolicyRule: { + anyApproval?: components["schemas"]["AnyApprovalRule"]; + createdAt: string; + deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; + deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; + environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; + gradualRollout?: components["schemas"]["GradualRolloutRule"]; + id: string; + policyId: string; + retry?: components["schemas"]["RetryRule"]; + rollback?: components["schemas"]["RollbackRule"]; + verification?: components["schemas"]["VerificationRule"]; + versionCooldown?: components["schemas"]["VersionCooldownRule"]; + versionSelector?: components["schemas"]["VersionSelectorRule"]; + }; + PolicySkip: { + /** + * Format: date-time + * @description When this skip was created + */ + createdAt: string; + /** @description User ID who created this skip */ + createdBy: string; + /** @description Environment this skip applies to. If null, applies to all environments. */ + environmentId?: string; + /** + * Format: date-time + * @description When this skip expires. If null, skip never expires. + */ + expiresAt?: string; + /** @description Unique identifier for the skip */ + id: string; + /** @description Required reason for why this skip is needed (e.g., incident ticket, emergency situation) */ + reason: string; + /** @description Resource this skip applies to. If null, applies to all resources (in the environment if specified, or globally). */ + resourceId?: string; + /** @description Rule ID this skip applies to */ + ruleId: string; + /** @description Deployment version this skip applies to */ + versionId: string; + /** @description Workspace this skip belongs to */ + workspaceId: string; + }; + PrometheusMetricProvider: { + /** + * @description Prometheus server address (supports Go templates) + * @example http://prometheus.example.com:9090 + */ + address: string; + /** @description Authentication configuration for Prometheus */ + authentication?: { + /** + * @description Bearer token for authentication (supports Go templates for variable references) + * @example {{.variables.prometheus_token}} + */ + bearerToken?: string; + /** @description OAuth2 client credentials flow */ + oauth2?: { + /** @description OAuth2 client ID (supports Go templates) */ + clientId: string; + /** @description OAuth2 client secret (supports Go templates) */ + clientSecret: string; + /** @description OAuth2 scopes */ + scopes?: string[]; + /** @description Token endpoint URL */ + tokenUrl: string; + }; + }; + /** @description Additional HTTP headers for the Prometheus request (values support Go templates) */ + headers?: { + /** @example X-Scope-OrgID */ + key: string; + /** @example tenant_a */ + value: string; + }[]; + /** + * @description Skip TLS certificate verification + * @default false + */ + insecure: boolean; + /** + * @description PromQL query expression (supports Go templates) + * @example sum(irate(istio_requests_total{reporter="source",destination_service=~"{{.resource.name}}",response_code!~"5.*"}[5m])) + */ + query: string; + /** @description If provided, a range query (/api/v1/query_range) is used instead of an instant query (/api/v1/query) */ + rangeQuery?: { + /** + * @description How far back from now for the query end, as a Prometheus duration (e.g., "0s" for now, "1m" for 1 minute ago). Defaults to "0s" (now) if unset. + * @example 0s + */ + end?: string; + /** + * @description How far back from now to start the query, as a Prometheus duration (e.g., "5m", "1h"). Defaults to 10 * step if unset. + * @example 5m + */ + start?: string; + /** + * @description Query resolution step width as a Prometheus duration (e.g., "15s", "1m", "500ms") + * @example 1m + */ + step: string; + }; + /** + * Format: int64 + * @description Query timeout in seconds + * @example 30 + */ + timeout?: number; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "prometheus"; + }; + PropertiesMatcher: { + properties: components["schemas"]["PropertyMatcher"][]; + }; + PropertyMatcher: { + fromProperty: string[]; + /** @enum {string} */ + operator: "equals" | "notEquals" | "contains" | "startsWith" | "endsWith" | "regex"; + toProperty: string[]; + }; + ReferenceValue: { + path: string[]; + reference: string; + }; + RelatableEntity: components["schemas"]["Deployment"] | components["schemas"]["Environment"] | components["schemas"]["Resource"]; + /** @enum {string} */ + RelatableEntityType: "deployment" | "environment" | "resource"; + /** @enum {string} */ + RelationDirection: "from" | "to"; + RelationshipRule: { + description?: string; + /** @description CEL expression to determine if the relationship rule should be used */ + fromSelector?: string; + fromType: components["schemas"]["RelatableEntityType"]; + id: string; + matcher: components["schemas"]["CelMatcher"] | components["schemas"]["PropertiesMatcher"]; + metadata: { + [key: string]: string; + }; + name: string; + reference: string; + relationshipType: string; + /** @description CEL expression to determine if the relationship rule should be used */ + toSelector?: string; + toType: components["schemas"]["RelatableEntityType"]; + workspaceId: string; + }; + Release: { + createdAt: string; + encryptedVariables: string[]; + /** Format: uuid */ + id: string; + releaseTarget: components["schemas"]["ReleaseTarget"]; + variables: { + [key: string]: components["schemas"]["LiteralValue"]; + }; + version: components["schemas"]["DeploymentVersion"]; + }; + ReleaseTarget: { + deploymentId: string; + environmentId: string; + resourceId: string; + }; + ReleaseTargetAndState: { + releaseTarget: components["schemas"]["ReleaseTarget"]; + state: components["schemas"]["ReleaseTargetState"]; + }; + ReleaseTargetItem: { + currentVersion?: { + id: string; + name: string; + tag: string; + } | null; + desiredVersion?: { + id: string; + name: string; + tag: string; + } | null; + environment: { + id: string; + name: string; + }; + latestJob?: { + /** Format: date-time */ + completedAt?: string; + /** Format: date-time */ + createdAt: string; + id: string; + message?: string; + metadata: { + [key: string]: string; + }; + status: components["schemas"]["JobStatus"]; + verifications: { + id: string; + jobId: string; + metrics: { + count: number; + failureCondition: string | null; + failureThreshold: number | null; + id: string; + jobId?: string; + name: string; + policyRuleVerificationMetricId?: string; + provider: { + [key: string]: unknown; + }; + successCondition: string; + successThreshold: number | null; + }[]; + }[]; + } | null; + releaseTarget: { + deploymentId: string; + environmentId: string; + resourceId: string; + }; + resource: { + id: string; + identifier: string; + kind: string; + name: string; + version: string; + }; + }; + ReleaseTargetPreview: { + deployment: components["schemas"]["Deployment"]; + environment: components["schemas"]["Environment"]; + system: components["schemas"]["System"]; + }; + ReleaseTargetState: { + currentRelease?: components["schemas"]["Release"]; + desiredRelease?: components["schemas"]["Release"]; + latestJob?: components["schemas"]["JobWithVerifications"]; + }; + ReleaseTargetSummary: { + currentVersion?: components["schemas"]["VersionSummary"]; + desiredVersion?: components["schemas"]["VersionSummary"]; + environment: components["schemas"]["EnvironmentSummary"]; + latestJob?: components["schemas"]["JobSummary"]; + releaseTarget: components["schemas"]["ReleaseTarget"]; + resource: components["schemas"]["ResourceSummary"]; + }; + ReleaseTargetWithState: { + deployment: components["schemas"]["Deployment"]; + environment: components["schemas"]["Environment"]; + releaseTarget: components["schemas"]["ReleaseTarget"]; + resource: components["schemas"]["Resource"]; + state: components["schemas"]["ReleaseTargetState"]; + }; + ResolvedPolicy: { + environmentIds: string[]; + policy: components["schemas"]["Policy"]; + releaseTargets: components["schemas"]["ReleaseTarget"][]; + }; + Resource: { + config: { + [key: string]: unknown; + }; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + deletedAt?: string; + id: string; + identifier: string; + kind: string; + /** Format: date-time */ + lockedAt?: string; + metadata: { + [key: string]: string; + }; + name: string; + providerId?: string; + /** Format: date-time */ + updatedAt?: string; + version: string; + workspaceId: string; + }; + ResourcePreviewRequest: { + config: { + [key: string]: unknown; + }; + identifier: string; + kind: string; + metadata: { + [key: string]: string; + }; + name: string; + version: string; + }; + ResourceProvider: { + /** Format: date-time */ + createdAt: string; + id: string; + metadata: { + [key: string]: string; + }; + name: string; + /** Format: uuid */ + workspaceId: string; + }; + ResourceSummary: { + id: string; + identifier: string; + kind: string; + name: string; + version: string; + }; + ResourceVariable: { + key: string; + resourceId: string; + value: components["schemas"]["Value"]; + }; + ResourceVariablesBulkUpdateEvent: { + resourceId: string; + variables: { + [key: string]: unknown; + }; + }; + RetryRule: { + /** + * Format: int32 + * @description Minimum seconds to wait between retry attempts. If null, retries are allowed immediately after job completion. + */ + backoffSeconds?: number; + /** + * @description Backoff strategy: "linear" uses constant backoffSeconds delay, "exponential" doubles the delay with each retry (backoffSeconds * 2^(attempt-1)). + * @default linear + * @enum {string} + */ + backoffStrategy: "linear" | "exponential"; + /** + * Format: int32 + * @description Maximum backoff time in seconds (cap for exponential backoff). If null, no maximum is enforced. + */ + maxBackoffSeconds?: number; + /** + * Format: int32 + * @description Maximum number of retries allowed. 0 means no retries (1 attempt total), 3 means up to 4 attempts (1 initial + 3 retries). + */ + maxRetries: number; + /** @description Job statuses that count toward the retry limit. If null or empty, defaults to ["failure", "invalidIntegration", "invalidJobAgent"] for maxRetries > 0, or ["failure", "invalidIntegration", "invalidJobAgent", "successful"] for maxRetries = 0. Cancelled and skipped jobs never count by default (allows redeployment after cancellation). Example: ["failure", "cancelled"] will only count failed/cancelled jobs. */ + retryOnStatuses?: components["schemas"]["JobStatus"][]; + }; + RollbackRule: { + /** @description Job statuses that will trigger a rollback */ + onJobStatuses?: components["schemas"]["JobStatus"][]; + /** + * @description If true, a release target will be rolled back if the verification fails + * @default false + */ + onVerificationFailure: boolean; + }; + RuleEvaluation: { + /** @description Whether the rule requires an action (e.g., approval, wait) */ + actionRequired: boolean; + /** + * @description Type of action required + * @enum {string} + */ + actionType?: "approval" | "wait"; + /** @description Whether the rule allows the deployment */ + allowed: boolean; + /** @description Additional details about the rule evaluation */ + details: { + [key: string]: unknown; + }; + /** @description Human-readable explanation of the rule result */ + message: string; + /** + * Format: date-time + * @description The time when this rule should be re-evaluated (e.g., when soak time will be complete, when gradual rollout schedule is due) + */ + nextEvaluationTime?: string; + /** @description The ID of the rule that was evaluated */ + ruleId: string; + /** + * Format: date-time + * @description The time when the rule requirement was satisfied (e.g., when approvals were met, soak time completed) + */ + satisfiedAt?: string; + }; + SensitiveValue: { + valueHash: string; + }; + SleepMetricProvider: { + /** + * Format: int32 + * @example 30 + */ + durationSeconds: number; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "sleep"; + }; + StringValue: string; + System: { + description?: string; + id: string; + metadata?: { + [key: string]: string; + }; + name: string; + workspaceId: string; + }; + SystemDeploymentLink: { + deploymentId: string; + systemId: string; + }; + SystemEnvironmentLink: { + environmentId: string; + systemId: string; + }; + TerraformCloudJobAgentConfig: { + /** @description Terraform Cloud address (e.g. https://app.terraform.io). */ + address: string; + /** @description Terraform Cloud organization name. */ + organization: string; + /** @description Terraform Cloud workspace template. */ + template: string; + /** @description Terraform Cloud API token. */ + token: string; + /** + * @description Whether to create a TFC run on dispatch. When false, only the workspace and variables are synced. + * @default true + */ + triggerRunOnChange: boolean; + /** @description The ctrlplane API endpoint for TFC webhook notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook). */ + webhookUrl: string; + }; + TerraformCloudRunMetricProvider: { + /** + * @description Terraform Cloud address + * @example https://app.terraform.io + */ + address: string; + /** + * @description Terraform Cloud run ID + * @example run-1234567890 + */ + runId: string; + /** + * @description Terraform Cloud token + * @example {{.variables.terraform_cloud_token}} + */ + token: string; + /** + * @description Provider type (enum property replaced by openapi-typescript) + * @enum {string} + */ + type: "terraformCloudRun"; + }; + TestRunnerJobAgentConfig: { + /** + * Format: int + * @description Delay in seconds before resolving the job. + */ + delaySeconds?: number; + /** @description Optional message to include in the job output. */ + message?: string; + /** @description Final status to set (e.g. "successful", "failure"). */ + status?: string; + }; + UserApprovalRecord: { + createdAt: string; + environmentId: string; + reason?: string; + status: components["schemas"]["ApprovalStatus"]; + userId: string; + versionId: string; + }; + Value: components["schemas"]["LiteralValue"] | components["schemas"]["ReferenceValue"] | components["schemas"]["SensitiveValue"]; + VerificationMeasurement: { + /** @description Raw measurement data */ + data: { + [key: string]: unknown; + }; + /** + * Format: date-time + * @description When measurement was taken + */ + measuredAt: string; + /** @description Measurement result message */ + message?: string; + status: components["schemas"]["VerificationMeasurementStatus"]; + }; + /** + * @description Status of a verification measurement + * @enum {string} + */ + VerificationMeasurementStatus: "passed" | "failed" | "inconclusive"; + VerificationMetricSpec: { + /** @description Number of measurements to take */ + count: number; + /** + * @description CEL expression to evaluate measurement failure (e.g., "result.statusCode == 500"), if not provided, a failure is just the opposite of the success condition + * @example result.statusCode == 500 + */ + failureCondition?: string; + /** + * @description Stop after this many consecutive failures (0 = no limit) + * @default 0 + */ + failureThreshold: number; + /** + * Format: int32 + * @description Interval between measurements in seconds + * @example 30 + */ + intervalSeconds: number; + /** @description Name of the verification metric */ + name: string; + provider: components["schemas"]["MetricProvider"]; + /** + * @description CEL expression to evaluate measurement success (e.g., "result.statusCode == 200") + * @example result.statusCode == 200 + */ + successCondition: string; + /** + * @description Minimum number of consecutive successful measurements required to consider the metric successful + * @example 0 + */ + successThreshold?: number; + }; + VerificationMetricStatus: components["schemas"]["VerificationMetricSpec"] & { + id: string; + /** @description Individual verification measurements taken for this metric */ + measurements: components["schemas"]["VerificationMeasurement"][]; + }; + VerificationRule: { + /** @description Metrics to verify */ + metrics: components["schemas"]["VerificationMetricSpec"][]; + /** + * @description When to trigger verification + * @default jobSuccess + * @enum {string} + */ + triggerOn: "jobCreated" | "jobStarted" | "jobSuccess" | "jobFailure"; + }; + VersionCooldownRule: { + /** + * Format: int32 + * @description Minimum time in seconds that must pass since the currently deployed (or in-progress) version was created before allowing another deployment. This enables batching of frequent upstream releases into periodic deployments. + */ + intervalSeconds: number; + }; + VersionSelectorRule: { + /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ + description?: string; + /** @description CEL expression to determine if the version selector should be used */ + selector: string; + }; + VersionSummary: { + id: string; + name: string; + tag: string; + }; + Workflow: { + id: string; + inputs: components["schemas"]["WorkflowInput"][]; + jobs: components["schemas"]["WorkflowJobAgent"][]; + name: string; + }; + WorkflowArrayInput: components["schemas"]["WorkflowManualArrayInput"]; + WorkflowBooleanInput: { + default?: boolean; + key: string; + /** @enum {string} */ + type: "boolean"; + }; + WorkflowInput: components["schemas"]["WorkflowStringInput"] | components["schemas"]["WorkflowNumberInput"] | components["schemas"]["WorkflowBooleanInput"] | components["schemas"]["WorkflowArrayInput"] | components["schemas"]["WorkflowObjectInput"]; + WorkflowJob: { + /** @description Configuration for the job agent */ + config: { + [key: string]: unknown; + }; + id: string; + index: number; + /** @description Reference to the job agent */ + ref: string; + workflowRunId: string; + }; + WorkflowJobAgent: { + /** @description Configuration for the job agent */ + config: { + [key: string]: unknown; + }; + id: string; + name: string; + /** @description Reference to the job agent */ + ref: string; + /** @description CEL expression to determine if the job agent should dispatch a job */ + selector: string; + }; + WorkflowManualArrayInput: { + default?: { + [key: string]: unknown; }[]; - /** @description Total number of matching resources */ - total: number; - }; + key: string; + /** @enum {string} */ + type: "array"; }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; + WorkflowNumberInput: { + default?: number; + key: string; + /** @enum {string} */ + type: "number"; }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; + WorkflowObjectInput: { + default?: { + [key: string]: unknown; + }; + key: string; + /** @enum {string} */ + type: "object"; + }; + WorkflowRun: { + id: string; + inputs: { + [key: string]: unknown; + }; + workflowId: string; + }; + WorkflowStringInput: { + default?: string; + key: string; + /** @enum {string} */ + type: "string"; }; - }; }; - }; - queryResources: { + responses: never; parameters: { - query?: { - /** @description Maximum number of items to return */ - limit?: number; - /** @description Number of items to skip */ - offset?: number; - }; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; + /** @description Type of the entity (deployment, environment, or resource) */ + relatableEntityType: components["schemas"]["RelatableEntityType"]; }; - requestBody: { - content: { - "application/json": { - /** @description CEL expression to filter resources. Defaults to "true" (all resources). */ - filter?: string; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + listReleaseTargets: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the deployment */ + deploymentId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description List of release targets for the deployment */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: components["schemas"]["ReleaseTargetItem"][]; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; }; - }; }; - responses: { - /** @description Paginated list of items */ - 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"]; - }; - }; + getJobVerificationStatus: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the job */ + jobId: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Aggregate verification status for the job */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** + * @description Aggregate verification status + * @enum {string} + */ + status: "passed" | "running" | "failed" | ""; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; }; - }; - createWorkflowRun: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description ID of the workflow */ - workflowId: string; - }; - cookie?: never; + validateResourceSelector: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** @description CEL expression to validate. */ + resourceSelector: string; + }; + }; + }; + responses: { + /** @description The validated resource selector */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + errors: string[]; + valid: boolean; + }; + }; + }; + }; }; - requestBody: { - content: { - "application/json": { - /** @description Input values for the workflow run. */ - inputs: { - [key: string]: unknown; - }; + computeAggergate: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description CEL expression to filter resources. Defaults to "true" (all resources). */ + filter?: string; + groupBy?: { + /** @description Label for this grouping */ + name: string; + /** @description Dot-path property to group by (e.g. kind, metadata.region) */ + property: string; + }[]; + }; + }; + }; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + groups: { + /** @description Number of resources in this group */ + count: number; + /** @description Map of grouping name to its value for this bucket */ + key: { + [key: string]: string; + }; + }[]; + /** @description Total number of matching resources */ + total: number; + }; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; }; - }; }; - responses: { - /** @description OK response */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["WorkflowRun"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + queryResources: { + parameters: { + query?: { + /** @description Maximum number of items to return */ + limit?: number; + /** @description Number of items to skip */ + offset?: number; + }; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description CEL expression to filter resources. Defaults to "true" (all resources). */ + filter?: string; + }; + }; + }; + responses: { + /** @description Paginated list of items */ + 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"]; + }; + }; + }; + }; + createWorkflowRun: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description ID of the workflow */ + workflowId: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description Input values for the workflow run. */ + inputs: { + [key: string]: unknown; + }; + }; + }; + }; + responses: { + /** @description OK response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WorkflowRun"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; }; - }; } From 207761e39eebcf21c34b5af22142ab690e888347 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Fri, 27 Mar 2026 16:23:41 -0400 Subject: [PATCH 18/36] feat: check if job is already running --- .../argo-workflow/workflow_submitter.go | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go index c4ff0f399..9bc24f80c 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -11,6 +11,7 @@ import ( wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/avast/retry-go" "github.com/charmbracelet/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // GoWorkflowSubmitter is the production implementation of WorkflowSubmitter @@ -22,7 +23,6 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( serverAddr, apiKey string, wf *wfv1.Workflow, ) (*wfv1.Workflow, error) { - ctx, apiClient, err := argoapiclient.NewClientFromOptsWithContext(ctx, argoapiclient.Opts{ ArgoServerOpts: argoapiclient.ArgoServerOpts{ URL: serverAddr, @@ -43,9 +43,40 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( namespace = "default" } + jobID := wf.Labels["job-id"] + + created, err := createWorkflowWithRetry(ctx, wfClient, namespace, jobID, wf) + return created, err +} + +func createWorkflowWithRetry( + ctx context.Context, + wfClient workflowpkg.WorkflowServiceClient, + namespace, jobID string, + wf *wfv1.Workflow, +) (*wfv1.Workflow, error) { var created *wfv1.Workflow - err = retry.Do( + err := retry.Do( func() error { + // Before creating, check whether a workflow for this job already + // exists. This makes retries idempotent when GenerateName is used: + // a previous attempt may have succeeded but the response was lost. + if jobID != "" { + list, listErr := wfClient.ListWorkflows(ctx, &workflowpkg.WorkflowListRequest{ + Namespace: namespace, + ListOptions: &metav1.ListOptions{ + LabelSelector: "job-id=" + jobID, + }, + }) + if listErr != nil && isRetryableError(listErr) { + return listErr + } + if listErr == nil && len(list.Items) > 0 { + created = &list.Items[0] + return nil + } + } + var createdErr error created, createdErr = wfClient.CreateWorkflow(ctx, &workflowpkg.WorkflowCreateRequest{ Namespace: namespace, From 6e594ae84fb821ee95ed1a82de88371fe767bdc8 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Fri, 27 Mar 2026 16:50:54 -0400 Subject: [PATCH 19/36] fix: remove unused var --- apps/api/src/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/config.ts b/apps/api/src/config.ts index 6c7444fca..e8fa4b4d2 100644 --- a/apps/api/src/config.ts +++ b/apps/api/src/config.ts @@ -41,7 +41,6 @@ export const env = createEnv({ AZURE_APP_CLIENT_ID: z.string().optional(), ARGO_WORKFLOW_WEBHOOK_SECRET: z.string().optional(), - ARGO_WORKFLOW_HTTP_ENDPOINT: z.string().optional(), }, runtimeEnv: process.env, From 2b13b398cef2741dbd71d4d28972fdd929c077cc Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 08:42:52 -0400 Subject: [PATCH 20/36] refactor: workflowTemplates no need for special treatment * workflow templates can be configured via json, I've updated the docs to have an example. Now the caller can just use normal yaml and ctrlplane templating to control triggering a workflow or a workflow template --- apps/workspace-engine/oapi/openapi.json | 101 +- .../oapi/spec/schemas/jobs.jsonnet | 46 +- apps/workspace-engine/pkg/oapi/oapi.gen.go | 111 +- .../jobagents/argo-workflow/workflow.go | 36 +- .../jobagents/argo-workflow/workflow_test.go | 56 - .../job-agents/argo-workflows.mdx | 169 +- packages/workspace-engine-sdk/src/schema.ts | 1473 ++--------------- 7 files changed, 367 insertions(+), 1625 deletions(-) diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index 5ca1c9bc6..c257cdfa6 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -54,85 +54,32 @@ "type": "object" }, "ArgoWorkflowJobAgentConfig": { - "oneOf": [ - { - "additionalProperties": false, - "description": "Inline workflow execution", - "properties": { - "apiKey": { - "description": "ArgoWorkflow API token.", - "type": "string" - }, - "inline": { - "description": "Execute inline workflow (defaults to false if omitted)", - "enum": [ - true - ], - "type": "boolean" - }, - "name": { - "description": "ArgoWorkflow job name", - "type": "string" - }, - "serverUrl": { - "description": "ArgoWorkflow server address (host[:port] or URL).", - "type": "string" - }, - "template": { - "description": "Inline workflow spec or template.", - "type": "string" - } - }, - "required": [ - "serverUrl", - "apiKey", - "template", - "name" - ], - "type": "object" + "description": "WorkflowTemplate reference execution", + "properties": { + "apiKey": { + "description": "ArgoWorkflow API token.", + "type": "string" }, - { - "additionalProperties": false, - "description": "WorkflowTemplate reference execution", - "properties": { - "apiKey": { - "description": "ArgoWorkflow API token.", - "type": "string" - }, - "inline": { - "description": "Use WorkflowTemplate reference (default mode)", - "enum": [ - false - ], - "type": "boolean" - }, - "name": { - "description": "ArgoWorkflow job name", - "type": "string" - }, - "namespace": { - "description": "WorkflowTemplate namespace", - "type": "string" - }, - "serverUrl": { - "description": "ArgoWorkflow server address (host[:port] or URL).", - "type": "string" - }, - "template": { - "description": "WorkflowTemplate name.", - "type": "string" - } - }, - "required": [ - "serverUrl", - "apiKey", - "template", - "name", - "namespace" - ], - "type": "object" + "name": { + "description": "ArgoWorkflow job name", + "type": "string" + }, + "serverUrl": { + "description": "ArgoWorkflow server address (host[:port] or URL).", + "type": "string" + }, + "template": { + "description": "WorkflowTemplate name.", + "type": "string" } - ] + }, + "required": [ + "serverUrl", + "apiKey", + "template", + "name" + ], + "type": "object" }, "BasicResource": { "properties": { diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 8cb2924f3..4896fa34f 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -161,43 +161,15 @@ local JobPropertyKeys = std.objectFields(Job.properties); }, ArgoWorkflowJobAgentConfig: { - oneOf: [ - { - type: 'object', - description: 'Inline workflow execution', - required: ['serverUrl', 'apiKey', 'template', 'name'], - properties: { - name: { type: 'string', description: 'ArgoWorkflow job name' }, - inline: { - type: 'boolean', - enum: [true], - description: 'Execute inline workflow (defaults to false if omitted)', - }, - serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, - apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, - template: { type: 'string', description: 'Inline workflow spec or template.' }, - }, - additionalProperties: false, - }, - { - type: 'object', - description: 'WorkflowTemplate reference execution', - required: ['serverUrl', 'apiKey', 'template', 'name', 'namespace'], - properties: { - name: { type: 'string', description: 'ArgoWorkflow job name' }, - inline: { - type: 'boolean', - enum: [false], - description: 'Use WorkflowTemplate reference (default mode)', - }, - serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, - apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, - template: { type: 'string', description: 'WorkflowTemplate name.' }, - namespace: { type: 'string', description: 'WorkflowTemplate namespace' }, - }, - additionalProperties: false, - }, - ], + type: 'object', + description: 'WorkflowTemplate reference execution', + required: ['serverUrl', 'apiKey', 'template', 'name'], + properties: { + name: { type: 'string', description: 'ArgoWorkflow job name' }, + serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, + apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, + template: { type: 'string', description: 'WorkflowTemplate name.' }, + }, }, TestRunnerJobAgentConfig: { type: 'object', diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index ce963ba58..0fa56a800 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -21,16 +21,6 @@ const ( ApprovalStatusRejected ApprovalStatus = "rejected" ) -// Defines values for ArgoWorkflowJobAgentConfig0Inline. -const ( - ArgoWorkflowJobAgentConfig0InlineTrue ArgoWorkflowJobAgentConfig0Inline = true -) - -// Defines values for ArgoWorkflowJobAgentConfig1Inline. -const ( - False ArgoWorkflowJobAgentConfig1Inline = false -) - // Defines values for DatadogMetricProviderAggregator. const ( Area DatadogMetricProviderAggregator = "area" @@ -124,7 +114,7 @@ const ( // Defines values for NullValue. const ( - NullValueTrue NullValue = true + True NullValue = true ) // Defines values for PrometheusMetricProviderType. @@ -237,46 +227,14 @@ type ArgoCDJobAgentConfig struct { Template string `json:"template"` } -// ArgoWorkflowJobAgentConfig defines model for ArgoWorkflowJobAgentConfig. +// ArgoWorkflowJobAgentConfig WorkflowTemplate reference execution type ArgoWorkflowJobAgentConfig struct { - union json.RawMessage -} - -// ArgoWorkflowJobAgentConfig0 Inline workflow execution -type ArgoWorkflowJobAgentConfig0 struct { - // ApiKey ArgoWorkflow API token. - ApiKey string `json:"apiKey"` - - // Inline Execute inline workflow (defaults to false if omitted) - Inline *ArgoWorkflowJobAgentConfig0Inline `json:"inline,omitempty"` - - // Name ArgoWorkflow job name - Name string `json:"name"` - - // ServerUrl ArgoWorkflow server address (host[:port] or URL). - ServerUrl string `json:"serverUrl"` - - // Template Inline workflow spec or template. - Template string `json:"template"` -} - -// ArgoWorkflowJobAgentConfig0Inline Execute inline workflow (defaults to false if omitted) -type ArgoWorkflowJobAgentConfig0Inline bool - -// ArgoWorkflowJobAgentConfig1 WorkflowTemplate reference execution -type ArgoWorkflowJobAgentConfig1 struct { // ApiKey ArgoWorkflow API token. ApiKey string `json:"apiKey"` - // Inline Use WorkflowTemplate reference (default mode) - Inline *ArgoWorkflowJobAgentConfig1Inline `json:"inline,omitempty"` - // Name ArgoWorkflow job name Name string `json:"name"` - // Namespace WorkflowTemplate namespace - Namespace string `json:"namespace"` - // ServerUrl ArgoWorkflow server address (host[:port] or URL). ServerUrl string `json:"serverUrl"` @@ -284,9 +242,6 @@ type ArgoWorkflowJobAgentConfig1 struct { Template string `json:"template"` } -// ArgoWorkflowJobAgentConfig1Inline Use WorkflowTemplate reference (default mode) -type ArgoWorkflowJobAgentConfig1Inline bool - // BasicResource defines model for BasicResource. type BasicResource struct { Id string `json:"id"` @@ -1530,68 +1485,6 @@ type QueryResourcesJSONRequestBody QueryResourcesJSONBody // CreateWorkflowRunJSONRequestBody defines body for CreateWorkflowRun for application/json ContentType. type CreateWorkflowRunJSONRequestBody CreateWorkflowRunJSONBody -// AsArgoWorkflowJobAgentConfig0 returns the union data inside the ArgoWorkflowJobAgentConfig as a ArgoWorkflowJobAgentConfig0 -func (t ArgoWorkflowJobAgentConfig) AsArgoWorkflowJobAgentConfig0() (ArgoWorkflowJobAgentConfig0, error) { - var body ArgoWorkflowJobAgentConfig0 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromArgoWorkflowJobAgentConfig0 overwrites any union data inside the ArgoWorkflowJobAgentConfig as the provided ArgoWorkflowJobAgentConfig0 -func (t *ArgoWorkflowJobAgentConfig) FromArgoWorkflowJobAgentConfig0(v ArgoWorkflowJobAgentConfig0) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeArgoWorkflowJobAgentConfig0 performs a merge with any union data inside the ArgoWorkflowJobAgentConfig, using the provided ArgoWorkflowJobAgentConfig0 -func (t *ArgoWorkflowJobAgentConfig) MergeArgoWorkflowJobAgentConfig0(v ArgoWorkflowJobAgentConfig0) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsArgoWorkflowJobAgentConfig1 returns the union data inside the ArgoWorkflowJobAgentConfig as a ArgoWorkflowJobAgentConfig1 -func (t ArgoWorkflowJobAgentConfig) AsArgoWorkflowJobAgentConfig1() (ArgoWorkflowJobAgentConfig1, error) { - var body ArgoWorkflowJobAgentConfig1 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromArgoWorkflowJobAgentConfig1 overwrites any union data inside the ArgoWorkflowJobAgentConfig as the provided ArgoWorkflowJobAgentConfig1 -func (t *ArgoWorkflowJobAgentConfig) FromArgoWorkflowJobAgentConfig1(v ArgoWorkflowJobAgentConfig1) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeArgoWorkflowJobAgentConfig1 performs a merge with any union data inside the ArgoWorkflowJobAgentConfig, using the provided ArgoWorkflowJobAgentConfig1 -func (t *ArgoWorkflowJobAgentConfig) MergeArgoWorkflowJobAgentConfig1(v ArgoWorkflowJobAgentConfig1) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -func (t ArgoWorkflowJobAgentConfig) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - return b, err -} - -func (t *ArgoWorkflowJobAgentConfig) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) - return err -} - // AsJobUpdateEvent0 returns the union data inside the JobUpdateEvent as a JobUpdateEvent0 func (t JobUpdateEvent) AsJobUpdateEvent0() (JobUpdateEvent0, error) { var body JobUpdateEvent0 diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index a2a79c03f..b468bd702 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -3,7 +3,6 @@ package argo_workflows import ( "bytes" "context" - "encoding/json" "fmt" "regexp" "strings" @@ -27,8 +26,6 @@ type WorkFlowJobAgentConfig struct { ApiKey string Template string Name string - Inline bool - Namespace string } type Getter interface { @@ -87,9 +84,7 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { wf, err := TemplateApplication( dispatchCtx, wfConfig.Template, - wfConfig.Inline, wfConfig.Name, - wfConfig.Namespace, ) if err != nil { return fmt.Errorf("failed to generate workflow from template: %w", err) @@ -149,23 +144,14 @@ func ParseJobAgentConfig( } wfT.Template = template - isInline, _ := config["inline"].(bool) - wfT.Inline = isInline name, ok := config["name"].(string) if !ok { return wfT, fmt.Errorf("name is required") } wfT.Name = name - namespace, _ := config["namespace"].(string) if serverAddr == "" || template == "" || name == "" { return wfT, fmt.Errorf("missing required fields in job agent config") } - if !isInline && namespace == "" { - return wfT, fmt.Errorf( - "when inline is false namespace must be set to trigger the correct workflow template", - ) - } - wfT.Namespace = namespace return wfT, nil } @@ -174,9 +160,7 @@ func ParseJobAgentConfig( func TemplateApplication( ctx *oapi.DispatchContext, tmpl string, - inline bool, name string, - namespace string, ) (*wfv1.Workflow, error) { t, err := templatefuncs.Parse("argoWorkflowAgentConfig", tmpl) if err != nil { @@ -188,18 +172,18 @@ func TemplateApplication( } var workflow wfv1.Workflow - if inline { - if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { - return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) - } - } else { - params := make(map[string]any) - if err := json.Unmarshal(buf.Bytes(), ¶ms); err != nil { - return nil, fmt.Errorf("failed to parse workflow template vars: %w", err) - } - workflow = *createWorkFlowTemplateCall(name, namespace, params) + if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { + return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } + if workflow.Name == "" && name == "" { + return nil, fmt.Errorf( + "a name must be provided either in the workflow spec or through the job agent config", + ) + } + if workflow.Name == "" && name != "" { + workflow.Name = name + } return &workflow, nil } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go index 3a7508398..dd6f9d88f 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go @@ -112,18 +112,6 @@ spec: args: ["hello world"] ` -const nonInlineWorkflowTemplate = ` -{{- $resourceName := .resource.name }} -{{- $resourceIdentifier := .resource.identifier }} -{{- $environmentName := .environment.name }} -{{- $repo := .release.version.tag }} -{ - "repo": "{{$repo}}", - "resource": "{{$resourceName}}", - "environment": "{{$environmentName}}" -} -` - func validConfig() oapi.JobAgentConfig { return oapi.JobAgentConfig{ "serverUrl": "https://argo.example.com", @@ -163,50 +151,6 @@ func newTestJob(id string, cfg oapi.JobAgentConfig) *oapi.Job { } } -// ----- TemplateApplication (non-inline) ----- - -func TestTemplateApplication_NonInline_RendersParamsAndCreatesTemplateRef(t *testing.T) { - tag := "v1.2.3" - ctx := &oapi.DispatchContext{ - Resource: &oapi.Resource{ - Name: "my-resource", - Identifier: "res-id-123", - }, - Environment: &oapi.Environment{ - Name: "production", - }, - Release: &oapi.Release{ - Version: oapi.DeploymentVersion{ - Tag: tag, - }, - }, - JobAgent: oapi.JobAgent{}, - JobAgentConfig: oapi.JobAgentConfig{}, - } - - wf, err := argo_workflows.TemplateApplication( - ctx, - nonInlineWorkflowTemplate, - false, - "my-workflow", - "default", - ) - require.NoError(t, err) - assert.Equal(t, "my-workflow-", wf.GenerateName) - require.NotNil(t, wf.Spec.WorkflowTemplateRef) - assert.Equal(t, "my-workflow", wf.Spec.WorkflowTemplateRef.Name) - - params := make(map[string]string) - for _, p := range wf.Spec.Arguments.Parameters { - params[p.Name] = p.Value.String() - } - assert.Equal(t, tag, params["repo"]) - assert.Equal(t, "my-resource", params["resource"]) - assert.Equal(t, "production", params["environment"]) -} - -// ----- Type ----- - func TestType(t *testing.T) { a := argo_workflows.New(&mockSubmitter{}, &mockSetter{}) assert.Equal(t, "argo-workflow", a.Type()) diff --git a/docs/integrations/job-agents/argo-workflows.mdx b/docs/integrations/job-agents/argo-workflows.mdx index 4c267df9c..c02860b7c 100644 --- a/docs/integrations/job-agents/argo-workflows.mdx +++ b/docs/integrations/job-agents/argo-workflows.mdx @@ -1,6 +1,6 @@ --- title: "Argo Workflows" -description: "Execute deployments using Argo Workflows" +description: "Execute pipelines using Argo Workflows" --- The Argo Workflow job agent triggers workflow dispatch events to execute your deployments. This is ideal for teams that use github for non git event triggered workflows. @@ -77,8 +77,6 @@ jobAgentConfig: | `apiKey` | Yes | ArgoWorkflow API token | | `template` | Yes | Go template for ArgoWorkflow Application | | `name` | Yes | Go template for ArgoWorkflow Application | -| `inline` | No | Boolean for if you're suppling an inline workflow ArgoWorkflow Application | -| `namespace` | If not Inline | If not supplying an inline template this is required | ## Template Context @@ -121,7 +119,7 @@ template invocation. ### Deployment Properties -Each job invocation is tied to a specific deployment, so `.deployment` values can differ +Each job invocation is tied to a specific deployment, so `.deployment` values can differ for every ArgoWorkflow template invocation. | Property | Type | Description | @@ -137,7 +135,7 @@ for every ArgoWorkflow template invocation. ### Environment Properties -Each job invocation is tied to a specific environment, so `.environment` values can differ +Each job invocation is tied to a specific environment, so `.environment` values can differ for every ArgoWorkflow template invocation. | Property | Type | Description | @@ -220,7 +218,8 @@ template: | apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: - name: {{ .deployment.slug | lower | replace "_" "-" }} + generateName: {{ .deployment.slug | lower | replace "_" "-" }} + namespace: labels: version: {{ .version.tag | trunc 63 }} deployed-at: {{ now | date "2006-01-02" }} @@ -230,10 +229,9 @@ template: | When an ArgoWorkflows job is created, Ctrlplane automatically listens for webhooks from the argo server -- Application sync status is `Synced` -- Application health status is `Healthy` or `Progressing` +- Application sync status is `Pending|Running|Succeeded|Failed|Error` -## Example: Inline vs Triggering a Template +## Example: Config for an inline workflow ```yaml type: argo-workflow @@ -259,20 +257,143 @@ jobAgentConfig: args: ["echo hello"] ``` -```json -type: argo-workflow -name: api-service -jobAgent: argo-workflow -jobAgentConfig: - serverUrl: argoworkflows.example.com:443 - apiKey: "{{.variables.argoworkflow_token}}" - inline: false - type: argo-workflow - namespace: mynamespace - template: | - { - "repo": "${variables.repo}}" - "branch": "${variables.branch}}" - } + + +## Example: Config for triggering a workflow template + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Workflow +metadata: + generateName: go-build- + namespace: argo +spec: + workflowTemplateRef: + name: go-build + clusterScope: true + arguments: + parameters: + - name: repo + value: master ``` + +## Example: Configuration for Argo Events + +To receive real-time status updates from Argo Workflows in Ctrlplane, you need an +[Argo Events](https://argoproj.github.io/argo-events/) `Sensor` that watches for +workflow phase changes and forwards them to the Ctrlplane webhook endpoint. + +### How it works + +1. An **EventSource** watches the Kubernetes API for changes to `Workflow` resources in + your cluster. +2. The **Sensor** below subscribes to those events and filters for meaningful phase + transitions: `Pending`, `Running`, `Succeeded`, `Failed`, and `Error`. +3. On each matching event the Sensor fires an HTTP `POST` to + `https:///api/argo/webhook`, including the workflow name, namespace, + uid, timestamps, current phase, and — critically — the `job-id` label that Ctrlplane + stamped on the workflow when it was dispatched. +4. Ctrlplane uses the `jobId` field to look up the in-flight job and update its status + in real time, so your deployment dashboard reflects the actual pipeline state without + polling. + +### Prerequisites + +- Argo Events installed in your cluster with an `EventBus` named `default`. +- A `Secret` named `webhook-secret` in the `argo-events` namespace containing a `token` + key whose value matches the API key configured in Ctrlplane. +- An EventSource (e.g. a `Resource` event source) that emits `workflow-status` events + for your `Workflow` objects — the event body must be the full Kubernetes object so + the field paths below resolve correctly. + +Apply the following `Sensor` manifest to start forwarding workflow status events: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Sensor +metadata: + name: workflow-status-webhook + namespace: argo-events +spec: + eventBusName: default + template: + serviceAccountName: default + dependencies: + - name: workflow-event + eventSourceName: workflow-resource + eventName: workflow-status + filters: + dataLogicalOperator: or + data: + - path: body.status.phase + type: string + value: + - Pending + - Running + - Succeeded + - Failed + - Error + triggers: + - template: + name: send-webhook + conditions: "workflow-event" + http: + url: https:///api/argo/webhook + method: POST + headers: + Content-Type: application/json + Accept: application/json + secureHeaders: + - name: Authorization + valueFrom: + secretKeyRef: + name: webhook-secret + key: token + payload: + - src: + dependencyName: workflow-event + dataKey: body.metadata.name + dest: workflowName + - src: + dependencyName: workflow-event + dataKey: body.metadata.namespace + dest: namespace + - src: + dependencyName: workflow-event + dataKey: body.metadata.uid + dest: uid + - src: + dependencyName: workflow-event + dataKey: body.metadata.creationTimestamp + dest: createdAt + - src: + dependencyName: workflow-event + dataKey: body.status.startedAt + dest: startedAt + - src: + dependencyName: workflow-event + dataKey: body.status.finishedAt + dest: finishedAt + - src: + dependencyName: workflow-event + dataKey: body.status.phase + dest: phase + - src: + dependencyName: workflow-event + dataKey: body.metadata.labels.job-id + dest: jobId + - src: + dependencyName: workflow-event + contextKey: type + dest: eventType + retryStrategy: + steps: 3 + duration: 3s + policy: + status: + allow: + - 200 + - 201 + +``` diff --git a/packages/workspace-engine-sdk/src/schema.ts b/packages/workspace-engine-sdk/src/schema.ts index f0fd6be96..5d947832e 100644 --- a/packages/workspace-engine-sdk/src/schema.ts +++ b/packages/workspace-engine-sdk/src/schema.ts @@ -4,694 +4,83 @@ */ export interface paths { - "/v1/deployments/{deploymentId}/release-targets": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** List release targets for a deployment */ - get: operations["listReleaseTargets"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/jobs/{jobId}/verification-status": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get aggregate verification status for a job */ - get: operations["getJobVerificationStatus"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/validate/resource-selector": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** Validate a resource selector */ - post: operations["validateResourceSelector"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/state": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Get the state of a release target */ - get: operations["getReleaseTargetState"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/aggregates": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Compute resource aggregate - * @description Filters resources by a CEL expression and groups them by specified properties, returning counts per group. - */ - post: operations["computeAggergate"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/resources/query": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Query resources with CEL expression - * @description Returns paginated resources that match the provided CEL expression. Use the "resource" variable in your expression to access resource properties. - */ - post: operations["queryResources"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/workspaces/{workspaceId}/workflows/{workflowId}/runs": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * Create a workflow run - * @description Creates a new run for the specified workflow with the provided inputs. - */ - post: operations["createWorkflowRun"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; -} -export type webhooks = Record; -export interface components { - schemas: { - AnyApprovalRule: { - /** Format: int32 */ - minApprovals: number; - }; - /** @enum {string} */ - ApprovalStatus: "approved" | "rejected"; - ArgoCDJobAgentConfig: { - /** @description ArgoCD API token. */ - apiKey: string; - /** @description ArgoCD server address (host[:port] or URL). */ - serverUrl: string; - /** @description ArgoCD application template. */ - template: string; - }; - BasicResource: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - workspaceId: string; - }; - BooleanValue: boolean; - CelMatcher: { - cel: string; - }; - DatadogMetricProvider: { - /** - * @description Datadog aggregator - * @default last - * @enum {string} - */ - aggregator: - | "avg" - | "min" - | "max" - | "sum" - | "last" - | "percentile" - | "mean" - | "l2norm" - | "area"; - /** - * @description Datadog API key (supports Go templates for variable references) - * @example {{.variables.dd_api_key}} - */ - apiKey: string; - /** - * @description Datadog Application key (supports Go templates for variable references) - * @example {{.variables.dd_app_key}} - */ - appKey: string; - /** @description Datadog formula (supports Go templates) */ - formula?: string; - /** - * Format: int64 - * @example 30 - */ - intervalSeconds?: number; - /** - * @description Datadog metrics queries (supports Go templates) - * @example { - * "q": "sum:requests.error.rate{service:{{.resource.name}}}" - * } - */ - queries: { - [key: string]: string; - }; - /** - * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) - * @default datadoghq.com - */ - site: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "datadog"; - }; - DeployDecision: { - policyResults: components["schemas"]["PolicyEvaluation"][]; - }; - Deployment: { - description?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId?: string; - jobAgents?: components["schemas"]["DeploymentJobAgent"][]; - metadata: { - [key: string]: string; - }; - name: string; - /** @description CEL expression to determine if the deployment should be used */ - resourceSelector?: string; - slug: string; - }; - DeploymentAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - }; - DeploymentDependencyRule: { - /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ - dependsOn: string; - }; - DeploymentJobAgent: { - config: components["schemas"]["JobAgentConfig"]; - ref: string; - /** @description CEL expression to determine if the job agent should be used */ - selector: string; - }; - DeploymentVariable: { - defaultValue?: components["schemas"]["LiteralValue"]; - deploymentId: string; - description?: string; - id: string; - key: string; - }; - DeploymentVariableValue: { - deploymentVariableId: string; - id: string; - /** Format: int64 */ - priority: number; - /** @description CEL expression to determine if the deployment variable value should be used */ - resourceSelector?: string; - value: components["schemas"]["Value"]; - }; - DeploymentVariableWithValues: { - values: components["schemas"]["DeploymentVariableValue"][]; - variable: components["schemas"]["DeploymentVariable"]; - }; - DeploymentVersion: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - deploymentId: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - message?: string; - metadata: { - [key: string]: string; - }; - name: string; - status: components["schemas"]["DeploymentVersionStatus"]; - tag: string; - }; - /** @enum {string} */ - DeploymentVersionStatus: - | "unspecified" - | "building" - | "ready" - | "failed" - | "rejected" - | "paused"; - DeploymentWindowRule: { - /** - * @description If true, deployments are only allowed during the window. If false, deployments are blocked during the window (deny window) - * @default true - */ - allowWindow: boolean; - /** - * Format: int32 - * @description Duration of each deployment window in minutes - */ - durationMinutes: number; - /** @description RFC 5545 recurrence rule defining when deployment windows start (e.g., FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR;BYHOUR=9) */ - rrule: string; - /** @description IANA timezone for the rrule (e.g., America/New_York). Defaults to UTC if not specified */ - timezone?: string; - }; - DeploymentWithVariablesAndSystems: { - deployment: components["schemas"]["Deployment"]; - systems: components["schemas"]["System"][]; - variables: components["schemas"]["DeploymentVariableWithValues"][]; - }; - DispatchContext: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - /** @description Resolved input values for the workflow run. */ - inputs?: { - [key: string]: unknown; - }; - jobAgent: components["schemas"]["JobAgent"]; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - release?: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - variables?: { - [key: string]: components["schemas"]["LiteralValue"]; - }; - version?: components["schemas"]["DeploymentVersion"]; - workflow?: components["schemas"]["Workflow"]; - workflowJob?: components["schemas"]["WorkflowJob"]; - workflowRun?: components["schemas"]["WorkflowRun"]; - }; - EntityRelation: { - direction: components["schemas"]["RelationDirection"]; - entity: components["schemas"]["RelatableEntity"]; - /** @description ID of the related entity */ - entityId: string; - entityType: components["schemas"]["RelatableEntityType"]; - rule: components["schemas"]["RelationshipRule"]; - }; - Environment: { - /** Format: date-time */ - createdAt: string; - description?: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - /** @description CEL expression to determine if the environment should be used */ - resourceSelector?: string; - workspaceId: string; - }; - EnvironmentProgressionRule: { - /** @description CEL expression to determine if the environment progression rule should be used */ - dependsOnEnvironmentSelector: string; - /** - * Format: int32 - * @description Maximum age of dependency deployment before blocking progression (prevents stale promotions) - */ - 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 - */ - minimumSockTimeMinutes: number; - /** - * Format: float - * @default 100 - */ - minimumSuccessPercentage: number; - successStatuses?: components["schemas"]["JobStatus"][]; - }; - EnvironmentSummary: { - id: string; - name: string; - }; - EnvironmentWithSystems: components["schemas"]["Environment"] & { - systems: components["schemas"]["System"][]; - }; - ErrorResponse: { - /** @example Workspace not found */ - error?: string; - }; - EvaluateReleaseTargetRequest: { - releaseTarget: components["schemas"]["ReleaseTarget"]; - version: components["schemas"]["DeploymentVersion"]; - }; - EvaluationScope: { - environmentId?: string; - versionId?: string; - }; - GithubEntity: { - installationId: number; - slug: string; - }; - GithubJobAgentConfig: { - /** - * Format: int - * @description GitHub app installation ID. - */ - installationId: number; - /** @description GitHub repository owner. */ - owner: string; - /** @description Git ref to run the workflow on (defaults to "main" if omitted). */ - ref?: string; - /** @description GitHub repository name. */ - repo: string; - /** - * Format: int64 - * @description GitHub Actions workflow ID. - */ - workflowId: number; - }; - GradualRolloutRule: { - /** - * @description Strategy for scheduling deployments to release targets. "linear": Each target is deployed at a fixed interval of timeScaleInterval seconds. "linear-normalized": Deployments are spaced evenly so that the last target is scheduled at or before timeScaleInterval seconds. See rolloutType algorithm documentation for details. - * @enum {string} - */ - rolloutType: "linear" | "linear-normalized"; - /** - * Format: int32 - * @description Base time interval in seconds used to compute the delay between deployments to release targets. - */ - timeScaleInterval: number; - }; - HTTPMetricProvider: { - /** @description Request body (supports Go templates) */ - body?: string; - /** @description HTTP headers (values support Go templates) */ - headers?: { - [key: string]: string; - }; - /** - * @description HTTP method - * @default GET - * @enum {string} - */ - method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"; - /** - * @description Request timeout (duration string, e.g., "30s") - * @default 30s - */ - timeout: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "http"; - /** - * @description HTTP endpoint URL (supports Go templates) - * @example http://{{ .resource.name }}.{{ .environment.name }}/health - */ - url: string; - }; - IntegerValue: number; - Job: { - /** Format: date-time */ - completedAt?: string; - /** Format: date-time */ - createdAt: string; - dispatchContext?: components["schemas"]["DispatchContext"]; - externalId?: string; - id: string; - jobAgentConfig: components["schemas"]["JobAgentConfig"]; - jobAgentId: string; - message?: string; - metadata: { - [key: string]: string; - }; - releaseId: string; - /** Format: date-time */ - startedAt?: string; - status: components["schemas"]["JobStatus"]; - traceToken?: string; - /** Format: date-time */ - updatedAt: string; - workflowJobId: string; - }; - JobAgent: { - config: components["schemas"]["JobAgentConfig"]; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - type: string; - workspaceId: string; - }; - JobAgentConfig: { - [key: string]: unknown; - }; - /** @enum {string} */ - JobStatus: - | "cancelled" - | "skipped" - | "inProgress" - | "actionRequired" - | "pending" - | "failure" - | "invalidJobAgent" - | "invalidIntegration" - | "externalRunNotFound" - | "successful"; - JobSummary: { - id: string; - /** @description External links extracted from job metadata */ - links?: { - [key: string]: string; - }; - message?: string; - status: components["schemas"]["JobStatus"]; - verifications: components["schemas"]["JobVerification"][]; - }; - JobUpdateEvent: { - agentId?: string; - externalId?: string; - fieldsToUpdate?: ( - | "completedAt" - | "createdAt" - | "dispatchContext" - | "externalId" - | "id" - | "jobAgentConfig" - | "jobAgentId" - | "message" - | "metadata" - | "releaseId" - | "startedAt" - | "status" - | "traceToken" - | "updatedAt" - | "workflowJobId" - )[]; - id?: string; - job: components["schemas"]["Job"]; - } & (unknown | unknown); - JobVerification: { - /** - * Format: date-time - * @description When verification was created - */ - createdAt: string; - id: string; - jobId: string; - /** @description Summary message of verification result */ - message?: string; - /** @description Metrics associated with this verification */ - metrics: components["schemas"]["VerificationMetricStatus"][]; - }; - /** @enum {string} */ - JobVerificationStatus: "running" | "passed" | "failed" | "cancelled"; - JobWithRelease: { - deployment?: components["schemas"]["Deployment"]; - environment?: components["schemas"]["Environment"]; - job: components["schemas"]["Job"]; - release: components["schemas"]["Release"]; - resource?: components["schemas"]["Resource"]; - }; - JobWithVerifications: { - job: components["schemas"]["Job"]; - verifications: components["schemas"]["JobVerification"][]; - }; - 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} */ - NullValue: true; - NumberValue: number; - ObjectValue: { - object: { - [key: string]: unknown; - }; - }; - Policy: { - createdAt: string; - description?: string; - enabled: boolean; - id: string; - /** @description Arbitrary metadata for the policy (record) */ - metadata: { - [key: string]: string; - }; - name: string; - priority: number; - rules: components["schemas"]["PolicyRule"][]; - /** @description CEL expression for matching release targets. Use "true" to match all targets. */ - selector: string; - workspaceId: string; + "/v1/deployments/{deploymentId}/release-targets": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List release targets for a deployment */ + get: operations["listReleaseTargets"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - PolicyEvaluation: { - policy?: components["schemas"]["Policy"]; - ruleResults: components["schemas"]["RuleEvaluation"][]; - summary?: string; + "/v1/jobs/{jobId}/verification-status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get aggregate verification status for a job */ + get: operations["getJobVerificationStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - PolicyRule: { - anyApproval?: components["schemas"]["AnyApprovalRule"]; - createdAt: string; - deploymentDependency?: components["schemas"]["DeploymentDependencyRule"]; - deploymentWindow?: components["schemas"]["DeploymentWindowRule"]; - environmentProgression?: components["schemas"]["EnvironmentProgressionRule"]; - gradualRollout?: components["schemas"]["GradualRolloutRule"]; - id: string; - policyId: string; - retry?: components["schemas"]["RetryRule"]; - rollback?: components["schemas"]["RollbackRule"]; - verification?: components["schemas"]["VerificationRule"]; - versionCooldown?: components["schemas"]["VersionCooldownRule"]; - versionSelector?: components["schemas"]["VersionSelectorRule"]; + "/v1/validate/resource-selector": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Validate a resource selector */ + post: operations["validateResourceSelector"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - PolicySkip: { - /** - * Format: date-time - * @description When this skip was created - */ - createdAt: string; - /** @description User ID who created this skip */ - createdBy: string; - /** @description Environment this skip applies to. If null, applies to all environments. */ - environmentId?: string; - /** - * Format: date-time - * @description When this skip expires. If null, skip never expires. - */ - expiresAt?: string; - /** @description Unique identifier for the skip */ - id: string; - /** @description Required reason for why this skip is needed (e.g., incident ticket, emergency situation) */ - reason: string; - /** @description Resource this skip applies to. If null, applies to all resources (in the environment if specified, or globally). */ - resourceId?: string; - /** @description Rule ID this skip applies to */ - ruleId: string; - /** @description Deployment version this skip applies to */ - versionId: string; - /** @description Workspace this skip belongs to */ - workspaceId: string; + "/v1/workspaces/{workspaceId}/release-targets/{releaseTargetKey}/state": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get the state of a release target */ + get: operations["getReleaseTargetState"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - PrometheusMetricProvider: { - /** - * @description Prometheus server address (supports Go templates) - * @example http://prometheus.example.com:9090 - */ - address: string; - /** @description Authentication configuration for Prometheus */ - authentication?: { - /** - * @description Bearer token for authentication (supports Go templates for variable references) - * @example {{.variables.prometheus_token}} - */ - bearerToken?: string; - /** @description OAuth2 client credentials flow */ - oauth2?: { - /** @description OAuth2 client ID (supports Go templates) */ - clientId: string; - /** @description OAuth2 client secret (supports Go templates) */ - clientSecret: string; - /** @description OAuth2 scopes */ - scopes?: string[]; - /** @description Token endpoint URL */ - tokenUrl: string; - }; - }; - /** @description Additional HTTP headers for the Prometheus request (values support Go templates) */ - headers?: { - /** @example X-Scope-OrgID */ - key: string; - /** @example tenant_a */ - value: string; - }[]; - /** - * @description Skip TLS certificate verification - * @default false - */ - insecure: boolean; - /** - * @description PromQL query expression (supports Go templates) - * @example sum(irate(istio_requests_total{reporter="source",destination_service=~"{{.resource.name}}",response_code!~"5.*"}[5m])) - */ - query: string; - /** @description If provided, a range query (/api/v1/query_range) is used instead of an instant query (/api/v1/query) */ - rangeQuery?: { + "/v1/workspaces/{workspaceId}/resources/aggregates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; /** * Compute resource aggregate * @description Filters resources by a CEL expression and groups them by specified properties, returning counts per group. @@ -761,32 +150,12 @@ export interface components { /** @description ArgoCD application template. */ template: string; }; + /** @description WorkflowTemplate reference execution */ ArgoWorkflowJobAgentConfig: { /** @description ArgoWorkflow API token. */ apiKey: string; - /** - * @description Execute inline workflow (defaults to false if omitted) - * @enum {boolean} - */ - inline?: true; - /** @description ArgoWorkflow job name */ - name: string; - /** @description ArgoWorkflow server address (host[:port] or URL). */ - serverUrl: string; - /** @description Inline workflow spec or template. */ - template: string; - } | { - /** @description ArgoWorkflow API token. */ - apiKey: string; - /** - * @description Use WorkflowTemplate reference (default mode) - * @enum {boolean} - */ - inline?: false; /** @description ArgoWorkflow job name */ name: string; - /** @description WorkflowTemplate namespace */ - namespace: string; /** @description ArgoWorkflow server address (host[:port] or URL). */ serverUrl: string; /** @description WorkflowTemplate name. */ @@ -837,520 +206,6 @@ export interface components { queries: { [key: string]: string; }; - successCondition: string; - successThreshold: number | null; - }[]; - }[]; - } | null; - releaseTarget: { - deploymentId: string; - environmentId: string; - resourceId: string; - }; - resource: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - }; - }; - ReleaseTargetPreview: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - system: components["schemas"]["System"]; - }; - ReleaseTargetState: { - currentRelease?: components["schemas"]["Release"]; - desiredRelease?: components["schemas"]["Release"]; - latestJob?: components["schemas"]["JobWithVerifications"]; - }; - ReleaseTargetStateResponse: { - currentRelease?: components["schemas"]["Release"]; - desiredRelease?: components["schemas"]["Release"]; - latestJob?: { - job: components["schemas"]["Job"]; - verifications: { - /** Format: date-time */ - createdAt: string; - id: string; - jobId: string; - message?: string; - metrics: components["schemas"]["VerificationMetricStatus"][]; - /** @description Computed aggregate status of this verification */ - status: string; - }[]; - }; - }; - ReleaseTargetSummary: { - currentVersion?: components["schemas"]["VersionSummary"]; - desiredVersion?: components["schemas"]["VersionSummary"]; - environment: components["schemas"]["EnvironmentSummary"]; - latestJob?: components["schemas"]["JobSummary"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["ResourceSummary"]; - }; - ReleaseTargetWithState: { - deployment: components["schemas"]["Deployment"]; - environment: components["schemas"]["Environment"]; - releaseTarget: components["schemas"]["ReleaseTarget"]; - resource: components["schemas"]["Resource"]; - state: components["schemas"]["ReleaseTargetState"]; - }; - ResolvedPolicy: { - environmentIds: string[]; - policy: components["schemas"]["Policy"]; - releaseTargets: components["schemas"]["ReleaseTarget"][]; - }; - Resource: { - config: { - [key: string]: unknown; - }; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - deletedAt?: string; - id: string; - identifier: string; - kind: string; - /** Format: date-time */ - lockedAt?: string; - metadata: { - [key: string]: string; - }; - name: string; - providerId?: string; - /** Format: date-time */ - updatedAt?: string; - version: string; - workspaceId: string; - }; - ResourcePreviewRequest: { - config: { - [key: string]: unknown; - }; - identifier: string; - kind: string; - metadata: { - [key: string]: string; - }; - name: string; - version: string; - }; - ResourceProvider: { - /** Format: date-time */ - createdAt: string; - id: string; - metadata: { - [key: string]: string; - }; - name: string; - /** Format: uuid */ - workspaceId: string; - }; - ResourceSummary: { - id: string; - identifier: string; - kind: string; - name: string; - version: string; - }; - ResourceVariable: { - key: string; - resourceId: string; - value: components["schemas"]["Value"]; - }; - ResourceVariablesBulkUpdateEvent: { - resourceId: string; - variables: { - [key: string]: unknown; - }; - }; - RetryRule: { - /** - * Format: int32 - * @description Minimum seconds to wait between retry attempts. If null, retries are allowed immediately after job completion. - */ - backoffSeconds?: number; - /** - * @description Backoff strategy: "linear" uses constant backoffSeconds delay, "exponential" doubles the delay with each retry (backoffSeconds * 2^(attempt-1)). - * @default linear - * @enum {string} - */ - backoffStrategy: "linear" | "exponential"; - /** - * Format: int32 - * @description Maximum backoff time in seconds (cap for exponential backoff). If null, no maximum is enforced. - */ - maxBackoffSeconds?: number; - /** - * Format: int32 - * @description Maximum number of retries allowed. 0 means no retries (1 attempt total), 3 means up to 4 attempts (1 initial + 3 retries). - */ - maxRetries: number; - /** @description Job statuses that count toward the retry limit. If null or empty, defaults to ["failure", "invalidIntegration", "invalidJobAgent"] for maxRetries > 0, or ["failure", "invalidIntegration", "invalidJobAgent", "successful"] for maxRetries = 0. Cancelled and skipped jobs never count by default (allows redeployment after cancellation). Example: ["failure", "cancelled"] will only count failed/cancelled jobs. */ - retryOnStatuses?: components["schemas"]["JobStatus"][]; - }; - RollbackRule: { - /** @description Job statuses that will trigger a rollback */ - onJobStatuses?: components["schemas"]["JobStatus"][]; - /** - * @description If true, a release target will be rolled back if the verification fails - * @default false - */ - onVerificationFailure: boolean; - }; - RuleEvaluation: { - /** @description Whether the rule requires an action (e.g., approval, wait) */ - actionRequired: boolean; - /** - * @description Type of action required - * @enum {string} - */ - actionType?: "approval" | "wait"; - /** @description Whether the rule allows the deployment */ - allowed: boolean; - /** @description Additional details about the rule evaluation */ - details: { - [key: string]: unknown; - }; - /** @description Human-readable explanation of the rule result */ - message: string; - /** - * Format: date-time - * @description The time when this rule should be re-evaluated (e.g., when soak time will be complete, when gradual rollout schedule is due) - */ - nextEvaluationTime?: string; - /** @description The ID of the rule that was evaluated */ - ruleId: string; - /** - * Format: date-time - * @description The time when the rule requirement was satisfied (e.g., when approvals were met, soak time completed) - */ - satisfiedAt?: string; - }; - SensitiveValue: { - valueHash: string; - }; - SleepMetricProvider: { - /** - * Format: int32 - * @example 30 - */ - durationSeconds: number; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "sleep"; - }; - StringValue: string; - System: { - description?: string; - id: string; - metadata?: { - [key: string]: string; - }; - name: string; - workspaceId: string; - }; - SystemDeploymentLink: { - deploymentId: string; - systemId: string; - }; - SystemEnvironmentLink: { - environmentId: string; - systemId: string; - }; - TerraformCloudJobAgentConfig: { - /** @description Terraform Cloud address (e.g. https://app.terraform.io). */ - address: string; - /** @description Terraform Cloud organization name. */ - organization: string; - /** @description Terraform Cloud workspace template. */ - template: string; - /** @description Terraform Cloud API token. */ - token: string; - /** - * @description Whether to create a TFC run on dispatch. When false, only the workspace and variables are synced. - * @default true - */ - triggerRunOnChange: boolean; - /** @description The ctrlplane API endpoint for TFC webhook notifications (e.g. https://ctrlplane.example.com/api/tfe/webhook). */ - webhookUrl: string; - }; - TerraformCloudRunMetricProvider: { - /** - * @description Terraform Cloud address - * @example https://app.terraform.io - */ - address: string; - /** - * @description Terraform Cloud run ID - * @example run-1234567890 - */ - runId: string; - /** - * @description Terraform Cloud token - * @example {{.variables.terraform_cloud_token}} - */ - token: string; - /** - * @description Provider type (enum property replaced by openapi-typescript) - * @enum {string} - */ - type: "terraformCloudRun"; - }; - TestRunnerJobAgentConfig: { - /** - * Format: int - * @description Delay in seconds before resolving the job. - */ - delaySeconds?: number; - /** @description Optional message to include in the job output. */ - message?: string; - /** @description Final status to set (e.g. "successful", "failure"). */ - status?: string; - }; - UserApprovalRecord: { - createdAt: string; - environmentId: string; - reason?: string; - status: components["schemas"]["ApprovalStatus"]; - userId: string; - versionId: string; - }; - Value: - | components["schemas"]["LiteralValue"] - | components["schemas"]["ReferenceValue"] - | components["schemas"]["SensitiveValue"]; - VerificationMeasurement: { - /** @description Raw measurement data */ - data: { - [key: string]: unknown; - }; - /** - * Format: date-time - * @description When measurement was taken - */ - measuredAt: string; - /** @description Measurement result message */ - message?: string; - status: components["schemas"]["VerificationMeasurementStatus"]; - }; - /** - * @description Status of a verification measurement - * @enum {string} - */ - VerificationMeasurementStatus: "passed" | "failed" | "inconclusive"; - VerificationMetricSpec: { - /** @description Number of measurements to take */ - count: number; - /** - * @description CEL expression to evaluate measurement failure (e.g., "result.statusCode == 500"), if not provided, a failure is just the opposite of the success condition - * @example result.statusCode == 500 - */ - failureCondition?: string; - /** - * @description Stop after this many consecutive failures (0 = no limit) - * @default 0 - */ - failureThreshold: number; - /** - * Format: int32 - * @description Interval between measurements in seconds - * @example 30 - */ - intervalSeconds: number; - /** @description Name of the verification metric */ - name: string; - provider: components["schemas"]["MetricProvider"]; - /** - * @description CEL expression to evaluate measurement success (e.g., "result.statusCode == 200") - * @example result.statusCode == 200 - */ - successCondition: string; - /** - * @description Minimum number of consecutive successful measurements required to consider the metric successful - * @example 0 - */ - successThreshold?: number; - }; - VerificationMetricStatus: components["schemas"]["VerificationMetricSpec"] & { - id: string; - /** @description Individual verification measurements taken for this metric */ - measurements: components["schemas"]["VerificationMeasurement"][]; - }; - VerificationRule: { - /** @description Metrics to verify */ - metrics: components["schemas"]["VerificationMetricSpec"][]; - /** - * @description When to trigger verification - * @default jobSuccess - * @enum {string} - */ - triggerOn: "jobCreated" | "jobStarted" | "jobSuccess" | "jobFailure"; - }; - VersionCooldownRule: { - /** - * Format: int32 - * @description Minimum time in seconds that must pass since the currently deployed (or in-progress) version was created before allowing another deployment. This enables batching of frequent upstream releases into periodic deployments. - */ - intervalSeconds: number; - }; - VersionSelectorRule: { - /** @description Human-readable description of what this version selector does. Example: "Only deploy v2.x versions to staging environments" */ - description?: string; - /** @description CEL expression to determine if the version selector should be used */ - selector: string; - }; - VersionSummary: { - id: string; - name: string; - tag: string; - }; - Workflow: { - id: string; - inputs: components["schemas"]["WorkflowInput"][]; - jobs: components["schemas"]["WorkflowJobAgent"][]; - name: string; - }; - WorkflowArrayInput: components["schemas"]["WorkflowManualArrayInput"]; - WorkflowBooleanInput: { - default?: boolean; - key: string; - /** @enum {string} */ - type: "boolean"; - }; - WorkflowInput: - | components["schemas"]["WorkflowStringInput"] - | components["schemas"]["WorkflowNumberInput"] - | components["schemas"]["WorkflowBooleanInput"] - | components["schemas"]["WorkflowArrayInput"] - | components["schemas"]["WorkflowObjectInput"]; - WorkflowJob: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - index: number; - /** @description Reference to the job agent */ - ref: string; - workflowRunId: string; - }; - WorkflowJobAgent: { - /** @description Configuration for the job agent */ - config: { - [key: string]: unknown; - }; - id: string; - name: string; - /** @description Reference to the job agent */ - ref: string; - /** @description CEL expression to determine if the job agent should dispatch a job */ - selector: string; - }; - WorkflowManualArrayInput: { - default?: { - [key: string]: unknown; - }[]; - key: string; - /** @enum {string} */ - type: "array"; - }; - WorkflowNumberInput: { - default?: number; - key: string; - /** @enum {string} */ - type: "number"; - }; - WorkflowObjectInput: { - default?: { - [key: string]: unknown; - }; - key: string; - /** @enum {string} */ - type: "object"; - }; - WorkflowRun: { - id: string; - inputs: { - [key: string]: unknown; - }; - workflowId: string; - }; - WorkflowStringInput: { - default?: string; - key: string; - /** @enum {string} */ - type: "string"; - }; - }; - responses: never; - parameters: { - /** @description Type of the entity (deployment, environment, or resource) */ - relatableEntityType: components["schemas"]["RelatableEntityType"]; - }; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - listReleaseTargets: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the deployment */ - deploymentId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description List of release targets for the deployment */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - items: components["schemas"]["ReleaseTargetItem"][]; - }; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - getJobVerificationStatus: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the job */ - jobId: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Aggregate verification status for the job */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { /** * @description Datadog site URL (e.g., datadoghq.com, datadoghq.eu, us3.datadoghq.com) * @default datadoghq.com @@ -1383,83 +238,49 @@ export interface operations { deployment: components["schemas"]["Deployment"]; systems: components["schemas"]["System"][]; }; - }; - }; - responses: { - /** @description The validated resource selector */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": { - errors: string[]; - valid: boolean; - }; - }; - }; - }; - }; - getReleaseTargetState: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - /** @description Key of the release target */ - releaseTargetKey: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Release target state */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ReleaseTargetStateResponse"]; - }; - }; - /** @description Invalid request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - /** @description Resource not found */ - 404: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; - computeAggergate: { - parameters: { - query?: never; - header?: never; - path: { - /** @description ID of the workspace */ - workspaceId: string; - }; - cookie?: never; - }; - requestBody: { - content: { - "application/json": { - /** @description CEL expression to filter resources. Defaults to "true" (all resources). */ - filter?: string; - groupBy?: { - /** @description Label for this grouping */ + DeploymentDependencyRule: { + /** @description CEL expression to match upstream deployment(s) that must have a successful release before this deployment can proceed. */ + dependsOn: string; + }; + DeploymentJobAgent: { + config: components["schemas"]["JobAgentConfig"]; + ref: string; + /** @description CEL expression to determine if the job agent should be used */ + selector: string; + }; + DeploymentVariable: { + defaultValue?: components["schemas"]["LiteralValue"]; + deploymentId: string; + description?: string; + id: string; + key: string; + }; + DeploymentVariableValue: { + deploymentVariableId: string; + id: string; + /** Format: int64 */ + priority: number; + /** @description CEL expression to determine if the deployment variable value should be used */ + resourceSelector?: string; + value: components["schemas"]["Value"]; + }; + DeploymentVariableWithValues: { + values: components["schemas"]["DeploymentVariableValue"][]; + variable: components["schemas"]["DeploymentVariable"]; + }; + DeploymentVersion: { + config: { + [key: string]: unknown; + }; + /** Format: date-time */ + createdAt: string; + deploymentId: string; + id: string; + jobAgentConfig: components["schemas"]["JobAgentConfig"]; + message?: string; + metadata: { + [key: string]: string; + }; name: string; status: components["schemas"]["DeploymentVersionStatus"]; tag: string; @@ -1982,6 +803,23 @@ export interface operations { desiredRelease?: components["schemas"]["Release"]; latestJob?: components["schemas"]["JobWithVerifications"]; }; + ReleaseTargetStateResponse: { + currentRelease?: components["schemas"]["Release"]; + desiredRelease?: components["schemas"]["Release"]; + latestJob?: { + job: components["schemas"]["Job"]; + verifications: { + /** Format: date-time */ + createdAt: string; + id: string; + jobId: string; + message?: string; + metrics: components["schemas"]["VerificationMetricStatus"][]; + /** @description Computed aggregate status of this verification */ + status: string; + }[]; + }; + }; ReleaseTargetSummary: { currentVersion?: components["schemas"]["VersionSummary"]; desiredVersion?: components["schemas"]["VersionSummary"]; @@ -2492,6 +1330,49 @@ export interface operations { }; }; }; + getReleaseTargetState: { + parameters: { + query?: never; + header?: never; + path: { + /** @description ID of the workspace */ + workspaceId: string; + /** @description Key of the release target */ + releaseTargetKey: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Release target state */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ReleaseTargetStateResponse"]; + }; + }; + /** @description Invalid request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Resource not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; computeAggergate: { parameters: { query?: never; From ad0a8039504a08e9712f06628a6c5afd92bc4d2e Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:05:36 -0400 Subject: [PATCH 21/36] feat: pass ssl config through config env --- apps/workspace-engine/pkg/config/env.go | 3 +++ .../svc/controllers/jobdispatch/controller.go | 7 ++++++- .../jobagents/argo-workflow/workflow.go | 16 ++++++++-------- .../argo-workflow/workflow_submitter.go | 6 ++++-- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/workspace-engine/pkg/config/env.go b/apps/workspace-engine/pkg/config/env.go index d4869a351..78859206a 100644 --- a/apps/workspace-engine/pkg/config/env.go +++ b/apps/workspace-engine/pkg/config/env.go @@ -49,6 +49,9 @@ type Config struct { // Whether to enable dry run for workflow jobs. DryRunEnabled bool `default:"false" envconfig:"DRY_RUN_ENABLED"` + + // Whether to skip TLS verification when connecting to the Argo Workflows server. + ArgoWorkflowInsecureSkipVerify bool `default:"false" envconfig:"ARGO_WORKFLOW_INSECURE_SKIP_VERIFY"` } // GetMaxConcurrency returns the max concurrency for a given service kind. diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go index 9a630e5c0..e6eba779e 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go @@ -134,7 +134,12 @@ func New(workerID string, pgxPool *pgxpool.Pool) *reconcile.Worker { ) dispatcher.Register(terraformcloud.New(pgSetter)) dispatcher.Register( - argoworkflow.New(&argoworkflow.GoWorkflowSubmitter{}, pgSetter), + argoworkflow.New( + &argoworkflow.GoWorkflowSubmitter{ + InsecureSkipVerify: config.Global.ArgoWorkflowInsecureSkipVerify, + }, + pgSetter, + ), ) maxConcurrency := config.GetMaxConcurrency(kind) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index b468bd702..270e9cdb3 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -170,19 +170,19 @@ func TemplateApplication( if err := t.Execute(&buf, ctx.Map()); err != nil { return nil, fmt.Errorf("failed to execute template: %w", err) } - var workflow wfv1.Workflow if err := yaml.Unmarshal(buf.Bytes(), &workflow); err != nil { return nil, fmt.Errorf("failed to unmarshal workflow: %w", err) } - - if workflow.Name == "" && name == "" { - return nil, fmt.Errorf( - "a name must be provided either in the workflow spec or through the job agent config", - ) + if workflow.GenerateName != "" { + workflow.Name = "" + } + if workflow.Name != "" { + workflow.Name = "" } - if workflow.Name == "" && name != "" { - workflow.Name = name + if workflow.GenerateName == "" { + workflow.GenerateName = fmt.Sprintf("%s-", name) + workflow.Name = "" } return &workflow, nil } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go index 9bc24f80c..bca68002c 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go @@ -16,7 +16,9 @@ import ( // GoWorkflowSubmitter is the production implementation of WorkflowSubmitter // that calls the Argo Workflows REST API. -type GoWorkflowSubmitter struct{} +type GoWorkflowSubmitter struct { + InsecureSkipVerify bool +} func (s *GoWorkflowSubmitter) SubmitWorkflow( ctx context.Context, @@ -28,7 +30,7 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( URL: serverAddr, Secure: true, HTTP1: true, - InsecureSkipVerify: true, + InsecureSkipVerify: s.InsecureSkipVerify, }, AuthSupplier: func() string { return apiKey From c13136a20b1d930c0c7fd5939537e9111e1b170a Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:06:36 -0400 Subject: [PATCH 22/36] fix: remove unused function --- .../jobagents/argo-workflow/workflow.go | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 270e9cdb3..9faab36d6 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -187,35 +187,6 @@ func TemplateApplication( return &workflow, nil } -func createWorkFlowTemplateCall( - name string, - namespace string, - params map[string]any, -) *wfv1.Workflow { - wf := &wfv1.Workflow{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("%s-", name), - }, - Spec: wfv1.WorkflowSpec{ - WorkflowTemplateRef: &wfv1.WorkflowTemplateRef{ - Name: name, - }, - Arguments: wfv1.Arguments{ - Parameters: []wfv1.Parameter{}, - }, - }, - } - wf.Namespace = namespace - for key, val := range params { - p := wfv1.Parameter{ - Name: key, - Value: wfv1.AnyStringPtr(val), - } - wf.Spec.Arguments.Parameters = append(wf.Spec.Arguments.Parameters, p) - } - return wf -} - // MakeApplicationK8sCompatible sanitises the workflow name and label // values so they conform to Kubernetes naming rules. func MakeApplicationK8sCompatible(wf *wfv1.Workflow) { From b1da6b7c5b9e8a992232937ed4fd4b348ec76b3b Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:08:15 -0400 Subject: [PATCH 23/36] fix: remove config dir --- apps/workspace-engine/.claude/settings.local.json | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 apps/workspace-engine/.claude/settings.local.json diff --git a/apps/workspace-engine/.claude/settings.local.json b/apps/workspace-engine/.claude/settings.local.json deleted file mode 100644 index ff37daf8a..000000000 --- a/apps/workspace-engine/.claude/settings.local.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(grep -E \"\\\\.\\(ts|sql|js\\)$\")", - "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, resource_selector AS deployment_resource_selector,\n job_agents\nFROM deployment;\n\")", - "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, resource_selector FROM environment;\n\")", - "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\nSELECT id, name, kind, identifier, metadata FROM resource LIMIT 5;\n\")", - "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \":*)", - "Bash(PGPASSWORD=ctrlplane psql -h localhost -U ctrlplane -d ctrlplane -c \"\\\\d reconcile_work_scope\")", - "Bash(grep -n DELETE /Users/mleone/Documents/repos/work/ctrlplane/apps/workspace-engine/pkg/reconcile/postgres/queries/*.sql)" - ] - } -} From 3edfed8e66b7f1609df94b4fe933d3835f71b489 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:09:34 -0400 Subject: [PATCH 24/36] fix: remove binary --- apps/relay/bin/relay | Bin 661246 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apps/relay/bin/relay diff --git a/apps/relay/bin/relay b/apps/relay/bin/relay deleted file mode 100644 index 90c6aab4e6abb46d36a8cca8e23c03f7b365ae14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 661246 zcmeEP37i~7)vunK%^o3vaD%9uNHEzXGueAXPn1J0Q2|BqLhu$v1V!W$#1BQj|Euci?m2q4XLlx9{F&^3rjM#u@4b5O z)zwoy{e6LCu&T{vDJ@7bXT z)AgZ1awrn34>Uu1#2g1R*$v`X=ABgPILw$N6(Gy7NZ7p>@nMgEL9|_gZh!1ayraLps zd$%tci3D~Iw0CC~`_m`v+_7a>xO-s1@T#TrhBuFNMz_wWtE)Tw@Wpi*50n3H#laU1 zEcP;>!#91P3_+JR-j8Xy1+03*PMfYX4BfzJS+2fhG20DK4d3Gg`ZJn#xo ztuZzks0Zc(oxloUE3h3%0%_nAz`4NXz-NG4fG+{}0N(_D0{jwq8F&q-f^2&M2LOiv ze&BsT7qAUT10Mp;2d)6V0^AAw4EQDRTj0;YYrrJV*i>L&U0R9ZT2K*l|6?L;O&;T?73xK15 z<-jUnJ+KKl9?*duKn55A&I2w6J_~#f_%`qm@LS+%pc)lB6_^9e2i5~YAPk%X+zfmX zcm((%@I3GeFm)1m0_Fp4zy{z0z=wd30#^Z_18xU?3OoV43j7OrM>S(jzye?ypaXrt zM}gCUYk~WL2Z28W&jYN6F)wf!a5yj@I04uW#DGEI!@#M)S-=gz&A>yz)4+4Uq+0L{ z90)W5?*oH>28aVgz%{@RfS&XhC zxEFX3cpCUK@OR*!z?;B~{h@E51?T{l0IPwGz!uy+Kre7Ia4K*qa3ydTa3Am#@Hb!z8m0Y#qkwI|Nx+AJ(|}umuL8dW>JDLS zKfn((0h@s1fsX?Z13v?*4nkjH`a7Ttpc~i*d;mBLxCpo( zco=vIcnz3&7|H`8Knl1LxEA;U@EGtGQ2kEEjs%Vcb^;mTD&Sh+X5fp!4}r%3_AbZ* zGy@$#Kd=ip1-J#c4fr)s^KQnb0Uf|XpbyvyTnk(e{0MjgVD*TJz*-;*oCjP1+y#6S zcovvE9c@3*4jco#ALs!t1nvXA4g3X|Is@r|rNBDi6yVFiH-P7X*MV8@fj)o`5C^UW zZU&wPo(JB2IMM)ffyKZwAO!RSX8{)iUjgm{{s8cqC>QVp3xE)CE^s?=H}G@dAHaWr z)+5lz0Kz~&a1n3?@Br`}@ONP5dr`MQ5*P-q1g-(T3%mv#Itw&_H9!v#22KYq2kr)* z0A2<5oeetzHULo|4qO6U4SWN*4|oPRpaK2`tOh;;oDJLpd=2;^@Dt!E;5lIbIjDYM z9q<9*Lf|Uk5#VpYOTb}s5fg#6KpMCi_%84gaKJp+7_b7^3G4!H1?~m@3Do(}rUQ$C zt$+?hfOCOMfNujo0hk~51uOs-17`!50rvq90Z#!h051av&xcO~OM#8RAaDclIp9~o z?}2HJNCO-NtO70so&x>?bTpxV06Y#n2{bmtcY*7G&jAksF96;a#%h7RfYm?`kN^gO z^MK2M8-d$_Nv(|S1H2QM4;%@s1x^4$z)s*);FG|YfqQ{RfL{Sm18)FzZHyfROa~f) zMZgvy0Q3W=0~Z3f0Cxkw16~K}+R^_2Rsu=j4B(T%)xhn*H-X21sU2uvfWv_%AOU;= zxCOWe_!00rFl7O368IkQ;Um#z0$&2Y0lW#!TnPUMP69p&+ys0L_$}}p@CH!*KG+t} z0&E682;2zV5Bval8QAYA^c8{azz2b|fSZAD0WSe>0`EN<{Yan>I1{)WxC?k3co~>^ z4Dv74UuF@4x|zP+!0<;48q|#b_sh(}B+b zuL1Aqgq;K3z)8SIfb)U7fX9JXfT>H+1_LXAFz^ZB65xK|cfjkwJC}kN;6xw_oCcf^ zTnD@e?6nL&4lD+C0OteO0pA7w3j7n8yd3TVd;k~%z6tytn6d)44fFtK1D^-}0K5ep zz7la3*b3|dt^n=;eg?b+?7s^319Sr;z?HyPfqwu8uSU!S&IFzWUIhLPOj`qg1x^IM z3H$~)crD^AkOVFOZUr6!{sHW>4s{J&415PTY(2(4z*^vB;6~sc;BUa2z`y_yq71;2GdQz~0BhAAt41?|@k+fM4Jw;0)k0;7(xmHqwcKcmp^{N8JF+fs=rpz^TB+z_Y;QUc?w+1+X0$0=^CW40su+3&S3P1;Ft@0(b;? z37FjnzX#3$o&f#=un6iGct3C|@O5C{9pDkz1oQzTz@LG)fR29H0uTqT0=@v;3)Dto z-#`?&47eM39C#I|8i0T3(ZEPBj%k_Uw4O}Hlc`KB73kF?xRQ(oqW+}b8^zpDG=g*M z(7YXv(*%?JG%rdLnAr*GN&B&x024&`!@YE5z$&U|B(2Aj$YUOu8G?xn{>)P&r$054 z!k>9L9FM06<^`25kj7+FP=4m!90;WQ2U!(+oPb8fpm}wHpcyA<%$`7J$vQEq-$RCM?Q)DBy zL-V$hT_295`{MC_^=&2y_66|QL6d^EL-Tf;1oey0!6xI&ys3yMh&6vS(4$AiM`|P% zB=~b`ovxXg>4bPz-&y8pXwvvH^Ylbg@CW$`;|m6&QS)9rC49*E>K#Z!CqX?G97vG+ zWMYvZYR8{R_jVA>Y?LsbOdDT@n;EGic6+9ZNT8NT)Ar*?+~1q(P0=lrD9II#hm9}u zav~m$63h#R41Sb1Ovt;T59>iaHfVgs^t8V(olX#f$&o}l?oUTiQi?1-C9m`aQhmlf zJs3&_#D{gE59jK>Sz1q=Y)F0@NrRC@pPn>6Wl?7G-bfP|4oG7IF0!^)L^0P>qW~69#yr1bwNA;BW%uRzJl1c|+Y4V?}V+VdwPHSV6{LH0g z$P|x7M{I{iD&vYn9)Bp=6DH)|M<7qe6C?h?M&EqjeB&%PkwIJ3i4Y`*c{mD1DC5h> zU05FmDY8z)-2wdN7A6hdpNfR>mwQuXFXSicUM!MI>p}VHlwaQU_eKI~LT)mWM#S74 zLFrm19f_)M(Js)5f3PW+hGIt#4F!^+R8wnf;{p_@M?>PnNRF5sNu&`9jc=o+3HL<8 zCh=U#hK72OhLC&PESLg}(@mlu56Mr5G(mU{VyTEG;&@08^gE=bxMn1^g*cnjNQ)lS zvAC5)Xo&`5VP8BM_7D3Fit>v*-a67_XcZ9q{DD-gkzfWY>WC#HapTL@d^EP9{b+1+ zRgfTS6t9Ow1sPw)MYA|lr81TCZL6y7)o!G>SyVC=z@HNx>(+4V`~?IfCA5fWWvV5i z58%%{m5YV)6O9qIM?fSYKA@wW8NHBRC|xL)A`IxM6nbygk*NVB68#JHZQe-6Gikji z6;j{k4fK=3QS^%=;Xdj!na5OICK<(_Ar!3CGE@@QpXiSa6HLka^kMuN>8(~Rq^Iy_ zTpkFu;?Fn{WK32N|KsRJ;={NNJvI`IQ(FU(47T8}keZCNvK!VkA3cSU#^(90x#uR& zJ&{--IU+w@&{K4ZBsJv`?X8=Hib?fN!>fu-Q;B}P z4^XH5q24~gI5inm{$jf~5bf6o#D`PjqBp@pW3%|6{-6^HO3)-e(7(f2RdYi9-- zFggGSfu5+Ib4vYEGzzlss=kBqfkYCX=`2{!kKhReLE$Ngkk|gq5Gs7KL!Drq>kZI2;(P`x(nmotJ_J9+@Ij`r9mqxxkB4BeEk7}whxrW4PoZQO z(@>IzPSyeGK8+TE?8W-FUG&33Qy3-T!{U=z??Vp;<5=6F8O3&_;xPg_Qwkphb!;lh z4^KdQEI;$^+N2MrlTq*&(?j|o=1(xeQuHQFDT(<6(q+s)lp0DEPi@-%P>MQf^3#?j zjDZw}!2Th$SMgxKo;D9KN{pZ#i$vw8El)njiYe46{#+#;h=ibD^bHUa)R8SCCT7q& z`ZJkGh!6^(PVwB2KeaQ8emjgjDL-vl3b56a#QZ^0e!5AC!BZd>)1$^$@hnC~q|$MC zb_nf8GSY)~KV&;}ML;--_^>DM!*f$TG?fuWoQ3_^?uFy?Z~^0gx{fdTX-fe1?U@lB zqwy3b7v#5N!eKvxPJ|E{p!up`JQ~N04?e{CDEa*Tk#u@Qe3+4WS2WT?5Ro}R{?y!L ze90mbu`vEdqb%K1e%jJvFp!R+<2(;@CHhb#l0$Y4yBEy_1F4>Q z`Uo5fbcC$EnW*}a$Am%*7#6jm_TzZUFQ#WOQ{$L-Scpn; z+L8=o_6AoXvEI1)2CWPnfe?=S;t6_WFN*$mIyvG)1VOqe!RekAE@7fQmv zHxiVejwI!6zdq0dUqn(d=C$kam!DesxXfSd)58&2I{;7nl>`h z)zLMLhm(W?w^Hd)l2G6lNaAW~Cl7N+_Bk`E+&@nkII*j;Wd^sjFo1W1U zihiZ|aHK^up(*jAEGPfrh3BE2^ul#N1nJT%GA6u)soWF){z?m7!HP0;=@g9 zp^1UC@#Qv0JdqX?EA;Iqk1140?;Q+8lH5yEJp(dCVak0KdG9f^-3 ziL#BZ7kyD_b#zoDn`w4pbyj@1Nv($>)Br(I0n8kva!;uhcSDA3JUx;S3tsf?CeJ8- zWLBjRpQ^+>a6=vDj6XLi1=(m|mvkXIaFfeqFC7Tu&rKE#pHk4Z@ij`E#1M zJ&9l*J0z##&<#>wd7(_OW85_xS_fJ*;KQl}O2sTj)`>#~^h5(Ra#i0B$*>rf&cJ=? zvp^D^I%Q~%<*h&>lJb)dozjcM)OM2fr~Lh~_)rW3yAk|uhCT`uK-84hM<*-jL#%}K z0&$d2)a1gnPH}~%P0WKLxlDGxqZNq#J#^cT`i;)G?&F zN%ifXgqS2sN3hyUvqf}3ZE*qSVQA!Ph41)#FyR>Q?KQp}6Je?h53^vbD83z%8520W z)VD)2Q}|KR1(WDM;qjvYP5PUM4n)miBn{@}$g+@IXL;S|R83aRNl}O5t*4RjJQf8B z5m}r#5D6S8QQ(iFjZohX$xO~vouDy54*~Nisi8<3k9%dG6%N5>UYg}2j8+&K5%?5F0^cIc45U&mrj6zc=# zfkVPHIxqox3_#eG_#jnM%?OniR;3vzu(XfIVd7ZIwvHT1GV|a;k3<|=#43A|9^uG6 zcgSz*U3o9otPs=tP;eLvbmpP!?6B@(hs0(Pc-#Sh4yp1va!zYXFT!4rXpyLkl^!)% z74HZX0Qr`|)Oqt{p5*W9b2M#&R`(gmL-8|in zu^NIy_JzVGZ5C?PJ!bG}HaLc#;L9FBDDR2`KPE9%?a_E3WIxV}iSY9v`kVup5jECn z=_D_iNmz8t@thNEi2CZ{$blI=QXa*l1VRz=SgFN2%hO50?m4DbGQo7BM|~F>F&CYG zHD_E)&S~1BoPRYNEulB_%#kdnpfQOm2k`2Y)E;(}P$qSXefbozG8Xe>t29KV`Q zD=w3}8GKV<$AU|wgDDw2`H$zPzZ3(Th%{QNVs930&ho}GbdDxRhxNu72 z_`MO+*fjd&8gJl7@jEsCO^wgwKh^kmHU6B&KjPtUdHBaTzlifkGt(?S=H5P4DzFJ;~Ym-4^3ZKgN=KJtz zd@sH)-;W=}59WvPcksjbJNdi#q5R#vo=rJ~9V{5KINoR4zWeQez<~!H%-K|Iyf9T1 zyAPX&a`vHOCt+)t{ZPbSypHW#r3vZS{_Fs@KT=L&2eN~}0q0fhKzY-PZDbC?>A{ql zvqRZC*r9^vVeFltd6*RCUF_YIjlD~V;$`)0I`Y)h&B<&Adk-X&QcdOi^8@&SY(||{ zqg3r>hqIZ;d^jcdvLo1gadLztJPSLxRO57(Ak0|QsnFpHtg4fAx?j+4i z)T)?|`Jo%mdoE^y)vLlh7v-h#1utklAdOaGJ!TEdmfxLzv&W^5TlVpW>*)dQhXU9;{HSAa< zBpp!V1KF|4HmcYnwpa+bNR&L8b)rTNL!u)A>Zi7jKxkqejEGFf^1uoWOxi*o65HAqiG9;$)aVE7Q)jbo}wSXjK5tz@gXhpi;G zr?Az6n>pOa{d_)e;jO$8gqqpvz1U=1#z|}qpUdY7&9A9~GoUg&Y%N=>;TL&qolp*E z>)tgGn)Jg0(wgL1S`5bnf6l6cvE!4-6a~{a;2N#(H zp?u(`3A_=tX4btgn`YyR9C)8;9=4Hf1Rq|QF-@F3$tzlc(R-`>l?9T-TSD3zc9LPWUe?2cd=~4W3T2od24Y7*$?g1e8tT4-e_rEPYy3uyAH#3a_^lcr<`-$a8Hx?_FKW=- zmo)x$jel8##7+E@8oyoRmuUPBjfI-D`4+FTYK%yA$f55z?49p=cm4Dk?>T(t5$~Nf zyJ61Uc|QOA#&*`r!YKY2ek@>GVfK}}9Fiks<^gH#G#fo5d3s4rvlE@*vB>Ajbw-NRqRa7Y-g~6+F2U5Q9~@8 z%+A&r>{1a_rJijswT`x1gR^bsTfn?v9d&lDjrnSJo`zaD0?aJu>u_R}wGTUAWAGOu zPZhWfyb?QG`3XY(GjM-zZHm1y z?9&uFWE|yuA+FSDlO(}vc8Q^a4b}ri6D6E5SsGD}R;xOUZ)BBtYtTd9OP8* z5@D0$im8tpcB94+cd4YcqO2QD&DOBbY3ydA)(cLzqlx~Ud2b)~dGua2xFcCx0|>Xk z*{G0X5SQnmnfrW!TD(=T(hSCBN8(nK-X!(~jUk*bL0KHfvK`u+VH3Zoq1lZ>VLOC& zDVnXv%^b(CWnb3VS4bKmi*S}NTf$CaU)9)c)MiZQQ5?%cs@T^w1OlpsuW72k@O6!$ z9XXE2;B=gQUFpA?pUMk9sjbPVq2Iz$z_S7Y~4)o>h%istNojrKacpG=ao z2Z$y*T@Hc{i?Ef=y-*o5mU-R#CQx_u{H8eC^M^tFiA9b4&Oxquud{)@mDCqa9$i0pbbM@b&)LQ=3ijmD{S(aCE;h&_^{*+IMVQ?1Bw zZ!&vKV?Wc7iAdH$V?KVWhH&+my@DsR$H~3qcz}X;6h*!V7wsdAD)xlNeonm2!I5Fl zztCvU&R>uT*Ro$}?AJmFV!aj#IWC!^d)aR^_FIil$2YYCd$HeX>`CcUN08_pq!H>4 z;D+6bYuN8K_6JHxQ4YR=bJeocu%|TkM{-?MFM01wT(E9Ft+7AJ5@bz0ZQiS9&uHkb zEJgh-Lp?mB>Y*1x?3I>h5@O`&paI)Uv;54ATV1ihGNY@=#nc#jIn0)!6f7 z02tGt5;24Yfxp@ac-ebkejJ}P$k>bhO=B-e=c2*{K{RT0xNg@%EqhUPMV6qiLS4UY zu(}tmVyoFp8n#X~Iey6!=oO9qojSuF>Xf4ezF6aEd^fXKY&_MnS2gUqiZm^-5ejb8 zam5s>mi;V+tJudp>~s$bLU-U} z9>LyT>KPRxRAmJ_190q&Fm(rnq5qM zll`p3`2AvYQtSZsX%D+Z-lNv6nF?zK9guCH0p!gZjUS2XFva9KNWZ_fZ+?~T%`PP> zYKRJP(C|_Q$zm&RPi0focuLIFRkO=H>~fEk0Q}?B2)9#c%it?%7OzbxaSm~f2-%J? z@fDt|9yvOM9`+eZDm8GZG1kQ-1MXzk`_&$HjVKP?F5xL(phjmVZ(vv3s}Nm85Bn^U zByXpFRvoUG&a^kX&cm*!>Y*~xSA@pU>R~WS+Mg~*(@2xkv#Mb?c-V~|O1%c@B+ zS1gmi37tbCNRpxhcALi0scL37*($x7-RxnXC&v8ztCR%ihQ_MdEgp8Os$)OSj2pG= z3m$AljjF+rzJa*B7UO(eH07yfU-Gao6HVx(fe2#^hihhn8rr=1t7JQ5K@GV16_f88 zb{pBU%?fcr3AGpdnumQ|){W{Mv74rqW#?%SXxtab?hDw zyVrxeKGF`=pdV&_k4^8B*nJ*$KQVwA0vuNnH{Si(H$ChDDIHC-HNx9h^81A`$O$g$ z7KrJWPoiJxDb)9>W8dY_GsFq2G=Y{M`zN*9wcjfK;z%iFe5&bKdAACG)#VD zMjUtTHoiZ5$ip7?kiI@F^hGAwBs#$yr02rF$stjfNLYxWL)mvd?0X&*LblMtzbz-3 zXv*#w;(*L#B|HN4^jVF6hdOB}s|j>Davq``lph(y#I~0`;$h!MyuvqiFF5;wh=D(# z7&wVNiis3(hEj1T)jOH}(8GR2f(wBk7W4lNDEcVeum$Qm_G1tGiJ>52p2z^j{Me?% zce0;)*kj}b6jNnIqQ`p*vbTOAMC@P@UWi~iw#tTs6LP;m42UuLm&Ew8b_xTea|O6`Qwydc(7qQ z@?x;?3+Wg8vtN1GuSJKU0fnLW3=S88-YJmqQ9+#?WSDwgXjzcDP*vBm-*~VMJ1Bc# zyA5DyCa#!XU(J5!VNc3}iQfjCo5AMy9`*;iHJw;r$$qcyOkq!X*dOT* z0LLEkPi05%YWB2;{fW{etdN9HD@q5kXFSxDQtL*ggBFb_R`SOvKB<-!edphcvaTf~ z^dpZ$e`6o^tOxten{5rMSHzS?P?Yo0DWu3%{uK5X4||RpXY{~P(C?_)XrNC1jo#~D zvZXnM{nf*sCmU$sPml+a_rbb4AcKdabKc5-L6fYUqmPZgHpoq2+9)<i1$FGfj00kQKXxW(ALk$?>mD{G ztm02(<7Cs*`Lm*~s3mAX;sRXl%|5}|Srp4#_%jrJ$;6NgVyh{()Aa-17A$k}C>rle2!-9GFJ&aNaClGjmRMs&;bUD*C|5#&|5HWB$na!V6%a0oVqDgUo%N#6g!b9m*+Zm6K9{J68}Lx4umM` zm!cMK;+B)uv70&jymT_rg!=jGpopth8ZWy=q`^$55amF2E9NGNuwbVd^``hCtS2BJV6FB<*O1kQ(1pPb#6r(mMHd451>ww%Zl9=VN`&9++#lFee1Ds<9fDH2tOaeh% zni=7^V~@^W_ASo7jrCW2%a-OloIS`zT_4IZ5%3*Fa}vEOMq2a7Jvf#zZW4PK9b4hO zm=9>hnY>=jzQ@@klw3|ReNU08p|{Qa04l|_f`?-ad$osSIQf0EQV(W$70nMhF^%pG zggck|r6dN~5M}}{@Nf+NF{Xla7~wZkH8oR!In%>2V1ASzq#cmkScyehFZ&T^Kjxr` z0}^&Jz47L!6mO|volZ1C<2))AcWg1YnmxwZ&p3(!S4e{Uk12sTdz`Z;=o3M3GW$7a zzaWY<-XUwkL<;yshM(IBdf6{AyC_)srC_C&{hG7i$aM}MzettxYpc4avEO29k<6Be zQ~w)`$iWM=&`fL}iqeoOZz{TuJ;~YcIlowD@JkV43s2fY(OwL%{&|YSzZ>A+)aRQ) zT}CYQ{vpd^_G5qK>}f95z%TKTN}xE@734%+H6srgBFn>!MJXS@%ka?OLQi1Ce6vq{=(UFoL}z20aaZM`zsmOG|cqUh+-yA&8Jjq+4JZT za+D2Ift|s~gc;$o={)acFL11?QZwb_R|$2)jF1ch%|PQjox~zhU{??g0zw$jf|eoN zp(H2__ZVR~Xt@yU(4|9hm_LCU5LjBME?(eAXonZH#e)vcnwZ~@y~x>1RQ;F~!oWot zqSPv;Z&0Y@4+Z;(w@W?9Qb4U<_A+O$&;sVmhK>K7vsb0xwopb0BxPiOw_9W#z47TE zq%^WxI2eRQp{SbIY(`SWUdJRU@wpO5qT#5a*F+)WAyPqoGUDNqYJX?3H#nPu5J>$$ zin2FYQTBRUAjAXZXP;(=z0n=I^H_MsKKH+6& zc`X|f^+Yj`b$9jhEv!IwN7)Xw%={gz4W94|Xp(1S5y+V>o#K^+J*T_1z%!Ua1Fc6%P#i{9?Xim%%oGzuJE!ey=2)`1uJoC*gy@t%F90Eh5RiX zbAB|Sz!g>g6n3>&j@YSnkiAA@i5GXWpEg;-uJN*Ky->m3WbHJwaE~x4SpBy#0qfxp zdH8)E{#_4$P;^pAyYMqi+8{Qf3Lf$BAA0zYJp3mf{!fsnBBTgcCe%`}>=Rstkp5jd&{#y@! zTKLP4Jv<0^`iF-C5BfL%TNZmhCaX&R01ZU49qWIpKo7YcB7Zw~KoqCY>+W!Y zrsF^dmOusvDxh@=$l`X^cz+*uvzL8d`S&UOn~+}w{G;(e7&PBX88H)jvp07nQpIlZ zvRkR7l{gXsxsH9oAZFwih0elNyZ?UCD`$==Tz%1S-fH$GFZ;5WmL;Un-|^szX@6f4 zH)&cB31&hg>?@}I?Zdw6Ww(*v3$vGj>J;J~vPeBbe5miMc_LsI+{?c1l@St#h`>wh zHV=y`c+i8p_6}<;yWPv~@FGbASuL~+R*~v<%Tue_oe+++`8{gPr{mNd_AG_Pj?h&OENihvil?B>J2=dYbejrR%TEN}j0_`rImk@D22(!9ZlCEO+d1cdp z*5W>St%lw2W#1$(77YSgJuEKVZ>q6|J%AoLRcA9)K#ylXV7vEip<*&7xP~xDyi(DM z0NgY&jb&T2{%S@w~!Y~ zCD9Q1R~`&Bk8qSPhfcTElG%6jD1jdYD0x$l(h#BzzKX~|4B62{b zL5c=&`5UQ|0E_WcmHx>CCdTl1w(r<9#uE+;D)!rGS&|* zuc&1|LVcUdSRhQxSU<87)UY3WvzM`cZ1TM~`-zv<^w2FK^ONo^mj<9Y9~T-$nw)91 zTJ}>fd(0~XAWVVUrWv?qc3bM$&%Eq$sx8c}gRGDm)0;oDsiTHH;blK3cP9753X%sG zl+{v^_Zkb{6eXuiv7~de&r>LLVWn8^uKEMYcE>?I%L~$%U`S1c)r7n7n4%b z--sCQWxw;XC#jYAoj9*zzxT2~5E}@5zn9bwVo!P5AH8zcT#9E*B+&fP?PBSZv~{gU zJy+=KIt>PgC(fSoI!t-g(u-05L;(N+=vmR!!Wpm@^t7e4$?O>~dzM0A5Ms=rln;wg z1>T>r_cf~6pD}Qvgt+r($?_!joRVt@6r=c!NdS8@HX#`Oc(-^6ue zLMkW&q@2+t6+SPb3mW!sdH4&IlVbeeyai_xz3c@qdr_$P1)sap1m7Ymp$CF+p8Btj5YYF@Dv#M$2|D?ZVyu3@iw8OEy()Ft_i zh)W2$ubSmdVy}7GKd4?s%lw)l_#y0d%)(Peh{hLT&Z8DrDJ9Lty()q*q9Q__9J?Tt zh!`^+nH^Uv>e&Bz*&C`ODKk=kljFJ-1omeC!qh&MZ&=iHii*%OLMZxNqV`DF|H}0_ zj8MGnKVGuZH-(lDWN&$CPLVp!|0K1+8jnKk6OJoqRj^b%6H)RF>JCz)jU0vN6c1t_ zsiHMq@>d!=-%nHXG)sWR3VJ*O9Ru{tv2K9Y&0O7`j!X{bUd2wWg6QS&-`yZ>acM_(sGpg8`RcKDY2V&mG z#K2T`{IR@*yII|@I`(lq&Qb+>AEj!h4)K4G35yX`R)tJqXH~JYt7x2y*~qs%{4`Ee zbIp_o6X+k~H2-^6HsM3)HOA)>VWKJ)6n-EkGe5#PW^iFHEu=3*sFnOVjX$GtEXw>r zEELd_F<4+Zr^?ZeFj&P?0rUwg--n%7#m=vikqEQdG8oChJdvWszw@f{dyf}Xu?wqE zdMmr2O7tGR?4m06sVc~b1M17Zn_XNbr=+P@Ms^QQvG{O0rszm!(#9Mz`yfPwPN128 ziMd@FB&bvW2~HNGL1fT+mv&gOR&)~kbQQa#3d-`K6r9NF$Kx|q>@uZEaDY;u$J2V4 zX%(tGlcsD?0hdLl_$dwUWiQ-dU{*zg{04lJ&b;jEDt1j3+PbU7`2p-&8~5bUAF*aZq%fFe zK36R2fDf1kR>H2WDi}qp*k{c;`>d?91KD*|?D{HnV~o*42WoUCKaYamQRE{S=b>MQ z5g1mZ-oy;5D9D3cg}rG<2wBxwj#~KXh_tw9 zHuBZ%rYiP1ssdW2zL4Y8=+4yAE52zYhC4ywCDU=m%!po4m0YL50qM2os&ig>{ov!* z;8pT<+r#ngzIeU)Z@zg{UoVg4XIo2)xYO3u(bm*VhbPsAzA)zv|(*q%Ta?JzWMcAkHoI$fu4xkS!%94#ji3`bMZ5M+IBY( z4$e*D_nO##f0rJ@Pu#KNS4wYdsq4u^qM`aosD4I#cvCdpnOWYueaT29uydfjJG0oI zK4IsMExW?q0}F;%EuA;Kd89MCbw*uX-Sj#JUYh1dLrP*iuEQ>w=V3!W4~zRT_qV{q zZb^ICor4}$e<)|i9*5ug!LkYe0X~dqdi#-RapU;y;SFB45Wl24XQ-OpdtNR3+ox+8 zet%0_wO|WtwGW522fO@ju60fubM(nQ+3AP>_FuSW?{=NDRd&wy=1#wTb-Iq7lHKg6 z>X_aB`}?AaivKD+=a;#5>GQUaw{6C*HYIiValSj*%-@=e4Pj-yIEvy#(lLX%kBnmq zbL$7WIdq)1+ZX%p!)Ry+D1!0cw!#}h6uv5U&{sPN8e7YnMZwb?`4Kf0i91kLn_U$C z@w5*+Aw&}0c!>~97#kB}C!Ex`?id=}kr?jpkF9En9pAVrom`Y$wJI=l)KON7RZVS+ zmkxF<9bS4|-zubDu(V@KU-+n_MlZ#O5J4MuH@ZtoTjx-6|0e8;kn#^i^>|G8FNp`` zkH4{(L;_oW(|$aDbH9B*HoL=~ggRbb;2*%NRIpWSIDRBiq1QVU_zAgP3x@}2SB*dr z8)V8Xq@fsI5>YT?Bo;|;L~6X_0owr`X#@~o54MgAi;Z|X)5GG9W9``;(uX~GVF8QG zZQ4rCiZ@`c$E&Ik$B3N<>2E&q)h*jgRuEY&{q67OYzCJ zC1j)6P%=s1QX^iDu1euYKJnR=j1NZeDoSm^+}MZyNNJ6}8ODxiz8E(1oFPit)_9_$cFdsWTdj|54n#9zOWGq# zNN;Qw;v4E&i`R#aT40t{FA-LaH;KWc@Y)QxqeJ}9%9ZO2bKPZAg(-%^!7cO-mc@Z2 z_UK7sE4R{lFrCUJ3({&-Nd;rVTy?cmb~zNcyL9Z~C3YKhqoQ^CP;t8~mphLd{d`-n z6Yol6SE=k1+UmdcTX*z2(D0E2i&C)Xv~RxBS?PXWYJhUC%5msyxjh6z7UitH0ow4q zJ{_;eCS2HQtX}PWbY%U^p&96I$_U*nDl6Jv-6=0SvX8eae6|K67RNg~s5!^>SoI&QUsg|VUB-(B z7S?Z_UoWep{^Zf+C2L-4?2ad_**qalv?*`RjxA0-8oPxTd{sfwow~9O+Xt5~W?1Vt zuIZ{5ElRzr3Snca;!1ZcvjKIkCaV?eP6T30jRx1zMy%wd=EVVQ$!au|MQhKQ^{i%N zgR!}o+iF(ng^f204=l=t3$cv0-z^75a=}20swfx5CkgL|J6v**O)r#SK7a&d%w~WuerLDmO=Aa=_>7){Q>D>aIBi6vQFK-R2Db$k9sCwV*H2yDXa8 zynI!ld(DR7aO=?U7K}a}G-Ye1Qt5uoU}X;#@dgTd)gNA@i#^H<8{e_?ik<~NypCH9 zyzmOZ5p+|_K?(2L$mnCCMOzh`oT&~2O%h4JnkXe-JdDfnI{_zxVSE&dAv}39m>5WAilT8^-vg<_FhgB_3 z#%CEFjm>=BVwCP8u}mn&a*nC98b)ett(gV)dDMUko9`BOAD8x%H7XbR9~|aGT1w|j`YcFq)Ah9)j@KZ-000XrOt0W zMJv-VhS`gvd9qAjnXo9TSV``}lYPKZj9L~Cwm}N=h``p1dBge-o-Da)cAmYl>SLpr zYE<10JIR)OlGKLJnq9mRH(NZPH7f+(L?+n{Zg3b(74LVb_R6QMr(TL#oTeI}QIC&i zYo!G0On8|(DY5v`vpH40IsO@)gi}4KoH=to>hd<{ns;IbZXjMLe2YR~O+e7OE$F(;t+Pv;K`$%`qM!k1_puMYOeNS>p zZ(?)v*7jYYMQ%pARJmF6M&`0=@%G-TaM8g_s;)$*7*;B&T-eJvvyakC6x;NheTb#8 z!ALS5qYe9ua%N^&i+9W5bsmMg;=UE)sST^1Hm8?vZCsL!C5F~C4G(TTAu+UgwLi=7 z)U$7>n>hTFDwk`B*J#a8;J$WU6@ZM6^40%@3t! zjK*EFTxXMGA>WK6XNJTIVRr(Lp`;DIjkiP@hGbTtRrjP&J+>$o62{Jss;<+rRiOvd*mwYPY5^C>uok`J`QEdZ4=5jt6@c^XSQz*o=()0s(4?v*-)i- zk<~?wAgdm9GqTCZTbLt*9b+JG_od=|fh*MuDH{IFL^g??BZ5j@m{(JKW*=Imw-GPS zc9uHx$T;XXFP%-{<~^b0Z70$?c886fd|78TsQ5QqMZ)5K-{f%Hid=Xe4gFtw49jtC zTRvjYJPK#%Cd6iAE+`OMtEW;3=uofG}RP<0T< zh;zl4+8SG|xV9;|b^gZGBL7f(Y-7vdip`;oEusFbxV9^^Y-s(K`8!V75$f5pbkY3f z{=kZ%QR145L5NNDW{h(X%9eDLCn~d3ss}1fTN`&%NR?ZEvT_;I)-m&!h~tSM*?_3q z4YvC`Gk9s5d824;QyD5HJV6~QzfkjqDq!_yyAR6r2?}P*6>go=w=TV5aaY^%Ti5OA zjfdL zM6*Kz!^zu<-)=`yD)1cHF~6h9sXJzn>#*r$L07t|Ikq_-+SQWiU$<;)qUHDnE_Bii zQtsy+8f{OHqmk~4!`k)aHVkI*md=Fk!|92$Vax-GKvJjv-B2K5&ysz|$ZiSIyQF0# z-52%uhxN$L-c-wyO=~)P5V!0kT$XB9^%(Q2%D4A}Y^22N;^7*yld-L_O*DacluH?l zEv;KcnXcd}Z?0l7DRe_~&Qo5+QE?umCOY&Z`GR$0KtxZ9oWXZ@G+^SA_ z9d+qOYlKa?RHm_tHHx>&az>XxKI~lh2VlNLB-pPf@iWeNI7$zqnXj^^_YmVPoH)nN zmwa6!*+PeOz8=i^^y7C>oqhFqh&)EmQj1bgu0MGUv0{QTT!QmXK+a1yz6mBo34XoD zId|7COb9mLPF6j7Yq)1s>b@b~vwR{JlPcd<9_6n4vFG)w1|cqc2os0lw_yWOYJYE! zE8AUHb3(BBc4`;K-oc&_sy5@WYCo)zxavSVb+{|uZ69S=k=@HLwU0kaZnq}rf=r3Q zZ4`=Tg_Wuu7yZJ#qE)BIO@rc?S;?O>6PTS|ncrBX8oNqdH0;@IQtzddR@OPU(#n_x zCpN|`xr@dik98I4#$*gC^LWK3I;PZ0cUH{GW_lE3G6Ni?^ChsfvqZ;=Uq)Y8FRsL} z(~>wc_R8_8wGK0VXCEH#S{hBCXyiyF^+A_8#xW~l7QbiNbsHilnwp9463uX;iC}sF znS!#;!jq!1m$Ao8#zm{M=apMZGdFR#RW_W{uI^z5RZM?&7!`xtL2y}v!LXcyhhiNWOCr%yA+4)w%Pvl(}l!&yD}JxE8q^p08LjmzXSMy^!ulo2cp{;POah(pOe|fG z$Ez0B)34_0X;W99IHCp4U_3D*dIIK?6nl^#>&cX@eO>M8<%{$Uk@(id&Btxn+J8dN zf~?I{wk-88S*>sATHX{&#x}KeZ`crDGf0oJI{Z57ZBY|u9C_PIp_)eLA2JlFr|kVl z{D$9%F*eruv!1JqMuK_@o7B=5?imMokTqs=G2ub-IiWph-Lz@b9%tf2+3nxfqMkZ5 z&fBxkCjzU2vx!l4s=awzyJcSMIk9y6KiBQar1fI0wN-)Ug_f_$GNn@DtwrvqD5#&#&#Z>pKhV^)6bha-P?~cZYtRT8E zHMC*Fg04tl!Io4sGuWDGOl^u~h1qy_Y;C4Fm|EH#@7xk=?Mxj%vXg!f{NJD-`7@vY z21(Qp3ja%FQ6V>LL_|wFaab{phG>CO+4K@|YOVS##=<52PC5`05!-pc2C-#8kzv~PjhkZOj#R2|XKU6RQ@nfEKxF<28@C4ATQiwff1)$pzxnMv#{`v_z1p{P zy^;_r_$jY|JbQe0r5rk?t>*D_Yi$p3;1= z=nVJ767|0^CSf|(nvPyJXK{RSOW%rUq$RdPpPz2o9ElB-X|j1@Rpo47x#yuLsC+6i z*`^F~rl=Q{@lj;EVtvlOPU@$v;cj+u%lyVH0nFxsW;%^P=DIo7bM_|M(8 zomeKp3N1_QE@7=fdc}lw@uR$PsIrt_)0LqZ<(pzSX#I|0tdy0DSFXBAKf9xnUHoEf ztV%*8DCuYLK-g+TuWd zLz&8JDXre&nw{a6txMNzIAL)|bMpAkO~Lhx29xv{uT!)yNAaWELAgrF_0k5rT(+Lb zSjw~4^%(NoIm!C1{1|9a04%ou>exwEs;X40tSZQCF2}%Xxiwj|r5Xc&?whA^5?9JB zvqFkC3}a(ih3Vp(w~UXr(#DuZ_@foQCKBsMz1kmQag)Ugyemewt{B{~PM^Oev|-)m zUH!+cUo>ZD)}z1cyO%Y`8h3WJM|;=%gMAw_OOM+S8tcH_4ReM(rps4aaa541Bx{Y{ z4X34q-rVOc-O$GiW^HTKZgQv$R>N=pB5*>D#Vc8-)-17$QhXu&XxTT$${E8#kef<` z16VTb7B|Q)PC0`M_2oYT;!?n@xxpQoRa<&DCf23q3~p{)Ft}{Zy6(*@=}}du=L3r^ zwYg_tx2V>b3$%19J=%q66x&@SE4>Pq4&u@95?c+Cr{dgHwnpr7k1gmMT98%fW>&4# zz#>^xQCzHrk7mz#Jmt*6?*4rjDrzQHsB%%v*rL0tgbIXrNqYCi_rm2_!Ki{FW<~g& zJM^K{`sG^_UF%m2w5(ncK7NkAX{^5=bLA0;Z{uEuDX}0+t*&FdsGq{)B)TfLUo^K< zbhpqjdOkDCj5J_Ku}chnpp~Q8JBU1TJzbp0kWt{p3(hT8?TJcLBTy!E}}*0#EJ`u$vnKv%Vm^s zNIZ&>7RjY}Gu_i(3(Cih{8k#lvOT5cp=Mdm4w#k1E$g}NGg|4+25%KlXJR>cO(``Z%hX0O_f{lUiFoDv3xLtNSbxhn~4Qm$4$UAZcGfy zInY@Rh>3f0tN+iO1B$R+&9~zb}wU$HQ`Tvbh17ZO9gs$^^P^`UT-uIu0PqQlHwKbA=3~m zUQ&J~?v^4|z1TZ{h64P9foO*Igj5P4{rE^LqTNW(aORUw?een=I1C-?mR%+`};Lxf~yAr8H^YOint>K-qU`uFCG$J4Lt(>En zCMjjgx08xS7OPNYCQ5n2Ic<8jUstANzvh!^cfTvka;;iCp<`EQ$RFA{r*~;b)3S)Z zXhAR?Jx)AJnK%ES1So54v2-eC)J%`4w8LBFs=73b-VHXiD3ywYV}YKiu9RxCAnT^s zcU#Q_?N+w-h-X=XQ}L=^ifX8cb6M2(l$%)`n6|Zh%-SkAzaIAl2dXMfHSAVf-g90= zCFj?`ZWrF}O=cDBZac`Tg*{PtensplF7|{wEFDh7lX%}=foQ63W!2DbFj7@+e#PwO z!c~0~(M^$dMv1YfS{YUTZuV?dY<}(S9=EcWuPV?~!EQ95J>@lftZh+>R}ADSfA`jZ zmi~8t(Ra zDN|L9X??A2jV+<}=Jw_V!Iogpg4X5*?F)jfJuU5Rt@9g$p+I9xdrwDOb4Oc8b4zb; zM@Mg4b6aaidwWN_KEJn7U*KC5#mtj60l2cSeN$lRuHksJW#{or+XrHawc$4VG;Vnc z%-3J}=qeqFDeJN+O4)fC2^vp(j9+qd_Hbj|s>$VZlB+g!wQg9yq`h^)Nb3n*ty?3_ z@@MgKDz~iVR-!^}Vi9>&uc{_g6xxVo&$88uZ*e4@DlfGPsD_x#R!O;RsWTAk)_0aq z0g_$?RA3Rd?4rC%z%zbjCRi~wG*!qL`U0^fkz^V5APH7N4;E!*4_Ql=ol2DK9*lXp zY&9JpNCc9!l2b;lnKUb*xGdsIcjaV0HmE1l5oiRhQ~5k9ZX;U}wdBw*zh;)=^_t~V z2vMtm9u!$6ewI^}x8=ohbJVuwchCAo$M+`N{2gmrHg#`V-PwIy&!Vof_FqJS70{VU zx1iFx#g7W4usP$YtXt)ZNTMX!%P*VvSu`u9KEW^%S2`@8rwHa3kF1P^^kJ*Jykpg- zfsGwY1F>KvHne1Cdvx)f^kDhT%_z50YO|5I6epX!Z71Sk!LnFqoj)Hg$P$xH>!kO1ESwq&BEjpenULFz6ZaUDYJZK9Cis87bB$Xr-;Tqx2@tD*3Jj_hklp^nh;cU_^ca67q&LWxU;H(C8AB(CRX})yYWZcCkG@;u zgU+_h;FjZ?R_|K7xP8+=$EG=*JuTs~jt^vs70{7ISgA?GS=CoDB+VI%s;lLStCHU0 zk@5~0>{KhJImtAUSNbboK$HTMv7I&uR!kW#+H1*zbBk@>FPCkV*QV6?u*^dTMYjAl zv@SCs#wF#mN=2~(R%9n#5ml74yANh7(&IPjRzw5RzshP2l4NDf!J=IeZLH6fan=hJ z2YQv!gF(3bN+_!aDkFX+WiMmn3N|Y-3RYw*Q1w`Q>&355Xan3Rrn}2F5wbp;a0NV~ zKna$9xxb*s^FmP{zQaq!DB*s4bIzuC8DO0kKn2xjJ1+qLt-bRj?7Q zppw8cQO{Ko)<KV>Lcgn3zEL&DcpNet?HEJ)Sh)y>dGSXs5VcptOB=u-u z#GmL7`}@-A1fGlwVdDX8|JSW2(XE^9TOqD2!iI_I5|YmAwH`n!J)08FUCz#E#5Hp= zZRy-)+{lohwJ3-26kd~-Tp8<)7nj7A&YcK3 z8C>yh%TttT`4m%ehK)4tn6)KVj234g@AX_ICU?nJUNMv}pF_3Jb!Q;hr`KEiUat+L z`s^ z#deipVSQIRxp*Xv0?ZOu1X6wCths4n{l;aqYa!k1SXeKf50!awCYC?4FVSMVd4K87 zOdy)K8{+cLC4qFHzQG{bjSUk)oQeV`8{-@MA}M)+Br(Q<7W0x-nVAyEX>4f6QVuQo zD3YWoL0u{aKVMmfeQL#XboD1&m9O;rzna)HHBrE?{#PaNus!G;_K;<@T>q<;JzFbg z$UF%O(Ft;u1x$4)-vh zrB~ksV7O$<*~2`$3mvd<$tOi6@nY>Ix_BYd!g_HfrVppZQSNM!nZC0x5Q!~~rcX3- zU?Z?WmpSTBF4bhsQg<(xV>Q@4$nF@;bq{fD8tb+u`n4*aU6OB0a=M;hAvqZ-L7ob7 z(pbcsokudMP`^J-Z=&7j!u3cSjP@GKp3FiFz zK#v{@=^>vuO{y?O@mw|CSg*nRgRGa=I_S;fKYy&6k_~o)o$NRXrlOIcp7KfNI-~KJ zzHOVEaJ(iG>!0hW6iWz7XK?KzwLS

7nB?dPd*oBE6tKRuPsC;#meaGKhqg6GD== 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%0DVkWjcGDvv)Wb&%p=@v(xEM=kmZQ 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 Date: Mon, 30 Mar 2026 09:16:14 -0400 Subject: [PATCH 25/36] fix: remove unused import --- .../jobdispatch/jobagents/argo-workflow/workflow.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 9faab36d6..1eef5f3f3 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -6,15 +6,14 @@ import ( "fmt" "regexp" "strings" + "workspace-engine/pkg/oapi" + "workspace-engine/pkg/templatefuncs" + "workspace-engine/svc/controllers/jobdispatch/jobagents/types" wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/templatefuncs" - "workspace-engine/svc/controllers/jobdispatch/jobagents/types" ) var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") From 45c23c794230bddd5c25dc96b57d7a58de5da796 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:22:18 -0400 Subject: [PATCH 26/36] docs: update example env file --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index 138436c21..a2e269ef7 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,4 @@ AUTH_SECRET='d0c1b54c50ccd3c89ee37e9c041f91748d361b09f8fd3b7fe542779c0f3f0983' AUTH_TRUST_HOST=false VARIABLES_AES_256_KEY=0000000000000000000000000000000000000000000000000000000000000000 +ARGO_WORKFLOW_INSECURE_SKIP_VERIFY=true From 646d3e2c9fb91ddfb26879efcad2ffe3238ec066 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 09:31:23 -0400 Subject: [PATCH 27/36] fix: lint issues --- .../jobdispatch/jobagents/argo-workflow/workflow.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go index 1eef5f3f3..deb41d072 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go @@ -6,14 +6,14 @@ import ( "fmt" "regexp" "strings" - "workspace-engine/pkg/oapi" - "workspace-engine/pkg/templatefuncs" - "workspace-engine/svc/controllers/jobdispatch/jobagents/types" wfv1 "github.com/argoproj/argo-workflows/v4/pkg/apis/workflow/v1alpha1" "github.com/goccy/go-yaml" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" + "workspace-engine/pkg/oapi" + "workspace-engine/pkg/templatefuncs" + "workspace-engine/svc/controllers/jobdispatch/jobagents/types" ) var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") From 3c3c7757da1845a0e247532abf135d2ea445902f Mon Sep 17 00:00:00 2001 From: Mike Leone <2907207+mleonidas@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:01:43 -0400 Subject: [PATCH 28/36] Update apps/api/src/routes/argoworkflow/workflow.ts Co-authored-by: Justin Brooks Signed-off-by: Mike Leone <2907207+mleonidas@users.noreply.github.com> --- apps/api/src/routes/argoworkflow/workflow.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index da0638fc4..507b60af9 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -26,12 +26,7 @@ const statusMap: Record = { export const mapTriggerToStatus = (trigger: string): JobStatus | null => statusMap[trigger] ?? null; -export const getJobId = (payload: ArgoWorkflowPayload): string => { - if (payload.jobId != null && payload.jobId !== "") { - return payload.jobId; - } - return payload.workflowName; -}; +export const getJobId = (payload: ArgoWorkflowPayload) => payload.jobId || payload.workflowName; export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { const { From 1198ea7195a6c15654b393e628458f7e999d0eff Mon Sep 17 00:00:00 2001 From: Mike Leone <2907207+mleonidas@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:02:59 -0400 Subject: [PATCH 29/36] Update apps/api/src/routes/argoworkflow/workflow.ts Co-authored-by: Justin Brooks Signed-off-by: Mike Leone <2907207+mleonidas@users.noreply.github.com> --- apps/api/src/routes/argoworkflow/workflow.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index 507b60af9..6ca37f193 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -30,7 +30,6 @@ export const getJobId = (payload: ArgoWorkflowPayload) => payload.jobId || paylo export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { const { - workflowName: _workflowName, uid, phase, startedAt, From 683860eb11a3bbea2f5d37bf35a3c13e0a0f7210 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 13:05:59 -0400 Subject: [PATCH 30/36] fix: package naming --- .../svc/controllers/jobdispatch/controller.go | 8 +++++--- .../{argo-workflow => argoworkflows}/workflow.go | 2 +- .../workflow_submitter.go | 2 +- .../{argo-workflow => argoworkflows}/workflow_test.go | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) rename apps/workspace-engine/svc/controllers/jobdispatch/jobagents/{argo-workflow => argoworkflows}/workflow.go (99%) rename apps/workspace-engine/svc/controllers/jobdispatch/jobagents/{argo-workflow => argoworkflows}/workflow_submitter.go (99%) rename apps/workspace-engine/svc/controllers/jobdispatch/jobagents/{argo-workflow => argoworkflows}/workflow_test.go (99%) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go index e6eba779e..a6238e894 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go @@ -17,14 +17,16 @@ import ( "workspace-engine/pkg/reconcile/postgres" "workspace-engine/svc/controllers/jobdispatch/jobagents" "workspace-engine/svc/controllers/jobdispatch/jobagents/argo" - argoworkflow "workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow" + argoworkflow "workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows" "workspace-engine/svc/controllers/jobdispatch/jobagents/github" "workspace-engine/svc/controllers/jobdispatch/jobagents/terraformcloud" "workspace-engine/svc/controllers/jobdispatch/jobagents/testrunner" ) -var tracer = otel.Tracer("workspace-engine/svc/controllers/jobdispatch") -var _ reconcile.Processor = (*Controller)(nil) +var ( + tracer = otel.Tracer("workspace-engine/svc/controllers/jobdispatch") + _ reconcile.Processor = (*Controller)(nil) +) type Controller struct { getter Getter diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go similarity index 99% rename from apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go rename to apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go index deb41d072..ab26c6b54 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go @@ -1,4 +1,4 @@ -package argo_workflows +package argoworkflows import ( "bytes" diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go similarity index 99% rename from apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go rename to apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go index bca68002c..231e34add 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go @@ -1,4 +1,4 @@ -package argo_workflows +package argoworkflows import ( "context" diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go similarity index 99% rename from apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go rename to apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go index dd6f9d88f..ffa4fcfb1 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go @@ -1,4 +1,4 @@ -package argo_workflows_test +package argoworkflows_test import ( "context" @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "workspace-engine/pkg/oapi" - argo_workflows "workspace-engine/svc/controllers/jobdispatch/jobagents/argo-workflow" + argo_workflows "workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows" ) // ----- Mocks ----- From 59f85f33e5d8bf2a7f5ba0787b8b4665481c30ac Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 14:00:01 -0400 Subject: [PATCH 31/36] feat: route based on jobAgent ID * allow for multi argo-workflow clusters, routes by job-agent id and pulls the secret from the agentConfig --- apps/api/src/routes/argoworkflow/index.ts | 36 +++++++++++++++----- apps/api/src/routes/argoworkflow/workflow.ts | 10 ++---- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts index d93b7fa92..f0e0d8b3e 100644 --- a/apps/api/src/routes/argoworkflow/index.ts +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -1,23 +1,41 @@ import type { Request, Response } from "express"; -import { env } from "@/config.js"; import { asyncHandler } from "@/types/api.js"; import { Router } from "express"; +import { eq } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import * as schema from "@ctrlplane/db/schema"; + import { handleArgoWorkflow } from "./workflow.js"; export const createArgoWorkflowRouter = (): Router => - Router().post("/webhook", asyncHandler(handleWebhookRequest)); + Router().post("/:id/webhook", asyncHandler(handleWebhookRequest)); -const verifyRequest = (req: Request): boolean => { - const authHeader = req.headers.authorization?.toString(); - if (authHeader == null) return false; - const secret = env.ARGO_WORKFLOW_WEBHOOK_SECRET; - return authHeader === secret; +const getJobAgent = async (id: string) => { + return db.query.jobAgent.findFirst({ + where: eq(schema.jobAgent.id, id), + }); }; const handleWebhookRequest = async (req: Request, res: Response) => { - const isVerified = verifyRequest(req); - if (!isVerified) { + const { id } = req.params; + + const agent = await getJobAgent(id); + if (agent == null) { + res.status(404).json({ message: "Job agent not found" }); + return; + } + + const config = agent.config as Record; + const webhookSecret = + typeof config.webhookSecret === "string" ? config.webhookSecret : null; + if (webhookSecret == null) { + res.status(500).json({ message: "Job agent has no webhookSecret configured" }); + return; + } + + const authHeader = req.headers.authorization?.toString(); + if (authHeader == null || authHeader !== webhookSecret) { res.status(401).json({ message: "Unauthorized" }); return; } diff --git a/apps/api/src/routes/argoworkflow/workflow.ts b/apps/api/src/routes/argoworkflow/workflow.ts index 6ca37f193..08c392055 100644 --- a/apps/api/src/routes/argoworkflow/workflow.ts +++ b/apps/api/src/routes/argoworkflow/workflow.ts @@ -26,15 +26,11 @@ const statusMap: Record = { export const mapTriggerToStatus = (trigger: string): JobStatus | null => statusMap[trigger] ?? null; -export const getJobId = (payload: ArgoWorkflowPayload) => payload.jobId || payload.workflowName; +export const getJobId = (payload: ArgoWorkflowPayload) => + payload.jobId ?? payload.workflowName; export const handleArgoWorkflow = async (payload: ArgoWorkflowPayload) => { - const { - uid, - phase, - startedAt, - finishedAt, - } = payload; + const { uid, phase, startedAt, finishedAt } = payload; const jobId = getJobId(payload); From 55b10281423f89d588721bf935555ed66fabbd41 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 14:01:35 -0400 Subject: [PATCH 32/36] fix: update docs to reflect new webhook --- docs/integrations/job-agents/argo-workflows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/job-agents/argo-workflows.mdx b/docs/integrations/job-agents/argo-workflows.mdx index c02860b7c..a197e96c7 100644 --- a/docs/integrations/job-agents/argo-workflows.mdx +++ b/docs/integrations/job-agents/argo-workflows.mdx @@ -339,7 +339,7 @@ spec: name: send-webhook conditions: "workflow-event" http: - url: https:///api/argo/webhook + url: https:///api/argo//webhook method: POST headers: Content-Type: application/json From 0b7731420dc11b54b66d2fe3aef2227f43791094 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 14:13:59 -0400 Subject: [PATCH 33/36] fix: eslint error --- apps/api/src/routes/argoworkflow/index.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/argoworkflow/index.ts b/apps/api/src/routes/argoworkflow/index.ts index f0e0d8b3e..d211eeec0 100644 --- a/apps/api/src/routes/argoworkflow/index.ts +++ b/apps/api/src/routes/argoworkflow/index.ts @@ -19,6 +19,10 @@ const getJobAgent = async (id: string) => { const handleWebhookRequest = async (req: Request, res: Response) => { const { id } = req.params; + if (id == null) { + res.status(400).json({ message: "Missing job agent id" }); + return; + } const agent = await getJobAgent(id); if (agent == null) { @@ -30,7 +34,9 @@ const handleWebhookRequest = async (req: Request, res: Response) => { const webhookSecret = typeof config.webhookSecret === "string" ? config.webhookSecret : null; if (webhookSecret == null) { - res.status(500).json({ message: "Job agent has no webhookSecret configured" }); + res + .status(500) + .json({ message: "Job agent has no webhookSecret configured" }); return; } From 5071eb5c51750606a24072905d240f011c6733ac Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 14:41:04 -0400 Subject: [PATCH 34/36] feat: update oapi spec --- apps/workspace-engine/oapi/openapi.json | 13 ++++++++++++- .../oapi/spec/schemas/jobs.jsonnet | 4 +++- apps/workspace-engine/pkg/config/env.go | 3 --- apps/workspace-engine/pkg/oapi/oapi.gen.go | 6 ++++++ .../svc/controllers/jobdispatch/controller.go | 4 +--- .../jobagents/argoworkflows/workflow.go | 18 ++++++++++++++---- .../argoworkflows/workflow_submitter.go | 7 +++---- .../jobagents/argoworkflows/workflow_test.go | 1 + 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/apps/workspace-engine/oapi/openapi.json b/apps/workspace-engine/oapi/openapi.json index c257cdfa6..e02b0dac6 100644 --- a/apps/workspace-engine/oapi/openapi.json +++ b/apps/workspace-engine/oapi/openapi.json @@ -60,6 +60,11 @@ "description": "ArgoWorkflow API token.", "type": "string" }, + "httpInsecure": { + "default": false, + "description": "ArgoWorkClient http(s) connection configuration setting", + "type": "boolean" + }, "name": { "description": "ArgoWorkflow job name", "type": "string" @@ -71,13 +76,19 @@ "template": { "description": "WorkflowTemplate name.", "type": "string" + }, + "webhookSecret": { + "description": "ArgoEvents webhookSecret", + "type": "string" } }, "required": [ "serverUrl", "apiKey", "template", - "name" + "name", + "webhookSecret", + "httpInsecure" ], "type": "object" }, diff --git a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet index 4896fa34f..667b1fe1b 100644 --- a/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet +++ b/apps/workspace-engine/oapi/spec/schemas/jobs.jsonnet @@ -163,12 +163,14 @@ local JobPropertyKeys = std.objectFields(Job.properties); ArgoWorkflowJobAgentConfig: { type: 'object', description: 'WorkflowTemplate reference execution', - required: ['serverUrl', 'apiKey', 'template', 'name'], + required: ['serverUrl', 'apiKey', 'template', 'name', 'webhookSecret', 'httpInsecure'], properties: { name: { type: 'string', description: 'ArgoWorkflow job name' }, serverUrl: { type: 'string', description: 'ArgoWorkflow server address (host[:port] or URL).' }, apiKey: { type: 'string', description: 'ArgoWorkflow API token.' }, template: { type: 'string', description: 'WorkflowTemplate name.' }, + webhookSecret: { type: 'string', description: 'ArgoEvents webhookSecret' }, + httpInsecure: { type: 'boolean', default: false, description: 'ArgoWorkClient http(s) connection configuration setting' }, }, }, TestRunnerJobAgentConfig: { diff --git a/apps/workspace-engine/pkg/config/env.go b/apps/workspace-engine/pkg/config/env.go index 78859206a..d4869a351 100644 --- a/apps/workspace-engine/pkg/config/env.go +++ b/apps/workspace-engine/pkg/config/env.go @@ -49,9 +49,6 @@ type Config struct { // Whether to enable dry run for workflow jobs. DryRunEnabled bool `default:"false" envconfig:"DRY_RUN_ENABLED"` - - // Whether to skip TLS verification when connecting to the Argo Workflows server. - ArgoWorkflowInsecureSkipVerify bool `default:"false" envconfig:"ARGO_WORKFLOW_INSECURE_SKIP_VERIFY"` } // GetMaxConcurrency returns the max concurrency for a given service kind. diff --git a/apps/workspace-engine/pkg/oapi/oapi.gen.go b/apps/workspace-engine/pkg/oapi/oapi.gen.go index 0fa56a800..9fd698af2 100644 --- a/apps/workspace-engine/pkg/oapi/oapi.gen.go +++ b/apps/workspace-engine/pkg/oapi/oapi.gen.go @@ -232,6 +232,9 @@ type ArgoWorkflowJobAgentConfig struct { // ApiKey ArgoWorkflow API token. ApiKey string `json:"apiKey"` + // HttpInsecure ArgoWorkClient http(s) connection configuration setting + HttpInsecure bool `json:"httpInsecure"` + // Name ArgoWorkflow job name Name string `json:"name"` @@ -240,6 +243,9 @@ type ArgoWorkflowJobAgentConfig struct { // Template WorkflowTemplate name. Template string `json:"template"` + + // WebhookSecret ArgoEvents webhookSecret + WebhookSecret string `json:"webhookSecret"` } // BasicResource defines model for BasicResource. diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go index a6238e894..e22fb27b9 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/controller.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/controller.go @@ -137,9 +137,7 @@ func New(workerID string, pgxPool *pgxpool.Pool) *reconcile.Worker { dispatcher.Register(terraformcloud.New(pgSetter)) dispatcher.Register( argoworkflow.New( - &argoworkflow.GoWorkflowSubmitter{ - InsecureSkipVerify: config.Global.ArgoWorkflowInsecureSkipVerify, - }, + &argoworkflow.GoWorkflowSubmitter{}, pgSetter, ), ) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go index ab26c6b54..0f2d82442 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go @@ -21,10 +21,11 @@ var tracer = otel.Tracer("workspace-engine/jobagents/argo-workflow") var _ types.Dispatchable = (*ArgoWorkflow)(nil) type WorkFlowJobAgentConfig struct { - ServerAddr string - ApiKey string - Template string - Name string + ServerAddr string + ApiKey string + Template string + Name string + InsecureSkipVerify bool } type Getter interface { @@ -52,6 +53,7 @@ type WorkflowSubmitter interface { SubmitWorkflow( ctx context.Context, serverAddr, apiKey string, + insecureSkipVerify bool, wf *wfv1.Workflow, ) (*wfv1.Workflow, error) } @@ -107,6 +109,7 @@ func (a *ArgoWorkflow) Dispatch(ctx context.Context, job *oapi.Job) error { asyncCtx, wfConfig.ServerAddr, wfConfig.ApiKey, + wfConfig.InsecureSkipVerify, wf, ) if err != nil { @@ -151,6 +154,13 @@ func ParseJobAgentConfig( if serverAddr == "" || template == "" || name == "" { return wfT, fmt.Errorf("missing required fields in job agent config") } + + if v, ok := config["httpInsecure"].(bool); ok { + wfT.InsecureSkipVerify = v + } + + fmt.Printf("parsed: %+v\n", wfT) + return wfT, nil } diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go index 231e34add..5557317ff 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_submitter.go @@ -16,13 +16,12 @@ import ( // GoWorkflowSubmitter is the production implementation of WorkflowSubmitter // that calls the Argo Workflows REST API. -type GoWorkflowSubmitter struct { - InsecureSkipVerify bool -} +type GoWorkflowSubmitter struct{} func (s *GoWorkflowSubmitter) SubmitWorkflow( ctx context.Context, serverAddr, apiKey string, + insecureSkipVerify bool, wf *wfv1.Workflow, ) (*wfv1.Workflow, error) { ctx, apiClient, err := argoapiclient.NewClientFromOptsWithContext(ctx, argoapiclient.Opts{ @@ -30,7 +29,7 @@ func (s *GoWorkflowSubmitter) SubmitWorkflow( URL: serverAddr, Secure: true, HTTP1: true, - InsecureSkipVerify: s.InsecureSkipVerify, + InsecureSkipVerify: insecureSkipVerify, }, AuthSupplier: func() string { return apiKey diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go index ffa4fcfb1..e3fcda581 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow_test.go @@ -33,6 +33,7 @@ type submitCall struct { func (m *mockSubmitter) SubmitWorkflow( _ context.Context, serverAddr, apiKey string, + _ bool, wf *wfv1.Workflow, ) (*wfv1.Workflow, error) { m.mu.Lock() From a8c42d0a866f38b4dd1d2e72044c70465c9b44f8 Mon Sep 17 00:00:00 2001 From: Mike Leone <2907207+mleonidas@users.noreply.github.com> Date: Mon, 30 Mar 2026 14:43:33 -0400 Subject: [PATCH 35/36] Update docs/integrations/job-agents/argo-workflows.mdx Co-authored-by: James Singleton Signed-off-by: Mike Leone <2907207+mleonidas@users.noreply.github.com> --- docs/integrations/job-agents/argo-workflows.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/job-agents/argo-workflows.mdx b/docs/integrations/job-agents/argo-workflows.mdx index a197e96c7..0a5a22ac6 100644 --- a/docs/integrations/job-agents/argo-workflows.mdx +++ b/docs/integrations/job-agents/argo-workflows.mdx @@ -21,7 +21,7 @@ sequenceDiagram ``` 1. Ctrlplane creates a job and dispatches it to ArgoWorkflows -2. ArgoEvents triggers your workflow with +2. ArgoEvents triggers your workflow 3. Your workflow fetches job context (version, environment, resource) 4. Your workflow executes the deployment 5. ArgoWorkflows sends a webhook event to Ctrlplane as the run progresses From 3d7e0a93a0527e8384460ee6560f61f54bf4ff66 Mon Sep 17 00:00:00 2001 From: Michael Leone Date: Mon, 30 Mar 2026 15:05:48 -0400 Subject: [PATCH 36/36] fix: remove print statement --- .../jobdispatch/jobagents/argoworkflows/workflow.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go index 0f2d82442..426776efb 100644 --- a/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go +++ b/apps/workspace-engine/svc/controllers/jobdispatch/jobagents/argoworkflows/workflow.go @@ -158,9 +158,6 @@ func ParseJobAgentConfig( if v, ok := config["httpInsecure"].(bool); ok { wfT.InsecureSkipVerify = v } - - fmt.Printf("parsed: %+v\n", wfT) - return wfT, nil }