orchestra-cli 0.0.5__tar.gz → 0.1.2__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.1.2/PKG-INFO +196 -0
- orchestra_cli-0.1.2/README.md +185 -0
- orchestra_cli-0.1.2/orchestra_cli/src/cli.py +12 -0
- orchestra_cli-0.1.2/orchestra_cli/src/import_pipeline.py +223 -0
- orchestra_cli-0.1.2/orchestra_cli/src/run_pipeline.py +182 -0
- orchestra_cli-0.0.5/orchestra_cli/src/cli.py → orchestra_cli-0.1.2/orchestra_cli/src/validate_pipeline.py +3 -44
- orchestra_cli-0.1.2/orchestra_cli/tests/__init__.py +0 -0
- orchestra_cli-0.1.2/orchestra_cli/tests/conftest.py +14 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli/tests/test_cli.py +0 -6
- orchestra_cli-0.1.2/orchestra_cli/tests/test_import_pipeline.py +264 -0
- orchestra_cli-0.1.2/orchestra_cli/tests/test_run_pipeline.py +260 -0
- orchestra_cli-0.1.2/orchestra_cli/tests/test_validate_pipeline.py +11 -0
- orchestra_cli-0.1.2/orchestra_cli/utils/constants.py +7 -0
- orchestra_cli-0.1.2/orchestra_cli/utils/git.py +50 -0
- orchestra_cli-0.1.2/orchestra_cli/utils/styling.py +22 -0
- orchestra_cli-0.1.2/orchestra_cli.egg-info/PKG-INFO +196 -0
- orchestra_cli-0.1.2/orchestra_cli.egg-info/SOURCES.txt +22 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/pyproject.toml +2 -2
- orchestra_cli-0.0.5/PKG-INFO +0 -34
- orchestra_cli-0.0.5/README.md +0 -23
- orchestra_cli-0.0.5/orchestra_cli.egg-info/PKG-INFO +0 -34
- orchestra_cli-0.0.5/orchestra_cli.egg-info/SOURCES.txt +0 -11
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli/src/__init__.py +0 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/dependency_links.txt +0 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/entry_points.txt +0 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/requires.txt +0 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/top_level.txt +0 -0
- {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: orchestra-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Orchestra CLI. Perform operations with Orchestra locally.
|
|
5
|
+
Author-email: Orchestra Technologies <support@getorchestra.io>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx>=0.28.1
|
|
9
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
10
|
+
Requires-Dist: typer>=0.16.0
|
|
11
|
+
|
|
12
|
+
# orchestra-cli
|
|
13
|
+
|
|
14
|
+
Orchestra CLI for working with Orchestra pipelines from your terminal.
|
|
15
|
+
|
|
16
|
+
Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install orchestra-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or with pipx:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pipx install orchestra-cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environment variables
|
|
31
|
+
|
|
32
|
+
- `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `run`).
|
|
33
|
+
- `BASE_URL`: Optional. Override the default API base
|
|
34
|
+
(`https://app.getorchestra.io/api/engine/public/pipelines/{}`) for non‑production/testing.
|
|
35
|
+
|
|
36
|
+
## Commands overview
|
|
37
|
+
|
|
38
|
+
- `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
|
|
39
|
+
- `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
|
|
40
|
+
- `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
|
|
41
|
+
|
|
42
|
+
Use `orchestra --help` or `orchestra <command> --help` for built-in help.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## validate
|
|
47
|
+
|
|
48
|
+
Validate a YAML file against the Orchestra API schema.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
orchestra validate path/to/pipeline.yaml
|
|
52
|
+
# or
|
|
53
|
+
orchestra-cli validate path/to/pipeline.yaml
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Options
|
|
57
|
+
|
|
58
|
+
- `file` (positional): Path to the YAML file to validate.
|
|
59
|
+
|
|
60
|
+
Behavior
|
|
61
|
+
|
|
62
|
+
- Prints a success message on valid input.
|
|
63
|
+
- On validation errors, prints the failing location(s), readable messages, and a YAML snippet when possible.
|
|
64
|
+
- Exit codes: `0` on success, `1` on invalid file/validation failure/HTTP error.
|
|
65
|
+
|
|
66
|
+
Example output (failure)
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
❌ Validation failed with status 422
|
|
70
|
+
Error at: pipeline.tasks[0].type
|
|
71
|
+
Invalid task type "foo"
|
|
72
|
+
|
|
73
|
+
YAML snippet:
|
|
74
|
+
pipeline:
|
|
75
|
+
tasks:
|
|
76
|
+
- type: foo
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## import
|
|
82
|
+
|
|
83
|
+
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.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export ORCHESTRA_API_KEY=... # required
|
|
87
|
+
|
|
88
|
+
orchestra import \
|
|
89
|
+
--alias my-pipeline \
|
|
90
|
+
--path ./pipelines/pipeline.yaml \
|
|
91
|
+
--working-branch my-feature-branch # optional; defaults to current local branch
|
|
92
|
+
# or
|
|
93
|
+
orchestra-cli import -a my-pipeline -p ./pipelines/pipeline.yaml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Options
|
|
97
|
+
|
|
98
|
+
- `-a, --alias` (required): The alias you want to register the pipeline under.
|
|
99
|
+
- `-p, --path` (required): Path to the YAML file. Must be inside a git repository.
|
|
100
|
+
- `-w, --working-branch` (optional): Branch to associate as the working branch for this pipeline import. If omitted, the current local git branch is used.
|
|
101
|
+
|
|
102
|
+
Notes
|
|
103
|
+
|
|
104
|
+
- The YAML is validated with the API before import; failures are printed clearly.
|
|
105
|
+
- Git details are detected automatically:
|
|
106
|
+
- Supported providers: GitHub, GitLab, Azure DevOps.
|
|
107
|
+
- The default branch is detected from `origin`.
|
|
108
|
+
- The working branch defaults to your current local branch when not specified.
|
|
109
|
+
- The YAML path is computed relative to the repository root.
|
|
110
|
+
- On success, the command prints the created pipeline ID (or a success message).
|
|
111
|
+
- Exit codes: `0` on success, `1` on failure.
|
|
112
|
+
|
|
113
|
+
Common errors
|
|
114
|
+
|
|
115
|
+
- Missing `ORCHESTRA_API_KEY`.
|
|
116
|
+
- Not running inside a git repository, or no `origin` remote configured.
|
|
117
|
+
- Could not detect storage provider or default branch.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## run
|
|
122
|
+
|
|
123
|
+
Start a pipeline run by alias. Optionally specify a branch and/or commit. By default, the command waits and polls the run status until completion.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
export ORCHESTRA_API_KEY=...
|
|
127
|
+
|
|
128
|
+
# Start and wait for completion
|
|
129
|
+
orchestra run --alias my-pipeline
|
|
130
|
+
|
|
131
|
+
# Start without waiting (prints run id and exits)
|
|
132
|
+
orchestra run -a my-pipeline --no-wait
|
|
133
|
+
|
|
134
|
+
# Start for a specific branch/commit
|
|
135
|
+
orchestra run -a my-pipeline -b feature/my-change -c 0123abc
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Options
|
|
139
|
+
|
|
140
|
+
- `-a, --alias` (required): Pipeline alias to run.
|
|
141
|
+
- `-b, --branch` (optional): Git branch name to associate with this run.
|
|
142
|
+
- `-c, --commit` (optional): Commit SHA to associate with this run.
|
|
143
|
+
- `--wait/--no-wait` (default: `--wait`): Poll until the run ends.
|
|
144
|
+
- `--force/--no-force` (default: `--no-force`): Skip confirmation if local git warnings are detected.
|
|
145
|
+
|
|
146
|
+
Behavior
|
|
147
|
+
|
|
148
|
+
- Prints the run ID when known and a link to the run lineage page.
|
|
149
|
+
- When waiting, polls status every ~5s until a terminal state:
|
|
150
|
+
- `SUCCEEDED` (exit `0`), `WARNING` (exit `0`), `SKIPPED` (exit `0`)
|
|
151
|
+
- `FAILED` or `CANCELLED` (exit `1`)
|
|
152
|
+
- When not waiting, exits after start and prints the run ID.
|
|
153
|
+
|
|
154
|
+
Non-interactive usage
|
|
155
|
+
|
|
156
|
+
- If your repo has warnings (e.g., uncommitted changes), the CLI prompts for confirmation unless `--force` is provided. For CI or scripts, pass `--force` or ensure a clean repo.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Examples
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# Validate a pipeline file
|
|
164
|
+
orchestra validate ./examples/etl.yaml
|
|
165
|
+
|
|
166
|
+
# Import a pipeline and capture the created ID
|
|
167
|
+
PIPELINE_ID=$(orchestra import -a finance-etl -p ./pipelines/etl.yaml)
|
|
168
|
+
|
|
169
|
+
# Start a run and wait for completion
|
|
170
|
+
orchestra run -a finance-etl
|
|
171
|
+
|
|
172
|
+
# Start a run and exit immediately
|
|
173
|
+
orchestra run -a finance-etl --no-wait
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
- Make sure [uv](https://github.com/astral-sh/uv) is installed
|
|
181
|
+
- Use `uv pip install -e ".[dev]"` to install the CLI in editable mode for development
|
|
182
|
+
- For local development, run `uv run orchestra` to start the CLI
|
|
183
|
+
- you can use `uv run --env-file .env ...` to run the CLI with env vars
|
|
184
|
+
- For testing, run `uv run pytest`
|
|
185
|
+
- For linting, run `uv run ruff check .`
|
|
186
|
+
- For formatting, run `uv run black --check .`
|
|
187
|
+
- For type checking, run `uv run pyright`
|
|
188
|
+
|
|
189
|
+
## Building and Releasing
|
|
190
|
+
|
|
191
|
+
- Bump the version in `pyproject.toml` or by running `uv version --bump <major/minor/patch>`
|
|
192
|
+
- Run `uv sync` to install the dependencies
|
|
193
|
+
- Run `uv build` to build the CLI
|
|
194
|
+
- Run `uv publish` to publish the CLI (you will need to pass the `--token` flag)
|
|
195
|
+
|
|
196
|
+
**Note: Failure to bump the version will result in a failed release.**
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# orchestra-cli
|
|
2
|
+
|
|
3
|
+
Orchestra CLI for working with Orchestra pipelines from your terminal.
|
|
4
|
+
|
|
5
|
+
Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install orchestra-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or with pipx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx install orchestra-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Environment variables
|
|
20
|
+
|
|
21
|
+
- `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `run`).
|
|
22
|
+
- `BASE_URL`: Optional. Override the default API base
|
|
23
|
+
(`https://app.getorchestra.io/api/engine/public/pipelines/{}`) for non‑production/testing.
|
|
24
|
+
|
|
25
|
+
## Commands overview
|
|
26
|
+
|
|
27
|
+
- `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
|
|
28
|
+
- `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
|
|
29
|
+
- `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
|
|
30
|
+
|
|
31
|
+
Use `orchestra --help` or `orchestra <command> --help` for built-in help.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## validate
|
|
36
|
+
|
|
37
|
+
Validate a YAML file against the Orchestra API schema.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
orchestra validate path/to/pipeline.yaml
|
|
41
|
+
# or
|
|
42
|
+
orchestra-cli validate path/to/pipeline.yaml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Options
|
|
46
|
+
|
|
47
|
+
- `file` (positional): Path to the YAML file to validate.
|
|
48
|
+
|
|
49
|
+
Behavior
|
|
50
|
+
|
|
51
|
+
- Prints a success message on valid input.
|
|
52
|
+
- On validation errors, prints the failing location(s), readable messages, and a YAML snippet when possible.
|
|
53
|
+
- Exit codes: `0` on success, `1` on invalid file/validation failure/HTTP error.
|
|
54
|
+
|
|
55
|
+
Example output (failure)
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
❌ Validation failed with status 422
|
|
59
|
+
Error at: pipeline.tasks[0].type
|
|
60
|
+
Invalid task type "foo"
|
|
61
|
+
|
|
62
|
+
YAML snippet:
|
|
63
|
+
pipeline:
|
|
64
|
+
tasks:
|
|
65
|
+
- type: foo
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## import
|
|
71
|
+
|
|
72
|
+
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.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
export ORCHESTRA_API_KEY=... # required
|
|
76
|
+
|
|
77
|
+
orchestra import \
|
|
78
|
+
--alias my-pipeline \
|
|
79
|
+
--path ./pipelines/pipeline.yaml \
|
|
80
|
+
--working-branch my-feature-branch # optional; defaults to current local branch
|
|
81
|
+
# or
|
|
82
|
+
orchestra-cli import -a my-pipeline -p ./pipelines/pipeline.yaml
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Options
|
|
86
|
+
|
|
87
|
+
- `-a, --alias` (required): The alias you want to register the pipeline under.
|
|
88
|
+
- `-p, --path` (required): Path to the YAML file. Must be inside a git repository.
|
|
89
|
+
- `-w, --working-branch` (optional): Branch to associate as the working branch for this pipeline import. If omitted, the current local git branch is used.
|
|
90
|
+
|
|
91
|
+
Notes
|
|
92
|
+
|
|
93
|
+
- The YAML is validated with the API before import; failures are printed clearly.
|
|
94
|
+
- Git details are detected automatically:
|
|
95
|
+
- Supported providers: GitHub, GitLab, Azure DevOps.
|
|
96
|
+
- The default branch is detected from `origin`.
|
|
97
|
+
- The working branch defaults to your current local branch when not specified.
|
|
98
|
+
- The YAML path is computed relative to the repository root.
|
|
99
|
+
- On success, the command prints the created pipeline ID (or a success message).
|
|
100
|
+
- Exit codes: `0` on success, `1` on failure.
|
|
101
|
+
|
|
102
|
+
Common errors
|
|
103
|
+
|
|
104
|
+
- Missing `ORCHESTRA_API_KEY`.
|
|
105
|
+
- Not running inside a git repository, or no `origin` remote configured.
|
|
106
|
+
- Could not detect storage provider or default branch.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## run
|
|
111
|
+
|
|
112
|
+
Start a pipeline run by alias. Optionally specify a branch and/or commit. By default, the command waits and polls the run status until completion.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
export ORCHESTRA_API_KEY=...
|
|
116
|
+
|
|
117
|
+
# Start and wait for completion
|
|
118
|
+
orchestra run --alias my-pipeline
|
|
119
|
+
|
|
120
|
+
# Start without waiting (prints run id and exits)
|
|
121
|
+
orchestra run -a my-pipeline --no-wait
|
|
122
|
+
|
|
123
|
+
# Start for a specific branch/commit
|
|
124
|
+
orchestra run -a my-pipeline -b feature/my-change -c 0123abc
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Options
|
|
128
|
+
|
|
129
|
+
- `-a, --alias` (required): Pipeline alias to run.
|
|
130
|
+
- `-b, --branch` (optional): Git branch name to associate with this run.
|
|
131
|
+
- `-c, --commit` (optional): Commit SHA to associate with this run.
|
|
132
|
+
- `--wait/--no-wait` (default: `--wait`): Poll until the run ends.
|
|
133
|
+
- `--force/--no-force` (default: `--no-force`): Skip confirmation if local git warnings are detected.
|
|
134
|
+
|
|
135
|
+
Behavior
|
|
136
|
+
|
|
137
|
+
- Prints the run ID when known and a link to the run lineage page.
|
|
138
|
+
- When waiting, polls status every ~5s until a terminal state:
|
|
139
|
+
- `SUCCEEDED` (exit `0`), `WARNING` (exit `0`), `SKIPPED` (exit `0`)
|
|
140
|
+
- `FAILED` or `CANCELLED` (exit `1`)
|
|
141
|
+
- When not waiting, exits after start and prints the run ID.
|
|
142
|
+
|
|
143
|
+
Non-interactive usage
|
|
144
|
+
|
|
145
|
+
- If your repo has warnings (e.g., uncommitted changes), the CLI prompts for confirmation unless `--force` is provided. For CI or scripts, pass `--force` or ensure a clean repo.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Validate a pipeline file
|
|
153
|
+
orchestra validate ./examples/etl.yaml
|
|
154
|
+
|
|
155
|
+
# Import a pipeline and capture the created ID
|
|
156
|
+
PIPELINE_ID=$(orchestra import -a finance-etl -p ./pipelines/etl.yaml)
|
|
157
|
+
|
|
158
|
+
# Start a run and wait for completion
|
|
159
|
+
orchestra run -a finance-etl
|
|
160
|
+
|
|
161
|
+
# Start a run and exit immediately
|
|
162
|
+
orchestra run -a finance-etl --no-wait
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
- Make sure [uv](https://github.com/astral-sh/uv) is installed
|
|
170
|
+
- Use `uv pip install -e ".[dev]"` to install the CLI in editable mode for development
|
|
171
|
+
- For local development, run `uv run orchestra` to start the CLI
|
|
172
|
+
- you can use `uv run --env-file .env ...` to run the CLI with env vars
|
|
173
|
+
- For testing, run `uv run pytest`
|
|
174
|
+
- For linting, run `uv run ruff check .`
|
|
175
|
+
- For formatting, run `uv run black --check .`
|
|
176
|
+
- For type checking, run `uv run pyright`
|
|
177
|
+
|
|
178
|
+
## Building and Releasing
|
|
179
|
+
|
|
180
|
+
- Bump the version in `pyproject.toml` or by running `uv version --bump <major/minor/patch>`
|
|
181
|
+
- Run `uv sync` to install the dependencies
|
|
182
|
+
- Run `uv build` to build the CLI
|
|
183
|
+
- Run `uv publish` to publish the CLI (you will need to pass the `--token` flag)
|
|
184
|
+
|
|
185
|
+
**Note: Failure to bump the version will result in a failed release.**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from .import_pipeline import import_pipeline
|
|
4
|
+
from .run_pipeline import run_pipeline
|
|
5
|
+
from .validate_pipeline import validate
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Orchestra CLI – perform operations with Orchestra locally.")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
app.command(name="validate")(validate)
|
|
11
|
+
app.command(name="import")(import_pipeline)
|
|
12
|
+
app.command(name="run")(run_pipeline)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
from ..utils.constants import get_api_url
|
|
11
|
+
from ..utils.git import detect_repo_root, git_warnings, run_git_command
|
|
12
|
+
from ..utils.styling import bold, green, indent_message, red, yellow
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _detect_repository_url(repo_root: Path) -> str | None:
|
|
16
|
+
ok, remote = run_git_command(["remote", "get-url", "origin"], repo_root)
|
|
17
|
+
if not ok or not remote:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
# First, handle special cases like Azure URLs by removing segments like /_git/
|
|
21
|
+
cleaned_remote = re.sub(r"/(?:_git|scm|v3)(?=/)", "", remote.strip())
|
|
22
|
+
|
|
23
|
+
# A single regex can then capture the owner/repo from the cleaned URL
|
|
24
|
+
pattern = r".*[:/]([^/]+)/([^/]+?)(?:\.git)?/?$"
|
|
25
|
+
|
|
26
|
+
if match := re.search(pattern, cleaned_remote):
|
|
27
|
+
return f"{match.group(1)}/{match.group(2)}"
|
|
28
|
+
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_remote_url(repo_root: Path) -> str | None:
|
|
33
|
+
"""Return the raw git remote URL for origin without transformation."""
|
|
34
|
+
ok, remote = run_git_command(["remote", "get-url", "origin"], repo_root)
|
|
35
|
+
if not ok:
|
|
36
|
+
return None
|
|
37
|
+
return remote
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _detect_default_branch(repo_root: Path) -> str | None:
|
|
41
|
+
# Try symbolic-ref of remote HEAD first
|
|
42
|
+
ok, out = run_git_command(["symbolic-ref", "refs/remotes/origin/HEAD"], repo_root)
|
|
43
|
+
if ok and out:
|
|
44
|
+
# refs/remotes/origin/main -> main
|
|
45
|
+
return out.split("/")[-1]
|
|
46
|
+
# Fallback to parsing `git remote show origin`
|
|
47
|
+
ok, out = run_git_command(["remote", "show", "origin"], repo_root)
|
|
48
|
+
if ok and out:
|
|
49
|
+
m = re.search(r"HEAD branch:\s*(\S+)", out)
|
|
50
|
+
if m:
|
|
51
|
+
return m.group(1)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _detect_current_branch(repo_root: Path) -> str | None:
|
|
56
|
+
ok, out = run_git_command(["rev-parse", "--abbrev-ref", "HEAD"], repo_root)
|
|
57
|
+
if ok and out:
|
|
58
|
+
return out
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _detect_storage_provider(repository_url: str | None) -> str:
|
|
63
|
+
if not repository_url:
|
|
64
|
+
typer.echo(red("Could not detect storage provider - no repository URL"))
|
|
65
|
+
raise typer.Exit(code=1)
|
|
66
|
+
url = repository_url.lower()
|
|
67
|
+
if "github.com" in url:
|
|
68
|
+
return "GITHUB"
|
|
69
|
+
if "gitlab.com" in url:
|
|
70
|
+
return "GITLAB"
|
|
71
|
+
if any(host in url for host in ["dev.azure.com", "azure.com", "visualstudio.com"]):
|
|
72
|
+
return "AZURE_DEVOPS"
|
|
73
|
+
typer.echo(red("Could not detect storage provider - no matching host"))
|
|
74
|
+
raise typer.Exit(code=1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_yaml(file: Path) -> tuple[dict | None, str | None]:
|
|
78
|
+
try:
|
|
79
|
+
with file.open("r") as f:
|
|
80
|
+
data = yaml.safe_load(f)
|
|
81
|
+
return data, None
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return None, str(e)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _validate_yaml_with_api(data: dict) -> tuple[bool, str | None]:
|
|
87
|
+
try:
|
|
88
|
+
response = httpx.post(get_api_url("schema"), json=data, timeout=15)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
return False, f"HTTP request failed: {e}"
|
|
91
|
+
|
|
92
|
+
if response.status_code == 200:
|
|
93
|
+
return True, None
|
|
94
|
+
try:
|
|
95
|
+
errors = response.json()
|
|
96
|
+
return False, json.dumps(errors, indent=2)
|
|
97
|
+
except Exception:
|
|
98
|
+
return False, response.text
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def import_pipeline(
|
|
102
|
+
alias: str = typer.Option(..., "--alias", "-a", help="Pipeline alias"),
|
|
103
|
+
path: Path = typer.Option(
|
|
104
|
+
...,
|
|
105
|
+
"--path",
|
|
106
|
+
"-p",
|
|
107
|
+
exists=True,
|
|
108
|
+
file_okay=True,
|
|
109
|
+
dir_okay=False,
|
|
110
|
+
readable=True,
|
|
111
|
+
resolve_path=True,
|
|
112
|
+
help="Path to pipeline YAML inside a git repository",
|
|
113
|
+
),
|
|
114
|
+
working_branch: str | None = typer.Option(
|
|
115
|
+
None,
|
|
116
|
+
"--working-branch",
|
|
117
|
+
"-w",
|
|
118
|
+
help="Git branch to use for the imported pipeline (defaults to current local branch)",
|
|
119
|
+
),
|
|
120
|
+
):
|
|
121
|
+
"""
|
|
122
|
+
Create a pipeline in Orchestra by referencing a YAML file in your git repository.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
if not path.exists():
|
|
126
|
+
typer.echo(red(f"File not found: {path}"))
|
|
127
|
+
raise typer.Exit(code=1)
|
|
128
|
+
|
|
129
|
+
api_key = os.getenv("ORCHESTRA_API_KEY")
|
|
130
|
+
if not api_key:
|
|
131
|
+
typer.echo(red("ORCHESTRA_API_KEY is not set"))
|
|
132
|
+
raise typer.Exit(code=1)
|
|
133
|
+
|
|
134
|
+
# Load and validate YAML with API first
|
|
135
|
+
data, err = _load_yaml(path)
|
|
136
|
+
if err is not None:
|
|
137
|
+
typer.echo(red(f"Invalid YAML: {err}"))
|
|
138
|
+
raise typer.Exit(code=1)
|
|
139
|
+
|
|
140
|
+
ok, err_msg = _validate_yaml_with_api(data or {})
|
|
141
|
+
if not ok:
|
|
142
|
+
typer.echo(red("❌ Validation failed"))
|
|
143
|
+
if err_msg:
|
|
144
|
+
typer.echo(yellow(indent_message(err_msg)))
|
|
145
|
+
raise typer.Exit(code=1)
|
|
146
|
+
|
|
147
|
+
# Detect git repository info
|
|
148
|
+
repo_root = detect_repo_root(path.parent)
|
|
149
|
+
if repo_root is None:
|
|
150
|
+
typer.echo(red("Not a git repository (could not detect repository root)"))
|
|
151
|
+
raise typer.Exit(code=1)
|
|
152
|
+
|
|
153
|
+
repository_slug = _detect_repository_url(repo_root)
|
|
154
|
+
if not repository_slug:
|
|
155
|
+
typer.echo(red("Could not detect repository URL from git"))
|
|
156
|
+
raise typer.Exit(code=1)
|
|
157
|
+
default_branch = _detect_default_branch(repo_root)
|
|
158
|
+
if not default_branch:
|
|
159
|
+
typer.echo(red("Could not detect default branch from git"))
|
|
160
|
+
raise typer.Exit(code=1)
|
|
161
|
+
|
|
162
|
+
# Determine working branch (explicit option or current branch)
|
|
163
|
+
if working_branch is None:
|
|
164
|
+
working_branch = _detect_current_branch(repo_root)
|
|
165
|
+
if not working_branch:
|
|
166
|
+
typer.echo(red("Could not detect current branch from git"))
|
|
167
|
+
raise typer.Exit(code=1)
|
|
168
|
+
|
|
169
|
+
# Compute YAML path relative to repo root
|
|
170
|
+
try:
|
|
171
|
+
yaml_path = str(path.resolve().relative_to(repo_root.resolve()))
|
|
172
|
+
except Exception:
|
|
173
|
+
typer.echo(red("YAML file must be inside the git repository"))
|
|
174
|
+
raise typer.Exit(code=1)
|
|
175
|
+
|
|
176
|
+
# Show warnings
|
|
177
|
+
for w in git_warnings(repo_root):
|
|
178
|
+
typer.echo(yellow(f"⚠ {w}"))
|
|
179
|
+
|
|
180
|
+
payload = {
|
|
181
|
+
"storage_provider": _detect_storage_provider(_get_remote_url(repo_root)),
|
|
182
|
+
"repository": repository_slug,
|
|
183
|
+
"default_branch": default_branch,
|
|
184
|
+
"working_branch": working_branch,
|
|
185
|
+
"yaml_path": yaml_path,
|
|
186
|
+
"alias": alias,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
response = httpx.post(
|
|
191
|
+
get_api_url("import"),
|
|
192
|
+
json=payload,
|
|
193
|
+
timeout=30,
|
|
194
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
195
|
+
)
|
|
196
|
+
except Exception as e:
|
|
197
|
+
typer.echo(red(f"HTTP request failed: {e}"))
|
|
198
|
+
raise typer.Exit(code=1)
|
|
199
|
+
|
|
200
|
+
if response.status_code == 201:
|
|
201
|
+
try:
|
|
202
|
+
body = response.json()
|
|
203
|
+
except Exception:
|
|
204
|
+
body = {}
|
|
205
|
+
pipeline_id = body.get("id")
|
|
206
|
+
if pipeline_id:
|
|
207
|
+
# Print only the pipeline id as requested
|
|
208
|
+
typer.echo(f"Pipeline with alias '{alias}' imported successfully: {pipeline_id}")
|
|
209
|
+
raise typer.Exit(code=0)
|
|
210
|
+
else:
|
|
211
|
+
# Fallback if server does not return a field we expect
|
|
212
|
+
typer.echo(green("✅ Pipeline imported successfully"))
|
|
213
|
+
if body:
|
|
214
|
+
typer.echo(bold(json.dumps(body)))
|
|
215
|
+
raise typer.Exit(code=0)
|
|
216
|
+
|
|
217
|
+
# Error handling for non-201 responses
|
|
218
|
+
typer.echo(red(f"❌ Import failed with status {response.status_code}"))
|
|
219
|
+
try:
|
|
220
|
+
typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
|
|
221
|
+
except Exception:
|
|
222
|
+
typer.echo(yellow(indent_message(response.text)))
|
|
223
|
+
raise typer.Exit(code=1)
|