fraisier 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. fraisier-0.1.1/.env.example +38 -0
  2. fraisier-0.1.1/.github/actions/deploy/action.yml +188 -0
  3. fraisier-0.1.1/.github/workflows/publish.yml +197 -0
  4. fraisier-0.1.1/.github/workflows/python-version-matrix.yml +95 -0
  5. fraisier-0.1.1/.github/workflows/quality-gate.yml +162 -0
  6. fraisier-0.1.1/.gitignore +15 -0
  7. fraisier-0.1.1/CHANGELOG.md +97 -0
  8. fraisier-0.1.1/Dockerfile +77 -0
  9. fraisier-0.1.1/PKG-INFO +298 -0
  10. fraisier-0.1.1/README.md +254 -0
  11. fraisier-0.1.1/development.md +635 -0
  12. fraisier-0.1.1/docker-compose.yml +233 -0
  13. fraisier-0.1.1/docs/api-reference.md +157 -0
  14. fraisier-0.1.1/docs/architecture.md +623 -0
  15. fraisier-0.1.1/docs/cli-reference.md +585 -0
  16. fraisier-0.1.1/docs/deployment-guide.md +681 -0
  17. fraisier-0.1.1/docs/failure-modes.md +129 -0
  18. fraisier-0.1.1/docs/getting-started-postgres.md +493 -0
  19. fraisier-0.1.1/docs/index.md +26 -0
  20. fraisier-0.1.1/docs/notifications.md +82 -0
  21. fraisier-0.1.1/docs/provider-bare-metal.md +801 -0
  22. fraisier-0.1.1/docs/provider-docker-compose.md +722 -0
  23. fraisier-0.1.1/docs/security.md +81 -0
  24. fraisier-0.1.1/docs/testing.md +651 -0
  25. fraisier-0.1.1/docs/webhook-reference.md +202 -0
  26. fraisier-0.1.1/examples/django-celery-postgres/.github/workflows/deploy.yml +48 -0
  27. fraisier-0.1.1/examples/django-celery-postgres/README.md +89 -0
  28. fraisier-0.1.1/examples/django-celery-postgres/confiture.yaml +12 -0
  29. fraisier-0.1.1/examples/django-celery-postgres/fraises.yaml +67 -0
  30. fraisier-0.1.1/examples/django-celery-postgres/myapp/__init__.py +0 -0
  31. fraisier-0.1.1/examples/django-celery-postgres/myapp/celery.py +11 -0
  32. fraisier-0.1.1/examples/django-celery-postgres/myapp/models.py +19 -0
  33. fraisier-0.1.1/examples/django-celery-postgres/myapp/settings.py +36 -0
  34. fraisier-0.1.1/examples/django-celery-postgres/myapp/tasks.py +14 -0
  35. fraisier-0.1.1/examples/django-celery-postgres/myapp/urls.py +11 -0
  36. fraisier-0.1.1/examples/django-celery-postgres/myapp/views.py +34 -0
  37. fraisier-0.1.1/fraises.example.yaml +223 -0
  38. fraisier-0.1.1/fraises.yaml +31 -0
  39. fraisier-0.1.1/fraisier/__init__.py +21 -0
  40. fraisier-0.1.1/fraisier/_env.py +32 -0
  41. fraisier-0.1.1/fraisier/cli/__init__.py +14 -0
  42. fraisier-0.1.1/fraisier/cli/_helpers.py +117 -0
  43. fraisier-0.1.1/fraisier/cli/db.py +324 -0
  44. fraisier-0.1.1/fraisier/cli/health.py +158 -0
  45. fraisier-0.1.1/fraisier/cli/main.py +417 -0
  46. fraisier-0.1.1/fraisier/cli/ops.py +337 -0
  47. fraisier-0.1.1/fraisier/cli/providers.py +199 -0
  48. fraisier-0.1.1/fraisier/cli/scaffold.py +41 -0
  49. fraisier-0.1.1/fraisier/cli/version.py +267 -0
  50. fraisier-0.1.1/fraisier/config.py +564 -0
  51. fraisier-0.1.1/fraisier/database.py +409 -0
  52. fraisier-0.1.1/fraisier/db/__init__.py +44 -0
  53. fraisier-0.1.1/fraisier/db/adapter.py +235 -0
  54. fraisier-0.1.1/fraisier/db/factory.py +226 -0
  55. fraisier-0.1.1/fraisier/db/history.py +279 -0
  56. fraisier-0.1.1/fraisier/db/lock_store.py +89 -0
  57. fraisier-0.1.1/fraisier/db/migrations/postgresql/001_create_tables.sql +82 -0
  58. fraisier-0.1.1/fraisier/db/migrations/postgresql/002_create_views.sql +74 -0
  59. fraisier-0.1.1/fraisier/db/migrations/postgresql/003_create_indexes.sql +59 -0
  60. fraisier-0.1.1/fraisier/db/migrations.py +270 -0
  61. fraisier-0.1.1/fraisier/db/observability.py +383 -0
  62. fraisier-0.1.1/fraisier/db/postgres_adapter.py +373 -0
  63. fraisier-0.1.1/fraisier/db/state.py +145 -0
  64. fraisier-0.1.1/fraisier/db/webhook_store.py +134 -0
  65. fraisier-0.1.1/fraisier/dbops/__init__.py +5 -0
  66. fraisier-0.1.1/fraisier/dbops/_validation.py +120 -0
  67. fraisier-0.1.1/fraisier/dbops/backup.py +137 -0
  68. fraisier-0.1.1/fraisier/dbops/confiture.py +452 -0
  69. fraisier-0.1.1/fraisier/dbops/guard.py +51 -0
  70. fraisier-0.1.1/fraisier/dbops/operations.py +138 -0
  71. fraisier-0.1.1/fraisier/dbops/restore.py +96 -0
  72. fraisier-0.1.1/fraisier/dbops/schema.py +59 -0
  73. fraisier-0.1.1/fraisier/dbops/templates.py +138 -0
  74. fraisier-0.1.1/fraisier/deployers/__init__.py +5 -0
  75. fraisier-0.1.1/fraisier/deployers/api.py +468 -0
  76. fraisier-0.1.1/fraisier/deployers/base.py +125 -0
  77. fraisier-0.1.1/fraisier/deployers/docker_compose.py +166 -0
  78. fraisier-0.1.1/fraisier/deployers/etl.py +86 -0
  79. fraisier-0.1.1/fraisier/deployers/mixins.py +300 -0
  80. fraisier-0.1.1/fraisier/deployers/scheduled.py +154 -0
  81. fraisier-0.1.1/fraisier/errors.py +220 -0
  82. fraisier-0.1.1/fraisier/git/__init__.py +19 -0
  83. fraisier-0.1.1/fraisier/git/base.py +119 -0
  84. fraisier-0.1.1/fraisier/git/bitbucket.py +122 -0
  85. fraisier-0.1.1/fraisier/git/gitea.py +131 -0
  86. fraisier-0.1.1/fraisier/git/github.py +110 -0
  87. fraisier-0.1.1/fraisier/git/gitlab.py +106 -0
  88. fraisier-0.1.1/fraisier/git/operations.py +95 -0
  89. fraisier-0.1.1/fraisier/git/registry.py +64 -0
  90. fraisier-0.1.1/fraisier/health_check.py +701 -0
  91. fraisier-0.1.1/fraisier/init_templates.py +178 -0
  92. fraisier-0.1.1/fraisier/locking.py +219 -0
  93. fraisier-0.1.1/fraisier/logging.py +371 -0
  94. fraisier-0.1.1/fraisier/metrics.py +481 -0
  95. fraisier-0.1.1/fraisier/notifications/__init__.py +1 -0
  96. fraisier-0.1.1/fraisier/notifications/base.py +100 -0
  97. fraisier-0.1.1/fraisier/notifications/dispatcher.py +119 -0
  98. fraisier-0.1.1/fraisier/notifications/git_issue.py +69 -0
  99. fraisier-0.1.1/fraisier/notifications/git_issues.py +223 -0
  100. fraisier-0.1.1/fraisier/notifications/messaging.py +87 -0
  101. fraisier-0.1.1/fraisier/notifications/rendering.py +51 -0
  102. fraisier-0.1.1/fraisier/notifications/templates/issue_body.md.j2 +34 -0
  103. fraisier-0.1.1/fraisier/providers/__init__.py +41 -0
  104. fraisier-0.1.1/fraisier/providers/bare_metal.py +474 -0
  105. fraisier-0.1.1/fraisier/providers/base.py +435 -0
  106. fraisier-0.1.1/fraisier/providers/docker_compose/__init__.py +5 -0
  107. fraisier-0.1.1/fraisier/providers/docker_compose/provider.py +799 -0
  108. fraisier-0.1.1/fraisier/runners.py +134 -0
  109. fraisier-0.1.1/fraisier/scaffold/__init__.py +1 -0
  110. fraisier-0.1.1/fraisier/scaffold/renderer.py +259 -0
  111. fraisier-0.1.1/fraisier/scaffold/templates/core/backup.service.j2 +19 -0
  112. fraisier-0.1.1/fraisier/scaffold/templates/core/backup.sh.j2 +38 -0
  113. fraisier-0.1.1/fraisier/scaffold/templates/core/backup.timer.j2 +10 -0
  114. fraisier-0.1.1/fraisier/scaffold/templates/core/confiture.yaml.j2 +26 -0
  115. fraisier-0.1.1/fraisier/scaffold/templates/core/db_deploy.sh.j2 +33 -0
  116. fraisier-0.1.1/fraisier/scaffold/templates/core/db_reset.sh.j2 +36 -0
  117. fraisier-0.1.1/fraisier/scaffold/templates/core/deploy-checker.timer.j2 +10 -0
  118. fraisier-0.1.1/fraisier/scaffold/templates/core/gateway.conf.j2 +159 -0
  119. fraisier-0.1.1/fraisier/scaffold/templates/core/install.sh.j2 +87 -0
  120. fraisier-0.1.1/fraisier/scaffold/templates/core/poll-deploy.service.j2 +23 -0
  121. fraisier-0.1.1/fraisier/scaffold/templates/core/service.j2 +51 -0
  122. fraisier-0.1.1/fraisier/scaffold/templates/core/sudoers.j2 +13 -0
  123. fraisier-0.1.1/fraisier/scaffold/templates/provider/deploy.yml.j2 +103 -0
  124. fraisier-0.1.1/fraisier/status.py +89 -0
  125. fraisier-0.1.1/fraisier/strategies.py +182 -0
  126. fraisier-0.1.1/fraisier/systemd.py +48 -0
  127. fraisier-0.1.1/fraisier/timeout.py +80 -0
  128. fraisier-0.1.1/fraisier/validation.py +457 -0
  129. fraisier-0.1.1/fraisier/versioning.py +226 -0
  130. fraisier-0.1.1/fraisier/webhook.py +628 -0
  131. fraisier-0.1.1/fraisier/webhook_rate_limit.py +29 -0
  132. fraisier-0.1.1/monitoring/README.md +203 -0
  133. fraisier-0.1.1/monitoring/alert-rules.yml +117 -0
  134. fraisier-0.1.1/monitoring/grafana/dashboards/fraisier-deployments.json +529 -0
  135. fraisier-0.1.1/monitoring/grafana/provisioning/dashboards/dashboard-provider.yml +17 -0
  136. fraisier-0.1.1/monitoring/grafana/provisioning/datasources/prometheus.yml +17 -0
  137. fraisier-0.1.1/monitoring/grafana-dashboard.json +656 -0
  138. fraisier-0.1.1/monitoring/prometheus.yml +59 -0
  139. fraisier-0.1.1/pyproject.toml +96 -0
  140. fraisier-0.1.1/scripts/postgres-init.sql +179 -0
  141. fraisier-0.1.1/tests/__init__.py +1 -0
  142. fraisier-0.1.1/tests/conftest.py +177 -0
  143. fraisier-0.1.1/tests/fixtures/__init__.py +0 -0
  144. fraisier-0.1.1/tests/fixtures/git_env.py +130 -0
  145. fraisier-0.1.1/tests/integration/__init__.py +0 -0
  146. fraisier-0.1.1/tests/integration/test_confiture_integration.py +186 -0
  147. fraisier-0.1.1/tests/test_api_deploy_health.py +208 -0
  148. fraisier-0.1.1/tests/test_api_rollback.py +453 -0
  149. fraisier-0.1.1/tests/test_atomic_rollback.py +173 -0
  150. fraisier-0.1.1/tests/test_atomic_version.py +158 -0
  151. fraisier-0.1.1/tests/test_bare_metal_ssh.py +331 -0
  152. fraisier-0.1.1/tests/test_cli_db.py +278 -0
  153. fraisier-0.1.1/tests/test_cli_deploy.py +335 -0
  154. fraisier-0.1.1/tests/test_compose_service_validation.py +74 -0
  155. fraisier-0.1.1/tests/test_config.py +100 -0
  156. fraisier-0.1.1/tests/test_config_validation.py +165 -0
  157. fraisier-0.1.1/tests/test_config_wiring.py +121 -0
  158. fraisier-0.1.1/tests/test_coverage_gaps.py +293 -0
  159. fraisier-0.1.1/tests/test_database.py +343 -0
  160. fraisier-0.1.1/tests/test_database_lock.py +143 -0
  161. fraisier-0.1.1/tests/test_db_isolation.py +75 -0
  162. fraisier-0.1.1/tests/test_dbops.py +986 -0
  163. fraisier-0.1.1/tests/test_dbops_backup.py +112 -0
  164. fraisier-0.1.1/tests/test_dbops_operations.py +192 -0
  165. fraisier-0.1.1/tests/test_dbops_restore.py +112 -0
  166. fraisier-0.1.1/tests/test_dbops_templates.py +55 -0
  167. fraisier-0.1.1/tests/test_deployer_git_mixin.py +344 -0
  168. fraisier-0.1.1/tests/test_deployers.py +694 -0
  169. fraisier-0.1.1/tests/test_deployment_recording.py +324 -0
  170. fraisier-0.1.1/tests/test_deployment_timeout.py +74 -0
  171. fraisier-0.1.1/tests/test_docker_compose_deployer.py +178 -0
  172. fraisier-0.1.1/tests/test_dry_run.py +183 -0
  173. fraisier-0.1.1/tests/test_dual_logging.py +120 -0
  174. fraisier-0.1.1/tests/test_e2e_deployments.py +460 -0
  175. fraisier-0.1.1/tests/test_error_hints.py +107 -0
  176. fraisier-0.1.1/tests/test_errors.py +263 -0
  177. fraisier-0.1.1/tests/test_exception_specificity.py +117 -0
  178. fraisier-0.1.1/tests/test_exec_health_validation.py +42 -0
  179. fraisier-0.1.1/tests/test_file_lock.py +105 -0
  180. fraisier-0.1.1/tests/test_git_deploy_env.py +75 -0
  181. fraisier-0.1.1/tests/test_git_issue_client.py +150 -0
  182. fraisier-0.1.1/tests/test_git_issue_notifier.py +106 -0
  183. fraisier-0.1.1/tests/test_git_operations.py +193 -0
  184. fraisier-0.1.1/tests/test_git_providers.py +294 -0
  185. fraisier-0.1.1/tests/test_hardening.py +667 -0
  186. fraisier-0.1.1/tests/test_health_check.py +338 -0
  187. fraisier-0.1.1/tests/test_health_validation.py +794 -0
  188. fraisier-0.1.1/tests/test_init.py +170 -0
  189. fraisier-0.1.1/tests/test_integration.py +807 -0
  190. fraisier-0.1.1/tests/test_lock_timeout_config.py +87 -0
  191. fraisier-0.1.1/tests/test_messaging_notifiers.py +111 -0
  192. fraisier-0.1.1/tests/test_metrics.py +98 -0
  193. fraisier-0.1.1/tests/test_notification_config_validation.py +115 -0
  194. fraisier-0.1.1/tests/test_notification_dispatcher.py +117 -0
  195. fraisier-0.1.1/tests/test_notification_wiring.py +58 -0
  196. fraisier-0.1.1/tests/test_notifications.py +175 -0
  197. fraisier-0.1.1/tests/test_observability.py +345 -0
  198. fraisier-0.1.1/tests/test_preflight_validation.py +90 -0
  199. fraisier-0.1.1/tests/test_provider_integration.py +561 -0
  200. fraisier-0.1.1/tests/test_providers.py +1512 -0
  201. fraisier-0.1.1/tests/test_rollback_everywhere.py +118 -0
  202. fraisier-0.1.1/tests/test_rollback_integration.py +212 -0
  203. fraisier-0.1.1/tests/test_runners.py +194 -0
  204. fraisier-0.1.1/tests/test_safe_env_parsing.py +99 -0
  205. fraisier-0.1.1/tests/test_scaffold.py +976 -0
  206. fraisier-0.1.1/tests/test_security.py +201 -0
  207. fraisier-0.1.1/tests/test_security_hardening.py +236 -0
  208. fraisier-0.1.1/tests/test_security_validation.py +70 -0
  209. fraisier-0.1.1/tests/test_ship.py +380 -0
  210. fraisier-0.1.1/tests/test_status.py +257 -0
  211. fraisier-0.1.1/tests/test_strategies.py +256 -0
  212. fraisier-0.1.1/tests/test_systemd.py +93 -0
  213. fraisier-0.1.1/tests/test_timeout_rollback.py +96 -0
  214. fraisier-0.1.1/tests/test_validate.py +321 -0
  215. fraisier-0.1.1/tests/test_validation.py +175 -0
  216. fraisier-0.1.1/tests/test_version.py +597 -0
  217. fraisier-0.1.1/tests/test_versioning.py +171 -0
  218. fraisier-0.1.1/tests/test_webhook.py +1180 -0
  219. fraisier-0.1.1/tests/test_webhook_integration.py +212 -0
  220. fraisier-0.1.1/tests/test_webhook_rate_limit.py +38 -0
  221. fraisier-0.1.1/tests/test_webhook_security.py +95 -0
  222. fraisier-0.1.1/uv.lock +1170 -0
@@ -0,0 +1,38 @@
1
+ # Fraisier Environment Configuration
2
+ # Copy this file to .env and update values for your environment
3
+
4
+ # ============================================================================
5
+ # Database Configuration
6
+ # ============================================================================
7
+ DATABASE_URL=postgresql://fraisier:fraisier_password@postgres:5432/fraisier
8
+
9
+ # ============================================================================
10
+ # Fraisier Application Settings
11
+ # ============================================================================
12
+ FRAISIER_LOG_LEVEL=INFO
13
+ FRAISIER_WEBHOOK_SECRET=dev-webhook-secret-change-me
14
+ PROMETHEUS_PORT=8001
15
+
16
+ # ============================================================================
17
+ # Monitoring and Observability
18
+ # ============================================================================
19
+
20
+ # Grafana Admin Credentials
21
+ GF_SECURITY_ADMIN_USER=admin
22
+ GF_SECURITY_ADMIN_PASSWORD=admin
23
+
24
+ # Prometheus Configuration
25
+ PROMETHEUS_RETENTION=15d
26
+
27
+ # Redis Cache (optional, for distributed deployments)
28
+ REDIS_PASSWORD=redis_password
29
+
30
+ # ============================================================================
31
+ # Development/Production Settings
32
+ # ============================================================================
33
+
34
+ # Environment: development, staging, production
35
+ ENVIRONMENT=development
36
+
37
+ # Enable verbose logging for debugging
38
+ DEBUG=false
@@ -0,0 +1,188 @@
1
+ name: "Fraisier Deploy"
2
+ description: "Trigger a Fraisier deployment via webhook and poll for completion"
3
+
4
+ inputs:
5
+ webhook_url:
6
+ description: "Fraisier webhook URL (e.g., https://deploy.example.com/webhook)"
7
+ required: true
8
+ webhook_secret:
9
+ description: "Webhook HMAC secret for signature verification"
10
+ required: true
11
+ fraise_name:
12
+ description: "Name of the fraise to deploy"
13
+ required: true
14
+ status_url:
15
+ description: "Base URL for status endpoint (e.g., https://deploy.example.com)"
16
+ required: true
17
+ environment:
18
+ description: "Target environment (development, staging, production)"
19
+ required: false
20
+ default: "production"
21
+ timeout:
22
+ description: "Max seconds to wait for deployment (0 = use environment default)"
23
+ required: false
24
+ default: "0"
25
+ poll_interval:
26
+ description: "Seconds between status polls"
27
+ required: false
28
+ default: "10"
29
+ create_issue:
30
+ description: "Create GitHub Issue on failure (true/false)"
31
+ required: false
32
+ default: "true"
33
+
34
+ outputs:
35
+ version:
36
+ description: "Deployed version after successful deployment"
37
+ value: ${{ steps.poll.outputs.version }}
38
+ state:
39
+ description: "Final deployment state"
40
+ value: ${{ steps.poll.outputs.state }}
41
+
42
+ runs:
43
+ using: "composite"
44
+ steps:
45
+ - name: Trigger deployment
46
+ shell: bash
47
+ env:
48
+ WEBHOOK_URL: ${{ inputs.webhook_url }}
49
+ WEBHOOK_SECRET: ${{ inputs.webhook_secret }}
50
+ COMMIT_SHA: ${{ github.sha }}
51
+ BRANCH: ${{ github.ref_name }}
52
+ run: |
53
+ # Compute HMAC-SHA256 signature
54
+ PAYLOAD='{"ref":"refs/heads/'"$BRANCH"'","after":"'"$COMMIT_SHA"'"}'
55
+ SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | cut -d' ' -f2)"
56
+
57
+ HTTP_CODE=$(curl -sf -o /dev/null -w '%{http_code}' \
58
+ -X POST "$WEBHOOK_URL" \
59
+ -H "Content-Type: application/json" \
60
+ -H "X-GitHub-Event: push" \
61
+ -H "X-Hub-Signature-256: $SIGNATURE" \
62
+ -d "$PAYLOAD")
63
+
64
+ if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
65
+ echo "::error::Webhook trigger failed with HTTP $HTTP_CODE"
66
+ exit 1
67
+ fi
68
+ echo "Webhook triggered successfully (HTTP $HTTP_CODE)"
69
+
70
+ - name: Poll for completion
71
+ id: poll
72
+ shell: bash
73
+ env:
74
+ STATUS_URL: ${{ inputs.status_url }}/api/status/${{ inputs.fraise_name }}
75
+ EXPECTED_SHA: ${{ github.sha }}
76
+ TIMEOUT: ${{ inputs.timeout }}
77
+ POLL_INTERVAL: ${{ inputs.poll_interval }}
78
+ ENVIRONMENT: ${{ inputs.environment }}
79
+ run: |
80
+ # Use environment default if timeout is 0
81
+ if [ "$TIMEOUT" = "0" ]; then
82
+ case "$ENVIRONMENT" in
83
+ development) TIMEOUT=120 ;;
84
+ staging) TIMEOUT=600 ;;
85
+ *) TIMEOUT=300 ;;
86
+ esac
87
+ fi
88
+
89
+ echo "Polling $STATUS_URL (timeout: ${TIMEOUT}s, interval: ${POLL_INTERVAL}s)"
90
+ DEADLINE=$((SECONDS + TIMEOUT))
91
+
92
+ while [ "$SECONDS" -lt "$DEADLINE" ]; do
93
+ RESPONSE=$(curl -sf "$STATUS_URL" 2>/dev/null) || {
94
+ echo "Status endpoint unreachable, retrying in ${POLL_INTERVAL}s..."
95
+ sleep "$POLL_INTERVAL"
96
+ continue
97
+ }
98
+
99
+ STATE=$(echo "$RESPONSE" | jq -r '.state // empty')
100
+ SHA=$(echo "$RESPONSE" | jq -r '.commit_sha // empty')
101
+ VERSION=$(echo "$RESPONSE" | jq -r '.version // empty')
102
+
103
+ # Check FAILURE first (key insight: fail fast, don't wait for SHA match)
104
+ if [ "$STATE" = "failed" ]; then
105
+ echo "::error::Deployment failed"
106
+ echo "state=failed" >> "$GITHUB_OUTPUT"
107
+ exit 1
108
+ fi
109
+
110
+ # Check SUCCESS with SHA match
111
+ if [ "$STATE" = "success" ] && [ "$SHA" = "$EXPECTED_SHA" ]; then
112
+ echo "Deployment succeeded: version=$VERSION"
113
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
114
+ echo "state=success" >> "$GITHUB_OUTPUT"
115
+ exit 0
116
+ fi
117
+
118
+ echo "Waiting: state=$STATE sha=${SHA:0:7}..."
119
+ sleep "$POLL_INTERVAL"
120
+ done
121
+
122
+ echo "::error::Deployment timed out after ${TIMEOUT}s"
123
+ echo "state=timeout" >> "$GITHUB_OUTPUT"
124
+ exit 1
125
+
126
+ - name: Create issue on failure
127
+ if: failure() && inputs.create_issue == 'true'
128
+ shell: bash
129
+ env:
130
+ STATUS_URL: ${{ inputs.status_url }}/api/status/${{ inputs.fraise_name }}
131
+ WEBHOOK_SECRET: ${{ inputs.webhook_secret }}
132
+ ENVIRONMENT: ${{ inputs.environment }}
133
+ COMMIT_SHA: ${{ github.sha }}
134
+ BRANCH: ${{ github.ref_name }}
135
+ ACTOR: ${{ github.actor }}
136
+ RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
137
+ GH_TOKEN: ${{ github.token }}
138
+ run: |
139
+ # Fetch failure details from authenticated endpoint
140
+ DETAILS=$(curl -sf "${STATUS_URL}/details" \
141
+ -H "X-Deployment-Token: ${WEBHOOK_SECRET}" 2>/dev/null) || DETAILS="{}"
142
+
143
+ ERROR_MSG=$(echo "$DETAILS" | jq -r '.error_message // "No error details available"')
144
+ MIGRATION=$(echo "$DETAILS" | jq -r '.migration_report // empty')
145
+
146
+ SHORT_SHA="${COMMIT_SHA:0:7}"
147
+
148
+ # Build issue body
149
+ BODY="## Deployment Failure
150
+
151
+ | Field | Value |
152
+ | --- | --- |
153
+ | Environment | \`${ENVIRONMENT}\` |
154
+ | Commit | \`${SHORT_SHA}\` (${COMMIT_SHA}) |
155
+ | Branch | \`${BRANCH}\` |
156
+ | Triggered by | @${ACTOR} |
157
+ | Workflow run | [View logs](${RUN_URL}) |
158
+
159
+ ## Error Details
160
+
161
+ \`\`\`
162
+ ${ERROR_MSG}
163
+ \`\`\`"
164
+
165
+ if [ -n "$MIGRATION" ] && [ "$MIGRATION" != "null" ]; then
166
+ BODY="${BODY}
167
+
168
+ ## Migration Report
169
+
170
+ \`\`\`json
171
+ ${MIGRATION}
172
+ \`\`\`"
173
+ fi
174
+
175
+ BODY="${BODY}
176
+
177
+ ## Remediation Checklist
178
+
179
+ - [ ] Review error details and workflow logs above
180
+ - [ ] Check database state and migration status
181
+ - [ ] Fix the root cause in a new commit
182
+ - [ ] Verify fix deploys successfully
183
+ - [ ] Close this issue"
184
+
185
+ gh issue create \
186
+ --title "Deploy failed: ${ENVIRONMENT} @ ${SHORT_SHA}" \
187
+ --label "deployment-failure" \
188
+ --body "$BODY"
@@ -0,0 +1,197 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ test:
10
+ name: Tests (Required for Release)
11
+ runs-on: ubuntu-latest
12
+
13
+ services:
14
+ postgres:
15
+ image: postgres:16-alpine
16
+ env:
17
+ POSTGRES_USER: fraisier
18
+ POSTGRES_PASSWORD: fraisier
19
+ POSTGRES_DB: fraisier_test
20
+ options: >-
21
+ --health-cmd pg_isready
22
+ --health-interval 10s
23
+ --health-timeout 5s
24
+ --health-retries 5
25
+ ports:
26
+ - 5432:5432
27
+
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: '3.12'
35
+
36
+ - name: Install uv
37
+ uses: astral-sh/setup-uv@v7
38
+
39
+ - name: Install dependencies
40
+ run: |
41
+ uv venv
42
+ uv pip install ".[dev]"
43
+
44
+ - name: Run tests
45
+ env:
46
+ FRAISIER_TEST_PG_URL: postgresql://fraisier:fraisier@localhost:5432/fraisier_test
47
+ run: |
48
+ uv run pytest tests/ \
49
+ --cov=fraisier \
50
+ --cov-report=xml \
51
+ --cov-report=term-missing \
52
+ -v
53
+
54
+ lint:
55
+ name: Lint (Required for Release)
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - name: Set up Python
60
+ uses: actions/setup-python@v5
61
+ with:
62
+ python-version: '3.12'
63
+ - name: Install uv
64
+ uses: astral-sh/setup-uv@v7
65
+ - name: Install dependencies
66
+ run: |
67
+ uv venv
68
+ uv pip install ruff
69
+ - name: Run ruff check
70
+ run: uv run ruff check .
71
+ - name: Run ruff format check
72
+ run: uv run ruff format --check .
73
+
74
+ type-check:
75
+ name: Type Check (Advisory)
76
+ runs-on: ubuntu-latest
77
+ continue-on-error: true
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - name: Set up Python
81
+ uses: actions/setup-python@v5
82
+ with:
83
+ python-version: '3.12'
84
+ - name: Install uv
85
+ uses: astral-sh/setup-uv@v7
86
+ - name: Install ty
87
+ run: uv tool install ty
88
+ - name: Run ty
89
+ run: ty check fraisier/ || echo "Type check warnings (non-blocking)"
90
+
91
+ security:
92
+ name: Security (Required for Release)
93
+ runs-on: ubuntu-latest
94
+ steps:
95
+ - uses: actions/checkout@v4
96
+ - name: Set up Python
97
+ uses: actions/setup-python@v5
98
+ with:
99
+ python-version: '3.12'
100
+ - name: Install uv
101
+ uses: astral-sh/setup-uv@v7
102
+ - name: Install dependencies
103
+ run: |
104
+ uv venv
105
+ uv pip install bandit
106
+ - name: Run bandit
107
+ run: uv run bandit -r fraisier/ -f json || true
108
+
109
+ build:
110
+ name: Build distribution
111
+ runs-on: ubuntu-latest
112
+ needs: [test, lint, type-check, security]
113
+
114
+ steps:
115
+ - uses: actions/checkout@v4
116
+ with:
117
+ fetch-depth: 0
118
+
119
+ - name: Install uv
120
+ uses: astral-sh/setup-uv@v7
121
+
122
+ - name: Build sdist and wheel
123
+ run: uv build
124
+
125
+ - name: Upload artifacts
126
+ uses: actions/upload-artifact@v5
127
+ with:
128
+ name: dist
129
+ path: dist/*
130
+
131
+ validate:
132
+ name: Validate built artifacts
133
+ runs-on: ubuntu-latest
134
+ needs: [build]
135
+
136
+ steps:
137
+ - name: Download artifacts
138
+ uses: actions/download-artifact@v5
139
+ with:
140
+ name: dist
141
+ path: dist
142
+
143
+ - name: Install validation tools
144
+ run: pip install twine
145
+
146
+ - name: Validate with twine
147
+ run: twine check dist/*
148
+
149
+ - name: List artifacts
150
+ run: |
151
+ echo "=== Built Artifacts ==="
152
+ ls -lh dist/
153
+
154
+ publish:
155
+ name: Publish to PyPI
156
+ runs-on: ubuntu-latest
157
+ needs: [validate]
158
+ environment: release
159
+ permissions:
160
+ id-token: write
161
+
162
+ steps:
163
+ - name: Download artifacts
164
+ uses: actions/download-artifact@v5
165
+ with:
166
+ name: dist
167
+ path: dist
168
+
169
+ - name: Install uv
170
+ uses: astral-sh/setup-uv@v7
171
+
172
+ - name: Publish to PyPI
173
+ run: uv publish --trusted-publishing always
174
+
175
+ create-release:
176
+ name: Create GitHub Release
177
+ runs-on: ubuntu-latest
178
+ needs: [publish]
179
+ permissions:
180
+ contents: write
181
+
182
+ steps:
183
+ - uses: actions/checkout@v4
184
+
185
+ - name: Download artifacts
186
+ uses: actions/download-artifact@v5
187
+ with:
188
+ name: dist
189
+ path: dist
190
+
191
+ - name: Create Release
192
+ uses: softprops/action-gh-release@v2
193
+ with:
194
+ generate_release_notes: true
195
+ files: dist/*
196
+ draft: false
197
+ prerelease: false
@@ -0,0 +1,95 @@
1
+ name: Python Version Matrix Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: python-matrix-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ test-matrix:
16
+ name: Test Python ${{ matrix.python-version }}
17
+ runs-on: ubuntu-latest
18
+
19
+ strategy:
20
+ fail-fast: false
21
+ matrix:
22
+ python-version: ['3.11', '3.12', '3.13']
23
+
24
+ services:
25
+ postgres:
26
+ image: postgres:16-alpine
27
+ env:
28
+ POSTGRES_USER: fraisier
29
+ POSTGRES_PASSWORD: fraisier
30
+ POSTGRES_DB: fraisier_test
31
+ options: >-
32
+ --health-cmd pg_isready
33
+ --health-interval 10s
34
+ --health-timeout 5s
35
+ --health-retries 5
36
+ ports:
37
+ - 5432:5432
38
+
39
+ steps:
40
+ - uses: actions/checkout@v4
41
+
42
+ - name: Set up Python ${{ matrix.python-version }}
43
+ uses: actions/setup-python@v5
44
+ with:
45
+ python-version: ${{ matrix.python-version }}
46
+
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@v7
49
+
50
+ - name: Install dependencies
51
+ run: |
52
+ uv venv
53
+ uv pip install ".[dev]"
54
+
55
+ - name: Verify environment
56
+ run: |
57
+ uv run python --version
58
+ pg_isready -h localhost -p 5432 -U fraisier && echo 'PostgreSQL ready' || echo 'PostgreSQL not ready'
59
+
60
+ - name: Run tests with coverage
61
+ env:
62
+ FRAISIER_TEST_PG_URL: postgresql://fraisier:fraisier@localhost:5432/fraisier_test
63
+ run: |
64
+ uv run pytest tests/ \
65
+ --cov=fraisier \
66
+ --cov-report=xml \
67
+ --cov-report=term-missing \
68
+ -v \
69
+ --tb=short
70
+
71
+ - name: Upload coverage to Codecov
72
+ uses: codecov/codecov-action@v5
73
+ with:
74
+ files: ./coverage.xml
75
+ flags: python-${{ matrix.python-version }}
76
+ name: Python-${{ matrix.python-version }}
77
+ fail_ci_if_error: false
78
+ continue-on-error: true
79
+
80
+ matrix-summary:
81
+ name: Python Version Matrix Summary
82
+ runs-on: ubuntu-latest
83
+ needs: [test-matrix]
84
+ if: always()
85
+
86
+ steps:
87
+ - name: Check matrix results
88
+ run: |
89
+ if [[ "${{ needs.test-matrix.result }}" == "success" ]]; then
90
+ echo "Python 3.11+ tests passed"
91
+ exit 0
92
+ else
93
+ echo "Python version testing failed"
94
+ exit 1
95
+ fi
@@ -0,0 +1,162 @@
1
+ name: Quality Gate
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: quality-gate-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ name: Tests
16
+ runs-on: ubuntu-latest
17
+
18
+ services:
19
+ postgres:
20
+ image: postgres:16-alpine
21
+ env:
22
+ POSTGRES_USER: fraisier
23
+ POSTGRES_PASSWORD: fraisier
24
+ POSTGRES_DB: fraisier_test
25
+ options: >-
26
+ --health-cmd pg_isready
27
+ --health-interval 10s
28
+ --health-timeout 5s
29
+ --health-retries 5
30
+ ports:
31
+ - 5432:5432
32
+
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - name: Set up Python
37
+ uses: actions/setup-python@v5
38
+ with:
39
+ python-version: '3.12'
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@v7
43
+
44
+ - name: Install dependencies
45
+ run: |
46
+ uv venv
47
+ uv pip install ".[dev]"
48
+
49
+ - name: Verify environment
50
+ run: |
51
+ uv run python --version
52
+ uv run pytest --version
53
+ pg_isready -h localhost -p 5432 -U fraisier && echo 'PostgreSQL ready' || echo 'PostgreSQL not ready'
54
+
55
+ - name: Run tests with coverage
56
+ env:
57
+ FRAISIER_TEST_PG_URL: postgresql://fraisier:fraisier@localhost:5432/fraisier_test
58
+ run: |
59
+ uv run pytest tests/ \
60
+ --cov=fraisier \
61
+ --cov-report=xml \
62
+ --cov-report=term-missing \
63
+ -v \
64
+ --tb=short
65
+
66
+ - name: Upload coverage
67
+ uses: codecov/codecov-action@v5
68
+ with:
69
+ files: ./coverage.xml
70
+ flags: unittests
71
+ name: Python-3.12
72
+ fail_ci_if_error: false
73
+ continue-on-error: true
74
+
75
+ lint:
76
+ name: Lint
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - name: Set up Python
81
+ uses: actions/setup-python@v5
82
+ with:
83
+ python-version: '3.12'
84
+ - name: Install uv
85
+ uses: astral-sh/setup-uv@v7
86
+ - name: Install dependencies
87
+ run: |
88
+ uv venv
89
+ uv pip install ruff
90
+ - name: Run ruff check
91
+ run: uv run ruff check .
92
+ - name: Run ruff format check
93
+ run: uv run ruff format --check .
94
+
95
+ type-check:
96
+ name: Type Check
97
+ runs-on: ubuntu-latest
98
+ steps:
99
+ - uses: actions/checkout@v4
100
+ - name: Set up Python
101
+ uses: actions/setup-python@v5
102
+ with:
103
+ python-version: '3.12'
104
+ - name: Install uv
105
+ uses: astral-sh/setup-uv@v7
106
+ - name: Install ty
107
+ run: uv tool install ty
108
+ - name: Run ty type check
109
+ run: ty check fraisier/
110
+ timeout-minutes: 5
111
+ continue-on-error: true
112
+
113
+ security:
114
+ name: Security
115
+ runs-on: ubuntu-latest
116
+ steps:
117
+ - uses: actions/checkout@v4
118
+ - name: Set up Python
119
+ uses: actions/setup-python@v5
120
+ with:
121
+ python-version: '3.12'
122
+ - name: Install uv
123
+ uses: astral-sh/setup-uv@v7
124
+ - name: Install dependencies
125
+ run: |
126
+ uv venv
127
+ uv pip install bandit
128
+ - name: Run bandit security scan
129
+ run: uv run bandit -r fraisier/ -f json || true
130
+ - name: Run Trivy vulnerability scanner
131
+ uses: aquasecurity/trivy-action@master
132
+ with:
133
+ scan-type: 'fs'
134
+ scan-ref: '.'
135
+ format: 'table'
136
+ severity: 'CRITICAL,HIGH'
137
+ continue-on-error: true
138
+
139
+ quality-gate:
140
+ name: Quality Gate
141
+ runs-on: ubuntu-latest
142
+ needs: [test, lint, type-check, security]
143
+ if: always()
144
+ steps:
145
+ - name: Check quality gate status
146
+ run: |
147
+ if [[ "${{ needs.test.result }}" != "success" ]]; then
148
+ echo "Tests failed"
149
+ exit 1
150
+ fi
151
+ if [[ "${{ needs.lint.result }}" != "success" ]]; then
152
+ echo "Lint checks failed"
153
+ exit 1
154
+ fi
155
+ if [[ "${{ needs.type-check.result }}" == "failure" ]]; then
156
+ echo "Type check has warnings (non-blocking)"
157
+ fi
158
+ if [[ "${{ needs.security.result }}" != "success" ]]; then
159
+ echo "Security checks failed"
160
+ exit 1
161
+ fi
162
+ echo "All quality checks passed"
@@ -0,0 +1,15 @@
1
+ *.db
2
+ tmp/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ dist/
10
+ *.egg-info/
11
+ .env
12
+ !.env.example
13
+ *.key
14
+ *.pem
15
+ .phases/