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
|
restart: unless-stopped
|
||||||
|
|
||||||
rustfs:
|
rustfs:
|
||||||
image: fhirworx/rustfs:${COMMIT_SHA:-latest}
|
image: fhirworx/rustfs:latest
|
||||||
container_name: rustfs
|
container_name: rustfs
|
||||||
user: "10001:10001"
|
user: "10001:10001"
|
||||||
networks:
|
networks:
|
||||||
@@ -98,7 +98,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: fhirworx/postgresql:${COMMIT_SHA:-latest}
|
image: fhirworx/postgresql:latest
|
||||||
container_name: postgres
|
container_name: postgres
|
||||||
networks:
|
networks:
|
||||||
- storage
|
- 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(
|
def provision_woodpecker(
|
||||||
values: dict[str, str],
|
values: dict[str, str],
|
||||||
*,
|
*,
|
||||||
@@ -271,9 +306,7 @@ def provision_woodpecker(
|
|||||||
base_url: str = "http://woodpecker-server:8000/api",
|
base_url: str = "http://woodpecker-server:8000/api",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Sync rotated credentials to Woodpecker repo secrets."""
|
"""Sync rotated credentials to Woodpecker repo secrets."""
|
||||||
token = values.get("GITEA_TOKEN", "")
|
token = _get_woodpecker_token(values)
|
||||||
if not token:
|
|
||||||
raise ValueError("GITEA_TOKEN not available — cannot sync Woodpecker secrets")
|
|
||||||
|
|
||||||
wp = WoodpeckerClient(token, base_url=base_url)
|
wp = WoodpeckerClient(token, base_url=base_url)
|
||||||
for secret_name, env_var in WOODPECKER_SECRET_MAP.items():
|
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:
|
def change_admin_password(self, username: str, password: str) -> dict:
|
||||||
return self.patch(
|
return self.patch(
|
||||||
f"/admin/users/{username}",
|
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()
|
).json()
|
||||||
|
|
||||||
# ── Tokens ─────────────────────────────────────────────
|
# ── Tokens ─────────────────────────────────────────────
|
||||||
|
|||||||
@@ -225,28 +225,36 @@ class TestProvisionGitea:
|
|||||||
class TestProvisionWoodpecker:
|
class TestProvisionWoodpecker:
|
||||||
def test_updates_secrets(self):
|
def test_updates_secrets(self):
|
||||||
values = derive_all(ROOT, COMMIT)
|
values = derive_all(ROOT, COMMIT)
|
||||||
values["GITEA_TOKEN"] = "tok"
|
|
||||||
mock_wp = MagicMock()
|
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)
|
provision_woodpecker(values, repo_id=1)
|
||||||
|
|
||||||
assert mock_wp.update_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
assert mock_wp.update_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
||||||
|
|
||||||
def test_creates_on_update_failure(self):
|
def test_creates_on_update_failure(self):
|
||||||
values = derive_all(ROOT, COMMIT)
|
values = derive_all(ROOT, COMMIT)
|
||||||
values["GITEA_TOKEN"] = "tok"
|
|
||||||
mock_wp = MagicMock()
|
mock_wp = MagicMock()
|
||||||
mock_wp.update_secret.side_effect = Exception("not found")
|
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)
|
provision_woodpecker(values, repo_id=1)
|
||||||
|
|
||||||
assert mock_wp.create_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
assert mock_wp.create_secret.call_count == len(WOODPECKER_SECRET_MAP)
|
||||||
|
|
||||||
def test_raises_without_token(self):
|
def test_raises_without_db_token(self):
|
||||||
with pytest.raises(ValueError, match="GITEA_TOKEN"):
|
with patch(
|
||||||
provision_woodpecker({}, repo_id=1)
|
"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:
|
class TestProvisionEndToEnd:
|
||||||
|
|||||||
Reference in New Issue
Block a user