Add version CLI arg

This commit is contained in:
Miguel Jacq 2025-12-29 14:29:11 +11:00
parent 8c19473e18
commit ad2abed612
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
3 changed files with 72 additions and 25 deletions

View file

@ -1,3 +1,7 @@
# 0.2.0
* Add version CLI arg
# 0.1.7 # 0.1.7
* Fix an attribution bug for certain files ending up in the wrong package/role. * Fix an attribution bug for certain files ending up in the wrong package/role.

View file

@ -15,6 +15,7 @@ from .harvest import harvest
from .manifest import manifest from .manifest import manifest
from .remote import remote_harvest from .remote import remote_harvest
from .sopsutil import SopsError, encrypt_file_binary from .sopsutil import SopsError, encrypt_file_binary
from .version import get_enroll_version
def _discover_config_path(argv: list[str]) -> Optional[Path]: def _discover_config_path(argv: list[str]) -> Optional[Path]:
@ -318,13 +319,6 @@ def _jt_mode(args: argparse.Namespace) -> str:
return "auto" return "auto"
def _add_remote_args(p: argparse.ArgumentParser) -> None:
p.add_argument(
"--remote-host",
help="SSH host to run harvesting on (if set, harvest runs remotely and is pulled locally).",
)
def _add_config_args(p: argparse.ArgumentParser) -> None: def _add_config_args(p: argparse.ArgumentParser) -> None:
p.add_argument( p.add_argument(
"-c", "-c",
@ -339,6 +333,13 @@ def _add_config_args(p: argparse.ArgumentParser) -> None:
action="store_true", action="store_true",
help="Do not load any INI config file (even if one would be auto-discovered).", help="Do not load any INI config file (even if one would be auto-discovered).",
) )
def _add_remote_args(p: argparse.ArgumentParser) -> None:
p.add_argument(
"--remote-host",
help="SSH host to run harvesting on (if set, harvest runs remotely and is pulled locally).",
)
p.add_argument( p.add_argument(
"--remote-port", "--remote-port",
type=int, type=int,
@ -354,11 +355,18 @@ def _add_config_args(p: argparse.ArgumentParser) -> None:
def main() -> None: def main() -> None:
ap = argparse.ArgumentParser(prog="enroll") ap = argparse.ArgumentParser(prog="enroll")
ap.add_argument(
"-v",
"--version",
action="version",
version=f"{get_enroll_version()}",
)
_add_config_args(ap) _add_config_args(ap)
sub = ap.add_subparsers(dest="cmd", required=True) sub = ap.add_subparsers(dest="cmd", required=True)
h = sub.add_parser("harvest", help="Harvest service/package/config state") h = sub.add_parser("harvest", help="Harvest service/package/config state")
_add_config_args(h) _add_config_args(h)
_add_remote_args(h)
h.add_argument( h.add_argument(
"--out", "--out",
help=( help=(
@ -406,7 +414,6 @@ def main() -> None:
action="store_true", action="store_true",
help="Don't use sudo on the remote host (when using --remote options). This may result in a limited harvest due to permission restrictions.", help="Don't use sudo on the remote host (when using --remote options). This may result in a limited harvest due to permission restrictions.",
) )
_add_remote_args(h)
m = sub.add_parser("manifest", help="Render Ansible roles from a harvest") m = sub.add_parser("manifest", help="Render Ansible roles from a harvest")
_add_config_args(m) _add_config_args(m)
@ -443,6 +450,7 @@ def main() -> None:
"single-shot", help="Harvest state, then manifest Ansible code, in one shot" "single-shot", help="Harvest state, then manifest Ansible code, in one shot"
) )
_add_config_args(s) _add_config_args(s)
_add_remote_args(s)
s.add_argument( s.add_argument(
"--harvest", "--harvest",
help=( help=(
@ -500,7 +508,6 @@ def main() -> None:
), ),
) )
_add_common_manifest_args(s) _add_common_manifest_args(s)
_add_remote_args(s)
d = sub.add_parser("diff", help="Compare two harvests and report differences") d = sub.add_parser("diff", help="Compare two harvests and report differences")
_add_config_args(d) _add_config_args(d)
@ -602,14 +609,12 @@ def main() -> None:
) )
args = ap.parse_args(argv) args = ap.parse_args(argv)
remote_host: Optional[str] = getattr(args, "remote_host", None)
try: try:
if args.cmd == "harvest": if args.cmd == "harvest":
sops_fps = getattr(args, "sops", None) sops_fps = getattr(args, "sops", None)
if remote_host: if args.remote_host:
if sops_fps: if sops_fps:
out_file = _resolve_sops_out_file(args.out, hint=remote_host) out_file = _resolve_sops_out_file(args.out, hint=args.remote_host)
with tempfile.TemporaryDirectory(prefix="enroll-harvest-") as td: with tempfile.TemporaryDirectory(prefix="enroll-harvest-") as td:
tmp_bundle = Path(td) / "bundle" tmp_bundle = Path(td) / "bundle"
tmp_bundle.mkdir(parents=True, exist_ok=True) tmp_bundle.mkdir(parents=True, exist_ok=True)
@ -619,7 +624,7 @@ def main() -> None:
pass pass
remote_harvest( remote_harvest(
local_out_dir=tmp_bundle, local_out_dir=tmp_bundle,
remote_host=remote_host, remote_host=args.remote_host,
remote_port=int(args.remote_port), remote_port=int(args.remote_port),
remote_user=args.remote_user, remote_user=args.remote_user,
dangerous=bool(args.dangerous), dangerous=bool(args.dangerous),
@ -635,11 +640,11 @@ def main() -> None:
out_dir = ( out_dir = (
Path(args.out) Path(args.out)
if args.out if args.out
else new_harvest_cache_dir(hint=remote_host).dir else new_harvest_cache_dir(hint=args.remote_host).dir
) )
state = remote_harvest( state = remote_harvest(
local_out_dir=out_dir, local_out_dir=out_dir,
remote_host=remote_host, remote_host=args.remote_host,
remote_port=int(args.remote_port), remote_port=int(args.remote_port),
remote_user=args.remote_user, remote_user=args.remote_user,
dangerous=bool(args.dangerous), dangerous=bool(args.dangerous),
@ -669,12 +674,16 @@ def main() -> None:
) )
print(str(out_file)) print(str(out_file))
else: else:
if not args.out: if args.out:
raise SystemExit( out_dir = args.out
"error: --out is required unless --remote-host is set" else:
out_dir = (
Path(args.out)
if args.out
else new_harvest_cache_dir(hint=args.remote_host).dir
) )
path = harvest( path = harvest(
args.out, out_dir,
dangerous=bool(args.dangerous), dangerous=bool(args.dangerous),
include_paths=list(getattr(args, "include_path", []) or []), include_paths=list(getattr(args, "include_path", []) or []),
exclude_paths=list(getattr(args, "exclude_path", []) or []), exclude_paths=list(getattr(args, "exclude_path", []) or []),
@ -747,9 +756,11 @@ def main() -> None:
raise SystemExit(2) raise SystemExit(2)
elif args.cmd == "single-shot": elif args.cmd == "single-shot":
sops_fps = getattr(args, "sops", None) sops_fps = getattr(args, "sops", None)
if remote_host: if args.remote_host:
if sops_fps: if sops_fps:
out_file = _resolve_sops_out_file(args.harvest, hint=remote_host) out_file = _resolve_sops_out_file(
args.harvest, hint=args.remote_host
)
with tempfile.TemporaryDirectory(prefix="enroll-harvest-") as td: with tempfile.TemporaryDirectory(prefix="enroll-harvest-") as td:
tmp_bundle = Path(td) / "bundle" tmp_bundle = Path(td) / "bundle"
tmp_bundle.mkdir(parents=True, exist_ok=True) tmp_bundle.mkdir(parents=True, exist_ok=True)
@ -759,7 +770,7 @@ def main() -> None:
pass pass
remote_harvest( remote_harvest(
local_out_dir=tmp_bundle, local_out_dir=tmp_bundle,
remote_host=remote_host, remote_host=args.remote_host,
remote_port=int(args.remote_port), remote_port=int(args.remote_port),
remote_user=args.remote_user, remote_user=args.remote_user,
dangerous=bool(args.dangerous), dangerous=bool(args.dangerous),
@ -784,11 +795,11 @@ def main() -> None:
harvest_dir = ( harvest_dir = (
Path(args.harvest) Path(args.harvest)
if args.harvest if args.harvest
else new_harvest_cache_dir(hint=remote_host).dir else new_harvest_cache_dir(hint=args.remote_host).dir
) )
remote_harvest( remote_harvest(
local_out_dir=harvest_dir, local_out_dir=harvest_dir,
remote_host=remote_host, remote_host=args.remote_host,
remote_port=int(args.remote_port), remote_port=int(args.remote_port),
remote_user=args.remote_user, remote_user=args.remote_user,
dangerous=bool(args.dangerous), dangerous=bool(args.dangerous),

32
enroll/version.py Normal file
View file

@ -0,0 +1,32 @@
from __future__ import annotations
def get_enroll_version() -> str:
"""
Best-effort version lookup that works when installed via:
- poetry/pip/wheel
- deb/rpm system packages
Falls back to "0+unknown" when running from an unpacked source tree.
"""
try:
from importlib.metadata import (
packages_distributions,
version,
)
except Exception:
# Very old Python or unusual environment
return "unknown"
# Map import package -> dist(s)
dist_names = []
try:
dist_names = (packages_distributions() or {}).get("enroll", []) or []
except Exception:
dist_names = []
# Try mapped dists first, then a reasonable default
for dist in [*dist_names, "enroll"]:
try:
return version(dist)
except Exception:
return "unknown"