impreza-cli 0.3.0__py3-none-any.whl

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.
@@ -0,0 +1,14 @@
1
+ """Impreza Host CLI — official command-line interface for the
2
+ Impreza Host public REST API.
3
+
4
+ Phase 2.1 ships the multi-context machinery (config file at
5
+ ``$XDG_CONFIG_HOME/impreza/config.toml`` on Linux/macOS,
6
+ ``%APPDATA%\\impreza\\config.toml`` on Windows) and the
7
+ ``impreza context`` subcommand surface. Subsequent fases (2.2+)
8
+ add the read-only resource commands on top.
9
+
10
+ Built on top of :mod:`impreza` (the SDK), so the network layer,
11
+ auth, retry, and error handling are inherited.
12
+ """
13
+
14
+ __version__ = "0.1.0a0"
@@ -0,0 +1,7 @@
1
+ """Subcommand modules — one per resource group.
2
+
3
+ Each module exposes an ``app`` (a ``typer.Typer`` instance) that
4
+ ``impreza_cli.main`` mounts under the right command name. Phase 2.1
5
+ ships :mod:`.context`. Subsequent fases add ``account``, ``catalog``,
6
+ ``domain``, ``vps``, ``invoice``, ``key``, etc.
7
+ """
@@ -0,0 +1,128 @@
1
+ """Shared helpers used across CLI commands.
2
+
3
+ Originally introduced as ``_vps_helpers.py`` in Phase 3.4 to break
4
+ a circular-import between ``commands/vps.py`` and the per-backend
5
+ sub-resource modules. Renamed to ``_helpers.py`` in Phase 4.3
6
+ because the helpers are not VPS-specific — every command that
7
+ catches :class:`~impreza.exceptions.ApiError` benefits from
8
+ routing through :func:`exit_on_api_error`.
9
+
10
+ Conventions kept consistent across every CLI command:
11
+
12
+ * :func:`exit_on_api_error` — the canonical mapping of
13
+ :class:`~impreza.exceptions.ApiError` to a single stderr line plus
14
+ ``raise typer.Exit(1)``. The 5 commands that grew their own local
15
+ copy during Phase 2 (account, catalog, domain, invoice, key)
16
+ consolidated onto this single import in 4.3.
17
+ * :func:`resolve_vps_or_exit` — bound-model lookup that maps
18
+ ``ResourceNotFound`` and "service exists but is not a VPS"
19
+ (:class:`InvalidRequest`) to the same friendly stderr lines the
20
+ 3.2 power verbs introduced. VPS-only by design.
21
+ * :func:`wait_for_operation` — the dotted-progress poll loop used
22
+ by reinstall / migrate (3.3) and by snapshots-rollback / backups-
23
+ create / backups-restore (3.4 onwards). Reimplements
24
+ ``op.wait()`` because the SDK helper is silent — for long-running
25
+ operations, silence reads as a hang.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import time
31
+ from typing import Any
32
+
33
+ import typer
34
+ from impreza import Operation, Vps
35
+ from impreza.exceptions import ApiError, InvalidRequest, ResourceNotFound
36
+
37
+ from ..output import error
38
+
39
+
40
+ def exit_on_api_error(exc: ApiError) -> None:
41
+ """Render a one-line error from an SDK :class:`ApiError` to
42
+ stderr and ``raise typer.Exit(1)``.
43
+
44
+ Includes the upstream ``code`` and request id when present so
45
+ bug reports can be triaged without re-running with ``--debug``.
46
+ Never returns — the ``raise`` is part of the contract.
47
+ """
48
+ parts = [exc.message]
49
+ if exc.code:
50
+ parts.append(f"(code={exc.code})")
51
+ if exc.request_id:
52
+ parts.append(f"[request_id={exc.request_id}]")
53
+ error(" ".join(parts))
54
+ raise typer.Exit(code=1)
55
+
56
+
57
+ def resolve_vps_or_exit(client: Any, service_id: int) -> Vps:
58
+ """Look up a VPS by id, mapping the standard SDK errors to the
59
+ same friendly stderr lines the 3.2 power verbs introduced.
60
+
61
+ Returns the bound :class:`~impreza.Vps` model (``VpsProxmox`` or
62
+ ``VpsCloud``). Callers can then dispatch to the right SDK method
63
+ without caring which backend they're on.
64
+ """
65
+ try:
66
+ return client.vps.get(service_id)
67
+ except ResourceNotFound:
68
+ error(f"VPS service {service_id} not found on this account.")
69
+ raise typer.Exit(code=1) from None
70
+ except InvalidRequest as exc:
71
+ # "Service exists but isn't a VPS" — the SDK message is
72
+ # already friendly, pass it through.
73
+ exit_on_api_error(exc)
74
+ raise # unreachable — exit_on_api_error always raises
75
+
76
+
77
+ def wait_for_operation(
78
+ op: Operation,
79
+ *,
80
+ label: str,
81
+ timeout: int,
82
+ poll_interval: float = 2.0,
83
+ ) -> None:
84
+ """Block on an :class:`Operation` future, printing one dot per
85
+ poll cycle so the user gets visible feedback during long
86
+ upstream queue runs.
87
+
88
+ Reimplements ``op.wait()`` rather than calling it because the
89
+ SDK helper is silent — for an operation that can run minutes
90
+ (reinstall, migrate, backup create/restore, snapshot rollback),
91
+ silence reads as a hang. Maps the SDK's
92
+ :class:`OperationFailed` / :class:`OperationTimeout` paths to
93
+ clean CLI errors with a remediation hint pointing at
94
+ ``--timeout``.
95
+ """
96
+ typer.echo(f"{label} (operation {op.uuid[:12]}) ", nl=False)
97
+ elapsed = 0.0
98
+ while not op.is_done():
99
+ if elapsed >= timeout:
100
+ typer.echo()
101
+ error(
102
+ f"Operation {op.uuid} did not finish within "
103
+ f"{timeout}s (last status: {op.status!r}). "
104
+ "Re-run with a larger --timeout to wait longer."
105
+ )
106
+ raise typer.Exit(code=1)
107
+ typer.echo(".", nl=False)
108
+ time.sleep(poll_interval)
109
+ elapsed += poll_interval
110
+ try:
111
+ op.refresh()
112
+ except ApiError as exc:
113
+ typer.echo()
114
+ exit_on_api_error(exc)
115
+ typer.echo(" done.")
116
+ if op.is_failure():
117
+ msg = f"Operation {op.uuid} ended in {op.status!r}"
118
+ if op.error:
119
+ msg += f" — {op.error}"
120
+ error(msg + ".")
121
+ raise typer.Exit(code=1)
122
+
123
+
124
+ __all__ = [
125
+ "exit_on_api_error",
126
+ "resolve_vps_or_exit",
127
+ "wait_for_operation",
128
+ ]