If icon
Set icon
OutputValues icon
Log icon
Get icon
Labels icon
SlackIncomingWebhook icon
Pause icon
Fail icon
Request icon

Deployment request form with dependent inputs and KV-driven dropdowns

Kestra input form with dependsOn, KV-fed SELECT and MULTISELECT, isRadio, prefill, ARRAY and SECRET inputs, plus approval, KV record and deployer webhook.

Categories
CoreInfrastructure

A deployment request form written as a flow: the questions appear in order, every dropdown shows only the options that fit the previous answers, and the options live in the KV store so the platform team can change them without editing the flow. The answers are validated again on the server, recorded, labelled on the execution, held for an approval when the policy says so, and posted to a deployer webhook (a CI job, Argo CD, a GitHub repository_dispatch endpoint). The sample points the webhook at an echo service so the flow runs out of the box.

It is also a worked example of the Kestra 2.0 form features: dependsOn with inputs and condition, expression reading kv(), isRadio, prefill, allowCustomValue, MULTISELECT, ARRAY with itemType, SECRET with a validator, label/value options and flow checks.

How it works

  1. action (isRadio: true) decides what the form shows: request reveals the request questions, seed_catalog only catalog, a JSON input with a sample catalog as default.
  2. environment is a radio with label/value options (the UI shows Production, the flow receives production).
  3. region is a SELECT whose expression is kv('deploy_form_regions')[inputs.environment]: the KV entry maps each environment to its regions, so the dropdown changes with the radio. dependsOn.inputs: [action, environment] delays rendering until both have values.
  4. services is a MULTISELECT fed by kv('deploy_form_services'). allowCustomValue: true lets the requester type a service that is not catalogued yet; the flow detects it and routes the request to an approver.
  5. version has a prefill (pre-typed in the form, but not a default: API callers must send it) and a semantic-version validator.
  6. strategy (radio) is asked only for staging and production, canary_percent (INT with min/max) only for a canary. notify_channels is an ARRAY of strings. use_private_registry (BOOL) reveals registry_token, a SECRET input with its own validator.
  7. Flow checks block a run before it exists: an empty service list, a pre-release tag going to production, a production canary above 25%, a malformed catalog.
  8. On request: review lists the services missing from the catalog, build_record writes the request as JSON and decides needs_approval (production, or any custom service), Labels tags the execution and record_pending stores the record in KV with a 30-day ttl.
  9. When an approval is needed, a Slack message links to the execution and wait_for_approval pauses for up to 4 hours (behavior: FAIL). The approver resumes with approve and comment; a rejection updates the record, labels the execution and fails the run with the comment.
  10. submit posts the record to the deployer webhook (request id in a header, registry token as bearer token, three attempts with backoff); record_submitted stores the HTTP code and the approval mode (manual or policy).
  11. errors reads errorLogs() once, tells an expired approval apart from a technical failure (label deploy_approval: expired), writes the reason into the KV record and alerts Slack. Rejections are decisions, not failures, so afterExecution reports them instead. finally logs where the record is.

What you get

  • Dropdowns that are data: change the two KV entries (or run seed_catalog with a new catalog) and the next form render shows the new options, with no new flow revision.
  • Validation API callers cannot bypass: a region outside the rendered list, a bad version tag or a value for a hidden input are refused with a 422 before the execution exists.
  • One KV record per request (deploy_request_<execution id>) with status pending, submitted, rejected, expired or failed, plus labels deploy_environment, deploy_region, deploy_version and deploy_approval to filter executions.

Who it's for

Platform and release engineers who want a self-service deployment request without building a web form, and anyone writing an input-heavy flow.

Why orchestrate this with Kestra

The form, the policy and the hand-off are one versioned file. Kestra renders the form, validates every submission the same way from the UI, the API or another flow, keeps the pause state for the approver, retries the deployer call and leaves an execution per request with labels and a KV record.

Prerequisites

  • Kestra 2.0 (tested on 2.0.5 OSS) with the Slack plugin.
  • KV entries deploy_form_regions and deploy_form_services in the flow's namespace: run the flow once with action: seed_catalog to create them.
  • kestra.encryption.secret-key in the server configuration if anyone turns on use_private_registry: without it the API answers 422 as soon as a SECRET input gets a value. The rest of the form works without it.

Secrets

  • SLACK_WEBHOOK_URL: approval requests, failure alerts and outcome messages (optional, every Slack task has allowFailure: true and the same text is logged).

Quick start

  1. Import the flow, click Execute, pick Seed or replace the catalog in KV and run it (edit the catalog JSON first if you already know your environments, regions and services).
  2. Click Execute again: pick an environment and watch the region list change, pick a region and the service list appears, type an unlisted service to see the approval route.
  3. Run a dev request: it is auto-approved and posted to the sample webhook. Open the KV store to see deploy_request_<id>.
  4. Run a production request and approve or reject it from the execution page (or POST .../executions/<id>/actions/resume with approve and comment).
  5. Replace deployer_webhook in variables with your deployer's endpoint and add SLACK_WEBHOOK_URL.

How to extend

  • Make the service list environment-specific like the regions: store {"dev": [...], "production": [...]} and use kv('deploy_form_services')[inputs.environment].
  • Replace kv() with http() or subflow() in an expression to list services from a service catalog or clusters from a cloud account.
  • Group the request inputs under a FORM input for a multi-step wizard; dependsOn.inputs then takes the dotted path (request.environment).

Notes from testing on 2.0.5

  • A value sent for a hidden input (dependsOn.condition false) is dropped; the input renders with its default or null, so hidden inputs without a default are read as inputs.x ?? ....
  • A Schedule trigger that sets only action is refused: trigger validation asks for every required input regardless of dependsOn, so the catalog refresh is manual.
  • A failing submit ran as two task runs of three attempts each on our single-node install, so the deployer should treat X-Deploy-Request-Id as an idempotency key.
  • The 5-hour sla fails the run and sets deploy_form_sla: breached. The quick errors tasks (labels, KV record) finished, but the Slack alert, finally and afterExecution were killed with the run; the labels and the KV record are the reliable trace.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.