Compare repo and store endpoints in token stats
This commit is contained in:
@@ -99,7 +99,7 @@ Podstawowe komendy tokenow:
|
|||||||
```bash
|
```bash
|
||||||
python3 workspace.py tokens scan
|
python3 workspace.py tokens scan
|
||||||
python3 workspace.py tokens read
|
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
|
## Fetch i switch
|
||||||
|
|||||||
+10
-9
@@ -118,22 +118,23 @@ python3 workspace.py tokens read --show-secrets
|
|||||||
|
|
||||||
### `tokens stats`
|
### `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:
|
Wynik ma ten sam model co `tokens read`, ale w formie zbiorczej:
|
||||||
|
|
||||||
- endpoint
|
- liczbe endpointow w repo
|
||||||
- type
|
- liczbe endpointow w store
|
||||||
- scheme
|
- laczna unie endpointow
|
||||||
- host
|
- statusy zgodnosci, na przyklad `in_sync`, `store_ahead`, `repo_ahead`
|
||||||
- port
|
- tabele endpointow z liczbami tokenow po obu stronach
|
||||||
- users
|
|
||||||
- tokens
|
|
||||||
|
|
||||||
Przyklad:
|
Przyklad:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 workspace.py tokens stats
|
python3 workspace.py tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||||
```
|
```
|
||||||
|
|
||||||
### `tokens write`
|
### `tokens write`
|
||||||
|
|||||||
+19
-4
@@ -283,24 +283,39 @@ python3 workspace.py tokens read --server http://77.90.8.171:3001
|
|||||||
|
|
||||||
## `tokens stats`
|
## `tokens stats`
|
||||||
|
|
||||||
Pokazuje statystyki tokenow per endpoint serwera.
|
Pokazuje statystyki endpointow z repo i `tokens.json`, a takze ich zgodnosc
|
||||||
|
wzgledem siebie.
|
||||||
|
|
||||||
Przelaczniki:
|
Przelaczniki:
|
||||||
|
|
||||||
|
- `--repo PATH`
|
||||||
|
Sciezka wewnatrz repo, z ktorego maja byc odczytane remote URL-e.
|
||||||
- `--server ENDPOINT`
|
- `--server ENDPOINT`
|
||||||
Ogranicza wynik do jednego endpointu.
|
Ogranicza wynik do jednego endpointu.
|
||||||
|
|
||||||
Typowy wynik:
|
Typowy wynik:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
endpoint<TAB>type<TAB>scheme<TAB>host<TAB>port<TAB>users<TAB>tokens
|
repo_root<TAB>...
|
||||||
http://77.90.8.171:3001<TAB>gitea<TAB>http<TAB>77.90.8.171<TAB>3001<TAB>1<TAB>1
|
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:
|
Przyklad:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 workspace.py tokens stats
|
python3 workspace.py tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||||
```
|
```
|
||||||
|
|
||||||
## `tokens write`
|
## `tokens write`
|
||||||
|
|||||||
+149
-23
@@ -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]:
|
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"])
|
remote_output = git_capture(repo_path, ["remote"])
|
||||||
if not remote_output:
|
if not remote_output:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
urls: list[str] = []
|
urls: list[tuple[str, str]] = []
|
||||||
seen: set[str] = set()
|
seen: set[tuple[str, str]] = set()
|
||||||
for remote_name in remote_output.splitlines():
|
for remote_name in remote_output.splitlines():
|
||||||
url_output = git_capture(repo_path, ["remote", "get-url", "--all", remote_name])
|
url_output = git_capture(repo_path, ["remote", "get-url", "--all", remote_name])
|
||||||
if not url_output:
|
if not url_output:
|
||||||
continue
|
continue
|
||||||
for remote_url in url_output.splitlines():
|
for remote_url in url_output.splitlines():
|
||||||
if remote_url in seen:
|
key = (remote_name, remote_url)
|
||||||
|
if key in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(remote_url)
|
seen.add(key)
|
||||||
urls.append(remote_url)
|
urls.append(key)
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
|
||||||
@@ -467,6 +472,89 @@ def token_store_servers(token_data: dict) -> list[tuple[str, dict]]:
|
|||||||
return sorted(servers.items())
|
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]:
|
def resolve_server_from_store(token_data: dict, endpoint: str | None) -> tuple[str, dict]:
|
||||||
servers = token_data.get("servers", {})
|
servers = token_data.get("servers", {})
|
||||||
if endpoint:
|
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:
|
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)
|
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:
|
if args.server:
|
||||||
server_entry = token_data.get("servers", {}).get(args.server)
|
if args.server not in set(endpoint_names):
|
||||||
if server_entry is None:
|
raise SystemExit(f"Missing server endpoint in repo/store: {args.server}")
|
||||||
raise SystemExit(f"Missing server endpoint in token store: {args.server}")
|
endpoint_names = [args.server]
|
||||||
servers = [(args.server, server_entry)]
|
|
||||||
|
|
||||||
print("endpoint\ttype\tscheme\thost\tport\tusers\ttokens")
|
status_names = ["in_sync", "repo_only", "store_only", "repo_ahead", "store_ahead", "diverged"]
|
||||||
for endpoint, server_entry in servers:
|
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(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(
|
print(
|
||||||
"\t".join(
|
"\t".join(row)
|
||||||
[
|
|
||||||
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)),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -979,6 +1104,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
"stats",
|
"stats",
|
||||||
help="Print per-server token statistics from tokens.json.",
|
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_stats_parser.add_argument("--server", help="Filter output to one server endpoint.")
|
||||||
|
|
||||||
tokens_write_parser = tokens_subparsers.add_parser(
|
tokens_write_parser = tokens_subparsers.add_parser(
|
||||||
|
|||||||
Reference in New Issue
Block a user