platform-fastapicommon 0.1.0__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.
- platform_fastapicommon-0.1.0/.dockerignore +29 -0
- platform_fastapicommon-0.1.0/.env-example +33 -0
- platform_fastapicommon-0.1.0/.github/CODEOWNERS +10 -0
- platform_fastapicommon-0.1.0/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- platform_fastapicommon-0.1.0/.github/ISSUE_TEMPLATE/feature_request.md +28 -0
- platform_fastapicommon-0.1.0/.github/SECURITY.md +32 -0
- platform_fastapicommon-0.1.0/.github/dependabot.yml +22 -0
- platform_fastapicommon-0.1.0/.github/pull_request_template.md +59 -0
- platform_fastapicommon-0.1.0/.github/workflows/ci.yml +334 -0
- platform_fastapicommon-0.1.0/.github/workflows/quality.yml +54 -0
- platform_fastapicommon-0.1.0/.github/workflows/release.yml +208 -0
- platform_fastapicommon-0.1.0/.github/workflows/test.yml +39 -0
- platform_fastapicommon-0.1.0/.github/workflows/validate.yml +33 -0
- platform_fastapicommon-0.1.0/.gitignore +221 -0
- platform_fastapicommon-0.1.0/.pre-commit-config.yaml +37 -0
- platform_fastapicommon-0.1.0/ARCHITECTURE.md +752 -0
- platform_fastapicommon-0.1.0/CHANGELOG.md +120 -0
- platform_fastapicommon-0.1.0/CONTRIBUTING.md +145 -0
- platform_fastapicommon-0.1.0/Dockerfile +49 -0
- platform_fastapicommon-0.1.0/Makefile +195 -0
- platform_fastapicommon-0.1.0/PKG-INFO +733 -0
- platform_fastapicommon-0.1.0/README.md +688 -0
- platform_fastapicommon-0.1.0/SECURITY.md +65 -0
- platform_fastapicommon-0.1.0/VERSIONING.md +201 -0
- platform_fastapicommon-0.1.0/docker/grafana/provisioning/datasources/datasources.yaml +39 -0
- platform_fastapicommon-0.1.0/docker/loki/loki.yaml +29 -0
- platform_fastapicommon-0.1.0/docker/otel-collector/config.yaml +30 -0
- platform_fastapicommon-0.1.0/docker/prometheus/prometheus.yml +8 -0
- platform_fastapicommon-0.1.0/docker/prometheus/prometheus.yml.tpl +8 -0
- platform_fastapicommon-0.1.0/docker/promtail/promtail.yaml +30 -0
- platform_fastapicommon-0.1.0/docker/tempo/tempo.yaml +26 -0
- platform_fastapicommon-0.1.0/docker-compose.yml +86 -0
- platform_fastapicommon-0.1.0/docs/Architecture.md +8 -0
- platform_fastapicommon-0.1.0/docs/MigrationGuide.md +26 -0
- platform_fastapicommon-0.1.0/docs/adr/ADR-001-logging.md +25 -0
- platform_fastapicommon-0.1.0/docs/adr/ADR-002-tracing.md +26 -0
- platform_fastapicommon-0.1.0/docs/adr/ADR-003-metrics.md +32 -0
- platform_fastapicommon-0.1.0/docs/adr/ADR-004-context-propagation.md +30 -0
- platform_fastapicommon-0.1.0/docs/architecture/README.md +21 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/bootstrap-flow.mmd +28 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/consuming-service-wiring.mmd +20 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/documentation-assets.mmd +34 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/exception-handling-flow.mmd +27 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/grpc-interceptors.mmd +29 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/metrics-flow.mmd +30 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/module-dependencies.mmd +60 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/observability-stack.mmd +31 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/rbac-dependency-flow.mmd +26 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/request-flow.mmd +33 -0
- platform_fastapicommon-0.1.0/docs/architecture/mermaid/tracing-initialization.mmd +19 -0
- platform_fastapicommon-0.1.0/examples/__init__.py +0 -0
- platform_fastapicommon-0.1.0/examples/fastapi_service/__init__.py +0 -0
- platform_fastapicommon-0.1.0/examples/fastapi_service/main.py +43 -0
- platform_fastapicommon-0.1.0/examples/grpc_service/__init__.py +0 -0
- platform_fastapicommon-0.1.0/examples/grpc_service/server.py +52 -0
- platform_fastapicommon-0.1.0/pyproject.toml +105 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/__init__.py +15 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/auth/__init__.py +24 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/auth/dependencies.py +23 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/auth/headers.py +15 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/bootstrap/__init__.py +3 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/bootstrap/app.py +66 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/config/__init__.py +3 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/config/settings.py +26 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/context/__init__.py +28 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/context/models.py +46 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/context/vars.py +79 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/__init__.py +19 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/errors.py +50 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/exceptions/handlers.py +51 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/grpc/__init__.py +4 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/grpc/client.py +63 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/grpc/server.py +95 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/health/__init__.py +3 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/health/router.py +44 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/logging/__init__.py +4 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/logging/config.py +52 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/logging/logger.py +13 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/metrics/__init__.py +19 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/metrics/definitions.py +33 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/metrics/middleware.py +31 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/metrics/router.py +12 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/middleware/__init__.py +4 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/middleware/context.py +55 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/middleware/timeout.py +28 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/propagation/__init__.py +3 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/propagation/client.py +48 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/rbac/__init__.py +3 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/rbac/dependencies.py +49 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/telemetry/__init__.py +7 -0
- platform_fastapicommon-0.1.0/src/fastapicommon/telemetry/tracing.py +44 -0
- platform_fastapicommon-0.1.0/tests/auth/test_auth.py +42 -0
- platform_fastapicommon-0.1.0/tests/bootstrap/test_bootstrap.py +81 -0
- platform_fastapicommon-0.1.0/tests/config/test_config.py +43 -0
- platform_fastapicommon-0.1.0/tests/context/test_context.py +78 -0
- platform_fastapicommon-0.1.0/tests/exceptions/test_exceptions.py +88 -0
- platform_fastapicommon-0.1.0/tests/grpc/test_grpc.py +153 -0
- platform_fastapicommon-0.1.0/tests/health/test_health.py +38 -0
- platform_fastapicommon-0.1.0/tests/logging/test_logging.py +41 -0
- platform_fastapicommon-0.1.0/tests/metrics/test_metrics.py +51 -0
- platform_fastapicommon-0.1.0/tests/middleware/test_context_middleware.py +69 -0
- platform_fastapicommon-0.1.0/tests/middleware/test_timeout_middleware.py +35 -0
- platform_fastapicommon-0.1.0/tests/propagation/test_propagation.py +43 -0
- platform_fastapicommon-0.1.0/tests/rbac/test_rbac.py +41 -0
- platform_fastapicommon-0.1.0/tests/telemetry/test_telemetry.py +15 -0
- platform_fastapicommon-0.1.0/tests/test_package.py +10 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.git
|
|
2
|
+
.github
|
|
3
|
+
.claude
|
|
4
|
+
.cursor
|
|
5
|
+
.venv
|
|
6
|
+
.env
|
|
7
|
+
.env.*
|
|
8
|
+
!.env-example
|
|
9
|
+
logs
|
|
10
|
+
*.log
|
|
11
|
+
coverage.*
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov
|
|
14
|
+
.pytest_cache
|
|
15
|
+
.mypy_cache
|
|
16
|
+
.ruff_cache
|
|
17
|
+
__pycache__
|
|
18
|
+
*.pyc
|
|
19
|
+
dist
|
|
20
|
+
build
|
|
21
|
+
*.egg-info
|
|
22
|
+
# test code and tooling config are not needed at build/runtime
|
|
23
|
+
tests/
|
|
24
|
+
*.md
|
|
25
|
+
!README.md
|
|
26
|
+
Makefile
|
|
27
|
+
docker-compose.yml
|
|
28
|
+
docker/prometheus/prometheus.yml
|
|
29
|
+
docs/
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copy to .env for local development: cp .env-example .env
|
|
2
|
+
|
|
3
|
+
# --- Application (examples/fastapi_service) ---
|
|
4
|
+
APP_NAME=platform-fastapicommon-example
|
|
5
|
+
APP_ENV=dev
|
|
6
|
+
APP_PORT=8080
|
|
7
|
+
BUILD_VERSION=dev
|
|
8
|
+
|
|
9
|
+
# --- OpenTelemetry ---
|
|
10
|
+
# Set to reach the local otel-collector started by `make docker-up` (OTLP/gRPC).
|
|
11
|
+
# Leave unset to record spans without exporting them.
|
|
12
|
+
OTLP_ENDPOINT=localhost:4317
|
|
13
|
+
ENABLE_TRACING=true
|
|
14
|
+
ENABLE_METRICS=true
|
|
15
|
+
|
|
16
|
+
# --- Local logs -> Loki (Promtail tails this file when using make docker-up) ---
|
|
17
|
+
LOG_FILE=logs/app.log
|
|
18
|
+
|
|
19
|
+
# --- Observability stack (Docker host port mappings) ---
|
|
20
|
+
OTEL_COLLECTOR_GRPC_PORT=4317
|
|
21
|
+
OTEL_COLLECTOR_HTTP_PORT=4318
|
|
22
|
+
TEMPO_HTTP_PORT=3200
|
|
23
|
+
LOKI_HTTP_PORT=3100
|
|
24
|
+
PROMETHEUS_HTTP_PORT=9090
|
|
25
|
+
GRAFANA_HTTP_PORT=3000
|
|
26
|
+
GRAFANA_ADMIN_USER=admin
|
|
27
|
+
GRAFANA_ADMIN_PASSWORD=admin
|
|
28
|
+
|
|
29
|
+
# Prometheus scrapes the example app on the host, not in the compose network.
|
|
30
|
+
PROMETHEUS_SCRAPE_TARGET=host.docker.internal:8080
|
|
31
|
+
|
|
32
|
+
# --- Tooling ---
|
|
33
|
+
PYTHON=python3
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Default: every file requires review from the platform team.
|
|
2
|
+
# Replace @BCBP-SOLUTIONS-FZC-LLC/platform-team with your actual team slug.
|
|
3
|
+
* @BCBP-SOLUTIONS-FZC-LLC/platform-team
|
|
4
|
+
|
|
5
|
+
# Workflow and release automation changes require explicit sign-off.
|
|
6
|
+
.github/workflows/ @BCBP-SOLUTIONS-FZC-LLC/platform-team
|
|
7
|
+
.github/dependabot.yml @BCBP-SOLUTIONS-FZC-LLC/platform-team
|
|
8
|
+
|
|
9
|
+
# Public API surface — extra scrutiny.
|
|
10
|
+
src/fastapicommon/ @BCBP-SOLUTIONS-FZC-LLC/platform-team
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a defect in fastapicommon middleware or helpers
|
|
4
|
+
title: '[BUG] '
|
|
5
|
+
labels: bug
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Description
|
|
10
|
+
A clear description of the bug.
|
|
11
|
+
|
|
12
|
+
## Library version
|
|
13
|
+
`fastapicommon==X.Y.Z`
|
|
14
|
+
|
|
15
|
+
## Python version
|
|
16
|
+
`python --version` output
|
|
17
|
+
|
|
18
|
+
## Steps to reproduce
|
|
19
|
+
1.
|
|
20
|
+
2.
|
|
21
|
+
3.
|
|
22
|
+
|
|
23
|
+
## Expected behaviour
|
|
24
|
+
What you expected to happen.
|
|
25
|
+
|
|
26
|
+
## Actual behaviour
|
|
27
|
+
What actually happened. Include error messages, stack traces, or log output.
|
|
28
|
+
|
|
29
|
+
## Minimal reproduction
|
|
30
|
+
```python
|
|
31
|
+
# paste the smallest Python snippet that triggers the bug
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Additional context
|
|
35
|
+
Any other relevant context (OS, container environment, related issues).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Propose a new middleware, helper, or enhancement
|
|
4
|
+
title: '[FEAT] '
|
|
5
|
+
labels: enhancement
|
|
6
|
+
assignees: ''
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Problem / motivation
|
|
10
|
+
What problem does this solve? Who is affected?
|
|
11
|
+
|
|
12
|
+
## Proposed solution
|
|
13
|
+
Describe the API or behaviour change you'd like.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
# Example usage of the proposed feature
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Alternatives considered
|
|
20
|
+
Other approaches you evaluated and why you ruled them out.
|
|
21
|
+
|
|
22
|
+
## Acceptance criteria
|
|
23
|
+
- [ ]
|
|
24
|
+
- [ ]
|
|
25
|
+
- [ ]
|
|
26
|
+
|
|
27
|
+
## Additional context
|
|
28
|
+
Links to related issues, RFCs, or prior art.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---------|-----------|
|
|
7
|
+
| 0.x | ✅ Active |
|
|
8
|
+
|
|
9
|
+
Older major versions are not patched. Upgrade to the latest 0.x release.
|
|
10
|
+
|
|
11
|
+
## Reporting a Vulnerability
|
|
12
|
+
|
|
13
|
+
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
14
|
+
|
|
15
|
+
Email: vijay@bcbpsolutions.com
|
|
16
|
+
|
|
17
|
+
Include in your report:
|
|
18
|
+
- Description of the vulnerability and affected component
|
|
19
|
+
- Steps to reproduce
|
|
20
|
+
- Potential impact (data exposure, privilege escalation, DoS, etc.)
|
|
21
|
+
- Suggested fix or patch (if any)
|
|
22
|
+
|
|
23
|
+
### Response timeline
|
|
24
|
+
|
|
25
|
+
| Step | Target |
|
|
26
|
+
|------|--------|
|
|
27
|
+
| Initial acknowledgement | 48 hours |
|
|
28
|
+
| Severity assessment | 5 business days |
|
|
29
|
+
| Patch release (critical/high) | 14 days |
|
|
30
|
+
| Public disclosure | After patch ships |
|
|
31
|
+
|
|
32
|
+
We follow responsible disclosure. We will credit reporters in the release notes unless anonymity is requested.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: pip
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: weekly
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
labels: [dependencies, python]
|
|
9
|
+
|
|
10
|
+
- package-ecosystem: github-actions
|
|
11
|
+
directory: /
|
|
12
|
+
schedule:
|
|
13
|
+
interval: weekly
|
|
14
|
+
open-pull-requests-limit: 5
|
|
15
|
+
labels: [dependencies, github-actions]
|
|
16
|
+
|
|
17
|
+
- package-ecosystem: docker
|
|
18
|
+
directory: /
|
|
19
|
+
schedule:
|
|
20
|
+
interval: weekly
|
|
21
|
+
open-pull-requests-limit: 5
|
|
22
|
+
labels: [dependencies, docker]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
Provide a clear description of the changes.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Type of Change
|
|
7
|
+
- [ ] Bug fix
|
|
8
|
+
- [ ] New feature
|
|
9
|
+
- [ ] Refactor
|
|
10
|
+
- [ ] Documentation
|
|
11
|
+
- [ ] Test
|
|
12
|
+
- [ ] Breaking change
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Testing
|
|
17
|
+
- [ ] Unit tests added/updated
|
|
18
|
+
- [ ] All tests passing (`make test`)
|
|
19
|
+
- [ ] Manual testing performed (if required)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Checklist
|
|
24
|
+
|
|
25
|
+
### Code Quality
|
|
26
|
+
- [ ] Code is properly formatted (`make format` — ruff format + black)
|
|
27
|
+
- [ ] Linting passed (`make lint` — ruff)
|
|
28
|
+
- [ ] Type checking passed (`make lint` — mypy --strict)
|
|
29
|
+
- [ ] No debug logs / commented code
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### Observability (IMPORTANT for your system)
|
|
34
|
+
- [ ] Logs include `request_id` / `correlation_id` (via `fastapicommon.logging.get_logger`, not stdlib `logging`)
|
|
35
|
+
- [ ] Errors use a `PlatformException` subclass, not a bare `HTTPException`
|
|
36
|
+
- [ ] Middleware ordering preserved if middleware changed (see `ARCHITECTURE.md#execution-model`)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### Security
|
|
41
|
+
- [ ] No secrets hardcoded
|
|
42
|
+
- [ ] Input validation handled
|
|
43
|
+
- [ ] RBAC (`RequireRole` / `RequireAnyRole` / `RequireAllRoles`) checked (if endpoint changes)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### Documentation
|
|
48
|
+
- [ ] README updated (if needed)
|
|
49
|
+
- [ ] `ARCHITECTURE.md` / ADRs updated (if a cross-cutting standard changed)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 🔗 Related Issue
|
|
54
|
+
Closes #<issue-id>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Deployment Notes
|
|
59
|
+
Mention anything important for deployment.
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# CI for https://github.com/BCBP-SOLUTIONS-FZC-LLC/platform-fastapicommon
|
|
2
|
+
name: CI
|
|
3
|
+
|
|
4
|
+
"on":
|
|
5
|
+
push:
|
|
6
|
+
branches: [main, develop]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main, develop]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
validate:
|
|
16
|
+
name: Validate
|
|
17
|
+
uses: ./.github/workflows/validate.yml
|
|
18
|
+
|
|
19
|
+
coverage:
|
|
20
|
+
name: Coverage (Python ${{ matrix.python-version }})
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
needs: validate
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: ['3.11', '3.12', '3.13', '3.14']
|
|
26
|
+
steps:
|
|
27
|
+
- name: Checkout
|
|
28
|
+
uses: actions/checkout@v7
|
|
29
|
+
|
|
30
|
+
- name: Set up Python
|
|
31
|
+
uses: actions/setup-python@v7
|
|
32
|
+
with:
|
|
33
|
+
python-version: ${{ matrix.python-version }}
|
|
34
|
+
cache: pip
|
|
35
|
+
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: pip install -e ".[dev,grpc,examples]"
|
|
38
|
+
|
|
39
|
+
- name: Test with coverage (>=90% required, enforced by pyproject.toml)
|
|
40
|
+
run: pytest
|
|
41
|
+
|
|
42
|
+
- name: Upload coverage report
|
|
43
|
+
uses: actions/upload-artifact@v7
|
|
44
|
+
if: always()
|
|
45
|
+
with:
|
|
46
|
+
name: coverage-report-py${{ matrix.python-version }}
|
|
47
|
+
path: coverage.xml
|
|
48
|
+
if-no-files-found: ignore
|
|
49
|
+
|
|
50
|
+
build:
|
|
51
|
+
name: Build
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
needs: coverage
|
|
54
|
+
steps:
|
|
55
|
+
- name: Checkout
|
|
56
|
+
uses: actions/checkout@v7
|
|
57
|
+
|
|
58
|
+
- name: Set up Python
|
|
59
|
+
uses: actions/setup-python@v7
|
|
60
|
+
with:
|
|
61
|
+
python-version: '3.12'
|
|
62
|
+
cache: pip
|
|
63
|
+
|
|
64
|
+
- name: Build wheel + sdist
|
|
65
|
+
run: |
|
|
66
|
+
pip install build
|
|
67
|
+
python -m build
|
|
68
|
+
|
|
69
|
+
- name: Verify build artifacts
|
|
70
|
+
run: ls dist/*.whl dist/*.tar.gz
|
|
71
|
+
|
|
72
|
+
- name: Upload build artifacts
|
|
73
|
+
uses: actions/upload-artifact@v7
|
|
74
|
+
with:
|
|
75
|
+
name: fastapicommon-dist
|
|
76
|
+
path: dist/
|
|
77
|
+
if-no-files-found: error
|
|
78
|
+
|
|
79
|
+
lint-dockerfile:
|
|
80
|
+
name: Lint Dockerfile
|
|
81
|
+
runs-on: ubuntu-latest
|
|
82
|
+
steps:
|
|
83
|
+
- name: Checkout
|
|
84
|
+
uses: actions/checkout@v7
|
|
85
|
+
|
|
86
|
+
- name: Run hadolint
|
|
87
|
+
uses: hadolint/hadolint-action@v3.3.0
|
|
88
|
+
with:
|
|
89
|
+
dockerfile: Dockerfile
|
|
90
|
+
failure-threshold: warning
|
|
91
|
+
|
|
92
|
+
build-image:
|
|
93
|
+
name: Build image (cache)
|
|
94
|
+
runs-on: ubuntu-latest
|
|
95
|
+
steps:
|
|
96
|
+
- name: Checkout
|
|
97
|
+
uses: actions/checkout@v7
|
|
98
|
+
|
|
99
|
+
# Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
|
|
100
|
+
# times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
|
|
101
|
+
# known transient flake, not a config problem. Retry once with the same action
|
|
102
|
+
# before failing the job.
|
|
103
|
+
- name: Set up Docker Buildx
|
|
104
|
+
id: buildx
|
|
105
|
+
continue-on-error: true
|
|
106
|
+
uses: docker/setup-buildx-action@v4
|
|
107
|
+
|
|
108
|
+
- name: Set up Docker Buildx (retry)
|
|
109
|
+
if: steps.buildx.outcome == 'failure'
|
|
110
|
+
uses: docker/setup-buildx-action@v4
|
|
111
|
+
|
|
112
|
+
- name: Build (no push)
|
|
113
|
+
uses: docker/build-push-action@v7
|
|
114
|
+
with:
|
|
115
|
+
context: .
|
|
116
|
+
file: ./Dockerfile
|
|
117
|
+
push: false
|
|
118
|
+
tags: platform-fastapicommon-example:ci
|
|
119
|
+
cache-from: type=gha
|
|
120
|
+
cache-to: type=gha,mode=max
|
|
121
|
+
|
|
122
|
+
trivy-scan:
|
|
123
|
+
name: Trivy CVE scan
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
needs: build-image
|
|
126
|
+
steps:
|
|
127
|
+
- name: Checkout
|
|
128
|
+
uses: actions/checkout@v7
|
|
129
|
+
|
|
130
|
+
# Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
|
|
131
|
+
# times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
|
|
132
|
+
# known transient flake, not a config problem. Retry once with the same action
|
|
133
|
+
# before failing the job.
|
|
134
|
+
- name: Set up Docker Buildx
|
|
135
|
+
id: buildx
|
|
136
|
+
continue-on-error: true
|
|
137
|
+
uses: docker/setup-buildx-action@v4
|
|
138
|
+
|
|
139
|
+
- name: Set up Docker Buildx (retry)
|
|
140
|
+
if: steps.buildx.outcome == 'failure'
|
|
141
|
+
uses: docker/setup-buildx-action@v4
|
|
142
|
+
|
|
143
|
+
# Rebuilds against the GHA cache populated by "Build image (cache)" — fast, and
|
|
144
|
+
# keeps this job self-contained (no cross-job image tarball transfer needed).
|
|
145
|
+
- name: Build image (load, cached)
|
|
146
|
+
uses: docker/build-push-action@v7
|
|
147
|
+
with:
|
|
148
|
+
context: .
|
|
149
|
+
file: ./Dockerfile
|
|
150
|
+
push: false
|
|
151
|
+
load: true
|
|
152
|
+
tags: platform-fastapicommon-example:ci
|
|
153
|
+
cache-from: type=gha
|
|
154
|
+
|
|
155
|
+
- name: Run Trivy vulnerability scan
|
|
156
|
+
uses: aquasecurity/trivy-action@v0.36.0
|
|
157
|
+
with:
|
|
158
|
+
image-ref: platform-fastapicommon-example:ci
|
|
159
|
+
format: table
|
|
160
|
+
severity: HIGH,CRITICAL
|
|
161
|
+
ignore-unfixed: true
|
|
162
|
+
exit-code: '1'
|
|
163
|
+
scanners: vuln
|
|
164
|
+
|
|
165
|
+
smoke-tests:
|
|
166
|
+
name: Smoke tests
|
|
167
|
+
runs-on: ubuntu-latest
|
|
168
|
+
needs: build-image
|
|
169
|
+
steps:
|
|
170
|
+
- name: Checkout
|
|
171
|
+
uses: actions/checkout@v7
|
|
172
|
+
|
|
173
|
+
# Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
|
|
174
|
+
# times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
|
|
175
|
+
# known transient flake, not a config problem. Retry once with the same action
|
|
176
|
+
# before failing the job.
|
|
177
|
+
- name: Set up Docker Buildx
|
|
178
|
+
id: buildx
|
|
179
|
+
continue-on-error: true
|
|
180
|
+
uses: docker/setup-buildx-action@v4
|
|
181
|
+
|
|
182
|
+
- name: Set up Docker Buildx (retry)
|
|
183
|
+
if: steps.buildx.outcome == 'failure'
|
|
184
|
+
uses: docker/setup-buildx-action@v4
|
|
185
|
+
|
|
186
|
+
- name: Build image (load, cached)
|
|
187
|
+
uses: docker/build-push-action@v7
|
|
188
|
+
with:
|
|
189
|
+
context: .
|
|
190
|
+
file: ./Dockerfile
|
|
191
|
+
push: false
|
|
192
|
+
load: true
|
|
193
|
+
tags: platform-fastapicommon-example:ci
|
|
194
|
+
cache-from: type=gha
|
|
195
|
+
|
|
196
|
+
- name: Run container
|
|
197
|
+
run: docker run -d --name smoke-test -p 8080:8080 platform-fastapicommon-example:ci
|
|
198
|
+
|
|
199
|
+
- name: Wait for health check
|
|
200
|
+
run: |
|
|
201
|
+
for i in $(seq 1 30); do
|
|
202
|
+
if curl -fsS http://localhost:8080/health >/dev/null 2>&1; then
|
|
203
|
+
echo "healthy after ${i}s"
|
|
204
|
+
exit 0
|
|
205
|
+
fi
|
|
206
|
+
sleep 1
|
|
207
|
+
done
|
|
208
|
+
echo "Service did not become healthy in time" >&2
|
|
209
|
+
docker logs smoke-test
|
|
210
|
+
exit 1
|
|
211
|
+
|
|
212
|
+
- name: Smoke test endpoints
|
|
213
|
+
run: |
|
|
214
|
+
set -euo pipefail
|
|
215
|
+
curl -fsS http://localhost:8080/health | grep -q '"status":"ok"'
|
|
216
|
+
curl -fsS http://localhost:8080/ready | grep -q '"status":"ok"'
|
|
217
|
+
curl -fsS http://localhost:8080/metrics | grep -q 'http_requests_total'
|
|
218
|
+
curl -fsS http://localhost:8080/widgets/me | grep -q '"user_id":null'
|
|
219
|
+
echo "All smoke checks passed"
|
|
220
|
+
|
|
221
|
+
- name: Container logs
|
|
222
|
+
if: always()
|
|
223
|
+
run: docker logs smoke-test || true
|
|
224
|
+
|
|
225
|
+
- name: Stop container
|
|
226
|
+
if: always()
|
|
227
|
+
run: docker rm -f smoke-test || true
|
|
228
|
+
|
|
229
|
+
docker:
|
|
230
|
+
name: Docker image (example service)
|
|
231
|
+
runs-on: ubuntu-latest
|
|
232
|
+
needs: build
|
|
233
|
+
if: github.event_name == 'push'
|
|
234
|
+
permissions:
|
|
235
|
+
contents: read
|
|
236
|
+
packages: write
|
|
237
|
+
steps:
|
|
238
|
+
- name: Checkout
|
|
239
|
+
uses: actions/checkout@v7
|
|
240
|
+
|
|
241
|
+
- name: Log in to GHCR
|
|
242
|
+
uses: docker/login-action@v4
|
|
243
|
+
with:
|
|
244
|
+
registry: ghcr.io
|
|
245
|
+
username: ${{ github.actor }}
|
|
246
|
+
password: ${{ secrets.GITHUB_TOKEN }}
|
|
247
|
+
|
|
248
|
+
- name: Docker metadata
|
|
249
|
+
id: meta
|
|
250
|
+
uses: docker/metadata-action@v6
|
|
251
|
+
with:
|
|
252
|
+
images: ghcr.io/bcbp-solutions-fzc-llc/platform-fastapicommon
|
|
253
|
+
tags: |
|
|
254
|
+
type=ref,event=branch
|
|
255
|
+
type=sha,prefix=sha-,format=short
|
|
256
|
+
|
|
257
|
+
# Booting buildx pulls the moby/buildkit image from Docker Hub, which occasionally
|
|
258
|
+
# times out on shared GitHub-hosted runner IPs ("context deadline exceeded") — a
|
|
259
|
+
# known transient flake, not a config problem. Retry once with the same action
|
|
260
|
+
# before failing the job.
|
|
261
|
+
- name: Set up Docker Buildx
|
|
262
|
+
id: buildx
|
|
263
|
+
continue-on-error: true
|
|
264
|
+
uses: docker/setup-buildx-action@v4
|
|
265
|
+
|
|
266
|
+
- name: Set up Docker Buildx (retry)
|
|
267
|
+
if: steps.buildx.outcome == 'failure'
|
|
268
|
+
uses: docker/setup-buildx-action@v4
|
|
269
|
+
|
|
270
|
+
- name: Build and push image
|
|
271
|
+
uses: docker/build-push-action@v7
|
|
272
|
+
with:
|
|
273
|
+
context: .
|
|
274
|
+
file: ./Dockerfile
|
|
275
|
+
push: true
|
|
276
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
277
|
+
build-args: BUILD_VERSION=dev-${{ github.sha }}
|
|
278
|
+
cache-from: type=gha
|
|
279
|
+
cache-to: type=gha,mode=max
|
|
280
|
+
|
|
281
|
+
pr-summary:
|
|
282
|
+
name: PR summary
|
|
283
|
+
runs-on: ubuntu-latest
|
|
284
|
+
needs: [validate, build-image, lint-dockerfile, trivy-scan, smoke-tests]
|
|
285
|
+
if: always() && github.event_name == 'pull_request'
|
|
286
|
+
permissions:
|
|
287
|
+
pull-requests: write
|
|
288
|
+
steps:
|
|
289
|
+
- name: Post summary comment
|
|
290
|
+
uses: actions/github-script@v9
|
|
291
|
+
with:
|
|
292
|
+
script: |
|
|
293
|
+
const results = {
|
|
294
|
+
'Validate (Quality + Test)': '${{ needs.validate.result }}',
|
|
295
|
+
'Build image (cache)': '${{ needs.build-image.result }}',
|
|
296
|
+
'Lint Dockerfile': '${{ needs.lint-dockerfile.result }}',
|
|
297
|
+
'Trivy CVE scan': '${{ needs.trivy-scan.result }}',
|
|
298
|
+
'Smoke tests': '${{ needs.smoke-tests.result }}',
|
|
299
|
+
};
|
|
300
|
+
const icon = (r) => (r === 'success' ? '✅' : r === 'skipped' ? '⏭️' : '❌');
|
|
301
|
+
const rows = Object.entries(results)
|
|
302
|
+
.map(([name, result]) => `| ${name} | ${icon(result)} ${result} |`)
|
|
303
|
+
.join('\n');
|
|
304
|
+
const body = [
|
|
305
|
+
'### CI summary',
|
|
306
|
+
'',
|
|
307
|
+
'| Check | Result |',
|
|
308
|
+
'|---|---|',
|
|
309
|
+
rows,
|
|
310
|
+
].join('\n');
|
|
311
|
+
|
|
312
|
+
const marker = '<!-- pr-summary-bot -->';
|
|
313
|
+
const fullBody = `${marker}\n${body}`;
|
|
314
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
315
|
+
owner: context.repo.owner,
|
|
316
|
+
repo: context.repo.repo,
|
|
317
|
+
issue_number: context.issue.number,
|
|
318
|
+
});
|
|
319
|
+
const existing = comments.find((c) => c.body.includes(marker));
|
|
320
|
+
if (existing) {
|
|
321
|
+
await github.rest.issues.updateComment({
|
|
322
|
+
owner: context.repo.owner,
|
|
323
|
+
repo: context.repo.repo,
|
|
324
|
+
comment_id: existing.id,
|
|
325
|
+
body: fullBody,
|
|
326
|
+
});
|
|
327
|
+
} else {
|
|
328
|
+
await github.rest.issues.createComment({
|
|
329
|
+
owner: context.repo.owner,
|
|
330
|
+
repo: context.repo.repo,
|
|
331
|
+
issue_number: context.issue.number,
|
|
332
|
+
body: fullBody,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Reusable workflow — called by validate.yml's "Quality" job. Its job id ("quality")
|
|
2
|
+
# combines with the calling chain to produce the "Validate / Quality / quality" required
|
|
3
|
+
# status check.
|
|
4
|
+
# Runs: checkout → Python setup → install → pip check → format check → ruff → mypy → pip-audit
|
|
5
|
+
name: Quality Check
|
|
6
|
+
|
|
7
|
+
"on":
|
|
8
|
+
workflow_call:
|
|
9
|
+
inputs:
|
|
10
|
+
ref:
|
|
11
|
+
description: 'Git ref to check out (leave empty to use the caller ref)'
|
|
12
|
+
type: string
|
|
13
|
+
required: false
|
|
14
|
+
default: ''
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
quality:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@v7
|
|
25
|
+
with:
|
|
26
|
+
ref: ${{ inputs.ref || github.ref }}
|
|
27
|
+
|
|
28
|
+
- name: Set up Python
|
|
29
|
+
uses: actions/setup-python@v7
|
|
30
|
+
with:
|
|
31
|
+
python-version: '3.12'
|
|
32
|
+
cache: pip
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: pip install -e ".[dev,grpc,examples]"
|
|
36
|
+
|
|
37
|
+
- name: Verify dependency resolution
|
|
38
|
+
run: pip check
|
|
39
|
+
|
|
40
|
+
- name: Format check (ruff format + black --check)
|
|
41
|
+
run: |
|
|
42
|
+
ruff format --check src tests examples
|
|
43
|
+
black --check src tests examples
|
|
44
|
+
|
|
45
|
+
- name: Lint (ruff)
|
|
46
|
+
run: ruff check src tests examples
|
|
47
|
+
|
|
48
|
+
- name: Type check (mypy --strict)
|
|
49
|
+
run: mypy
|
|
50
|
+
|
|
51
|
+
- name: Vulnerability check (pip-audit)
|
|
52
|
+
run: |
|
|
53
|
+
pip install pip-audit
|
|
54
|
+
pip-audit
|