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,111 @@
|
|
|
1
|
+
# Two fan-outs over one grid: item three of the second reads item three of the first.
|
|
2
|
+
#
|
|
3
|
+
# A fanned step's output is the list of its items' outputs, so a step that depends on a
|
|
4
|
+
# fan-out sees the whole batch. That is right for a join and wrong for everything that is
|
|
5
|
+
# still per item: writing one file per district, calling one endpoint per region, sending one
|
|
6
|
+
# message per order. Those want the matching item, not the list.
|
|
7
|
+
#
|
|
8
|
+
# for_each: ${steps.<name>.items} is that. The step maps over the grid the named fan-out
|
|
9
|
+
# already has instead of a grid of its own, and the two are paired by position: item 3 here is
|
|
10
|
+
# item 3 there, ${item} is the same element in both, and ${steps.<name>.item.output} is the
|
|
11
|
+
# matching item's output.
|
|
12
|
+
#
|
|
13
|
+
# Three things the pairing buys, and one it costs:
|
|
14
|
+
#
|
|
15
|
+
# 1. Cardinality is still fixed when the run is created. The grid comes from a step that was
|
|
16
|
+
# itself expanded then, so nothing here waits on execution to know how wide it is.
|
|
17
|
+
# 2. The names must be a direct dependency: the adopted step goes in depends_on. Pairing
|
|
18
|
+
# across a step that is not waited for would read an item that has not run.
|
|
19
|
+
# 3. Adoption chains. A third step may map over the second's items and still reach the
|
|
20
|
+
# first's, because they are all one grid.
|
|
21
|
+
# 4. The cost: an item whose match did not succeed is SKIPPED, not failed and not run. Its
|
|
22
|
+
# input never existed, so there is nothing to attempt. The rest of the grid carries on.
|
|
23
|
+
#
|
|
24
|
+
# Hop by hop:
|
|
25
|
+
#
|
|
26
|
+
# shape transform.jq, once per region. Under items: continue a region that refuses is
|
|
27
|
+
# recorded against its own item and the others finish.
|
|
28
|
+
# write_one storage.write, mapping over shape's grid rather than a list of its own. One
|
|
29
|
+
# file per region, named for ${item}, holding ${steps.shape.item.output.value} --
|
|
30
|
+
# that region's own object, not the batch.
|
|
31
|
+
# manifest the join, with no for_each at all: one attempt over the whole list of files.
|
|
32
|
+
#
|
|
33
|
+
# EXPECT THIS RUN TO SUCCEED in about a second, with three files written and a manifest naming
|
|
34
|
+
# all three.
|
|
35
|
+
#
|
|
36
|
+
# To change it: -p refuse=west makes shape's west item fail, which skips write_one's west item
|
|
37
|
+
# rather than failing it, leaves the other two written, and ends the run
|
|
38
|
+
# completed_with_errors with a manifest of two files.
|
|
39
|
+
#
|
|
40
|
+
# dg run --local examples/patterns/fan-out-item-wise.yaml
|
|
41
|
+
# dg run --local examples/patterns/fan-out-item-wise.yaml -p refuse=west # completed_with_errors
|
|
42
|
+
|
|
43
|
+
format: dirigent/v1
|
|
44
|
+
kind: pipeline
|
|
45
|
+
code: fan-out-item-wise
|
|
46
|
+
name: One item to one item
|
|
47
|
+
description: |
|
|
48
|
+
`for_each: ${steps.<name>.items}` maps a step over another fan-out's grid, so the two are
|
|
49
|
+
paired by item position and `${steps.<name>.item.output}` is the matching item's output.
|
|
50
|
+
|
|
51
|
+
An item whose match did not succeed is skipped rather than failed, and the rest of the grid
|
|
52
|
+
carries on.
|
|
53
|
+
|
|
54
|
+
tags: [patterns, storage, transform, fan-out, references, starter]
|
|
55
|
+
|
|
56
|
+
requires:
|
|
57
|
+
blocks:
|
|
58
|
+
- storage.write
|
|
59
|
+
- transform.jq
|
|
60
|
+
|
|
61
|
+
params:
|
|
62
|
+
type: object
|
|
63
|
+
properties:
|
|
64
|
+
regions:
|
|
65
|
+
type: array
|
|
66
|
+
default: [east, west, north]
|
|
67
|
+
minItems: 1
|
|
68
|
+
maxItems: 16
|
|
69
|
+
items:
|
|
70
|
+
type: string
|
|
71
|
+
description: One item, one file, per element.
|
|
72
|
+
refuse:
|
|
73
|
+
type: string
|
|
74
|
+
default: ""
|
|
75
|
+
description: A region whose shape step fails, so its write is skipped rather than run.
|
|
76
|
+
|
|
77
|
+
steps:
|
|
78
|
+
shape:
|
|
79
|
+
block: transform.jq
|
|
80
|
+
for_each: "${params.regions}"
|
|
81
|
+
items: continue
|
|
82
|
+
config:
|
|
83
|
+
input:
|
|
84
|
+
region: "${item}"
|
|
85
|
+
refuse: "${params.refuse}"
|
|
86
|
+
# error() is how a jq program fails one item deliberately; without the refuse knob this
|
|
87
|
+
# program is just the object-building half.
|
|
88
|
+
program: |
|
|
89
|
+
if .region == .refuse then error("region \(.region) refused the export")
|
|
90
|
+
else {region: .region, rows: [{station: "\(.region)-st-0", celsius: -4}]}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
write_one:
|
|
94
|
+
block: storage.write
|
|
95
|
+
depends_on: [shape]
|
|
96
|
+
# The whole point of the file: this step has no grid of its own, it takes shape's.
|
|
97
|
+
for_each: "${steps.shape.items}"
|
|
98
|
+
config:
|
|
99
|
+
# ${item} is shape's element, so two items never write the same key.
|
|
100
|
+
target: "${run.scratch}/regions/${item}.json"
|
|
101
|
+
# ...and this is that element's own output, not the list of every item's.
|
|
102
|
+
value: "${steps.shape.item.output.value}"
|
|
103
|
+
|
|
104
|
+
manifest:
|
|
105
|
+
block: transform.jq
|
|
106
|
+
depends_on: [write_one]
|
|
107
|
+
# No for_each, so this is the join: one attempt, and the batch arrives as a list.
|
|
108
|
+
config:
|
|
109
|
+
input: "${steps.write_one.output}"
|
|
110
|
+
program: |
|
|
111
|
+
{files: length, uris: [.[].uri], total_bytes: ([.[].bytes_written] | add)}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# for_each as a literal list: the smallest fan-out there is.
|
|
2
|
+
#
|
|
3
|
+
# for_each takes one of two things: a reference to a list, or a list written out in the
|
|
4
|
+
# document. This file is the second, which is the right form when the elements are part of the
|
|
5
|
+
# pipeline's shape rather than part of a run's input -- the three environments you deploy to,
|
|
6
|
+
# the four quarters, the fixed set of feeds. Nobody should have to pass those in to get an
|
|
7
|
+
# ordinary run.
|
|
8
|
+
#
|
|
9
|
+
# What a fan-out actually is: one step definition, N run items, one attempt each, all of them
|
|
10
|
+
# claimable at once. The item grid exists from the moment the run is visible, because
|
|
11
|
+
# for_each is expanded when the run is CREATED. That has a consequence worth knowing before
|
|
12
|
+
# you reach for it: for_each may read params, run, and another fan-out's grid as
|
|
13
|
+
# ${steps.<step>.items}, but never a step's output, because the cardinality has to be known
|
|
14
|
+
# before anything executes. A fan-out over a list fetched at run time is not expressible, and
|
|
15
|
+
# that is deliberate rather than missing.
|
|
16
|
+
#
|
|
17
|
+
# Hop by hop:
|
|
18
|
+
#
|
|
19
|
+
# greet transform.jq, once per name in the literal list. ${item} is the element, and
|
|
20
|
+
# here that is a plain string.
|
|
21
|
+
# collect runs ONCE, not once per item, because it has no for_each of its own. It reads
|
|
22
|
+
# ${steps.greet.output}, which is the list of the items' outputs in item order.
|
|
23
|
+
#
|
|
24
|
+
# EXPECT THIS RUN TO SUCCEED in about a second. Nothing here touches the network or runs code
|
|
25
|
+
# on the worker, so it needs no allowlist and no connection.
|
|
26
|
+
#
|
|
27
|
+
# To change it: fan-out-from-params.yaml is the same shape with the list supplied by the
|
|
28
|
+
# caller, fan-out-nested-objects.yaml is the same shape with objects as elements, and
|
|
29
|
+
# fan-out-item-wise.yaml is a second step mapping over this one's grid instead of joining it.
|
|
30
|
+
#
|
|
31
|
+
# dg run --local examples/patterns/fan-out-literal-list.yaml
|
|
32
|
+
|
|
33
|
+
format: dirigent/v1
|
|
34
|
+
kind: pipeline
|
|
35
|
+
code: fan-out-literal-list
|
|
36
|
+
name: A fan-out over a literal list
|
|
37
|
+
description: |
|
|
38
|
+
`for_each` written as a list in the document: one step definition, one run item per
|
|
39
|
+
element, all claimable at once.
|
|
40
|
+
|
|
41
|
+
A literal list is the right form when the elements are part of the pipeline's shape
|
|
42
|
+
rather than part of a run's input.
|
|
43
|
+
|
|
44
|
+
tags: [patterns, transform, fan-out, graph]
|
|
45
|
+
|
|
46
|
+
requires:
|
|
47
|
+
blocks:
|
|
48
|
+
- transform.jq
|
|
49
|
+
|
|
50
|
+
params:
|
|
51
|
+
type: object
|
|
52
|
+
properties:
|
|
53
|
+
greeting:
|
|
54
|
+
type: string
|
|
55
|
+
description: The word each item is greeted with; it is the same for every item.
|
|
56
|
+
default: hei
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
greet:
|
|
60
|
+
block: transform.jq
|
|
61
|
+
# Written out here rather than referenced, because these three are what this pipeline is
|
|
62
|
+
# about. A list that changes per run belongs in params instead.
|
|
63
|
+
for_each: [oslo, bergen, tromso]
|
|
64
|
+
config:
|
|
65
|
+
input:
|
|
66
|
+
# ${item} is this run item's element. It is only in scope inside a step that fans
|
|
67
|
+
# out; anywhere else it is an unknown reference and the attempt is rejected.
|
|
68
|
+
city: "${item}"
|
|
69
|
+
greeting: "${params.greeting}"
|
|
70
|
+
program: |
|
|
71
|
+
{city, line: "\(.greeting), \(.city)"}
|
|
72
|
+
|
|
73
|
+
collect:
|
|
74
|
+
block: transform.jq
|
|
75
|
+
depends_on: [greet]
|
|
76
|
+
# One attempt, not three. A step joins a fan-out simply by having no for_each of its own.
|
|
77
|
+
config:
|
|
78
|
+
# The list of the items' outputs, in item order.
|
|
79
|
+
input: "${steps.greet.output}"
|
|
80
|
+
program: |
|
|
81
|
+
{lines: [.[].value.line], cities: length}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# An item that is an object, and the fields of it a step reaches into.
|
|
2
|
+
#
|
|
3
|
+
# An element of a for_each list is any JSON value, and once it is an object the item stops
|
|
4
|
+
# being a label and becomes a small record. ${item.code} reaches a field, ${item.limits.max_ms}
|
|
5
|
+
# reaches a nested one, and ${item} on its own is still the whole object -- so one element can
|
|
6
|
+
# carry everything one item's work needs instead of forcing three parallel lists that have to
|
|
7
|
+
# stay in the same order.
|
|
8
|
+
#
|
|
9
|
+
# The reference rule that makes this work: a reference that is the ENTIRE value resolves to
|
|
10
|
+
# the typed value, so "${item.limits.max_ms}" is the integer 2000 downstream and not the text
|
|
11
|
+
# "2000". A reference inside a larger string interpolates as text instead, which is why the
|
|
12
|
+
# URL below reads as a URL and the threshold reads as a number.
|
|
13
|
+
#
|
|
14
|
+
# A missing field is not an empty string. ${item.nickname} on an element that has no nickname
|
|
15
|
+
# raises, and the engine settles that attempt as rejected rather than quietly proceeding with
|
|
16
|
+
# nothing -- so every element in the list must carry every field the step names. Give the
|
|
17
|
+
# optional ones a default in the list itself.
|
|
18
|
+
#
|
|
19
|
+
# Hop by hop:
|
|
20
|
+
#
|
|
21
|
+
# probe one item per feed. Each item posts its own code and its own latency budget, so
|
|
22
|
+
# the three items do genuinely different work from one step definition. Postman
|
|
23
|
+
# Echo answers with what it was sent, which is how each item's element comes back
|
|
24
|
+
# attached to that item's output.
|
|
25
|
+
# verdict a join over the batch, comparing each feed's measured round trip against the
|
|
26
|
+
# budget that travelled with it.
|
|
27
|
+
#
|
|
28
|
+
# EXPECT THIS RUN TO SUCCEED in about two seconds, with three feeds and a verdict each. The
|
|
29
|
+
# items run side by side, so the run costs one round trip rather than three.
|
|
30
|
+
#
|
|
31
|
+
# To change it: add a feed to the list, or lower a budget below the real round trip and watch
|
|
32
|
+
# that feed's verdict flip to slow. The budget lives on the element rather than in params
|
|
33
|
+
# because it belongs to one feed and not to the run.
|
|
34
|
+
#
|
|
35
|
+
# dg run --local examples/patterns/fan-out-nested-objects.yaml
|
|
36
|
+
|
|
37
|
+
format: dirigent/v1
|
|
38
|
+
kind: pipeline
|
|
39
|
+
code: fan-out-nested-objects
|
|
40
|
+
name: Items that are objects
|
|
41
|
+
description: |
|
|
42
|
+
A `for_each` element is any JSON value. Once it is an object, `${item.code}` reaches a
|
|
43
|
+
field and `${item.limits.max_ms}` a nested one, so one element carries everything one item's
|
|
44
|
+
work needs.
|
|
45
|
+
|
|
46
|
+
A whole reference resolves to the typed value; a missing field is a rejected attempt, not
|
|
47
|
+
an empty string.
|
|
48
|
+
|
|
49
|
+
tags: [patterns, http, transform, fan-out, references]
|
|
50
|
+
|
|
51
|
+
requires:
|
|
52
|
+
blocks:
|
|
53
|
+
- http.request
|
|
54
|
+
- transform.jq
|
|
55
|
+
|
|
56
|
+
params:
|
|
57
|
+
type: object
|
|
58
|
+
properties:
|
|
59
|
+
tolerance_ms:
|
|
60
|
+
type: integer
|
|
61
|
+
description: Slack added to every feed's own threshold before the verdict is taken.
|
|
62
|
+
default: 500
|
|
63
|
+
minimum: 0
|
|
64
|
+
maximum: 5000
|
|
65
|
+
|
|
66
|
+
steps:
|
|
67
|
+
probe:
|
|
68
|
+
block: http.request
|
|
69
|
+
# Three records, not three parallel lists. Nothing has to stay in step with anything.
|
|
70
|
+
for_each:
|
|
71
|
+
- {code: cases, path: post, limits: {max_ms: 2000}}
|
|
72
|
+
- {code: climate, path: post, limits: {max_ms: 2500}}
|
|
73
|
+
- {code: population, path: post, limits: {max_ms: 400}}
|
|
74
|
+
config:
|
|
75
|
+
# Interpolated into a larger string, so the field is rendered as text inside the URL.
|
|
76
|
+
url: "https://postman-echo.com/${item.path}"
|
|
77
|
+
method: POST
|
|
78
|
+
body:
|
|
79
|
+
feed: "${item.code}"
|
|
80
|
+
# The nested field, carried in the request so it comes back in the echo. That is how a
|
|
81
|
+
# per-item value reaches the join: the join sees outputs, not elements, so anything an
|
|
82
|
+
# element knows has to be inside an item's output to survive the fan-in. A whole
|
|
83
|
+
# reference stays typed, so this arrives as the integer it was written as.
|
|
84
|
+
max_ms: "${item.limits.max_ms}"
|
|
85
|
+
|
|
86
|
+
verdict:
|
|
87
|
+
block: transform.jq
|
|
88
|
+
depends_on: [probe]
|
|
89
|
+
config:
|
|
90
|
+
input:
|
|
91
|
+
measured: "${steps.probe.output}"
|
|
92
|
+
tolerance_ms: "${params.tolerance_ms}"
|
|
93
|
+
# json is the typed echo of the body that was posted, so max_ms is still a number here
|
|
94
|
+
# and needs no tonumber. A query string would have come back as text.
|
|
95
|
+
program: |
|
|
96
|
+
.tolerance_ms as $tolerance
|
|
97
|
+
| {
|
|
98
|
+
feeds: [
|
|
99
|
+
.measured[]
|
|
100
|
+
| {
|
|
101
|
+
feed: .body.json.feed,
|
|
102
|
+
duration_ms,
|
|
103
|
+
budget_ms: (.body.json.max_ms + $tolerance)
|
|
104
|
+
}
|
|
105
|
+
| .verdict = (if .duration_ms <= .budget_ms then "within budget" else "slow" end)
|
|
106
|
+
]
|
|
107
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Fan out, then reduce: N items in, one aggregate out, and no keyword for either.
|
|
2
|
+
#
|
|
3
|
+
# The join is not a construct. A step joins a fan-out by depending on it and NOT declaring a
|
|
4
|
+
# for_each of its own -- that is the whole mechanism, and it is why nothing here says "join"
|
|
5
|
+
# or "reduce". One step definition, one attempt, and the batch arrives as a list.
|
|
6
|
+
#
|
|
7
|
+
# Three facts about what the join sees:
|
|
8
|
+
#
|
|
9
|
+
# 1. ${steps.measure.output} is a JSON array of the items' outputs, in item order.
|
|
10
|
+
# 2. It is a value like any other, so a jq program reduces it the way jq reduces anything.
|
|
11
|
+
# 3. Under items: continue a failed item is absent from that array, so the join must compute
|
|
12
|
+
# from what is there rather than from what was asked for. add/length below is right
|
|
13
|
+
# whatever survived; dividing by a hard-coded four would not be.
|
|
14
|
+
#
|
|
15
|
+
# Hop by hop:
|
|
16
|
+
#
|
|
17
|
+
# measure a literal list of station readings, fanned out. Each item scales one reading and
|
|
18
|
+
# emits a small object. This stands in for a fetch, and it needs no network, so the
|
|
19
|
+
# aggregation is what you watch rather than the HTTP.
|
|
20
|
+
# summary the join. One attempt, the whole array, count and min and max and mean out of it.
|
|
21
|
+
#
|
|
22
|
+
# EXPECT THIS RUN TO SUCCEED in about a second, with a summary over four stations.
|
|
23
|
+
#
|
|
24
|
+
# To change it: add a station to the literal list and nothing else needs editing -- the join is
|
|
25
|
+
# already written for whatever arrives. fan-in.yaml on the graph/ shelf is the same shape
|
|
26
|
+
# shipping the raw batch onward instead of reducing it.
|
|
27
|
+
#
|
|
28
|
+
# dg run --local examples/patterns/fan-out-then-join.yaml
|
|
29
|
+
# dg run --local examples/patterns/fan-out-then-join.yaml -p scale=2
|
|
30
|
+
|
|
31
|
+
format: dirigent/v1
|
|
32
|
+
kind: pipeline
|
|
33
|
+
code: fan-out-then-join
|
|
34
|
+
name: Fan out, then reduce
|
|
35
|
+
description: |
|
|
36
|
+
A step joins a fan-out by depending on it and having no `for_each` of its own: one attempt,
|
|
37
|
+
and the batch arrives as a JSON array of the items' outputs.
|
|
38
|
+
|
|
39
|
+
The aggregate is computed from what is in that array, never from the width that was asked
|
|
40
|
+
for.
|
|
41
|
+
|
|
42
|
+
tags: [patterns, transform, fan-out, graph]
|
|
43
|
+
|
|
44
|
+
requires:
|
|
45
|
+
blocks:
|
|
46
|
+
- transform.jq
|
|
47
|
+
|
|
48
|
+
params:
|
|
49
|
+
type: object
|
|
50
|
+
properties:
|
|
51
|
+
scale:
|
|
52
|
+
type: number
|
|
53
|
+
description: A factor every reading is multiplied by before the summary reduces them.
|
|
54
|
+
default: 1.0
|
|
55
|
+
minimum: 0.1
|
|
56
|
+
maximum: 10.0
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
measure:
|
|
60
|
+
block: transform.jq
|
|
61
|
+
# Objects rather than strings, so one item carries a whole reading. ${item.station} and
|
|
62
|
+
# ${item.celsius} reach into it; fan-out-nested-objects.yaml is that on its own.
|
|
63
|
+
for_each:
|
|
64
|
+
- {station: fornebu, celsius: 4.5}
|
|
65
|
+
- {station: blindern, celsius: 3.1}
|
|
66
|
+
- {station: gardermoen, celsius: -1.2}
|
|
67
|
+
- {station: tryvann, celsius: -4.8}
|
|
68
|
+
config:
|
|
69
|
+
input:
|
|
70
|
+
station: "${item.station}"
|
|
71
|
+
# A whole reference resolves to the typed value, so this is a number downstream and
|
|
72
|
+
# not the text of one. That is what lets the program below multiply it.
|
|
73
|
+
celsius: "${item.celsius}"
|
|
74
|
+
scale: "${params.scale}"
|
|
75
|
+
program: |
|
|
76
|
+
{station, celsius: (.celsius * .scale)}
|
|
77
|
+
|
|
78
|
+
summary:
|
|
79
|
+
block: transform.jq
|
|
80
|
+
depends_on: [measure]
|
|
81
|
+
# No for_each: this is the join, and one attempt sees all four items.
|
|
82
|
+
config:
|
|
83
|
+
input: "${steps.measure.output}"
|
|
84
|
+
program: |
|
|
85
|
+
[.[].value.celsius] |
|
|
86
|
+
{stations: length, min: min, max: max, mean: (add / length)}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Turning one block family's logs up, without turning the whole run's logs up.
|
|
2
|
+
#
|
|
3
|
+
# A run keeps info and above by default. --log-level changes that FOR ONE RUN, and it is a
|
|
4
|
+
# property of the run rather than of the document -- which is the right place for it: how
|
|
5
|
+
# loudly a pipeline logs is an operational question asked while something is being
|
|
6
|
+
# investigated, not a fact about the pipeline that belongs in git and in a review.
|
|
7
|
+
#
|
|
8
|
+
# THE FLAG TAKES TWO FORMS, and it repeats:
|
|
9
|
+
#
|
|
10
|
+
# --log-level debug every block. The blunt instrument.
|
|
11
|
+
# --log-level PATTERN=LEVEL one family. The one worth learning.
|
|
12
|
+
#
|
|
13
|
+
# PATTERN is an fnmatch over the BLOCK ID, and the most specific match wins -- the longest
|
|
14
|
+
# pattern, with an exact id beating any wildcard. So:
|
|
15
|
+
#
|
|
16
|
+
# --log-level 'http.*=debug' both HTTP blocks, nothing else
|
|
17
|
+
# --log-level 'http.request=debug' one block, even alongside a looser http.* rule
|
|
18
|
+
# --log-level debug --log-level 'transform.*=warning'
|
|
19
|
+
# everything loud, except the jq steps
|
|
20
|
+
#
|
|
21
|
+
# WHY IT IS PER-FAMILY AND NOT PER-STEP: a level is about a block's own chattiness, and the
|
|
22
|
+
# same block is usually several steps. Turning http.* up is asking one implementation to
|
|
23
|
+
# explain itself, which is what an investigation actually wants.
|
|
24
|
+
#
|
|
25
|
+
# This document exists to give the flag something to bite on: three block families, several
|
|
26
|
+
# steps in each, all of them cheap. Run it once plain and once loud and compare the log lines
|
|
27
|
+
# -- and note that the RECORDS are identical either way. Only the logs change; a run's outcome
|
|
28
|
+
# never depends on how much it said about itself.
|
|
29
|
+
#
|
|
30
|
+
# Hop by hop:
|
|
31
|
+
#
|
|
32
|
+
# fetch_one, fetch_two two http.request steps, so an http.* pattern has more than one
|
|
33
|
+
# thing to affect.
|
|
34
|
+
# shape, tally two transform.jq steps, the family to turn DOWN when the HTTP is
|
|
35
|
+
# what is being investigated.
|
|
36
|
+
# settle a value.const, a third family, so a wildcard has a boundary.
|
|
37
|
+
#
|
|
38
|
+
# EXPECT THIS RUN TO SUCCEED in about two seconds, whichever levels are set.
|
|
39
|
+
#
|
|
40
|
+
# dg run --local examples/patterns/log-levels.yaml
|
|
41
|
+
# dg run --local examples/patterns/log-levels.yaml --log-level 'http.*=debug'
|
|
42
|
+
# dg run --local examples/patterns/log-levels.yaml --log-level debug --log-level 'transform.*=warning'
|
|
43
|
+
# dg run --local examples/patterns/log-levels.yaml --log-level 'http.*=debug' | dg format
|
|
44
|
+
#
|
|
45
|
+
# A schedule carries its own log levels too, which is how a nightly firing can be made
|
|
46
|
+
# permanently loud without every ad hoc run being loud with it.
|
|
47
|
+
|
|
48
|
+
format: dirigent/v1
|
|
49
|
+
kind: pipeline
|
|
50
|
+
code: log-levels
|
|
51
|
+
name: One family, turned up
|
|
52
|
+
description: |
|
|
53
|
+
`--log-level` is a **run** setting, not a document one: `debug` for everything, or
|
|
54
|
+
`PATTERN=LEVEL` for one block family, repeatable.
|
|
55
|
+
|
|
56
|
+
The pattern is an fnmatch over the block id and the most specific match wins. The records
|
|
57
|
+
a run emits are the same either way; only the log lines change.
|
|
58
|
+
|
|
59
|
+
tags: [patterns, http, transform, observability]
|
|
60
|
+
|
|
61
|
+
requires:
|
|
62
|
+
blocks:
|
|
63
|
+
- http.request
|
|
64
|
+
- transform.jq
|
|
65
|
+
- value.const
|
|
66
|
+
|
|
67
|
+
params:
|
|
68
|
+
type: object
|
|
69
|
+
properties:
|
|
70
|
+
dataset:
|
|
71
|
+
type: string
|
|
72
|
+
description: Echoed by both calls, so a debug line names which step made which request.
|
|
73
|
+
default: cases
|
|
74
|
+
|
|
75
|
+
steps:
|
|
76
|
+
fetch_one:
|
|
77
|
+
block: http.request
|
|
78
|
+
config:
|
|
79
|
+
url: https://postman-echo.com/get
|
|
80
|
+
query:
|
|
81
|
+
dataset: "${params.dataset}"
|
|
82
|
+
part: "1"
|
|
83
|
+
|
|
84
|
+
fetch_two:
|
|
85
|
+
block: http.request
|
|
86
|
+
# No edge to fetch_one, so the two run side by side and their log lines interleave --
|
|
87
|
+
# which is exactly when knowing that every line carries its step name starts to matter.
|
|
88
|
+
config:
|
|
89
|
+
url: https://postman-echo.com/get
|
|
90
|
+
query:
|
|
91
|
+
dataset: "${params.dataset}"
|
|
92
|
+
part: "2"
|
|
93
|
+
|
|
94
|
+
shape:
|
|
95
|
+
block: transform.jq
|
|
96
|
+
depends_on: [fetch_one, fetch_two]
|
|
97
|
+
config:
|
|
98
|
+
input:
|
|
99
|
+
one: "${steps.fetch_one.output.body.args}"
|
|
100
|
+
two: "${steps.fetch_two.output.body.args}"
|
|
101
|
+
program: |
|
|
102
|
+
{parts: [.one.part, .two.part], dataset: .one.dataset}
|
|
103
|
+
|
|
104
|
+
tally:
|
|
105
|
+
block: transform.jq
|
|
106
|
+
depends_on: [shape]
|
|
107
|
+
config:
|
|
108
|
+
input: "${steps.shape.output.value}"
|
|
109
|
+
program: |
|
|
110
|
+
{dataset, parts: (.parts | length)}
|
|
111
|
+
|
|
112
|
+
settle:
|
|
113
|
+
block: value.const
|
|
114
|
+
depends_on: [tally]
|
|
115
|
+
# A third family, so 'http.*' and 'transform.*' each have something they plainly do not
|
|
116
|
+
# cover.
|
|
117
|
+
config:
|
|
118
|
+
value:
|
|
119
|
+
note: the run's records do not change with its log level
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Where a value lives: in a step's output, in the run's scratch space, or in storage.
|
|
2
|
+
#
|
|
3
|
+
# A value travels in a step's output. That is the whole of it: a step reads
|
|
4
|
+
# ${steps.x.output.field} from a step it depends on, and no block reads or writes storage to
|
|
5
|
+
# move a value around. The two ends of a run are the exception, and they are blocks of their
|
|
6
|
+
# own -- storage.read is the only way a value comes in from storage, and storage.write the
|
|
7
|
+
# only way one goes out.
|
|
8
|
+
#
|
|
9
|
+
# THE ENGINE'S OWN DECISION is separate from both, and it is about size rather than about
|
|
10
|
+
# what a document said. Every successful attempt keeps its output whole, whatever its size,
|
|
11
|
+
# so a reference always resolves. Alongside it the engine files a copy: at or below
|
|
12
|
+
# inline_artifact_max (16384 bytes by default) the copy is inlined, and above it the
|
|
13
|
+
# canonical JSON is streamed to the run's scratch prefix and the attempt records the URI it
|
|
14
|
+
# went to. Nothing in this document chooses that, and nothing downstream has to know which
|
|
15
|
+
# happened.
|
|
16
|
+
#
|
|
17
|
+
# dg runs show <run-id> the attempt says where its output's copy went
|
|
18
|
+
# DIRIGENT_INLINE_ARTIFACT_MAX the instance setting that moves the line
|
|
19
|
+
#
|
|
20
|
+
# WHERE A URI MAY POINT. ${run.scratch} is this run's own directory under the instance's
|
|
21
|
+
# artifact root. file:// URIs are refused outside that root, so a stored pipeline can never
|
|
22
|
+
# be turned into an arbitrary-file read, and a run cannot write over another run's work. An
|
|
23
|
+
# s3:// URI goes to whichever connection the instance binds that scheme to.
|
|
24
|
+
#
|
|
25
|
+
# Hop by hop:
|
|
26
|
+
#
|
|
27
|
+
# fetch an http.request. The answer is a few hundred bytes, so its copy is inlined,
|
|
28
|
+
# and the next step reads .body straight out of the output.
|
|
29
|
+
# bulk jq builds four thousand rows from it. The serialised output is far over the
|
|
30
|
+
# default cap, so the engine spills that copy to the run's scratch.
|
|
31
|
+
# count reads ${steps.bulk.output.value} anyway. A spilled copy changes nothing for a
|
|
32
|
+
# reference: the attempt kept the value whole.
|
|
33
|
+
# keep storage.write, the one way out. The rows become an object with a URI.
|
|
34
|
+
# read_back storage.read, the one way back in. The object becomes a value again.
|
|
35
|
+
# compare the small answer and the round-tripped rows, side by side.
|
|
36
|
+
#
|
|
37
|
+
# EXPECT THIS RUN TO SUCCEED in about three seconds, with bulk's output spilled and every
|
|
38
|
+
# reference to it resolving regardless.
|
|
39
|
+
#
|
|
40
|
+
# dg run --local examples/patterns/outputs-inline-vs-storage.yaml
|
|
41
|
+
|
|
42
|
+
format: dirigent/v1
|
|
43
|
+
kind: pipeline
|
|
44
|
+
code: outputs-inline-vs-storage
|
|
45
|
+
name: A value, and the storage on either side of it
|
|
46
|
+
description: |
|
|
47
|
+
A value travels in a step's output. `storage.read` is the only way one comes in from
|
|
48
|
+
storage and `storage.write` the only way one goes out.
|
|
49
|
+
|
|
50
|
+
Where the engine files its copy of an output is a separate question, decided by size:
|
|
51
|
+
at or below `inline_artifact_max` (16KB by default) it is inlined on the attempt, and
|
|
52
|
+
above it spilled to the run's scratch space. A reference resolves either way.
|
|
53
|
+
|
|
54
|
+
tags: [patterns, http, storage, transform, outputs]
|
|
55
|
+
|
|
56
|
+
requires:
|
|
57
|
+
blocks:
|
|
58
|
+
- http.request
|
|
59
|
+
- transform.jq
|
|
60
|
+
- storage.write
|
|
61
|
+
- storage.read
|
|
62
|
+
|
|
63
|
+
params:
|
|
64
|
+
type: object
|
|
65
|
+
properties:
|
|
66
|
+
dataset:
|
|
67
|
+
type: string
|
|
68
|
+
description: Echoed back by the endpoint, so the answer has something recognisable in it.
|
|
69
|
+
default: cases
|
|
70
|
+
rows:
|
|
71
|
+
type: integer
|
|
72
|
+
description: How many rows the bulk step builds. Above about three hundred the output spills.
|
|
73
|
+
default: 4000
|
|
74
|
+
minimum: 1
|
|
75
|
+
|
|
76
|
+
steps:
|
|
77
|
+
fetch:
|
|
78
|
+
block: http.request
|
|
79
|
+
config:
|
|
80
|
+
url: https://postman-echo.com/get
|
|
81
|
+
query:
|
|
82
|
+
dataset: "${params.dataset}"
|
|
83
|
+
|
|
84
|
+
bulk:
|
|
85
|
+
block: transform.jq
|
|
86
|
+
depends_on: [fetch]
|
|
87
|
+
config:
|
|
88
|
+
input:
|
|
89
|
+
dataset: "${steps.fetch.output.body.args.dataset}"
|
|
90
|
+
rows: "${params.rows}"
|
|
91
|
+
program: |
|
|
92
|
+
. as {$dataset, $rows} | [range($rows) | {row: ., dataset: $dataset, seen_at: "postman-echo"}]
|
|
93
|
+
|
|
94
|
+
count:
|
|
95
|
+
block: transform.jq
|
|
96
|
+
depends_on: [bulk]
|
|
97
|
+
config:
|
|
98
|
+
# The reference is the same reference it would be for a four-field output. Whether the
|
|
99
|
+
# engine inlined the copy or spilled it is not something a document can see.
|
|
100
|
+
input: "${steps.bulk.output.value}"
|
|
101
|
+
program: |
|
|
102
|
+
{rows: length, first: .[0]}
|
|
103
|
+
|
|
104
|
+
keep:
|
|
105
|
+
block: storage.write
|
|
106
|
+
depends_on: [bulk]
|
|
107
|
+
config:
|
|
108
|
+
target: "${run.scratch}/rows/${params.dataset}.json"
|
|
109
|
+
value: "${steps.bulk.output.value}"
|
|
110
|
+
|
|
111
|
+
read_back:
|
|
112
|
+
block: storage.read
|
|
113
|
+
depends_on: [keep]
|
|
114
|
+
config:
|
|
115
|
+
source: "${steps.keep.output.uri}"
|
|
116
|
+
# This step's own guard on how much it will hold. A humane size, and an object bigger
|
|
117
|
+
# than it fails the step rather than exhausting the worker; bytes nobody has to look
|
|
118
|
+
# at move with storage.copy instead, which this does not bound.
|
|
119
|
+
max_size: 8mb
|
|
120
|
+
|
|
121
|
+
compare:
|
|
122
|
+
block: transform.jq
|
|
123
|
+
depends_on: [fetch, count, keep, read_back]
|
|
124
|
+
config:
|
|
125
|
+
input:
|
|
126
|
+
inline_dataset: "${steps.fetch.output.body.args.dataset}"
|
|
127
|
+
counted: "${steps.count.output.value.rows}"
|
|
128
|
+
written_uri: "${steps.keep.output.uri}"
|
|
129
|
+
written_bytes: "${steps.keep.output.bytes_written}"
|
|
130
|
+
read_bytes: "${steps.read_back.output.bytes_read}"
|
|
131
|
+
round_tripped: "${steps.read_back.output.value}"
|
|
132
|
+
program: |
|
|
133
|
+
{
|
|
134
|
+
inline_dataset,
|
|
135
|
+
counted,
|
|
136
|
+
written_uri,
|
|
137
|
+
written_bytes,
|
|
138
|
+
read_bytes,
|
|
139
|
+
same: (.counted == (.round_tripped | length))
|
|
140
|
+
}
|