orchestra-cli 0.3.4__tar.gz → 0.4.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.
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/PKG-INFO +19 -4
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/README.md +18 -3
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/build_pipeline.py +5 -12
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/cli.py +2 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/create_pipeline.py +3 -3
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/delete_pipeline.py +3 -3
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/fetch_pipelines.py +3 -3
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/get_pipeline.py +3 -3
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/import_pipeline.py +3 -3
- orchestra_cli-0.4.0/orchestra_cli/src/login.py +162 -0
- orchestra_cli-0.4.0/orchestra_cli/src/login_page.html +42 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/migrate_pipeline.py +7 -7
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/run_pipeline.py +15 -23
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/task_logs.py +9 -11
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/update_pipeline.py +4 -4
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/validate_pipeline.py +8 -4
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/conftest.py +11 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_create_pipeline.py +12 -0
- orchestra_cli-0.4.0/orchestra_cli/tests/test_login.py +180 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_run_pipeline.py +120 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_validate_pipeline.py +41 -0
- orchestra_cli-0.4.0/orchestra_cli/tests/utils/test_api.py +194 -0
- orchestra_cli-0.4.0/orchestra_cli/utils/api.py +150 -0
- orchestra_cli-0.4.0/orchestra_cli/utils/credentials.py +49 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/pipeline_lookup.py +1 -2
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/yaml_loader.py +27 -1
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/PKG-INFO +19 -4
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/SOURCES.txt +5 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/pyproject.toml +4 -1
- orchestra_cli-0.3.4/orchestra_cli/utils/api.py +0 -65
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/__init__.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/src/pipeline_upsert.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/__init__.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_build_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_cli.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_delete_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_fetch_pipelines.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_get_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_import_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_migrate_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_pipeline_selector.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_task_logs.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/test_update_pipeline.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/utils/__init__.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/tests/utils/test_git.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/constants.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/git.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/pipeline_selector.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/pipeline_update.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli/utils/styling.py +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/dependency_links.txt +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/entry_points.txt +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/requires.txt +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/orchestra_cli.egg-info/top_level.txt +0 -0
- {orchestra_cli-0.3.4 → orchestra_cli-0.4.0}/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.4.0
|
|
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,7 +34,7 @@ pipx install orchestra-cli
|
|
|
34
34
|
|
|
35
35
|
## Environment variables
|
|
36
36
|
|
|
37
|
-
- `ORCHESTRA_API_KEY`:
|
|
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.
|
|
38
38
|
- `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
|
|
39
39
|
|
|
40
40
|
## Command structure
|
|
@@ -43,6 +43,7 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
43
43
|
|
|
44
44
|
| Command | Description |
|
|
45
45
|
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
46
|
+
| `orchestra login` | Log in through your browser so later commands need no API key. |
|
|
46
47
|
| `orchestra pipeline validate <file>` | Validate a pipeline YAML locally against the Orchestra API schema. |
|
|
47
48
|
| `orchestra pipeline import` | Register a pipeline YAML (from a git repo) with Orchestra under an alias. |
|
|
48
49
|
| `orchestra pipeline get` | Fetch one pipeline by path, alias, or pipeline ID. |
|
|
@@ -55,6 +56,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
55
56
|
| `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
|
|
56
57
|
| `orchestra task logs` | Fetch or follow logs for a single task run. |
|
|
57
58
|
|
|
59
|
+
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
|
+
|
|
58
61
|
Use `orchestra --help`, `orchestra <noun> --help`, or `orchestra <noun> <verb> --help` for built-in help.
|
|
59
62
|
|
|
60
63
|
### Legacy command names
|
|
@@ -77,7 +80,7 @@ New code and documentation should prefer the noun/verb form.
|
|
|
77
80
|
|
|
78
81
|
## pipeline validate
|
|
79
82
|
|
|
80
|
-
Validate a
|
|
83
|
+
Validate a pipeline file against the Orchestra API schema.
|
|
81
84
|
|
|
82
85
|
```bash
|
|
83
86
|
orchestra pipeline validate path/to/pipeline.yaml
|
|
@@ -87,7 +90,7 @@ orchestra-cli pipeline validate path/to/pipeline.yaml
|
|
|
87
90
|
|
|
88
91
|
Options
|
|
89
92
|
|
|
90
|
-
- `file` (positional): Path to the
|
|
93
|
+
- `file` (positional): Path to the `.yaml`, `.yml` or `.oml` file to validate.
|
|
91
94
|
|
|
92
95
|
Behavior
|
|
93
96
|
|
|
@@ -110,6 +113,18 @@ pipeline:
|
|
|
110
113
|
|
|
111
114
|
---
|
|
112
115
|
|
|
116
|
+
## login
|
|
117
|
+
|
|
118
|
+
Log in through your browser instead of pasting an API key. The token is cached in `~/.orchestra/credentials.json` (readable only by you) and refreshed automatically, so later commands need no `ORCHESTRA_API_KEY`.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
orchestra login
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
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
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
113
128
|
## pipeline import
|
|
114
129
|
|
|
115
130
|
Create (import) a pipeline in Orchestra by referencing a YAML file inside a git repository. The command infers your repository host/provider, default branch, and YAML path relative to the repo root.
|
|
@@ -20,7 +20,7 @@ pipx install orchestra-cli
|
|
|
20
20
|
|
|
21
21
|
## Environment variables
|
|
22
22
|
|
|
23
|
-
- `ORCHESTRA_API_KEY`:
|
|
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.
|
|
24
24
|
- `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
|
|
25
25
|
|
|
26
26
|
## Command structure
|
|
@@ -29,6 +29,7 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
29
29
|
|
|
30
30
|
| Command | Description |
|
|
31
31
|
| ------------------------------------ | ------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `orchestra login` | Log in through your browser so later commands need no API key. |
|
|
32
33
|
| `orchestra pipeline validate <file>` | Validate a pipeline YAML locally against the Orchestra API schema. |
|
|
33
34
|
| `orchestra pipeline import` | Register a pipeline YAML (from a git repo) with Orchestra under an alias. |
|
|
34
35
|
| `orchestra pipeline get` | Fetch one pipeline by path, alias, or pipeline ID. |
|
|
@@ -41,6 +42,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
|
|
|
41
42
|
| `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
|
|
42
43
|
| `orchestra task logs` | Fetch or follow logs for a single task run. |
|
|
43
44
|
|
|
45
|
+
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
|
+
|
|
44
47
|
Use `orchestra --help`, `orchestra <noun> --help`, or `orchestra <noun> <verb> --help` for built-in help.
|
|
45
48
|
|
|
46
49
|
### Legacy command names
|
|
@@ -63,7 +66,7 @@ New code and documentation should prefer the noun/verb form.
|
|
|
63
66
|
|
|
64
67
|
## pipeline validate
|
|
65
68
|
|
|
66
|
-
Validate a
|
|
69
|
+
Validate a pipeline file against the Orchestra API schema.
|
|
67
70
|
|
|
68
71
|
```bash
|
|
69
72
|
orchestra pipeline validate path/to/pipeline.yaml
|
|
@@ -73,7 +76,7 @@ orchestra-cli pipeline validate path/to/pipeline.yaml
|
|
|
73
76
|
|
|
74
77
|
Options
|
|
75
78
|
|
|
76
|
-
- `file` (positional): Path to the
|
|
79
|
+
- `file` (positional): Path to the `.yaml`, `.yml` or `.oml` file to validate.
|
|
77
80
|
|
|
78
81
|
Behavior
|
|
79
82
|
|
|
@@ -96,6 +99,18 @@ pipeline:
|
|
|
96
99
|
|
|
97
100
|
---
|
|
98
101
|
|
|
102
|
+
## login
|
|
103
|
+
|
|
104
|
+
Log in through your browser instead of pasting an API key. The token is cached in `~/.orchestra/credentials.json` (readable only by you) and refreshed automatically, so later commands need no `ORCHESTRA_API_KEY`.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
orchestra login
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
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
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
99
114
|
## pipeline import
|
|
100
115
|
|
|
101
116
|
Create (import) a pipeline in Orchestra by referencing a YAML file inside a git repository. The command infers your repository host/provider, default branch, and YAML path relative to the repo root.
|
|
@@ -3,7 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
import httpx
|
|
4
4
|
import typer
|
|
5
5
|
|
|
6
|
-
from ..utils.api import auth_headers, fail_with_response, request_or_exit,
|
|
6
|
+
from ..utils.api import auth_headers, fail_with_response, request_or_exit, require_credential
|
|
7
7
|
from ..utils.constants import get_create_pipeline_url, get_update_pipeline_url
|
|
8
8
|
from ..utils.git import confirm_git_warnings_or_exit, prepare_git_backed_run_target
|
|
9
9
|
from ..utils.pipeline_lookup import lookup_existing_pipeline
|
|
@@ -68,7 +68,6 @@ def _create_draft_pipeline(
|
|
|
68
68
|
lookup_selector: PipelineSelector,
|
|
69
69
|
pipeline_data: dict[str, object],
|
|
70
70
|
force: bool,
|
|
71
|
-
api_key: str,
|
|
72
71
|
) -> tuple[PipelineSelector, int]:
|
|
73
72
|
create_selector = _build_create_selector(path, lookup_selector, force)
|
|
74
73
|
payload = build_upsert_payload(pipeline_data, publish=False, selector=create_selector)
|
|
@@ -79,7 +78,7 @@ def _create_draft_pipeline(
|
|
|
79
78
|
get_create_pipeline_url(),
|
|
80
79
|
json=payload,
|
|
81
80
|
timeout=30,
|
|
82
|
-
headers=auth_headers(
|
|
81
|
+
headers=auth_headers(),
|
|
83
82
|
)
|
|
84
83
|
|
|
85
84
|
if create_response.status_code == 201:
|
|
@@ -98,7 +97,6 @@ def _create_draft_pipeline(
|
|
|
98
97
|
def _update_draft_pipeline(
|
|
99
98
|
existing_pipeline: dict[str, object],
|
|
100
99
|
pipeline_data: dict[str, object],
|
|
101
|
-
api_key: str,
|
|
102
100
|
) -> tuple[PipelineSelector, int]:
|
|
103
101
|
update_selector = build_update_selector(existing_pipeline, "Build")
|
|
104
102
|
payload = build_upsert_payload(pipeline_data, publish=False, selector=update_selector)
|
|
@@ -109,7 +107,7 @@ def _update_draft_pipeline(
|
|
|
109
107
|
get_update_pipeline_url(),
|
|
110
108
|
json=payload,
|
|
111
109
|
timeout=30,
|
|
112
|
-
headers=auth_headers(
|
|
110
|
+
headers=auth_headers(),
|
|
113
111
|
)
|
|
114
112
|
|
|
115
113
|
if update_response.status_code == 200:
|
|
@@ -145,7 +143,7 @@ def build_pipeline(
|
|
|
145
143
|
"""
|
|
146
144
|
Validate local YAML, create or update a draft pipeline, and start the draft version.
|
|
147
145
|
"""
|
|
148
|
-
|
|
146
|
+
require_credential()
|
|
149
147
|
if path is None:
|
|
150
148
|
typer.echo(
|
|
151
149
|
red("A pipeline YAML file path is required (use -p or --path with your YAML file)"),
|
|
@@ -155,7 +153,7 @@ def build_pipeline(
|
|
|
155
153
|
|
|
156
154
|
confirm_git_warnings_or_exit(force, path)
|
|
157
155
|
pipeline_data = load_validated_pipeline_data(path)
|
|
158
|
-
existing_pipeline = lookup_existing_pipeline(lookup_selector,
|
|
156
|
+
existing_pipeline = lookup_existing_pipeline(lookup_selector, "Build", allow_404=True)
|
|
159
157
|
|
|
160
158
|
if existing_pipeline is None:
|
|
161
159
|
run_selector, version_number = _create_draft_pipeline(
|
|
@@ -163,11 +161,9 @@ def build_pipeline(
|
|
|
163
161
|
lookup_selector=lookup_selector,
|
|
164
162
|
pipeline_data=pipeline_data,
|
|
165
163
|
force=force,
|
|
166
|
-
api_key=api_key,
|
|
167
164
|
)
|
|
168
165
|
start_pipeline_run(
|
|
169
166
|
selector=run_selector,
|
|
170
|
-
api_key=api_key,
|
|
171
167
|
payload=build_run_payload(
|
|
172
168
|
branch=branch,
|
|
173
169
|
commit=commit,
|
|
@@ -183,12 +179,10 @@ def build_pipeline(
|
|
|
183
179
|
run_selector, version_number = _update_draft_pipeline(
|
|
184
180
|
existing_pipeline=existing_pipeline,
|
|
185
181
|
pipeline_data=pipeline_data,
|
|
186
|
-
api_key=api_key,
|
|
187
182
|
)
|
|
188
183
|
|
|
189
184
|
start_pipeline_run(
|
|
190
185
|
selector=run_selector,
|
|
191
|
-
api_key=api_key,
|
|
192
186
|
payload=build_run_payload(
|
|
193
187
|
branch=branch,
|
|
194
188
|
commit=commit,
|
|
@@ -222,7 +216,6 @@ def build_pipeline(
|
|
|
222
216
|
|
|
223
217
|
start_pipeline_run(
|
|
224
218
|
selector=run_selector,
|
|
225
|
-
api_key=api_key,
|
|
226
219
|
payload=build_run_payload(
|
|
227
220
|
branch=git_branch,
|
|
228
221
|
commit=git_commit,
|
|
@@ -6,6 +6,7 @@ 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 .login import login
|
|
9
10
|
from .migrate_pipeline import migrate_pipeline
|
|
10
11
|
from .run_pipeline import run_pipeline
|
|
11
12
|
from .task_logs import task_logs
|
|
@@ -18,6 +19,7 @@ pipeline_app = typer.Typer(help="Manage Orchestra pipelines (validate, import, r
|
|
|
18
19
|
app.add_typer(pipeline_app, name="pipeline")
|
|
19
20
|
task_app = typer.Typer(help="Inspect Orchestra task runs.")
|
|
20
21
|
app.add_typer(task_app, name="task")
|
|
22
|
+
app.command(name="login")(login)
|
|
21
23
|
|
|
22
24
|
pipeline_app.command(name="validate")(validate)
|
|
23
25
|
pipeline_app.command(name="import")(import_pipeline)
|
|
@@ -7,7 +7,7 @@ from ..utils.api import (
|
|
|
7
7
|
auth_headers,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
|
-
|
|
10
|
+
require_credential,
|
|
11
11
|
)
|
|
12
12
|
from ..utils.constants import get_create_pipeline_url
|
|
13
13
|
from ..utils.pipeline_selector import (
|
|
@@ -36,7 +36,7 @@ def create_pipeline(
|
|
|
36
36
|
"""
|
|
37
37
|
Create an Orchestra-backed pipeline from a local YAML file.
|
|
38
38
|
"""
|
|
39
|
-
|
|
39
|
+
require_credential()
|
|
40
40
|
if path is None:
|
|
41
41
|
typer.echo(red("Provide --path to create a pipeline from YAML"))
|
|
42
42
|
raise typer.Exit(code=1)
|
|
@@ -54,7 +54,7 @@ def create_pipeline(
|
|
|
54
54
|
get_create_pipeline_url(),
|
|
55
55
|
json=payload,
|
|
56
56
|
timeout=30,
|
|
57
|
-
headers=auth_headers(
|
|
57
|
+
headers=auth_headers(),
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
if response.status_code == 201:
|
|
@@ -7,7 +7,7 @@ from ..utils.api import (
|
|
|
7
7
|
auth_headers,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
|
-
|
|
10
|
+
require_credential,
|
|
11
11
|
)
|
|
12
12
|
from ..utils.constants import get_delete_pipeline_url
|
|
13
13
|
from ..utils.pipeline_selector import (
|
|
@@ -27,7 +27,7 @@ def delete_pipeline(
|
|
|
27
27
|
"""
|
|
28
28
|
Delete a pipeline by selector.
|
|
29
29
|
"""
|
|
30
|
-
|
|
30
|
+
require_credential()
|
|
31
31
|
selector = resolve_pipeline_selector(alias, pipeline_id, path)
|
|
32
32
|
|
|
33
33
|
if not typer.confirm(f"Delete pipeline ({selector.display()})?"):
|
|
@@ -39,7 +39,7 @@ def delete_pipeline(
|
|
|
39
39
|
get_delete_pipeline_url(),
|
|
40
40
|
params=selector.to_payload(),
|
|
41
41
|
timeout=30,
|
|
42
|
-
headers=auth_headers(
|
|
42
|
+
headers=auth_headers(),
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
if response.status_code == 204:
|
|
@@ -7,7 +7,7 @@ from ..utils.api import (
|
|
|
7
7
|
auth_headers,
|
|
8
8
|
fail_with_response,
|
|
9
9
|
request_or_exit,
|
|
10
|
-
|
|
10
|
+
require_credential,
|
|
11
11
|
)
|
|
12
12
|
from ..utils.constants import get_api_url
|
|
13
13
|
from ..utils.styling import indent_message, red, yellow
|
|
@@ -19,13 +19,13 @@ def fetch_pipelines():
|
|
|
19
19
|
|
|
20
20
|
The API always includes each pipeline's latest run metadata.
|
|
21
21
|
"""
|
|
22
|
-
|
|
22
|
+
require_credential()
|
|
23
23
|
|
|
24
24
|
response = request_or_exit(
|
|
25
25
|
httpx.get,
|
|
26
26
|
get_api_url("pipelines"),
|
|
27
27
|
timeout=30,
|
|
28
|
-
headers=auth_headers(
|
|
28
|
+
headers=auth_headers(),
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
if response.status_code == 200:
|
|
@@ -10,7 +10,7 @@ from ..utils.api import (
|
|
|
10
10
|
auth_headers,
|
|
11
11
|
fail_with_response,
|
|
12
12
|
request_or_exit,
|
|
13
|
-
|
|
13
|
+
require_credential,
|
|
14
14
|
)
|
|
15
15
|
from ..utils.constants import get_pipeline_url
|
|
16
16
|
from ..utils.pipeline_selector import (
|
|
@@ -50,7 +50,7 @@ def get_pipeline(
|
|
|
50
50
|
"""
|
|
51
51
|
Fetch one pipeline using the shared selector model.
|
|
52
52
|
"""
|
|
53
|
-
|
|
53
|
+
require_credential()
|
|
54
54
|
selector = resolve_pipeline_selector(alias, pipeline_id, path)
|
|
55
55
|
|
|
56
56
|
response = request_or_exit(
|
|
@@ -58,7 +58,7 @@ def get_pipeline(
|
|
|
58
58
|
get_pipeline_url(),
|
|
59
59
|
params=selector.to_payload(),
|
|
60
60
|
timeout=30,
|
|
61
|
-
headers=auth_headers(
|
|
61
|
+
headers=auth_headers(),
|
|
62
62
|
)
|
|
63
63
|
|
|
64
64
|
if response.status_code == 200:
|
|
@@ -8,7 +8,7 @@ from ..utils.api import (
|
|
|
8
8
|
auth_headers,
|
|
9
9
|
fail_with_response,
|
|
10
10
|
request_or_exit,
|
|
11
|
-
|
|
11
|
+
require_credential,
|
|
12
12
|
)
|
|
13
13
|
from ..utils.constants import get_api_url
|
|
14
14
|
from ..utils.git import (
|
|
@@ -43,7 +43,7 @@ def import_pipeline(
|
|
|
43
43
|
"""
|
|
44
44
|
Create a pipeline in Orchestra by referencing a YAML file in your git repository.
|
|
45
45
|
"""
|
|
46
|
-
|
|
46
|
+
require_credential()
|
|
47
47
|
if path is None:
|
|
48
48
|
typer.echo(red("Provide --path to import a pipeline from git"))
|
|
49
49
|
raise typer.Exit(code=1)
|
|
@@ -102,7 +102,7 @@ def import_pipeline(
|
|
|
102
102
|
get_api_url("pipelines/import"),
|
|
103
103
|
json=payload,
|
|
104
104
|
timeout=30,
|
|
105
|
-
headers=auth_headers(
|
|
105
|
+
headers=auth_headers(),
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
if response.status_code == 201:
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import secrets
|
|
4
|
+
import time
|
|
5
|
+
import webbrowser
|
|
6
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
7
|
+
from importlib.resources import files
|
|
8
|
+
from urllib.parse import parse_qs, urlencode, urlparse
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from ..utils.api import fail_with_response, request_or_exit, token_response_to_credentials
|
|
14
|
+
from ..utils.constants import get_base_url
|
|
15
|
+
from ..utils.credentials import save_credentials
|
|
16
|
+
from ..utils.styling import green, red, yellow
|
|
17
|
+
|
|
18
|
+
SCOPES = "orchestra:read orchestra:write offline_access"
|
|
19
|
+
CALLBACK_PATH = "/callback"
|
|
20
|
+
LOGIN_TIMEOUT_SECONDS = 300
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _CallbackHandler(BaseHTTPRequestHandler):
|
|
24
|
+
params: dict[str, str] | None = None
|
|
25
|
+
# Bounds reads on connections a browser opens speculatively and never uses, which
|
|
26
|
+
# would otherwise block the single-threaded server past the login deadline.
|
|
27
|
+
timeout = 5
|
|
28
|
+
|
|
29
|
+
def do_GET(self): # noqa: N802
|
|
30
|
+
url = urlparse(self.path)
|
|
31
|
+
if url.path != CALLBACK_PATH:
|
|
32
|
+
self.send_response(404)
|
|
33
|
+
self.end_headers()
|
|
34
|
+
return
|
|
35
|
+
_CallbackHandler.params = {k: v[0] for k, v in parse_qs(url.query).items()}
|
|
36
|
+
self.send_response(200)
|
|
37
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
38
|
+
self.end_headers()
|
|
39
|
+
self.wfile.write(files(__package__).joinpath("login_page.html").read_bytes())
|
|
40
|
+
|
|
41
|
+
def log_message(self, format, *args): # noqa: ARG002
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _pkce_pair() -> tuple[str, str]:
|
|
46
|
+
verifier = secrets.token_urlsafe(64)
|
|
47
|
+
digest = hashlib.sha256(verifier.encode()).digest()
|
|
48
|
+
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
|
|
49
|
+
return verifier, challenge
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _wait_for_callback(server: HTTPServer, state: str) -> dict[str, str]:
|
|
53
|
+
"""Wait for the redirect carrying this login's ``state``.
|
|
54
|
+
|
|
55
|
+
Any other request to the callback (a prefetch, a stale tab, another local
|
|
56
|
+
process) is ignored rather than ending the login.
|
|
57
|
+
"""
|
|
58
|
+
deadline = time.monotonic() + LOGIN_TIMEOUT_SECONDS
|
|
59
|
+
server.timeout = 1
|
|
60
|
+
while True:
|
|
61
|
+
_CallbackHandler.params = None
|
|
62
|
+
if time.monotonic() > deadline:
|
|
63
|
+
typer.echo(red("Timed out waiting for the browser to complete login"))
|
|
64
|
+
raise typer.Exit(code=1)
|
|
65
|
+
server.handle_request()
|
|
66
|
+
params = _CallbackHandler.params
|
|
67
|
+
if params is not None and params.get("state") == state:
|
|
68
|
+
return params
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def login():
|
|
72
|
+
"""
|
|
73
|
+
Log in to Orchestra in your browser and cache the token for later commands.
|
|
74
|
+
"""
|
|
75
|
+
base_url = get_base_url()
|
|
76
|
+
response = request_or_exit(
|
|
77
|
+
httpx.get,
|
|
78
|
+
f"{base_url}/.well-known/oauth-authorization-server",
|
|
79
|
+
timeout=30,
|
|
80
|
+
)
|
|
81
|
+
if response.status_code != 200:
|
|
82
|
+
raise fail_with_response("Discovery", response)
|
|
83
|
+
# The web app answers unknown paths with a 200 HTML page, so a host without an
|
|
84
|
+
# authorization server behind it still reaches this point.
|
|
85
|
+
try:
|
|
86
|
+
metadata = response.json()
|
|
87
|
+
except ValueError:
|
|
88
|
+
metadata = None
|
|
89
|
+
required = ("authorization_endpoint", "token_endpoint", "registration_endpoint")
|
|
90
|
+
if not isinstance(metadata, dict) or not all(k in metadata for k in required):
|
|
91
|
+
typer.echo(red(f"{base_url} does not support `orchestra login`"))
|
|
92
|
+
raise typer.Exit(code=1)
|
|
93
|
+
|
|
94
|
+
with HTTPServer(("127.0.0.1", 0), _CallbackHandler) as server:
|
|
95
|
+
redirect_uri = f"http://127.0.0.1:{server.server_port}{CALLBACK_PATH}"
|
|
96
|
+
|
|
97
|
+
response = request_or_exit(
|
|
98
|
+
httpx.post,
|
|
99
|
+
metadata["registration_endpoint"],
|
|
100
|
+
json={
|
|
101
|
+
"client_name": "Orchestra CLI",
|
|
102
|
+
"redirect_uris": [redirect_uri],
|
|
103
|
+
"grant_types": ["authorization_code", "refresh_token"],
|
|
104
|
+
"response_types": ["code"],
|
|
105
|
+
"token_endpoint_auth_method": "none",
|
|
106
|
+
},
|
|
107
|
+
timeout=30,
|
|
108
|
+
)
|
|
109
|
+
if response.status_code not in (200, 201):
|
|
110
|
+
raise fail_with_response("Client registration", response)
|
|
111
|
+
client_id = response.json()["client_id"]
|
|
112
|
+
|
|
113
|
+
verifier, challenge = _pkce_pair()
|
|
114
|
+
state = secrets.token_urlsafe(32)
|
|
115
|
+
authorize_url = f"{metadata['authorization_endpoint']}?" + urlencode(
|
|
116
|
+
{
|
|
117
|
+
"response_type": "code",
|
|
118
|
+
"client_id": client_id,
|
|
119
|
+
"redirect_uri": redirect_uri,
|
|
120
|
+
"scope": SCOPES,
|
|
121
|
+
"state": state,
|
|
122
|
+
"code_challenge": challenge,
|
|
123
|
+
"code_challenge_method": "S256",
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
typer.echo(yellow("Opening your browser to log in. If it does not open, visit:"))
|
|
128
|
+
typer.echo(authorize_url)
|
|
129
|
+
webbrowser.open(authorize_url)
|
|
130
|
+
params = _wait_for_callback(server, state)
|
|
131
|
+
|
|
132
|
+
if "error" in params:
|
|
133
|
+
detail = params.get("error_description", params["error"])
|
|
134
|
+
typer.echo(red(f"Login failed: {detail}"))
|
|
135
|
+
raise typer.Exit(code=1)
|
|
136
|
+
|
|
137
|
+
response = request_or_exit(
|
|
138
|
+
httpx.post,
|
|
139
|
+
metadata["token_endpoint"],
|
|
140
|
+
data={
|
|
141
|
+
"grant_type": "authorization_code",
|
|
142
|
+
"code": params.get("code", ""),
|
|
143
|
+
"redirect_uri": redirect_uri,
|
|
144
|
+
"client_id": client_id,
|
|
145
|
+
"code_verifier": verifier,
|
|
146
|
+
},
|
|
147
|
+
timeout=30,
|
|
148
|
+
)
|
|
149
|
+
if response.status_code != 200:
|
|
150
|
+
raise fail_with_response("Token exchange", response)
|
|
151
|
+
token = response.json()
|
|
152
|
+
if "refresh_token" not in token:
|
|
153
|
+
typer.echo(red("Login failed: no refresh token was granted, so the login cannot be kept"))
|
|
154
|
+
raise typer.Exit(code=1)
|
|
155
|
+
|
|
156
|
+
save_credentials(
|
|
157
|
+
token_response_to_credentials(
|
|
158
|
+
token,
|
|
159
|
+
{"client_id": client_id, "token_endpoint": metadata["token_endpoint"]},
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
typer.echo(green(f"✅ Logged in to {base_url}"))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Orchestra CLI login</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body { height: 100%; margin: 0; }
|
|
9
|
+
body {
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
padding: 0 16px;
|
|
14
|
+
background: linear-gradient(to bottom, #3754BF, #253B8E 45%, #0D1431);
|
|
15
|
+
color: #FFFFFF;
|
|
16
|
+
font-family: "DM Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
.logo { height: 34px; width: auto; margin-bottom: 40px; }
|
|
20
|
+
h1 { margin: 0 0 12px; font-size: 28px; font-weight: 500; }
|
|
21
|
+
p { margin: 0; font-size: 16px; color: #BAC7F4; }
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<main>
|
|
26
|
+
<svg class="logo" role="img" aria-label="Orchestra" width="210" height="34" viewBox="0 0 210 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
27
|
+
<path d="M46.4619 16.8629C46.4619 8.8581 51.4514 3.26221 59.249 3.26221C67.0467 3.26221 72.073 8.8581 72.073 16.8629C72.073 24.8677 67.0467 30.4636 59.249 30.4636C51.4514 30.4636 46.4619 24.8677 46.4619 16.8629ZM66.8626 16.8629C66.8626 11.4525 64.0903 7.59826 59.249 7.59826C54.4077 7.59826 51.6732 11.4525 51.6732 16.8629C51.6732 22.2733 54.4445 26.1276 59.249 26.1276C64.0535 26.1276 66.8626 22.2733 66.8626 16.8629Z" fill="white"/>
|
|
28
|
+
<path d="M86.1213 11.1182V15.3426H84.3108C81.1695 15.3426 79.4695 16.5655 79.4327 20.4197V30.0185H74.7764V11.1182H79.3223V14.6392C80.0245 12.749 81.7981 11.1182 84.829 11.1182H86.1222H86.1213Z" fill="white"/>
|
|
29
|
+
<path d="M96.4644 30.4638C90.7727 30.4638 86.8923 26.2764 86.8923 20.5688C86.8923 14.8613 90.7359 10.7485 96.4276 10.7485C101.047 10.7485 104.337 13.2682 105.186 17.6043H100.493C100.013 15.4547 98.6826 14.1579 96.5013 14.1579C93.5081 14.1579 91.8449 16.7893 91.8449 20.5688C91.8449 24.3483 93.5081 27.0544 96.5013 27.0544C98.8667 27.0544 100.013 25.646 100.493 23.5712H105.186C104.337 28.018 101.047 30.4638 96.4644 30.4638Z" fill="white"/>
|
|
30
|
+
<path d="M112.572 3.82422V14.1938C113.57 12.3775 115.418 10.7475 118.633 10.7475C122.698 10.7475 124.842 13.2303 124.842 17.0845V30.0179H120.149V18.1219C120.149 15.8247 119.224 14.4163 116.712 14.4163C114.199 14.4163 112.573 16.3065 112.573 19.271V30.0179H107.916V3.82422H112.573H112.572Z" fill="white"/>
|
|
31
|
+
<path d="M137.074 10.7485C142.988 10.7485 146.646 15.3809 146.425 21.6071H132.308C132.493 25.0535 134.266 27.1292 137.186 27.1292C139.514 27.1292 140.807 25.7577 141.398 24.0161H146.055C145.02 27.6848 141.952 30.4647 137.148 30.4647C131.383 30.4647 127.502 26.2773 127.502 20.5697C127.502 14.8622 131.309 10.7494 137.074 10.7494V10.7485ZM137.074 14.0463C134.487 14.0463 132.862 15.8626 132.455 18.6786H141.694C141.25 15.8987 139.662 14.0463 137.074 14.0463Z" fill="white"/>
|
|
32
|
+
<path d="M147.76 23.9041H152.528C152.787 26.2761 154.523 27.2766 156.852 27.2766C159.181 27.2766 160.178 26.0906 160.178 24.6822C160.178 22.9775 158.922 22.6074 155.817 21.8653C151.605 20.8648 148.389 19.8274 148.389 16.1956C148.389 12.9348 151.567 10.7104 156.408 10.7104C161.25 10.7104 164.206 13.1194 164.686 16.418H159.993C159.772 14.7872 158.478 13.8236 156.372 13.8236C154.265 13.8236 153.12 14.7502 153.12 15.8615C153.12 17.3807 154.672 17.7148 157.444 18.3812C161.657 19.3817 165.021 20.3821 165.021 24.385C165.021 28.3879 161.731 30.4627 156.853 30.4627C151.494 30.4627 148.168 28.0538 147.761 23.9032L147.76 23.9041Z" fill="white"/>
|
|
33
|
+
<path d="M173.555 14.3419V24.5702C173.555 26.201 173.962 26.6459 175.477 26.6459H177.694V30.0184H173.962C170.118 30.0184 168.898 28.3875 168.898 24.9043V14.3419H166.089V11.118H168.898V5.80762H173.555V11.118H177.694V14.3419H173.555Z" fill="white"/>
|
|
34
|
+
<path d="M191.738 11.1182V15.3426H189.928C186.786 15.3426 185.086 16.5655 185.049 20.4197V30.0185H180.393V11.1182H184.939V14.6392C185.641 12.749 187.415 11.1182 190.446 11.1182H191.739H191.738Z" fill="white"/>
|
|
35
|
+
<path d="M192.419 24.6824C192.419 20.902 195.264 19.7169 199.699 18.5678C202.655 17.7898 204.614 17.4557 204.614 16.0112C204.614 14.7883 203.394 13.9355 201.288 13.9355C199.181 13.9355 197.703 14.8991 197.555 16.6776H192.936C193.38 13.3421 196.262 10.7485 201.436 10.7485C206.609 10.7485 209.418 12.8612 209.418 16.8262V30.019H204.984V26.7951C204.282 28.7591 202.471 30.4638 198.923 30.4638C195.375 30.4638 192.419 28.3512 192.419 24.6824ZM204.8 22.1249V18.8263C204.393 19.8268 203.247 20.3455 201.326 20.9389C198.814 21.6801 197.224 22.4581 197.224 24.4222C197.224 26.1638 198.592 27.2012 200.476 27.2012C203.21 27.2012 204.8 24.904 204.8 22.124V22.1249Z" fill="white"/>
|
|
36
|
+
<path d="M25.7719 10.3911L30.0232 6.143C32.1819 8.74145 33.5794 11.9935 33.8765 15.5586H27.8726C27.6236 13.6369 26.8803 11.8705 25.7719 10.3911ZM10.2383 8.23585C12.0953 6.81357 14.4182 5.96822 16.9383 5.96822C19.4583 5.96822 21.7812 6.81357 23.6383 8.23585L27.8851 3.99219C24.9277 1.50164 21.1084 0 16.9383 0C12.7682 0 8.94879 1.50164 5.99144 3.99219L10.2383 8.23585ZM6.00393 15.5586C6.25291 13.6369 6.99626 11.8705 8.1046 10.3911L3.8533 6.143C1.69463 8.74234 0.297163 11.9944 0 15.5586H6.00393ZM27.8476 18.5904C27.0695 23.9148 22.4826 28.0033 16.9383 28.0033C11.3939 28.0033 6.80707 23.9148 6.02892 18.5904H0.0160628C0.825451 27.2186 8.0921 33.9706 16.9383 33.9706C25.7844 33.9706 33.0511 27.2177 33.8605 18.5904H27.8476ZM16.9383 10.5641C13.3893 10.5641 10.5113 13.439 10.5113 16.9862C10.5113 20.5334 13.3884 23.4083 16.9383 23.4083C20.4882 23.4083 23.3652 20.5334 23.3652 16.9862C23.3652 13.439 20.4882 10.5641 16.9383 10.5641Z" fill="white"/>
|
|
37
|
+
</svg>
|
|
38
|
+
<h1>Return to your terminal</h1>
|
|
39
|
+
<p>to finish logging in to Orchestra. You can close this tab.</p>
|
|
40
|
+
</main>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
@@ -5,7 +5,7 @@ from typing import Any
|
|
|
5
5
|
import httpx
|
|
6
6
|
import typer
|
|
7
7
|
|
|
8
|
-
from ..utils.api import auth_headers, fail_with_response, request_or_exit,
|
|
8
|
+
from ..utils.api import auth_headers, fail_with_response, request_or_exit, require_credential
|
|
9
9
|
from ..utils.constants import get_api_url
|
|
10
10
|
from ..utils.git import (
|
|
11
11
|
GitAction,
|
|
@@ -121,7 +121,7 @@ def _choose_migration_version(existing_pipeline: dict[str, object], force: bool)
|
|
|
121
121
|
raise typer.Exit(code=1)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def _download_pipeline_yaml(selector: PipelineSelector, version: int | None
|
|
124
|
+
def _download_pipeline_yaml(selector: PipelineSelector, version: int | None) -> str:
|
|
125
125
|
params = selector.to_payload()
|
|
126
126
|
if version is not None:
|
|
127
127
|
params["version"] = str(version)
|
|
@@ -131,7 +131,7 @@ def _download_pipeline_yaml(selector: PipelineSelector, version: int | None, api
|
|
|
131
131
|
get_api_url("pipeline/data"),
|
|
132
132
|
params=params,
|
|
133
133
|
timeout=30,
|
|
134
|
-
headers=auth_headers(
|
|
134
|
+
headers=auth_headers(),
|
|
135
135
|
)
|
|
136
136
|
if response.status_code != 200:
|
|
137
137
|
raise fail_with_response("Migrate", response)
|
|
@@ -344,7 +344,7 @@ def migrate_pipeline(
|
|
|
344
344
|
"""
|
|
345
345
|
Migrate an Orchestra-backed pipeline to git-backed storage.
|
|
346
346
|
"""
|
|
347
|
-
|
|
347
|
+
require_credential()
|
|
348
348
|
selector = _resolve_migrate_selector(alias, pipeline_id)
|
|
349
349
|
|
|
350
350
|
repo_root = require_repo_root(path, GitAction.MIGRATE)
|
|
@@ -370,7 +370,7 @@ def migrate_pipeline(
|
|
|
370
370
|
typer.echo(red("Could not detect current branch from git"))
|
|
371
371
|
raise typer.Exit(code=1)
|
|
372
372
|
|
|
373
|
-
existing_pipeline = lookup_existing_pipeline(selector,
|
|
373
|
+
existing_pipeline = lookup_existing_pipeline(selector, "Migrate")
|
|
374
374
|
if existing_pipeline is None:
|
|
375
375
|
typer.echo(red("❌ Migrate failed: pipeline lookup returned no pipeline"))
|
|
376
376
|
raise typer.Exit(code=1)
|
|
@@ -382,7 +382,7 @@ def migrate_pipeline(
|
|
|
382
382
|
raise typer.Exit(code=1)
|
|
383
383
|
|
|
384
384
|
target_version = _choose_migration_version(existing_pipeline, force)
|
|
385
|
-
downloaded_yaml = _download_pipeline_yaml(selector, target_version
|
|
385
|
+
downloaded_yaml = _download_pipeline_yaml(selector, target_version)
|
|
386
386
|
target_path, selected_yaml = _resolve_target_path_and_yaml(path, downloaded_yaml, force)
|
|
387
387
|
relative_path = ensure_repo_relative_path(target_path, repo_root, GitAction.MIGRATE)
|
|
388
388
|
_write_yaml(target_path, selected_yaml)
|
|
@@ -411,7 +411,7 @@ def migrate_pipeline(
|
|
|
411
411
|
params=selector.to_payload(),
|
|
412
412
|
json=payload,
|
|
413
413
|
timeout=30,
|
|
414
|
-
headers=auth_headers(
|
|
414
|
+
headers=auth_headers(),
|
|
415
415
|
)
|
|
416
416
|
if not (200 <= response.status_code < 300):
|
|
417
417
|
raise fail_with_response("Migrate", response)
|