Run every command on the build VM over SSH (M1.3) #93
No files matched your search
@@ -233,12 +233,14 @@ class AgentConfig:
|
|||||||
workspace: Path
|
workspace: Path
|
||||||
project: str
|
project: str
|
||||||
llm_base_url: str
|
llm_base_url: str
|
||||||
llm_token: str
|
# Credentials never appear in a repr: a failing test or a log line
|
||||||
|
# would print them.
|
||||||
|
llm_token: str = field(repr=False)
|
||||||
roles: RoleMap
|
roles: RoleMap
|
||||||
matrix: MatrixConfig
|
matrix: MatrixConfig
|
||||||
operators: frozenset[str]
|
operators: frozenset[str]
|
||||||
gitea_url: str
|
gitea_url: str
|
||||||
gitea_token: str
|
gitea_token: str = field(repr=False)
|
||||||
gitea_repo: str
|
gitea_repo: str
|
||||||
gitea_operators: frozenset[str]
|
gitea_operators: frozenset[str]
|
||||||
build_host: BuildHost
|
build_host: BuildHost
|
||||||
@@ -248,7 +250,7 @@ class AgentConfig:
|
|||||||
# (push_user, push_token), or the path to a deploy key (push_key).
|
# (push_user, push_token), or the path to a deploy key (push_key).
|
||||||
push_key: Path | None = None
|
push_key: Path | None = None
|
||||||
push_user: str | None = None
|
push_user: str | None = None
|
||||||
push_token: str | None = None
|
push_token: str | None = field(default=None, repr=False)
|
||||||
max_output_tokens: int = DEFAULT_MAX_OUTPUT_TOKENS
|
max_output_tokens: int = DEFAULT_MAX_OUTPUT_TOKENS
|
||||||
request_timeout_s: float = DEFAULT_REQUEST_TIMEOUT_S
|
request_timeout_s: float = DEFAULT_REQUEST_TIMEOUT_S
|
||||||
tuning: Tuning = field(default_factory=Tuning)
|
tuning: Tuning = field(default_factory=Tuning)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Pull requests are opened from the agent's own host with its Gitea API token
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import shlex
|
import shlex
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from nisse_dev.execution import HostUnavailable, SSHHost
|
from nisse_dev.execution import HostUnavailable, SSHHost
|
||||||
@@ -67,14 +67,15 @@ class HTTPSToken:
|
|||||||
"""A push-only account's token, for HTTPS remotes (D4)."""
|
"""A push-only account's token, for HTTPS remotes (D4)."""
|
||||||
|
|
||||||
username: str
|
username: str
|
||||||
token: str
|
# Never in a repr: a failing test or a log line would print it.
|
||||||
|
token: str = field(repr=False)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SSHKey:
|
class SSHKey:
|
||||||
"""A deploy key's private half, for SSH remotes (D4)."""
|
"""A deploy key's private half, for SSH remotes (D4)."""
|
||||||
|
|
||||||
private_key: str
|
private_key: str = field(repr=False)
|
||||||
|
|
||||||
|
|
||||||
PushCredential = HTTPSToken | SSHKey
|
PushCredential = HTTPSToken | SSHKey
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import re
|
|||||||
import threading
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -40,7 +40,7 @@ class TransportError(RuntimeError):
|
|||||||
class MatrixConfig:
|
class MatrixConfig:
|
||||||
homeserver: str
|
homeserver: str
|
||||||
user_id: str
|
user_id: str
|
||||||
token: str
|
token: str = field(repr=False) # never in a repr or a log line
|
||||||
room_id: str
|
room_id: str
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -360,3 +360,10 @@ def test_a_push_token_needs_its_user_and_excludes_a_key() -> None:
|
|||||||
build(gitea__push_user="pusher")
|
build(gitea__push_user="pusher")
|
||||||
with pytest.raises(ConfigError, match="not both"):
|
with pytest.raises(ConfigError, match="not both"):
|
||||||
build(gitea__push_user="p", gitea__push_token="t", gitea__push_key="~/k")
|
build(gitea__push_user="p", gitea__push_token="t", gitea__push_key="~/k")
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_credential_appears_in_the_config_repr() -> None:
|
||||||
|
config = build(gitea__push_user="pusher", gitea__push_token="${TEST_GITEA_TOKEN}")
|
||||||
|
text = repr(config)
|
||||||
|
for secret in SECRETS.values():
|
||||||
|
assert secret not in text
|
||||||
@@ -135,10 +135,25 @@ def test_a_failed_push_still_kills_its_agent(
|
|||||||
|
|
||||||
|
|
||||||
def _agents() -> set[str]:
|
def _agents() -> set[str]:
|
||||||
|
"""Live ssh-agents. A killed one lingers as a zombie in a CI container,
|
||||||
|
where nothing reaps orphans, so zombies don't count."""
|
||||||
out = subprocess.run(
|
out = subprocess.run(
|
||||||
["pgrep", "-x", "ssh-agent"], capture_output=True, text=True, check=False
|
["pgrep", "-x", "ssh-agent"], capture_output=True, text=True, check=False
|
||||||
).stdout
|
).stdout
|
||||||
return set(out.split())
|
live = set()
|
||||||
|
for pid in out.split():
|
||||||
|
try:
|
||||||
|
stat = Path(f"/proc/{pid}/stat").read_text()
|
||||||
|
except FileNotFoundError:
|
||||||
|
continue
|
||||||
|
if stat.rsplit(")", 1)[1].split()[0] != "Z":
|
||||||
|
live.add(pid)
|
||||||
|
return live
|
||||||
|
|
||||||
|
|
||||||
|
def test_credentials_never_appear_in_a_repr() -> None:
|
||||||
|
assert "s3cr3t" not in repr(HTTPSToken("pusher", "s3cr3t"))
|
||||||
|
assert "PRIVATE" not in repr(SSHKey("-----BEGIN OPENSSH PRIVATE KEY-----"))
|
||||||
|
|
||||||
|
|
||||||
def test_a_remote_url_is_quoted_not_interpreted(repo: GitRepo, tmp_path: Path) -> None:
|
def test_a_remote_url_is_quoted_not_interpreted(repo: GitRepo, tmp_path: Path) -> None:
|
||||||
|
|||||||
Reference in new issue
Block a user