Compare repo and store endpoints in token stats
This commit is contained in:
@@ -99,7 +99,7 @@ Podstawowe komendy tokenow:
|
||||
```bash
|
||||
python3 workspace.py tokens scan
|
||||
python3 workspace.py tokens read
|
||||
python3 workspace.py tokens stats
|
||||
python3 workspace.py tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||
```
|
||||
|
||||
## Fetch i switch
|
||||
|
||||
+10
-9
@@ -118,22 +118,23 @@ python3 workspace.py tokens read --show-secrets
|
||||
|
||||
### `tokens stats`
|
||||
|
||||
Pokazuje statystyki per endpoint serwera.
|
||||
Pokazuje statystyki per endpoint serwera i porownuje dwa zrodla:
|
||||
|
||||
- endpointy znalezione w remote URL-ach repo
|
||||
- endpointy zapisane w `tokens.json`
|
||||
|
||||
Wynik ma ten sam model co `tokens read`, ale w formie zbiorczej:
|
||||
|
||||
- endpoint
|
||||
- type
|
||||
- scheme
|
||||
- host
|
||||
- port
|
||||
- users
|
||||
- tokens
|
||||
- liczbe endpointow w repo
|
||||
- liczbe endpointow w store
|
||||
- laczna unie endpointow
|
||||
- statusy zgodnosci, na przyklad `in_sync`, `store_ahead`, `repo_ahead`
|
||||
- tabele endpointow z liczbami tokenow po obu stronach
|
||||
|
||||
Przyklad:
|
||||
|
||||
```bash
|
||||
python3 workspace.py tokens stats
|
||||
python3 workspace.py tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||
```
|
||||
|
||||
### `tokens write`
|
||||
|
||||
+19
-4
@@ -283,24 +283,39 @@ python3 workspace.py tokens read --server http://77.90.8.171:3001
|
||||
|
||||
## `tokens stats`
|
||||
|
||||
Pokazuje statystyki tokenow per endpoint serwera.
|
||||
Pokazuje statystyki endpointow z repo i `tokens.json`, a takze ich zgodnosc
|
||||
wzgledem siebie.
|
||||
|
||||
Przelaczniki:
|
||||
|
||||
- `--repo PATH`
|
||||
Sciezka wewnatrz repo, z ktorego maja byc odczytane remote URL-e.
|
||||
- `--server ENDPOINT`
|
||||
Ogranicza wynik do jednego endpointu.
|
||||
|
||||
Typowy wynik:
|
||||
|
||||
```text
|
||||
endpoint<TAB>type<TAB>scheme<TAB>host<TAB>port<TAB>users<TAB>tokens
|
||||
http://77.90.8.171:3001<TAB>gitea<TAB>http<TAB>77.90.8.171<TAB>3001<TAB>1<TAB>1
|
||||
repo_root<TAB>...
|
||||
token_path<TAB>...
|
||||
repo_endpoints<TAB>1
|
||||
store_endpoints<TAB>1
|
||||
union_endpoints<TAB>1
|
||||
in_sync<TAB>0
|
||||
repo_only<TAB>0
|
||||
store_only<TAB>0
|
||||
repo_ahead<TAB>0
|
||||
store_ahead<TAB>1
|
||||
diverged<TAB>0
|
||||
|
||||
endpoint<TAB>type<TAB>repo_remotes<TAB>repo_urls<TAB>repo_auth_urls<TAB>repo_plain_urls<TAB>repo_users<TAB>repo_tokens<TAB>store_users<TAB>store_tokens<TAB>repo_only_tokens<TAB>store_only_tokens<TAB>status
|
||||
http://77.90.8.171:3001<TAB>gitea<TAB>origin<TAB>1<TAB>0<TAB>1<TAB>0<TAB>0<TAB>1<TAB>1<TAB>0<TAB>1<TAB>store_ahead
|
||||
```
|
||||
|
||||
Przyklad:
|
||||
|
||||
```bash
|
||||
python3 workspace.py tokens stats
|
||||
python3 workspace.py tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||
```
|
||||
|
||||
## `tokens write`
|
||||
|
||||
+146
-20
@@ -376,21 +376,26 @@ def register_token(token_data: dict, server_info: dict, user_name: str, token_va
|
||||
|
||||
|
||||
def remote_urls(repo_path: Path) -> list[str]:
|
||||
return [remote_url for _, remote_url in remote_name_urls(repo_path)]
|
||||
|
||||
|
||||
def remote_name_urls(repo_path: Path) -> list[tuple[str, str]]:
|
||||
remote_output = git_capture(repo_path, ["remote"])
|
||||
if not remote_output:
|
||||
return []
|
||||
|
||||
urls: list[str] = []
|
||||
seen: set[str] = set()
|
||||
urls: list[tuple[str, str]] = []
|
||||
seen: set[tuple[str, str]] = set()
|
||||
for remote_name in remote_output.splitlines():
|
||||
url_output = git_capture(repo_path, ["remote", "get-url", "--all", remote_name])
|
||||
if not url_output:
|
||||
continue
|
||||
for remote_url in url_output.splitlines():
|
||||
if remote_url in seen:
|
||||
key = (remote_name, remote_url)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(remote_url)
|
||||
urls.append(remote_url)
|
||||
seen.add(key)
|
||||
urls.append(key)
|
||||
return urls
|
||||
|
||||
|
||||
@@ -467,6 +472,89 @@ def token_store_servers(token_data: dict) -> list[tuple[str, dict]]:
|
||||
return sorted(servers.items())
|
||||
|
||||
|
||||
def token_pairs_from_users(users_data: dict) -> set[tuple[str, str]]:
|
||||
token_pairs: set[tuple[str, str]] = set()
|
||||
for user_name, user_entry in users_data.items():
|
||||
if not isinstance(user_entry, dict):
|
||||
continue
|
||||
for token_value in user_entry.get("tokens", {}).values():
|
||||
if isinstance(token_value, str):
|
||||
token_pairs.add((str(user_name), token_value))
|
||||
return token_pairs
|
||||
|
||||
|
||||
def count_users(users_data: dict) -> int:
|
||||
return len(users_data)
|
||||
|
||||
|
||||
def collect_repo_server_entries(config: WorkspaceConfig, repo_path: Path) -> tuple[Path | None, dict[str, dict]]:
|
||||
repo_root = git_repo_root(repo_path)
|
||||
if repo_root is None:
|
||||
return None, {}
|
||||
|
||||
repo_servers: dict[str, dict] = {}
|
||||
for remote_name, remote_url in remote_name_urls(repo_root):
|
||||
server_info = server_info_from_url(config, remote_url)
|
||||
if server_info is None:
|
||||
continue
|
||||
|
||||
endpoint = server_info["endpoint"]
|
||||
entry = repo_servers.setdefault(
|
||||
endpoint,
|
||||
{
|
||||
"type": server_info["type"],
|
||||
"scheme": server_info["scheme"],
|
||||
"host": server_info["host"],
|
||||
"port": server_info["port"],
|
||||
"remotes": set(),
|
||||
"urls": 0,
|
||||
"auth_urls": 0,
|
||||
"plain_urls": 0,
|
||||
"users": {},
|
||||
},
|
||||
)
|
||||
entry["remotes"].add(remote_name)
|
||||
entry["urls"] += 1
|
||||
for field_name in ["type", "scheme", "host", "port"]:
|
||||
if field_name not in entry or entry[field_name] in {"", None, "unknown"}:
|
||||
entry[field_name] = server_info[field_name]
|
||||
|
||||
credentials = remote_credentials(remote_url)
|
||||
if credentials is None:
|
||||
entry["plain_urls"] += 1
|
||||
continue
|
||||
|
||||
entry["auth_urls"] += 1
|
||||
user_name, token_value = credentials
|
||||
user_entry = entry["users"].setdefault(user_name, {"tokens": {}})
|
||||
tokens = user_entry.setdefault("tokens", {})
|
||||
if token_value not in tokens.values():
|
||||
tokens[next_token_name(tokens)] = token_value
|
||||
|
||||
return repo_root, repo_servers
|
||||
|
||||
|
||||
def compare_server_entries(repo_entry: dict | None, store_entry: dict | None) -> tuple[str, int, int]:
|
||||
if repo_entry is None and store_entry is None:
|
||||
return "missing", 0, 0
|
||||
if repo_entry is not None and store_entry is None:
|
||||
return "repo_only", count_server_tokens(repo_entry), 0
|
||||
if repo_entry is None and store_entry is not None:
|
||||
return "store_only", 0, count_server_tokens(store_entry)
|
||||
|
||||
repo_pairs = token_pairs_from_users(repo_entry.get("users", {}))
|
||||
store_pairs = token_pairs_from_users(store_entry.get("users", {}))
|
||||
repo_only_pairs = repo_pairs - store_pairs
|
||||
store_only_pairs = store_pairs - repo_pairs
|
||||
if not repo_only_pairs and not store_only_pairs:
|
||||
return "in_sync", 0, 0
|
||||
if repo_only_pairs and not store_only_pairs:
|
||||
return "repo_ahead", len(repo_only_pairs), 0
|
||||
if store_only_pairs and not repo_only_pairs:
|
||||
return "store_ahead", 0, len(store_only_pairs)
|
||||
return "diverged", len(repo_only_pairs), len(store_only_pairs)
|
||||
|
||||
|
||||
def resolve_server_from_store(token_data: dict, endpoint: str | None) -> tuple[str, dict]:
|
||||
servers = token_data.get("servers", {})
|
||||
if endpoint:
|
||||
@@ -599,28 +687,65 @@ def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||
|
||||
|
||||
def run_tokens_stats(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||
repo_root, repo_servers = collect_repo_server_entries(config, resolve_repo_argument(args.repo))
|
||||
token_data = load_token_store(config)
|
||||
servers = token_store_servers(token_data)
|
||||
store_servers = token_data.get("servers", {})
|
||||
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
||||
if args.server:
|
||||
server_entry = token_data.get("servers", {}).get(args.server)
|
||||
if server_entry is None:
|
||||
raise SystemExit(f"Missing server endpoint in token store: {args.server}")
|
||||
servers = [(args.server, server_entry)]
|
||||
if args.server not in set(endpoint_names):
|
||||
raise SystemExit(f"Missing server endpoint in repo/store: {args.server}")
|
||||
endpoint_names = [args.server]
|
||||
|
||||
print("endpoint\ttype\tscheme\thost\tport\tusers\ttokens")
|
||||
for endpoint, server_entry in servers:
|
||||
print(
|
||||
"\t".join(
|
||||
status_names = ["in_sync", "repo_only", "store_only", "repo_ahead", "store_ahead", "diverged"]
|
||||
status_counts = {status_name: 0 for status_name in status_names}
|
||||
|
||||
print(f"repo_root\t{repo_root or ''}")
|
||||
print(f"token_path\t{config.token_path}")
|
||||
print(f"repo_endpoints\t{len(repo_servers)}")
|
||||
print(f"store_endpoints\t{len(store_servers)}")
|
||||
print(f"union_endpoints\t{len(endpoint_names)}")
|
||||
|
||||
table_rows: list[list[str]] = []
|
||||
for endpoint in endpoint_names:
|
||||
repo_entry = repo_servers.get(endpoint)
|
||||
store_entry = store_servers.get(endpoint)
|
||||
status_name, repo_only_count, store_only_count = compare_server_entries(repo_entry, store_entry)
|
||||
if status_name in status_counts:
|
||||
status_counts[status_name] += 1
|
||||
|
||||
sample_entry = repo_entry or store_entry or {}
|
||||
repo_users = repo_entry.get("users", {}) if repo_entry else {}
|
||||
store_users = store_entry.get("users", {}) if store_entry else {}
|
||||
table_rows.append(
|
||||
[
|
||||
endpoint,
|
||||
str(server_entry.get("type", "unknown")),
|
||||
str(server_entry.get("scheme", "")),
|
||||
str(server_entry.get("host", "")),
|
||||
str(server_entry.get("port", "")),
|
||||
str(len(server_entry.get("users", {}))),
|
||||
str(count_server_tokens(server_entry)),
|
||||
str(sample_entry.get("type", "unknown")),
|
||||
",".join(sorted(repo_entry.get("remotes", set()))) if repo_entry else "",
|
||||
str(repo_entry.get("urls", 0) if repo_entry else 0),
|
||||
str(repo_entry.get("auth_urls", 0) if repo_entry else 0),
|
||||
str(repo_entry.get("plain_urls", 0) if repo_entry else 0),
|
||||
str(count_users(repo_users)),
|
||||
str(count_server_tokens(repo_entry) if repo_entry else 0),
|
||||
str(count_users(store_users)),
|
||||
str(count_server_tokens(store_entry) if store_entry else 0),
|
||||
str(repo_only_count),
|
||||
str(store_only_count),
|
||||
status_name,
|
||||
]
|
||||
)
|
||||
|
||||
for status_name in status_names:
|
||||
print(f"{status_name}\t{status_counts[status_name]}")
|
||||
|
||||
print()
|
||||
print(
|
||||
"endpoint\ttype\trepo_remotes\trepo_urls\trepo_auth_urls\trepo_plain_urls\t"
|
||||
"repo_users\trepo_tokens\tstore_users\tstore_tokens\trepo_only_tokens\t"
|
||||
"store_only_tokens\tstatus"
|
||||
)
|
||||
for row in table_rows:
|
||||
print(
|
||||
"\t".join(row)
|
||||
)
|
||||
|
||||
|
||||
@@ -979,6 +1104,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
"stats",
|
||||
help="Print per-server token statistics from tokens.json.",
|
||||
)
|
||||
add_repo_option(tokens_stats_parser)
|
||||
tokens_stats_parser.add_argument("--server", help="Filter output to one server endpoint.")
|
||||
|
||||
tokens_write_parser = tokens_subparsers.add_parser(
|
||||
|
||||
Reference in New Issue
Block a user