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
31 changes: 25 additions & 6 deletions .github/workflows/sync-cloud-run-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,16 @@ jobs:
summarize() {
"$@" | sha256sum | cut -d ' ' -f1
}
active_traffic_digest() {
gcloud run services describe "${CLOUD_RUN_SERVICE}" \
--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=(",", ":")))' \
| sha256sum | cut -d ' ' -f1
}
{
echo "traffic=$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(status.traffic)')"
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 "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)')"
Expand Down Expand Up @@ -354,6 +362,15 @@ jobs:
run: |
set -euo pipefail
summarize() { "$@" | sha256sum | cut -d ' ' -f1; }
active_traffic_digest() {
gcloud run services describe "${CLOUD_RUN_SERVICE}" \
--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=(",", ":")))' \
| sha256sum | cut -d ' ' -f1
}
fail_readback() { echo "No-traffic deployment readback failed: $1" >&2; exit 1; }
traffic="$(awk -F= '$1 == "traffic" { print $2 }' "${BASELINE_PATH}")"
config="$(awk -F= '$1 == "config" { print $2 }' "${BASELINE_PATH}")"
scheduler="$(awk -F= '$1 == "scheduler" { print $2 }' "${BASELINE_PATH}")"
Expand All @@ -362,11 +379,13 @@ jobs:
ready_revision="$(gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='value(status.latestReadyRevisionName)')"
revision_sha="$(gcloud run revisions describe "${revision_name}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='value(metadata.labels.commit-sha)')"
revision_image="$(gcloud run revisions describe "${revision_name}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='value(spec.containers.image)')"
[ "${revision_name}" = "${ready_revision}" ] && [ "${revision_sha}" = "${EXPECTED_SHA}" ] && [[ "${revision_image}" == *"${EXPECTED_IMAGE_DIGEST}"* ]]
[ "${traffic}" = "$(summarize gcloud run services describe "${CLOUD_RUN_SERVICE}" --project="${GCP_PROJECT_ID}" --region="${CLOUD_RUN_REGION}" --format='json(status.traffic)')" ]
[ "${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)')" ]
[ "${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)')" ]
[ "${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)')" ]
[ "${revision_name}" = "${ready_revision}" ] || fail_readback "created revision is not ready"
[ "${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"
[ "${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"
{
echo "## No-traffic deployment readback"
echo "- Revision commit SHA and image digest verified."
Expand Down
2 changes: 2 additions & 0 deletions tests/test_sync_cloud_run_env_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@ def test_sync_cloud_run_env_workflow_is_fail_closed_and_no_traffic_by_default():
assert "--no-traffic" in deploy_block
assert "Capture read-only deployment baseline" in workflow
assert "Verify no-traffic deployment readback" in workflow
assert "active_traffic_digest()" in workflow
assert 'fail_readback "effective traffic changed"' in workflow
assert 'inputs.allow_configuration_sync }}" != "true"' in workflow
assert 'inputs.allow_traffic_promotion == true' in workflow
assert 'inputs.allow_cleanup == true' in workflow