"""Argument parsing and top-level orchestration of STAGE 1-4. Mirrors the bash original's literal top-to-bottom execution order: banner and the "STAGE 1: Collecting information" header print before anything else, then argument parsing, then mode dispatch. The `openstack` CLI binary check that used to run here is gone -- OpenStack access is now direct Neutron/Keystone REST calls (`keystone_auth.py`, `neutron_client.py`), so no external binary is required at all. """ from __future__ import annotations import argparse import os import sys from . import audit, keystone_auth, sync from .constants import BANNER, SPRUT_API_BASE, USAGE from .sprut_client import SprutClient def print_banner() -> None: print(BANNER) def print_usage() -> None: print(USAGE) def _rule() -> None: print("*" * 86) def parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--dry-run", action="store_true", dest="dry_run") parser.add_argument("--audit-only", action="store_true", dest="audit_only") parser.add_argument("--from-audit", dest="from_audit", default=None) parser.add_argument("-h", "--help", action="store_true", dest="help") parser.add_argument("positional", nargs="*") args = parser.parse_args(argv) if args.help: print_usage() sys.exit(0) audit_only_seen = args.audit_only from_audit_seen = args.from_audit is not None if audit_only_seen and from_audit_seen: print("Error: --audit-only and --from-audit are mutually exclusive.", file=sys.stderr) sys.exit(1) if from_audit_seen: mode = "from-audit" elif audit_only_seen: mode = "audit-only" else: mode = "full" input_csv = None output_file = None if mode == "from-audit": if not args.from_audit: print("Error: --from-audit requires a path to an audit JSON file.", file=sys.stderr) print_usage() sys.exit(1) if not os.path.isfile(args.from_audit): print(f"Error: audit file '{args.from_audit}' not found.", file=sys.stderr) sys.exit(1) else: if not args.positional: print("Error: No input file provided.") print_usage() sys.exit(1) input_csv = args.positional[0] if len(args.positional) > 1: output_file = args.positional[1] return argparse.Namespace( mode=mode, dry_run=args.dry_run, from_audit=args.from_audit, input_csv=input_csv, output_file=output_file, ) def _default_output_file() -> str: from datetime import datetime return f"vpnaas_audit_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" def main(argv: list[str] | None = None) -> int: # Python buffers stdout in blocks (not line-by-line) whenever it isn't a # TTY, while stderr stays unbuffered -- under redirection (e.g. `2>&1 | # tee log`) that visibly reorders stdout/stderr lines relative to each # other. Bash's `echo` has no such issue (each call is its own unbuffered # write), so line-buffer stdout here to keep interleaving faithful. sys.stdout.reconfigure(line_buffering=True) print_banner() print("{====================STAGE 1: Collecting information====================}") print() print() args = parse_args(argv if argv is not None else sys.argv[1:]) if args.dry_run: print("Running in --dry-run mode: no objects will be created in Sprut.") if args.mode == "from-audit": state = audit.load_from_audit_json(args.from_audit) token, _catalog = keystone_auth.authenticate() sprut_client = SprutClient(SPRUT_API_BASE, token, args.dry_run) audit.recheck_advanced_routers_from_audit(state, sprut_client) else: state, sprut_client = audit.collect( args.input_csv, SPRUT_API_BASE, args.dry_run, audit_only=(args.mode == "audit-only") ) output_file = args.output_file or _default_output_file() print(f"Executing STEP 10: Writing audit JSON to {output_file}") audit_data = audit.to_audit_dict(state, args.input_csv) audit.write_audit_json(audit_data, output_file) print("STEP 10 complete: audit JSON exported") _rule() print("{====================STAGE 1: COMPLETE====================}") print() if args.mode == "audit-only": print("Mode --audit-only: stopping after STAGE 1 (Sprut configuration stages skipped).") return 0 print("{====================STAGE 2: Collecting Existing Sprut Objects====================}") sprut_state = sync.collect_sprut_state(sprut_client) print("STAGE 2 complete: Sprut objects collected") _rule() print("{====================STAGE 3: Comparing and Creating Missing Objects in Sprut====================}") correspondence = sync.compare_and_create_all(state, sprut_client, sprut_state) print("STAGE 3 complete: Missing Sprut objects created") _rule() print("{====================STAGE 4: Creating IPsec Site Connections in Sprut====================}") sync.create_ipsec_site_connections(state, sprut_client, sprut_state, correspondence) print("STAGE 4 complete: IPsec site connections created in Sprut") _rule() return 0