List icon
Export icon
ArchiveDecompress icon
Loop icon
Request icon
OutputValues icon
Log icon
If icon
Set icon
GetKeys icon
Pause icon
Get icon
Toggle icon
Sleep icon
Fail icon
SlackIncomingWebhook icon
Schedule icon

Gate a Kestra upgrade against every live flow

Validate live Kestra flows against a candidate Kestra version and quarantine triggers for new invalid flows before upgrade.

Categories
CoreInfrastructure

Safety

A Kestra upgrade can leave a deployed flow accepted by the current server but rejected when its next trigger runs. This gate makes the candidate validation result explicit before the rollout and disables only triggers on flows that become invalid on the candidate.

Guard What it stops
Current versus candidate validation Existing problems are not misreported as upgrade regressions.
Candidate constraint and replacement detection A missing plugin type or invalid constraint is classified as INVALID.
Pause after the report Trigger writes require a human decision and a fresh state read.
KV rollback records Every flow whose triggers are disabled has the trigger ids that were enabled recorded. The record holds no credential.
Post-write trigger read The run fails if any enabled trigger is still enabled after Toggle.
Flow-level queue and candidate revalidation Two executions never write together and a flow fixed during the Pause is revalidated and skipped.

How it works

  1. inventory (io.kestra.plugin.kestra.flows.List) reads the live namespace and export (io.kestra.plugin.kestra.flows.Export) stores a complete source snapshot in Kestra internal storage.
  2. scan (io.kestra.plugin.core.flow.Loop) sends each flow representation to the current and candidate /flows/validate endpoints with io.kestra.plugin.core.http.Request.
  3. facts and classify (io.kestra.plugin.core.output.OutputValues) subtract current deprecation paths and warnings from candidate results. A candidate constraint that differs from the current constraint or a replacement notice, is INVALID; a new deprecation is NEW_DEPRECATION; a new warning is NEW_WARNING; all else is UNCHANGED.
  4. Each INVALID result is recorded with io.kestra.plugin.core.kv.Set. The dry-run report is emitted as one table row per flow in the execution log.
  5. When auto_remediate is true and dry_run is false, approval (io.kestra.plugin.core.flow.Pause) holds the finding keys. After the approval each flow is handled on its own: recheck_source and recheck_candidate read its source again and validate it on the candidate and a flow that no longer has a candidate constraint or replacement notice is skipped. prewrite_inventory reads the runtime trigger state and the write goes ahead only when a trigger is enabled both at runtime and in the flow source. save_original stores the enabled trigger ids in KV, io.kestra.plugin.kestra.triggers.Toggle disables the flow's triggers and after a short wait verify_inventory fails the run if an enabled trigger remains. The trigger reads use core.http.Request because the Kestra flow inventory task exposes source triggers but not the persisted runtime flag. The flow runs one execution at a time (concurrency limit 1 with QUEUE), so two runs never write together.

What you get

A per-flow classification table, candidate-only deprecation and warning deltas and auditable KV records for every trigger quarantined before an upgrade.

Who it's for

Platform teams that run Kestra in production and need a pre-upgrade decision with a bounded, reversible quarantine action.

Why orchestrate this with Kestra

The same instance that owns the live flows can export them, validate them through both Kestra APIs, pause for an operator and record every trigger change in its execution history. The plugin tasks keep selection and writes tied to Kestra identifiers instead of a separately maintained inventory script.

Tested end to end

Kestra 2.0.6 ran the gate. A Kestra 2.0.5 server held the live flows and a second Kestra 2.0.6 server was the candidate, all on disposable containers. The fixture holds a healthy control, a flow that uses a task type the candidate no longer ships, a flow whose dbt Cloud task uses two properties the candidate newly deprecates and an invalid flow whose only trigger is already disabled in the flow source. Trigger state was read from the current server's own trigger API after every case, separately from the flow's reads.

The approved run disabled exactly one trigger and left the other three flows alone. The password is a SECRET input; it was absent from every execution, child execution, log and KV record the run produced. Each flow is revalidated on the candidate right before its write, so a flow fixed during the Pause keeps its trigger.

Case Execution Result
Clean namespace cB18atBsf4qRUl9VnQIQw SUCCESS. One UNCHANGED row, no record, no write
Dry run 4ljJuhitgm4a4SyzjLD3V9 SUCCESS. INVALID x2, NEW_DEPRECATION x1, UNCHANGED x1; no trigger changed
Declined approval 2Iz2XucH7ZnIVt3CC0oxHH SUCCESS. No Toggle, every trigger state unchanged
Approved quarantine nAhgkak7WuANT0fwtDKWC SUCCESS. Only the candidate-invalid flow with an enabled trigger was disabled and verified; healthy, deprecated and source-disabled flows untouched; one rollback record and no password stored anywhere
Repeat QCZGfJPq55gY4LMmFBx1R SUCCESS. Trigger already disabled, no second Toggle
Trigger re-enabled during the settle window 64pZSRoRnAvWvh07uKJbSY FAILED as intended. Toggle reported success, the read-back found the trigger enabled
Flow fixed during the Pause 3co5uNCy8OCKlI3P5io7Av SUCCESS. Revalidation found no candidate problem, trigger left enabled
Trigger disabled during the Pause 4xkhqYQHfJGJEUfcmFdhIH SUCCESS. Skipped as stale, no Toggle
Current server stopped during the Pause 2jklmoQFyC28nvZjLEYOpW FAILED as intended at the source re-read, no Toggle
Fresh servers: clean namespace i8hz7TtUhO2OncTX94N7K SUCCESS
Fresh servers: dry run 1RZsu0Ta8hT4X1PMFfKvJG SUCCESS. Same four classifications
Fresh servers: declined 1rf1Et4IYkUAmo9KXti94O SUCCESS
Fresh servers: approved quarantine 6crDNERlpCvhj9JIzcbqfp SUCCESS. Same single trigger disabled
Fresh servers: repeat 7XD1soLkXszJurcPlKNXWM SUCCESS
Fresh servers: trigger re-enabled during settle 1NlRTiMizP89pAAF73L92C FAILED as intended
Fresh servers: flow fixed during the Pause 5F0lTzXsj9KyUUHsnfzWmB SUCCESS
Fresh servers: trigger disabled during the Pause 3x2mtuFQE40bjVwufzkI1d SUCCESS
Fresh servers: current server stopped AWRrWWAOig8i0oD1g0okp FAILED as intended

The classifier ran on synthetic validation results, through the same classify task copied unchanged into a probe flow, to cover boundaries a stored flow cannot reach (the current server refuses to store a flow that is invalid for it):

Classifier check Execution Class
candidate adds first constraint 7dI3X5Qxt9iP5sOiUtp5Qx INVALID
candidate changes existing constraint 7YggGbhBakkzjNVs5yt8Kj INVALID
same constraint on both 2A1eru7q5SQY1UnX1edSjY UNCHANGED
candidate fixes constraint 3eVbAAcmoBLuWLkmsdYGP8 UNCHANGED
replacement notice 1sYo4Syv6TGPiythYBVNj1 INVALID
new deprecation only 4mEt18PwbUgEKsABYwABLn NEW_DEPRECATION
deprecation already on current 4FeV8bg1Dj9v0tnuVnIlps UNCHANGED
new warning only 6RA8mllDObFY31wawF12mJ NEW_WARNING

Prerequisites

Kestra OSS 2.0.6 with the Kestra and core plugins, an API account that can list and export flows, validate flows, set KV values and toggle triggers and a second Kestra URL running the candidate image. The worker must be able to reach both URLs.

Secrets

  • KESTRA_PASSWORD: password for the Kestra API account.
  • SLACK_WEBHOOK_URL: Slack-compatible incoming webhook for the errors handler.
  • DBT_CLOUD_TOKEN: only needed by fixture flows that use dbt Cloud.

Inputs

Input Purpose Safe default
kestra_url Current flow and trigger API base URL http://localhost:8080
kestra_user API account name admin@kestra.io
kestra_password SECRET input filled from KESTRA_PASSWORD; never written to KV none
candidate_url Candidate validation API base URL http://localhost:8080
namespace Exact namespace to export and scan company.team
tenant_id API tenant segment main
auto_remediate Allow trigger quarantine after approval false
dry_run Prevent writes when true true
record_namespace KV namespace for audit and rollback records company.team

Quick start

  1. Deploy this flow to the current Kestra instance and leave the schedule disabled.
  2. Run it with dry_run: true, auto_remediate: false, the target namespace and the candidate URL.
  3. Review the per-flow table and the candidate-only deprecation and warning deltas.
  4. Re-run with auto_remediate: true and dry_run: false only when the listed candidate-invalid triggers are approved in the Pause form.
  5. Reverse a quarantine after the candidate is fixed: the KV record kestra-compat-<execution-id>-disabled-<namespace>-<flow> lists the trigger ids that were enabled; enable those with the Toggle task.

How to extend

Add a candidate image URL or a tenant-aware API authentication block when the deployment uses a different network. To quarantine a narrower set, add a namespace or label filter to inventory and export, then keep the post-toggle verification unchanged.

Limits: The validation endpoint can report only what the candidate image knows about; custom plugins must be installed in the candidate worker before this gate can clear them. Toggle has no conditional write, so another operator who changes a trigger between the final read and the write is caught only by the read-back that follows. The queue serializes executions of this flow, not other writers. Toggle without a trigger id disables every trigger of the flow and the record lists only the ones that were enabled. The gate does not re-enable triggers on its own.

Links

This blueprint was created by zkasuran.

See How

New to Kestra?

Use blueprints to kickstart your first workflows.