Migrate all repos into monorepo context folders

Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,154 @@
"""GitLab Rotator module for the PAT Manager system.
Handles automatic token rotation for GitLab PATs using the
GitLab Personal Access Tokens API.
"""
from __future__ import annotations
import os
from datetime import date
import httpx
from .errors import RotationError
from .models import RotationResult
class GitLabRotator:
"""Rotates GitLab Personal Access Tokens via the GitLab API.
Uses POST /api/v4/personal_access_tokens/{id}/rotate to obtain a new
token with a fresh expiry date. Implements retry logic (one retry on
failure) and skips rotation for already-expired tokens.
Attributes:
timeout: HTTP request timeout in seconds.
"""
def __init__(self, timeout: int = 30) -> None:
self.timeout = timeout
self._gitlab_url = os.environ.get("GITLAB_URL", "https://gitlab.com")
async def rotate(
self, token_id: str, current_token: str, expiry_date: str | None = None
) -> RotationResult:
"""Rotate a GitLab PAT to obtain a new token and expiry date.
If the token is already expired (based on the provided expiry_date),
rotation is skipped and an error result is returned indicating manual
intervention is needed.
On HTTP error or timeout, retries once. If the retry also fails,
returns an error RotationResult.
Args:
token_id: The GitLab personal access token ID (numeric string).
current_token: The current token value used for authentication.
expiry_date: Optional ISO 8601 date string (YYYY-MM-DD) of the
token's current expiry. Used to skip expired tokens.
Returns:
RotationResult with success=True and new token/expiry on success,
or success=False with an error message on failure.
"""
# Skip rotation for expired tokens
if expiry_date is not None:
try:
parsed_expiry = date.fromisoformat(expiry_date)
if parsed_expiry < date.today():
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=(
f"Token {token_id} is expired (expiry: {expiry_date}). "
"Manual intervention required — cannot rotate an expired token."
),
)
except ValueError:
# Invalid date format — proceed with rotation attempt
pass
url = f"{self._gitlab_url}/api/v4/personal_access_tokens/{token_id}/rotate"
headers = {"PRIVATE-TOKEN": current_token}
# First attempt
result = await self._attempt_rotation(url, headers, token_id)
if result.success:
return result
# Retry once on failure
result = await self._attempt_rotation(url, headers, token_id)
return result
async def _attempt_rotation(
self, url: str, headers: dict[str, str], token_id: str
) -> RotationResult:
"""Make a single rotation API call.
Args:
url: The full rotation endpoint URL.
headers: Request headers including PRIVATE-TOKEN.
token_id: The token ID for error messages.
Returns:
RotationResult indicating success or failure.
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.post(url, headers=headers)
except httpx.TimeoutException:
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=f"Rotation request timed out for token {token_id}",
)
except httpx.ConnectError as exc:
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=f"Connection error during rotation for token {token_id}: {exc}",
)
except httpx.HTTPError as exc:
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=f"HTTP error during rotation for token {token_id}: {exc}",
)
if not (200 <= response.status_code < 300):
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=(
f"GitLab API returned HTTP {response.status_code} "
f"for token {token_id} rotation"
),
)
# Parse successful response
try:
data = response.json()
new_token = data["token"]
new_expiry_date = data["expires_at"]
except (KeyError, ValueError) as exc:
return RotationResult(
success=False,
new_token=None,
new_expiry_date=None,
error_message=(
f"Failed to parse rotation response for token {token_id}: {exc}"
),
)
return RotationResult(
success=True,
new_token=new_token,
new_expiry_date=new_expiry_date,
error_message=None,
)