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.
Files changed (31) hide show
  1. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/PKG-INFO +18 -1
  2. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/README.md +15 -0
  3. lazyops_cli-1.1.0/commands/aws.py +37 -0
  4. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/run.py +7 -69
  5. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/PKG-INFO +18 -1
  6. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/SOURCES.txt +3 -0
  7. lazyops_cli-1.1.0/lazyops_cli.egg-info/requires.txt +4 -0
  8. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/pyproject.toml +3 -1
  9. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/catalog.py +31 -2
  10. lazyops_cli-1.1.0/registry/cmdb.py +23 -0
  11. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/config.py +7 -1
  12. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/fetch.py +51 -7
  13. lazyops_cli-1.1.0/registry/runner.py +79 -0
  14. lazyops_cli-1.0.0/lazyops_cli.egg-info/requires.txt +0 -2
  15. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/LICENSE +0 -0
  16. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/__init__.py +0 -0
  17. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/list.py +0 -0
  18. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/pack.py +0 -0
  19. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/search.py +0 -0
  20. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/commands/source.py +0 -0
  21. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli/__init__.py +0 -0
  22. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli/cli.py +0 -0
  23. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/dependency_links.txt +0 -0
  24. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/entry_points.txt +0 -0
  25. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/lazyops_cli.egg-info/top_level.txt +0 -0
  26. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/__init__.py +0 -0
  27. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/discover.py +0 -0
  28. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/packs.py +0 -0
  29. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/paths.py +0 -0
  30. {lazyops_cli-1.0.0 → lazyops_cli-1.1.0}/registry/source.py +0 -0
  31. {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.0.0
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.discover import read_workflow
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.paths import PROJECT_ROOT, venv_python
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
- _run_target(pack, plugin, list(extra_args or []))
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.0.0
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
@@ -0,0 +1,4 @@
1
+ typer>=0.12.0
2
+ PyYAML>=6.0
3
+ requests>=2.28.0
4
+ boto3>=1.26.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lazyops-cli"
7
- version = "1.0.0"
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
- raise CatalogError(f"Failed to load pack {pack}: {exc}") from exc
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 shutil,yaml
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
- Sparse-clone one workflow dir at ref.
39
- Returns path to the plugin dir (contains workflow.yaml).
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)
@@ -1,2 +0,0 @@
1
- typer>=0.12.0
2
- PyYAML>=6.0
File without changes
File without changes