veltro-cli 0.15.7__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 (35) hide show
  1. veltro_cli-0.15.7/PKG-INFO +40 -0
  2. veltro_cli-0.15.7/README.md +14 -0
  3. veltro_cli-0.15.7/pyproject.toml +45 -0
  4. veltro_cli-0.15.7/setup.cfg +4 -0
  5. veltro_cli-0.15.7/veltro_cli/__init__.py +6 -0
  6. veltro_cli-0.15.7/veltro_cli/__main__.py +7 -0
  7. veltro_cli-0.15.7/veltro_cli/cli.py +478 -0
  8. veltro_cli-0.15.7/veltro_cli/lib/__init__.py +3 -0
  9. veltro_cli-0.15.7/veltro_cli/lib/agent.py +37 -0
  10. veltro_cli-0.15.7/veltro_cli/lib/backup.py +139 -0
  11. veltro_cli-0.15.7/veltro_cli/lib/compose.py +276 -0
  12. veltro_cli-0.15.7/veltro_cli/lib/datasets.py +611 -0
  13. veltro_cli-0.15.7/veltro_cli/lib/demo.py +421 -0
  14. veltro_cli-0.15.7/veltro_cli/lib/demo_replay.py +794 -0
  15. veltro_cli-0.15.7/veltro_cli/lib/doctor.py +99 -0
  16. veltro_cli-0.15.7/veltro_cli/lib/env_init.py +93 -0
  17. veltro_cli-0.15.7/veltro_cli/lib/errors.py +40 -0
  18. veltro_cli-0.15.7/veltro_cli/lib/host.py +346 -0
  19. veltro_cli-0.15.7/veltro_cli/lib/install.py +400 -0
  20. veltro_cli-0.15.7/veltro_cli/lib/k8s.py +57 -0
  21. veltro_cli-0.15.7/veltro_cli/lib/paths.py +42 -0
  22. veltro_cli-0.15.7/veltro_cli/lib/products.py +54 -0
  23. veltro_cli-0.15.7/veltro_cli/lib/release.py +88 -0
  24. veltro_cli-0.15.7/veltro_cli/lib/status.py +170 -0
  25. veltro_cli-0.15.7/veltro_cli/lib/suite_http.py +227 -0
  26. veltro_cli-0.15.7/veltro_cli/lib/support.py +35 -0
  27. veltro_cli-0.15.7/veltro_cli/lib/tls.py +113 -0
  28. veltro_cli-0.15.7/veltro_cli/lib/toml_config.py +130 -0
  29. veltro_cli-0.15.7/veltro_cli/lib/upgrade.py +379 -0
  30. veltro_cli-0.15.7/veltro_cli/py.typed +0 -0
  31. veltro_cli-0.15.7/veltro_cli.egg-info/PKG-INFO +40 -0
  32. veltro_cli-0.15.7/veltro_cli.egg-info/SOURCES.txt +33 -0
  33. veltro_cli-0.15.7/veltro_cli.egg-info/dependency_links.txt +1 -0
  34. veltro_cli-0.15.7/veltro_cli.egg-info/entry_points.txt +2 -0
  35. veltro_cli-0.15.7/veltro_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: veltro-cli
3
+ Version: 0.15.7
4
+ Summary: Operator CLI for the Veltro security operations suite (Compose + Helm).
5
+ Author: Veltro Security
6
+ License: AGPL-3.0-only
7
+ Project-URL: Homepage, https://github.com/veltrosecurity/veltro
8
+ Project-URL: Documentation, https://github.com/veltrosecurity/veltro#readme
9
+ Project-URL: Repository, https://github.com/veltrosecurity/veltro
10
+ Project-URL: Issues, https://github.com/veltrosecurity/veltro/issues
11
+ Keywords: veltro,security,soc,compose,helm
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3
16
+ Classifier: Operating System :: POSIX
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: System :: Systems Administration
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+
27
+ # veltro-cli
28
+
29
+ Stdlib-only operator CLI for the Veltro suite. Every Veltro release publishes
30
+ it at the release's version. Install a release with:
31
+
32
+ ```sh
33
+ curl -fsSL https://raw.githubusercontent.com/veltrosecurity/veltro/vX.Y.Z/install.sh \
34
+ | VELTRO_RELEASE=X.Y.Z sh
35
+ ```
36
+
37
+ or `pip install veltro-cli==X.Y.Z` and `veltro install --suite-version X.Y.Z`.
38
+ Leave the version out to take the newest release.
39
+
40
+ Requires Python ≥ 3.10 and Docker Compose ≥ 2.20 on the host.
@@ -0,0 +1,14 @@
1
+ # veltro-cli
2
+
3
+ Stdlib-only operator CLI for the Veltro suite. Every Veltro release publishes
4
+ it at the release's version. Install a release with:
5
+
6
+ ```sh
7
+ curl -fsSL https://raw.githubusercontent.com/veltrosecurity/veltro/vX.Y.Z/install.sh \
8
+ | VELTRO_RELEASE=X.Y.Z sh
9
+ ```
10
+
11
+ or `pip install veltro-cli==X.Y.Z` and `veltro install --suite-version X.Y.Z`.
12
+ Leave the version out to take the newest release.
13
+
14
+ Requires Python ≥ 3.10 and Docker Compose ≥ 2.20 on the host.
@@ -0,0 +1,45 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "veltro-cli"
7
+ version = "0.15.7"
8
+ description = "Operator CLI for the Veltro security operations suite (Compose + Helm)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "AGPL-3.0-only" }
12
+ authors = [{ name = "Veltro Security" }]
13
+ keywords = ["veltro", "security", "soc", "compose", "helm"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: System Administrators",
18
+ "License :: OSI Approved :: GNU Affero General Public License v3",
19
+ "Operating System :: POSIX",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Security",
26
+ "Topic :: System :: Systems Administration",
27
+ ]
28
+ # Stdlib only — no third-party runtime dependencies.
29
+ dependencies = []
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/veltrosecurity/veltro"
33
+ Documentation = "https://github.com/veltrosecurity/veltro#readme"
34
+ Repository = "https://github.com/veltrosecurity/veltro"
35
+ Issues = "https://github.com/veltrosecurity/veltro/issues"
36
+
37
+ [project.scripts]
38
+ veltro = "veltro_cli.cli:main"
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["."]
42
+ include = ["veltro_cli*"]
43
+
44
+ [tool.setuptools.package-data]
45
+ veltro_cli = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """veltro-cli — stdlib-only operator entry point for the Veltro suite."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.15.7"
6
+ __all__ = ["__version__"]
@@ -0,0 +1,7 @@
1
+ """python -m veltro_cli"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from veltro_cli.cli import main
6
+
7
+ raise SystemExit(main())
@@ -0,0 +1,478 @@
1
+ """veltro — single operator entry point (stdlib only)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Sequence
9
+
10
+ from veltro_cli import __version__
11
+ from veltro_cli.lib.agent import enroll_agent
12
+ from veltro_cli.lib.backup import cmd_backup, cmd_restore
13
+ from veltro_cli.lib.compose import ComposeClient
14
+ from veltro_cli.lib.demo import ACTIONS as DEMO_ACTIONS, run_demo
15
+ from veltro_cli.lib.doctor import run_doctor
16
+ from veltro_cli.lib.env_init import init_env
17
+ from veltro_cli.lib.errors import VeltroCliError, print_error
18
+ from veltro_cli.lib.install import install as run_install
19
+ from veltro_cli.lib.k8s import render as k8s_render
20
+ from veltro_cli.lib.paths import resolve_dir
21
+ from veltro_cli.lib.products import resolve_selection
22
+ from veltro_cli.lib.status import collect_status, format_status
23
+ from veltro_cli.lib.support import capture_support_bundle
24
+ from veltro_cli.lib.toml_config import (
25
+ TLS_MODES,
26
+ VELTRO_TOML,
27
+ VeltroConfig,
28
+ read_config,
29
+ )
30
+ from veltro_cli.lib.upgrade import execute_upgrade, plan_upgrade
31
+
32
+
33
+ def _load_ctx(args: argparse.Namespace) -> tuple[Path, str, bool, tuple[str, ...]]:
34
+ root = resolve_dir(getattr(args, "dir", None))
35
+ cfg = read_config(root / VELTRO_TOML)
36
+ products = resolve_selection(root, getattr(args, "products", None))
37
+ tls_mode = getattr(args, "tls", None) or (cfg.tls_mode if cfg else "off")
38
+ monitoring = bool(getattr(args, "monitoring", False) or (cfg.monitoring if cfg else False))
39
+ return root, tls_mode, monitoring, products
40
+
41
+
42
+ def _client(args: argparse.Namespace) -> ComposeClient:
43
+ root, tls_mode, monitoring, products = _load_ctx(args)
44
+ return ComposeClient(root, products=products, tls_mode=tls_mode, monitoring=monitoring)
45
+
46
+
47
+ def cmd_version(_args: argparse.Namespace) -> int:
48
+ print(f"veltro-cli {__version__}")
49
+ return 0
50
+
51
+
52
+ def cmd_init_env(args: argparse.Namespace) -> int:
53
+ root = resolve_dir(args.dir)
54
+ generated = init_env(root, force=bool(args.force))
55
+ print(f"wrote {root / '.env'}")
56
+ print(f" generated {len(generated)} secret(s): {' '.join(generated)}")
57
+ return 0
58
+
59
+
60
+ def cmd_install(args: argparse.Namespace) -> int:
61
+ source = Path(args.source).resolve() if args.source else None
62
+ dest = run_install(
63
+ products=args.products,
64
+ directory=args.dir,
65
+ public_url=args.public_url,
66
+ tls_mode=args.tls,
67
+ yes=bool(args.yes),
68
+ source=source,
69
+ release_url=args.release_url,
70
+ release_sha256=args.release_sha256,
71
+ suite_version=args.suite_version,
72
+ monitoring=bool(args.monitoring),
73
+ skip_up=bool(args.skip_up),
74
+ )
75
+ print(f"installed into {dest}")
76
+ shown_public_url = args.public_url or (read_config(dest / VELTRO_TOML) or VeltroConfig()).public_url
77
+ saved = read_config(dest / VELTRO_TOML) or VeltroConfig()
78
+ print(f" products={saved.products} tls={args.tls} public_url={shown_public_url}")
79
+ if args.tls == "internal":
80
+ print(
81
+ " trust CA after startup: "
82
+ f"{dest / 'tls/caddy-data/caddy/pki/authorities/local/root.crt'}"
83
+ )
84
+ print(" next: veltro status")
85
+ return 0
86
+
87
+
88
+ def cmd_up(args: argparse.Namespace) -> int:
89
+ client = _client(args)
90
+ compose_args = tuple(getattr(args, "compose_args", ()))
91
+ if args.dry_run:
92
+ client.up(*compose_args, dry_run=True)
93
+ for call in client.calls:
94
+ print(call.as_shell())
95
+ return 0
96
+ from veltro_cli.lib.install import start_stack
97
+
98
+ result = start_stack(client, *compose_args)
99
+ if result.stdout:
100
+ print(result.stdout, end="" if result.stdout.endswith("\n") else "\n")
101
+ if result.returncode != 0:
102
+ raise VeltroCliError(
103
+ result.stderr.strip() or "compose up failed",
104
+ remedy="run `veltro doctor` and `veltro logs`",
105
+ )
106
+ return 0
107
+
108
+
109
+ def cmd_down(args: argparse.Namespace) -> int:
110
+ client = _client(args)
111
+ result = client.down(*getattr(args, "compose_args", ()), dry_run=bool(args.dry_run))
112
+ if args.dry_run:
113
+ print(client.calls[-1].as_shell())
114
+ return 0
115
+ if result.returncode != 0:
116
+ raise VeltroCliError(result.stderr.strip() or "compose down failed")
117
+ return 0
118
+
119
+
120
+ def cmd_restart(args: argparse.Namespace) -> int:
121
+ client = _client(args)
122
+ result = client.restart(*getattr(args, "compose_args", ()), dry_run=bool(args.dry_run))
123
+ if args.dry_run:
124
+ print(client.calls[-1].as_shell())
125
+ return 0
126
+ if result.returncode != 0:
127
+ raise VeltroCliError(result.stderr.strip() or "compose restart failed")
128
+ return 0
129
+
130
+
131
+ def cmd_ps(args: argparse.Namespace) -> int:
132
+ client = _client(args)
133
+ result = client.ps(*getattr(args, "compose_args", ()), dry_run=bool(args.dry_run))
134
+ if args.dry_run:
135
+ print(client.calls[-1].as_shell())
136
+ return 0
137
+ sys.stdout.write(result.stdout)
138
+ if result.returncode != 0:
139
+ sys.stderr.write(result.stderr)
140
+ return result.returncode
141
+
142
+
143
+ def cmd_logs(args: argparse.Namespace) -> int:
144
+ client = _client(args)
145
+ result = client.logs(*args.service, dry_run=bool(args.dry_run))
146
+ if args.dry_run:
147
+ print(client.calls[-1].as_shell())
148
+ return 0
149
+ sys.stdout.write(result.stdout)
150
+ if result.returncode != 0:
151
+ sys.stderr.write(result.stderr)
152
+ return result.returncode
153
+
154
+
155
+ def cmd_config(args: argparse.Namespace) -> int:
156
+ client = _client(args)
157
+ compose_args = ("--format", args.format) if args.format else ()
158
+ result = client.config(*compose_args, dry_run=bool(args.dry_run))
159
+ if args.dry_run:
160
+ print(client.calls[-1].as_shell())
161
+ return 0
162
+ sys.stdout.write(result.stdout)
163
+ if result.returncode != 0:
164
+ sys.stderr.write(result.stderr)
165
+ return result.returncode
166
+
167
+ def cmd_pull(args: argparse.Namespace) -> int:
168
+ client = _client(args)
169
+ result = client.pull(*getattr(args, "compose_args", ()), dry_run=bool(args.dry_run))
170
+ if args.dry_run:
171
+ print(client.calls[-1].as_shell())
172
+ return 0
173
+ sys.stdout.write(result.stdout)
174
+ if result.returncode != 0:
175
+ sys.stderr.write(result.stderr)
176
+ return result.returncode
177
+
178
+
179
+ def cmd_status(args: argparse.Namespace) -> int:
180
+ root, tls_mode, monitoring, products = _load_ctx(args)
181
+ cfg = read_config(root / VELTRO_TOML)
182
+ public_url = args.public_url or (cfg.public_url if cfg else "http://localhost")
183
+ client = ComposeClient(root, products=products, tls_mode=tls_mode, monitoring=monitoring)
184
+
185
+ def fetcher(url: str, timeout: float = 3.0):
186
+ if args.dry_run:
187
+ return 200, '{"status":"ok"}'
188
+ from veltro_cli.lib.status import default_fetcher
189
+
190
+ return default_fetcher(url, timeout)
191
+
192
+ doc = collect_status(
193
+ root,
194
+ products=products,
195
+ public_url=public_url,
196
+ client=client,
197
+ fetcher=fetcher,
198
+ tls_mode=tls_mode,
199
+ check_latest=not args.dry_run,
200
+ )
201
+ sys.stdout.write(format_status(doc))
202
+ bad = any(p["state"] not in {"ready", "not_installed"} for p in doc.get("probes") or [])
203
+ return 1 if bad and not args.dry_run else 0
204
+
205
+
206
+ def cmd_doctor(args: argparse.Namespace) -> int:
207
+ return run_doctor(
208
+ directory=args.dir,
209
+ products=args.products,
210
+ public_url=args.public_url,
211
+ http_port=int(args.http_port),
212
+ )
213
+
214
+
215
+ def cmd_demo(args: argparse.Namespace) -> int:
216
+ return run_demo(
217
+ args.action,
218
+ directory=args.dir,
219
+ datasets=args.dataset,
220
+ extra_args=args.seed_arg,
221
+ )
222
+
223
+
224
+ def cmd_agent(args: argparse.Namespace) -> int:
225
+ client = _client(args)
226
+ return enroll_agent(
227
+ directory=args.dir,
228
+ environment=args.environment,
229
+ client=client,
230
+ dry_run=bool(args.dry_run),
231
+ )
232
+
233
+
234
+ def cmd_upgrade(args: argparse.Namespace) -> int:
235
+ root = resolve_dir(args.dir)
236
+ cfg = read_config(root / VELTRO_TOML)
237
+ current = (cfg.installed_suite_version if cfg else "") or "0.0.0"
238
+ if not args.to:
239
+ print(f"installed: {current}")
240
+ print("pass --to X.Y.Z to plan/execute an upgrade")
241
+ return 0
242
+ plan = plan_upgrade(directory=root, target=args.to, current=current)
243
+ print(f"upgrade plan: {plan['from']} → {plan['to']}")
244
+ for delta in plan["module_deltas"]:
245
+ print(f" {delta['module']}: {delta['from']} → {delta['to']}")
246
+ for warning in plan["warnings"]:
247
+ print(f" WARNING: {warning}")
248
+ for step in plan["steps"]:
249
+ print(f" - {step}")
250
+ if args.dry_run:
251
+ return 0
252
+ execute_upgrade(directory=root, target=args.to, current=current)
253
+ print(f"upgraded to {args.to}")
254
+ return 0
255
+
256
+
257
+ def cmd_k8s(args: argparse.Namespace) -> int:
258
+ if args.k8s_command != "render":
259
+ raise VeltroCliError(f"unknown k8s subcommand {args.k8s_command!r}")
260
+ products = resolve_selection(resolve_dir(getattr(args, "dir", None)), args.products)
261
+ out = k8s_render(
262
+ products=products,
263
+ values=args.values or (),
264
+ dry_run=bool(args.dry_run),
265
+ )
266
+ print(out)
267
+ return 0
268
+
269
+
270
+ def cmd_support_bundle(args: argparse.Namespace) -> int:
271
+ path = capture_support_bundle(
272
+ directory=args.dir,
273
+ output=args.output,
274
+ base_url=args.base_url,
275
+ )
276
+ print(path)
277
+ return 0
278
+
279
+
280
+ def build_parser() -> argparse.ArgumentParser:
281
+ parser = argparse.ArgumentParser(
282
+ prog="veltro",
283
+ description="Operator CLI for the Veltro security operations suite.",
284
+ )
285
+ parser.add_argument("--version", action="store_true", help="print CLI version")
286
+ parser.add_argument("--dir", default=None, help="install / checkout directory (default: discover or ./veltro)")
287
+ parser.add_argument("--dry-run", action="store_true", help="print actions without running docker")
288
+
289
+ sub = parser.add_subparsers(dest="command")
290
+
291
+ p_install = sub.add_parser("install", help="download/materialise and start Veltro")
292
+ p_install.add_argument("--products", default=None, help="comma-separated subset of vectorflow,chad,warden; empty for core")
293
+ p_install.add_argument("--dir", default="./veltro")
294
+ p_install.add_argument("--public-url", default=None)
295
+ p_install.add_argument("--tls", choices=TLS_MODES, default="off")
296
+ p_install.add_argument("--yes", action="store_true")
297
+ p_install.add_argument("--source", default=None, help="path to a Veltro git checkout to copy from")
298
+ p_install.add_argument("--release-url", default=None, help="URL of a release tarball")
299
+ p_install.add_argument("--release-sha256", default=None, help="required SHA-256 for --release-url")
300
+ p_install.add_argument(
301
+ "--suite-version",
302
+ default=None,
303
+ help="Veltro release to install, X.Y.Z (default: the newest release)",
304
+ )
305
+ p_install.add_argument("--monitoring", action="store_true")
306
+ p_install.add_argument("--skip-up", action="store_true", help="materialise files only (tests)")
307
+ p_install.set_defaults(func=cmd_install)
308
+
309
+ def _lifecycle(name: str, func, help_text: str, *, passthrough: bool = False):
310
+ p = sub.add_parser(name, help=help_text)
311
+ p.add_argument("--products", default=None)
312
+ p.add_argument("--tls", choices=TLS_MODES, default=None)
313
+ p.add_argument("--monitoring", action="store_true")
314
+ p.add_argument("--dir", default=None)
315
+ p.add_argument("--dry-run", action="store_true")
316
+ if passthrough:
317
+ p.add_argument("compose_args", nargs=argparse.REMAINDER)
318
+ p.set_defaults(func=func)
319
+ return p
320
+
321
+ _lifecycle("up", cmd_up, "start the stack (compose up -d --wait)", passthrough=True)
322
+ _lifecycle("down", cmd_down, "stop containers (keeps volumes)", passthrough=True)
323
+ _lifecycle("restart", cmd_restart, "restart services", passthrough=True)
324
+ _lifecycle("ps", cmd_ps, "show service status", passthrough=True)
325
+ _lifecycle("pull", cmd_pull, "pull pinned images", passthrough=True)
326
+ p_logs = _lifecycle("logs", cmd_logs, "follow logs")
327
+ p_logs.add_argument("service", nargs="*")
328
+ p_config = _lifecycle("config", cmd_config, "render resolved compose config")
329
+ p_config.add_argument("--format", choices=("yaml", "json"), default=None)
330
+
331
+ p_status = sub.add_parser("status", help="compose ps + HTTP health probes")
332
+ p_status.add_argument("--products", default=None)
333
+ p_status.add_argument("--tls", choices=TLS_MODES, default=None)
334
+ p_status.add_argument("--dir", default=None)
335
+ p_status.add_argument("--public-url", default=None)
336
+ p_status.add_argument("--dry-run", action="store_true")
337
+ p_status.set_defaults(func=cmd_status)
338
+
339
+ p_doctor = sub.add_parser("doctor", help="host and install preflight checks")
340
+ p_doctor.add_argument("--products", default=None)
341
+ p_doctor.add_argument("--dir", default=None)
342
+ p_doctor.add_argument("--public-url", default=None)
343
+ p_doctor.add_argument("--http-port", default="80")
344
+ p_doctor.set_defaults(func=cmd_doctor)
345
+
346
+ p_demo = sub.add_parser("demo", help="list, seed or reset the demo sandbox datasets")
347
+ p_demo.add_argument("action", choices=DEMO_ACTIONS)
348
+ p_demo.add_argument(
349
+ "--dataset",
350
+ action="append",
351
+ default=[],
352
+ metavar="ID",
353
+ help="dataset id from content/datasets (repeatable, comma-separated, or `all`); defaults to brute-force on seed",
354
+ )
355
+ p_demo.add_argument("--dir", default=None)
356
+ # A trailing REMAINDER positional would swallow `--dataset` itself, so the
357
+ # golden-path seeder's extra arguments are passed one at a time.
358
+ p_demo.add_argument("--seed-arg", action="append", default=[], metavar="ARG",
359
+ help="extra argument forwarded to scripts/seed-demo.sh (repeatable)")
360
+ p_demo.set_defaults(func=cmd_demo)
361
+
362
+ p_agent = sub.add_parser("agent", help="agent enrollment helpers")
363
+ agent_sub = p_agent.add_subparsers(dest="agent_command")
364
+ p_enroll = agent_sub.add_parser("enroll", help="start vf-agent profile and prepare token dir")
365
+ p_enroll.add_argument("--environment", default=None)
366
+ p_enroll.add_argument("--dir", default=None)
367
+ p_enroll.add_argument("--products", default=None)
368
+ p_enroll.add_argument("--dry-run", action="store_true")
369
+ p_enroll.set_defaults(func=cmd_agent)
370
+
371
+ p_upgrade = sub.add_parser("upgrade", help="upgrade to a newer suite release")
372
+ p_upgrade.add_argument("--to", default=None, help="Veltro release to upgrade to, X.Y.Z")
373
+ p_upgrade.add_argument("--dry-run", action="store_true")
374
+ p_upgrade.add_argument("--dir", default=None)
375
+ p_upgrade.set_defaults(func=cmd_upgrade)
376
+
377
+ p_backup = sub.add_parser("backup", help="create a suite backup (§1.5)")
378
+ p_backup.add_argument("--to", default=None)
379
+ p_backup.add_argument("--auto", action="store_true")
380
+ p_backup.add_argument("--products", default=None)
381
+ p_backup.add_argument("--include-env", action="store_true")
382
+ p_backup.add_argument("--passphrase", default=None)
383
+ p_backup.add_argument("--skip-optional", action="store_true")
384
+ p_backup.add_argument("--schedule", default=None)
385
+ p_backup.add_argument("--backup-dir", default=None)
386
+ p_backup.add_argument("--keep", type=int, default=7)
387
+ p_backup.add_argument("--dir", default=None)
388
+ p_backup.set_defaults(func=cmd_backup)
389
+
390
+ p_restore = sub.add_parser("restore", help="restore a suite backup (§1.5)")
391
+ p_restore.add_argument("path", nargs="?")
392
+ p_restore.add_argument("--yes", action="store_true")
393
+ p_restore.add_argument("--products", default=None)
394
+ p_restore.add_argument("--passphrase", default=None)
395
+ p_restore.add_argument("--dry-run", action="store_true")
396
+ p_restore.add_argument("--skip-start", action="store_true")
397
+ p_restore.add_argument("--dir", default=None)
398
+ p_restore.set_defaults(func=cmd_restore)
399
+
400
+ p_k8s = sub.add_parser("k8s", help="kubernetes helpers")
401
+ k8s_sub = p_k8s.add_subparsers(dest="k8s_command")
402
+ p_render = k8s_sub.add_parser("render", help="helm template the umbrella chart")
403
+ p_render.add_argument(
404
+ "--products",
405
+ default=None,
406
+ help="comma-separated subset of vectorflow,chad,warden on the core; empty for core only",
407
+ )
408
+ p_render.add_argument("--values", action="append", default=[])
409
+ p_render.add_argument("--dry-run", action="store_true")
410
+ p_render.set_defaults(func=cmd_k8s)
411
+
412
+ p_sb = sub.add_parser("support-bundle", help="capture a secret-free support bundle")
413
+ p_sb.add_argument("--dir", default=None)
414
+ p_sb.add_argument("--output", default=None)
415
+ p_sb.add_argument("--base-url", default=None)
416
+ p_sb.set_defaults(func=cmd_support_bundle)
417
+
418
+ p_init = sub.add_parser("init-env", help="bootstrap .env from .env.example")
419
+ p_init.add_argument("--dir", default=None)
420
+ p_init.add_argument("--force", action="store_true")
421
+ p_init.set_defaults(func=cmd_init_env)
422
+
423
+ return parser
424
+
425
+
426
+ def main(argv: Sequence[str] | None = None) -> int:
427
+ argv_list = list(sys.argv[1:] if argv is None else argv)
428
+ parser = build_parser()
429
+ if any(arg == "--profile" or arg.startswith("--profile=") for arg in argv_list):
430
+ print("error: --profile was removed; select products with --products or VELTRO_PRODUCTS", file=sys.stderr)
431
+ return 2
432
+ try:
433
+ args, unknown = parser.parse_known_args(argv_list)
434
+ if unknown:
435
+ if getattr(args, "command", None) in {"up", "down", "restart", "ps", "pull"}:
436
+ args.compose_args = [*getattr(args, "compose_args", ()), *unknown]
437
+ elif getattr(args, "command", None) == "logs":
438
+ args.service = [*getattr(args, "service", ()), *unknown]
439
+ else:
440
+ parser.error("unrecognized arguments: " + " ".join(unknown))
441
+ except SystemExit as exc:
442
+ return int(exc.code or 0)
443
+
444
+ # Top-level --dir / --dry-run should win when a subcommand left defaults.
445
+ # argparse subparsers do not inherit parent option values.
446
+ if hasattr(args, "dir") and args.dir is None:
447
+ parent = argparse.ArgumentParser(add_help=False)
448
+ parent.add_argument("--dir", default=None)
449
+ parent.add_argument("--version", action="store_true")
450
+ parent.add_argument("--dry-run", action="store_true")
451
+ parent_args, _ = parent.parse_known_args(argv_list)
452
+ if parent_args.dir is not None:
453
+ args.dir = parent_args.dir
454
+ if parent_args.dry_run and not getattr(args, "dry_run", False):
455
+ args.dry_run = True
456
+
457
+ if getattr(args, "version", False) and not getattr(args, "command", None):
458
+ return cmd_version(args)
459
+
460
+ func = getattr(args, "func", None)
461
+ if func is None:
462
+ if getattr(args, "version", False):
463
+ return cmd_version(args)
464
+ parser.print_help()
465
+ return 2
466
+ try:
467
+ return int(func(args))
468
+ except VeltroCliError as exc:
469
+ return print_error(exc)
470
+ except KeyboardInterrupt:
471
+ print("interrupted", file=sys.stderr)
472
+ return 130
473
+ except Exception as exc: # noqa: BLE001
474
+ return print_error(exc)
475
+
476
+
477
+ if __name__ == "__main__":
478
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ """Library pieces shared by the CLI and thin script shims."""
2
+
3
+ from __future__ import annotations
@@ -0,0 +1,37 @@
1
+ """veltro agent enroll — agent-up + token drop ceremony."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from veltro_cli.lib.compose import ComposeClient
8
+ from veltro_cli.lib.errors import VeltroCliError
9
+ from veltro_cli.lib.paths import enrollment_token_dir, resolve_dir
10
+
11
+
12
+ def enroll_agent(
13
+ *,
14
+ directory: str | Path | None = None,
15
+ environment: str | None = None,
16
+ client: ComposeClient | None = None,
17
+ dry_run: bool = False,
18
+ ) -> int:
19
+ root = resolve_dir(directory)
20
+ client = client or ComposeClient(root)
21
+ token_dir = enrollment_token_dir(root)
22
+ if not dry_run:
23
+ token_dir.mkdir(parents=True, exist_ok=True)
24
+ result = client.agent_up(dry_run=dry_run)
25
+ if result.returncode != 0:
26
+ raise VeltroCliError(
27
+ f"agent-up failed: {result.stderr.strip() or result.stdout.strip()}",
28
+ remedy="ensure the suite is up and an enrollment token will be dropped into "
29
+ f"{token_dir} (see README 'VectorFlow agent enrollment')",
30
+ )
31
+ # Token minting is owned by the seeder / VF API; we only start the agent profile.
32
+ if environment:
33
+ # Record intent for operators; actual enroll uses the token file.
34
+ marker = token_dir / "environment-name"
35
+ if not dry_run:
36
+ marker.write_text(environment + "\n", encoding="utf-8")
37
+ return 0