feat(timeline): full state history and quieter chart cyan #71
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Ops gate | |
| # OPS-01β¦OPS-13 β static release/operations gates. | |
| # | |
| # Everything this workflow runs is deterministic and offline: no cluster, | |
| # no credentials, no network. It validates the machine-readable manifests | |
| # under ops/ against the actual state of the repository, so a broken | |
| # release/operations control fails at PR time rather than during an | |
| # incident. | |
| # | |
| # What it enforces (see `go run ./cmd/ops-gate -list`): | |
| # epics Β· smoke Β· rollback Β· restore Β· migrations Β· rollout | |
| # config-parity Β· helm-secrets Β· supply-chain Β· capacity Β· retention | |
| # runbooks Β· scorecard | |
| # | |
| # The migration gate is the one most likely to fail an unrelated PR, and | |
| # that is intentional: a new migration without a forward-compatibility, | |
| # rollback, duration, and lock-risk review is exactly what this is for. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ops-gate-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| jobs: | |
| gate: | |
| name: Static ops gates | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 | |
| with: | |
| go-version: '1.25' | |
| - name: Run all ops gates | |
| run: go run ./cmd/ops-gate -summary "$GITHUB_STEP_SUMMARY" | |
| - name: Unit tests for the gate implementations | |
| run: go test ./internal/ops/... ./cmd/ops-gate/... ./cmd/smoke-gate/... ./cmd/rollback-evaluator/... ./cmd/release-annotate/... ./cmd/readiness-scorecard/... | |
| - name: Lifecycle behaviour tests (shutdown, readiness, draining, leases, SSE) | |
| run: go test ./internal/ops/... ./internal/api/sse/... -run 'Lifecycle|Readiness|PreStop|Drain|Lease|SSEHandler' -v | |
| # The scorecard is generated, never hand-edited. `-check` | |
| # regenerates it in memory and compares content, ignoring the | |
| # `Generated:` timestamp β so a stale document fails, but merely | |
| # regenerating on a different day does not. | |
| - name: Production readiness scorecard is up to date | |
| run: go run ./cmd/readiness-scorecard -check | |
| # ββ Fixtures are EXECUTED, not merely inspected ββββββββββββββββββββ | |
| # | |
| # `-check fixtures` reconstructs the schema from migrations/ and | |
| # verifies every INSERT column list offline. That catches dropped and | |
| # identity columns β the class of defect that made both seed scripts | |
| # unrunnable β but column names are only part of what can be wrong. | |
| # This job runs each registered fixture against a freshly migrated | |
| # database and asserts the declared tables end up non-empty. | |
| fixture-execution: | |
| name: Execute SQL fixtures against a migrated database | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| services: | |
| postgres: | |
| # TimescaleDB, not plain postgres: migration 000142_baseline_typed | |
| # does CREATE EXTENSION timescaledb. Digest-pinned per OPS-08. | |
| image: timescale/timescaledb-ha@sha256:a693dd7fbb75b51c3d717507a9956501686edb123b48dd90b094fd5612d53abe # pg17 | |
| env: | |
| POSTGRES_USER: fixture | |
| POSTGRES_PASSWORD: fixture | |
| POSTGRES_DB: teslasync_fixture | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 10 | |
| env: | |
| PGPASSWORD: fixture | |
| DB_URL: postgres://fixture:fixture@localhost:5432/teslasync_fixture?sslmode=disable | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 | |
| with: | |
| go-version: '1.25' | |
| - name: Static fixture/schema check | |
| run: go run ./cmd/ops-gate -check fixtures | |
| - name: Install migrate + psql client | |
| run: | | |
| go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@v4.18.1 | |
| sudo apt-get update -qq && sudo apt-get install -y -qq postgresql-client | |
| - name: Apply migrations | |
| run: migrate -path migrations -database "$DB_URL" up | |
| - name: Execute Fleet API budget reservation contract | |
| run: TESLASYNC_TEST_DSN="$DB_URL" go test ./internal/database/teslabudget -run TestRepoReserveAgainstPostgres -count=1 -v | |
| - name: Execute every registered fixture | |
| run: | | |
| set -euo pipefail | |
| FAILED=0 | |
| for fixture in $(go run ./cmd/ops-gate -print-fixtures); do | |
| echo "ββ executing $fixture" | |
| if ! psql "$DB_URL" -v ON_ERROR_STOP=1 -f "$fixture"; then | |
| echo "::error file=$fixture::fixture failed to execute against the migrated schema" | |
| FAILED=1 | |
| fi | |
| done | |
| exit $FAILED | |
| # Running twice proves idempotency: a fixture that doubles its rows | |
| # on re-run makes the drill's parity numbers depend on how many | |
| # times it has executed. | |
| - name: Re-execute (idempotency) | |
| run: | | |
| set -euo pipefail | |
| for fixture in $(go run ./cmd/ops-gate -print-fixtures); do | |
| psql "$DB_URL" -v ON_ERROR_STOP=1 -f "$fixture" > /dev/null | |
| done | |
| - name: Assert the declared tables are non-empty | |
| run: | | |
| set -euo pipefail | |
| FAILED=0 | |
| for t in $(go run ./cmd/ops-gate -check restore -print-critical-tables); do | |
| n=$(psql "$DB_URL" -tAc "SELECT count(*) FROM ${t};" | tr -d '[:space:]') | |
| echo "${t}=${n}" | |
| if [ "$n" -eq 0 ]; then | |
| echo "::error::critical table '${t}' is empty after executing every fixture; the restore drill would compare 0 against 0" | |
| FAILED=1 | |
| fi | |
| done | |
| exit $FAILED | |
| helm-rollout: | |
| name: Helm rollout controls | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 | |
| with: | |
| go-version: '1.25' | |
| - uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 | |
| with: | |
| version: v3.16.0 | |
| # Default values must render exactly the pre-existing workloads: | |
| # no canary Deployment, no paused rollout, api/web on | |
| # RollingUpdate 1/0, workers with no strategy block at all. | |
| - name: Defaults preserve existing deployment behaviour | |
| run: | | |
| set -euo pipefail | |
| helm template test helm/teslasync > default.yaml | |
| if grep -q 'teslasync.io/rollout' default.yaml; then | |
| echo "::error::default (legacy) selectors must not carry a rollout label" | |
| exit 1 | |
| fi | |
| if grep -qE '^\s+paused: true' default.yaml; then | |
| echo "::error::rollout.paused defaulted to true" | |
| exit 1 | |
| fi | |
| grep -q 'maxSurge: 1' default.yaml | |
| grep -q 'maxUnavailable: 0' default.yaml | |
| # OPS-05/OPS-09 post-template invariants: selector disjointness, | |
| # Service selecting both tiers, no Service publishing the drain | |
| # port, and a grace period that can hold the shutdown budget. | |
| - name: Rendered-manifest assertions (default) | |
| run: go run ./cmd/ops-gate -verify-helm-render default.yaml | |
| - name: Canary requires disjoint selectors | |
| run: | | |
| set -euo pipefail | |
| # With legacy selectors the stable Deployment/HPA/PDB would | |
| # also match canary pods, so the chart must refuse to render. | |
| if helm template test helm/teslasync --set rollout.api.canary.enabled=true > /dev/null 2>&1; then | |
| echo "::error::canary rendered with legacy selectors; the overlap guard is gone" | |
| exit 1 | |
| fi | |
| echo "canary correctly refused in legacy selector mode" | |
| - name: Rendered-manifest assertions (canary + autoscaling + PDB) | |
| run: | | |
| set -euo pipefail | |
| helm template test helm/teslasync \ | |
| --set rollout.selectorMode=disjoint \ | |
| --set rollout.api.canary.enabled=true \ | |
| --set rollout.web.canary.enabled=true \ | |
| --set rollout.api.canary.replicaCount=2 \ | |
| --set autoscaling.enabled=true \ | |
| --set podDisruptionBudget.enabled=true \ | |
| --set web.podDisruptionBudget.enabled=true > canary.yaml | |
| grep -q 'name: test-teslasync-api-canary' canary.yaml | |
| grep -q 'name: test-teslasync-web-canary' canary.yaml | |
| go run ./cmd/ops-gate -verify-helm-render canary.yaml -expect-canary | |
| - name: Rendered-manifest assertions (disjoint, no canary) | |
| run: | | |
| set -euo pipefail | |
| helm template test helm/teslasync \ | |
| --set rollout.selectorMode=disjoint \ | |
| --set autoscaling.enabled=true > disjoint.yaml | |
| go run ./cmd/ops-gate -verify-helm-render disjoint.yaml | |
| - name: Pause switch renders | |
| run: | | |
| set -euo pipefail | |
| helm template test helm/teslasync --set rollout.paused=true > paused.yaml | |
| grep -qE '^\s+paused: true' paused.yaml | |
| - name: No credential is rendered into a ConfigMap | |
| env: | |
| CANARY_GOOGLE: CANARYVALUEGOOGLE | |
| CANARY_AZURE: CANARYVALUEAZURE | |
| CANARY_TESLA: CANARYVALUETESLA | |
| run: | | |
| set -euo pipefail | |
| helm template test helm/teslasync \ | |
| --set config.googleMaps.apiKey="$CANARY_GOOGLE" \ | |
| --set config.azureMaps.apiKey="$CANARY_AZURE" \ | |
| --set tesla.clientSecret="$CANARY_TESLA" > secrets.yaml | |
| # Extract only ConfigMap documents and assert no credential value | |
| # appears in any of them. | |
| awk 'BEGIN{RS="\n---\n"} /kind: ConfigMap/' secrets.yaml > configmaps.yaml | |
| for needle in "$CANARY_GOOGLE" "$CANARY_AZURE" "$CANARY_TESLA"; do | |
| if grep -q "$needle" configmaps.yaml; then | |
| echo "::error::a credential rendered into a ConfigMap" | |
| exit 1 | |
| fi | |
| done | |
| - name: Secret management contracts | |
| env: | |
| TEST_DATABASE_PASSWORD: CIOnlyDatabasePassword-2026 | |
| TEST_GRAFANA_PASSWORD: CIOnlyGrafanaPassword-2026 | |
| TEST_OPERATOR_TOKEN: CIOnlyOperatorToken-2026 | |
| run: | | |
| set -euo pipefail | |
| # Offline/GitOps renders are deterministic and only reference a | |
| # pre-provisioned runtime Secret. | |
| helm template test helm/teslasync > default-again.yaml | |
| cmp default.yaml default-again.yaml | |
| if grep -q '^kind: Secret$' default.yaml; then | |
| echo "::error::default offline render emitted credential material" | |
| exit 1 | |
| fi | |
| if helm template test helm/teslasync \ | |
| --set secrets.create=true \ | |
| --set-string postgresql.auth.password=teslasync >/dev/null 2>&1; then | |
| echo "::error::known weak PostgreSQL password was accepted" | |
| exit 1 | |
| fi | |
| helm template test helm/teslasync \ | |
| --set secrets.create=true \ | |
| --set-string postgresql.auth.password="$TEST_DATABASE_PASSWORD" \ | |
| --set-string grafana.adminPassword="$TEST_GRAFANA_PASSWORD" \ | |
| --set-string operator.token="$TEST_OPERATOR_TOKEN" \ | |
| --set unitDriftValidator.enabled=true \ | |
| > chart-managed-secret.yaml | |
| grep -q '^kind: Secret$' chart-managed-secret.yaml | |
| grep -q 'TESLASYNC_OPERATOR_TOKEN:' chart-managed-secret.yaml | |
| helm template test helm/teslasync \ | |
| --set-string secrets.existingSecret=teslasync-runtime \ | |
| --set unitDriftValidator.enabled=true \ | |
| > existing-secret.yaml | |
| grep -q 'kind: CronJob' existing-secret.yaml | |
| grep -q 'name: teslasync-runtime' existing-secret.yaml | |
| if grep -q '^kind: Secret$' existing-secret.yaml; then | |
| echo "::error::chart-managed Secret rendered in existing Secret mode" | |
| exit 1 | |
| fi | |
| helm template test helm/teslasync \ | |
| --set externalSecrets.enabled=true \ | |
| --set-string externalSecrets.secretStoreRef.name=production-secrets \ | |
| --set-string externalSecrets.target.name=teslasync-runtime \ | |
| --set-string 'externalSecrets.dataFrom[0].extract.key=teslasync/production' \ | |
| --set unitDriftValidator.enabled=true \ | |
| > external-secret.yaml | |
| grep -q '^kind: ExternalSecret$' external-secret.yaml | |
| grep -q 'name: teslasync-runtime' external-secret.yaml | |
| grep -q 'kind: CronJob' external-secret.yaml | |
| if grep -q '^kind: Secret$' external-secret.yaml; then | |
| echo "::error::chart-managed Secret rendered alongside ExternalSecret" | |
| exit 1 | |
| fi | |
| - name: Lint | |
| run: helm lint helm/teslasync --strict | |
| # OPS-03 β migration hook secret gate. | |
| # | |
| # The migration Job is a pre-install/pre-upgrade hook that reads | |
| # DATABASE_PASS from the runtime Secret via envFrom. Helm applies | |
| # hooks BEFORE ordinary manifests, so an ExternalSecret rendered as | |
| # an ordinary manifest did not exist when the Job was scheduled: on | |
| # a fresh install ESO had not been asked to fetch anything and the | |
| # pod sat in CreateContainerConfigError until the hook timed out. | |
| # | |
| # Every supported secret mode is rendered and checked here, plus the | |
| # negative controls, because this invariant only exists after | |
| # templating. | |
| - name: Migration hook secret gate | |
| run: | | |
| set -euo pipefail | |
| go test ./internal/ops/ -run 'MigrationGate|RealChart' -count=1 | |
| # Belt and braces: assert the rendered shapes directly so a | |
| # regression is visible even if the Go test is skipped. | |
| helm template test helm/teslasync \ | |
| --set externalSecrets.enabled=true \ | |
| --set-string externalSecrets.secretStoreRef.name=production-secrets \ | |
| --set-string externalSecrets.target.name=teslasync-runtime \ | |
| --set-string 'externalSecrets.dataFrom[0].extract.key=teslasync/production' \ | |
| > gate-external.yaml | |
| go run ./cmd/ops-gate -verify-helm-render gate-external.yaml | |
| grep -q 'wait-for-runtime-secret' gate-external.yaml | |
| grep -q 'optional: true' gate-external.yaml | |
| # Ordinary -> hook conversion must survive Helm's regular | |
| # removal phase, and the hook recreate must not garbage-collect | |
| # the target Secret. | |
| grep -q 'helm.sh/resource-policy: keep' gate-external.yaml | |
| grep -q 'creationPolicy: "Orphan"' gate-external.yaml | |
| helm template test helm/teslasync \ | |
| --set secrets.create=true \ | |
| --set-string postgresql.auth.password="$TEST_DATABASE_PASSWORD" \ | |
| --set-string grafana.adminPassword="$TEST_GRAFANA_PASSWORD" \ | |
| > gate-managed.yaml | |
| go run ./cmd/ops-gate -verify-helm-render gate-managed.yaml | |
| grep -q 'helm.sh/resource-policy: keep' gate-managed.yaml | |
| helm template test helm/teslasync > gate-default.yaml | |
| go run ./cmd/ops-gate -verify-helm-render gate-default.yaml | |
| if grep -q 'wait-for-runtime-secret' gate-default.yaml; then | |
| echo "::error::default render gained a migration gate wait; existing behaviour changed" | |
| exit 1 | |
| fi | |
| helm template test helm/teslasync \ | |
| --set-string secrets.existingSecret=teslasync-runtime > gate-existing.yaml | |
| go run ./cmd/ops-gate -verify-helm-render gate-existing.yaml | |
| helm template test helm/teslasync \ | |
| --set-string secrets.existingSecret=teslasync-runtime \ | |
| --set migrationGate.mode=require > gate-require.yaml | |
| go run ./cmd/ops-gate -verify-helm-render gate-require.yaml | |
| grep -q 'wait-for-runtime-secret' gate-require.yaml | |
| # ββ Combinations the chart must refuse to render βββββββββββ | |
| refuse() { | |
| local label="$1"; shift | |
| if helm template test helm/teslasync "$@" > /dev/null 2>&1; then | |
| echo "::error::${label} rendered; the migration gate contract is not enforced" | |
| exit 1 | |
| fi | |
| echo "refused: ${label}" | |
| } | |
| # require + a chart-rendered source is a guaranteed | |
| # fresh-install timeout: Helm cannot apply an ordinary manifest | |
| # until the pre-install hooks have finished. | |
| refuse 'require + externalSecrets' \ | |
| --set externalSecrets.enabled=true \ | |
| --set-string externalSecrets.secretStoreRef.name=s \ | |
| --set-string 'externalSecrets.dataFrom[0].extract.key=k' \ | |
| --set migrationGate.mode=require | |
| refuse 'require + secrets.create' \ | |
| --set secrets.create=true \ | |
| --set-string postgresql.auth.password="$TEST_DATABASE_PASSWORD" \ | |
| --set-string grafana.adminPassword="$TEST_GRAFANA_PASSWORD" \ | |
| --set migrationGate.mode=require | |
| refuse 'hook + no chart-rendered source' --set migrationGate.mode=hook | |
| refuse 'hook + existingSecret' \ | |
| --set-string secrets.existingSecret=teslasync-runtime \ | |
| --set migrationGate.mode=hook | |
| # Owner sets ownerReferences, so the hook recreate would | |
| # garbage-collect the target Secret. | |
| refuse 'hook + creationPolicy Owner' \ | |
| --set externalSecrets.enabled=true \ | |
| --set-string externalSecrets.secretStoreRef.name=s \ | |
| --set-string 'externalSecrets.dataFrom[0].extract.key=k' \ | |
| --set-string externalSecrets.target.creationPolicy=Owner | |
| echo "migration hook secret gate contracts hold" | |
| env: | |
| TEST_DATABASE_PASSWORD: CIOnlyDatabasePassword-2026 | |
| TEST_GRAFANA_PASSWORD: CIOnlyGrafanaPassword-2026 |