Files
2026-06-30 20:37:40 +02:00

12 KiB

Implementation Plan: PAT Renewal

Overview

Implement an automated PAT lifecycle management system as a Python script (scripts/pat_manager.py) that runs via GitHub Actions. The implementation follows a modular architecture with 7 components: Registry, Expiry Checker, GitLab Rotator, Secret Updater, Alert Sender, Summary Reporter, and Main Orchestrator. All modules use Python 3.11+, httpx for HTTP, and pytest with Hypothesis for property-based testing.

Tasks

  • 1. Set up project structure and core data models

    • 1.1 Create directory structure and dependencies

      • Create scripts/pat_manager/ package directory with __init__.py
      • Create scripts/pat_manager/models.py with PatEntry dataclass, PatStatus enum, CheckResult dataclass, RotationResult dataclass, AlertResult dataclass, and AlertChannel enum
      • Create scripts/pat_manager/errors.py with all custom exception classes (PatManagerError, RegistryValidationError, ServiceCheckError, RotationError, SecretUpdateError, AlertError)
      • Add requirements.txt with httpx>=0.27 and requirements-dev.txt with hypothesis>=6.100, pytest>=8.0, pytest-asyncio>=0.23, pytest-cov>=5.0, respx>=0.21
      • Requirements: 1.1, 1.4
    • 1.2 Create PAT Registry JSON schema and example file

      • Create pat-registry.json with the JSON schema structure (version "1.0", empty tokens array)
      • Create pat-registry.example.json with example entries for all four services
      • Requirements: 1.1, 1.6
  • 2. Implement PAT Registry module

    • 2.1 Implement registry loading and validation (scripts/pat_manager/registry.py)

      • Implement PatRegistry class with load(path) method that reads and parses JSON
      • Implement validate_entry(entry) that checks all required fields, service enum values, token_name length (max 128 chars), expiry_date ISO 8601 format, and renewal_method matching service
      • Implement duplicate detection for (service, token_name) combinations
      • Implement create_empty(path) for creating a new empty registry file
      • Implement save(path, entries) for persisting registry changes
      • Raise RegistryValidationError with specific failed fields on invalid entries
      • Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6
    • 2.2 Write property test: Registry serialization round-trip

      • Property 1: Registry serialization round-trip
      • Validates: Requirements 1.1
      • Use Hypothesis to generate valid PatEntry lists and verify saveload produces equivalent entries
    • 2.3 Write property test: Validation accepts only valid entries

      • Property 2: Validation accepts only valid entries
      • Validates: Requirements 1.2, 1.4
      • Use Hypothesis to generate both valid and invalid entry dicts, verify validator accepts/rejects correctly
    • 2.4 Write property test: Invalid entries and duplicates preserve registry state

      • Property 3: Invalid entries and duplicates preserve registry state
      • Validates: Requirements 1.3, 1.5
      • Use Hypothesis to generate registries and invalid/duplicate entries, verify registry unchanged after rejected add
  • 3. Implement Expiry Checker module

    • 3.1 Implement expiry classification logic (scripts/pat_manager/checker.py)

      • Implement ExpiryChecker class with configurable expiry_window_days (default 14), timeout (default 30s), and max_retries (default 2)
      • Implement classify_by_date(expiry_date, reference_date, window) pure function for date-based classification
      • Implement check_all(entries, secrets) async method that iterates all entries and returns CheckResult list
      • Requirements: 2.2, 2.3, 2.4, 2.7
    • 3.2 Implement service-specific check methods

      • Implement check_gitlab(entry, token) — calls GET /personal_access_tokens, reads expires_at, classifies
      • Implement check_jira(entry, token) — authenticated call, HTTP 401 = expired
      • Implement check_confluence(entry, token) — authenticated call, HTTP 401 = expired
      • Implement check_orgmylife(entry, token) — authenticated call, HTTP 401/403 = expired
      • Handle timeout (30s), retries (2 attempts with 5s backoff), and fallback to "check failed"
      • Handle null expiry_date entries: classify based on API call result only
      • Handle stale expiry_date warning when API succeeds but stored date has passed
      • Requirements: 2.1, 2.5, 2.6, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8
    • 3.3 Write property test: Expiry classification is deterministic and correct

      • Property 4: Expiry classification is deterministic and correct
      • Validates: Requirements 2.2, 2.3, 2.4, 6.5
      • Use Hypothesis to generate expiry dates and reference dates, verify classification matches expected rules
    • 3.4 Write property test: Classification completeness

      • Property 5: Classification completeness
      • Validates: Requirements 2.7
      • Use Hypothesis to generate N-entry registries, verify exactly N results each with exactly one valid status
    • 3.5 Write property test: Non-auth HTTP errors yield "check failed"

      • Property 12: Non-auth HTTP errors yield "check failed"
      • Validates: Requirements 6.6
      • Use Hypothesis to generate HTTP status codes not in {200-299, 401, 403}, verify classification is "check failed"
  • 4. Checkpoint - Ensure all tests pass

    • Ensure all tests pass, ask the user if questions arise.
  • 5. Implement GitLab Rotator module

    • 5.1 Implement GitLab token rotation (scripts/pat_manager/rotator.py)

      • Implement GitLabRotator class with rotate(token_id, current_token) async method
      • Call POST /personal_access_tokens/{id}/rotate with the current token
      • Return RotationResult with new expiry date on success
      • Implement retry logic: retry once on failure, then return error result
      • Skip rotation for expired tokens (return error indicating manual intervention needed)
      • Requirements: 3.1, 3.4, 3.7
    • 5.2 Write property test: Registry update after successful rotation

      • Property 6: Registry update after successful rotation
      • Validates: Requirements 3.3
      • Use Hypothesis to generate rotation responses with new expiry dates, verify registry entry updated correctly
  • 6. Implement Secret Updater module

    • 6.1 Implement GitHub secret update (scripts/pat_manager/secret_updater.py)
      • Implement SecretUpdater class with update_secret(secret_name, secret_value) async method
      • Use GitHub API to encrypt and update repository secrets (libsodium sealed box encryption)
      • Handle failure: retain previous secret value, report error without exposing token
      • Requirements: 3.2, 3.5, 7.1, 7.4, 7.5
  • 7. Implement Alert Sender module

    • 7.1 Implement alert sending (scripts/pat_manager/alerter.py)

      • Implement AlertSender class with configurable AlertChannel (OrgMyLife or email)
      • Implement send_alert(result) that creates OrgMyLife task or sends email based on channel
      • OrgMyLife task title format: [PAT Renewal] {service} - {token_name}
      • Include days remaining (0 for expired) in alert message
      • Implement check_existing_task(service, token_name, expiry_date) for idempotent alerting
      • Handle alert channel unreachable: log failure, signal non-zero exit
      • Implement retry logic: 1 retry with 2s backoff
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6
    • 7.2 Write property test: Alert triggering correctness

      • Property 7: Alert triggering correctness
      • Validates: Requirements 4.1, 5.4
      • Use Hypothesis to generate classified PAT sets, verify zero alerts when all healthy, one alert per expiring/expired manual token
    • 7.3 Write property test: Alert message completeness

      • Property 8: Alert message completeness
      • Validates: Requirements 4.2, 4.3
      • Use Hypothesis to generate PAT entries requiring alerts, verify OrgMyLife title contains service+token, email contains all required fields
    • 7.4 Write property test: Days remaining calculation

      • Property 9: Days remaining calculation
      • Validates: Requirements 4.4
      • Use Hypothesis to generate expiry/reference date pairs, verify days_remaining == max(0, (expiry - ref).days)
    • 7.5 Write property test: Alert idempotence

      • Property 10: Alert idempotence
      • Validates: Requirements 4.5
      • Use Hypothesis to generate PAT alert scenarios, verify no duplicate task created when open task exists
  • 8. Implement Summary Reporter module

    • 8.1 Implement summary report generation (scripts/pat_manager/reporter.py)

      • Implement SummaryReporter class with report(results, rotations) method
      • Generate one line per PAT with service name, token identifier, and classification status
      • Include rotation results (old/new expiry dates) for rotated tokens
      • Requirements: 5.3
    • 8.2 Write property test: Summary report completeness

      • Property 11: Summary report completeness
      • Validates: Requirements 5.3
      • Use Hypothesis to generate check result lists, verify report contains one line per result with service, token_name, and status
  • 9. Checkpoint - Ensure all tests pass

    • Ensure all tests pass, ask the user if questions arise.
  • 10. Implement Main Orchestrator and GitHub Actions workflow

    • 10.1 Implement main orchestrator (scripts/pat_manager/__main__.py)

      • Implement main() async function that coordinates all modules
      • Load registry → check all → rotate GitLab tokens → update secrets → send alerts → report summary
      • Mask all token values with ::add-mask::{value} before any operations
      • Handle rotation success + secret update failure → trigger alert flow
      • Return exit code: 0 (success), 1 (alert delivery failure), 2 (registry validation failure)
      • Log rotation events with old and new expiration dates (Req 3.6)
      • Requirements: 3.5, 3.6, 5.4, 5.5, 7.1, 7.2
    • 10.2 Create GitHub Actions workflow file

      • Create .github/workflows/pat-check.yml with cron schedule 0 6 * * 1 (Monday 06:00 UTC)
      • Add workflow_dispatch trigger
      • Configure Python 3.11 setup, dependency installation, and script execution
      • Map all required secrets as environment variables (GITLAB_PAT, JIRA_PAT, CONFLUENCE_PAT, ORGMYLIFE_API_SECRET, ORGMYLIFE_USER, ORGMYLIFE_PASS, PAT_MANAGER_TOKEN, ALERT_CHANNEL)
      • Ensure all API URLs use HTTPS
      • Requirements: 5.1, 5.2, 7.3
    • 10.3 Write integration tests for orchestrator flow

      • Test full happy path: all healthy → no alerts, summary only
      • Test GitLab rotation flow with mocked APIs (respx)
      • Test rotation success + secret update failure → alert triggered
      • Test alert channel unreachable → non-zero exit code
      • Test service timeout after retries → "check failed" classification
      • Test null expiry_date classification scenarios
      • Requirements: 2.5, 2.6, 3.2, 3.4, 3.5, 4.6, 5.4, 5.5, 6.8
  • 11. Final checkpoint - Ensure all tests pass

    • Ensure all tests pass, ask the user if questions arise.

Notes

  • Tasks marked with * are optional and can be skipped for faster MVP
  • Each task references specific requirements for traceability
  • Checkpoints ensure incremental validation
  • Property tests validate universal correctness properties from the design document using Hypothesis
  • Unit/integration tests validate specific examples and edge cases using pytest + respx
  • The implementation uses Python 3.11+, httpx for HTTP calls, and async/await throughout
  • All token values must be masked in logs via GitHub Actions ::add-mask:: mechanism

Task Dependency Graph

{
  "waves": [
    { "id": 0, "tasks": ["1.1", "1.2"] },
    { "id": 1, "tasks": ["2.1"] },
    { "id": 2, "tasks": ["2.2", "2.3", "2.4", "3.1"] },
    { "id": 3, "tasks": ["3.2", "3.3", "3.4", "3.5"] },
    { "id": 4, "tasks": ["5.1", "6.1", "7.1", "8.1"] },
    { "id": 5, "tasks": ["5.2", "7.2", "7.3", "7.4", "7.5", "8.2"] },
    { "id": 6, "tasks": ["10.1", "10.2"] },
    { "id": 7, "tasks": ["10.3"] }
  ]
}