lazyops-cli 1.0.0__tar.gz → 1.1.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.
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/PKG-INFO +18 -1
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/README.md +15 -0
- lazyops_cli-1.1.0/commands/aws.py +37 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/run.py +7 -69
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/PKG-INFO +18 -1
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/SOURCES.txt +3 -0
- lazyops_cli-1.1.0/lazyops_cli.egg-info/requires.txt +4 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/pyproject.toml +3 -1
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/catalog.py +31 -2
- lazyops_cli-1.1.0/registry/cmdb.py +23 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/config.py +7 -1
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/fetch.py +51 -7
- lazyops_cli-1.1.0/registry/runner.py +79 -0
- lazyops_cli-1.0.0/lazyops_cli.egg-info/requires.txt +0 -2
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/LICENSE +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/__init__.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/list.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/pack.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/search.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/source.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli/__init__.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli/cli.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/dependency_links.txt +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/entry_points.txt +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/top_level.txt +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/__init__.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/discover.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/packs.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/paths.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/source.py +0 -0
- {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lazyops-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: CLI to discover, fetch, and run reusable automation workflows from remote plugin packs.
|
|
5
5
|
Author: Mridul Tiwari
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: typer>=0.12.0
|
|
27
27
|
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
Requires-Dist: boto3>=1.26.0
|
|
28
30
|
Dynamic: license-file
|
|
29
31
|
|
|
30
32
|
# LazyOps
|
|
@@ -173,6 +175,8 @@ source:
|
|
|
173
175
|
packs:
|
|
174
176
|
- aws
|
|
175
177
|
- kubernetes
|
|
178
|
+
cmdb:
|
|
179
|
+
url: https://cmdb.paytmpayments.com
|
|
176
180
|
```
|
|
177
181
|
|
|
178
182
|
## Commands
|
|
@@ -190,6 +194,19 @@ packs:
|
|
|
190
194
|
| `lazyops run` | Interactive workflow picker |
|
|
191
195
|
| `lazyops list` | List workflows in installed packs |
|
|
192
196
|
| `lazyops search <query>` | Search by name, id, or description |
|
|
197
|
+
| `lazyops aws trace <domain>` | Trace domain → ALB → backend EC2 IPs (CMDB + AWS) |
|
|
198
|
+
|
|
199
|
+
### AWS trace
|
|
200
|
+
|
|
201
|
+
Requires the `aws` pack (`lazyops pack add aws`). On VPN, CMDB defaults to `https://cmdb.paytmpayments.com` (override with `--cmdb-url` or `cmdb.url` in `~/.lazyops/config.yaml`).
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
lazyops aws trace seller.example.com
|
|
205
|
+
lazyops aws trace seller.example.com --cmdb-url https://cmdb.paytmpayments.com
|
|
206
|
+
lazyops aws trace seller.example.com --source aws --region ap-south-1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Bundled plugin for local dev: `plugins/aws/trace/`. Copy to [lazyops-plugins](https://github.com/MridulTi/lazyops-plugins) at `plugins/aws/trace/` for remote catalog releases.
|
|
193
210
|
|
|
194
211
|
## Example manifest
|
|
195
212
|
|
|
@@ -144,6 +144,8 @@ source:
|
|
|
144
144
|
packs:
|
|
145
145
|
- aws
|
|
146
146
|
- kubernetes
|
|
147
|
+
cmdb:
|
|
148
|
+
url: https://cmdb.paytmpayments.com
|
|
147
149
|
```
|
|
148
150
|
|
|
149
151
|
## Commands
|
|
@@ -161,6 +163,19 @@ packs:
|
|
|
161
163
|
| `lazyops run` | Interactive workflow picker |
|
|
162
164
|
| `lazyops list` | List workflows in installed packs |
|
|
163
165
|
| `lazyops search <query>` | Search by name, id, or description |
|
|
166
|
+
| `lazyops aws trace <domain>` | Trace domain → ALB → backend EC2 IPs (CMDB + AWS) |
|
|
167
|
+
|
|
168
|
+
### AWS trace
|
|
169
|
+
|
|
170
|
+
Requires the `aws` pack (`lazyops pack add aws`). On VPN, CMDB defaults to `https://cmdb.paytmpayments.com` (override with `--cmdb-url` or `cmdb.url` in `~/.lazyops/config.yaml`).
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
lazyops aws trace seller.example.com
|
|
174
|
+
lazyops aws trace seller.example.com --cmdb-url https://cmdb.paytmpayments.com
|
|
175
|
+
lazyops aws trace seller.example.com --source aws --region ap-south-1
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Bundled plugin for local dev: `plugins/aws/trace/`. Copy to [lazyops-plugins](https://github.com/MridulTi/lazyops-plugins) at `plugins/aws/trace/` for remote catalog releases.
|
|
164
179
|
|
|
165
180
|
## Example manifest
|
|
166
181
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from registry.cmdb import get_cmdb_url
|
|
4
|
+
from registry.runner import run_target
|
|
5
|
+
|
|
6
|
+
aws_app = typer.Typer(help="AWS operational shortcuts")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register(app: typer.Typer):
|
|
10
|
+
app.add_typer(aws_app, name="aws")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@aws_app.command("trace", help="Trace a domain to ALB and backend EC2 instance IPs")
|
|
14
|
+
def aws_trace(
|
|
15
|
+
domain: str = typer.Argument(..., help="Domain to trace, e.g. seller.paytmpayments.com"),
|
|
16
|
+
cmdb_url: str | None = typer.Option(
|
|
17
|
+
None,
|
|
18
|
+
"--cmdb-url",
|
|
19
|
+
help="CMDB base URL (default: config or https://cmdb.paytmpayments.com)",
|
|
20
|
+
),
|
|
21
|
+
source: str = typer.Option(
|
|
22
|
+
"auto",
|
|
23
|
+
"--source",
|
|
24
|
+
help="Data source: auto (CMDB + AWS), cmdb (CMDB only where possible), aws (skip CMDB)",
|
|
25
|
+
),
|
|
26
|
+
region: str | None = typer.Option(
|
|
27
|
+
None,
|
|
28
|
+
"--region",
|
|
29
|
+
help="AWS region override for ELBv2/EC2 calls",
|
|
30
|
+
),
|
|
31
|
+
):
|
|
32
|
+
"""Resolve domain → load balancer → backend instances via DNS, CMDB, and AWS."""
|
|
33
|
+
resolved_cmdb = get_cmdb_url(cmdb_url)
|
|
34
|
+
args = [domain, "--cmdb-url", resolved_cmdb, "--source", source]
|
|
35
|
+
if region:
|
|
36
|
+
args.extend(["--region", region])
|
|
37
|
+
run_target("aws", "trace", args)
|
|
@@ -5,82 +5,20 @@ import subprocess
|
|
|
5
5
|
import typer
|
|
6
6
|
import yaml
|
|
7
7
|
|
|
8
|
-
from registry.
|
|
9
|
-
from registry.fetch import FetchError, fetch_pack_dir, fetch_plugin_dir, list_pack_plugins
|
|
8
|
+
from registry.fetch import FetchError, fetch_pack_dir, list_pack_plugins
|
|
10
9
|
from registry.packs import list_packs, pack_installed
|
|
11
|
-
from registry.
|
|
10
|
+
from registry.runner import run_target
|
|
12
11
|
from registry.source import SourceError, get_source
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
def _parse_target(target:str)-> tuple[str,str]:
|
|
14
|
+
def _parse_target(target: str) -> tuple[str, str]:
|
|
16
15
|
if "/" not in target:
|
|
17
16
|
raise typer.BadParameter("Use pack/plugin format, eg. aws/addpatchclasstag")
|
|
18
17
|
|
|
19
|
-
pack,plugin = target.split("/",1)
|
|
18
|
+
pack, plugin = target.split("/", 1)
|
|
20
19
|
if not pack or not plugin:
|
|
21
20
|
raise typer.BadParameter("Use pack/plugin format, e.g. aws/addpatchclasstag")
|
|
22
|
-
return pack,plugin
|
|
23
|
-
|
|
24
|
-
def _run_target(pack: str, plugin: str, extra_args: list[str]) -> None:
|
|
25
|
-
if not pack_installed(pack):
|
|
26
|
-
typer.secho(
|
|
27
|
-
f"Pack not installed: {pack}. Run: lazyops pack add {pack}",
|
|
28
|
-
fg=typer.colors.RED,
|
|
29
|
-
err=True,
|
|
30
|
-
)
|
|
31
|
-
raise typer.Exit(1)
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
source = get_source()
|
|
35
|
-
except SourceError as exc:
|
|
36
|
-
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
37
|
-
raise typer.Exit(1)
|
|
38
|
-
|
|
39
|
-
tmp_root = None
|
|
40
|
-
workflow_dir = None
|
|
41
|
-
try:
|
|
42
|
-
workflow_dir = fetch_plugin_dir(
|
|
43
|
-
url=source["url"],
|
|
44
|
-
ref=source["ref"],
|
|
45
|
-
path_prefix=source.get("path_prefix", "plugins"),
|
|
46
|
-
pack=pack,
|
|
47
|
-
plugin=plugin,
|
|
48
|
-
)
|
|
49
|
-
tmp_root = workflow_dir.parent.parent
|
|
50
|
-
|
|
51
|
-
workflow_path = workflow_dir / "workflow.yaml"
|
|
52
|
-
workflow = read_workflow(workflow_path)
|
|
53
|
-
entrypoint = workflow_dir / workflow["entrypoint"]
|
|
54
|
-
inputs = list(extra_args or [])
|
|
55
|
-
|
|
56
|
-
env = os.environ.copy()
|
|
57
|
-
env.setdefault("LAZYOPS_ROOT", str(PROJECT_ROOT))
|
|
58
|
-
env.setdefault("WORKFLOW_ID", workflow.get("id", plugin))
|
|
59
|
-
env.setdefault("WORKFLOW_ROOT", str(workflow_dir))
|
|
60
|
-
env.setdefault("WORKFLOW_RUNTIME", workflow["runtime"])
|
|
61
|
-
env.setdefault("WORKFLOW_VERSION", workflow.get("version", "1.0.0"))
|
|
62
|
-
env.setdefault("WORKFLOW_PACK", pack)
|
|
63
|
-
env.setdefault("WORKFLOW_PLUGIN", plugin)
|
|
64
|
-
env.setdefault("WORKFLOW_SOURCE_REF", source["ref"])
|
|
65
|
-
|
|
66
|
-
if workflow["runtime"] == "bash":
|
|
67
|
-
cmd = ["bash", str(entrypoint)] + inputs
|
|
68
|
-
elif workflow["runtime"] == "python":
|
|
69
|
-
cmd = [venv_python(), str(entrypoint)] + inputs
|
|
70
|
-
elif workflow["runtime"] == "node":
|
|
71
|
-
cmd = ["node", str(entrypoint)] + inputs
|
|
72
|
-
else:
|
|
73
|
-
typer.secho(f"Unsupported runtime: {workflow['runtime']}", fg=typer.colors.RED, err=True)
|
|
74
|
-
raise typer.Exit(1)
|
|
75
|
-
|
|
76
|
-
subprocess.run(cmd, check=True, env=env, cwd=workflow_dir)
|
|
77
|
-
|
|
78
|
-
except FetchError as exc:
|
|
79
|
-
typer.secho(f"Failed to fetch workflow: {exc}", fg=typer.colors.RED, err=True)
|
|
80
|
-
raise typer.Exit(1)
|
|
81
|
-
finally:
|
|
82
|
-
if tmp_root is not None:
|
|
83
|
-
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
21
|
+
return pack, plugin
|
|
84
22
|
|
|
85
23
|
|
|
86
24
|
def _pick_index(prompt: str, max_index: int) -> int:
|
|
@@ -141,7 +79,6 @@ def _interactive_run() -> tuple[str, str]:
|
|
|
141
79
|
pack,
|
|
142
80
|
)
|
|
143
81
|
|
|
144
|
-
# plugins list order from git; enrich with local workflow.yaml names
|
|
145
82
|
plugin_labels: dict[str, str] = {}
|
|
146
83
|
for child in pack_dir.iterdir():
|
|
147
84
|
if not child.is_dir() or child.name == "pack.yaml":
|
|
@@ -171,6 +108,7 @@ def _interactive_run() -> tuple[str, str]:
|
|
|
171
108
|
|
|
172
109
|
return pack, plugin
|
|
173
110
|
|
|
111
|
+
|
|
174
112
|
def register(app: typer.Typer):
|
|
175
113
|
|
|
176
114
|
@app.command("run", help="Run a workflow: lazyops run <pack>/<plugin> [args...]")
|
|
@@ -182,4 +120,4 @@ def register(app: typer.Typer):
|
|
|
182
120
|
pack, plugin = _interactive_run()
|
|
183
121
|
else:
|
|
184
122
|
pack, plugin = _parse_target(target)
|
|
185
|
-
|
|
123
|
+
run_target(pack, plugin, list(extra_args or []))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lazyops-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: CLI to discover, fetch, and run reusable automation workflows from remote plugin packs.
|
|
5
5
|
Author: Mridul Tiwari
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,8 @@ Description-Content-Type: text/markdown
|
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Requires-Dist: typer>=0.12.0
|
|
27
27
|
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
Requires-Dist: boto3>=1.26.0
|
|
28
30
|
Dynamic: license-file
|
|
29
31
|
|
|
30
32
|
# LazyOps
|
|
@@ -173,6 +175,8 @@ source:
|
|
|
173
175
|
packs:
|
|
174
176
|
- aws
|
|
175
177
|
- kubernetes
|
|
178
|
+
cmdb:
|
|
179
|
+
url: https://cmdb.paytmpayments.com
|
|
176
180
|
```
|
|
177
181
|
|
|
178
182
|
## Commands
|
|
@@ -190,6 +194,19 @@ packs:
|
|
|
190
194
|
| `lazyops run` | Interactive workflow picker |
|
|
191
195
|
| `lazyops list` | List workflows in installed packs |
|
|
192
196
|
| `lazyops search <query>` | Search by name, id, or description |
|
|
197
|
+
| `lazyops aws trace <domain>` | Trace domain → ALB → backend EC2 IPs (CMDB + AWS) |
|
|
198
|
+
|
|
199
|
+
### AWS trace
|
|
200
|
+
|
|
201
|
+
Requires the `aws` pack (`lazyops pack add aws`). On VPN, CMDB defaults to `https://cmdb.paytmpayments.com` (override with `--cmdb-url` or `cmdb.url` in `~/.lazyops/config.yaml`).
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
lazyops aws trace seller.example.com
|
|
205
|
+
lazyops aws trace seller.example.com --cmdb-url https://cmdb.paytmpayments.com
|
|
206
|
+
lazyops aws trace seller.example.com --source aws --region ap-south-1
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Bundled plugin for local dev: `plugins/aws/trace/`. Copy to [lazyops-plugins](https://github.com/MridulTi/lazyops-plugins) at `plugins/aws/trace/` for remote catalog releases.
|
|
193
210
|
|
|
194
211
|
## Example manifest
|
|
195
212
|
|
|
@@ -2,6 +2,7 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
commands/__init__.py
|
|
5
|
+
commands/aws.py
|
|
5
6
|
commands/list.py
|
|
6
7
|
commands/pack.py
|
|
7
8
|
commands/run.py
|
|
@@ -17,9 +18,11 @@ lazyops_cli.egg-info/requires.txt
|
|
|
17
18
|
lazyops_cli.egg-info/top_level.txt
|
|
18
19
|
registry/__init__.py
|
|
19
20
|
registry/catalog.py
|
|
21
|
+
registry/cmdb.py
|
|
20
22
|
registry/config.py
|
|
21
23
|
registry/discover.py
|
|
22
24
|
registry/fetch.py
|
|
23
25
|
registry/packs.py
|
|
24
26
|
registry/paths.py
|
|
27
|
+
registry/runner.py
|
|
25
28
|
registry/source.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "lazyops-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "CLI to discover, fetch, and run reusable automation workflows from remote plugin packs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -27,6 +27,8 @@ classifiers = [
|
|
|
27
27
|
dependencies = [
|
|
28
28
|
"typer>=0.12.0",
|
|
29
29
|
"PyYAML>=6.0",
|
|
30
|
+
"requests>=2.28.0",
|
|
31
|
+
"boto3>=1.26.0",
|
|
30
32
|
]
|
|
31
33
|
|
|
32
34
|
[project.urls]
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
import shutil
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
import yaml
|
|
6
8
|
|
|
7
|
-
from registry.fetch import FetchError, fetch_pack_dir
|
|
9
|
+
from registry.fetch import FetchError, fetch_pack_dir, list_local_plugins
|
|
8
10
|
from registry.packs import list_packs
|
|
11
|
+
from registry.paths import PROJECT_ROOT
|
|
9
12
|
from registry.source import SourceError, get_source
|
|
10
13
|
|
|
11
14
|
|
|
@@ -26,7 +29,27 @@ def iter_installed_workflows() -> list[dict]:
|
|
|
26
29
|
ref = source["ref"]
|
|
27
30
|
path_prefix = source.get("path_prefix", "plugins")
|
|
28
31
|
results: list[dict] = []
|
|
32
|
+
seen_keys: set[tuple[str, str]] = set()
|
|
33
|
+
|
|
34
|
+
def _append_local(pack: str) -> None:
|
|
35
|
+
for plugin in list_local_plugins(pack):
|
|
36
|
+
key = (pack, plugin)
|
|
37
|
+
if key in seen_keys:
|
|
38
|
+
continue
|
|
39
|
+
yaml_path = PROJECT_ROOT / "plugins" / pack / plugin / "workflow.yaml"
|
|
40
|
+
env_root = os.environ.get("LAZYOPS_PLUGINS_ROOT", "").strip()
|
|
41
|
+
if env_root:
|
|
42
|
+
alt = Path(env_root) / pack / plugin / "workflow.yaml"
|
|
43
|
+
if alt.is_file():
|
|
44
|
+
yaml_path = alt
|
|
45
|
+
if not yaml_path.is_file():
|
|
46
|
+
continue
|
|
47
|
+
workflow = yaml.safe_load(yaml_path.read_text()) or {}
|
|
48
|
+
seen_keys.add(key)
|
|
49
|
+
results.append({"pack": pack, "plugin": plugin, "workflow": workflow})
|
|
50
|
+
|
|
29
51
|
for pack in list_packs():
|
|
52
|
+
_append_local(pack)
|
|
30
53
|
tmp_root = None
|
|
31
54
|
try:
|
|
32
55
|
tmp_root, pack_dir = fetch_pack_dir(url, ref, path_prefix, pack)
|
|
@@ -36,14 +59,20 @@ def iter_installed_workflows() -> list[dict]:
|
|
|
36
59
|
yaml_path = child / "workflow.yaml"
|
|
37
60
|
if not yaml_path.is_file():
|
|
38
61
|
continue
|
|
62
|
+
key = (pack, child.name)
|
|
63
|
+
if key in seen_keys:
|
|
64
|
+
continue
|
|
39
65
|
workflow = yaml.safe_load(yaml_path.read_text()) or {}
|
|
66
|
+
seen_keys.add(key)
|
|
40
67
|
results.append({
|
|
41
68
|
"pack": pack,
|
|
42
69
|
"plugin": child.name,
|
|
43
70
|
"workflow": workflow,
|
|
44
71
|
})
|
|
45
72
|
except FetchError as exc:
|
|
46
|
-
|
|
73
|
+
if not any(r["pack"] == pack for r in results):
|
|
74
|
+
import sys
|
|
75
|
+
print(f"Warning: skipping pack {pack}: {exc}", file=sys.stderr)
|
|
47
76
|
finally:
|
|
48
77
|
if tmp_root is not None:
|
|
49
78
|
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from registry.config import load_config
|
|
6
|
+
|
|
7
|
+
DEFAULT_CMDB_URL = "https://cmdb.paytmpayments.com"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_cmdb_url(override: str | None = None) -> str:
|
|
11
|
+
"""Resolve CMDB base URL: flag/env > config.yaml > default."""
|
|
12
|
+
if override:
|
|
13
|
+
return override.rstrip("/")
|
|
14
|
+
env_url = os.environ.get("LAZYOPS_CMDB_URL", "").strip()
|
|
15
|
+
if env_url:
|
|
16
|
+
return env_url.rstrip("/")
|
|
17
|
+
config = load_config()
|
|
18
|
+
cmdb = config.get("cmdb") or {}
|
|
19
|
+
if isinstance(cmdb, dict):
|
|
20
|
+
cfg_url = (cmdb.get("url") or "").strip()
|
|
21
|
+
if cfg_url:
|
|
22
|
+
return cfg_url.rstrip("/")
|
|
23
|
+
return DEFAULT_CMDB_URL
|
|
@@ -6,7 +6,7 @@ import yaml,os
|
|
|
6
6
|
DEFAULT_CONFIG_DIR = Path(os.environ.get("LAZYOPS_CONFIG_DIR", str(Path.home() / ".lazyops")))
|
|
7
7
|
CONFIG_PATH = DEFAULT_CONFIG_DIR / "config.yaml"
|
|
8
8
|
|
|
9
|
-
DEFAULT_CONFIG = {"source": None, "packs": []}
|
|
9
|
+
DEFAULT_CONFIG = {"source": None, "packs": [], "cmdb": None}
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def ensure_config_dir() -> None:
|
|
@@ -38,6 +38,12 @@ def _migrate_config(data: dict) -> dict:
|
|
|
38
38
|
}
|
|
39
39
|
if "packs" not in data or not isinstance(data["packs"], list):
|
|
40
40
|
data["packs"] = []
|
|
41
|
+
if data.get("cmdb") is None:
|
|
42
|
+
data["cmdb"] = None
|
|
43
|
+
elif isinstance(data["cmdb"], dict):
|
|
44
|
+
data["cmdb"] = {"url": (data["cmdb"].get("url") or "").strip() or None}
|
|
45
|
+
if data["cmdb"]["url"] is None:
|
|
46
|
+
data["cmdb"] = None
|
|
41
47
|
return data
|
|
42
48
|
|
|
43
49
|
def load_config() -> dict:
|
|
@@ -1,15 +1,56 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
4
5
|
import subprocess
|
|
5
6
|
import tempfile
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from registry.paths import PROJECT_ROOT
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
class FetchError(Exception):
|
|
10
15
|
pass
|
|
11
16
|
|
|
12
17
|
|
|
18
|
+
def _local_plugin_dir(pack: str, plugin: str) -> Path | None:
|
|
19
|
+
"""Use bundled or LAZYOPS_PLUGINS_ROOT plugins when present (dev / offline)."""
|
|
20
|
+
roots: list[Path] = []
|
|
21
|
+
env_root = os.environ.get("LAZYOPS_PLUGINS_ROOT", "").strip()
|
|
22
|
+
if env_root:
|
|
23
|
+
roots.append(Path(env_root))
|
|
24
|
+
roots.append(PROJECT_ROOT / "plugins")
|
|
25
|
+
for root in roots:
|
|
26
|
+
candidate = root / pack / plugin
|
|
27
|
+
if (candidate / "workflow.yaml").is_file():
|
|
28
|
+
return candidate
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def list_local_plugins(pack: str) -> list[str]:
|
|
33
|
+
"""Plugin folder names under local plugins/<pack>/ (bundled dev plugins)."""
|
|
34
|
+
names: list[str] = []
|
|
35
|
+
roots: list[Path] = []
|
|
36
|
+
env_root = os.environ.get("LAZYOPS_PLUGINS_ROOT", "").strip()
|
|
37
|
+
if env_root:
|
|
38
|
+
roots.append(Path(env_root))
|
|
39
|
+
roots.append(PROJECT_ROOT / "plugins")
|
|
40
|
+
seen: set[str] = set()
|
|
41
|
+
for root in roots:
|
|
42
|
+
pack_dir = root / pack
|
|
43
|
+
if not pack_dir.is_dir():
|
|
44
|
+
continue
|
|
45
|
+
for child in sorted(pack_dir.iterdir()):
|
|
46
|
+
if not child.is_dir() or child.name == "pack.yaml":
|
|
47
|
+
continue
|
|
48
|
+
if (child / "workflow.yaml").is_file() and child.name not in seen:
|
|
49
|
+
seen.add(child.name)
|
|
50
|
+
names.append(child.name)
|
|
51
|
+
return names
|
|
52
|
+
|
|
53
|
+
|
|
13
54
|
def _run_git(args: list[str], cwd: Path | None = None) -> None:
|
|
14
55
|
result = subprocess.run(
|
|
15
56
|
["git",*args],
|
|
@@ -32,13 +73,16 @@ def fetch_plugin_dir(
|
|
|
32
73
|
ref: str,
|
|
33
74
|
path_prefix: str,
|
|
34
75
|
pack: str,
|
|
35
|
-
plugin: str
|
|
36
|
-
) -> Path:
|
|
76
|
+
plugin: str,
|
|
77
|
+
) -> tuple[Path, Path | None, bool]:
|
|
37
78
|
"""
|
|
38
|
-
|
|
39
|
-
Returns
|
|
40
|
-
Caller deletes parent temp dir when done.
|
|
79
|
+
Resolve one workflow dir at ref (local plugins first, then sparse git clone).
|
|
80
|
+
Returns (plugin_dir, tmp_root_or_none, should_delete_tmp).
|
|
41
81
|
"""
|
|
82
|
+
local = _local_plugin_dir(pack, plugin)
|
|
83
|
+
if local is not None:
|
|
84
|
+
return local, None, False
|
|
85
|
+
|
|
42
86
|
rel = plugin_relpath(path_prefix, pack, plugin)
|
|
43
87
|
tmp_root = Path(tempfile.mkdtemp(prefix="lazyops-"))
|
|
44
88
|
repo_dir = tmp_root / "repo"
|
|
@@ -64,7 +108,7 @@ def fetch_plugin_dir(
|
|
|
64
108
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
65
109
|
shutil.move(str(workflow_dir), str(dest))
|
|
66
110
|
shutil.rmtree(repo_dir, ignore_errors=True)
|
|
67
|
-
return dest
|
|
111
|
+
return dest, tmp_root, True
|
|
68
112
|
except Exception:
|
|
69
113
|
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
70
114
|
raise
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from registry.discover import read_workflow
|
|
10
|
+
from registry.fetch import FetchError, fetch_plugin_dir
|
|
11
|
+
from registry.packs import pack_installed
|
|
12
|
+
from registry.paths import PROJECT_ROOT, venv_python
|
|
13
|
+
from registry.source import SourceError, get_source
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_target(pack: str, plugin: str, extra_args: list[str]) -> None:
|
|
17
|
+
if not pack_installed(pack):
|
|
18
|
+
typer.secho(
|
|
19
|
+
f"Pack not installed: {pack}. Run: lazyops pack add {pack}",
|
|
20
|
+
fg=typer.colors.RED,
|
|
21
|
+
err=True,
|
|
22
|
+
)
|
|
23
|
+
raise typer.Exit(1)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
source = get_source()
|
|
27
|
+
except SourceError as exc:
|
|
28
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
29
|
+
raise typer.Exit(1)
|
|
30
|
+
|
|
31
|
+
tmp_root = None
|
|
32
|
+
workflow_dir = None
|
|
33
|
+
delete_tmp = True
|
|
34
|
+
try:
|
|
35
|
+
workflow_dir, tmp_root, delete_tmp = fetch_plugin_dir(
|
|
36
|
+
url=source["url"],
|
|
37
|
+
ref=source["ref"],
|
|
38
|
+
path_prefix=source.get("path_prefix", "plugins"),
|
|
39
|
+
pack=pack,
|
|
40
|
+
plugin=plugin,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
workflow_path = workflow_dir / "workflow.yaml"
|
|
44
|
+
workflow = read_workflow(workflow_path)
|
|
45
|
+
entrypoint = workflow_dir / workflow["entrypoint"]
|
|
46
|
+
inputs = list(extra_args or [])
|
|
47
|
+
|
|
48
|
+
env = os.environ.copy()
|
|
49
|
+
env.setdefault("LAZYOPS_ROOT", str(PROJECT_ROOT))
|
|
50
|
+
env.setdefault("WORKFLOW_ID", workflow.get("id", plugin))
|
|
51
|
+
env.setdefault("WORKFLOW_ROOT", str(workflow_dir))
|
|
52
|
+
env.setdefault("WORKFLOW_RUNTIME", workflow["runtime"])
|
|
53
|
+
env.setdefault("WORKFLOW_VERSION", workflow.get("version", "1.0.0"))
|
|
54
|
+
env.setdefault("WORKFLOW_PACK", pack)
|
|
55
|
+
env.setdefault("WORKFLOW_PLUGIN", plugin)
|
|
56
|
+
env.setdefault("WORKFLOW_SOURCE_REF", source["ref"])
|
|
57
|
+
|
|
58
|
+
if workflow["runtime"] == "bash":
|
|
59
|
+
cmd = ["bash", str(entrypoint)] + inputs
|
|
60
|
+
elif workflow["runtime"] == "python":
|
|
61
|
+
cmd = [venv_python(), str(entrypoint)] + inputs
|
|
62
|
+
elif workflow["runtime"] == "node":
|
|
63
|
+
cmd = ["node", str(entrypoint)] + inputs
|
|
64
|
+
else:
|
|
65
|
+
typer.secho(
|
|
66
|
+
f"Unsupported runtime: {workflow['runtime']}",
|
|
67
|
+
fg=typer.colors.RED,
|
|
68
|
+
err=True,
|
|
69
|
+
)
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
|
|
72
|
+
subprocess.run(cmd, check=True, env=env, cwd=workflow_dir)
|
|
73
|
+
|
|
74
|
+
except FetchError as exc:
|
|
75
|
+
typer.secho(f"Failed to fetch workflow: {exc}", fg=typer.colors.RED, err=True)
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
finally:
|
|
78
|
+
if delete_tmp and tmp_root is not None:
|
|
79
|
+
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|