Sync tokens.json from remote URLs

This commit is contained in:
mpabi
2026-04-26 10:13:29 +02:00
parent b4cb23ef4d
commit a0930de5e1
4 changed files with 151 additions and 10 deletions
+9 -5
View File
@@ -30,7 +30,8 @@ Masz dwie drogi.
### Droga 1: remote `r1` z tokenem w URL
To jest wariant dydaktyczny, jesli uczen ma cwiczyc reczne dodawanie remota z
tokenem do konkretnego zdalnego endpointu.
tokenem do konkretnego zdalnego endpointu. Jesli launcher zobaczy URL w formacie
`http://LOGIN:TOKEN@...`, zapisze ten token lokalnie do `tokens/tokens.json`.
Przyklad:
@@ -40,7 +41,7 @@ git fetch r1 main
git switch --track -c main r1/main
```
### Droga 2: lokalny `tokens/gitea_tokens.json`
### Droga 2: lokalny `tokens/tokens.json`
To jest wariant alternatywny, wygodniejszy wtedy, gdy launcher ma sam wykonywac
`clone`, `fetch` i `push`, albo gdy uzytkownik po zajeciach chce pracowac juz na
@@ -49,7 +50,7 @@ wlasnych repo bez wpisywania tokena do kazdego remota.
Przed operacjami wymagajacymi autoryzacji dodaj lokalny token do:
```bash
~/dev/workspace/rv/tokens/gitea_tokens.json
~/dev/workspace/rv/tokens/tokens.json
```
Minimalny format:
@@ -73,16 +74,19 @@ Przyklad:
```bash
mkdir -p ~/dev/workspace/rv/tokens
chmod 700 ~/dev/workspace/rv/tokens
chmod 600 ~/dev/workspace/rv/tokens/gitea_tokens.json
chmod 600 ~/dev/workspace/rv/tokens/tokens.json
```
Jesli token jest trzymany tylko w `tokens/gitea_tokens.json`, remote `r1` moze
Jesli token jest trzymany tylko w `tokens/tokens.json`, remote `r1` moze
byc zapisany bez sekretu:
```bash
git remote add r1 http://77.90.8.171:3001/edu-tools/rv-launcher.git
```
Launcher umie tez przemigrowac stary plik `tokens/gitea_tokens.json` do nowego
`tokens/tokens.json`.
## Fetch i switch
Domyslna galaz launchera to `main`.
+10 -5
View File
@@ -32,7 +32,8 @@ Masz dwie drogi.
#### Droga 1: remote `r1` z tokenem w URL
To jest wariant dydaktyczny, jesli uczen ma cwiczyc reczne dodawanie remota z
tokenem do zdalnego endpointu.
tokenem do zdalnego endpointu. Jesli launcher zobaczy URL w formacie
`http://LOGIN:TOKEN@...`, zapisze ten token lokalnie do `tokens/tokens.json`.
Przyklad:
@@ -42,12 +43,12 @@ git fetch r1 main
git switch --track -c main r1/main
```
#### Droga 2: lokalny `tokens/gitea_tokens.json`
#### Droga 2: lokalny `tokens/tokens.json`
Przed operacjami wymagajacymi autoryzacji dodaj lokalny token do:
```bash
~/dev/workspace/rv/tokens/gitea_tokens.json
~/dev/workspace/rv/tokens/tokens.json
```
Minimalny format pliku:
@@ -71,14 +72,17 @@ Przyklad:
```bash
mkdir -p ~/dev/workspace/rv/tokens
chmod 700 ~/dev/workspace/rv/tokens
chmod 600 ~/dev/workspace/rv/tokens/gitea_tokens.json
chmod 600 ~/dev/workspace/rv/tokens/tokens.json
```
Wariant z `tokens/gitea_tokens.json` jest wygodniejszy wtedy, gdy launcher ma
Wariant z `tokens/tokens.json` jest wygodniejszy wtedy, gdy launcher ma
sam wykonywac `clone`, `fetch` i `push`, albo gdy uzytkownik po zajeciach chce
pracowac z wieloma repo na swoim koncie bez wpisywania tokena do kazdego
remota.
Launcher umie tez przemigrowac stary plik `tokens/gitea_tokens.json` do nowego
`tokens/tokens.json`.
### Fetch i switch
Domyslna galaz launchera to `main`.
@@ -143,6 +147,7 @@ config_path<TAB>...
workspace_root<TAB>...
series_root<TAB>...
socket_root<TAB>...
token_path<TAB>...
tools_root<TAB>...
git_base_url<TAB>...
git_source_org<TAB>...
+1
View File
@@ -3,6 +3,7 @@
"workspace_root": "~/dev/workspace/rv",
"series_root": "~/dev/workspace/rv/series",
"socket_root": "~/dev/workspace/rv/sockets",
"token_file": "~/dev/workspace/rv/tokens/tokens.json",
"tools_root_candidates": [
"~/dev/workspace/tools/rv32i-hazard3-env",
"~/dev/workspace/rv/tools/rv32i-hazard3-env"
+131
View File
@@ -11,6 +11,7 @@ import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import unquote, urlsplit
def expand_path(raw_path: str, base_dir: Path) -> Path:
@@ -38,6 +39,8 @@ class WorkspaceConfig:
workspace_root: Path
series_root: Path
socket_root: Path
token_path: Path
legacy_token_path: Path
tools_root_candidates: list[Path]
tools_root: Path
defaults: dict[str, str]
@@ -51,6 +54,11 @@ def load_config(config_path: Path) -> WorkspaceConfig:
workspace_root = expand_path(raw.get("workspace_root", "~/dev/workspace/rv"), base_dir)
series_root = expand_path(raw.get("series_root", str(workspace_root / "series")), base_dir)
socket_root = expand_path(raw.get("socket_root", str(workspace_root / "sockets")), base_dir)
token_path = expand_path(raw.get("token_file", str(workspace_root / "tokens" / "tokens.json")), base_dir)
legacy_token_path = expand_path(
raw.get("legacy_token_file", str(workspace_root / "tokens" / "gitea_tokens.json")),
base_dir,
)
candidate_values = raw.get("tools_root_candidates")
if not candidate_values:
@@ -75,6 +83,8 @@ def load_config(config_path: Path) -> WorkspaceConfig:
workspace_root=workspace_root,
series_root=series_root,
socket_root=socket_root,
token_path=token_path,
legacy_token_path=legacy_token_path,
tools_root_candidates=tools_root_candidates,
tools_root=tools_root,
defaults=defaults,
@@ -183,6 +193,124 @@ def git_capture(repo_path: Path, git_args: list[str]) -> str | None:
return result.stdout.strip()
def git_repo_root(repo_path: Path) -> Path | None:
repo_root = git_capture(repo_path, ["rev-parse", "--show-toplevel"])
if not repo_root:
return None
return Path(repo_root)
def migrate_legacy_token_store(config: WorkspaceConfig) -> None:
if config.token_path.exists() or not config.legacy_token_path.exists():
return
config.token_path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(config.token_path.parent, 0o700)
config.legacy_token_path.replace(config.token_path)
os.chmod(config.token_path, 0o600)
def load_token_store(config: WorkspaceConfig) -> dict:
migrate_legacy_token_store(config)
if not config.token_path.exists():
return {"users": {}}
raw_data = json.loads(config.token_path.read_text(encoding="utf-8"))
if not isinstance(raw_data, dict):
raise SystemExit(f"Invalid token store format: {config.token_path}")
raw_data.setdefault("users", {})
return raw_data
def write_token_store(config: WorkspaceConfig, token_data: dict) -> None:
config.token_path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(config.token_path.parent, 0o700)
temp_path = config.token_path.with_suffix(config.token_path.suffix + ".tmp")
temp_path.write_text(json.dumps(token_data, indent=2, sort_keys=True) + "\n", encoding="utf-8")
os.chmod(temp_path, 0o600)
temp_path.replace(config.token_path)
os.chmod(config.token_path, 0o600)
def next_token_name(tokens: dict[str, str]) -> str:
index = 1
while f"t{index}" in tokens:
index += 1
return f"t{index}"
def register_token(token_data: dict, user_name: str, token_value: str) -> bool:
users = token_data.setdefault("users", {})
user_entry = users.setdefault(user_name, {})
tokens = user_entry.setdefault("tokens", {})
for existing_token in tokens.values():
if existing_token == token_value:
return False
tokens[next_token_name(tokens)] = token_value
return True
def remote_urls(repo_path: Path) -> list[str]:
remote_output = git_capture(repo_path, ["remote"])
if not remote_output:
return []
urls: list[str] = []
seen: set[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:
continue
seen.add(remote_url)
urls.append(remote_url)
return urls
def remote_credentials(remote_url: str) -> tuple[str, str] | None:
split_url = urlsplit(remote_url)
if split_url.scheme not in {"http", "https"}:
return None
if split_url.username is None or split_url.password is None:
return None
return unquote(split_url.username), unquote(split_url.password)
def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> bool:
repo_root = git_repo_root(repo_path)
if repo_root is None:
migrate_legacy_token_store(config)
return False
found_credentials: list[tuple[str, str]] = []
for remote_url in remote_urls(repo_root):
credentials = remote_credentials(remote_url)
if credentials is not None:
found_credentials.append(credentials)
if not found_credentials:
migrate_legacy_token_store(config)
return False
token_data = load_token_store(config)
changed = False
for user_name, token_value in found_credentials:
if register_token(token_data, user_name, token_value):
changed = True
if changed or not config.token_path.exists():
write_token_store(config, token_data)
return changed
def git_current_branch(config: WorkspaceConfig, repo_path: Path) -> str:
branch_name = git_capture(repo_path, ["branch", "--show-current"])
if branch_name:
@@ -253,6 +381,7 @@ def print_submission_plan(config: WorkspaceConfig, args: argparse.Namespace) ->
series_name, card_name = resolve_selector(config, args.series, args.card)
selector = f"{series_name}/{card_name}"
card_path = resolve_card_path(config, series_name, card_name)
sync_tokens_from_repo(config, card_path)
class_name = slug_value(args.class_name, "class")
branch_name = slug_value(args.branch or args.nick, "branch")
source_url = args.source_url or discover_source_url(config, card_path, series_name, card_name)
@@ -376,6 +505,7 @@ def print_config(config: WorkspaceConfig) -> None:
print(f"workspace_root\t{config.workspace_root}")
print(f"series_root\t{config.series_root}")
print(f"socket_root\t{config.socket_root}")
print(f"token_path\t{config.token_path}")
print(f"tools_root\t{config.tools_root}")
print(f"git_base_url\t{config.git.base_url}")
print(f"git_source_org\t{config.git.source_org}")
@@ -452,6 +582,7 @@ def main() -> int:
parser = build_parser()
args = parser.parse_args()
config = load_config(args.config)
sync_tokens_from_repo(config, Path.cwd())
if args.command == "show-config":
print_config(config)