Show token repo and store rows per item

This commit is contained in:
mpabi
2026-04-26 15:54:09 +02:00
parent f13b09c317
commit b4e638ef4f
4 changed files with 187 additions and 101 deletions
+2 -2
View File
@@ -156,8 +156,8 @@ Podstawowe komendy tokenow:
./rvctl tokens stats --repo ~/dev/workspace/rv/series/inf/03 ./rvctl tokens stats --repo ~/dev/workspace/rv/series/inf/03
``` ```
`tokens scan` wypisuje tabele TSV. Remote bez `LOGIN:TOKEN@` bedzie pokazany `tokens scan` wypisuje waskie tabele TSV. Dla jednego endpointu zobaczysz
jako `url_kind=plain` i `result=no_credentials`. osobny wiersz `source=repo` oraz osobny wiersz `source=tokens.json`.
## Fetch i switch ## Fetch i switch
+26 -20
View File
@@ -301,19 +301,26 @@ Przelaczniki:
Typowy wynik: Typowy wynik:
```text ```text
summary context
repo_root<TAB>remotes<TAB>urls<TAB>scanned<TAB>auth_urls<TAB>plain_urls<TAB>unsupported_urls<TAB>found<TAB>added<TAB>servers<TAB>token_path item<TAB>value
...<TAB>1<TAB>1<TAB>1<TAB>1<TAB>0<TAB>0<TAB>1<TAB>1<TAB>1<TAB>... repo_root<TAB>...
token_path<TAB>...
remotes scan
remote<TAB>endpoint<TAB>type<TAB>url_kind<TAB>user<TAB>result item<TAB>value
r1<TAB>http://77.90.8.171:3001<TAB>gitea<TAB>auth<TAB>u1<TAB>added remotes<TAB>1
urls<TAB>1
items
item<TAB>source<TAB>endpoint<TAB>remote<TAB>kind<TAB>user<TAB>token<TAB>result<TAB>status
1<TAB>repo<TAB>http://77.90.8.171:3001<TAB>r1<TAB>auth<TAB>u1<TAB>remote<TAB>existing<TAB>in_sync
1<TAB>tokens.json<TAB>http://77.90.8.171:3001<TAB><TAB>store<TAB>u1<TAB>t1<TAB>present<TAB>in_sync
``` ```
Jesli remote istnieje, ale ma URL bez `LOGIN:TOKEN@`, wynik bedzie mial Jesli remote istnieje, ale ma URL bez `LOGIN:TOKEN@`, wynik bedzie mial
`plain_urls=1`, `auth_urls=0`, `found=0`, `added=0`, a w tabeli `remotes` w wierszu `repo` wartosci `kind=plain` i `result=no_credentials`. Jezeli
pojawi sie `url_kind=plain` i `result=no_credentials`. To znaczy, ze `scan` ten sam endpoint istnieje w `tokens.json`, drugi wiersz tego samego `item`
sprawdzil remote, ale nie znalazl sekretu do zapisania. pokaze `source=tokens.json`, `token=t1` i `result=present`.
Przyklad: Przyklad:
@@ -370,20 +377,19 @@ Przelaczniki:
Typowy wynik: Typowy wynik:
```text ```text
context
item<TAB>value
repo_root<TAB>... repo_root<TAB>...
token_path<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 items
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 item<TAB>source<TAB>endpoint<TAB>remote<TAB>kind<TAB>user<TAB>token<TAB>result<TAB>status
1<TAB>repo<TAB>http://77.90.8.171:3001<TAB>r1<TAB>plain<TAB><TAB><TAB>no_credentials<TAB>store_ahead
1<TAB>tokens.json<TAB>http://77.90.8.171:3001<TAB><TAB>store<TAB>u1<TAB>t1<TAB>present<TAB>store_ahead
status
item<TAB>value
store_ahead<TAB>1
``` ```
Przyklad: Przyklad:
+18 -9
View File
@@ -84,16 +84,25 @@ Dzialanie:
- skanuje remote URL-e w repo - skanuje remote URL-e w repo
- zapisuje znalezione tokeny do `tokens.json` - zapisuje znalezione tokeny do `tokens.json`
- zapisuje je per endpoint serwera - zapisuje je per endpoint serwera
- wypisuje wynik jako tabele TSV: `summary` oraz `remotes` - wypisuje wynik jako waskie tabele TSV: `context`, `scan` oraz `items`
Kolumny w tabeli `remotes`: Tabela `items` porownuje dwa zrodla dla tego samego endpointu. Ten sam `item`
moze miec dwa wiersze:
- `remote` - nazwa remote, na przyklad `r1` - `source=repo` - stan remote URL-i w repo
- `source=tokens.json` - stan lokalnego store tokenow
Kolumny w tabeli `items`:
- `item` - numer porownywanego endpointu
- `source` - `repo` albo `tokens.json`
- `endpoint` - serwer bez sekretu, na przyklad `http://77.90.8.171:3001` - `endpoint` - serwer bez sekretu, na przyklad `http://77.90.8.171:3001`
- `type` - typ serwera, na przyklad `gitea` - `remote` - nazwa remote, na przyklad `r1`; tylko dla `source=repo`
- `url_kind` - `auth`, `plain` albo `unsupported` - `kind` - `auth`, `plain`, `store` albo `missing`
- `user` - login odczytany z URL, tylko dla `auth` - `user` - login odczytany z URL albo ze store
- `result` - `added`, `existing`, `no_credentials` albo `ignored` - `token` - `remote` dla tokenu w URL albo nazwa tokenu ze store, na przyklad `t1`
- `result` - `added`, `existing`, `present`, `no_credentials` albo `missing`
- `status` - relacja repo do `tokens.json`, na przyklad `in_sync` albo `store_ahead`
Przyklad: Przyklad:
@@ -133,13 +142,13 @@ Pokazuje statystyki per endpoint serwera i porownuje dwa zrodla:
- endpointy znalezione w remote URL-ach repo - endpointy znalezione w remote URL-ach repo
- endpointy zapisane w `tokens.json` - endpointy zapisane w `tokens.json`
Wynik ma ten sam model co `tokens read`, ale w formie zbiorczej: Wynik ma ten sam model porownania co `tokens scan`, ale nie zapisuje zmian:
- liczbe endpointow w repo - liczbe endpointow w repo
- liczbe endpointow w store - liczbe endpointow w store
- laczna unie endpointow - laczna unie endpointow
- statusy zgodnosci, na przyklad `in_sync`, `store_ahead`, `repo_ahead` - statusy zgodnosci, na przyklad `in_sync`, `store_ahead`, `repo_ahead`
- tabele endpointow z liczbami tokenow po obu stronach - tabele `items`, w ktorej repo i `tokens.json` sa osobnymi wierszami tego samego itemu
Przyklad: Przyklad:
+141 -70
View File
@@ -619,6 +619,122 @@ def compare_server_entries(repo_entry: dict | None, store_entry: dict | None) ->
return "diverged", len(repo_only_pairs), len(store_only_pairs) return "diverged", len(repo_only_pairs), len(store_only_pairs)
def token_name_rows(users_data: dict) -> list[tuple[str, str]]:
rows: list[tuple[str, str]] = []
for user_name in sorted(users_data):
user_entry = users_data[user_name]
if not isinstance(user_entry, dict):
continue
tokens = user_entry.get("tokens", {})
if not tokens:
rows.append((str(user_name), ""))
continue
for token_name in sorted(tokens):
rows.append((str(user_name), str(token_name)))
return rows
def repo_result_for_entry(repo_entry: dict | None) -> tuple[str, str]:
if repo_entry is None:
return "missing", "missing"
if repo_entry.get("auth_urls", 0):
return "auth", "present"
if repo_entry.get("plain_urls", 0):
return "plain", "no_credentials"
return "empty", "no_credentials"
def print_token_context(
repo_root: Path | None,
config: WorkspaceConfig,
repo_servers: dict[str, dict],
store_servers: dict[str, dict],
endpoint_count: int,
) -> None:
print("context")
print("item\tvalue")
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"tokens_endpoints\t{len(store_servers)}")
print(f"items\t{endpoint_count}")
def print_status_counts(status_counts: dict[str, int]) -> None:
print("status")
print("item\tvalue")
for status_name in ["in_sync", "repo_only", "store_only", "repo_ahead", "store_ahead", "diverged"]:
print(f"{status_name}\t{status_counts[status_name]}")
def print_token_item_rows(
endpoint_names: list[str],
repo_servers: dict[str, dict],
store_servers: dict[str, dict],
scan_rows: list[dict[str, str]] | None = None,
) -> dict[str, int]:
status_names = ["in_sync", "repo_only", "store_only", "repo_ahead", "store_ahead", "diverged"]
status_counts = {status_name: 0 for status_name in status_names}
scan_rows_by_endpoint: dict[str, list[dict[str, str]]] = {}
for row in scan_rows or []:
endpoint = row.get("endpoint", "")
if endpoint:
scan_rows_by_endpoint.setdefault(endpoint, []).append(row)
print("items")
print("item\tsource\tendpoint\tremote\tkind\tuser\ttoken\tresult\tstatus")
for index, endpoint in enumerate(endpoint_names, start=1):
item_id = str(index)
repo_entry = repo_servers.get(endpoint)
store_entry = store_servers.get(endpoint)
status_name, _, _ = compare_server_entries(repo_entry, store_entry)
if status_name in status_counts:
status_counts[status_name] += 1
repo_rows = scan_rows_by_endpoint.get(endpoint, [])
if repo_rows:
for row in repo_rows:
token_label = "remote" if row["url_kind"] == "auth" else ""
print(
f"{item_id}\trepo\t{endpoint}\t{row['remote']}\t{row['url_kind']}\t"
f"{row['user']}\t{token_label}\t{row['result']}\t{status_name}"
)
elif repo_entry is None:
print(f"{item_id}\trepo\t{endpoint}\t\tmissing\t\t\tmissing\t{status_name}")
else:
repo_kind, repo_result = repo_result_for_entry(repo_entry)
remote_names = ",".join(sorted(repo_entry.get("remotes", set())))
repo_users = token_name_rows(repo_entry.get("users", {}))
if repo_users:
for user_name, _ in repo_users:
print(
f"{item_id}\trepo\t{endpoint}\t{remote_names}\t{repo_kind}\t"
f"{user_name}\tremote\t{repo_result}\t{status_name}"
)
else:
print(
f"{item_id}\trepo\t{endpoint}\t{remote_names}\t{repo_kind}\t\t\t"
f"{repo_result}\t{status_name}"
)
if store_entry is None:
print(f"{item_id}\ttokens.json\t{endpoint}\t\tmissing\t\t\tmissing\t{status_name}")
continue
store_users = token_name_rows(store_entry.get("users", {}))
if not store_users:
print(f"{item_id}\ttokens.json\t{endpoint}\t\tstore\t\t\tempty\t{status_name}")
continue
for user_name, token_name in store_users:
print(
f"{item_id}\ttokens.json\t{endpoint}\t\tstore\t{user_name}\t"
f"{token_name}\tpresent\t{status_name}"
)
return status_counts
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:
@@ -707,27 +823,28 @@ def resolve_repo_argument(repo_arg: str | None) -> Path:
def run_tokens_scan(config: WorkspaceConfig, args: argparse.Namespace) -> None: def run_tokens_scan(config: WorkspaceConfig, args: argparse.Namespace) -> None:
report = sync_tokens_from_repo(config, resolve_repo_argument(args.repo)) repo_path = resolve_repo_argument(args.repo)
repo_root = report["repo_root"] report = sync_tokens_from_repo(config, repo_path)
print("summary") repo_root, repo_servers = collect_repo_server_entries(config, repo_path)
print( token_data = load_token_store(config)
"repo_root\tremotes\turls\tscanned\tauth_urls\tplain_urls\t" store_servers = token_data.get("servers", {})
"unsupported_urls\tfound\tadded\tservers\ttoken_path" endpoint_names = sorted(set(repo_servers) | set(store_servers))
)
print( print_token_context(repo_root, config, repo_servers, store_servers, len(endpoint_names))
f"{repo_root or ''}\t{report['remotes']}\t{report['urls']}\t"
f"{report['scanned']}\t{report['auth_urls']}\t{report['plain_urls']}\t"
f"{report['unsupported_urls']}\t{report['found']}\t{report['added']}\t"
f"{report['servers']}\t{config.token_path}"
)
print() print()
print("remotes") print("scan")
print("remote\tendpoint\ttype\turl_kind\tuser\tresult") print("item\tvalue")
for row in report["remote_rows"]: print(f"remotes\t{report['remotes']}")
print( print(f"urls\t{report['urls']}")
f"{row['remote']}\t{row['endpoint']}\t{row['type']}\t" print(f"scanned\t{report['scanned']}")
f"{row['url_kind']}\t{row['user']}\t{row['result']}" print(f"auth_urls\t{report['auth_urls']}")
) print(f"plain_urls\t{report['plain_urls']}")
print(f"unsupported_urls\t{report['unsupported_urls']}")
print(f"found\t{report['found']}")
print(f"added\t{report['added']}")
print(f"servers\t{report['servers']}")
print()
print_token_item_rows(endpoint_names, repo_servers, store_servers, report["remote_rows"])
def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None: def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
@@ -774,57 +891,11 @@ def run_tokens_stats(config: WorkspaceConfig, args: argparse.Namespace) -> None:
raise SystemExit(f"Missing server endpoint in repo/store: {args.server}") raise SystemExit(f"Missing server endpoint in repo/store: {args.server}")
endpoint_names = [args.server] endpoint_names = [args.server]
status_names = ["in_sync", "repo_only", "store_only", "repo_ahead", "store_ahead", "diverged"] print_token_context(repo_root, config, repo_servers, store_servers, len(endpoint_names))
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()
print( status_counts = print_token_item_rows(endpoint_names, repo_servers, store_servers)
"endpoint\ttype\trepo_remotes\trepo_urls\trepo_auth_urls\trepo_plain_urls\t" print()
"repo_users\trepo_tokens\tstore_users\tstore_tokens\trepo_only_tokens\t" print_status_counts(status_counts)
"store_only_tokens\tstatus"
)
for row in table_rows:
print(
"\t".join(row)
)
def run_tokens_write(config: WorkspaceConfig, args: argparse.Namespace) -> None: def run_tokens_write(config: WorkspaceConfig, args: argparse.Namespace) -> None: