dirigent-cli 0.26.0__tar.gz → 0.27.0__tar.gz
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_cli-0.26.0 → dirigent_cli-0.27.0}/PKG-INFO +9 -9
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/pyproject.toml +9 -9
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/pyproject.toml.orig +9 -9
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/commands.py +7 -7
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/main.py +3 -8
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/messages.py +0 -5
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/seeding.py +1 -2
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/source_commands.py +37 -3
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/summaries.py +11 -2
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/triggers.py +5 -237
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/LICENSE +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/README.md +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/__init__.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/aliases.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/context.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/explain.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/formatters.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/graph.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/health.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/init_form.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/local.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/output.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/params.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/profiles.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/project.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/py.typed +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/reaper.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/scaffold.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/schemas.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/sources.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/starters.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/stream.py +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/README.md.tmpl +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/__init__.py.tmpl +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/operator.py.tmpl +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/pyproject.toml.tmpl +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/test_plugin.py.tmpl +0 -0
- {dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/timing.py +0 -0
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirigent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: The dirigent command line interface (dirigent / dg).
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
8
|
Classifier: Programming Language :: Python :: 3.13
|
|
9
|
-
Requires-Dist: dirigent-block-execute==0.
|
|
10
|
-
Requires-Dist: dirigent-blocks==0.
|
|
11
|
-
Requires-Dist: dirigent-client==0.
|
|
12
|
-
Requires-Dist: dirigent-common==0.
|
|
13
|
-
Requires-Dist: dirigent-core==0.
|
|
14
|
-
Requires-Dist: dirigent-examples==0.
|
|
15
|
-
Requires-Dist: dirigent-plugin==0.
|
|
16
|
-
Requires-Dist: dirigent-server==0.
|
|
9
|
+
Requires-Dist: dirigent-block-execute==0.27.0
|
|
10
|
+
Requires-Dist: dirigent-blocks==0.27.0
|
|
11
|
+
Requires-Dist: dirigent-client==0.27.0
|
|
12
|
+
Requires-Dist: dirigent-common==0.27.0
|
|
13
|
+
Requires-Dist: dirigent-core==0.27.0
|
|
14
|
+
Requires-Dist: dirigent-examples==0.27.0
|
|
15
|
+
Requires-Dist: dirigent-plugin==0.27.0
|
|
16
|
+
Requires-Dist: dirigent-server==0.27.0
|
|
17
17
|
Requires-Dist: httpx2>=2.12.0
|
|
18
18
|
Requires-Dist: python-dotenv>=1.1.0
|
|
19
19
|
Requires-Dist: pyyaml>=6.0.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "The dirigent command line interface (dirigent / dg)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -11,14 +11,14 @@ classifiers = [
|
|
|
11
11
|
"Programming Language :: Python :: 3.13",
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"dirigent-block-execute==0.
|
|
15
|
-
"dirigent-blocks==0.
|
|
16
|
-
"dirigent-client==0.
|
|
17
|
-
"dirigent-common==0.
|
|
18
|
-
"dirigent-core==0.
|
|
19
|
-
"dirigent-examples==0.
|
|
20
|
-
"dirigent-plugin==0.
|
|
21
|
-
"dirigent-server==0.
|
|
14
|
+
"dirigent-block-execute==0.27.0",
|
|
15
|
+
"dirigent-blocks==0.27.0",
|
|
16
|
+
"dirigent-client==0.27.0",
|
|
17
|
+
"dirigent-common==0.27.0",
|
|
18
|
+
"dirigent-core==0.27.0",
|
|
19
|
+
"dirigent-examples==0.27.0",
|
|
20
|
+
"dirigent-plugin==0.27.0",
|
|
21
|
+
"dirigent-server==0.27.0",
|
|
22
22
|
"httpx2>=2.12.0",
|
|
23
23
|
"python-dotenv>=1.1.0",
|
|
24
24
|
"pyyaml>=6.0.3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "The dirigent command line interface (dirigent / dg)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -11,14 +11,14 @@ classifiers = [
|
|
|
11
11
|
"Programming Language :: Python :: 3.13",
|
|
12
12
|
]
|
|
13
13
|
dependencies = [
|
|
14
|
-
"dirigent-block-execute==0.
|
|
15
|
-
"dirigent-blocks==0.
|
|
16
|
-
"dirigent-client==0.
|
|
17
|
-
"dirigent-common==0.
|
|
18
|
-
"dirigent-core==0.
|
|
19
|
-
"dirigent-examples==0.
|
|
20
|
-
"dirigent-plugin==0.
|
|
21
|
-
"dirigent-server==0.
|
|
14
|
+
"dirigent-block-execute==0.27.0",
|
|
15
|
+
"dirigent-blocks==0.27.0",
|
|
16
|
+
"dirigent-client==0.27.0",
|
|
17
|
+
"dirigent-common==0.27.0",
|
|
18
|
+
"dirigent-core==0.27.0",
|
|
19
|
+
"dirigent-examples==0.27.0",
|
|
20
|
+
"dirigent-plugin==0.27.0",
|
|
21
|
+
"dirigent-server==0.27.0",
|
|
22
22
|
"httpx2>=2.12.0",
|
|
23
23
|
"python-dotenv>=1.1.0",
|
|
24
24
|
"pyyaml>=6.0.3",
|
|
@@ -140,6 +140,7 @@ from dirigent_cli.sources import (
|
|
|
140
140
|
read_path,
|
|
141
141
|
)
|
|
142
142
|
from dirigent_cli.stream import Sink, track_steps, use_scratch_prefix
|
|
143
|
+
from dirigent_cli.summaries import credential_line
|
|
143
144
|
from dirigent_cli.timing import RunProfile, attempt_timing, by_step, profile, step_timing
|
|
144
145
|
from dirigent_client import (
|
|
145
146
|
CLEAR,
|
|
@@ -315,15 +316,15 @@ def _print_plan(result: ApplyResult, origin: str) -> bool:
|
|
|
315
316
|
|
|
316
317
|
|
|
317
318
|
def _print_source_tokens(result: ApplyResult, *, base_url: str) -> None:
|
|
318
|
-
"""Print each token an apply minted for a source, once, with
|
|
319
|
+
"""Print each token or signing secret an apply minted for a source, once, with how it is presented.
|
|
319
320
|
|
|
320
|
-
|
|
321
|
+
There is no second chance to read either.
|
|
321
322
|
"""
|
|
322
323
|
for minted in result.sources.tokens:
|
|
323
324
|
console.print(f"\n source [bold]{minted.code}[/] {base_url.rstrip('/')}{minted.url_path}")
|
|
324
|
-
console.print(f"
|
|
325
|
+
console.print(f" {credential_line(minted.auth, minted.header, minted.token)}")
|
|
325
326
|
if result.sources.tokens:
|
|
326
|
-
console.print("\n[yellow]Each token is shown once.[/]
|
|
327
|
+
console.print("\n[yellow]Each token and signing secret is shown once.[/]\n")
|
|
327
328
|
|
|
328
329
|
|
|
329
330
|
def _print_graph(steps: Sequence[GraphStep], title: str = "steps") -> None:
|
|
@@ -374,9 +375,7 @@ def apply_command(
|
|
|
374
375
|
as_code: Annotated[str | None, typer.Option("--as", help="Register under a different code.")] = None,
|
|
375
376
|
paused: Annotated[
|
|
376
377
|
bool,
|
|
377
|
-
typer.Option(
|
|
378
|
-
"--paused", help="Create the schedules and watches this apply mints paused; existing ones are untouched."
|
|
379
|
-
),
|
|
378
|
+
typer.Option("--paused", help="Create the schedules this apply creates paused; existing ones are untouched."),
|
|
380
379
|
] = False,
|
|
381
380
|
prune: Annotated[
|
|
382
381
|
bool,
|
|
@@ -563,6 +562,7 @@ def validate_command(
|
|
|
563
562
|
blocks=blocks,
|
|
564
563
|
format_checker=checker,
|
|
565
564
|
target=targets.get(definition.pipeline) if isinstance(definition, TriggersDefinition) else None,
|
|
565
|
+
source_kinds=host.sources,
|
|
566
566
|
)
|
|
567
567
|
if issues:
|
|
568
568
|
emit_fact(
|
|
@@ -126,8 +126,6 @@ app.add_typer(commands.examples_app, rich_help_panel=DEFINE_PANEL)
|
|
|
126
126
|
app.add_typer(commands.schema_app, rich_help_panel=DEFINE_PANEL)
|
|
127
127
|
app.add_typer(commands.connection_app, rich_help_panel=CONNECT_PANEL)
|
|
128
128
|
app.add_typer(triggers.schedule_app, rich_help_panel=TRIGGER_PANEL)
|
|
129
|
-
app.add_typer(triggers.webhook_app, rich_help_panel=TRIGGER_PANEL)
|
|
130
|
-
app.add_typer(triggers.watch_app, rich_help_panel=TRIGGER_PANEL)
|
|
131
129
|
app.add_typer(source_commands.source_app, rich_help_panel=TRIGGER_PANEL)
|
|
132
130
|
app.add_typer(triggers.trigger_document_app, rich_help_panel=TRIGGER_PANEL)
|
|
133
131
|
app.add_typer(triggers.alerts_app, rich_help_panel=TRIGGER_PANEL)
|
|
@@ -627,7 +625,6 @@ def prune_command(
|
|
|
627
625
|
ctx: typer.Context,
|
|
628
626
|
runs: Annotated[str | None, typer.Option(help="Keep settled runs younger than this, such as 30d.")] = None,
|
|
629
627
|
logs: Annotated[str | None, typer.Option(help="Keep log entries younger than this.")] = None,
|
|
630
|
-
deliveries: Annotated[str | None, typer.Option(help="Keep webhook deliveries younger than this.")] = None,
|
|
631
628
|
inputs: Annotated[str | None, typer.Option(help="Keep the record of inputs to a source younger than this.")] = None,
|
|
632
629
|
firings: Annotated[str | None, typer.Option(help="Keep schedule firings younger than this.")] = None,
|
|
633
630
|
notifications: Annotated[str | None, typer.Option(help="Keep sent alerts younger than this.")] = None,
|
|
@@ -647,7 +644,7 @@ def prune_command(
|
|
|
647
644
|
settings = get_settings()
|
|
648
645
|
state = state_of(ctx)
|
|
649
646
|
try:
|
|
650
|
-
policy = _prune_policy(settings, runs, logs,
|
|
647
|
+
policy = _prune_policy(settings, runs, logs, inputs, firings, notifications, scratch=scratch)
|
|
651
648
|
except ValueError as error:
|
|
652
649
|
refuse(INVALID_AGE, status=2, title="Invalid age", detail=str(error))
|
|
653
650
|
raise typer.Exit(code=2) from error
|
|
@@ -658,7 +655,6 @@ def _prune_policy(
|
|
|
658
655
|
settings: Settings,
|
|
659
656
|
runs: str | None,
|
|
660
657
|
logs: str | None,
|
|
661
|
-
deliveries: str | None,
|
|
662
658
|
inputs: str | None,
|
|
663
659
|
firings: str | None,
|
|
664
660
|
notifications: str | None,
|
|
@@ -675,7 +671,6 @@ def _prune_policy(
|
|
|
675
671
|
return retention.Policy(
|
|
676
672
|
runs=age(runs, settings.retention_runs),
|
|
677
673
|
logs=age(logs, settings.retention_logs),
|
|
678
|
-
deliveries=age(deliveries, settings.retention_deliveries),
|
|
679
674
|
inputs=age(inputs, settings.retention_inputs),
|
|
680
675
|
firings=age(firings, settings.retention_firings),
|
|
681
676
|
notifications=age(notifications, settings.retention_notifications),
|
|
@@ -921,7 +916,7 @@ def dev(
|
|
|
921
916
|
typer.Option(
|
|
922
917
|
"--seed",
|
|
923
918
|
help="Apply every dirigent/v1 document under this directory once the API answers, "
|
|
924
|
-
"with its schedules
|
|
919
|
+
"with its schedules paused; name it more than once to seed one directory after another.",
|
|
925
920
|
),
|
|
926
921
|
] = None,
|
|
927
922
|
seed_installed: Annotated[
|
|
@@ -944,7 +939,7 @@ def dev(
|
|
|
944
939
|
|
|
945
940
|
--seed fills the instance from a directory of documents the moment it answers: the
|
|
946
941
|
connections a file or a document declares and the standalone JSON Schema files are created
|
|
947
|
-
first, then every document is applied with its schedules
|
|
942
|
+
first, then every document is applied with its schedules paused, and one refused
|
|
948
943
|
for naming something a later document supplies is applied again once the rest are in. A
|
|
949
944
|
document an instance will not store is reported and passed over, because a corpus holds
|
|
950
945
|
those on purpose.
|
|
@@ -232,11 +232,6 @@ ONE_CLOCK = CLI.define("one_clock", "a schedule takes exactly one of --cron, --i
|
|
|
232
232
|
|
|
233
233
|
AT_TAKES_AN_INSTANT = CLI.define("at_takes_an_instant", "--at takes an ISO instant, and {value} is not one")
|
|
234
234
|
|
|
235
|
-
MAP_TAKES_A_PATH = CLI.define(
|
|
236
|
-
"map_takes_a_path",
|
|
237
|
-
"--map takes param=$.path.into.payload, and {entry} has no '='",
|
|
238
|
-
)
|
|
239
|
-
|
|
240
235
|
NOT_AN_ALERT_EVENT = CLI.define("not_an_alert_event", "{event} is not an alert event ({allowed})")
|
|
241
236
|
|
|
242
237
|
NOT_AN_IMPORTANCE = CLI.define("not_an_importance", "--importance {value} is not an importance ({allowed})")
|
|
@@ -216,7 +216,7 @@ async def _connection(client: Dirigent, spec: ConnectionSpec) -> str:
|
|
|
216
216
|
|
|
217
217
|
|
|
218
218
|
async def _apply(client: Dirigent, raw: JsonMap, origin: str) -> Record:
|
|
219
|
-
"""Apply one document with its schedules
|
|
219
|
+
"""Apply one document with its schedules paused, without the sections it carries."""
|
|
220
220
|
document = {name: value for name, value in raw.items() if name not in CARRIED}
|
|
221
221
|
try:
|
|
222
222
|
result = await client.pipelines.apply(
|
|
@@ -237,7 +237,6 @@ async def _apply(client: Dirigent, raw: JsonMap, origin: str) -> Record:
|
|
|
237
237
|
pipeline=result.plan.code,
|
|
238
238
|
action=result.plan.action.value,
|
|
239
239
|
schedules_paused=result.triggers.schedules_created,
|
|
240
|
-
watches_paused=result.triggers.watches_created,
|
|
241
240
|
)
|
|
242
241
|
|
|
243
242
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"""The source command group: where a pipeline's inputs enter, what has arrived, and calling one."""
|
|
2
2
|
|
|
3
|
+
import hashlib
|
|
4
|
+
import hmac
|
|
3
5
|
import json
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import Annotated, Final
|
|
8
|
+
from urllib.parse import urlencode
|
|
6
9
|
|
|
7
10
|
import httpx2
|
|
8
11
|
import typer
|
|
@@ -18,8 +21,11 @@ from dirigent_plugin.sources import Route
|
|
|
18
21
|
#: Where ``dg source call`` reads the token a source was given when ``--source-token`` names none.
|
|
19
22
|
TOKEN_ENV: Final = "DIRIGENT_SOURCE_TOKEN"
|
|
20
23
|
|
|
21
|
-
#:
|
|
22
|
-
|
|
24
|
+
#: Where ``dg source call`` and ``dg source set-secret`` read a signing secret when no option names one.
|
|
25
|
+
SECRET_ENV: Final = "DIRIGENT_SOURCE_SECRET"
|
|
26
|
+
|
|
27
|
+
#: How long a call waits for its answer, past the longest an instance holds a caller by default.
|
|
28
|
+
CALL_TIMEOUT: Final = httpx2.Timeout(90.0, connect=DEFAULT_CONNECT_TIMEOUT)
|
|
23
29
|
|
|
24
30
|
source_app = typer.Typer(
|
|
25
31
|
name="source",
|
|
@@ -80,10 +86,29 @@ def source_rotate(ctx: typer.Context, pipeline: PipelineArg, code: SourceArg) ->
|
|
|
80
86
|
code=code,
|
|
81
87
|
pipeline=pipeline,
|
|
82
88
|
url=f"{base.rstrip('/')}{minted.url_path}",
|
|
89
|
+
auth=minted.auth,
|
|
90
|
+
header=minted.header,
|
|
83
91
|
token=minted.token,
|
|
84
92
|
)
|
|
85
93
|
|
|
86
94
|
|
|
95
|
+
@source_app.command("set-secret")
|
|
96
|
+
def source_set_secret(
|
|
97
|
+
ctx: typer.Context,
|
|
98
|
+
pipeline: PipelineArg,
|
|
99
|
+
code: SourceArg,
|
|
100
|
+
secret: Annotated[
|
|
101
|
+
str | None,
|
|
102
|
+
typer.Option("--secret", envvar=SECRET_ENV, help="The secret its sender issued; prompted for when unset."),
|
|
103
|
+
] = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Replace a signed source's signing secret with one its sender issued, such as a provider's own."""
|
|
106
|
+
given = secret or typer.prompt("signing secret", hide_input=True)
|
|
107
|
+
with client_for(state_of(ctx)) as dg:
|
|
108
|
+
row = dg.call(dg.sources.set_secret(pipeline, code, given))
|
|
109
|
+
emit_fact("source.secret_set", message="secret set", code=code, pipeline=pipeline, prefix=row.token_prefix)
|
|
110
|
+
|
|
111
|
+
|
|
87
112
|
@source_app.command("inputs")
|
|
88
113
|
def source_inputs(
|
|
89
114
|
ctx: typer.Context,
|
|
@@ -120,6 +145,10 @@ def source_call(
|
|
|
120
145
|
str | None,
|
|
121
146
|
typer.Option("--source-token", envvar=TOKEN_ENV, help="The source's own token, not the CLI's credential."),
|
|
122
147
|
] = None,
|
|
148
|
+
signing_secret: Annotated[
|
|
149
|
+
str | None,
|
|
150
|
+
typer.Option("--signing-secret", envvar=SECRET_ENV, help="The secret a signed source's body is signed with."),
|
|
151
|
+
] = None,
|
|
123
152
|
) -> None:
|
|
124
153
|
"""Call a received source the way its callers do, and write down what it answered.
|
|
125
154
|
|
|
@@ -143,6 +172,12 @@ def source_call(
|
|
|
143
172
|
elif text is not None:
|
|
144
173
|
content = _read(text).encode()
|
|
145
174
|
headers["Content-Type"] = "text/plain; charset=utf-8"
|
|
175
|
+
elif form is not None:
|
|
176
|
+
content = urlencode(_pairs(form)).encode()
|
|
177
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
178
|
+
if signing_secret and row.signature_header:
|
|
179
|
+
digest = hmac.new(signing_secret.encode(), content or b"", hashlib.sha256).hexdigest()
|
|
180
|
+
headers[row.signature_header] = f"sha256={digest}"
|
|
146
181
|
try:
|
|
147
182
|
with httpx2.Client(timeout=CALL_TIMEOUT) as http:
|
|
148
183
|
answered = http.request(
|
|
@@ -151,7 +186,6 @@ def source_call(
|
|
|
151
186
|
headers=headers,
|
|
152
187
|
params=_pairs(query),
|
|
153
188
|
content=content,
|
|
154
|
-
data=_pairs(form) if form is not None else None,
|
|
155
189
|
)
|
|
156
190
|
except httpx2.HTTPError as error:
|
|
157
191
|
fail(SOURCE_UNREACHABLE, url=url, detail=str(error))
|
|
@@ -256,15 +256,24 @@ def _source_token(record: Record) -> RenderableType | None:
|
|
|
256
256
|
token = record.get("token")
|
|
257
257
|
if not token:
|
|
258
258
|
return None
|
|
259
|
+
auth = str(record.get("auth") or "token")
|
|
260
|
+
header = record.get("header")
|
|
259
261
|
return Group(
|
|
260
262
|
"",
|
|
261
263
|
f" [bold]{escape(str(record.get('url') or ''))}[/]",
|
|
262
|
-
f" [bold]
|
|
264
|
+
f" [bold]{credential_line(auth, str(header) if header else None, escape(str(token)))}[/]",
|
|
263
265
|
"",
|
|
264
|
-
"[yellow]This is the only time
|
|
266
|
+
"[yellow]This is the only time it is shown.[/]",
|
|
265
267
|
)
|
|
266
268
|
|
|
267
269
|
|
|
270
|
+
def credential_line(auth: str, header: str | None, secret: str) -> str:
|
|
271
|
+
"""Say how a caller presents a source's credential: a bearer token, or a signature keyed with a secret."""
|
|
272
|
+
if auth == "hmac":
|
|
273
|
+
return f"{header}: sha256=<HMAC-SHA256 of the body, hex>, keyed with {secret}"
|
|
274
|
+
return f"Authorization: Bearer {secret}"
|
|
275
|
+
|
|
276
|
+
|
|
268
277
|
def _files(record: Record) -> list[RenderableType]:
|
|
269
278
|
"""List what was written, named against the directory it was written into."""
|
|
270
279
|
written = _texts(record, "files")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""The trigger and alerting command groups: schedules
|
|
1
|
+
"""The trigger and alerting command groups: schedules and alert rules."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Annotated, Any, cast
|
|
@@ -9,7 +9,6 @@ from dirigent_cli.commands import paged, parse_importance, parse_log_levels, par
|
|
|
9
9
|
from dirigent_cli.context import Session, client_for, state_of
|
|
10
10
|
from dirigent_cli.messages import (
|
|
11
11
|
AT_TAKES_AN_INSTANT,
|
|
12
|
-
MAP_TAKES_A_PATH,
|
|
13
12
|
NOT_AN_ALERT_EVENT,
|
|
14
13
|
ONE_BODY,
|
|
15
14
|
ONE_CLOCK,
|
|
@@ -28,18 +27,12 @@ from dirigent_cli.output import (
|
|
|
28
27
|
throttle_note,
|
|
29
28
|
watching,
|
|
30
29
|
)
|
|
31
|
-
from dirigent_client import LOG_NOTIFIER, TEST_SUBJECT, AlertEvent, AlertScope
|
|
30
|
+
from dirigent_client import LOG_NOTIFIER, TEST_SUBJECT, AlertEvent, AlertScope
|
|
32
31
|
from dirigent_common import Message
|
|
33
32
|
|
|
34
33
|
schedule_app = typer.Typer(
|
|
35
34
|
name="schedule", help="A pipeline's clocks: cron, interval, or one-time.", no_args_is_help=True
|
|
36
35
|
)
|
|
37
|
-
webhook_app = typer.Typer(name="webhook", help="Inbound webhooks and their delivery history.", no_args_is_help=True)
|
|
38
|
-
watch_app = typer.Typer(
|
|
39
|
-
name="watch",
|
|
40
|
-
help="Sensors kept waiting: one run always waiting on each, the next armed on success.",
|
|
41
|
-
no_args_is_help=True,
|
|
42
|
-
)
|
|
43
36
|
trigger_document_app = typer.Typer(
|
|
44
37
|
name="trigger-document",
|
|
45
38
|
help="Documents that declare clocks for a pipeline defined elsewhere.",
|
|
@@ -251,222 +244,6 @@ def schedule_delete(
|
|
|
251
244
|
emit_fact("schedule.deleted", message="deleted", code=code, pipeline=pipeline)
|
|
252
245
|
|
|
253
246
|
|
|
254
|
-
@watch_app.command("list")
|
|
255
|
-
def watch_list(
|
|
256
|
-
ctx: typer.Context,
|
|
257
|
-
pipeline: Annotated[str, typer.Argument(help="The pipeline whose watches to list.")],
|
|
258
|
-
) -> None:
|
|
259
|
-
"""List a pipeline's watches, each with the run it has waiting and how its waits are going."""
|
|
260
|
-
with client_for(state_of(ctx)) as dg:
|
|
261
|
-
rows = list(paged(lambda after, size: dg.call(dg.watches.list(pipeline, after=after, limit=size)), None))
|
|
262
|
-
emit_records("watch", rows)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
@watch_app.command("show")
|
|
266
|
-
def watch_show(
|
|
267
|
-
ctx: typer.Context,
|
|
268
|
-
pipeline: Annotated[str, typer.Argument(help="The pipeline the watch belongs to.")],
|
|
269
|
-
code: Annotated[str, typer.Argument(help="The watch to show.")],
|
|
270
|
-
) -> None:
|
|
271
|
-
"""Show one watch: the step it waits on, where it left off, and its last error."""
|
|
272
|
-
with client_for(state_of(ctx)) as dg:
|
|
273
|
-
row = dg.call(dg.watches.get(pipeline, code))
|
|
274
|
-
emit_one("watch", row)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
@watch_app.command("pause")
|
|
278
|
-
def watch_pause(
|
|
279
|
-
ctx: typer.Context,
|
|
280
|
-
pipeline: Annotated[str, typer.Argument()],
|
|
281
|
-
code: Annotated[str, typer.Argument()],
|
|
282
|
-
) -> None:
|
|
283
|
-
"""Stop a watch, cancelling the run it has waiting, and keep where it left off."""
|
|
284
|
-
with client_for(state_of(ctx)) as dg:
|
|
285
|
-
row = dg.call(dg.watches.pause(pipeline, code))
|
|
286
|
-
emit_fact("watch.paused", message="paused", code=code, pipeline=pipeline, cursor=row.cursor)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
@watch_app.command("resume")
|
|
290
|
-
def watch_resume(
|
|
291
|
-
ctx: typer.Context,
|
|
292
|
-
pipeline: Annotated[str, typer.Argument()],
|
|
293
|
-
code: Annotated[str, typer.Argument()],
|
|
294
|
-
) -> None:
|
|
295
|
-
"""Start a watch again, arming a run from where its last success left off."""
|
|
296
|
-
with client_for(state_of(ctx)) as dg:
|
|
297
|
-
row = dg.call(dg.watches.resume(pipeline, code))
|
|
298
|
-
emit_fact(
|
|
299
|
-
"watch.resumed",
|
|
300
|
-
message="resumed",
|
|
301
|
-
code=code,
|
|
302
|
-
pipeline=pipeline,
|
|
303
|
-
waiting_run_id=row.waiting_run_id,
|
|
304
|
-
cursor=row.cursor,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
def _print_token(minted: WebhookTokenOut, *, base_url: str) -> None:
|
|
309
|
-
"""Print a minted token once, with the URL already assembled.
|
|
310
|
-
|
|
311
|
-
The instance stores only the token's hash; there is no second chance to read it.
|
|
312
|
-
"""
|
|
313
|
-
console.print(f"\n POST [bold]{base_url.rstrip('/')}{minted.url_path}[/]")
|
|
314
|
-
console.print(f" Token [bold]{minted.token}[/]")
|
|
315
|
-
console.print("\n[yellow]This token is shown once.[/] The instance stores only its hash.\n")
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
@webhook_app.command("create")
|
|
319
|
-
def webhook_create(
|
|
320
|
-
ctx: typer.Context,
|
|
321
|
-
pipeline: Annotated[str, typer.Argument(help="The pipeline this webhook starts.")],
|
|
322
|
-
code: Annotated[str, typer.Argument(help="What to call it; unique within the pipeline.")],
|
|
323
|
-
name: Annotated[str | None, typer.Option("--name", help="A human title for this webhook.")] = None,
|
|
324
|
-
description: Annotated[str | None, typer.Option("--description", help="What this webhook is for.")] = None,
|
|
325
|
-
map_value: Annotated[
|
|
326
|
-
list[str] | None,
|
|
327
|
-
typer.Option("--map", help="param=$.path.into.payload, repeatable."),
|
|
328
|
-
] = None,
|
|
329
|
-
hmac_secret: Annotated[
|
|
330
|
-
str | None,
|
|
331
|
-
typer.Option("--hmac-secret", help="Require an X-Dirigent-Signature over the raw body."),
|
|
332
|
-
] = None,
|
|
333
|
-
rate_limit: Annotated[int, typer.Option("--rate-limit", help="Deliveries a minute this token may make.")] = 60,
|
|
334
|
-
priority: Annotated[
|
|
335
|
-
str | None,
|
|
336
|
-
typer.Option(
|
|
337
|
-
"--priority",
|
|
338
|
-
help="How far ahead of other runs an accepted delivery's run is claimed: low, normal, or high. "
|
|
339
|
-
"Omitted takes the pipeline's own.",
|
|
340
|
-
),
|
|
341
|
-
] = None,
|
|
342
|
-
) -> None:
|
|
343
|
-
"""Declare a webhook and print its token once, with the URL to POST to."""
|
|
344
|
-
mapping: dict[str, str] = {}
|
|
345
|
-
for entry in map_value or []:
|
|
346
|
-
if "=" not in entry:
|
|
347
|
-
_fail(MAP_TAKES_A_PATH, entry=repr(entry))
|
|
348
|
-
key, path = entry.split("=", 1)
|
|
349
|
-
mapping[key.strip()] = path.strip()
|
|
350
|
-
with client_for(state_of(ctx)) as dg:
|
|
351
|
-
minted = dg.call(
|
|
352
|
-
dg.webhooks.create(
|
|
353
|
-
pipeline,
|
|
354
|
-
code,
|
|
355
|
-
name=name,
|
|
356
|
-
description=description,
|
|
357
|
-
params_from_payload=mapping,
|
|
358
|
-
hmac_secret=hmac_secret,
|
|
359
|
-
rate_limit_per_minute=rate_limit,
|
|
360
|
-
priority=parse_priority(priority),
|
|
361
|
-
)
|
|
362
|
-
)
|
|
363
|
-
base = dg.url
|
|
364
|
-
if state_of(ctx).json_output:
|
|
365
|
-
return emit_fact(
|
|
366
|
-
"webhook.created",
|
|
367
|
-
message="created",
|
|
368
|
-
code=minted.code,
|
|
369
|
-
pipeline=pipeline,
|
|
370
|
-
url=f"{base.rstrip('/')}{minted.url_path}",
|
|
371
|
-
token=minted.token,
|
|
372
|
-
)
|
|
373
|
-
console.print(f"[green]created[/] webhook [bold]{minted.code}[/] on {pipeline}")
|
|
374
|
-
_print_token(minted, base_url=base)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
@webhook_app.command("list")
|
|
378
|
-
def webhook_list(
|
|
379
|
-
ctx: typer.Context,
|
|
380
|
-
pipeline: Annotated[str, typer.Argument(help="The pipeline whose webhooks to list.")],
|
|
381
|
-
) -> None:
|
|
382
|
-
"""List a pipeline's webhooks, with their mapping but never their tokens."""
|
|
383
|
-
with client_for(state_of(ctx)) as dg:
|
|
384
|
-
rows = list(paged(lambda after, size: dg.call(dg.webhooks.list(pipeline, after=after, limit=size)), None))
|
|
385
|
-
if state_of(ctx).json_output:
|
|
386
|
-
return emit_records("webhook", rows)
|
|
387
|
-
table(
|
|
388
|
-
f"webhooks of {pipeline}",
|
|
389
|
-
["code", "name", "token", "signed", "active", "limit/min", "maps", "last delivery"],
|
|
390
|
-
[
|
|
391
|
-
[
|
|
392
|
-
row.code,
|
|
393
|
-
row.name or "-",
|
|
394
|
-
f"{row.token_prefix}...",
|
|
395
|
-
render_bool(row.signed),
|
|
396
|
-
render_bool(row.active),
|
|
397
|
-
str(row.rate_limit_per_minute),
|
|
398
|
-
", ".join(sorted(row.params_from_payload)) or "-",
|
|
399
|
-
moment(row.last_delivery_at),
|
|
400
|
-
]
|
|
401
|
-
for row in rows
|
|
402
|
-
],
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
@webhook_app.command("rotate-token")
|
|
407
|
-
def webhook_rotate(
|
|
408
|
-
ctx: typer.Context,
|
|
409
|
-
pipeline: Annotated[str, typer.Argument()],
|
|
410
|
-
code: Annotated[str, typer.Argument()],
|
|
411
|
-
) -> None:
|
|
412
|
-
"""Mint a new token and forget the old one; every caller has to be updated."""
|
|
413
|
-
with client_for(state_of(ctx)) as dg:
|
|
414
|
-
minted = dg.call(dg.webhooks.rotate_token(pipeline, code))
|
|
415
|
-
base = dg.url
|
|
416
|
-
if state_of(ctx).json_output:
|
|
417
|
-
return emit_fact(
|
|
418
|
-
"webhook.token_rotated",
|
|
419
|
-
message="rotated",
|
|
420
|
-
code=code,
|
|
421
|
-
pipeline=pipeline,
|
|
422
|
-
url=f"{base.rstrip('/')}{minted.url_path}",
|
|
423
|
-
token=minted.token,
|
|
424
|
-
)
|
|
425
|
-
console.print(f"[green]rotated[/] the token of webhook [bold]{code}[/]; the previous one no longer works")
|
|
426
|
-
_print_token(minted, base_url=base)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
@webhook_app.command("deliveries")
|
|
430
|
-
def webhook_deliveries(
|
|
431
|
-
ctx: typer.Context,
|
|
432
|
-
pipeline: Annotated[str, typer.Argument()],
|
|
433
|
-
code: Annotated[str, typer.Argument()],
|
|
434
|
-
) -> None:
|
|
435
|
-
"""Show what has arrived at a webhook, refusals included."""
|
|
436
|
-
with client_for(state_of(ctx)) as dg:
|
|
437
|
-
rows = list(
|
|
438
|
-
paged(lambda after, size: dg.call(dg.webhooks.deliveries(pipeline, code, after=after, limit=size)), None)
|
|
439
|
-
)
|
|
440
|
-
if state_of(ctx).json_output:
|
|
441
|
-
return emit_records("delivery", rows)
|
|
442
|
-
table(
|
|
443
|
-
f"deliveries to {code}",
|
|
444
|
-
["received", "outcome", "run", "from", "detail"],
|
|
445
|
-
[
|
|
446
|
-
[
|
|
447
|
-
moment(row.created_at),
|
|
448
|
-
styled(row.outcome.value),
|
|
449
|
-
str(row.run_id or "-")[:8],
|
|
450
|
-
row.source or "-",
|
|
451
|
-
row.reason or "-",
|
|
452
|
-
]
|
|
453
|
-
for row in rows
|
|
454
|
-
],
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
@webhook_app.command("delete")
|
|
459
|
-
def webhook_delete(
|
|
460
|
-
ctx: typer.Context,
|
|
461
|
-
pipeline: Annotated[str, typer.Argument()],
|
|
462
|
-
code: Annotated[str, typer.Argument()],
|
|
463
|
-
) -> None:
|
|
464
|
-
"""Remove a webhook, its token, and its delivery history."""
|
|
465
|
-
with client_for(state_of(ctx)) as dg:
|
|
466
|
-
dg.call(dg.webhooks.delete(pipeline, code))
|
|
467
|
-
emit_fact("webhook.deleted", message="deleted", code=code, pipeline=pipeline)
|
|
468
|
-
|
|
469
|
-
|
|
470
247
|
@alerts_rules_app.command("list")
|
|
471
248
|
def alerts_rules_list(
|
|
472
249
|
ctx: typer.Context,
|
|
@@ -680,7 +457,7 @@ def trigger_document_show(
|
|
|
680
457
|
ctx: typer.Context,
|
|
681
458
|
code: Annotated[str, typer.Argument(help="The triggers document to read.")],
|
|
682
459
|
) -> None:
|
|
683
|
-
"""Show one triggers document and the schedules
|
|
460
|
+
"""Show one triggers document and the schedules it owns."""
|
|
684
461
|
with client_for(state_of(ctx)) as dg:
|
|
685
462
|
detail = dg.call(dg.trigger_documents.get(code))
|
|
686
463
|
if state_of(ctx).json_output:
|
|
@@ -689,8 +466,6 @@ def trigger_document_show(
|
|
|
689
466
|
console.print(f" pipeline [bold]{detail.pipeline}[/]")
|
|
690
467
|
console.print(f" digest [dim]{detail.digest}[/]")
|
|
691
468
|
console.print(f" schedules {', '.join(detail.schedules) or '-'}")
|
|
692
|
-
console.print(f" webhooks {', '.join(detail.webhooks) or '-'}")
|
|
693
|
-
console.print(f" watches {', '.join(detail.watches) or '-'}")
|
|
694
469
|
if detail.description:
|
|
695
470
|
console.print(f"\n{detail.description}")
|
|
696
471
|
|
|
@@ -700,17 +475,10 @@ def trigger_document_delete(
|
|
|
700
475
|
ctx: typer.Context,
|
|
701
476
|
code: Annotated[str, typer.Argument(help="The triggers document to remove.")],
|
|
702
477
|
) -> None:
|
|
703
|
-
"""Remove a triggers document and every schedule
|
|
478
|
+
"""Remove a triggers document and every schedule it declared."""
|
|
704
479
|
with client_for(state_of(ctx)) as dg:
|
|
705
480
|
dg.call(dg.trigger_documents.delete(code))
|
|
706
|
-
emit_fact(
|
|
707
|
-
"trigger_document.deleted",
|
|
708
|
-
message="deleted",
|
|
709
|
-
code=code,
|
|
710
|
-
schedules="deleted",
|
|
711
|
-
webhooks="deleted",
|
|
712
|
-
watches="deleted",
|
|
713
|
-
)
|
|
481
|
+
emit_fact("trigger_document.deleted", message="deleted", code=code, schedules="deleted")
|
|
714
482
|
|
|
715
483
|
|
|
716
484
|
@alerts_app.command("retry")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/__init__.py.tmpl
RENAMED
|
File without changes
|
{dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/operator.py.tmpl
RENAMED
|
File without changes
|
{dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/pyproject.toml.tmpl
RENAMED
|
File without changes
|
{dirigent_cli-0.26.0 → dirigent_cli-0.27.0}/src/dirigent_cli/templates/pack/test_plugin.py.tmpl
RENAMED
|
File without changes
|
|
File without changes
|