orchestra-cli 0.4.0__tar.gz → 0.5.1__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.
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/PKG-INFO +45 -3
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/README.md +44 -2
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/build_pipeline.py +19 -8
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/cli.py +6 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/create_pipeline.py +5 -5
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/delete_pipeline.py +5 -5
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/fetch_pipelines.py +5 -6
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/get_pipeline.py +5 -5
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/import_pipeline.py +5 -5
- orchestra_cli-0.5.1/orchestra_cli/src/list_accounts.py +16 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/login.py +49 -3
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/migrate_pipeline.py +18 -10
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/run_pipeline.py +55 -21
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/task_logs.py +23 -15
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/update_pipeline.py +6 -6
- orchestra_cli-0.5.1/orchestra_cli/src/use_account.py +61 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/conftest.py +16 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_delete_pipeline.py +4 -15
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_fetch_pipelines.py +41 -0
- orchestra_cli-0.5.1/orchestra_cli/tests/test_list_accounts.py +84 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_login.py +136 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_run_pipeline.py +101 -1
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_task_logs.py +34 -0
- orchestra_cli-0.5.1/orchestra_cli/tests/test_use_account.py +214 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/test_api.py +35 -0
- orchestra_cli-0.5.1/orchestra_cli/tests/utils/test_picker.py +45 -0
- orchestra_cli-0.5.1/orchestra_cli/utils/accounts.py +44 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/api.py +60 -11
- orchestra_cli-0.5.1/orchestra_cli/utils/picker.py +58 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_lookup.py +3 -4
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/PKG-INFO +45 -3
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/SOURCES.txt +7 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/pyproject.toml +1 -1
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/__init__.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/login_page.html +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/pipeline_upsert.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/src/validate_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/__init__.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_build_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_cli.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_create_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_get_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_import_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_migrate_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_pipeline_selector.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_update_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/test_validate_pipeline.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/__init__.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/tests/utils/test_git.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/constants.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/credentials.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/git.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_selector.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/pipeline_update.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/styling.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli/utils/yaml_loader.py +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/dependency_links.txt +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/entry_points.txt +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/requires.txt +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/orchestra_cli.egg-info/top_level.txt +0 -0
- {orchestra_cli-0.4.0 → orchestra_cli-0.5.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: orchestra-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Orchestra CLI. Perform operations with Orchestra locally.
|
|
5
5
|
Author-email: Orchestra Technologies <support@getorchestra.io>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -34,12 +34,13 @@ pipx install orchestra-cli
|
|
|
34
34
|
|
|
35
35
|
## Environment variables
|
|
36
36
|
|
|
37
|
-
- `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
|
|
37
|
+
- `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`, `accounts list`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
|
|
38
|
+
- `ORCHESTRA_ACCOUNT_ID`: Optional. The workspace to act in when your `orchestra login` covers several. Every `pipeline` and `task` command that calls the API also takes `--account-id`, which wins over the variable, and both win over the default saved by `orchestra accounts use`. API keys belong to one account and don't need it.
|
|
38
39
|
- `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
|
|
39
40
|
|
|
40
41
|
## Command structure
|
|
41
42
|
|
|
42
|
-
Commands follow a `noun verb` shape. The current nouns are `pipeline` and `
|
|
43
|
+
Commands follow a `noun verb` shape. The current nouns are `pipeline`, `task` and `accounts`:
|
|
43
44
|
|
|
44
45
|
| Command | Description |
|
|
45
46
|
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
@@ -55,6 +56,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
55
56
|
| `orchestra pipeline run` | Start a pipeline run by selector, optionally pinning branch/commit and waiting. |
|
|
56
57
|
| `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
|
|
57
58
|
| `orchestra task logs` | Fetch or follow logs for a single task run. |
|
|
59
|
+
| `orchestra accounts list` | List the workspaces your login or API key covers, with their ids, as JSON. |
|
|
60
|
+
| `orchestra accounts use [id\|name]` | Save the workspace later commands act in when no `--account-id` is given. |
|
|
58
61
|
|
|
59
62
|
Commands that read a local pipeline file (`pipeline validate`, `import`, `new`, `update`, `build`, and `run --path` with `--task`) accept `.yaml`, `.yml` and `.oml` files; any other extension is rejected. `.oml` files are read as YAML, so only OML written in YAML or JSON syntax is supported. OML block terminators (`.`) and embeds (`%sql ... %%`) fail with an `Invalid YAML` error.
|
|
60
63
|
|
|
@@ -123,6 +126,8 @@ orchestra login
|
|
|
123
126
|
|
|
124
127
|
Logins are per host: a login made with `BASE_URL` pointing at another environment is only used against that environment. CI and scripts, where nobody is logged in, keep using `ORCHESTRA_API_KEY`.
|
|
125
128
|
|
|
129
|
+
After logging in it saves a default workspace, as `accounts use` does: the only one if your login covers one, or the one you pick from a list if it covers several. Esc skips the default, and without a terminal it prints a hint to run `orchestra accounts use` instead. If the accounts can't be listed, the login still succeeds with no default set.
|
|
130
|
+
|
|
126
131
|
---
|
|
127
132
|
|
|
128
133
|
## pipeline import
|
|
@@ -413,6 +418,43 @@ Behavior
|
|
|
413
418
|
|
|
414
419
|
---
|
|
415
420
|
|
|
421
|
+
## accounts list
|
|
422
|
+
|
|
423
|
+
List the workspaces the current credential covers. An `orchestra login` token can cover several workspaces; an API key lists just its own.
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
orchestra accounts list
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Behavior
|
|
430
|
+
|
|
431
|
+
- Sends `GET /public/v1/accounts`, with no account header.
|
|
432
|
+
- Prints the `[{id, name}]` response as pretty JSON, adding `default: true` to the account saved by `accounts use` and `false` to the rest.
|
|
433
|
+
- Exit codes: `0` on success, `1` on failure.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## accounts use
|
|
438
|
+
|
|
439
|
+
Save a default workspace for an `orchestra login` that covers several, so commands no longer need `--account-id`.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
orchestra accounts use 'some great account'
|
|
443
|
+
orchestra accounts use 00000000-0000-0000-0000-000000000000
|
|
444
|
+
orchestra accounts use # pick from a list
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Behavior
|
|
448
|
+
|
|
449
|
+
- Fetches the accounts as `accounts list` does, then matches an exact id first and a case-insensitive exact name second.
|
|
450
|
+
- Exits `1`, listing the valid names, when nothing matches, and asks for the id when several workspaces share the name.
|
|
451
|
+
- With no argument, lists the workspaces with the cursor on the current default: ↑/↓ moves, Enter saves, and Esc, Ctrl+C or Ctrl+D exits `1` leaving the default unchanged. Without a terminal (CI, piped input) it exits `1` and asks for an id or name instead.
|
|
452
|
+
- Saves the account into the login's entry in `~/.orchestra/credentials.json`. It survives token refreshes; running `orchestra login` again resets it.
|
|
453
|
+
- `--account-id` and `ORCHESTRA_ACCOUNT_ID` still override the default. It is never sent with an API key.
|
|
454
|
+
- Exits `1` when you are using an API key rather than a login, since a key covers one account.
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
416
458
|
## task logs
|
|
417
459
|
|
|
418
460
|
Fetch or follow logs for a single task run.
|
|
@@ -20,12 +20,13 @@ pipx install orchestra-cli
|
|
|
20
20
|
|
|
21
21
|
## Environment variables
|
|
22
22
|
|
|
23
|
-
- `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
|
|
23
|
+
- `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`, `accounts list`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
|
|
24
|
+
- `ORCHESTRA_ACCOUNT_ID`: Optional. The workspace to act in when your `orchestra login` covers several. Every `pipeline` and `task` command that calls the API also takes `--account-id`, which wins over the variable, and both win over the default saved by `orchestra accounts use`. API keys belong to one account and don't need it.
|
|
24
25
|
- `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
|
|
25
26
|
|
|
26
27
|
## Command structure
|
|
27
28
|
|
|
28
|
-
Commands follow a `noun verb` shape. The current nouns are `pipeline` and `
|
|
29
|
+
Commands follow a `noun verb` shape. The current nouns are `pipeline`, `task` and `accounts`:
|
|
29
30
|
|
|
30
31
|
| Command | Description |
|
|
31
32
|
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
@@ -41,6 +42,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
41
42
|
| `orchestra pipeline run` | Start a pipeline run by selector, optionally pinning branch/commit and waiting. |
|
|
42
43
|
| `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
|
|
43
44
|
| `orchestra task logs` | Fetch or follow logs for a single task run. |
|
|
45
|
+
| `orchestra accounts list` | List the workspaces your login or API key covers, with their ids, as JSON. |
|
|
46
|
+
| `orchestra accounts use [id\|name]` | Save the workspace later commands act in when no `--account-id` is given. |
|
|
44
47
|
|
|
45
48
|
Commands that read a local pipeline file (`pipeline validate`, `import`, `new`, `update`, `build`, and `run --path` with `--task`) accept `.yaml`, `.yml` and `.oml` files; any other extension is rejected. `.oml` files are read as YAML, so only OML written in YAML or JSON syntax is supported. OML block terminators (`.`) and embeds (`%sql ... %%`) fail with an `Invalid YAML` error.
|
|
46
49
|
|
|
@@ -109,6 +112,8 @@ orchestra login
|
|
|
109
112
|
|
|
110
113
|
Logins are per host: a login made with `BASE_URL` pointing at another environment is only used against that environment. CI and scripts, where nobody is logged in, keep using `ORCHESTRA_API_KEY`.
|
|
111
114
|
|
|
115
|
+
After logging in it saves a default workspace, as `accounts use` does: the only one if your login covers one, or the one you pick from a list if it covers several. Esc skips the default, and without a terminal it prints a hint to run `orchestra accounts use` instead. If the accounts can't be listed, the login still succeeds with no default set.
|
|
116
|
+
|
|
112
117
|
---
|
|
113
118
|
|
|
114
119
|
## pipeline import
|
|
@@ -399,6 +404,43 @@ Behavior
|
|
|
399
404
|
|
|
400
405
|
---
|
|
401
406
|
|
|
407
|
+
## accounts list
|
|
408
|
+
|
|
409
|
+
List the workspaces the current credential covers. An `orchestra login` token can cover several workspaces; an API key lists just its own.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
orchestra accounts list
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Behavior
|
|
416
|
+
|
|
417
|
+
- Sends `GET /public/v1/accounts`, with no account header.
|
|
418
|
+
- Prints the `[{id, name}]` response as pretty JSON, adding `default: true` to the account saved by `accounts use` and `false` to the rest.
|
|
419
|
+
- Exit codes: `0` on success, `1` on failure.
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## accounts use
|
|
424
|
+
|
|
425
|
+
Save a default workspace for an `orchestra login` that covers several, so commands no longer need `--account-id`.
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
orchestra accounts use 'some great account'
|
|
429
|
+
orchestra accounts use 00000000-0000-0000-0000-000000000000
|
|
430
|
+
orchestra accounts use # pick from a list
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Behavior
|
|
434
|
+
|
|
435
|
+
- Fetches the accounts as `accounts list` does, then matches an exact id first and a case-insensitive exact name second.
|
|
436
|
+
- Exits `1`, listing the valid names, when nothing matches, and asks for the id when several workspaces share the name.
|
|
437
|
+
- With no argument, lists the workspaces with the cursor on the current default: ↑/↓ moves, Enter saves, and Esc, Ctrl+C or Ctrl+D exits `1` leaving the default unchanged. Without a terminal (CI, piped input) it exits `1` and asks for an id or name instead.
|
|
438
|
+
- Saves the account into the login's entry in `~/.orchestra/credentials.json`. It survives token refreshes; running `orchestra login` again resets it.
|
|
439
|
+
- `--account-id` and `ORCHESTRA_ACCOUNT_ID` still override the default. It is never sent with an API key.
|
|
440
|
+
- Exits `1` when you are using an API key rather than a login, since a key covers one account.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
402
444
|
## task logs
|
|
403
445
|
|
|
404
446
|
Fetch or follow logs for a single task run.
|
|
@@ -3,7 +3,13 @@ from pathlib import Path
|
|
|
3
3
|
import httpx
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
|
-
from ..utils.api import
|
|
6
|
+
from ..utils.api import (
|
|
7
|
+
account_id_option,
|
|
8
|
+
api_client,
|
|
9
|
+
fail_with_response,
|
|
10
|
+
request_or_exit,
|
|
11
|
+
require_credential,
|
|
12
|
+
)
|
|
7
13
|
from ..utils.constants import get_create_pipeline_url, get_update_pipeline_url
|
|
8
14
|
from ..utils.git import confirm_git_warnings_or_exit, prepare_git_backed_run_target
|
|
9
15
|
from ..utils.pipeline_lookup import lookup_existing_pipeline
|
|
@@ -64,6 +70,7 @@ def _build_create_selector(
|
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
def _create_draft_pipeline(
|
|
73
|
+
client: httpx.Client,
|
|
67
74
|
path: Path,
|
|
68
75
|
lookup_selector: PipelineSelector,
|
|
69
76
|
pipeline_data: dict[str, object],
|
|
@@ -74,11 +81,9 @@ def _create_draft_pipeline(
|
|
|
74
81
|
|
|
75
82
|
typer.echo(f"Creating draft pipeline ({create_selector.display()})")
|
|
76
83
|
create_response = request_or_exit(
|
|
77
|
-
|
|
84
|
+
client.post,
|
|
78
85
|
get_create_pipeline_url(),
|
|
79
86
|
json=payload,
|
|
80
|
-
timeout=30,
|
|
81
|
-
headers=auth_headers(),
|
|
82
87
|
)
|
|
83
88
|
|
|
84
89
|
if create_response.status_code == 201:
|
|
@@ -95,6 +100,7 @@ def _create_draft_pipeline(
|
|
|
95
100
|
|
|
96
101
|
|
|
97
102
|
def _update_draft_pipeline(
|
|
103
|
+
client: httpx.Client,
|
|
98
104
|
existing_pipeline: dict[str, object],
|
|
99
105
|
pipeline_data: dict[str, object],
|
|
100
106
|
) -> tuple[PipelineSelector, int]:
|
|
@@ -103,11 +109,9 @@ def _update_draft_pipeline(
|
|
|
103
109
|
|
|
104
110
|
typer.echo(f"Updating draft pipeline ({update_selector.display()})")
|
|
105
111
|
update_response = request_or_exit(
|
|
106
|
-
|
|
112
|
+
client.put,
|
|
107
113
|
get_update_pipeline_url(),
|
|
108
114
|
json=payload,
|
|
109
|
-
timeout=30,
|
|
110
|
-
headers=auth_headers(),
|
|
111
115
|
)
|
|
112
116
|
|
|
113
117
|
if update_response.status_code == 200:
|
|
@@ -139,11 +143,13 @@ def build_pipeline(
|
|
|
139
143
|
"--force/--no-force",
|
|
140
144
|
help="Ignore any warnings and run the pipeline anyway",
|
|
141
145
|
),
|
|
146
|
+
account_id: str | None = account_id_option(),
|
|
142
147
|
) -> None:
|
|
143
148
|
"""
|
|
144
149
|
Validate local YAML, create or update a draft pipeline, and start the draft version.
|
|
145
150
|
"""
|
|
146
151
|
require_credential()
|
|
152
|
+
client = api_client(account_id)
|
|
147
153
|
if path is None:
|
|
148
154
|
typer.echo(
|
|
149
155
|
red("A pipeline YAML file path is required (use -p or --path with your YAML file)"),
|
|
@@ -153,16 +159,18 @@ def build_pipeline(
|
|
|
153
159
|
|
|
154
160
|
confirm_git_warnings_or_exit(force, path)
|
|
155
161
|
pipeline_data = load_validated_pipeline_data(path)
|
|
156
|
-
existing_pipeline = lookup_existing_pipeline(lookup_selector, "Build", allow_404=True)
|
|
162
|
+
existing_pipeline = lookup_existing_pipeline(client, lookup_selector, "Build", allow_404=True)
|
|
157
163
|
|
|
158
164
|
if existing_pipeline is None:
|
|
159
165
|
run_selector, version_number = _create_draft_pipeline(
|
|
166
|
+
client=client,
|
|
160
167
|
path=path,
|
|
161
168
|
lookup_selector=lookup_selector,
|
|
162
169
|
pipeline_data=pipeline_data,
|
|
163
170
|
force=force,
|
|
164
171
|
)
|
|
165
172
|
start_pipeline_run(
|
|
173
|
+
client=client,
|
|
166
174
|
selector=run_selector,
|
|
167
175
|
payload=build_run_payload(
|
|
168
176
|
branch=branch,
|
|
@@ -177,11 +185,13 @@ def build_pipeline(
|
|
|
177
185
|
pipeline_storage_provider = storage_provider(existing_pipeline)
|
|
178
186
|
if pipeline_storage_provider is None or pipeline_storage_provider == "ORCHESTRA":
|
|
179
187
|
run_selector, version_number = _update_draft_pipeline(
|
|
188
|
+
client=client,
|
|
180
189
|
existing_pipeline=existing_pipeline,
|
|
181
190
|
pipeline_data=pipeline_data,
|
|
182
191
|
)
|
|
183
192
|
|
|
184
193
|
start_pipeline_run(
|
|
194
|
+
client=client,
|
|
185
195
|
selector=run_selector,
|
|
186
196
|
payload=build_run_payload(
|
|
187
197
|
branch=branch,
|
|
@@ -215,6 +225,7 @@ def build_pipeline(
|
|
|
215
225
|
)
|
|
216
226
|
|
|
217
227
|
start_pipeline_run(
|
|
228
|
+
client=client,
|
|
218
229
|
selector=run_selector,
|
|
219
230
|
payload=build_run_payload(
|
|
220
231
|
branch=git_branch,
|
|
@@ -6,11 +6,13 @@ from .delete_pipeline import delete_pipeline
|
|
|
6
6
|
from .fetch_pipelines import fetch_pipelines
|
|
7
7
|
from .get_pipeline import get_pipeline
|
|
8
8
|
from .import_pipeline import import_pipeline
|
|
9
|
+
from .list_accounts import list_accounts
|
|
9
10
|
from .login import login
|
|
10
11
|
from .migrate_pipeline import migrate_pipeline
|
|
11
12
|
from .run_pipeline import run_pipeline
|
|
12
13
|
from .task_logs import task_logs
|
|
13
14
|
from .update_pipeline import update_pipeline
|
|
15
|
+
from .use_account import use_account
|
|
14
16
|
from .validate_pipeline import validate
|
|
15
17
|
|
|
16
18
|
app = typer.Typer(help="Orchestra CLI – perform operations with Orchestra locally.")
|
|
@@ -20,6 +22,8 @@ app.add_typer(pipeline_app, name="pipeline")
|
|
|
20
22
|
task_app = typer.Typer(help="Inspect Orchestra task runs.")
|
|
21
23
|
app.add_typer(task_app, name="task")
|
|
22
24
|
app.command(name="login")(login)
|
|
25
|
+
accounts_app = typer.Typer(help="List and choose the workspaces your credential covers.")
|
|
26
|
+
app.add_typer(accounts_app, name="accounts")
|
|
23
27
|
|
|
24
28
|
pipeline_app.command(name="validate")(validate)
|
|
25
29
|
pipeline_app.command(name="import")(import_pipeline)
|
|
@@ -32,6 +36,8 @@ pipeline_app.command(name="build")(build_pipeline)
|
|
|
32
36
|
pipeline_app.command(name="delete")(delete_pipeline)
|
|
33
37
|
pipeline_app.command(name="migrate")(migrate_pipeline)
|
|
34
38
|
task_app.command(name="logs")(task_logs)
|
|
39
|
+
accounts_app.command(name="list")(list_accounts)
|
|
40
|
+
accounts_app.command(name="use")(use_account)
|
|
35
41
|
|
|
36
42
|
# Legacy top-level aliases (hidden) - keep the old `orchestra <command>` syntax working
|
|
37
43
|
# so existing scripts and CI pipelines do not break. Hidden from `--help` to keep the
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
import httpx
|
|
4
3
|
import typer
|
|
5
4
|
|
|
6
5
|
from ..utils.api import (
|
|
7
|
-
|
|
6
|
+
account_id_option,
|
|
7
|
+
api_client,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
10
|
require_credential,
|
|
@@ -32,11 +32,13 @@ def create_pipeline(
|
|
|
32
32
|
"--publish/--no-publish",
|
|
33
33
|
help="Whether the pipeline is published and can be triggered",
|
|
34
34
|
),
|
|
35
|
+
account_id: str | None = account_id_option(),
|
|
35
36
|
):
|
|
36
37
|
"""
|
|
37
38
|
Create an Orchestra-backed pipeline from a local YAML file.
|
|
38
39
|
"""
|
|
39
40
|
require_credential()
|
|
41
|
+
client = api_client(account_id)
|
|
40
42
|
if path is None:
|
|
41
43
|
typer.echo(red("Provide --path to create a pipeline from YAML"))
|
|
42
44
|
raise typer.Exit(code=1)
|
|
@@ -50,11 +52,9 @@ def create_pipeline(
|
|
|
50
52
|
payload = build_upsert_payload(data, publish, selector)
|
|
51
53
|
|
|
52
54
|
response = request_or_exit(
|
|
53
|
-
|
|
55
|
+
client.post,
|
|
54
56
|
get_create_pipeline_url(),
|
|
55
57
|
json=payload,
|
|
56
|
-
timeout=30,
|
|
57
|
-
headers=auth_headers(),
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
if response.status_code == 201:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
import httpx
|
|
4
3
|
import typer
|
|
5
4
|
|
|
6
5
|
from ..utils.api import (
|
|
7
|
-
|
|
6
|
+
account_id_option,
|
|
7
|
+
api_client,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
10
|
require_credential,
|
|
@@ -23,11 +23,13 @@ def delete_pipeline(
|
|
|
23
23
|
path: Path | None = pipeline_path_option(),
|
|
24
24
|
alias: str | None = pipeline_alias_option(),
|
|
25
25
|
pipeline_id: str | None = pipeline_id_option(),
|
|
26
|
+
account_id: str | None = account_id_option(),
|
|
26
27
|
):
|
|
27
28
|
"""
|
|
28
29
|
Delete a pipeline by selector.
|
|
29
30
|
"""
|
|
30
31
|
require_credential()
|
|
32
|
+
client = api_client(account_id)
|
|
31
33
|
selector = resolve_pipeline_selector(alias, pipeline_id, path)
|
|
32
34
|
|
|
33
35
|
if not typer.confirm(f"Delete pipeline ({selector.display()})?"):
|
|
@@ -35,11 +37,9 @@ def delete_pipeline(
|
|
|
35
37
|
raise typer.Exit(code=1)
|
|
36
38
|
|
|
37
39
|
response = request_or_exit(
|
|
38
|
-
|
|
40
|
+
client.delete,
|
|
39
41
|
get_delete_pipeline_url(),
|
|
40
42
|
params=selector.to_payload(),
|
|
41
|
-
timeout=30,
|
|
42
|
-
headers=auth_headers(),
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
if response.status_code == 204:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
|
-
import httpx
|
|
4
3
|
import typer
|
|
5
4
|
|
|
6
5
|
from ..utils.api import (
|
|
7
|
-
|
|
6
|
+
account_id_option,
|
|
7
|
+
api_client,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
10
|
require_credential,
|
|
@@ -13,19 +13,18 @@ from ..utils.constants import get_api_url
|
|
|
13
13
|
from ..utils.styling import indent_message, red, yellow
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def fetch_pipelines():
|
|
16
|
+
def fetch_pipelines(account_id: str | None = account_id_option()):
|
|
17
17
|
"""
|
|
18
18
|
Fetch pipelines available to the current Orchestra API key.
|
|
19
19
|
|
|
20
20
|
The API always includes each pipeline's latest run metadata.
|
|
21
21
|
"""
|
|
22
22
|
require_credential()
|
|
23
|
+
client = api_client(account_id)
|
|
23
24
|
|
|
24
25
|
response = request_or_exit(
|
|
25
|
-
|
|
26
|
+
client.get,
|
|
26
27
|
get_api_url("pipelines"),
|
|
27
|
-
timeout=30,
|
|
28
|
-
headers=auth_headers(),
|
|
29
28
|
)
|
|
30
29
|
|
|
31
30
|
if response.status_code == 200:
|
|
@@ -3,11 +3,11 @@ import re
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import cast
|
|
5
5
|
|
|
6
|
-
import httpx
|
|
7
6
|
import typer
|
|
8
7
|
|
|
9
8
|
from ..utils.api import (
|
|
10
|
-
|
|
9
|
+
account_id_option,
|
|
10
|
+
api_client,
|
|
11
11
|
fail_with_response,
|
|
12
12
|
request_or_exit,
|
|
13
13
|
require_credential,
|
|
@@ -46,19 +46,19 @@ def get_pipeline(
|
|
|
46
46
|
path: Path | None = pipeline_path_option(),
|
|
47
47
|
alias: str | None = pipeline_alias_option(),
|
|
48
48
|
pipeline_id: str | None = pipeline_id_option(),
|
|
49
|
+
account_id: str | None = account_id_option(),
|
|
49
50
|
):
|
|
50
51
|
"""
|
|
51
52
|
Fetch one pipeline using the shared selector model.
|
|
52
53
|
"""
|
|
53
54
|
require_credential()
|
|
55
|
+
client = api_client(account_id)
|
|
54
56
|
selector = resolve_pipeline_selector(alias, pipeline_id, path)
|
|
55
57
|
|
|
56
58
|
response = request_or_exit(
|
|
57
|
-
|
|
59
|
+
client.get,
|
|
58
60
|
get_pipeline_url(),
|
|
59
61
|
params=selector.to_payload(),
|
|
60
|
-
timeout=30,
|
|
61
|
-
headers=auth_headers(),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
if response.status_code == 200:
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
import httpx
|
|
5
4
|
import typer
|
|
6
5
|
|
|
7
6
|
from ..utils.api import (
|
|
8
|
-
|
|
7
|
+
account_id_option,
|
|
8
|
+
api_client,
|
|
9
9
|
fail_with_response,
|
|
10
10
|
request_or_exit,
|
|
11
11
|
require_credential,
|
|
@@ -39,11 +39,13 @@ def import_pipeline(
|
|
|
39
39
|
"-w",
|
|
40
40
|
help="Git branch to use for the imported pipeline (defaults to current local branch)",
|
|
41
41
|
),
|
|
42
|
+
account_id: str | None = account_id_option(),
|
|
42
43
|
):
|
|
43
44
|
"""
|
|
44
45
|
Create a pipeline in Orchestra by referencing a YAML file in your git repository.
|
|
45
46
|
"""
|
|
46
47
|
require_credential()
|
|
48
|
+
client = api_client(account_id)
|
|
47
49
|
if path is None:
|
|
48
50
|
typer.echo(red("Provide --path to import a pipeline from git"))
|
|
49
51
|
raise typer.Exit(code=1)
|
|
@@ -98,11 +100,9 @@ def import_pipeline(
|
|
|
98
100
|
payload["alias"] = alias
|
|
99
101
|
|
|
100
102
|
response = request_or_exit(
|
|
101
|
-
|
|
103
|
+
client.post,
|
|
102
104
|
get_api_url("pipelines/import"),
|
|
103
105
|
json=payload,
|
|
104
|
-
timeout=30,
|
|
105
|
-
headers=auth_headers(),
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
if response.status_code == 201:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from ..utils.accounts import fetch_accounts
|
|
6
|
+
from ..utils.api import api_client
|
|
7
|
+
from ..utils.credentials import load_credentials
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_accounts():
|
|
11
|
+
"""List the Orchestra workspaces the current login or API key covers."""
|
|
12
|
+
accounts = fetch_accounts(api_client(scoped=False))
|
|
13
|
+
# Read after fetching, which forgets a dead login whose default no longer applies.
|
|
14
|
+
default_id = (load_credentials() or {}).get("account_id")
|
|
15
|
+
accounts = [{**account, "default": account["id"] == default_id} for account in accounts]
|
|
16
|
+
typer.echo(json.dumps(accounts, indent=2))
|
|
@@ -10,9 +10,16 @@ from urllib.parse import parse_qs, urlencode, urlparse
|
|
|
10
10
|
import httpx
|
|
11
11
|
import typer
|
|
12
12
|
|
|
13
|
-
from ..utils.
|
|
13
|
+
from ..utils.accounts import fetch_accounts, pick_account
|
|
14
|
+
from ..utils.api import (
|
|
15
|
+
api_client,
|
|
16
|
+
fail_with_response,
|
|
17
|
+
request_or_exit,
|
|
18
|
+
token_response_to_credentials,
|
|
19
|
+
)
|
|
14
20
|
from ..utils.constants import get_base_url
|
|
15
|
-
from ..utils.credentials import save_credentials
|
|
21
|
+
from ..utils.credentials import load_credentials, save_credentials
|
|
22
|
+
from ..utils.picker import can_pick
|
|
16
23
|
from ..utils.styling import green, red, yellow
|
|
17
24
|
|
|
18
25
|
SCOPES = "orchestra:read orchestra:write offline_access"
|
|
@@ -159,4 +166,43 @@ def login():
|
|
|
159
166
|
{"client_id": client_id, "token_endpoint": metadata["token_endpoint"]},
|
|
160
167
|
),
|
|
161
168
|
)
|
|
162
|
-
|
|
169
|
+
_choose_default_account(f"✅ Logged in to {base_url}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _choose_default_account(logged_in: str) -> None:
|
|
173
|
+
"""Save the workspace later commands act in, so a multi-account login works from the start.
|
|
174
|
+
|
|
175
|
+
The login is already saved, so nothing here can fail it.
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
accounts = fetch_accounts(api_client(scoped=False))
|
|
179
|
+
except typer.Exit:
|
|
180
|
+
typer.echo(green(logged_in))
|
|
181
|
+
typer.echo(yellow("Could not list your accounts, so no default account is set."))
|
|
182
|
+
return
|
|
183
|
+
if len(accounts) == 1:
|
|
184
|
+
_save_default_account(accounts[0])
|
|
185
|
+
typer.echo(green(f"{logged_in} ({accounts[0]['name']})"))
|
|
186
|
+
return
|
|
187
|
+
typer.echo(green(logged_in))
|
|
188
|
+
if not accounts:
|
|
189
|
+
return
|
|
190
|
+
if not can_pick():
|
|
191
|
+
typer.echo(yellow("Your login covers several accounts; run `orchestra accounts use`."))
|
|
192
|
+
return
|
|
193
|
+
typer.echo("Pick a default account:")
|
|
194
|
+
account = pick_account(accounts)
|
|
195
|
+
if account is None:
|
|
196
|
+
typer.echo(yellow("No default account set; run `orchestra accounts use` to pick one."))
|
|
197
|
+
return
|
|
198
|
+
_save_default_account(account)
|
|
199
|
+
typer.echo(green(f"Default account: {account['name']}"))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _save_default_account(account: dict) -> None:
|
|
203
|
+
# Read again after fetching, which may have refreshed and rotated the saved tokens.
|
|
204
|
+
credentials = load_credentials()
|
|
205
|
+
if credentials is not None:
|
|
206
|
+
save_credentials(
|
|
207
|
+
{**credentials, "account_id": account["id"], "account_name": account["name"]},
|
|
208
|
+
)
|
|
@@ -5,7 +5,13 @@ from typing import Any
|
|
|
5
5
|
import httpx
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
|
-
from ..utils.api import
|
|
8
|
+
from ..utils.api import (
|
|
9
|
+
account_id_option,
|
|
10
|
+
api_client,
|
|
11
|
+
fail_with_response,
|
|
12
|
+
request_or_exit,
|
|
13
|
+
require_credential,
|
|
14
|
+
)
|
|
9
15
|
from ..utils.constants import get_api_url
|
|
10
16
|
from ..utils.git import (
|
|
11
17
|
GitAction,
|
|
@@ -121,17 +127,19 @@ def _choose_migration_version(existing_pipeline: dict[str, object], force: bool)
|
|
|
121
127
|
raise typer.Exit(code=1)
|
|
122
128
|
|
|
123
129
|
|
|
124
|
-
def _download_pipeline_yaml(
|
|
130
|
+
def _download_pipeline_yaml(
|
|
131
|
+
client: httpx.Client,
|
|
132
|
+
selector: PipelineSelector,
|
|
133
|
+
version: int | None,
|
|
134
|
+
) -> str:
|
|
125
135
|
params = selector.to_payload()
|
|
126
136
|
if version is not None:
|
|
127
137
|
params["version"] = str(version)
|
|
128
138
|
|
|
129
139
|
response = request_or_exit(
|
|
130
|
-
|
|
140
|
+
client.get,
|
|
131
141
|
get_api_url("pipeline/data"),
|
|
132
142
|
params=params,
|
|
133
|
-
timeout=30,
|
|
134
|
-
headers=auth_headers(),
|
|
135
143
|
)
|
|
136
144
|
if response.status_code != 200:
|
|
137
145
|
raise fail_with_response("Migrate", response)
|
|
@@ -340,11 +348,13 @@ def migrate_pipeline(
|
|
|
340
348
|
"--force/--no-force",
|
|
341
349
|
help="Skip interactive prompts and continue with inferred migration choices",
|
|
342
350
|
),
|
|
351
|
+
account_id: str | None = account_id_option(),
|
|
343
352
|
) -> None:
|
|
344
353
|
"""
|
|
345
354
|
Migrate an Orchestra-backed pipeline to git-backed storage.
|
|
346
355
|
"""
|
|
347
356
|
require_credential()
|
|
357
|
+
client = api_client(account_id)
|
|
348
358
|
selector = _resolve_migrate_selector(alias, pipeline_id)
|
|
349
359
|
|
|
350
360
|
repo_root = require_repo_root(path, GitAction.MIGRATE)
|
|
@@ -370,7 +380,7 @@ def migrate_pipeline(
|
|
|
370
380
|
typer.echo(red("Could not detect current branch from git"))
|
|
371
381
|
raise typer.Exit(code=1)
|
|
372
382
|
|
|
373
|
-
existing_pipeline = lookup_existing_pipeline(selector, "Migrate")
|
|
383
|
+
existing_pipeline = lookup_existing_pipeline(client, selector, "Migrate")
|
|
374
384
|
if existing_pipeline is None:
|
|
375
385
|
typer.echo(red("❌ Migrate failed: pipeline lookup returned no pipeline"))
|
|
376
386
|
raise typer.Exit(code=1)
|
|
@@ -382,7 +392,7 @@ def migrate_pipeline(
|
|
|
382
392
|
raise typer.Exit(code=1)
|
|
383
393
|
|
|
384
394
|
target_version = _choose_migration_version(existing_pipeline, force)
|
|
385
|
-
downloaded_yaml = _download_pipeline_yaml(selector, target_version)
|
|
395
|
+
downloaded_yaml = _download_pipeline_yaml(client, selector, target_version)
|
|
386
396
|
target_path, selected_yaml = _resolve_target_path_and_yaml(path, downloaded_yaml, force)
|
|
387
397
|
relative_path = ensure_repo_relative_path(target_path, repo_root, GitAction.MIGRATE)
|
|
388
398
|
_write_yaml(target_path, selected_yaml)
|
|
@@ -406,12 +416,10 @@ def migrate_pipeline(
|
|
|
406
416
|
payload["working_branch"] = effective_working_branch
|
|
407
417
|
|
|
408
418
|
response = request_or_exit(
|
|
409
|
-
|
|
419
|
+
client.patch,
|
|
410
420
|
get_api_url("pipelines/storage-settings"),
|
|
411
421
|
params=selector.to_payload(),
|
|
412
422
|
json=payload,
|
|
413
|
-
timeout=30,
|
|
414
|
-
headers=auth_headers(),
|
|
415
423
|
)
|
|
416
424
|
if not (200 <= response.status_code < 300):
|
|
417
425
|
raise fail_with_response("Migrate", response)
|