fix deploy: Gitea login_name, WP token from DB, pin infra images
- GiteaClient.change_admin_password: add required login_name and source_id fields (was returning 422 Unprocessable Entity) - provision_woodpecker: read WP API token from woodpecker DB hash instead of using Gitea token (Woodpecker uses Gitea OAuth, its API requires the user hash not a Gitea API token) - Pin fhirworx/rustfs and fhirworx/postgresql to :latest — these aren't built by the pipeline so COMMIT_SHA tag doesn't exist
This commit is contained in:
@@ -58,7 +58,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
rustfs:
|
||||
image: fhirworx/rustfs:${COMMIT_SHA:-latest}
|
||||
image: fhirworx/rustfs:latest
|
||||
container_name: rustfs
|
||||
user: "10001:10001"
|
||||
networks:
|
||||
@@ -98,7 +98,7 @@ services:
|
||||
restart: unless-stopped
|
||||
|
||||
postgres:
|
||||
image: fhirworx/postgresql:${COMMIT_SHA:-latest}
|
||||
image: fhirworx/postgresql:latest
|
||||
container_name: postgres
|
||||
networks:
|
||||
- storage
|
||||
|
||||
@@ -264,6 +264,41 @@ WOODPECKER_SECRET_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def _get_woodpecker_token(
|
||||
values: dict[str, str], *, container: str = "postgres"
|
||||
) -> str:
|
||||
"""Read Woodpecker user API token from its database.
|
||||
|
||||
Woodpecker authenticates via Gitea OAuth. Its REST API uses the
|
||||
user's `hash` field from the woodpecker DB, not a Gitea API token.
|
||||
"""
|
||||
wp_db_pw = values.get("WOODPECKER_DB_PASSWORD", "")
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"exec",
|
||||
"-e",
|
||||
f"PGPASSWORD={wp_db_pw}",
|
||||
container,
|
||||
"psql",
|
||||
"-U",
|
||||
"woodpecker",
|
||||
"-d",
|
||||
"woodpecker",
|
||||
"-t",
|
||||
"-A",
|
||||
"-c",
|
||||
"SELECT hash FROM users WHERE login='kert' LIMIT 1",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
token = result.stdout.strip()
|
||||
if not token:
|
||||
raise ValueError("Could not read Woodpecker token from DB")
|
||||
return token
|
||||
|
||||
|
||||
def provision_woodpecker(
|
||||
values: dict[str, str],
|
||||
*,
|
||||
@@ -271,9 +306,7 @@ def provision_woodpecker(
|
||||
base_url: str = "http://woodpecker-server:8000/api",
|
||||
) -> None:
|
||||
"""Sync rotated credentials to Woodpecker repo secrets."""
|
||||
token = values.get("GITEA_TOKEN", "")
|
||||
if not token:
|
||||
raise ValueError("GITEA_TOKEN not available — cannot sync Woodpecker secrets")
|
||||
token = _get_woodpecker_token(values)
|
||||
|
||||
wp = WoodpeckerClient(token, base_url=base_url)
|
||||
for secret_name, env_var in WOODPECKER_SECRET_MAP.items():
|
||||
|
||||
@@ -66,7 +66,12 @@ class GiteaClient(Client):
|
||||
def change_admin_password(self, username: str, password: str) -> dict:
|
||||
return self.patch(
|
||||
f"/admin/users/{username}",
|
||||
json={"password": password, "must_change_password": False},
|
||||
json={
|
||||
"login_name": username,
|
||||
"source_id": 0,
|
||||
"password": password,
|
||||
"must_change_password": False,
|
||||
},
|
||||
).json()
|
||||
|
||||
# ── Tokens ─────────────────────────────────────────────
|
||||
|
||||
@@ -225,28 +225,36 @@ class TestProvisionGitea:
|
||||
class TestProvisionWoodpecker:
|
||||
def test_updates_secrets(self):
|
||||
values = derive_all(ROOT, COMMIT)
|
||||
values["GITEA_TOKEN"] = "tok"
|
||||
mock_wp = MagicMock()
|
||||
|
||||
with patch("api.auth.provision.WoodpeckerClient", return_value=mock_wp):
|
||||
with (
|
||||
patch("api.auth.provision._get_woodpecker_token", return_value="wp-hash"),
|
||||
patch("api.auth.provision.WoodpeckerClient", return_value=mock_wp),
|
||||
):
|
||||
provision_woodpecker(values, repo_id=1)
|
||||
|
||||
assert mock_wp.update_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
||||
|
||||
def test_creates_on_update_failure(self):
|
||||
values = derive_all(ROOT, COMMIT)
|
||||
values["GITEA_TOKEN"] = "tok"
|
||||
mock_wp = MagicMock()
|
||||
mock_wp.update_secret.side_effect = Exception("not found")
|
||||
|
||||
with patch("api.auth.provision.WoodpeckerClient", return_value=mock_wp):
|
||||
with (
|
||||
patch("api.auth.provision._get_woodpecker_token", return_value="wp-hash"),
|
||||
patch("api.auth.provision.WoodpeckerClient", return_value=mock_wp),
|
||||
):
|
||||
provision_woodpecker(values, repo_id=1)
|
||||
|
||||
assert mock_wp.create_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
||||
|
||||
def test_raises_without_token(self):
|
||||
with pytest.raises(ValueError, match="GITEA_TOKEN"):
|
||||
provision_woodpecker({}, repo_id=1)
|
||||
def test_raises_without_db_token(self):
|
||||
with patch(
|
||||
"api.auth.provision._get_woodpecker_token",
|
||||
side_effect=ValueError("Could not read"),
|
||||
):
|
||||
with pytest.raises(ValueError, match="Could not read"):
|
||||
provision_woodpecker({}, repo_id=1)
|
||||
|
||||
|
||||
class TestProvisionEndToEnd:
|
||||
|
||||
Reference in New Issue
Block a user