New to Kestra?
Use blueprints to kickstart your first workflows.
Trigger and wait on a Dagster job run, alert Slack when it is not SUCCESS, and optionally launch a fresh retry run.
This repo's own dagster-trigger-job.yaml blueprint shows how to launch a Dagster job
and block until it finishes - and its own "How to extend" section names the two things
it deliberately leaves out: a schedule, and branching on the run's status to alert on
FAILURE. This blueprint is that extension. It triggers job_name, waits for a
terminal state, classifies the result into a three-state risk level, alerts Slack on
anything but a clean SUCCESS, and can launch one fresh retry run - leaving the
original failed or canceled run exactly as Dagster recorded it.
periodic_job_compliance_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours, always forcing auto_remediate: "false" and dry_run: "true" through its own inputs: override regardless of this flow's defaults. Shipped disabled so you can validate a manual run first.trigger_and_wait_job_run (io.kestra.plugin.dagster.TriggerRun, wait: true, allowFailure: true) launches job_name and blocks until it reaches a terminal state, returning status (QUEUED, STARTING, STARTED, SUCCESS, FAILURE, or CANCELED, per the plugin's own documentation).classify_run_outcome (io.kestra.plugin.core.debug.Return) classifies the result as UNREACHABLE (status undefined - the GraphQL call itself failed), AT_RISK (status is anything other than SUCCESS), or HEALTHY.route_by_outcome (io.kestra.plugin.core.flow.Switch) branches on that classification:HEALTHY: log_healthy records the successful run.AT_RISK: a nested route_remediation (Switch on auto_remediate) either considers remediation ("true", gated again by dry_run through check_dry_run) or only logs that remediation is disabled ("false"). When both gates allow it, retrigger_job_run (io.kestra.plugin.dagster.TriggerRun, wait: false) launches a brand new run. alert_at_risk always fires to Slack regardless of which path ran.UNREACHABLE: alert_unreachable pages Slack directly - a broken GraphQL call must never read as "job succeeded".log_audit_complete always runs last, printing the classification regardless of which branch fired.errors block alerts Slack separately if the flow itself fails outside these handled branches.HEALTHY/AT_RISK/UNREACHABLE) so a broken GraphQL connection is never mistaken for a successful run.job_name.FAILURE or CANCELED, instead of relying on Dagster's own notification setup.dagster-trigger-job.yaml - that blueprint's own "How to extend" section names exactly this: a schedule, and a branch on status that alerts on failure.Dagster's own scheduler can re-run a job on a cron, but a single trigger-and-wait call makes no decision about what counts as a compliance breach, does not notify anyone by itself, and has no independent authorization gate before launching a retry. Kestra supplies the schedule, the three-way risk classification as a first-class branch, two independent authorization gates before a new run is launched, and an execution history that shows exactly when a job first came back non-SUCCESS and whether a retry was launched.
job_name's repository/location known.Local testing:
docker run -d --name dagster-compliance-gate -p 3001:3000 dagster/dagster-k8s:latest dagster-webserver -h 0.0.0.0 -p 3000
docker network connect YOUR_KESTRA_NETWORK dagster-compliance-gate (only needed if the Kestra Worker runs in a separate Docker network than this container; set DAGSTER_BASE_URL to http://dagster-compliance-gate:3000/graphql from inside that network, or http://localhost:3001/graphql from the host). A bare webserver container has no code location loaded; deploy a sample job and code location per Dagster's own open-source quickstart before exercising AT_RISK/HEALTHY end to end - this has not been done in this environment.
DAGSTER_BASE_URL: Dagster GraphQL endpoint, matching this plugin's own documented pattern (see dagster-trigger-job.yaml).DAGSTER_TOKEN: bearer token used in the Authorization header for Dagster API authentication.SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_at_risk, alert_unreachable, and the errors block.SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_DAGSTER_TOKEN=$(echo -n '<your-dagster-token>' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.job_name (STRING, default example_job): job triggered and audited.repository (STRING, no default): repository containing job_name.location (STRING, no default): code location containing repository.max_wait_duration (STRING, default PT30M): ceiling this flow waits for a terminal state.auto_remediate (SELECT: "false", "true"; default "false"): must be "true" for a retry run to even be considered.dry_run (SELECT: "true", "false"; default "true"): must be explicitly "false", together with auto_remediate: "true", for a fresh run to actually launch.outputs.trigger_and_wait_job_run.runId / .status / .startTime / .endTime: the audited run's own result for every attempted run.outputs.classify_run_outcome.value: HEALTHY, AT_RISK, or UNREACHABLE for every run.outputs.retrigger_job_run.runId: present only when remediation actually launched a new run.DAGSTER_BASE_URL, DAGSTER_TOKEN, and SLACK_WEBHOOK_URL secrets.log_healthy fires.job_name at a job that deliberately raises an exception and re-run; confirm alert_at_risk fires with status: FAILURE, and that log_remediation_disabled logs without launching a retry.auto_remediate: "true" and dry_run: "true" against the same failing job and confirm log_dry_run_remediation describes the retry without launching it.auto_remediate: "true" and dry_run: "false" and confirm retrigger_job_run launches, visible as a new run in the Dagster UI.periodic_job_compliance_check once you trust the check; it always runs in the safe auto_remediate: "false" / dry_run: "true" mode regardless of what you leave the flow's own defaults set to.job_name/repository/location triples with io.kestra.plugin.core.flow.Loop to audit every critical job in a code location in one run.vars or an external store, so a job that fails repeatedly escalates instead of retrying forever on every scheduled audit.Pause approval task instead of (or alongside) the static auto_remediate input, for a human-reviewed retry on a production job.body.tags on both TriggerRun calls (per this plugin's own documented pattern in dagster-trigger-job.yaml) to stamp each audited and retried run with the originating Kestra execution.id, for full bidirectional lineage.TriggerRun. Verified from the plugin's own documentation: there is no separate "get run status" task to audit an already-running or already-finished run without launching a new one - every audit in this blueprint is itself a real trigger, not a read-only check.wait: true blocks for up to maxDuration. A job that genuinely needs longer than the default PT30M to finish will be classified UNREACHABLE-adjacent behavior at the Kestra level (the task itself may time out) well before it actually fails in Dagster; raise max_wait_duration to match the job's real expected runtime.retrigger_job_run.runId is always different from trigger_and_wait_job_run.runId - this blueprint does not attempt to use any Dagster-side "retry from failure" mechanism, since no such task is documented on this plugin.allowFailure: true is what lets UNREACHABLE be classified instead of failing the execution outright. A base Task property, not specific to this plugin; without it, a GraphQL connection failure would abort the flow before classify_run_outcome ever runs.auto_remediate and dry_run are independent gates, not a single boolean. Both must be "true"/"false" respectively for retrigger_job_run to run; setting only one leaves the other still blocking.Switch case keys are quoted strings matching the classifier's exact output. "HEALTHY", "AT_RISK", and "UNREACHABLE" must match classify_run_outcome.value verbatim; the defaults: block exists specifically to catch any value that does not match one of those three cases.