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.pywithPatEntrydataclass,PatStatusenum,CheckResultdataclass,RotationResultdataclass,AlertResultdataclass, andAlertChannelenum - Create
scripts/pat_manager/errors.pywith all custom exception classes (PatManagerError,RegistryValidationError,ServiceCheckError,RotationError,SecretUpdateError,AlertError) - Add
requirements.txtwithhttpx>=0.27andrequirements-dev.txtwithhypothesis>=6.100,pytest>=8.0,pytest-asyncio>=0.23,pytest-cov>=5.0,respx>=0.21 - Requirements: 1.1, 1.4
- Create
-
1.2 Create PAT Registry JSON schema and example file
- Create
pat-registry.jsonwith the JSON schema structure (version "1.0", empty tokens array) - Create
pat-registry.example.jsonwith example entries for all four services - Requirements: 1.1, 1.6
- Create
-
-
2. Implement PAT Registry module
-
2.1 Implement registry loading and validation (
scripts/pat_manager/registry.py)- Implement
PatRegistryclass withload(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
RegistryValidationErrorwith specific failed fields on invalid entries - Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6
- Implement
-
2.2 Write property test: Registry serialization round-trip
- Property 1: Registry serialization round-trip
- Validates: Requirements 1.1
- Use Hypothesis to generate valid
PatEntrylists and verifysave→loadproduces 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
ExpiryCheckerclass with configurableexpiry_window_days(default 14),timeout(default 30s), andmax_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 returnsCheckResultlist - Requirements: 2.2, 2.3, 2.4, 2.7
- Implement
-
3.2 Implement service-specific check methods
- Implement
check_gitlab(entry, token)— callsGET /personal_access_tokens, readsexpires_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_dateentries: classify based on API call result only - Handle stale
expiry_datewarning 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
- Implement
-
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
GitLabRotatorclass withrotate(token_id, current_token)async method - Call
POST /personal_access_tokens/{id}/rotatewith the current token - Return
RotationResultwith 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
- Implement
-
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
SecretUpdaterclass withupdate_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
- Implement
- 6.1 Implement GitHub secret update (
-
7. Implement Alert Sender module
-
7.1 Implement alert sending (
scripts/pat_manager/alerter.py)- Implement
AlertSenderclass with configurableAlertChannel(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
- Implement
-
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
SummaryReporterclass withreport(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
- Implement
-
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
- Implement
-
10.2 Create GitHub Actions workflow file
- Create
.github/workflows/pat-check.ymlwith cron schedule0 6 * * 1(Monday 06:00 UTC) - Add
workflow_dispatchtrigger - 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
- Create
-
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"] }
]
}