dirigent-examples 0.15.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dirigent_examples/__init__.py +22 -0
- dirigent_examples/py.typed +0 -0
- dirigent_examples/shelves/README.md +299 -0
- dirigent_examples/shelves/composition/README.md +18 -0
- dirigent_examples/shelves/composition/chained-instances.yaml +97 -0
- dirigent_examples/shelves/composition/composition-child.yaml +64 -0
- dirigent_examples/shelves/composition/composition-parent.yaml +89 -0
- dirigent_examples/shelves/connections.yaml +52 -0
- dirigent_examples/shelves/demo/README.md +19 -0
- dirigent_examples/shelves/demo/markdown-showcase.yaml +117 -0
- dirigent_examples/shelves/demo/params-showcase.yaml +92 -0
- dirigent_examples/shelves/demo/requires.yaml +65 -0
- dirigent_examples/shelves/demo/weekly-import-malawi.yaml +54 -0
- dirigent_examples/shelves/demo/weekly-import-nepal.yaml +75 -0
- dirigent_examples/shelves/docker/README.md +29 -0
- dirigent_examples/shelves/docker/docker-build-push.yaml +110 -0
- dirigent_examples/shelves/docker/docker-build-run.yaml +106 -0
- dirigent_examples/shelves/docker/docker-compose-database.yaml +124 -0
- dirigent_examples/shelves/docker/docker-compose-failing-up.yaml +83 -0
- dirigent_examples/shelves/docker/docker-compose-file.yaml +117 -0
- dirigent_examples/shelves/docker/docker-compose-profiles-env.yaml +133 -0
- dirigent_examples/shelves/docker/docker-compose-stack.yaml +70 -0
- dirigent_examples/shelves/docker/docker-hello.yaml +53 -0
- dirigent_examples/shelves/docker/docker-remote-daemon.yaml +92 -0
- dirigent_examples/shelves/docker/docker-run-failing-teardown.yaml +94 -0
- dirigent_examples/shelves/docker/docker-ticker.yaml +49 -0
- dirigent_examples/shelves/execute/README.md +16 -0
- dirigent_examples/shelves/execute/long-log.yaml +89 -0
- dirigent_examples/shelves/failure/README.md +20 -0
- dirigent_examples/shelves/failure/error-handler.yaml +89 -0
- dirigent_examples/shelves/failure/optional-step.yaml +82 -0
- dirigent_examples/shelves/failure/retries.yaml +75 -0
- dirigent_examples/shelves/failure/retry-budget.yaml +82 -0
- dirigent_examples/shelves/failure/step-timeout.yaml +96 -0
- dirigent_examples/shelves/git/README.md +32 -0
- dirigent_examples/shelves/git/git-checkout-build.yaml +125 -0
- dirigent_examples/shelves/git/git-checkout-compose.yaml +138 -0
- dirigent_examples/shelves/git/git-checkout-public.yaml +84 -0
- dirigent_examples/shelves/graph/README.md +22 -0
- dirigent_examples/shelves/graph/deep-chain.yaml +119 -0
- dirigent_examples/shelves/graph/fan-in.yaml +80 -0
- dirigent_examples/shelves/graph/fan-out.yaml +66 -0
- dirigent_examples/shelves/graph/linear.yaml +66 -0
- dirigent_examples/shelves/graph/parallel-branches.yaml +57 -0
- dirigent_examples/shelves/graph/parallel-sleep.yaml +55 -0
- dirigent_examples/shelves/graph/skip-diamond.yaml +105 -0
- dirigent_examples/shelves/graph/wide-fan.yaml +147 -0
- dirigent_examples/shelves/hello-world.yaml +30 -0
- dirigent_examples/shelves/open-data/README.md +67 -0
- dirigent_examples/shelves/open-data/feeds-composition.yaml +120 -0
- dirigent_examples/shelves/open-data/gdacs-disaster-updates.yaml +230 -0
- dirigent_examples/shelves/open-data/github-releases-relay.yaml +225 -0
- dirigent_examples/shelves/open-data/hdx-dataset-watch.yaml +211 -0
- dirigent_examples/shelves/open-data/kobo-submissions-to-csv.yaml +149 -0
- dirigent_examples/shelves/open-data/nominatim-geocode-facilities.yaml +169 -0
- dirigent_examples/shelves/open-data/odk-central-submissions.yaml +146 -0
- dirigent_examples/shelves/open-data/open-meteo-weekly-report.yaml +142 -0
- dirigent_examples/shelves/open-data/overpass-health-facilities.yaml +154 -0
- dirigent_examples/shelves/open-data/usgs-earthquakes-alert.yaml +208 -0
- dirigent_examples/shelves/open-data/who-gho-indicators-to-parquet.yaml +163 -0
- dirigent_examples/shelves/open-data/wikidata-country-reference.yaml +146 -0
- dirigent_examples/shelves/open-data/world-bank-population-trend.yaml +166 -0
- dirigent_examples/shelves/patterns/README.md +144 -0
- dirigent_examples/shelves/patterns/concurrency-queue.yaml +89 -0
- dirigent_examples/shelves/patterns/concurrency-replace.yaml +92 -0
- dirigent_examples/shelves/patterns/concurrency-skip.yaml +97 -0
- dirigent_examples/shelves/patterns/connections-referenced-vs-carried.yaml +140 -0
- dirigent_examples/shelves/patterns/deadline-on-a-sensor.yaml +106 -0
- dirigent_examples/shelves/patterns/fan-out-continue.yaml +88 -0
- dirigent_examples/shelves/patterns/fan-out-fail-fast.yaml +80 -0
- dirigent_examples/shelves/patterns/fan-out-from-params.yaml +84 -0
- dirigent_examples/shelves/patterns/fan-out-item-wise.yaml +111 -0
- dirigent_examples/shelves/patterns/fan-out-literal-list.yaml +81 -0
- dirigent_examples/shelves/patterns/fan-out-nested-objects.yaml +107 -0
- dirigent_examples/shelves/patterns/fan-out-then-join.yaml +86 -0
- dirigent_examples/shelves/patterns/log-levels.yaml +119 -0
- dirigent_examples/shelves/patterns/outputs-inline-vs-storage.yaml +140 -0
- dirigent_examples/shelves/patterns/params-every-type.yaml +259 -0
- dirigent_examples/shelves/patterns/params-validation-refuses.yaml +131 -0
- dirigent_examples/shelves/patterns/pipeline-run-child.yaml +96 -0
- dirigent_examples/shelves/patterns/pipeline-run-fire-and-forget.yaml +99 -0
- dirigent_examples/shelves/patterns/pipeline-run-strict.yaml +103 -0
- dirigent_examples/shelves/patterns/pipeline-run-wait.yaml +107 -0
- dirigent_examples/shelves/patterns/pipeline-run-with-params.yaml +124 -0
- dirigent_examples/shelves/patterns/poll-cadence.yaml +102 -0
- dirigent_examples/shelves/patterns/priority-layered.yaml +120 -0
- dirigent_examples/shelves/patterns/references-cheat-sheet.yaml +186 -0
- dirigent_examples/shelves/patterns/retry-budget-exhausted.yaml +92 -0
- dirigent_examples/shelves/patterns/retry-exponential-backoff.yaml +88 -0
- dirigent_examples/shelves/patterns/retry-only-transient.yaml +108 -0
- dirigent_examples/shelves/patterns/retry-with-jitter.yaml +102 -0
- dirigent_examples/shelves/patterns/rule-all-done.yaml +83 -0
- dirigent_examples/shelves/patterns/rule-all-success.yaml +79 -0
- dirigent_examples/shelves/patterns/rule-always.yaml +92 -0
- dirigent_examples/shelves/patterns/rule-one-failed.yaml +86 -0
- dirigent_examples/shelves/patterns/schedule-at-once.yaml +110 -0
- dirigent_examples/shelves/patterns/schedule-cron-timezone.yaml +121 -0
- dirigent_examples/shelves/patterns/schedule-interval.yaml +109 -0
- dirigent_examples/shelves/patterns/schedule-window-half-open.yaml +105 -0
- dirigent_examples/shelves/patterns/sensor-http-ready.yaml +121 -0
- dirigent_examples/shelves/patterns/sensor-storage-exists.yaml +134 -0
- dirigent_examples/shelves/patterns/step-names-and-keys.yaml +99 -0
- dirigent_examples/shelves/patterns/timeout-fails-the-step.yaml +94 -0
- dirigent_examples/shelves/patterns/timeout-skips-the-step.yaml +102 -0
- dirigent_examples/shelves/patterns/webhook-mapping-nested-payload.yaml +125 -0
- dirigent_examples/shelves/patterns/webhook-signed.yaml +144 -0
- dirigent_examples/shelves/preview/s3-parquet-to-ingestion.yaml +92 -0
- dirigent_examples/shelves/python/README.md +31 -0
- dirigent_examples/shelves/python/apply_and_run.py +52 -0
- dirigent_examples/shelves/python/ci_gate.py +76 -0
- dirigent_examples/shelves/python/connections.py +61 -0
- dirigent_examples/shelves/python/error_handling.py +84 -0
- dirigent_examples/shelves/python/follow_logs.py +39 -0
- dirigent_examples/shelves/python/list_and_filter.py +52 -0
- dirigent_examples/shelves/queues/README.md +59 -0
- dirigent_examples/shelves/queues/kafka-consume-then-transform.yaml +105 -0
- dirigent_examples/shelves/queues/kafka-produce-then-consume.yaml +124 -0
- dirigent_examples/shelves/queues/rabbitmq-consume-ack-on-success.yaml +102 -0
- dirigent_examples/shelves/queues/report-to-kafka.yaml +105 -0
- dirigent_examples/shelves/queues/report-to-rabbitmq.yaml +105 -0
- dirigent_examples/shelves/recipes/README.md +130 -0
- dirigent_examples/shelves/recipes/csv-header-rules.yaml +147 -0
- dirigent_examples/shelves/recipes/csv-to-ndjson.yaml +107 -0
- dirigent_examples/shelves/recipes/etl-csv-clean-validate-parquet.yaml +207 -0
- dirigent_examples/shelves/recipes/filter-by-predicate.yaml +116 -0
- dirigent_examples/shelves/recipes/filter-then-map-then-reduce.yaml +109 -0
- dirigent_examples/shelves/recipes/http-fetch-validate-post.yaml +142 -0
- dirigent_examples/shelves/recipes/http-follow-redirects.yaml +100 -0
- dirigent_examples/shelves/recipes/http-get-with-query.yaml +96 -0
- dirigent_examples/shelves/recipes/http-headers-and-auth-connection.yaml +110 -0
- dirigent_examples/shelves/recipes/http-post-file-from-storage.yaml +117 -0
- dirigent_examples/shelves/recipes/http-post-json-echo.yaml +105 -0
- dirigent_examples/shelves/recipes/http-post-report.yaml +183 -0
- dirigent_examples/shelves/recipes/http-save-body-to-storage.yaml +106 -0
- dirigent_examples/shelves/recipes/http-success-status-list.yaml +80 -0
- dirigent_examples/shelves/recipes/http-timeout-override.yaml +104 -0
- dirigent_examples/shelves/recipes/jq-dedupe-by-key.yaml +78 -0
- dirigent_examples/shelves/recipes/jq-defaults-and-nulls.yaml +91 -0
- dirigent_examples/shelves/recipes/jq-group-by-and-sum.yaml +74 -0
- dirigent_examples/shelves/recipes/jq-join-two-lists.yaml +77 -0
- dirigent_examples/shelves/recipes/jq-long-to-wide.yaml +76 -0
- dirigent_examples/shelves/recipes/jq-nested-to-flat.yaml +89 -0
- dirigent_examples/shelves/recipes/jq-pivot-wide-to-long.yaml +65 -0
- dirigent_examples/shelves/recipes/jq-running-totals.yaml +82 -0
- dirigent_examples/shelves/recipes/jq-string-cleaning.yaml +88 -0
- dirigent_examples/shelves/recipes/jq-top-n.yaml +85 -0
- dirigent_examples/shelves/recipes/jq-validate-in-jq-vs-schema.yaml +124 -0
- dirigent_examples/shelves/recipes/jq-window-dates.yaml +82 -0
- dirigent_examples/shelves/recipes/json-to-csv-flattening.yaml +141 -0
- dirigent_examples/shelves/recipes/large-output-to-storage.yaml +134 -0
- dirigent_examples/shelves/recipes/map-enrich-with-lookup.yaml +96 -0
- dirigent_examples/shelves/recipes/ndjson-to-parquet.yaml +130 -0
- dirigent_examples/shelves/recipes/pagination-by-fan-out.yaml +115 -0
- dirigent_examples/shelves/recipes/parquet-round-trip-types.yaml +163 -0
- dirigent_examples/shelves/recipes/reconcile-two-sources.yaml +159 -0
- dirigent_examples/shelves/recipes/report-built-in.yaml +72 -0
- dirigent_examples/shelves/recipes/report-daily-digest.yaml +186 -0
- dirigent_examples/shelves/recipes/report-to-file.yaml +131 -0
- dirigent_examples/shelves/recipes/report-to-webhook.yaml +136 -0
- dirigent_examples/shelves/recipes/schema-carried.yaml +112 -0
- dirigent_examples/shelves/recipes/schema-formats.yaml +107 -0
- dirigent_examples/shelves/recipes/schema-referenced.yaml +86 -0
- dirigent_examples/shelves/recipes/schema-refuses-then-rule.yaml +127 -0
- dirigent_examples/shelves/recipes/storage-copy-dated-archive.yaml +114 -0
- dirigent_examples/shelves/recipes/storage-exists-gate.yaml +127 -0
- dirigent_examples/shelves/recipes/storage-manifest-of-a-fan-out.yaml +104 -0
- dirigent_examples/shelves/recipes/storage-write-then-read.yaml +119 -0
- dirigent_examples/shelves/recipes/webhook-post-hmac.yaml +132 -0
- dirigent_examples/shelves/recipes/webhook-post-summary.yaml +142 -0
- dirigent_examples/shelves/s3/README.md +34 -0
- dirigent_examples/shelves/s3/report-to-s3.yaml +93 -0
- dirigent_examples/shelves/s3/s3-copy-and-verify.yaml +105 -0
- dirigent_examples/shelves/s3/s3-csv-report.yaml +87 -0
- dirigent_examples/shelves/s3/s3-parquet-report.yaml +77 -0
- dirigent_examples/shelves/s3/s3-round-trip.yaml +125 -0
- dirigent_examples/shelves/schemas/README.md +36 -0
- dirigent_examples/shelves/schemas/echo-reading.json +18 -0
- dirigent_examples/shelves/schemas/ou-record.json +13 -0
- dirigent_examples/shelves/schemas/station-reading.json +13 -0
- dirigent_examples/shelves/sensors/README.md +16 -0
- dirigent_examples/shelves/sensors/sensor-gate.yaml +65 -0
- dirigent_examples/shelves/sensors/time-window.yaml +61 -0
- dirigent_examples/shelves/sql/README.md +52 -0
- dirigent_examples/shelves/sql/duckdb-parquet-to-report.yaml +146 -0
- dirigent_examples/shelves/sql/sql-postgres-readonly.yaml +111 -0
- dirigent_examples/shelves/sql/sql-query-to-storage.yaml +85 -0
- dirigent_examples/shelves/sql/sql-sqlite-roundtrip.yaml +114 -0
- dirigent_examples/shelves/sql/warehouse.sql +42 -0
- dirigent_examples/shelves/transform/README.md +36 -0
- dirigent_examples/shelves/transform/csv-report.yaml +55 -0
- dirigent_examples/shelves/transform/jq-filter-and-map.yaml +70 -0
- dirigent_examples/shelves/transform/jq-group-and-aggregate.yaml +70 -0
- dirigent_examples/shelves/transform/jq-join-two-sources.yaml +98 -0
- dirigent_examples/shelves/transform/jq-reshape.yaml +91 -0
- dirigent_examples/shelves/transform/jq-stream-through-storage.yaml +112 -0
- dirigent_examples/shelves/transform/ndjson-round-trip.yaml +56 -0
- dirigent_examples/shelves/transform/parquet-round-trip.yaml +68 -0
- dirigent_examples/shelves/transform/std-convert-fan-out.yaml +142 -0
- dirigent_examples/shelves/transform/xml-feed-to-ndjson.yaml +116 -0
- dirigent_examples/shelves/transform/yaml-config-to-json.yaml +104 -0
- dirigent_examples/shelves/triggers/README.md +45 -0
- dirigent_examples/shelves/triggers/at-one-time.yaml +78 -0
- dirigent_examples/shelves/triggers/cron-nightly.yaml +79 -0
- dirigent_examples/shelves/triggers/cron-windowed.yaml +86 -0
- dirigent_examples/shelves/triggers/document-nightly.yaml +80 -0
- dirigent_examples/shelves/triggers/interval-rolling.yaml +88 -0
- dirigent_examples/shelves/triggers/managed-and-manual.yaml +109 -0
- dirigent_examples/shelves/triggers/webhook-trigger.yaml +75 -0
- dirigent_examples/shelves/validate/README.md +31 -0
- dirigent_examples/shelves/validate/expects-a-shape.yaml +56 -0
- dirigent_examples/shelves/validate/the-shape-is-wrong.yaml +46 -0
- dirigent_examples-0.15.0.dist-info/METADATA +21 -0
- dirigent_examples-0.15.0.dist-info/RECORD +216 -0
- dirigent_examples-0.15.0.dist-info/WHEEL +4 -0
- dirigent_examples-0.15.0.dist-info/entry_points.txt +3 -0
- dirigent_examples-0.15.0.dist-info/licenses/LICENSE +18 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# pipeline.run with wait: true -- a parent step that is a parked row, not a held worker.
|
|
2
|
+
#
|
|
3
|
+
# wait is the default, and it is what makes composition useful: the parent's step finishes
|
|
4
|
+
# when the CHILD finishes, so everything downstream of it genuinely runs after the child's
|
|
5
|
+
# work. The child's final status becomes this step's outcome -- succeeded is succeeded, failed
|
|
6
|
+
# is failed -- and the parent's step output carries the child's code, its run id and its
|
|
7
|
+
# status.
|
|
8
|
+
#
|
|
9
|
+
# HOW IT WAITS MATTERS. pipeline.run is submit-then-probe: the child run is created, a handle
|
|
10
|
+
# is stored, and the parent's attempt is parked with a wake-up time. So a parent waiting on a
|
|
11
|
+
# three-hour child holds a row for three hours and a worker for none of it, and the parent
|
|
12
|
+
# does not have to be running anywhere in particular for the child to progress.
|
|
13
|
+
#
|
|
14
|
+
# poll is the cadence of that probing, and it is a step field like any other. The block's own
|
|
15
|
+
# default is five seconds; 1s below is so the example finishes while you watch it. A real
|
|
16
|
+
# parent waiting on an hour-long child leaves it out.
|
|
17
|
+
#
|
|
18
|
+
# A dirigent document holds exactly one pipeline, so composition is two files, and the apply
|
|
19
|
+
# order matters: the child has to exist before the parent applies at all. requires.pipelines
|
|
20
|
+
# is what says so, and an apply against an instance without the child is refused up front with
|
|
21
|
+
# the code to apply first -- rather than failing at the step, at five in the morning.
|
|
22
|
+
#
|
|
23
|
+
# Hop by hop:
|
|
24
|
+
#
|
|
25
|
+
# load_east runs the child for one region and waits. Its output is the child's identity and
|
|
26
|
+
# outcome, not the child's data: a child's outputs stay in the child's run.
|
|
27
|
+
# archive reads that output back. It runs only because the child finished, which is the
|
|
28
|
+
# guarantee wait: true buys.
|
|
29
|
+
#
|
|
30
|
+
# EXPECT THIS RUN TO SUCCEED in about three seconds, with load_east reporting the child's
|
|
31
|
+
# status as succeeded.
|
|
32
|
+
#
|
|
33
|
+
# Locally there is no instance to have applied the child to, so hand it over:
|
|
34
|
+
#
|
|
35
|
+
# dg run --local examples/patterns/pipeline-run-wait.yaml \
|
|
36
|
+
# --also-apply examples/patterns/pipeline-run-child.yaml
|
|
37
|
+
#
|
|
38
|
+
# Against a server it is two applies and a run:
|
|
39
|
+
#
|
|
40
|
+
# dg apply examples/patterns/pipeline-run-child.yaml
|
|
41
|
+
# dg apply examples/patterns/pipeline-run-wait.yaml
|
|
42
|
+
# dg run pipeline-run-wait --watch
|
|
43
|
+
|
|
44
|
+
format: dirigent/v1
|
|
45
|
+
kind: pipeline
|
|
46
|
+
code: pipeline-run-wait
|
|
47
|
+
name: A parent that waits
|
|
48
|
+
description: |
|
|
49
|
+
`pipeline.run` with the default `wait: true` finishes when the child finishes, and the
|
|
50
|
+
child's status becomes the step's outcome.
|
|
51
|
+
|
|
52
|
+
It is submit-then-probe, so a parent waiting on a three-hour child holds a database row
|
|
53
|
+
and no worker at all.
|
|
54
|
+
|
|
55
|
+
tags: [patterns, pipeline, transform, composition]
|
|
56
|
+
|
|
57
|
+
requires:
|
|
58
|
+
blocks:
|
|
59
|
+
- pipeline.run
|
|
60
|
+
- transform.jq
|
|
61
|
+
# The child, by code. An apply against an instance that does not hold it is refused here
|
|
62
|
+
# rather than at the step.
|
|
63
|
+
pipelines:
|
|
64
|
+
- pipeline-run-child
|
|
65
|
+
|
|
66
|
+
params:
|
|
67
|
+
type: object
|
|
68
|
+
properties:
|
|
69
|
+
region:
|
|
70
|
+
type: string
|
|
71
|
+
description: Which region the child is asked to load.
|
|
72
|
+
default: east
|
|
73
|
+
pattern: "^[a-z][a-z0-9-]*$"
|
|
74
|
+
day:
|
|
75
|
+
type: string
|
|
76
|
+
format: date
|
|
77
|
+
default: "2026-01-01"
|
|
78
|
+
|
|
79
|
+
steps:
|
|
80
|
+
load_east:
|
|
81
|
+
block: pipeline.run
|
|
82
|
+
# Faster than the block's five-second default so the example settles while you watch. A
|
|
83
|
+
# real parent leaves this out: probing a long child every second buys nothing.
|
|
84
|
+
poll: 1s
|
|
85
|
+
config:
|
|
86
|
+
pipeline: pipeline-run-child
|
|
87
|
+
# Written out rather than forwarded. The child's schema is its interface, and a parent
|
|
88
|
+
# that passed its own parameters through would be coupled to whatever it was run with.
|
|
89
|
+
params:
|
|
90
|
+
region: "${params.region}"
|
|
91
|
+
day: "${params.day}"
|
|
92
|
+
# The default, written once so the pair with pipeline-run-fire-and-forget.yaml reads as
|
|
93
|
+
# a choice rather than as an omission.
|
|
94
|
+
wait: true
|
|
95
|
+
|
|
96
|
+
archive:
|
|
97
|
+
block: transform.jq
|
|
98
|
+
depends_on: [load_east]
|
|
99
|
+
config:
|
|
100
|
+
input:
|
|
101
|
+
# The child's identity and outcome. Its data is not here: a child's outputs belong to
|
|
102
|
+
# the child's run, and a parent that needs a value reads it from a store both can see.
|
|
103
|
+
pipeline: "${steps.load_east.output.pipeline}"
|
|
104
|
+
run_id: "${steps.load_east.output.run_id}"
|
|
105
|
+
status: "${steps.load_east.output.status}"
|
|
106
|
+
program: |
|
|
107
|
+
{archived: .pipeline, child_run: .run_id, ended_as: .status}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Passing parameters to a child, and the two things the engine checks about them and when.
|
|
2
|
+
#
|
|
3
|
+
# A parent writes the child's parameters out. It does not forward its own, and there is no
|
|
4
|
+
# syntax for "pass everything through" -- because the child's schema is the contract, and a
|
|
5
|
+
# parent that forwarded whatever it was run with would break the moment either side grew a
|
|
6
|
+
# parameter.
|
|
7
|
+
#
|
|
8
|
+
# WHEN THE CHECKING HAPPENS, in two stages:
|
|
9
|
+
#
|
|
10
|
+
# at apply requires.pipelines is checked, so a parent naming a child the instance does
|
|
11
|
+
# not hold is refused with the code to apply first.
|
|
12
|
+
# at execute the params map below is validated against the CHILD's schema, by the child, as
|
|
13
|
+
# the step runs. So a wrong parameter is a failed step with the schema's own
|
|
14
|
+
# message, not a refused apply -- the parent has no way to know at apply time
|
|
15
|
+
# what a ${params.x} will resolve to.
|
|
16
|
+
#
|
|
17
|
+
# max_depth is the other guard, and it counts the chain reaching this run rather than the
|
|
18
|
+
# calls this document makes. The default is 5, so a parent calling a child that calls a child
|
|
19
|
+
# is fine and a cycle is not: a pipeline that eventually calls itself fails at the depth limit
|
|
20
|
+
# instead of filling the instance with runs.
|
|
21
|
+
#
|
|
22
|
+
# A FAN-OUT OF pipeline.run IS THE USEFUL SHAPE. One item per region, each starting its own
|
|
23
|
+
# child run with its own parameters, all of them parked rows rather than held workers. That is
|
|
24
|
+
# how a parent runs eleven children in parallel without eleven of anything.
|
|
25
|
+
#
|
|
26
|
+
# Hop by hop:
|
|
27
|
+
#
|
|
28
|
+
# plan builds the list of regions to load, so the fan-out's width is one value the
|
|
29
|
+
# rest of the document reads.
|
|
30
|
+
# load one child run per region, waited on, each with its own parameters. Note the
|
|
31
|
+
# day is the same for all of them and the region is not: an element carries what
|
|
32
|
+
# differs, and params carries what does not.
|
|
33
|
+
# summarise the join. It reads the batch of child outcomes -- codes, run ids and statuses,
|
|
34
|
+
# which is everything a parent gets back from a child.
|
|
35
|
+
#
|
|
36
|
+
# EXPECT THIS RUN TO SUCCEED in about five seconds, with three child runs and three statuses
|
|
37
|
+
# of succeeded.
|
|
38
|
+
#
|
|
39
|
+
# To change it: -p regions='["east"]' runs one child; a region that does not match the child's
|
|
40
|
+
# pattern fails that item at execute time with the child's own message, which is the two-stage
|
|
41
|
+
# checking above made visible.
|
|
42
|
+
#
|
|
43
|
+
# dg run --local examples/patterns/pipeline-run-with-params.yaml \
|
|
44
|
+
# --also-apply examples/patterns/pipeline-run-child.yaml
|
|
45
|
+
# dg run --local examples/patterns/pipeline-run-with-params.yaml \
|
|
46
|
+
# --also-apply examples/patterns/pipeline-run-child.yaml -p regions='["east","west"]'
|
|
47
|
+
|
|
48
|
+
format: dirigent/v1
|
|
49
|
+
kind: pipeline
|
|
50
|
+
code: pipeline-run-with-params
|
|
51
|
+
name: Children, one per region
|
|
52
|
+
description: |
|
|
53
|
+
A parent writes its child's parameters out explicitly; they are validated against the
|
|
54
|
+
**child's** schema when the step executes, not when the parent is applied.
|
|
55
|
+
|
|
56
|
+
A fan-out of `pipeline.run` is how one parent runs many children in parallel, each a
|
|
57
|
+
parked row rather than a held worker.
|
|
58
|
+
|
|
59
|
+
tags: [patterns, pipeline, transform, composition, fan-out]
|
|
60
|
+
|
|
61
|
+
requires:
|
|
62
|
+
blocks:
|
|
63
|
+
- pipeline.run
|
|
64
|
+
- transform.jq
|
|
65
|
+
pipelines:
|
|
66
|
+
- pipeline-run-child
|
|
67
|
+
|
|
68
|
+
params:
|
|
69
|
+
type: object
|
|
70
|
+
properties:
|
|
71
|
+
regions:
|
|
72
|
+
type: array
|
|
73
|
+
description: One child run per element.
|
|
74
|
+
default: [east, west, north]
|
|
75
|
+
minItems: 1
|
|
76
|
+
maxItems: 16
|
|
77
|
+
items:
|
|
78
|
+
type: string
|
|
79
|
+
# The same pattern the child declares. Duplicating it here is what turns a child-side
|
|
80
|
+
# failure at execute time into a parent-side refusal before the run exists.
|
|
81
|
+
pattern: "^[a-z][a-z0-9-]*$"
|
|
82
|
+
day:
|
|
83
|
+
type: string
|
|
84
|
+
format: date
|
|
85
|
+
description: The day every child loads; the same for all of them.
|
|
86
|
+
default: "2026-01-01"
|
|
87
|
+
|
|
88
|
+
steps:
|
|
89
|
+
plan:
|
|
90
|
+
block: transform.jq
|
|
91
|
+
config:
|
|
92
|
+
input: "${params.regions}"
|
|
93
|
+
program: |
|
|
94
|
+
{regions: ., count: length}
|
|
95
|
+
|
|
96
|
+
load:
|
|
97
|
+
block: pipeline.run
|
|
98
|
+
depends_on: [plan]
|
|
99
|
+
# The fan-out reads params, not the plan step's output: cardinality is fixed when the run
|
|
100
|
+
# is created, so a for_each names params, run, or another fan-out's grid -- never a step's
|
|
101
|
+
# output.
|
|
102
|
+
for_each: "${params.regions}"
|
|
103
|
+
# One region refusing is not a reason to abandon the others; the join below counts what
|
|
104
|
+
# actually landed.
|
|
105
|
+
items: continue
|
|
106
|
+
poll: 1s
|
|
107
|
+
config:
|
|
108
|
+
pipeline: pipeline-run-child
|
|
109
|
+
params:
|
|
110
|
+
# What differs per child comes from the element.
|
|
111
|
+
region: "${item}"
|
|
112
|
+
# What is the same for every child comes from the parent's parameters.
|
|
113
|
+
day: "${params.day}"
|
|
114
|
+
|
|
115
|
+
summarise:
|
|
116
|
+
block: transform.jq
|
|
117
|
+
depends_on: [load]
|
|
118
|
+
config:
|
|
119
|
+
input: "${steps.load.output}"
|
|
120
|
+
program: |
|
|
121
|
+
{
|
|
122
|
+
children: [.[] | {pipeline, run_id, status}],
|
|
123
|
+
started: length
|
|
124
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# poll: what the cadence buys, and what it costs, measured on the same wait twice.
|
|
2
|
+
#
|
|
3
|
+
# poll is how often a sensor looks. It is not how long the wait is, and it is not accuracy for
|
|
4
|
+
# free: a sensor learns nothing between pokes, so the answer arrives up to one whole poll
|
|
5
|
+
# interval late. Coarse polling is cheap and blunt; fine polling is precise and chatty. That
|
|
6
|
+
# is the entire trade, and this file measures it rather than asserting it.
|
|
7
|
+
#
|
|
8
|
+
# Two time.sleep sensors, both asked for exactly the same wait, differing only in cadence.
|
|
9
|
+
# time.sleep reports waited_ms, which is the configured duration plus however much of a poll
|
|
10
|
+
# interval it had to sit through after the wait was already over -- so the overshoot is
|
|
11
|
+
# readable in the output instead of having to be timed by hand.
|
|
12
|
+
#
|
|
13
|
+
# NOTE WHERE THE CADENCE IS WRITTEN. poll, deadline, timeout and retry are step fields, and
|
|
14
|
+
# the reference language reaches config and for_each only. So the two cadences below are
|
|
15
|
+
# literals: a run's shape is not something a caller talks the engine into at run time. Only
|
|
16
|
+
# the wait itself, which is block config, takes a parameter.
|
|
17
|
+
#
|
|
18
|
+
# Hop by hop:
|
|
19
|
+
#
|
|
20
|
+
# fine poll: 1s over the wait. It pokes once a second, and waited_ms lands close to the
|
|
21
|
+
# configured duration.
|
|
22
|
+
# coarse poll: 4s over the same wait. The wait being over is not noticed until the next
|
|
23
|
+
# poke, so waited_ms lands up to four seconds higher.
|
|
24
|
+
# compare reads both and subtracts. The difference is what the coarse cadence cost in
|
|
25
|
+
# latency, and it is bounded by the poll interval, which is the rule of thumb.
|
|
26
|
+
#
|
|
27
|
+
# The two sensors have no edge between them, so they wait side by side and the run takes about
|
|
28
|
+
# as long as the slower one rather than the sum. Width in a DAG is not a keyword: it is what
|
|
29
|
+
# two steps that do not name each other already are.
|
|
30
|
+
#
|
|
31
|
+
# EXPECT THIS RUN TO SUCCEED, in about eight to nine seconds, with a coarse_cost_ms of
|
|
32
|
+
# somewhere between zero and four seconds depending on where the wait fell between pokes.
|
|
33
|
+
#
|
|
34
|
+
# To change it: -p wait=20 makes the run longer without changing the gap, because the
|
|
35
|
+
# overshoot depends on the cadence and not on the length of the wait -- which is exactly the
|
|
36
|
+
# thing worth internalising before choosing a poll interval for a twelve-hour sensor.
|
|
37
|
+
#
|
|
38
|
+
# dg run --local examples/patterns/poll-cadence.yaml
|
|
39
|
+
# dg run --local examples/patterns/poll-cadence.yaml -p wait=20
|
|
40
|
+
|
|
41
|
+
format: dirigent/v1
|
|
42
|
+
kind: pipeline
|
|
43
|
+
code: poll-cadence
|
|
44
|
+
name: What a poll interval costs
|
|
45
|
+
description: |
|
|
46
|
+
The same wait polled every second and every four seconds, with the difference in
|
|
47
|
+
`waited_ms` reported.
|
|
48
|
+
|
|
49
|
+
A sensor learns nothing between pokes, so its answer is late by up to one poll interval.
|
|
50
|
+
Cheap and blunt, or precise and chatty -- measured here rather than asserted.
|
|
51
|
+
|
|
52
|
+
tags: [patterns, sensor, transform, timeout]
|
|
53
|
+
|
|
54
|
+
requires:
|
|
55
|
+
blocks:
|
|
56
|
+
- time.sleep
|
|
57
|
+
- transform.jq
|
|
58
|
+
|
|
59
|
+
params:
|
|
60
|
+
type: object
|
|
61
|
+
properties:
|
|
62
|
+
wait:
|
|
63
|
+
type: integer
|
|
64
|
+
description: How many seconds both sensors are asked to wait for.
|
|
65
|
+
default: 6
|
|
66
|
+
minimum: 1
|
|
67
|
+
maximum: 60
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
fine:
|
|
71
|
+
block: time.sleep
|
|
72
|
+
# A literal, because a step field is not interpolated. One second is a chatty cadence for
|
|
73
|
+
# anything real; it is here so the tight end of the trade is visible.
|
|
74
|
+
poll: 1s
|
|
75
|
+
config:
|
|
76
|
+
# Config is interpolated, and a configured duration is written humanely rather than as
|
|
77
|
+
# a bare number of seconds, so the parameter goes inside a string that ends in s.
|
|
78
|
+
for: "${params.wait}s"
|
|
79
|
+
|
|
80
|
+
coarse:
|
|
81
|
+
block: time.sleep
|
|
82
|
+
# The same wait, looked at a quarter as often. Nothing else differs, which is what makes
|
|
83
|
+
# the two outputs comparable.
|
|
84
|
+
poll: 4s
|
|
85
|
+
config:
|
|
86
|
+
for: "${params.wait}s"
|
|
87
|
+
|
|
88
|
+
compare:
|
|
89
|
+
block: transform.jq
|
|
90
|
+
depends_on: [fine, coarse]
|
|
91
|
+
# A join: no rule written, so all_success, and it waits for both sensors.
|
|
92
|
+
config:
|
|
93
|
+
input:
|
|
94
|
+
fine_ms: "${steps.fine.output.waited_ms}"
|
|
95
|
+
coarse_ms: "${steps.coarse.output.waited_ms}"
|
|
96
|
+
program: |
|
|
97
|
+
{
|
|
98
|
+
fine_ms,
|
|
99
|
+
coarse_ms,
|
|
100
|
+
coarse_cost_ms: (.coarse_ms - .fine_ms),
|
|
101
|
+
rule_of_thumb: "a sensor's answer is late by up to one poll interval"
|
|
102
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# One pipeline, three answers to "how urgently", each overriding the one before it.
|
|
2
|
+
#
|
|
3
|
+
# Every run carries a priority -- low, normal, or high -- and the claim takes a higher one
|
|
4
|
+
# first. It is one word, and it LAYERS the way parameters already do:
|
|
5
|
+
#
|
|
6
|
+
# the document priority: low what this pipeline is by default
|
|
7
|
+
# a trigger priority: normal what a schedule or webhook fires it at
|
|
8
|
+
# an ad hoc run --priority high what a person asks for, once
|
|
9
|
+
#
|
|
10
|
+
# The specific one wins, and the answer is PINNED ON THE RUN when it is created. Editing this
|
|
11
|
+
# document tomorrow does not reorder a run that started today, which is the same promise the
|
|
12
|
+
# pinned pipeline version makes about everything else a run executes.
|
|
13
|
+
#
|
|
14
|
+
# WHAT THE THREE WORDS ARE FOR, in the case this document is drawn from -- a bulk reload that
|
|
15
|
+
# takes hours and matters to nobody in particular:
|
|
16
|
+
#
|
|
17
|
+
# low its default. It is enormous, it is not urgent, and it should be behind
|
|
18
|
+
# whatever a person is waiting on. That is what low means: last.
|
|
19
|
+
# normal what the nightly schedule fires it at, because a nightly run that never
|
|
20
|
+
# finishes is a nightly run that failed. Ordinary, not deferred.
|
|
21
|
+
# high what an incident asks for by hand. The reload is now the thing being waited
|
|
22
|
+
# on, so it goes ahead of everything queued.
|
|
23
|
+
#
|
|
24
|
+
# THE CLAIM'S ORDER IS PRIORITY, THEN FAIRNESS, THEN DUE TIME. Fairness is round-robin
|
|
25
|
+
# between runs: the claim ranks each run's due attempts within that run and takes one from
|
|
26
|
+
# each in turn, so this document's fan-out over four regions interleaves with a two-step run
|
|
27
|
+
# queued beside it rather than holding every worker slot until it drains. Priority sorts
|
|
28
|
+
# ahead of that, so a high run's attempts still come first.
|
|
29
|
+
#
|
|
30
|
+
# WHAT PRIORITY CANNOT DO: it never takes a slot that is already busy. An attempt that is
|
|
31
|
+
# running is never cancelled to make room for an urgent one, because that means killing work
|
|
32
|
+
# with side effects nobody can take back. A high run is claimed first the moment a slot frees
|
|
33
|
+
# -- so with every slot held by a long step, it still waits for one to finish.
|
|
34
|
+
#
|
|
35
|
+
# Hop by hop:
|
|
36
|
+
#
|
|
37
|
+
# plan turns the parameters into the regions this reload covers.
|
|
38
|
+
# reload a fan-out, one item per region -- the many small units that make a run worth
|
|
39
|
+
# being fair about in the first place.
|
|
40
|
+
# receipt joins them back and reports what was covered.
|
|
41
|
+
#
|
|
42
|
+
# EXPECT THIS RUN TO SUCCEED in about a second, with no network. A local run is alone in a
|
|
43
|
+
# throwaway instance, so --priority is refused there: there is nothing to be ahead of.
|
|
44
|
+
#
|
|
45
|
+
# dg run --local examples/patterns/priority-layered.yaml
|
|
46
|
+
# dg apply examples/patterns/priority-layered.yaml
|
|
47
|
+
# dg run priority-layered # low: the document's own
|
|
48
|
+
# dg run priority-layered --priority high # the incident case
|
|
49
|
+
# dg runs list # ! marks high, a muted low marks low
|
|
50
|
+
|
|
51
|
+
format: dirigent/v1
|
|
52
|
+
kind: pipeline
|
|
53
|
+
code: priority-layered
|
|
54
|
+
name: Low by default, high by hand
|
|
55
|
+
description: |
|
|
56
|
+
`priority` is one word -- `low`, `normal` or `high` -- and it layers: the document declares
|
|
57
|
+
the default, a schedule or webhook overrides it for what it triggers, and `dg run
|
|
58
|
+
--priority` overrides it once more. The resolved word is pinned on the run at creation.
|
|
59
|
+
|
|
60
|
+
The claim orders by priority, then round-robin fairness between runs, then due time.
|
|
61
|
+
Nothing is preempted: an attempt already running is never cancelled for an urgent one.
|
|
62
|
+
|
|
63
|
+
tags: [patterns, transform, priority, schedule]
|
|
64
|
+
|
|
65
|
+
# Low, because a bulk reload should be behind whatever somebody is waiting on.
|
|
66
|
+
priority: low
|
|
67
|
+
|
|
68
|
+
requires:
|
|
69
|
+
blocks:
|
|
70
|
+
- transform.jq
|
|
71
|
+
|
|
72
|
+
params:
|
|
73
|
+
type: object
|
|
74
|
+
properties:
|
|
75
|
+
regions:
|
|
76
|
+
type: array
|
|
77
|
+
description: The regions this reload covers; one fan-out item each.
|
|
78
|
+
items:
|
|
79
|
+
type: string
|
|
80
|
+
default: [nordics, nepal, sahel, andes]
|
|
81
|
+
|
|
82
|
+
steps:
|
|
83
|
+
plan:
|
|
84
|
+
block: transform.jq
|
|
85
|
+
config:
|
|
86
|
+
input:
|
|
87
|
+
regions: "${params.regions}"
|
|
88
|
+
program: |
|
|
89
|
+
{regions, total: (.regions | length)}
|
|
90
|
+
|
|
91
|
+
reload:
|
|
92
|
+
block: transform.jq
|
|
93
|
+
depends_on: [plan]
|
|
94
|
+
# A fan-out is where fairness earns its keep: four items here, four hundred in the real
|
|
95
|
+
# thing, and every one of them is a unit the claim interleaves with other runs' work.
|
|
96
|
+
for_each: "${params.regions}"
|
|
97
|
+
config:
|
|
98
|
+
input:
|
|
99
|
+
region: "${item}"
|
|
100
|
+
program: |
|
|
101
|
+
{region, rows: 1200}
|
|
102
|
+
|
|
103
|
+
receipt:
|
|
104
|
+
block: transform.jq
|
|
105
|
+
depends_on: [reload]
|
|
106
|
+
config:
|
|
107
|
+
input: "${steps.reload.output}"
|
|
108
|
+
program: |
|
|
109
|
+
{regions: length, rows: (map(.value.rows) | add)}
|
|
110
|
+
|
|
111
|
+
triggers:
|
|
112
|
+
schedules:
|
|
113
|
+
- code: nightly-reload
|
|
114
|
+
name: Nightly, at ordinary urgency
|
|
115
|
+
description: A nightly run that never finishes is a nightly run that failed.
|
|
116
|
+
cron: "0 2 * * *"
|
|
117
|
+
timezone: UTC
|
|
118
|
+
# The schedule overrides the document's low for what IT fires, and nothing else. An
|
|
119
|
+
# ad hoc run of the same pipeline is still low unless it says otherwise.
|
|
120
|
+
priority: normal
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Every ${...} form the reference language allows, each used once, in one document.
|
|
2
|
+
#
|
|
3
|
+
# THERE ARE FOUR NAMESPACES AND NOTHING ELSE. No expressions, no arithmetic, no conditionals,
|
|
4
|
+
# no functions, no defaults, no coalescing. A reference names a value or it does not resolve.
|
|
5
|
+
#
|
|
6
|
+
# params.<name> a run parameter
|
|
7
|
+
# params.<name>.<path> a leaf inside an object parameter
|
|
8
|
+
# params.<name>.<index> an element of an array parameter
|
|
9
|
+
# steps.<key>.output a step's whole output
|
|
10
|
+
# steps.<key>.output.<field> one field of it
|
|
11
|
+
# steps.<key>.output.<index> one element of a fan-out step's output list
|
|
12
|
+
# steps.<key>.items the grid a fan-out maps over, and only for_each reads it
|
|
13
|
+
# steps.<key>.item.output the matching item's output, in a step that shares its grid
|
|
14
|
+
# item this run item's element, inside a step that fans out
|
|
15
|
+
# item.<path> a field of it, when the element is an object
|
|
16
|
+
# run.id this run's uuid
|
|
17
|
+
# run.scratch this run's own directory under the artifact root
|
|
18
|
+
# run.window.start the start of the interval this run covers
|
|
19
|
+
# run.window.end the end of it, exclusive
|
|
20
|
+
#
|
|
21
|
+
# TWO RULES ABOUT WHAT A REFERENCE BECOMES:
|
|
22
|
+
#
|
|
23
|
+
# A reference that is the WHOLE value resolves to the typed value. "${params.count}" is the
|
|
24
|
+
# integer 4 downstream, "${params.window}" is an object, "${steps.x.output}" is an array.
|
|
25
|
+
# A reference INSIDE a larger string interpolates as text: "day-${params.day}" is a string,
|
|
26
|
+
# null renders as empty, and a boolean renders as true or false.
|
|
27
|
+
#
|
|
28
|
+
# AND ONE ABOUT WHAT DOES NOT RESOLVE. An unknown reference is not an empty string: it raises,
|
|
29
|
+
# and the engine settles that attempt as rejected, naming what was actually available. That
|
|
30
|
+
# applies to a misspelled parameter, a step that produced no output, a field that is not
|
|
31
|
+
# there, ${item} outside a fan-out, and ${run.window.start} on a run that carries no window.
|
|
32
|
+
#
|
|
33
|
+
# WHERE REFERENCES ARE RESOLVED: in a step's config, and in for_each. Nowhere else. poll,
|
|
34
|
+
# deadline, timeout, retry, rule and concurrency are the pipeline's shape, and a caller does
|
|
35
|
+
# not get to talk the engine into a different one at run time.
|
|
36
|
+
#
|
|
37
|
+
# One more rule this document cannot show without the allowlist: in a config field a block
|
|
38
|
+
# marked as a shell string -- shell.run's `command` -- every substituted value is shell-quoted,
|
|
39
|
+
# so a parameter that arrived in a webhook payload becomes exactly one word and the
|
|
40
|
+
# metacharacters the author typed keep their meaning.
|
|
41
|
+
#
|
|
42
|
+
# THIS FILE NEEDS A WINDOW. run.window.* is a property of the run, so an ad hoc run has to be
|
|
43
|
+
# given one; without it this document is refused at the first step, naming the reference. That
|
|
44
|
+
# refusal is the demonstration too:
|
|
45
|
+
#
|
|
46
|
+
# dg run --local examples/patterns/references-cheat-sheet.yaml --window 2026-06-01..2026-06-02
|
|
47
|
+
# dg run --local examples/patterns/references-cheat-sheet.yaml # refused: this run carries no window
|
|
48
|
+
|
|
49
|
+
format: dirigent/v1
|
|
50
|
+
kind: pipeline
|
|
51
|
+
code: references-cheat-sheet
|
|
52
|
+
name: The whole reference language
|
|
53
|
+
description: |
|
|
54
|
+
Four namespaces -- `params`, `steps`, `item`, `run` -- and no expressions, conditionals or
|
|
55
|
+
functions anywhere.
|
|
56
|
+
|
|
57
|
+
A whole reference resolves to the typed value; one inside a larger string interpolates as
|
|
58
|
+
text; an unknown one is rejected rather than resolved to empty. Needs `--window`.
|
|
59
|
+
|
|
60
|
+
tags: [patterns, transform, fan-out, references]
|
|
61
|
+
|
|
62
|
+
requires:
|
|
63
|
+
blocks:
|
|
64
|
+
- transform.jq
|
|
65
|
+
|
|
66
|
+
params:
|
|
67
|
+
type: object
|
|
68
|
+
properties:
|
|
69
|
+
day:
|
|
70
|
+
type: string
|
|
71
|
+
format: date
|
|
72
|
+
default: "2026-06-01"
|
|
73
|
+
count:
|
|
74
|
+
type: integer
|
|
75
|
+
description: An integer, so the typed-value rule has something to be visible on.
|
|
76
|
+
default: 4
|
|
77
|
+
minimum: 1
|
|
78
|
+
regions:
|
|
79
|
+
type: array
|
|
80
|
+
description: An array, indexed once below and fanned out over once.
|
|
81
|
+
default: [east, west, north]
|
|
82
|
+
minItems: 1
|
|
83
|
+
items:
|
|
84
|
+
type: string
|
|
85
|
+
window:
|
|
86
|
+
type: object
|
|
87
|
+
description: An object parameter, so a nested leaf has somewhere to be.
|
|
88
|
+
default: {days: 7, align: midnight}
|
|
89
|
+
properties:
|
|
90
|
+
days: {type: integer}
|
|
91
|
+
align: {type: string}
|
|
92
|
+
|
|
93
|
+
steps:
|
|
94
|
+
namespaces:
|
|
95
|
+
block: transform.jq
|
|
96
|
+
config:
|
|
97
|
+
input:
|
|
98
|
+
# params, three ways: whole, a nested leaf, and an element by index.
|
|
99
|
+
day: "${params.day}"
|
|
100
|
+
align: "${params.window.align}"
|
|
101
|
+
first_region: "${params.regions.0}"
|
|
102
|
+
# An integer stays an integer, because the reference is the entire value.
|
|
103
|
+
typed_count: "${params.count}"
|
|
104
|
+
# The same reference inside a larger string is text, and reads as text downstream.
|
|
105
|
+
interpolated: "loading ${params.count} regions for ${params.day}"
|
|
106
|
+
# run: the two values every run has.
|
|
107
|
+
run_id: "${run.id}"
|
|
108
|
+
scratch: "${run.scratch}"
|
|
109
|
+
# run.window: only on a run that carries one. Half-open, so start is included and
|
|
110
|
+
# end is not.
|
|
111
|
+
window_start: "${run.window.start}"
|
|
112
|
+
window_end: "${run.window.end}"
|
|
113
|
+
program: |
|
|
114
|
+
.
|
|
115
|
+
|
|
116
|
+
per_region:
|
|
117
|
+
block: transform.jq
|
|
118
|
+
# for_each is the one place outside config where references resolve.
|
|
119
|
+
for_each: "${params.regions}"
|
|
120
|
+
depends_on: [namespaces]
|
|
121
|
+
config:
|
|
122
|
+
input:
|
|
123
|
+
# item: this run item's element. Outside a fan-out this reference does not resolve.
|
|
124
|
+
region: "${item}"
|
|
125
|
+
# A step's whole output, and one field of it, from inside a fan-out.
|
|
126
|
+
day: "${steps.namespaces.output.value.day}"
|
|
127
|
+
program: |
|
|
128
|
+
{region, day}
|
|
129
|
+
|
|
130
|
+
paired_region:
|
|
131
|
+
block: transform.jq
|
|
132
|
+
depends_on: [per_region]
|
|
133
|
+
# steps.<key>.items is the grid per_region maps over, so this step gets that grid rather
|
|
134
|
+
# than one of its own and the two are paired by item position.
|
|
135
|
+
for_each: "${steps.per_region.items}"
|
|
136
|
+
config:
|
|
137
|
+
input:
|
|
138
|
+
# ...which is what lets this read the MATCHING item's output instead of the whole
|
|
139
|
+
# list. fan-out-item-wise.yaml is this on its own.
|
|
140
|
+
region: "${steps.per_region.item.output.value.region}"
|
|
141
|
+
# ${item} is the same element it was in per_region.
|
|
142
|
+
element: "${item}"
|
|
143
|
+
program: |
|
|
144
|
+
{region, element}
|
|
145
|
+
|
|
146
|
+
per_feed:
|
|
147
|
+
block: transform.jq
|
|
148
|
+
# A literal list of objects, so item.<path> has somewhere to reach.
|
|
149
|
+
for_each:
|
|
150
|
+
- {code: cases, weight: 2}
|
|
151
|
+
- {code: climate, weight: 1}
|
|
152
|
+
depends_on: [namespaces]
|
|
153
|
+
config:
|
|
154
|
+
input:
|
|
155
|
+
# item.<path>: a field of an element that is an object. A field the element does not
|
|
156
|
+
# carry raises here rather than resolving to an empty string.
|
|
157
|
+
feed: "${item.code}"
|
|
158
|
+
weight: "${item.weight}"
|
|
159
|
+
# ${item} on its own is still the whole element.
|
|
160
|
+
element: "${item}"
|
|
161
|
+
program: |
|
|
162
|
+
{feed, weight, element}
|
|
163
|
+
|
|
164
|
+
collect:
|
|
165
|
+
block: transform.jq
|
|
166
|
+
depends_on: [per_region, paired_region, per_feed, namespaces]
|
|
167
|
+
config:
|
|
168
|
+
input:
|
|
169
|
+
# A fan-out step's whole output: a JSON array of the items' outputs, in item order.
|
|
170
|
+
every_item: "${steps.per_region.output}"
|
|
171
|
+
# One element of it, by index. Under items: continue a failed item is absent from the
|
|
172
|
+
# list, so index 0 is the first item that WORKED rather than the first one asked for.
|
|
173
|
+
first_item: "${steps.per_region.output.0.value.region}"
|
|
174
|
+
paired: "${steps.paired_region.output}"
|
|
175
|
+
feeds: "${steps.per_feed.output}"
|
|
176
|
+
# A whole step output, unindexed, so the object arrives intact.
|
|
177
|
+
namespaces: "${steps.namespaces.output.value}"
|
|
178
|
+
program: |
|
|
179
|
+
{
|
|
180
|
+
regions: [.every_item[].value.region],
|
|
181
|
+
paired: [.paired[].value.region],
|
|
182
|
+
feeds: [.feeds[].value.feed],
|
|
183
|
+
first_item,
|
|
184
|
+
covered: "\(.namespaces.window_start)..\(.namespaces.window_end)",
|
|
185
|
+
typed_count_is_a_number: (.namespaces.typed_count | type)
|
|
186
|
+
}
|