b51df2ee0f
CI / docker (push) Blocked by required conditions
CI / status-check (push) Blocked by required conditions
CI / build (push) Successful in 20s
CI / helm (push) Successful in 23s
CI / lint (push) Successful in 3m19s
CI / typecheck (push) Successful in 3m57s
CI / benchmark-regression (push) Has been skipped
CI / security (push) Successful in 4m5s
CI / integration_tests (push) Successful in 7m25s
CI / unit_tests (push) Failing after 13m18s
CI / quality (push) Failing after 13m19s
CI / e2e_tests (push) Failing after 18m16s
CI / coverage (push) Failing after 19m20s
CI / benchmark-publish (push) Failing after 28m16s
## Summary This PR adds Kubernetes Helm deployment support for the CleverAgents server. Closes #928 ### What this PR includes - Helm chart under `k8s/` with Deployment, Service, optional Ingress, ConfigMap, ServiceAccount, Secrets, NOTES, and optional Redis subchart configuration. - Multi-stage `Dockerfile.server` for server runtime deployment. - Deployment-focused docs in `k8s/README.md`. - Behave + Robot + benchmark coverage for chart/deployment wiring. ### Review fixes applied (cycle 11 — hurui200320 review #2687) **Critical fix:** 1. **CI SHA256 checksum verification fixed** — All 3 Helm install blocks (`unit_tests`, `integration_tests`, `helm` jobs) now save the tarball using its original filename (`helm-v3.16.4-linux-amd64.tar.gz`) instead of `helm.tgz`, so the `.sha256sum` file can correctly locate and verify it. This was causing all 3 CI Helm jobs to fail with "No such file or directory". **Major fixes (test coverage gaps):** 2. **405 Allow header now tested** — The existing "POST to known path returns method not allowed" scenario now asserts that the `Allow: GET` header is present. Added a second 405 scenario testing `POST /live` for broader `_KNOWN_PATHS` coverage (review item #11). 3. **Security-hardening headers now tested** — New scenario "HTTP responses include security-hardening headers" verifies `content-length`, `x-content-type-options: nosniff`, and `cache-control: no-store` headers are present on HTTP responses. 4. **Lifespan warning logging now tested** — New scenario "Unrecognised lifespan message type logs warning and continues" queues `lifespan.startup` → `lifespan.bogus` → `lifespan.shutdown` and verifies: (a) the app completes the lifespan cycle cleanly, (b) a warning is logged mentioning the unrecognised type. ### Review fixes applied (cycle 10) **Critical/Major fixes (from hurui200320's prior REQUEST_CHANGES):** 1. **Rebased branch onto master** — Removed merge commit per CONTRIBUTING.md rebase-only policy. Clean linear history restored. 2. **Fixed commit message body** — Replaced literal `\n` sequences with actual newlines. `ISSUES CLOSED: #928` footer is now on its own line after a blank separator. 3. **Moved uvicorn import to module top level** — `from uvicorn import run as uvicorn_run` is now at the top of `src/cleveragents/cli/commands/server.py` per Import Guidelines. Updated test mock target from `uvicorn.run` to `cleveragents.cli.commands.server.uvicorn_run`. 4. **Added SHA256 checksum verification** — All three Helm CLI install blocks in `.forgejo/workflows/ci.yml` now download and verify `helm.sha256sum` before extracting the binary. **Minor fixes:** 5. **Added `Dockerfile.server` build to CI** — New "Build Docker image (Server)" step in the `docker` job validates the server Dockerfile. 6. **ASGI 405 Method Not Allowed** — Known paths (`/`, `/live`, `/ready`, `/health`) now return 405 with `Allow: GET` header for non-GET methods, per RFC 9110 §15.5.6. Added `_KNOWN_PATHS` frozenset. 7. **WebSocket close protocol fix** — App now calls `await receive()` to consume the `websocket.connect` event before closing. Changed close code from 1000 (Normal Closure) to 1008 (Policy Violation). 8. **Lifespan handler logging** — Unrecognised lifespan message types are now logged as warnings instead of silently consumed. 9. **Security-hardening headers** — `_send_response` now includes `content-length`, `x-content-type-options: nosniff`, and `cache-control: no-store` on all HTTP responses. 10. **`.dockerignore` credential patterns** — Added `*.pem`, `*.key`, `*.p12`, `*.pfx`, `credentials*.json`. 11. **`--log-level` validation** — Constrained to `click.Choice(["critical", "error", "warning", "info", "debug", "trace"])` for clean CLI validation errors. 12. **Reverted unrelated semgrep pre-commit change** — `pass_filenames` and `entry` restored to original values per atomic commit hygiene. 13. **Removed unused `ReceiveCallable` type alias** and `Callable`/`Awaitable` imports from `asgi_app_steps.py`. 14. **Fixed redundant `shutil.which("helm")` check** — `_skip_if_helm_missing` now returns `bool` to eliminate the duplicate check in `_render_chart`. 15. **Improved test deque error handling** — Lifespan test receive mock now raises descriptive `AssertionError` instead of opaque `IndexError`. 16. **Scope type dispatch** — Changed `if/if/if` to `if/elif/elif` for mutually exclusive ASGI scope types. 17. **Dockerfile.server base image** — Standardised to `python:3.13-slim` (floating minor) consistent with CLI Dockerfile. 18. **Dockerfile layer caching** — Split `uv pip install build` and `python -m build` into separate `RUN` instructions. 19. **Removed extraneous double blank line** in Dockerfile.server. ### Deferred items (acknowledged, not in scope) - PodDisruptionBudget, HorizontalPodAutoscaler, NetworkPolicy — Follow-up for production hardening. - `appVersion: "1.0.0"` placeholder — Needs tracking issue for release versioning alignment. - Readiness probe with downstream dependency checks — Documented limitation. - Cross-system test for probe paths matching ASGI routes — Test enhancement. - Improved benchmarks (helm template timing vs PyYAML parsing) — Benchmark quality improvement. - CI DRY violation (Helm install 3×) — Code quality improvement, consider composite action. - File length limits exceeded (`k8s_helm_chart_steps.py` 551 lines, `helper_k8s_helm_chart.py` 678 lines) — Non-blocking, can be split in follow-up. - `runAsGroup: 1000` in pod security context — Defense-in-depth improvement. - HEAD method support on known paths — RFC compliance, does not affect K8s probes. - `click.Choice` log-level validation via CLI runner test — Test gap. ### Scope note: status-check CI gate The `status-check` job now includes `integration_tests`, `e2e_tests`, and `helm` in its `needs` list. The `helm` job is new in this PR. The `integration_tests` and `e2e_tests` additions fix previously-missing gate checks — included here since this PR modifies both of those jobs to install Helm. ### Quality gates - `nox -e lint` ✅ - `nox -e typecheck` ✅ - `nox -e unit_tests` ✅ (12,321 scenarios passed, 4 skipped) - `nox -e integration_tests` — 3 pre-existing failures in unrelated areas (plan correction, resource types) - `nox -e e2e_tests` — pre-existing failures (LLM API keys not available in local env) - `nox -e coverage_report` ✅ (**97.7%**) Reviewed-on: #1085 Reviewed-by: Jeffrey Phillips Freeman <jeffrey.freeman@cleverthis.com> Co-authored-by: Brent E. Edwards <brent.edwards@cleverthis.com> Co-committed-by: Brent E. Edwards <brent.edwards@cleverthis.com>
203 lines
11 KiB
Plaintext
203 lines
11 KiB
Plaintext
*** Settings ***
|
|
Documentation Integration tests for Kubernetes Helm chart cross-file consistency.
|
|
... Every test case validates interactions between multiple chart files
|
|
... (templates, values.yaml, Chart.yaml, Dockerfile) to catch wiring
|
|
... errors that single-file Behave scenarios cannot detect.
|
|
Resource ${CURDIR}/common.resource
|
|
Library OperatingSystem
|
|
Library Collections
|
|
Library String
|
|
Library Process
|
|
Suite Setup Setup Test Environment
|
|
Suite Teardown Cleanup Test Environment
|
|
|
|
*** Variables ***
|
|
${K8S_DIR} ${CURDIR}/../k8s
|
|
${TEMPLATES_DIR} ${K8S_DIR}/templates
|
|
${DOCKERFILE_SERVER} ${CURDIR}/../Dockerfile.server
|
|
${HELPER} ${CURDIR}/helper_k8s_helm_chart.py
|
|
|
|
*** Keywords ***
|
|
Assert Rendered Check Result
|
|
[Arguments] ${result} ${ok_marker} ${skip_marker}
|
|
Should Be Equal As Integers ${result.rc} 0 Render check failed: ${result.stderr}
|
|
${stdout}= Strip String ${result.stdout}
|
|
${strict}= Get Environment Variable CLEVERAGENTS_REQUIRE_HELM_RENDER_ASSERTIONS false
|
|
${strict}= Convert To Lower Case ${strict}
|
|
IF '${strict}' == 'true' or '${strict}' == '1' or '${strict}' == 'yes'
|
|
Should Be Equal As Strings ${stdout} ${ok_marker}
|
|
ELSE
|
|
${is_ok}= Run Keyword And Return Status Should Be Equal As Strings ${stdout} ${ok_marker}
|
|
IF not ${is_ok}
|
|
Should Be Equal As Strings ${stdout} ${skip_marker}
|
|
END
|
|
END
|
|
|
|
*** Test Cases ***
|
|
Port Values Are Consistent Across Templates Values And Dockerfile
|
|
[Documentation] Cross-file check: service.port and server.port must agree
|
|
... in values.yaml, deployment.yaml must reference server.port
|
|
... for containerPort, service.yaml must use named port, and
|
|
... Dockerfile EXPOSE must match the default port.
|
|
[Tags] k8s integration critical
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_cross_file_port_consistency
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Port consistency check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Port consistency OK
|
|
|
|
Secrets Template References Match Values Schema And Deployment Wiring
|
|
[Documentation] Cross-file check: secrets.yaml must reference the same
|
|
... credential keys defined in values.yaml, and deployment.yaml
|
|
... must reference the same secret key names.
|
|
[Tags] k8s integration critical
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_secrets_structure
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Secrets structure check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Secrets structure OK
|
|
|
|
Deployment Command Args Match Dockerfile Entrypoint And ASGI Module
|
|
[Documentation] Cross-file check: Deployment template passes server config
|
|
... as CLI args to uvicorn, matching the Dockerfile ENTRYPOINT,
|
|
... and both reference the same ASGI application module.
|
|
[Tags] k8s integration critical
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_deployment_command_args
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: deployment-command-args-rendered
|
|
... SKIP: deployment-command-args-rendered
|
|
|
|
Deployment EnvFrom References ConfigMap Name And Values Keys
|
|
[Documentation] Cross-file check: Deployment template uses envFrom with
|
|
... configMapRef matching the ConfigMap name suffix, and the
|
|
... ConfigMap env vars correspond to values.yaml server keys.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_envfrom_wiring
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: envfrom-wiring-rendered
|
|
... SKIP: envfrom-wiring-rendered
|
|
|
|
Redis Host Helper Matches Bitnami Naming And Chart Dependency
|
|
[Documentation] Cross-file check: the Redis host helper uses .Release.Name
|
|
... to match Bitnami subchart naming, the deployment template
|
|
... uses this helper, and Chart.yaml declares the dependency.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_redis_host_helper
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Redis host helper check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Redis host helper OK
|
|
|
|
Probe Configuration Matches Between Values And Deployment Template
|
|
[Documentation] Cross-file check: probes in values.yaml split /live and /ready
|
|
... and the deployment template renders them via toYaml.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_probe_config
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Probe config check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Probe config OK
|
|
|
|
Image Defaults Are Consistent Between Values And Deployment Template
|
|
[Documentation] Cross-file check: image section in values.yaml has sensible
|
|
... defaults and the deployment template correctly references
|
|
... repository, pullPolicy, and tag values.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_image_defaults
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Image defaults check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Image defaults OK
|
|
|
|
Service Defaults Match Between Values And Service Template
|
|
[Documentation] Cross-file check: service section in values.yaml defaults
|
|
... to ClusterIP on port 8000 and the service template
|
|
... references the correct values paths.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_service_defaults
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Service defaults check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Service defaults OK
|
|
|
|
Server Dockerfile Is Multi-Stage With Non-Root User
|
|
[Documentation] Cross-file check: Dockerfile.server uses multi-stage build
|
|
... and creates the same non-root user referenced by the
|
|
... podSecurityContext in values.yaml.
|
|
[Tags] k8s dockerfile
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_dockerfile_multistage_nonroot
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=30s
|
|
Should Be Equal As Integers ${result.rc} 0 Dockerfile multi-stage check failed: ${result.stderr}
|
|
Should Contain ${result.stdout} Dockerfile multi-stage OK
|
|
|
|
Redis Disabled Rendering Omits Redis Environment Wiring
|
|
[Documentation] Render-based check (when Helm is available): with
|
|
... redis.enabled=false, rendered Deployment must not
|
|
... include Redis environment variables.
|
|
... If Helm is unavailable, helper returns a skip note.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_redis_disabled_rendering
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: redis-disabled-rendering
|
|
... SKIP: redis-disabled-rendering
|
|
|
|
Database Existing Secret Rendering Uses Existing Secret Wiring
|
|
[Documentation] Render-based check (when Helm is available): with
|
|
... database.existingSecret configured, rendered Deployment
|
|
... should reference that secret for CLEVERAGENTS_DATABASE_URL.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_database_existing_secret_rendering
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: database-existing-secret-rendering
|
|
... SKIP: database-existing-secret-rendering
|
|
|
|
Redis Enabled Existing Secret Rendering Uses Existing Secret Wiring
|
|
[Documentation] Render-based check (when Helm is available): with
|
|
... redis.enabled=true and redis.auth.existingSecret set,
|
|
... rendered Deployment should consume that secret.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_redis_enabled_existing_secret_rendering
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: redis-enabled-existing-secret-rendering
|
|
... SKIP: redis-enabled-existing-secret-rendering
|
|
|
|
Redis Enabled Inline Password Rendering Uses Generated Secret Wiring
|
|
[Documentation] Render-based check (when Helm is available): with
|
|
... redis.enabled=true and redis.auth.password set,
|
|
... rendered Deployment should reference generated redis Secret.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_redis_enabled_inline_password_rendering
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: redis-enabled-inline-password-rendering
|
|
... SKIP: redis-enabled-inline-password-rendering
|
|
|
|
Redis Enabled Fallback Rendering Uses Bitnami Secret Wiring
|
|
[Documentation] Render-based check (when Helm is available): with
|
|
... redis.enabled=true and no explicit Redis credential,
|
|
... rendered Deployment should reference Bitnami secret.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_redis_enabled_bitnami_fallback_rendering
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: redis-enabled-bitnami-fallback-rendering
|
|
... SKIP: redis-enabled-bitnami-fallback-rendering
|
|
|
|
Ingress TLS Rendering Succeeds With TLS Configured
|
|
[Documentation] Render-based positive check (when Helm is available):
|
|
... ingress.enabled=true with TLS configured and
|
|
... allowInsecure=false should render Ingress successfully.
|
|
[Tags] k8s integration
|
|
${result}= Run Process ${PYTHON} ${HELPER} validate_ingress_tls_rendering_success
|
|
... cwd=${WORKSPACE} on_timeout=kill timeout=120s
|
|
Assert Rendered Check Result
|
|
... ${result}
|
|
... OK: ingress-tls-rendering-success
|
|
... SKIP: ingress-tls-rendering-success
|