Skip to content

Commit 83edc00

Browse files
committed
feat(infrahubctl): add telemetry commands
Signed-off-by: Fatih Acar <fatih@opsmill.com>
1 parent 0629c5a commit 83edc00

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# `infrahubctl telemetry`
2+
3+
**Usage**:
4+
5+
```console
6+
$ infrahubctl telemetry [OPTIONS] COMMAND [ARGS]...
7+
```
8+
9+
**Options**:
10+
11+
* `--install-completion`: Install completion for the current shell.
12+
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
13+
* `--help`: Show this message and exit.
14+
15+
**Commands**:
16+
17+
* `export`: Export telemetry snapshots to a JSON file.
18+
* `list`: List telemetry snapshots with summary...
19+
20+
## `infrahubctl telemetry export`
21+
22+
Export telemetry snapshots to a JSON file.
23+
24+
Pages through the API automatically so that all matching snapshots are exported,
25+
not just the first page.
26+
27+
**Usage**:
28+
29+
```console
30+
$ infrahubctl telemetry export [OPTIONS]
31+
```
32+
33+
**Options**:
34+
35+
* `--output TEXT`: Output file path [default: telemetry-export.json]
36+
* `--start-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: Start date filter (ISO 8601)
37+
* `--end-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: End date filter (ISO 8601)
38+
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
39+
* `--help`: Show this message and exit.
40+
41+
## `infrahubctl telemetry list`
42+
43+
List telemetry snapshots with summary information.
44+
45+
**Usage**:
46+
47+
```console
48+
$ infrahubctl telemetry list [OPTIONS]
49+
```
50+
51+
**Options**:
52+
53+
* `--start-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: Start date filter (ISO 8601)
54+
* `--end-date [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%dT%H:%M:%S%z]`: End date filter (ISO 8601)
55+
* `--limit INTEGER`: Maximum number of results [default: 50]
56+
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
57+
* `--help`: Show this message and exit.

infrahub_sdk/ctl/cli_commands.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from ..ctl.repository import find_repository_config_file, get_repository_config
3636
from ..ctl.schema import app as schema_app
3737
from ..ctl.task import app as task_app
38+
from ..ctl.telemetry import app as telemetry_app
3839
from ..ctl.transform import list_transforms
3940
from ..ctl.utils import (
4041
catch_exception,
@@ -69,6 +70,7 @@
6970
app.add_typer(object_app, name="object")
7071
app.add_typer(graphql_app, name="graphql")
7172
app.add_typer(task_app, name="task")
73+
app.add_typer(telemetry_app, name="telemetry")
7274

7375
app.command(name="dump")(dump)
7476
app.command(name="load")(load)

infrahub_sdk/ctl/telemetry.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from datetime import datetime
5+
from pathlib import Path
6+
from typing import Any
7+
from urllib.parse import urlencode
8+
9+
import typer
10+
from rich.console import Console
11+
from rich.table import Table
12+
13+
from ..async_typer import AsyncTyper
14+
from .client import initialize_client
15+
from .parameters import CONFIG_PARAM
16+
from .utils import catch_exception
17+
18+
app = AsyncTyper()
19+
console = Console()
20+
21+
EXPORT_PAGE_SIZE = 1000
22+
23+
24+
@app.command(name="list")
25+
@catch_exception(console=console)
26+
async def list_snapshots(
27+
start_date: datetime | None = typer.Option(
28+
None, help="Start date filter (ISO 8601)", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"]
29+
),
30+
end_date: datetime | None = typer.Option(
31+
None, help="End date filter (ISO 8601)", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"]
32+
),
33+
limit: int = typer.Option(50, help="Maximum number of results"),
34+
_: str = CONFIG_PARAM,
35+
) -> None:
36+
"""List telemetry snapshots with summary information."""
37+
client = initialize_client()
38+
39+
params: dict[str, str | int] = {"limit": limit}
40+
if start_date:
41+
params["start_date"] = start_date.isoformat()
42+
if end_date:
43+
params["end_date"] = end_date.isoformat()
44+
45+
url = f"{client.address}/api/telemetry/snapshots?{urlencode(params)}"
46+
response = await client._get(url=url, timeout=client.default_timeout)
47+
response.raise_for_status()
48+
data = response.json()
49+
50+
snapshots = data.get("snapshots", [])
51+
if not snapshots:
52+
console.print("No telemetry snapshots found.")
53+
return
54+
55+
table = Table()
56+
table.add_column("Date")
57+
table.add_column("Version")
58+
table.add_column("Type")
59+
table.add_column("Deployment")
60+
table.add_column("Remote Status")
61+
62+
for snap in snapshots:
63+
table.add_row(
64+
snap.get("created_at", ""),
65+
snap.get("infrahub_version", ""),
66+
snap.get("kind", ""),
67+
snap.get("deployment_id", ""),
68+
snap.get("remote_send_status", ""),
69+
)
70+
71+
console.print(table)
72+
console.print(f"Showing {len(snapshots)} of {data.get('count', len(snapshots))} total snapshots")
73+
74+
75+
@app.command(name="export")
76+
@catch_exception(console=console)
77+
async def export_snapshots(
78+
output: str = typer.Option("telemetry-export.json", help="Output file path"),
79+
start_date: datetime | None = typer.Option(
80+
None, help="Start date filter (ISO 8601)", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"]
81+
),
82+
end_date: datetime | None = typer.Option(
83+
None, help="End date filter (ISO 8601)", formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S%z"]
84+
),
85+
_: str = CONFIG_PARAM,
86+
) -> None:
87+
"""Export telemetry snapshots to a JSON file.
88+
89+
Pages through the API automatically so that all matching snapshots are exported,
90+
not just the first page.
91+
"""
92+
client = initialize_client()
93+
94+
base_params: dict[str, str | int] = {}
95+
if start_date:
96+
base_params["start_date"] = start_date.isoformat()
97+
if end_date:
98+
base_params["end_date"] = end_date.isoformat()
99+
100+
snapshots: list[dict[str, Any]] = []
101+
offset = 0
102+
total: int | None = None
103+
104+
while True:
105+
params: dict[str, str | int] = {**base_params, "limit": EXPORT_PAGE_SIZE, "offset": offset}
106+
url = f"{client.address}/api/telemetry/snapshots?{urlencode(params)}"
107+
response = await client._get(url=url, timeout=client.default_timeout)
108+
response.raise_for_status()
109+
data = response.json()
110+
111+
page: list[dict[str, Any]] = data.get("snapshots", [])
112+
snapshots.extend(page)
113+
114+
if total is None:
115+
total = int(data.get("count", len(page)))
116+
117+
if len(page) < EXPORT_PAGE_SIZE or len(snapshots) >= total:
118+
break
119+
120+
offset += EXPORT_PAGE_SIZE
121+
122+
if not snapshots:
123+
console.print("No telemetry snapshots found.")
124+
raise typer.Exit(code=2)
125+
126+
output_path = Path(output)
127+
output_path.write_text(json.dumps(snapshots, indent=2), encoding="utf-8")
128+
console.print(f"Exported {len(snapshots)} snapshots to {output_path}")

0 commit comments

Comments
 (0)