Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/sync-cloud-run-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -281,14 +281,14 @@ jobs:
--project="${GCP_PROJECT_ID}" \
--region="${CLOUD_RUN_REGION}" \
--format='json(status.traffic)' \
| python3 -c 'import json, sys; traffic = json.load(sys.stdin) or []; active = [{"revisionName": row.get("revisionName"), "percent": int(row.get("percent", 0))} for row in traffic if isinstance(row, dict) and int(row.get("percent", 0)) > 0]; print(json.dumps(sorted(active, key=lambda row: (str(row["revisionName"]), row["percent"])), separators=(",", ":")))' \
| python3 -c 'import json, sys; traffic = (json.load(sys.stdin).get("status", {}).get("traffic") or []); active = [{"revisionName": row.get("revisionName"), "percent": int(row.get("percent", 0))} for row in traffic if isinstance(row, dict) and int(row.get("percent", 0)) > 0]; print(json.dumps(sorted(active, key=lambda row: (str(row["revisionName"]), row["percent"])), separators=(",", ":")))' \
| sha256sum | cut -d ' ' -f1
}
{
echo "traffic=$(active_traffic_digest)"
echo "config=$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(spec.template.spec.serviceAccountName,spec.template.spec.containerConcurrency,spec.template.spec.timeoutSeconds,spec.template.spec.containers.resources,spec.template.spec.containers.env.name,spec.template.spec.containers.env.valueFrom.secretKeyRef.name)')"
echo "config=$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(spec.template.spec.serviceAccountName,spec.template.spec.containerConcurrency,spec.template.spec.timeoutSeconds,spec.template.spec.containers[].resources,spec.template.spec.containers[].env[].name,spec.template.spec.containers[].env[].valueFrom.secretKeyRef)')"
echo "scheduler=$(summarize gcloud scheduler jobs list --project="${GCP_PROJECT_ID}" --location="${CLOUD_SCHEDULER_LOCATION:-${CLOUD_RUN_REGION}}" --sort-by=name --format='json(name,state,schedule,timeZone,httpTarget.uri,httpTarget.httpMethod,httpTarget.oidcToken.serviceAccountEmail,httpTarget.oidcToken.audience)')"
echo "iam=$(summarize gcloud run services get-iam-policy "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(bindings.role,bindings.members)')"
echo "iam=$(summarize gcloud run services get-iam-policy "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(bindings[].role,bindings[].members,bindings[].condition)')"
} > "${BASELINE_PATH}"
echo "path=${BASELINE_PATH}" >> "${GITHUB_OUTPUT}"

Expand Down Expand Up @@ -367,7 +367,7 @@ jobs:
--project="${GCP_PROJECT_ID}" \
--region="${CLOUD_RUN_REGION}" \
--format='json(status.traffic)' \
| python3 -c 'import json, sys; traffic = json.load(sys.stdin) or []; active = [{"revisionName": row.get("revisionName"), "percent": int(row.get("percent", 0))} for row in traffic if isinstance(row, dict) and int(row.get("percent", 0)) > 0]; print(json.dumps(sorted(active, key=lambda row: (str(row["revisionName"]), row["percent"])), separators=(",", ":")))' \
| python3 -c 'import json, sys; traffic = (json.load(sys.stdin).get("status", {}).get("traffic") or []); active = [{"revisionName": row.get("revisionName"), "percent": int(row.get("percent", 0))} for row in traffic if isinstance(row, dict) and int(row.get("percent", 0)) > 0]; print(json.dumps(sorted(active, key=lambda row: (str(row["revisionName"]), row["percent"])), separators=(",", ":")))' \
| sha256sum | cut -d ' ' -f1
}
fail_readback() { echo "No-traffic deployment readback failed: $1" >&2; exit 1; }
Expand All @@ -382,15 +382,15 @@ jobs:
for _ in $(seq 1 30); do
revision_ready="$(
{ gcloud run revisions describe "${revision_name}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(status.conditions)' 2>/dev/null \
| python3 -c 'import json, sys; conditions = json.load(sys.stdin) or []; print("True" if any(isinstance(condition, dict) and condition.get("type") == "Ready" and condition.get("status") == "True" for condition in conditions) else "False")' 2>/dev/null; } \
| python3 -c 'import json, sys; conditions = (json.load(sys.stdin).get("status", {}).get("conditions") or []); print("True" if any(isinstance(condition, dict) and condition.get("type") == "Ready" and condition.get("status") == "True" for condition in conditions) else "False")' 2>/dev/null; } \
|| printf 'False'
)"
[ "${revision_ready}" = "True" ] && break
sleep 2
done
revision_ready_category="$(
{ gcloud run revisions describe "${revision_name}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(status.conditions)' 2>/dev/null \
| python3 -c 'import json, sys; conditions = json.load(sys.stdin) or []; ready = next((condition for condition in conditions if isinstance(condition, dict) and condition.get("type") == "Ready"), {}); reason = str(ready.get("reason") or "").upper(); print("HEALTHCHECK_FAILURE") if "HEALTH" in reason or "PROBE" in reason else print("CONTAINER_FAILURE") if "CONTAINER" in reason or "IMAGE" in reason else print("RESOURCE_FAILURE") if "RESOURCE" in reason or "QUOTA" in reason else print("PROGRESS_DEADLINE") if "DEADLINE" in reason or "TIMEOUT" in reason else print("PLATFORM_FAILURE") if "INTERNAL" in reason or "PLATFORM" in reason else print("UNKNOWN")' 2>/dev/null; } \
| python3 -c 'import json, sys; conditions = (json.load(sys.stdin).get("status", {}).get("conditions") or []); ready = next((condition for condition in conditions if isinstance(condition, dict) and condition.get("type") == "Ready"), {}); reason = str(ready.get("reason") or "").upper(); print("HEALTHCHECK_FAILURE") if "HEALTH" in reason or "PROBE" in reason else print("CONTAINER_FAILURE") if "CONTAINER" in reason or "IMAGE" in reason else print("RESOURCE_FAILURE") if "RESOURCE" in reason or "QUOTA" in reason else print("PROGRESS_DEADLINE") if "DEADLINE" in reason or "TIMEOUT" in reason else print("PLATFORM_FAILURE") if "INTERNAL" in reason or "PLATFORM" in reason else print("UNKNOWN")' 2>/dev/null; } \
|| printf 'UNKNOWN'
)"
if [ "${revision_ready}" != "True" ]; then
Expand All @@ -400,9 +400,9 @@ jobs:
[ "${revision_sha}" = "${EXPECTED_SHA}" ] || fail_readback "revision commit SHA mismatch"
[[ "${revision_image}" == *"${EXPECTED_IMAGE_DIGEST}"* ]] || fail_readback "revision image digest mismatch"
[ "${traffic}" = "$(active_traffic_digest)" ] || fail_readback "effective traffic changed"
[ "${config}" = "$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(spec.template.spec.serviceAccountName,spec.template.spec.containerConcurrency,spec.template.spec.timeoutSeconds,spec.template.spec.containers.resources,spec.template.spec.containers.env.name,spec.template.spec.containers.env.valueFrom.secretKeyRef.name)')" ] || fail_readback "configuration changed"
[ "${config}" = "$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(spec.template.spec.serviceAccountName,spec.template.spec.containerConcurrency,spec.template.spec.timeoutSeconds,spec.template.spec.containers[].resources,spec.template.spec.containers[].env[].name,spec.template.spec.containers[].env[].valueFrom.secretKeyRef)')" ] || fail_readback "configuration changed"
[ "${scheduler}" = "$(summarize gcloud scheduler jobs list --project="${GCP_PROJECT_ID}" --location="${CLOUD_SCHEDULER_LOCATION:-${CLOUD_RUN_REGION}}" --sort-by=name --format='json(name,state,schedule,timeZone,httpTarget.uri,httpTarget.httpMethod,httpTarget.oidcToken.serviceAccountEmail,httpTarget.oidcToken.audience)')" ] || fail_readback "Scheduler changed"
[ "${iam}" = "$(summarize gcloud run services get-iam-policy "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(bindings.role,bindings.members)')" ] || fail_readback "IAM changed"
[ "${iam}" = "$(summarize gcloud run services get-iam-policy "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(bindings[].role,bindings[].members,bindings[].condition)')" ] || fail_readback "IAM changed"
{
echo "## No-traffic deployment readback"
echo "- Revision commit SHA and image digest verified."
Expand Down
58 changes: 58 additions & 0 deletions tests/test_sync_cloud_run_env_workflow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from __future__ import annotations

import json
import re
import subprocess
import sys
from pathlib import Path

import pytest


def test_sync_cloud_run_env_workflow_requires_manual_dispatch():
workflow_path = Path(__file__).resolve().parents[1] / ".github/workflows/sync-cloud-run-env.yml"
Expand Down Expand Up @@ -304,3 +310,55 @@ def test_sync_cloud_run_env_workflow_is_fail_closed_and_no_traffic_by_default():
assert 'inputs.allow_configuration_sync }}" != "true"' in workflow
assert 'inputs.allow_traffic_promotion == true' in workflow
assert 'inputs.allow_cleanup == true' in workflow


def _run_readback_python(marker, payload):
workflow = (Path(__file__).resolve().parents[1] / ".github/workflows/sync-cloud-run-env.yml").read_text()
commands = [code for code in re.findall(r"python3 -c '([^']+)'", workflow) if marker in code]
assert commands
results = [subprocess.run([sys.executable, "-c", code], input=json.dumps(payload),
text=True, capture_output=True, check=True).stdout.strip()
for code in commands]
assert len(set(results)) == 1 # capture and verify must interpret traffic identically
return results[0]


@pytest.mark.parametrize("ready,expected", [("True", "True"), ("False", "False"), ("Unknown", "False")])
def test_readback_parses_nested_ready_conditions(ready, expected):
assert _run_readback_python('print("True" if any(', {
"status": {"conditions": [{"type": "Ready", "status": ready}]},
}) == expected


def test_readback_does_not_invent_readiness_when_conditions_missing():
assert _run_readback_python('print("True" if any(', {}) == "False"


def test_readback_classifies_nested_failure_without_exposing_message():
assert _run_readback_python('reason = str(ready.get(', {
"status": {"conditions": [{"type": "Ready", "status": "False",
"reason": "HealthCheckContainerError",
"message": "synthetic private diagnostic"}]},
}) == "HEALTHCHECK_FAILURE"


def test_readback_traffic_changes_are_not_hashed_as_empty_lists():
def traffic(revision, percent=100):
return {"status": {"traffic": [{"revisionName": revision, "percent": percent}]}}

before = _run_readback_python("active = [", traffic("old-revision"))
after = _run_readback_python("active = [", traffic("new-revision"))
assert json.loads(before) == [{"revisionName": "old-revision", "percent": 100}]
assert before != after
tagged = traffic("old-revision")
tagged["status"]["traffic"].append({"revisionName": "zero-traffic", "percent": 0, "tag": "test"})
assert _run_readback_python("active = [", tagged) == before


def test_readback_baseline_projections_include_array_members():
workflow = (Path(__file__).resolve().parents[1] / ".github/workflows/sync-cloud-run-env.yml").read_text()
for field in ("spec.template.spec.containers[].resources",
"spec.template.spec.containers[].env[].name",
"spec.template.spec.containers[].env[].valueFrom.secretKeyRef",
"bindings[].role", "bindings[].members", "bindings[].condition"):
assert workflow.count(field) == 2 # baseline capture and comparison