vortex-cli 8.1.1__tar.gz → 8.2.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.
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/PKG-INFO +75 -9
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/README.md +74 -8
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/pyproject.toml +1 -1
- vortex_cli-8.2.0/vortex/commands/agenda.py +403 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/execute.py +0 -6
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/object_.py +58 -26
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/main.py +10 -1
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/registry.py +4 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/webdesign.py +20 -5
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/PKG-INFO +75 -9
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/SOURCES.txt +1 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/LICENSE +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/setup.cfg +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/__init__.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/__main__.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/cli.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/colour.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/__init__.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/app.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/clean.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/clone.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/code.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/compile.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/db.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/diff.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/docs.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/fetch.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/info.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/keyword.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/libs.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/log.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/pull.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/search.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/servers.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/use.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/commands/watch.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/constants.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/docs/Blackbook v2.md +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/docs/Blackbook.pdf +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/docs/index.html +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/docs/marked.min.js +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/git.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/lib/puakma-6.0.40.jar +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/libs.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/logging.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/models.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/output.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/schedule.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/server_options.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/soap.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/spinner.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/sync.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/util.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex/workspace.py +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/dependency_links.txt +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/entry_points.txt +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/requires.txt +0 -0
- {vortex_cli-8.1.1 → vortex_cli-8.2.0}/vortex_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortex_cli
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: Vortex CLI
|
|
5
5
|
Author-email: Jordan Amos <jordan.amos@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -106,6 +106,18 @@ While it is possible to use without it, this software has been purposefully desi
|
|
|
106
106
|
java_environment_name = JavaSE-17 ; Java Execution Environment name https://docs.osgi.org/reference/eenames.html
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
## Upgrading to 8.2
|
|
110
|
+
|
|
111
|
+
- **`object run` runs ACTIONs, and waits.** It calls the gateway's new `POST run` (gateway
|
|
112
|
+
`AppVersion` 0.1.4 or later) instead of sending `tell agenda run` to the console, and
|
|
113
|
+
reports how long the action took, its reply and any exception (see
|
|
114
|
+
[Running an action](#running-an-action)). An older gateway refuses it with `NOT_EXPOSED`.
|
|
115
|
+
It no longer accepts a SCHEDULED_ACTION.
|
|
116
|
+
- **`vortex agenda`** lists the scheduled actions AGENDA has loaded and queues one with
|
|
117
|
+
`agenda run`, refusing the cases where AGENDA would silently do nothing (see
|
|
118
|
+
[The agenda](#the-agenda)). It replaces both `object run` on a scheduled action and
|
|
119
|
+
`vortex execute tell agenda run ...`.
|
|
120
|
+
|
|
109
121
|
## Upgrading to 8.1
|
|
110
122
|
|
|
111
123
|
8.1 does two things. It replaces `vortex config` with plain top-level commands that read
|
|
@@ -289,7 +301,7 @@ vortex object list [QUERY] [--app-id ID [--local]] [--strict --inherits-from|-
|
|
|
289
301
|
--ids-only --show-params --type T...] [--json]
|
|
290
302
|
vortex object grep PATTERN [--app-id ID] [--output-paths|--output-apps]
|
|
291
303
|
[--include-resources|--type T...] [--json]
|
|
292
|
-
vortex object run ID --app-id ID
|
|
304
|
+
vortex object run ID --app-id ID [--output] [--json]
|
|
293
305
|
vortex object delete ID... --app-id ID [--yes]
|
|
294
306
|
|
|
295
307
|
── keyword ──────────────────────────────────────────────────────────────────
|
|
@@ -315,6 +327,10 @@ vortex db update-column DB TABLE COLUMN --app-id ID [--name --type --size --desc
|
|
|
315
327
|
--cascade-delete|--no-cascade-delete]
|
|
316
328
|
vortex db delete-column DB TABLE COLUMN --app-id ID [--yes]
|
|
317
329
|
|
|
330
|
+
── agenda ───────────────────────────────────────────────────────────────────
|
|
331
|
+
vortex agenda [list] [--app-id ID] [--json]
|
|
332
|
+
vortex agenda run ID --app-id ID [--param KEY=VALUE ...] [--refresh --force] [--json]
|
|
333
|
+
|
|
318
334
|
── Workspace ────────────────────────────────────────────────────────────────
|
|
319
335
|
vortex ls [app list filters] [--json] = app list --local
|
|
320
336
|
vortex clone APP... [--group --reclone --all --get-resources --open-urls --timeout]
|
|
@@ -412,7 +428,7 @@ addins match:
|
|
|
412
428
|
|
|
413
429
|
| Shortcut | Sends | Effect |
|
|
414
430
|
|---|---|---|
|
|
415
|
-
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run |
|
|
431
|
+
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run (raw text; `vortex agenda` parses it) |
|
|
416
432
|
| `refresh-agenda` | `tell agenda refresh` | AGENDA rereads every schedule now |
|
|
417
433
|
| `refresh-design APP_ID` | `tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N` | rebuilds the application's design from its template, as `app refresh` does (a clone is then out of date) |
|
|
418
434
|
| `flush-cache` | `tell http cache flush` | clears the design cache and all action class loaders |
|
|
@@ -420,12 +436,28 @@ addins match:
|
|
|
420
436
|
`tell http flush cache` is **not** a valid command: the HTTP addin only matches
|
|
421
437
|
`cache flush`, and anything else does nothing, silently. Use `flush-cache`.
|
|
422
438
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
439
|
+
`vortex app refresh APP_ID` also lives with its noun: it sends
|
|
440
|
+
`tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N`, which rebuilds the design from its
|
|
441
|
+
template (a clone is then out of date).
|
|
442
|
+
|
|
443
|
+
### Running an action
|
|
444
|
+
|
|
445
|
+
`vortex object run ID --app-id N` runs an ACTION now, as your identity, and waits for it:
|
|
446
|
+
the gateway's `POST run` (`GatewaySystem`) loads the action's class, executes it and replies
|
|
447
|
+
with how long it took, its redirect, content type and reply size. `--output` also prints the
|
|
448
|
+
action's reply (its buffer, as text). An action that throws exits 1 with `ACTION_FAILED` and
|
|
449
|
+
the exception; a path with no compiled ACTION is `NOT_FOUND`.
|
|
450
|
+
|
|
451
|
+
- **ACTIONs only.** A SCHEDULED_ACTION (or any other type) is `WRONG_TYPE`. Scheduled
|
|
452
|
+
actions are queued with AGENDA: `vortex agenda run ID --app-id N` ([The agenda](#the-agenda)).
|
|
453
|
+
- **No client connection.** The action's `getOutputStream()` is null, as under AGENDA, so it
|
|
454
|
+
must buffer its reply (`write`/`setBuffer`); one that streams unconditionally fails with
|
|
455
|
+
the NullPointerException as its result.
|
|
456
|
+
- **Synchronous.** The CLI waits up to 300 s. Behind Cloudflare (prod) the request is cut
|
|
457
|
+
at about 100 s; the action still finishes on the server - check `vortex log`.
|
|
458
|
+
- **Gateway required.** It needs a gateway with the `run` route; an older gateway refuses with
|
|
459
|
+
`NOT_EXPOSED`. Earlier versions of `object run` sent `tell agenda run`, which only reached
|
|
460
|
+
scheduled actions on AGENDA's list and did nothing, silently, for an ACTION.
|
|
429
461
|
|
|
430
462
|
The 8.0 flags (`--show-schedule`, `--refresh-agenda`, `--flush-cache`, `--refresh-design`,
|
|
431
463
|
`--run`) were removed in 8.1. `app refresh` was `app refresh-design` in 8.1.0; the old
|
|
@@ -573,6 +605,40 @@ vortex object get 7901 --app-id 62 # the parsed schedule
|
|
|
573
605
|
starts; if that lands between the command's read and write, the old `LastRun` is written
|
|
574
606
|
back and the action may run one interval early.
|
|
575
607
|
|
|
608
|
+
### The agenda
|
|
609
|
+
|
|
610
|
+
AGENDA has no API beyond its console commands, so `vortex agenda` sends them through the same
|
|
611
|
+
console route as `vortex execute` (the gateway's `POST console`, `GatewaySystem`, or the SOAP
|
|
612
|
+
console without the gateway) and parses the text they answer.
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
vortex agenda -s dev # = agenda list
|
|
616
|
+
vortex agenda list --app-id 9 -s dev
|
|
617
|
+
vortex agenda run 7825 --app-id 60 -s dev [--param AppID=12] [--refresh] [--force]
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
- **`list`** - `tell agenda schedule`: every scheduled action AGENDA has **loaded**, by next
|
|
621
|
+
run (server time), with the waitlist and AGENDA's counts (running, waitlisted, scheduled,
|
|
622
|
+
max at once). An action with empty `Options` is loaded as *on demand only*. Not listed: an
|
|
623
|
+
action with `Schedule=N`, one in a disabled application or an application with scheduled
|
|
624
|
+
actions disabled, and one created or renamed since AGENDA's last refresh (every 15
|
|
625
|
+
minutes). AGENDA says how many actions are running, never which.
|
|
626
|
+
- **`run`** - queues a SCHEDULED_ACTION: `tell agenda run /group/app.pma/Name[?&KEY=VALUE...]`.
|
|
627
|
+
It runs as the system session when one of AGENDA's slots is free; its result is only in
|
|
628
|
+
`vortex log`. AGENDA answers that command with nothing, whatever it did, so `run` checks
|
|
629
|
+
first and sends nothing when AGENDA would not run it:
|
|
630
|
+
- `NOT_ON_AGENDA` - the action has `Schedule=N` (give it a schedule first), or it is not on
|
|
631
|
+
the loaded list. `--refresh` sends `tell agenda refresh` and checks again (for an action
|
|
632
|
+
created or renamed in the last 15 minutes).
|
|
633
|
+
- `ALREADY_QUEUED` - it is already waitlisted; AGENDA would skip it.
|
|
634
|
+
- `PREFIX_COLLISION` - AGENDA matches the path by prefix and queues it once per matching
|
|
635
|
+
item, so `.../SyncAll` would also match an item `.../Sync` and run twice. `--force` sends
|
|
636
|
+
it anyway.
|
|
637
|
+
- `WRONG_TYPE` - not a SCHEDULED_ACTION (an ACTION is run with `vortex object run`).
|
|
638
|
+
- A queued run moves the action's next scheduled run (AGENDA resets it), and it can't be
|
|
639
|
+
stopped from here (`vortex execute tell agenda stop /group/app.pma/Name` asks the action to
|
|
640
|
+
quit; it has to check for that itself).
|
|
641
|
+
|
|
576
642
|
### Keyword values and secrets
|
|
577
643
|
|
|
578
644
|
Keywords are an application's live configuration, and in practice they hold credentials in
|
|
@@ -62,6 +62,18 @@ While it is possible to use without it, this software has been purposefully desi
|
|
|
62
62
|
java_environment_name = JavaSE-17 ; Java Execution Environment name https://docs.osgi.org/reference/eenames.html
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## Upgrading to 8.2
|
|
66
|
+
|
|
67
|
+
- **`object run` runs ACTIONs, and waits.** It calls the gateway's new `POST run` (gateway
|
|
68
|
+
`AppVersion` 0.1.4 or later) instead of sending `tell agenda run` to the console, and
|
|
69
|
+
reports how long the action took, its reply and any exception (see
|
|
70
|
+
[Running an action](#running-an-action)). An older gateway refuses it with `NOT_EXPOSED`.
|
|
71
|
+
It no longer accepts a SCHEDULED_ACTION.
|
|
72
|
+
- **`vortex agenda`** lists the scheduled actions AGENDA has loaded and queues one with
|
|
73
|
+
`agenda run`, refusing the cases where AGENDA would silently do nothing (see
|
|
74
|
+
[The agenda](#the-agenda)). It replaces both `object run` on a scheduled action and
|
|
75
|
+
`vortex execute tell agenda run ...`.
|
|
76
|
+
|
|
65
77
|
## Upgrading to 8.1
|
|
66
78
|
|
|
67
79
|
8.1 does two things. It replaces `vortex config` with plain top-level commands that read
|
|
@@ -245,7 +257,7 @@ vortex object list [QUERY] [--app-id ID [--local]] [--strict --inherits-from|-
|
|
|
245
257
|
--ids-only --show-params --type T...] [--json]
|
|
246
258
|
vortex object grep PATTERN [--app-id ID] [--output-paths|--output-apps]
|
|
247
259
|
[--include-resources|--type T...] [--json]
|
|
248
|
-
vortex object run ID --app-id ID
|
|
260
|
+
vortex object run ID --app-id ID [--output] [--json]
|
|
249
261
|
vortex object delete ID... --app-id ID [--yes]
|
|
250
262
|
|
|
251
263
|
── keyword ──────────────────────────────────────────────────────────────────
|
|
@@ -271,6 +283,10 @@ vortex db update-column DB TABLE COLUMN --app-id ID [--name --type --size --desc
|
|
|
271
283
|
--cascade-delete|--no-cascade-delete]
|
|
272
284
|
vortex db delete-column DB TABLE COLUMN --app-id ID [--yes]
|
|
273
285
|
|
|
286
|
+
── agenda ───────────────────────────────────────────────────────────────────
|
|
287
|
+
vortex agenda [list] [--app-id ID] [--json]
|
|
288
|
+
vortex agenda run ID --app-id ID [--param KEY=VALUE ...] [--refresh --force] [--json]
|
|
289
|
+
|
|
274
290
|
── Workspace ────────────────────────────────────────────────────────────────
|
|
275
291
|
vortex ls [app list filters] [--json] = app list --local
|
|
276
292
|
vortex clone APP... [--group --reclone --all --get-resources --open-urls --timeout]
|
|
@@ -368,7 +384,7 @@ addins match:
|
|
|
368
384
|
|
|
369
385
|
| Shortcut | Sends | Effect |
|
|
370
386
|
|---|---|---|
|
|
371
|
-
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run |
|
|
387
|
+
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run (raw text; `vortex agenda` parses it) |
|
|
372
388
|
| `refresh-agenda` | `tell agenda refresh` | AGENDA rereads every schedule now |
|
|
373
389
|
| `refresh-design APP_ID` | `tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N` | rebuilds the application's design from its template, as `app refresh` does (a clone is then out of date) |
|
|
374
390
|
| `flush-cache` | `tell http cache flush` | clears the design cache and all action class loaders |
|
|
@@ -376,12 +392,28 @@ addins match:
|
|
|
376
392
|
`tell http flush cache` is **not** a valid command: the HTTP addin only matches
|
|
377
393
|
`cache flush`, and anything else does nothing, silently. Use `flush-cache`.
|
|
378
394
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
395
|
+
`vortex app refresh APP_ID` also lives with its noun: it sends
|
|
396
|
+
`tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N`, which rebuilds the design from its
|
|
397
|
+
template (a clone is then out of date).
|
|
398
|
+
|
|
399
|
+
### Running an action
|
|
400
|
+
|
|
401
|
+
`vortex object run ID --app-id N` runs an ACTION now, as your identity, and waits for it:
|
|
402
|
+
the gateway's `POST run` (`GatewaySystem`) loads the action's class, executes it and replies
|
|
403
|
+
with how long it took, its redirect, content type and reply size. `--output` also prints the
|
|
404
|
+
action's reply (its buffer, as text). An action that throws exits 1 with `ACTION_FAILED` and
|
|
405
|
+
the exception; a path with no compiled ACTION is `NOT_FOUND`.
|
|
406
|
+
|
|
407
|
+
- **ACTIONs only.** A SCHEDULED_ACTION (or any other type) is `WRONG_TYPE`. Scheduled
|
|
408
|
+
actions are queued with AGENDA: `vortex agenda run ID --app-id N` ([The agenda](#the-agenda)).
|
|
409
|
+
- **No client connection.** The action's `getOutputStream()` is null, as under AGENDA, so it
|
|
410
|
+
must buffer its reply (`write`/`setBuffer`); one that streams unconditionally fails with
|
|
411
|
+
the NullPointerException as its result.
|
|
412
|
+
- **Synchronous.** The CLI waits up to 300 s. Behind Cloudflare (prod) the request is cut
|
|
413
|
+
at about 100 s; the action still finishes on the server - check `vortex log`.
|
|
414
|
+
- **Gateway required.** It needs a gateway with the `run` route; an older gateway refuses with
|
|
415
|
+
`NOT_EXPOSED`. Earlier versions of `object run` sent `tell agenda run`, which only reached
|
|
416
|
+
scheduled actions on AGENDA's list and did nothing, silently, for an ACTION.
|
|
385
417
|
|
|
386
418
|
The 8.0 flags (`--show-schedule`, `--refresh-agenda`, `--flush-cache`, `--refresh-design`,
|
|
387
419
|
`--run`) were removed in 8.1. `app refresh` was `app refresh-design` in 8.1.0; the old
|
|
@@ -529,6 +561,40 @@ vortex object get 7901 --app-id 62 # the parsed schedule
|
|
|
529
561
|
starts; if that lands between the command's read and write, the old `LastRun` is written
|
|
530
562
|
back and the action may run one interval early.
|
|
531
563
|
|
|
564
|
+
### The agenda
|
|
565
|
+
|
|
566
|
+
AGENDA has no API beyond its console commands, so `vortex agenda` sends them through the same
|
|
567
|
+
console route as `vortex execute` (the gateway's `POST console`, `GatewaySystem`, or the SOAP
|
|
568
|
+
console without the gateway) and parses the text they answer.
|
|
569
|
+
|
|
570
|
+
```
|
|
571
|
+
vortex agenda -s dev # = agenda list
|
|
572
|
+
vortex agenda list --app-id 9 -s dev
|
|
573
|
+
vortex agenda run 7825 --app-id 60 -s dev [--param AppID=12] [--refresh] [--force]
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
- **`list`** - `tell agenda schedule`: every scheduled action AGENDA has **loaded**, by next
|
|
577
|
+
run (server time), with the waitlist and AGENDA's counts (running, waitlisted, scheduled,
|
|
578
|
+
max at once). An action with empty `Options` is loaded as *on demand only*. Not listed: an
|
|
579
|
+
action with `Schedule=N`, one in a disabled application or an application with scheduled
|
|
580
|
+
actions disabled, and one created or renamed since AGENDA's last refresh (every 15
|
|
581
|
+
minutes). AGENDA says how many actions are running, never which.
|
|
582
|
+
- **`run`** - queues a SCHEDULED_ACTION: `tell agenda run /group/app.pma/Name[?&KEY=VALUE...]`.
|
|
583
|
+
It runs as the system session when one of AGENDA's slots is free; its result is only in
|
|
584
|
+
`vortex log`. AGENDA answers that command with nothing, whatever it did, so `run` checks
|
|
585
|
+
first and sends nothing when AGENDA would not run it:
|
|
586
|
+
- `NOT_ON_AGENDA` - the action has `Schedule=N` (give it a schedule first), or it is not on
|
|
587
|
+
the loaded list. `--refresh` sends `tell agenda refresh` and checks again (for an action
|
|
588
|
+
created or renamed in the last 15 minutes).
|
|
589
|
+
- `ALREADY_QUEUED` - it is already waitlisted; AGENDA would skip it.
|
|
590
|
+
- `PREFIX_COLLISION` - AGENDA matches the path by prefix and queues it once per matching
|
|
591
|
+
item, so `.../SyncAll` would also match an item `.../Sync` and run twice. `--force` sends
|
|
592
|
+
it anyway.
|
|
593
|
+
- `WRONG_TYPE` - not a SCHEDULED_ACTION (an ACTION is run with `vortex object run`).
|
|
594
|
+
- A queued run moves the action's next scheduled run (AGENDA resets it), and it can't be
|
|
595
|
+
stopped from here (`vortex execute tell agenda stop /group/app.pma/Name` asks the action to
|
|
596
|
+
quit; it has to check for that itself).
|
|
597
|
+
|
|
532
598
|
### Keyword values and secrets
|
|
533
599
|
|
|
534
600
|
Keywords are an application's live configuration, and in practice they hold credentials in
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vortex agenda [list] | run - the server's scheduled actions, through AGENDA.
|
|
3
|
+
|
|
4
|
+
AGENDA (puakma.addin.agenda) has no API beyond its console commands, so
|
|
5
|
+
both verbs go through the console route 'vortex execute' uses (the
|
|
6
|
+
gateway's POST console, GatewaySystem, or the SOAP console without the
|
|
7
|
+
gateway) and parse AGENDA's text replies:
|
|
8
|
+
|
|
9
|
+
tell agenda schedule one line per LOADED item, sorted by path:
|
|
10
|
+
'/grp/app.pma/Name 1D NextRun=Fri 09.Oct.26 22:00:00'
|
|
11
|
+
tell agenda status counts: running, waitlisted, scheduled, ...
|
|
12
|
+
tell agenda waitlist status one line per queued item: '/grp/app.pma/Name Running=...'
|
|
13
|
+
|
|
14
|
+
'list' shows only what AGENDA has loaded: an action in a disabled app,
|
|
15
|
+
an app with scheduled actions disabled, or one created since the last
|
|
16
|
+
refresh (every 15 minutes) is not on it. No console command says which
|
|
17
|
+
actions are running - only how many.
|
|
18
|
+
|
|
19
|
+
'run' sends 'tell agenda run /grp/app.pma/Name', which AGENDA matches
|
|
20
|
+
against its loaded list and queues (AGENDA.forceRun). That command
|
|
21
|
+
answers nothing either way, so 'run' checks first what AGENDA would do:
|
|
22
|
+
|
|
23
|
+
- Not on the list: AGENDA ignores the command silently - NOT_ON_AGENDA
|
|
24
|
+
(--refresh rereads the list first). 'Schedule=N' in the Options is
|
|
25
|
+
never loaded; EMPTY Options load as an on-demand item.
|
|
26
|
+
- Already waitlisted: AGENDA skips it - ALREADY_QUEUED.
|
|
27
|
+
- AGENDA matches by PREFIX (sent path startsWith item path, case-
|
|
28
|
+
insensitive) and queues the sent path once per matching item, so
|
|
29
|
+
'SyncAll' also matches an item 'Sync' and runs twice - PREFIX_COLLISION
|
|
30
|
+
unless --force.
|
|
31
|
+
|
|
32
|
+
A queued run executes as the system session when a slot is free; its
|
|
33
|
+
result is only in the server log. ACTIONs are run with 'object run'.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import argparse
|
|
39
|
+
import datetime
|
|
40
|
+
import logging
|
|
41
|
+
import re
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
from vortex import cli
|
|
45
|
+
from vortex import registry
|
|
46
|
+
from vortex.commands import execute
|
|
47
|
+
from vortex.models import DesignType
|
|
48
|
+
from vortex.output import CommandError
|
|
49
|
+
from vortex.output import render_table
|
|
50
|
+
from vortex.output import text
|
|
51
|
+
from vortex.registry import Command
|
|
52
|
+
from vortex.registry import Context
|
|
53
|
+
from vortex.registry import usage
|
|
54
|
+
from vortex.webdesign import to_int
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger("vortex")
|
|
57
|
+
|
|
58
|
+
SCHEDULE_CMD = execute.AGENDA_SCHEDULE_CMD
|
|
59
|
+
REFRESH_CMD = execute.AGENDA_REFRESH_CMD
|
|
60
|
+
STATUS_CMD = "tell agenda status"
|
|
61
|
+
WAITLIST_CMD = "tell agenda waitlist status"
|
|
62
|
+
RUN_CMD = "tell agenda run %s"
|
|
63
|
+
|
|
64
|
+
# AgendaItem.getSchedule(): path, interval + type letter, next run
|
|
65
|
+
_SCHEDULE_LINE = re.compile(r"^(/\S+)\s+(\d+)([A-Z])\s+NextRun=(.+?)\s*$")
|
|
66
|
+
# AgendaAction.toString(): path, Running=, LastRun=
|
|
67
|
+
_WAITLIST_LINE = re.compile(r"^(/\S+)\s+Running=")
|
|
68
|
+
_STATUS_LINE = re.compile(r"^([A-Za-z][A-Za-z ]+):\s*(\d+)\s*$")
|
|
69
|
+
# AgendaItem's SimpleDateFormat for NextRun
|
|
70
|
+
_NEXT_RUN_FORMAT = "%a %d.%b.%y %H:%M:%S"
|
|
71
|
+
# AgendaItem.SCHED_* letters
|
|
72
|
+
_UNITS = {
|
|
73
|
+
"S": "sec",
|
|
74
|
+
"I": "min",
|
|
75
|
+
"H": "hour",
|
|
76
|
+
"D": "day",
|
|
77
|
+
"W": "week",
|
|
78
|
+
"M": "month",
|
|
79
|
+
"Y": "year",
|
|
80
|
+
}
|
|
81
|
+
_PARAM = re.compile(r"^[A-Za-z0-9_.-]+=[A-Za-z0-9_.:-]*$")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _console(ctx: Context, command: str) -> str:
|
|
85
|
+
return execute.console_command(ctx.server, command) or ""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_schedule(reply: str) -> list[dict[str, Any]]:
|
|
89
|
+
"""The items of a 'tell agenda schedule' reply (lines that don't parse are skipped)."""
|
|
90
|
+
items = []
|
|
91
|
+
for line in reply.splitlines():
|
|
92
|
+
m = _SCHEDULE_LINE.match(line.strip())
|
|
93
|
+
if m is None:
|
|
94
|
+
continue
|
|
95
|
+
path, interval, unit, next_run = m.groups()
|
|
96
|
+
app_path, _, name = path.rpartition("/")
|
|
97
|
+
app = app_path.lstrip("/").removesuffix(".pma")
|
|
98
|
+
items.append(
|
|
99
|
+
{
|
|
100
|
+
"path": path,
|
|
101
|
+
"app": app,
|
|
102
|
+
"name": name,
|
|
103
|
+
"interval": int(interval),
|
|
104
|
+
"unit": unit,
|
|
105
|
+
"every": _every(int(interval), unit),
|
|
106
|
+
"nextRun": _next_run(next_run),
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
return items
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _every(interval: int, unit: str) -> str:
|
|
113
|
+
if unit == "N":
|
|
114
|
+
return "on demand"
|
|
115
|
+
word = _UNITS.get(unit)
|
|
116
|
+
if word is None:
|
|
117
|
+
return f"{interval}{unit}"
|
|
118
|
+
return f"{interval} {word}{'' if interval == 1 else 's'}"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _next_run(value: str) -> str | None:
|
|
122
|
+
"""NextRun as ISO 8601 (server time), None when UnScheduled, else as sent."""
|
|
123
|
+
if value == "UnScheduled":
|
|
124
|
+
return None
|
|
125
|
+
try:
|
|
126
|
+
return datetime.datetime.strptime(value, _NEXT_RUN_FORMAT).isoformat()
|
|
127
|
+
except ValueError:
|
|
128
|
+
return value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def parse_status(reply: str) -> dict[str, int]:
|
|
132
|
+
"""The counts in a 'tell agenda status' reply, camelCase keys ('Actions running' -> running)."""
|
|
133
|
+
counts = {}
|
|
134
|
+
for line in reply.splitlines():
|
|
135
|
+
m = _STATUS_LINE.match(line.strip())
|
|
136
|
+
if m is None:
|
|
137
|
+
continue
|
|
138
|
+
words = m.group(1).lower().removeprefix("actions ").split()
|
|
139
|
+
key = words[0] + "".join(w.title() for w in words[1:])
|
|
140
|
+
counts[key] = int(m.group(2))
|
|
141
|
+
return counts
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def parse_waitlist(reply: str) -> list[str]:
|
|
145
|
+
"""The paths in a 'tell agenda waitlist status' reply."""
|
|
146
|
+
paths = []
|
|
147
|
+
for line in reply.splitlines():
|
|
148
|
+
m = _WAITLIST_LINE.match(line.strip())
|
|
149
|
+
if m is not None:
|
|
150
|
+
paths.append(m.group(1))
|
|
151
|
+
return paths
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _sort_key(item: dict[str, Any]) -> tuple[int, str, str]:
|
|
155
|
+
# scheduled first by next run, then on-demand items by path
|
|
156
|
+
next_run = item["nextRun"]
|
|
157
|
+
return (0, next_run, item["path"]) if next_run else (1, "", item["path"])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# -- list ------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _list(ctx: Context, args: argparse.Namespace) -> dict[str, Any]:
|
|
164
|
+
items = parse_schedule(_console(ctx, SCHEDULE_CMD))
|
|
165
|
+
queued = {p.lower() for p in parse_waitlist(_console(ctx, WAITLIST_CMD))}
|
|
166
|
+
if args.app_id is not None:
|
|
167
|
+
from vortex.commands.app import find_app_row
|
|
168
|
+
|
|
169
|
+
app = find_app_row(ctx, args.app_id)
|
|
170
|
+
group = text(app.get("appgroup"))
|
|
171
|
+
want = f"{group + '/' if group else ''}{text(app.get('appname'))}".lower()
|
|
172
|
+
items = [i for i in items if i["app"].lower() == want]
|
|
173
|
+
for item in items:
|
|
174
|
+
item["waitlisted"] = item["path"].lower() in queued
|
|
175
|
+
return {
|
|
176
|
+
"status": parse_status(_console(ctx, STATUS_CMD)),
|
|
177
|
+
"items": sorted(items, key=_sort_key),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _render_list(ctx: Context, args: argparse.Namespace, data: Any) -> None:
|
|
182
|
+
rows = [
|
|
183
|
+
(
|
|
184
|
+
i["app"],
|
|
185
|
+
i["name"],
|
|
186
|
+
i["every"],
|
|
187
|
+
(i["nextRun"] or "on demand only").replace("T", " "),
|
|
188
|
+
"waitlisted" if i["waitlisted"] else "",
|
|
189
|
+
)
|
|
190
|
+
for i in data["items"]
|
|
191
|
+
]
|
|
192
|
+
if rows:
|
|
193
|
+
render_table(
|
|
194
|
+
rows, ["App", "Action", "Every", "Next run (server time)", "Queue"]
|
|
195
|
+
)
|
|
196
|
+
else:
|
|
197
|
+
print("No scheduled actions on the agenda")
|
|
198
|
+
status = data["status"]
|
|
199
|
+
if status:
|
|
200
|
+
print(
|
|
201
|
+
f"\n{status.get('scheduled', len(rows))} scheduled · "
|
|
202
|
+
f"{status.get('running', 0)} running · "
|
|
203
|
+
f"{status.get('waitlisted', 0)} waitlisted · "
|
|
204
|
+
f"max {status.get('maxConcurrentActions', '?')} at once"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _configure_list(parser: argparse.ArgumentParser) -> None:
|
|
209
|
+
parser.add_argument(
|
|
210
|
+
"--app-id",
|
|
211
|
+
metavar="ID",
|
|
212
|
+
type=cli.positive_id,
|
|
213
|
+
help="Only this application's scheduled actions",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# -- run -------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _params(args: argparse.Namespace) -> str:
|
|
221
|
+
"""'?&k=v&k2=v2' for --param, as 'app refresh' sends RefreshDesign?&AppID=N."""
|
|
222
|
+
for param in args.param:
|
|
223
|
+
if not _PARAM.match(param):
|
|
224
|
+
usage(
|
|
225
|
+
args,
|
|
226
|
+
f"--param '{param}' must be KEY=VALUE (letters, digits, '_', '.', "
|
|
227
|
+
"'-'; the value may also hold ':')",
|
|
228
|
+
)
|
|
229
|
+
return "?&" + "&".join(args.param) if args.param else ""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _run(ctx: Context, args: argparse.Namespace) -> dict[str, Any]:
|
|
233
|
+
from vortex.commands.app import find_app_row
|
|
234
|
+
|
|
235
|
+
query = _params(args)
|
|
236
|
+
app = find_app_row(ctx, args.app_id)
|
|
237
|
+
row = ctx.client.get_design(args.app_id, args.id)
|
|
238
|
+
name = text(row.get("name"))
|
|
239
|
+
design_type = to_int(row.get("designtype") or 0)
|
|
240
|
+
if design_type != DesignType.SCHEDULED_ACTION:
|
|
241
|
+
try:
|
|
242
|
+
type_name = DesignType(design_type).name
|
|
243
|
+
except ValueError:
|
|
244
|
+
type_name = text(row.get("designtype"))
|
|
245
|
+
raise CommandError(
|
|
246
|
+
f"[{args.id}] {name} is a {type_name}: only a SCHEDULED_ACTION runs "
|
|
247
|
+
"through AGENDA",
|
|
248
|
+
"WRONG_TYPE",
|
|
249
|
+
hint=(
|
|
250
|
+
f"run an ACTION with: vortex object run {args.id} --app-id "
|
|
251
|
+
f"{args.app_id}"
|
|
252
|
+
if design_type == DesignType.ACTION
|
|
253
|
+
else None
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
group = text(app.get("appgroup"))
|
|
257
|
+
path = f"/{group + '/' if group else ''}{text(app.get('appname'))}.pma/{name}"
|
|
258
|
+
sent = path + query
|
|
259
|
+
if "schedule=n" in text(row.get("options")).lower():
|
|
260
|
+
# AGENDA.refreshActionList skips 'Schedule=N' outright (empty Options
|
|
261
|
+
# load as on-demand instead), so --refresh can't help
|
|
262
|
+
raise CommandError(
|
|
263
|
+
f"{path} has Schedule=N (never), so AGENDA does not load it and would "
|
|
264
|
+
"ignore the run (silently) - nothing was sent",
|
|
265
|
+
"NOT_ON_AGENDA",
|
|
266
|
+
hint=(
|
|
267
|
+
f"give it a schedule first: vortex object update {args.id} "
|
|
268
|
+
f"--app-id {args.app_id} --schedule D --start-time 03:00 ..."
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
items = parse_schedule(_console(ctx, SCHEDULE_CMD))
|
|
273
|
+
refreshed = False
|
|
274
|
+
if args.refresh and not _on_agenda(items, path):
|
|
275
|
+
_console(ctx, REFRESH_CMD)
|
|
276
|
+
refreshed = True
|
|
277
|
+
items = parse_schedule(_console(ctx, SCHEDULE_CMD))
|
|
278
|
+
if not _on_agenda(items, path):
|
|
279
|
+
raise CommandError(
|
|
280
|
+
f"{path} is not on AGENDA's list, so AGENDA would ignore the run "
|
|
281
|
+
"(silently) - nothing was sent",
|
|
282
|
+
"NOT_ON_AGENDA",
|
|
283
|
+
hint=(
|
|
284
|
+
"the application or its scheduled actions are disabled"
|
|
285
|
+
if refreshed
|
|
286
|
+
else "if it was created or renamed in the last 15 minutes, pass "
|
|
287
|
+
"--refresh; otherwise the application or its scheduled actions "
|
|
288
|
+
"are disabled"
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
if any(
|
|
292
|
+
p.lower() == path.lower() for p in parse_waitlist(_console(ctx, WAITLIST_CMD))
|
|
293
|
+
):
|
|
294
|
+
raise CommandError(
|
|
295
|
+
f"{path} is already waitlisted - AGENDA would skip the run",
|
|
296
|
+
"ALREADY_QUEUED",
|
|
297
|
+
)
|
|
298
|
+
others = [
|
|
299
|
+
i["path"]
|
|
300
|
+
for i in items
|
|
301
|
+
if sent.lower().startswith(i["path"].lower())
|
|
302
|
+
and i["path"].lower() != path.lower()
|
|
303
|
+
]
|
|
304
|
+
if others and not args.force:
|
|
305
|
+
raise CommandError(
|
|
306
|
+
f"AGENDA matches by prefix: {sent} also matches {', '.join(others)}, "
|
|
307
|
+
f"so it would queue {name} {len(others) + 1} times - nothing was sent",
|
|
308
|
+
"PREFIX_COLLISION",
|
|
309
|
+
hint="pass --force to send it anyway",
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
command = RUN_CMD % sent
|
|
313
|
+
_console(ctx, command)
|
|
314
|
+
return {
|
|
315
|
+
"designbucketid": row.get("designbucketid"),
|
|
316
|
+
"name": name,
|
|
317
|
+
"path": sent,
|
|
318
|
+
"command": command,
|
|
319
|
+
"agendaRefreshed": refreshed,
|
|
320
|
+
"queued": len(others) + 1,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _on_agenda(items: list[dict[str, Any]], path: str) -> bool:
|
|
325
|
+
return any(i["path"].lower() == path.lower() for i in items)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _render_run(ctx: Context, args: argparse.Namespace, data: Any) -> None:
|
|
329
|
+
times = f" {data['queued']} times" if data["queued"] > 1 else ""
|
|
330
|
+
print(
|
|
331
|
+
f"Queued {data['path']} with AGENDA{times}: it runs as the system session "
|
|
332
|
+
"when a slot is free.\nFollow it with: vortex agenda -s "
|
|
333
|
+
f"{ctx.server.name} / vortex log -s {ctx.server.name}"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _configure_run(parser: argparse.ArgumentParser) -> None:
|
|
338
|
+
parser.add_argument("id", metavar="ID", type=cli.positive_id)
|
|
339
|
+
parser.add_argument(
|
|
340
|
+
"--app-id",
|
|
341
|
+
metavar="ID",
|
|
342
|
+
type=cli.positive_id,
|
|
343
|
+
required=True,
|
|
344
|
+
help="The scheduled action's application (numeric)",
|
|
345
|
+
)
|
|
346
|
+
parser.add_argument(
|
|
347
|
+
"--param",
|
|
348
|
+
metavar="KEY=VALUE",
|
|
349
|
+
action="append",
|
|
350
|
+
default=[],
|
|
351
|
+
help="Pass a parameter to the action (repeatable): ...Name?&KEY=VALUE",
|
|
352
|
+
)
|
|
353
|
+
parser.add_argument(
|
|
354
|
+
"--refresh",
|
|
355
|
+
action="store_true",
|
|
356
|
+
help="Reread AGENDA's list first when the action is not on it",
|
|
357
|
+
)
|
|
358
|
+
parser.add_argument(
|
|
359
|
+
"--force",
|
|
360
|
+
action="store_true",
|
|
361
|
+
help="Send even when another action's path is a prefix of this one",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
COMMANDS = [
|
|
366
|
+
Command(
|
|
367
|
+
"agenda",
|
|
368
|
+
"list",
|
|
369
|
+
"The scheduled actions AGENDA has loaded, by next run",
|
|
370
|
+
_list,
|
|
371
|
+
routes=(registry.CONSOLE,),
|
|
372
|
+
configure=_configure_list,
|
|
373
|
+
render=_render_list,
|
|
374
|
+
note="--app-id also reads the inventory",
|
|
375
|
+
description=(
|
|
376
|
+
"The scheduled actions AGENDA has loaded ('tell agenda schedule'),\n"
|
|
377
|
+
"sorted by next run, with the waitlist and AGENDA's counts. Only\n"
|
|
378
|
+
"what AGENDA loaded: an action in a disabled app, in an app with\n"
|
|
379
|
+
"scheduled actions disabled, or created since the last refresh is\n"
|
|
380
|
+
"not listed. 'vortex agenda' alone is 'vortex agenda list'."
|
|
381
|
+
),
|
|
382
|
+
),
|
|
383
|
+
Command(
|
|
384
|
+
"agenda",
|
|
385
|
+
"run",
|
|
386
|
+
"Queue a scheduled action with AGENDA now",
|
|
387
|
+
_run,
|
|
388
|
+
routes=(registry.INVENTORY, registry.DESIGN_GET, registry.CONSOLE),
|
|
389
|
+
write=True,
|
|
390
|
+
configure=_configure_run,
|
|
391
|
+
render=_render_run,
|
|
392
|
+
description=(
|
|
393
|
+
"Queue a SCHEDULED_ACTION with AGENDA now ('tell agenda run\n"
|
|
394
|
+
"/group/app.pma/Name'). It runs as the system session when a slot\n"
|
|
395
|
+
"is free; its result is only in 'vortex log'. Checked first,\n"
|
|
396
|
+
"because AGENDA answers nothing: refused when the action is not on\n"
|
|
397
|
+
"AGENDA's list (NOT_ON_AGENDA; --refresh rereads it), already\n"
|
|
398
|
+
"waitlisted (ALREADY_QUEUED), or would also match another action\n"
|
|
399
|
+
"by prefix and run twice (PREFIX_COLLISION; --force sends it).\n"
|
|
400
|
+
"ACTIONs are run with 'vortex object run'."
|
|
401
|
+
),
|
|
402
|
+
),
|
|
403
|
+
]
|
|
@@ -17,7 +17,6 @@ from vortex.workspace import Workspace
|
|
|
17
17
|
logger = logging.getLogger("vortex")
|
|
18
18
|
|
|
19
19
|
REFRESH_APPLICATION_CMD = "tell agenda run /%s/RefreshDesign?&AppID=%d"
|
|
20
|
-
RUN_CMD = "tell agenda run /%s"
|
|
21
20
|
# The console shortcuts - the exact strings the addins match
|
|
22
21
|
# (AGENDA.java tell(): "schedule", "refresh"; HTTP.java tell(): "cache flush"
|
|
23
22
|
# only - 'tell http flush cache' silently does nothing)
|
|
@@ -56,11 +55,6 @@ def refresh_design_command(server: PuakmaServer, app_id: int) -> str:
|
|
|
56
55
|
return REFRESH_APPLICATION_CMD % (server.webdesign_path, app_id)
|
|
57
56
|
|
|
58
57
|
|
|
59
|
-
def run_command(server_path: str) -> str:
|
|
60
|
-
"""'tell agenda run /group/app.pma/action' for a design's server path."""
|
|
61
|
-
return RUN_CMD % server_path.lstrip("/")
|
|
62
|
-
|
|
63
|
-
|
|
64
58
|
def resolve_command(words: list[str]) -> str:
|
|
65
59
|
"""The console command for 'vortex execute WORDS...': a shortcut, or the words."""
|
|
66
60
|
command = " ".join(words).strip()
|
|
@@ -1073,49 +1073,80 @@ def _add_content(parser: argparse.ArgumentParser) -> None:
|
|
|
1073
1073
|
|
|
1074
1074
|
# -- run -------------------------------------------------------------------
|
|
1075
1075
|
|
|
1076
|
-
|
|
1076
|
+
_RUN_REFUSAL_HELP = {
|
|
1077
|
+
"NOT_EXPOSED": (
|
|
1078
|
+
"this server's gateway has no run route - upgrade vortex/gateway "
|
|
1079
|
+
"(object run no longer goes through the console)"
|
|
1080
|
+
),
|
|
1081
|
+
"NOT_FOUND": "the server has no compiled ACTION at that path",
|
|
1082
|
+
}
|
|
1077
1083
|
|
|
1078
1084
|
|
|
1079
|
-
def _run(ctx: Context, args: argparse.Namespace) -> dict[str, Any]:
|
|
1080
|
-
from vortex.commands import execute
|
|
1085
|
+
def _run(ctx: Context, args: argparse.Namespace) -> dict[str, Any] | Outcome:
|
|
1081
1086
|
from vortex.commands.app import find_app_row
|
|
1087
|
+
from vortex.webdesign import GatewayClient
|
|
1088
|
+
from vortex.webdesign import GatewayRefused
|
|
1089
|
+
from vortex.webdesign import refusal_detail
|
|
1082
1090
|
|
|
1083
1091
|
app = find_app_row(ctx, args.app_id)
|
|
1084
1092
|
row = ctx.client.get_design(args.app_id, args.id)
|
|
1085
|
-
try:
|
|
1086
|
-
design_type = DesignType(to_int(row.get("designtype") or 0))
|
|
1087
|
-
except ValueError:
|
|
1088
|
-
design_type = DesignType.ERROR
|
|
1089
1093
|
name = text(row.get("name"))
|
|
1090
|
-
|
|
1094
|
+
design_type = to_int(row.get("designtype") or 0)
|
|
1095
|
+
if design_type != DesignType.ACTION:
|
|
1096
|
+
hint = None
|
|
1097
|
+
if design_type == DesignType.SCHEDULED_ACTION:
|
|
1098
|
+
hint = (
|
|
1099
|
+
"queue it with AGENDA: vortex agenda run "
|
|
1100
|
+
f"{args.id} --app-id {args.app_id}"
|
|
1101
|
+
)
|
|
1091
1102
|
raise CommandError(
|
|
1092
1103
|
f"[{args.id}] {name} is a {_type_name(row.get('designtype'))}: only "
|
|
1093
|
-
"an ACTION
|
|
1104
|
+
"an ACTION can be run",
|
|
1094
1105
|
"WRONG_TYPE",
|
|
1106
|
+
hint=hint,
|
|
1095
1107
|
)
|
|
1096
1108
|
group = text(app.get("appgroup"))
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1109
|
+
path = f"/{group + '/' if group else ''}{text(app.get('appname'))}.pma/{name}"
|
|
1110
|
+
try:
|
|
1111
|
+
with ctx.server as s:
|
|
1112
|
+
reply = GatewayClient(s).run(path, reply=args.output)
|
|
1113
|
+
except GatewayRefused as e:
|
|
1114
|
+
raise CommandError(
|
|
1115
|
+
refusal_detail(e, _RUN_REFUSAL_HELP), e.error_code, role=e.role
|
|
1116
|
+
) from e
|
|
1117
|
+
data = {
|
|
1101
1118
|
"designbucketid": row.get("designbucketid"),
|
|
1102
1119
|
"name": name,
|
|
1103
|
-
|
|
1104
|
-
"path": f"/{server_path}",
|
|
1105
|
-
"command": command,
|
|
1106
|
-
"reply": reply or "",
|
|
1120
|
+
**{k: v for k, v in reply.items() if k != "ok"},
|
|
1107
1121
|
}
|
|
1122
|
+
if data.get("exception"):
|
|
1123
|
+
return Outcome(
|
|
1124
|
+
data,
|
|
1125
|
+
CommandError(f"{path} threw: {data['exception']}", "ACTION_FAILED"),
|
|
1126
|
+
)
|
|
1127
|
+
return data
|
|
1108
1128
|
|
|
1109
1129
|
|
|
1110
1130
|
def _render_run(ctx: Context, args: argparse.Namespace, data: Any) -> None:
|
|
1111
|
-
|
|
1112
|
-
if data
|
|
1113
|
-
print(data["
|
|
1131
|
+
seconds = to_int(data.get("durationMs") or 0) / 1000
|
|
1132
|
+
if data.get("exception"):
|
|
1133
|
+
print(f"Ran {data['path']}: it threw after {seconds:.2f}s")
|
|
1134
|
+
return
|
|
1135
|
+
print(f"Ran {data['path']} in {seconds:.2f}s")
|
|
1136
|
+
if data.get("redirectTo"):
|
|
1137
|
+
print(f"Redirect: {data['redirectTo']}")
|
|
1138
|
+
if args.output and data.get("body"):
|
|
1139
|
+
print(data["body"])
|
|
1114
1140
|
|
|
1115
1141
|
|
|
1116
1142
|
def _configure_run(parser: argparse.ArgumentParser) -> None:
|
|
1117
1143
|
parser.add_argument("id", metavar="ID", type=cli.positive_id)
|
|
1118
1144
|
_add_app_id(parser)
|
|
1145
|
+
parser.add_argument(
|
|
1146
|
+
"--output",
|
|
1147
|
+
action="store_true",
|
|
1148
|
+
help="Also print the action's reply (its buffer, as text)",
|
|
1149
|
+
)
|
|
1119
1150
|
|
|
1120
1151
|
|
|
1121
1152
|
def _configure_get(parser: argparse.ArgumentParser) -> None:
|
|
@@ -1245,17 +1276,18 @@ COMMANDS = [
|
|
|
1245
1276
|
Command(
|
|
1246
1277
|
"object",
|
|
1247
1278
|
"run",
|
|
1248
|
-
"Run an action
|
|
1279
|
+
"Run an action now and wait for it (the gateway's POST run)",
|
|
1249
1280
|
_run,
|
|
1250
|
-
routes=(registry.INVENTORY, registry.DESIGN_GET, registry.
|
|
1281
|
+
routes=(registry.INVENTORY, registry.DESIGN_GET, registry.RUN),
|
|
1251
1282
|
write=True,
|
|
1252
1283
|
configure=_configure_run,
|
|
1253
1284
|
render=_render_run,
|
|
1254
1285
|
description=(
|
|
1255
|
-
"Run an ACTION
|
|
1256
|
-
"'
|
|
1257
|
-
"
|
|
1258
|
-
"the
|
|
1286
|
+
"Run an ACTION now, as this identity, and wait for it to finish:\n"
|
|
1287
|
+
"the gateway's POST run (needs GatewaySystem) loads and executes\n"
|
|
1288
|
+
"the action and reports how long it took, its redirect and reply\n"
|
|
1289
|
+
"size - and the exception if it threw (exit 1). --output prints\n"
|
|
1290
|
+
"the action's reply. Scheduled actions are not run here."
|
|
1259
1291
|
),
|
|
1260
1292
|
),
|
|
1261
1293
|
]
|
|
@@ -84,6 +84,8 @@ STUBS = {
|
|
|
84
84
|
REMOVED_IN = {"config": "8.1", "find": "8.1", "grep": "8.1", "libs": "8.1"}
|
|
85
85
|
# Old names that still work, quietly: rewritten before parsing
|
|
86
86
|
ALIASES = {"use": ["server", "use"], "ls": ["app", "list", "--local"]}
|
|
87
|
+
# Nouns that run a verb when given none ('vortex agenda' = 'vortex agenda list')
|
|
88
|
+
DEFAULT_VERBS = {"agenda": "list"}
|
|
87
89
|
# 7.x forms of commands whose NAME 8.0 reuses as a noun
|
|
88
90
|
OLD_FORMS = {
|
|
89
91
|
"db": (
|
|
@@ -115,7 +117,14 @@ def _command_index(tokens: Sequence[str]) -> int | None:
|
|
|
115
117
|
|
|
116
118
|
def _apply_aliases(argv: list[str]) -> list[str]:
|
|
117
119
|
i = _command_index(argv)
|
|
118
|
-
if i is None
|
|
120
|
+
if i is None:
|
|
121
|
+
return argv
|
|
122
|
+
if argv[i] in DEFAULT_VERBS:
|
|
123
|
+
rest = argv[i + 1 :]
|
|
124
|
+
if not rest or (rest[0].startswith("-") and rest[0] not in ("-h", "--help")):
|
|
125
|
+
return [*argv[: i + 1], DEFAULT_VERBS[argv[i]], *rest]
|
|
126
|
+
return argv
|
|
127
|
+
if argv[i] not in ALIASES:
|
|
119
128
|
return argv
|
|
120
129
|
return [*argv[:i], *ALIASES[argv[i]], *argv[i + 1 :]]
|
|
121
130
|
|
|
@@ -70,6 +70,7 @@ SYSTEMJAR = "GET /systemjar"
|
|
|
70
70
|
WHOAMI = "GET /whoami"
|
|
71
71
|
LOGS = "GET /logs"
|
|
72
72
|
CONSOLE = "POST /console"
|
|
73
|
+
RUN = "POST /run"
|
|
73
74
|
EXPORT = "GET /export"
|
|
74
75
|
IMPORT = "POST /import"
|
|
75
76
|
|
|
@@ -78,6 +79,7 @@ NOUN_HELP = {
|
|
|
78
79
|
"object": "Design objects: list, get, grep, create, update, diff, copy, delete, run",
|
|
79
80
|
"keyword": "Application keywords: list, get, set, delete",
|
|
80
81
|
"db": "Databases: connections, SQL queries and the data dictionary",
|
|
82
|
+
"agenda": "Scheduled actions (AGENDA): list, run",
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
|
|
@@ -203,6 +205,7 @@ class Command:
|
|
|
203
205
|
|
|
204
206
|
|
|
205
207
|
def all_commands() -> list[Command]:
|
|
208
|
+
from vortex.commands import agenda
|
|
206
209
|
from vortex.commands import app
|
|
207
210
|
from vortex.commands import db
|
|
208
211
|
from vortex.commands import diff
|
|
@@ -219,6 +222,7 @@ def all_commands() -> list[Command]:
|
|
|
219
222
|
*keyword.COMMANDS,
|
|
220
223
|
*db.COMMANDS,
|
|
221
224
|
*fetch.COMMANDS,
|
|
225
|
+
*agenda.COMMANDS,
|
|
222
226
|
]
|
|
223
227
|
|
|
224
228
|
|
|
@@ -24,10 +24,10 @@ webdesign's own. It adds three things:
|
|
|
24
24
|
- Guards: no ids that belong to another application (NOT_FOUND), no
|
|
25
25
|
system database (SYSTEM_DB), one DML/SELECT statement per SQL request
|
|
26
26
|
(SQL_NOT_ALLOWED).
|
|
27
|
-
- Its own routes on the same base URL: whoami, logs, console,
|
|
28
|
-
and import (GatewayClient). Without the gateway, whoami/logs/
|
|
29
|
-
import do not exist (GatewayNotInstalled); export uses
|
|
30
|
-
ExportPMX.
|
|
27
|
+
- Its own routes on the same base URL: whoami, logs, console, run,
|
|
28
|
+
export and import (GatewayClient). Without the gateway, whoami/logs/
|
|
29
|
+
console/run/import do not exist (GatewayNotInstalled); export uses
|
|
30
|
+
webdesign's ExportPMX.
|
|
31
31
|
|
|
32
32
|
Every failure carries a machine-readable code (`.code`): the gateway's
|
|
33
33
|
own refusal code as sent, or one the CLI sets - NOT_FOUND (404),
|
|
@@ -110,6 +110,8 @@ _ZIP_MAGIC = b"PK\x03\x04"
|
|
|
110
110
|
DOWNLOAD_TIMEOUT_SECONDS = 120
|
|
111
111
|
UPDATE_TIMEOUT_SECONDS = 60
|
|
112
112
|
REQUEST_TIMEOUT_SECONDS = 30
|
|
113
|
+
# object run waits for the action to finish (prod's Cloudflare still cuts at ~100s)
|
|
114
|
+
RUN_TIMEOUT_SECONDS = 300
|
|
113
115
|
|
|
114
116
|
_ROUTE_MISSING = "Api does not exist"
|
|
115
117
|
|
|
@@ -775,7 +777,7 @@ class WhoAmI(NamedTuple):
|
|
|
775
777
|
class GatewayClient(WebDesignClient):
|
|
776
778
|
"""
|
|
777
779
|
The gateway's local routes (never forwarded to webdesign): whoami, logs,
|
|
778
|
-
console, export and import. On a server with a blank gateway_path they
|
|
780
|
+
console, run, export and import. On a server with a blank gateway_path they
|
|
779
781
|
raise GatewayNotInstalled - except export, which goes to webdesign's
|
|
780
782
|
ExportPMX instead.
|
|
781
783
|
"""
|
|
@@ -831,6 +833,19 @@ class GatewayClient(WebDesignClient):
|
|
|
831
833
|
reply = _as_dict(self._request("POST", "console", {"cmd": cmd}))
|
|
832
834
|
return str(reply.get("reply") or "")
|
|
833
835
|
|
|
836
|
+
def run(self, path: str, reply: bool = False) -> dict[str, Any]:
|
|
837
|
+
"""
|
|
838
|
+
POST run (GatewaySystem): runs the ACTION at path
|
|
839
|
+
(/group/app.pma/Name) now, as this identity, and waits for it. The
|
|
840
|
+
reply is {ok, path, durationMs, redirectTo, contentType,
|
|
841
|
+
httpReplyCode, size}, plus body (the action's reply as text) when
|
|
842
|
+
reply is True, or exception when the action threw. A path that is
|
|
843
|
+
not a compiled ACTION is NOT_FOUND.
|
|
844
|
+
"""
|
|
845
|
+
self._require_gateway("'vortex object run'")
|
|
846
|
+
body = {"path": path, "reply": "1" if reply else ""}
|
|
847
|
+
return _as_dict(self._request("POST", "run", body, timeout=RUN_TIMEOUT_SECONDS))
|
|
848
|
+
|
|
834
849
|
def get_last_log_items(
|
|
835
850
|
self,
|
|
836
851
|
limit_items: int,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vortex_cli
|
|
3
|
-
Version: 8.
|
|
3
|
+
Version: 8.2.0
|
|
4
4
|
Summary: Vortex CLI
|
|
5
5
|
Author-email: Jordan Amos <jordan.amos@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -106,6 +106,18 @@ While it is possible to use without it, this software has been purposefully desi
|
|
|
106
106
|
java_environment_name = JavaSE-17 ; Java Execution Environment name https://docs.osgi.org/reference/eenames.html
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
## Upgrading to 8.2
|
|
110
|
+
|
|
111
|
+
- **`object run` runs ACTIONs, and waits.** It calls the gateway's new `POST run` (gateway
|
|
112
|
+
`AppVersion` 0.1.4 or later) instead of sending `tell agenda run` to the console, and
|
|
113
|
+
reports how long the action took, its reply and any exception (see
|
|
114
|
+
[Running an action](#running-an-action)). An older gateway refuses it with `NOT_EXPOSED`.
|
|
115
|
+
It no longer accepts a SCHEDULED_ACTION.
|
|
116
|
+
- **`vortex agenda`** lists the scheduled actions AGENDA has loaded and queues one with
|
|
117
|
+
`agenda run`, refusing the cases where AGENDA would silently do nothing (see
|
|
118
|
+
[The agenda](#the-agenda)). It replaces both `object run` on a scheduled action and
|
|
119
|
+
`vortex execute tell agenda run ...`.
|
|
120
|
+
|
|
109
121
|
## Upgrading to 8.1
|
|
110
122
|
|
|
111
123
|
8.1 does two things. It replaces `vortex config` with plain top-level commands that read
|
|
@@ -289,7 +301,7 @@ vortex object list [QUERY] [--app-id ID [--local]] [--strict --inherits-from|-
|
|
|
289
301
|
--ids-only --show-params --type T...] [--json]
|
|
290
302
|
vortex object grep PATTERN [--app-id ID] [--output-paths|--output-apps]
|
|
291
303
|
[--include-resources|--type T...] [--json]
|
|
292
|
-
vortex object run ID --app-id ID
|
|
304
|
+
vortex object run ID --app-id ID [--output] [--json]
|
|
293
305
|
vortex object delete ID... --app-id ID [--yes]
|
|
294
306
|
|
|
295
307
|
── keyword ──────────────────────────────────────────────────────────────────
|
|
@@ -315,6 +327,10 @@ vortex db update-column DB TABLE COLUMN --app-id ID [--name --type --size --desc
|
|
|
315
327
|
--cascade-delete|--no-cascade-delete]
|
|
316
328
|
vortex db delete-column DB TABLE COLUMN --app-id ID [--yes]
|
|
317
329
|
|
|
330
|
+
── agenda ───────────────────────────────────────────────────────────────────
|
|
331
|
+
vortex agenda [list] [--app-id ID] [--json]
|
|
332
|
+
vortex agenda run ID --app-id ID [--param KEY=VALUE ...] [--refresh --force] [--json]
|
|
333
|
+
|
|
318
334
|
── Workspace ────────────────────────────────────────────────────────────────
|
|
319
335
|
vortex ls [app list filters] [--json] = app list --local
|
|
320
336
|
vortex clone APP... [--group --reclone --all --get-resources --open-urls --timeout]
|
|
@@ -412,7 +428,7 @@ addins match:
|
|
|
412
428
|
|
|
413
429
|
| Shortcut | Sends | Effect |
|
|
414
430
|
|---|---|---|
|
|
415
|
-
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run |
|
|
431
|
+
| `schedule` | `tell agenda schedule` | lists every scheduled action and its next run (raw text; `vortex agenda` parses it) |
|
|
416
432
|
| `refresh-agenda` | `tell agenda refresh` | AGENDA rereads every schedule now |
|
|
417
433
|
| `refresh-design APP_ID` | `tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N` | rebuilds the application's design from its template, as `app refresh` does (a clone is then out of date) |
|
|
418
434
|
| `flush-cache` | `tell http cache flush` | clears the design cache and all action class loaders |
|
|
@@ -420,12 +436,28 @@ addins match:
|
|
|
420
436
|
`tell http flush cache` is **not** a valid command: the HTTP addin only matches
|
|
421
437
|
`cache flush`, and anything else does nothing, silently. Use `flush-cache`.
|
|
422
438
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
439
|
+
`vortex app refresh APP_ID` also lives with its noun: it sends
|
|
440
|
+
`tell agenda run /<webdesign_path>/RefreshDesign?&AppID=N`, which rebuilds the design from its
|
|
441
|
+
template (a clone is then out of date).
|
|
442
|
+
|
|
443
|
+
### Running an action
|
|
444
|
+
|
|
445
|
+
`vortex object run ID --app-id N` runs an ACTION now, as your identity, and waits for it:
|
|
446
|
+
the gateway's `POST run` (`GatewaySystem`) loads the action's class, executes it and replies
|
|
447
|
+
with how long it took, its redirect, content type and reply size. `--output` also prints the
|
|
448
|
+
action's reply (its buffer, as text). An action that throws exits 1 with `ACTION_FAILED` and
|
|
449
|
+
the exception; a path with no compiled ACTION is `NOT_FOUND`.
|
|
450
|
+
|
|
451
|
+
- **ACTIONs only.** A SCHEDULED_ACTION (or any other type) is `WRONG_TYPE`. Scheduled
|
|
452
|
+
actions are queued with AGENDA: `vortex agenda run ID --app-id N` ([The agenda](#the-agenda)).
|
|
453
|
+
- **No client connection.** The action's `getOutputStream()` is null, as under AGENDA, so it
|
|
454
|
+
must buffer its reply (`write`/`setBuffer`); one that streams unconditionally fails with
|
|
455
|
+
the NullPointerException as its result.
|
|
456
|
+
- **Synchronous.** The CLI waits up to 300 s. Behind Cloudflare (prod) the request is cut
|
|
457
|
+
at about 100 s; the action still finishes on the server - check `vortex log`.
|
|
458
|
+
- **Gateway required.** It needs a gateway with the `run` route; an older gateway refuses with
|
|
459
|
+
`NOT_EXPOSED`. Earlier versions of `object run` sent `tell agenda run`, which only reached
|
|
460
|
+
scheduled actions on AGENDA's list and did nothing, silently, for an ACTION.
|
|
429
461
|
|
|
430
462
|
The 8.0 flags (`--show-schedule`, `--refresh-agenda`, `--flush-cache`, `--refresh-design`,
|
|
431
463
|
`--run`) were removed in 8.1. `app refresh` was `app refresh-design` in 8.1.0; the old
|
|
@@ -573,6 +605,40 @@ vortex object get 7901 --app-id 62 # the parsed schedule
|
|
|
573
605
|
starts; if that lands between the command's read and write, the old `LastRun` is written
|
|
574
606
|
back and the action may run one interval early.
|
|
575
607
|
|
|
608
|
+
### The agenda
|
|
609
|
+
|
|
610
|
+
AGENDA has no API beyond its console commands, so `vortex agenda` sends them through the same
|
|
611
|
+
console route as `vortex execute` (the gateway's `POST console`, `GatewaySystem`, or the SOAP
|
|
612
|
+
console without the gateway) and parses the text they answer.
|
|
613
|
+
|
|
614
|
+
```
|
|
615
|
+
vortex agenda -s dev # = agenda list
|
|
616
|
+
vortex agenda list --app-id 9 -s dev
|
|
617
|
+
vortex agenda run 7825 --app-id 60 -s dev [--param AppID=12] [--refresh] [--force]
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
- **`list`** - `tell agenda schedule`: every scheduled action AGENDA has **loaded**, by next
|
|
621
|
+
run (server time), with the waitlist and AGENDA's counts (running, waitlisted, scheduled,
|
|
622
|
+
max at once). An action with empty `Options` is loaded as *on demand only*. Not listed: an
|
|
623
|
+
action with `Schedule=N`, one in a disabled application or an application with scheduled
|
|
624
|
+
actions disabled, and one created or renamed since AGENDA's last refresh (every 15
|
|
625
|
+
minutes). AGENDA says how many actions are running, never which.
|
|
626
|
+
- **`run`** - queues a SCHEDULED_ACTION: `tell agenda run /group/app.pma/Name[?&KEY=VALUE...]`.
|
|
627
|
+
It runs as the system session when one of AGENDA's slots is free; its result is only in
|
|
628
|
+
`vortex log`. AGENDA answers that command with nothing, whatever it did, so `run` checks
|
|
629
|
+
first and sends nothing when AGENDA would not run it:
|
|
630
|
+
- `NOT_ON_AGENDA` - the action has `Schedule=N` (give it a schedule first), or it is not on
|
|
631
|
+
the loaded list. `--refresh` sends `tell agenda refresh` and checks again (for an action
|
|
632
|
+
created or renamed in the last 15 minutes).
|
|
633
|
+
- `ALREADY_QUEUED` - it is already waitlisted; AGENDA would skip it.
|
|
634
|
+
- `PREFIX_COLLISION` - AGENDA matches the path by prefix and queues it once per matching
|
|
635
|
+
item, so `.../SyncAll` would also match an item `.../Sync` and run twice. `--force` sends
|
|
636
|
+
it anyway.
|
|
637
|
+
- `WRONG_TYPE` - not a SCHEDULED_ACTION (an ACTION is run with `vortex object run`).
|
|
638
|
+
- A queued run moves the action's next scheduled run (AGENDA resets it), and it can't be
|
|
639
|
+
stopped from here (`vortex execute tell agenda stop /group/app.pma/Name` asks the action to
|
|
640
|
+
quit; it has to check for that itself).
|
|
641
|
+
|
|
576
642
|
### Keyword values and secrets
|
|
577
643
|
|
|
578
644
|
Keywords are an application's live configuration, and in practice they hold credentials in
|
|
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
|
|
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
|
|
File without changes
|