This commit is contained in:
parent
04e586c23e
commit
8f96b44539
7 changed files with 357 additions and 4 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -42,6 +42,7 @@ jobs:
|
||||||
lib/llm.py \
|
lib/llm.py \
|
||||||
lib/post_extract.py \
|
lib/post_extract.py \
|
||||||
ops/check_gcp_infra_readiness.py \
|
ops/check_gcp_infra_readiness.py \
|
||||||
|
ops/plan_gcp_iam_split.py \
|
||||||
ops/sqlite_to_postgres_dump.py \
|
ops/sqlite_to_postgres_dump.py \
|
||||||
telegram/approvals.py \
|
telegram/approvals.py \
|
||||||
scripts/check_crabbox_ci_contract.py \
|
scripts/check_crabbox_ci_contract.py \
|
||||||
|
|
@ -52,6 +53,8 @@ jobs:
|
||||||
tests/test_decision_engine_replay.py \
|
tests/test_decision_engine_replay.py \
|
||||||
tests/test_evaluate_agent_routing.py \
|
tests/test_evaluate_agent_routing.py \
|
||||||
tests/test_gcp_cloudsql_restore_drill.py \
|
tests/test_gcp_cloudsql_restore_drill.py \
|
||||||
|
tests/test_gcp_iam_split_plan.py \
|
||||||
|
tests/test_gcp_readiness_workflow.py \
|
||||||
tests/test_phase1b_end_to_end.py \
|
tests/test_phase1b_end_to_end.py \
|
||||||
tests/test_sqlite_to_postgres_dump.py \
|
tests/test_sqlite_to_postgres_dump.py \
|
||||||
tests/test_eval_parse.py \
|
tests/test_eval_parse.py \
|
||||||
|
|
|
||||||
11
.github/workflows/gcp-readiness.yml
vendored
11
.github/workflows/gcp-readiness.yml
vendored
|
|
@ -2,6 +2,11 @@ name: gcp-readiness
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
service_account:
|
||||||
|
description: GCP service account to impersonate for the read-only readiness probe
|
||||||
|
required: false
|
||||||
|
default: sa-artifact-builder@teleo-501523.iam.gserviceaccount.com
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
@ -14,7 +19,7 @@ concurrency:
|
||||||
env:
|
env:
|
||||||
PROJECT_ID: teleo-501523
|
PROJECT_ID: teleo-501523
|
||||||
WORKLOAD_IDENTITY_PROVIDER: projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/living-ip-github
|
WORKLOAD_IDENTITY_PROVIDER: projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/living-ip-github
|
||||||
ARTIFACT_SERVICE_ACCOUNT: sa-artifact-builder@teleo-501523.iam.gserviceaccount.com
|
READINESS_SERVICE_ACCOUNT: ${{ inputs.service_account || 'sa-artifact-builder@teleo-501523.iam.gserviceaccount.com' }}
|
||||||
READINESS_ARTIFACT_DIR: gcp-readiness-artifacts
|
READINESS_ARTIFACT_DIR: gcp-readiness-artifacts
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
@ -29,7 +34,7 @@ jobs:
|
||||||
uses: google-github-actions/auth@v2
|
uses: google-github-actions/auth@v2
|
||||||
with:
|
with:
|
||||||
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
|
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
|
||||||
service_account: ${{ env.ARTIFACT_SERVICE_ACCOUNT }}
|
service_account: ${{ env.READINESS_SERVICE_ACCOUNT }}
|
||||||
|
|
||||||
- uses: google-github-actions/setup-gcloud@v2
|
- uses: google-github-actions/setup-gcloud@v2
|
||||||
|
|
||||||
|
|
@ -40,7 +45,7 @@ jobs:
|
||||||
mkdir -p "${READINESS_ARTIFACT_DIR}"
|
mkdir -p "${READINESS_ARTIFACT_DIR}"
|
||||||
{
|
{
|
||||||
echo "project=${PROJECT_ID}"
|
echo "project=${PROJECT_ID}"
|
||||||
echo "service_account=${ARTIFACT_SERVICE_ACCOUNT}"
|
echo "service_account=${READINESS_SERVICE_ACCOUNT}"
|
||||||
echo "workflow_ref=${GITHUB_REF}"
|
echo "workflow_ref=${GITHUB_REF}"
|
||||||
echo "revision=${GITHUB_SHA}"
|
echo "revision=${GITHUB_SHA}"
|
||||||
} > "${READINESS_ARTIFACT_DIR}/context.txt"
|
} > "${READINESS_ARTIFACT_DIR}/context.txt"
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,16 @@ uploads stdout, stderr, exit code, and a summary as the `gcp-readiness` artifact
|
||||||
It is intentionally non-mutating. If the Artifact Registry service account lacks
|
It is intentionally non-mutating. If the Artifact Registry service account lacks
|
||||||
broader read permissions, the artifact is still the proof of the exact IAM gap.
|
broader read permissions, the artifact is still the proof of the exact IAM gap.
|
||||||
|
|
||||||
|
After applying the dedicated readiness service account from the IAM split plan,
|
||||||
|
run the same workflow against that account:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh workflow run gcp-readiness.yml \
|
||||||
|
--repo living-ip/teleo-infrastructure \
|
||||||
|
--ref main \
|
||||||
|
-f service_account=sa-teleo-readiness@teleo-501523.iam.gserviceaccount.com
|
||||||
|
```
|
||||||
|
|
||||||
For a local/manual Cloud Build proof:
|
For a local/manual Cloud Build proof:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -127,6 +137,39 @@ be converted and restored into PostgreSQL with table/row-count parity, but the
|
||||||
same import still needs to run against the GCP Cloud SQL standby through an
|
same import still needs to run against the GCP Cloud SQL standby through an
|
||||||
approved GCP auth and network path.
|
approved GCP auth and network path.
|
||||||
|
|
||||||
|
## IAM Split Plan
|
||||||
|
|
||||||
|
Do not make `sa-artifact-builder@teleo-501523.iam.gserviceaccount.com` the broad
|
||||||
|
infra account. Keep it scoped to Docker image publishing.
|
||||||
|
|
||||||
|
Use the generated plan for the next privilege boundary:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 ops/plan_gcp_iam_split.py --format json
|
||||||
|
python3 ops/plan_gcp_iam_split.py --format shell
|
||||||
|
```
|
||||||
|
|
||||||
|
The plan creates two separate accounts:
|
||||||
|
|
||||||
|
- `sa-teleo-readiness@teleo-501523.iam.gserviceaccount.com`
|
||||||
|
- GitHub WIF account for read-only readiness checks.
|
||||||
|
- Needs read-only roles for Artifact Registry, IAM/service-account/WIF
|
||||||
|
metadata, Compute/network posture, Cloud SQL metadata, backup buckets, and
|
||||||
|
Secret Manager metadata.
|
||||||
|
- `sa-teleo-restore-drill@teleo-501523.iam.gserviceaccount.com`
|
||||||
|
- Operator account for explicit Cloud SQL restore drills.
|
||||||
|
- Needs Cloud SQL edit rights for the import operation and object-admin access
|
||||||
|
to the restore bucket path.
|
||||||
|
|
||||||
|
Cloud SQL imports also require the Cloud SQL instance service account to read the
|
||||||
|
GCS object. The plan includes a command that discovers that instance service
|
||||||
|
account and grants it `roles/storage.objectAdmin` on
|
||||||
|
`gs://teleo-501523-prod-backups`.
|
||||||
|
|
||||||
|
This plan is not itself a completed redundancy proof. DB redundancy is complete
|
||||||
|
only after the restore drill imports source data into Cloud SQL and the retained
|
||||||
|
`target-counts.sql` readback matches the source/local restore proof.
|
||||||
|
|
||||||
## Communication Posture
|
## Communication Posture
|
||||||
|
|
||||||
Current GCP communication hardening is limited to the surfaces already read back:
|
Current GCP communication hardening is limited to the surfaces already read back:
|
||||||
|
|
|
||||||
|
|
@ -267,9 +267,10 @@ def check_github_actions_readiness_ci() -> Check:
|
||||||
text = workflow.read_text()
|
text = workflow.read_text()
|
||||||
required = [
|
required = [
|
||||||
"workflow_dispatch",
|
"workflow_dispatch",
|
||||||
|
"service_account:",
|
||||||
"google-github-actions/auth@v2",
|
"google-github-actions/auth@v2",
|
||||||
"ops/check_gcp_infra_readiness.py",
|
"ops/check_gcp_infra_readiness.py",
|
||||||
GITHUB_ARTIFACT_SERVICE_ACCOUNT,
|
"READINESS_SERVICE_ACCOUNT",
|
||||||
"READINESS_ARTIFACT_DIR: gcp-readiness-artifacts",
|
"READINESS_ARTIFACT_DIR: gcp-readiness-artifacts",
|
||||||
"gcp-readiness-summary.json",
|
"gcp-readiness-summary.json",
|
||||||
"actions/upload-artifact@v4",
|
"actions/upload-artifact@v4",
|
||||||
|
|
|
||||||
260
ops/plan_gcp_iam_split.py
Normal file
260
ops/plan_gcp_iam_split.py
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Emit the least-privilege GCP IAM split for Teleo CI/CD and DB drills.
|
||||||
|
|
||||||
|
This script is intentionally dry-run by default. It does not read or print
|
||||||
|
secret values; it only emits the service accounts, roles, and commands needed
|
||||||
|
to separate Docker publishing from infra readiness and Cloud SQL restore drills.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import shlex
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
|
||||||
|
PROJECT = "teleo-501523"
|
||||||
|
PROJECT_NUMBER = "785938879453"
|
||||||
|
GITHUB_REPOSITORY = "living-ip/teleo-infrastructure"
|
||||||
|
WIF_POOL = "github-actions"
|
||||||
|
WIF_PROVIDER = "living-ip-github"
|
||||||
|
BACKUP_BUCKET = "teleo-501523-prod-backups"
|
||||||
|
CLOUDSQL_INSTANCE = "teleo-pgvector-standby"
|
||||||
|
|
||||||
|
ARTIFACT_SERVICE_ACCOUNT = f"sa-artifact-builder@{PROJECT}.iam.gserviceaccount.com"
|
||||||
|
READINESS_SERVICE_ACCOUNT_ID = "sa-teleo-readiness"
|
||||||
|
READINESS_SERVICE_ACCOUNT = f"{READINESS_SERVICE_ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com"
|
||||||
|
RESTORE_SERVICE_ACCOUNT_ID = "sa-teleo-restore-drill"
|
||||||
|
RESTORE_SERVICE_ACCOUNT = f"{RESTORE_SERVICE_ACCOUNT_ID}@{PROJECT}.iam.gserviceaccount.com"
|
||||||
|
|
||||||
|
READINESS_PROJECT_ROLES = [
|
||||||
|
"roles/artifactregistry.reader",
|
||||||
|
"roles/iam.serviceAccountViewer",
|
||||||
|
"roles/iam.workloadIdentityPoolViewer",
|
||||||
|
"roles/iam.securityReviewer",
|
||||||
|
"roles/compute.viewer",
|
||||||
|
"roles/cloudsql.viewer",
|
||||||
|
"roles/storage.viewer",
|
||||||
|
"roles/secretmanager.viewer",
|
||||||
|
]
|
||||||
|
|
||||||
|
RESTORE_PROJECT_ROLES = [
|
||||||
|
"roles/cloudsql.editor",
|
||||||
|
]
|
||||||
|
|
||||||
|
RESTORE_BUCKET_ROLES = [
|
||||||
|
"roles/storage.objectAdmin",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Binding:
|
||||||
|
scope: str
|
||||||
|
member: str
|
||||||
|
role: str
|
||||||
|
reason: str
|
||||||
|
|
||||||
|
|
||||||
|
def shell_join(parts: list[str]) -> str:
|
||||||
|
return " ".join(shlex.quote(part) for part in parts)
|
||||||
|
|
||||||
|
|
||||||
|
def github_wif_member() -> str:
|
||||||
|
return (
|
||||||
|
f"principalSet://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/"
|
||||||
|
f"workloadIdentityPools/{WIF_POOL}/attribute.repository/{GITHUB_REPOSITORY}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_plan() -> dict[str, object]:
|
||||||
|
readiness_bindings = [
|
||||||
|
Binding(
|
||||||
|
scope=f"projects/{PROJECT}",
|
||||||
|
member=f"serviceAccount:{READINESS_SERVICE_ACCOUNT}",
|
||||||
|
role=role,
|
||||||
|
reason="read-only GitHub readiness probe",
|
||||||
|
)
|
||||||
|
for role in READINESS_PROJECT_ROLES
|
||||||
|
]
|
||||||
|
restore_bindings = [
|
||||||
|
Binding(
|
||||||
|
scope=f"projects/{PROJECT}",
|
||||||
|
member=f"serviceAccount:{RESTORE_SERVICE_ACCOUNT}",
|
||||||
|
role=role,
|
||||||
|
reason="operator-triggered Cloud SQL import operation",
|
||||||
|
)
|
||||||
|
for role in RESTORE_PROJECT_ROLES
|
||||||
|
]
|
||||||
|
restore_bucket_bindings = [
|
||||||
|
Binding(
|
||||||
|
scope=f"gs://{BACKUP_BUCKET}",
|
||||||
|
member=f"serviceAccount:{RESTORE_SERVICE_ACCOUNT}",
|
||||||
|
role=role,
|
||||||
|
reason="upload and verify generated restore SQL in the backup bucket",
|
||||||
|
)
|
||||||
|
for role in RESTORE_BUCKET_ROLES
|
||||||
|
]
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"iam",
|
||||||
|
"service-accounts",
|
||||||
|
"create",
|
||||||
|
READINESS_SERVICE_ACCOUNT_ID,
|
||||||
|
"--project",
|
||||||
|
PROJECT,
|
||||||
|
"--display-name",
|
||||||
|
"Teleo read-only infra readiness",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"iam",
|
||||||
|
"service-accounts",
|
||||||
|
"add-iam-policy-binding",
|
||||||
|
READINESS_SERVICE_ACCOUNT,
|
||||||
|
"--project",
|
||||||
|
PROJECT,
|
||||||
|
"--member",
|
||||||
|
github_wif_member(),
|
||||||
|
"--role",
|
||||||
|
"roles/iam.workloadIdentityUser",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
*[
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"projects",
|
||||||
|
"add-iam-policy-binding",
|
||||||
|
PROJECT,
|
||||||
|
"--member",
|
||||||
|
binding.member,
|
||||||
|
"--role",
|
||||||
|
binding.role,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for binding in readiness_bindings
|
||||||
|
],
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"iam",
|
||||||
|
"service-accounts",
|
||||||
|
"create",
|
||||||
|
RESTORE_SERVICE_ACCOUNT_ID,
|
||||||
|
"--project",
|
||||||
|
PROJECT,
|
||||||
|
"--display-name",
|
||||||
|
"Teleo Cloud SQL restore drill",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
*[
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"projects",
|
||||||
|
"add-iam-policy-binding",
|
||||||
|
PROJECT,
|
||||||
|
"--member",
|
||||||
|
binding.member,
|
||||||
|
"--role",
|
||||||
|
binding.role,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for binding in restore_bindings
|
||||||
|
],
|
||||||
|
*[
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gcloud",
|
||||||
|
"storage",
|
||||||
|
"buckets",
|
||||||
|
"add-iam-policy-binding",
|
||||||
|
binding.scope,
|
||||||
|
"--member",
|
||||||
|
binding.member,
|
||||||
|
"--role",
|
||||||
|
binding.role,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for binding in restore_bucket_bindings
|
||||||
|
],
|
||||||
|
"CLOUDSQL_INSTANCE_SA=$(gcloud sql instances describe "
|
||||||
|
+ shlex.quote(CLOUDSQL_INSTANCE)
|
||||||
|
+ " --project "
|
||||||
|
+ shlex.quote(PROJECT)
|
||||||
|
+ " --format='value(serviceAccountEmailAddress)')",
|
||||||
|
"gcloud storage buckets add-iam-policy-binding "
|
||||||
|
+ shlex.quote(f"gs://{BACKUP_BUCKET}")
|
||||||
|
+ ' --member "serviceAccount:${CLOUDSQL_INSTANCE_SA}" --role roles/storage.objectAdmin',
|
||||||
|
shell_join(
|
||||||
|
[
|
||||||
|
"gh",
|
||||||
|
"workflow",
|
||||||
|
"run",
|
||||||
|
"gcp-readiness.yml",
|
||||||
|
"--repo",
|
||||||
|
GITHUB_REPOSITORY,
|
||||||
|
"--ref",
|
||||||
|
"main",
|
||||||
|
"-f",
|
||||||
|
f"service_account={READINESS_SERVICE_ACCOUNT}",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"artifact": "teleo_gcp_iam_split_plan",
|
||||||
|
"project": PROJECT,
|
||||||
|
"claim_ceiling": "dry_run_plan_only_until_applied_by_authenticated_gcp_admin",
|
||||||
|
"kept_narrow": {
|
||||||
|
"artifact_service_account": ARTIFACT_SERVICE_ACCOUNT,
|
||||||
|
"reason": "Docker image publishing only; do not add broad infra-read or restore roles here.",
|
||||||
|
},
|
||||||
|
"readiness_service_account": READINESS_SERVICE_ACCOUNT,
|
||||||
|
"restore_service_account": RESTORE_SERVICE_ACCOUNT,
|
||||||
|
"github_wif_provider": f"projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{WIF_POOL}/providers/{WIF_PROVIDER}",
|
||||||
|
"github_wif_member": github_wif_member(),
|
||||||
|
"readiness_project_roles": READINESS_PROJECT_ROLES,
|
||||||
|
"restore_project_roles": RESTORE_PROJECT_ROLES,
|
||||||
|
"restore_bucket_roles": RESTORE_BUCKET_ROLES,
|
||||||
|
"cloudsql_instance_service_account_binding": {
|
||||||
|
"bucket": f"gs://{BACKUP_BUCKET}",
|
||||||
|
"role": "roles/storage.objectAdmin",
|
||||||
|
"reason": "Cloud SQL imports read the SQL object through the Cloud SQL instance service account.",
|
||||||
|
},
|
||||||
|
"bindings": {
|
||||||
|
"readiness": [asdict(binding) for binding in readiness_bindings],
|
||||||
|
"restore_operator": [asdict(binding) for binding in restore_bindings],
|
||||||
|
"restore_bucket": [asdict(binding) for binding in restore_bucket_bindings],
|
||||||
|
},
|
||||||
|
"commands": commands,
|
||||||
|
"post_apply_checks": [
|
||||||
|
f"gh workflow run gcp-readiness.yml --repo {GITHUB_REPOSITORY} --ref main -f service_account={READINESS_SERVICE_ACCOUNT}",
|
||||||
|
"EXECUTE=1 ops/run_gcp_cloudsql_restore_drill.sh",
|
||||||
|
"retain target-counts.sql query readback before claiming GCP DB redundancy",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--format", choices=("json", "shell"), default="json")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
plan = build_plan()
|
||||||
|
if args.format == "shell":
|
||||||
|
print("# Dry-run command plan. Review before executing with an authenticated GCP admin.")
|
||||||
|
for command in plan["commands"]:
|
||||||
|
print(command)
|
||||||
|
else:
|
||||||
|
print(json.dumps(plan, indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
39
tests/test_gcp_iam_split_plan.py
Normal file
39
tests/test_gcp_iam_split_plan.py
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_plan(*args: str) -> str:
|
||||||
|
return subprocess.check_output(
|
||||||
|
["python3", "ops/plan_gcp_iam_split.py", *args],
|
||||||
|
cwd=Path(__file__).resolve().parents[1],
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_gcp_iam_split_keeps_artifact_builder_narrow() -> None:
|
||||||
|
plan = json.loads(run_plan())
|
||||||
|
|
||||||
|
assert plan["kept_narrow"]["artifact_service_account"] == (
|
||||||
|
"sa-artifact-builder@teleo-501523.iam.gserviceaccount.com"
|
||||||
|
)
|
||||||
|
assert plan["readiness_service_account"] == "sa-teleo-readiness@teleo-501523.iam.gserviceaccount.com"
|
||||||
|
assert plan["restore_service_account"] == "sa-teleo-restore-drill@teleo-501523.iam.gserviceaccount.com"
|
||||||
|
assert "roles/artifactregistry.writer" not in plan["readiness_project_roles"]
|
||||||
|
assert "roles/cloudsql.editor" not in plan["readiness_project_roles"]
|
||||||
|
assert "roles/compute.viewer" in plan["readiness_project_roles"]
|
||||||
|
assert "roles/cloudsql.viewer" in plan["readiness_project_roles"]
|
||||||
|
assert "roles/secretmanager.viewer" in plan["readiness_project_roles"]
|
||||||
|
assert plan["restore_project_roles"] == ["roles/cloudsql.editor"]
|
||||||
|
assert plan["restore_bucket_roles"] == ["roles/storage.objectAdmin"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_gcp_iam_split_shell_plan_has_post_apply_checks() -> None:
|
||||||
|
shell = run_plan("--format", "shell")
|
||||||
|
|
||||||
|
assert "sa-teleo-readiness" in shell
|
||||||
|
assert "sa-teleo-restore-drill" in shell
|
||||||
|
assert "roles/iam.workloadIdentityUser" in shell
|
||||||
|
assert "CLOUDSQL_INSTANCE_SA=$(gcloud sql instances describe" in shell
|
||||||
|
assert '--member "serviceAccount:${CLOUDSQL_INSTANCE_SA}"' in shell
|
||||||
|
assert "gh workflow run gcp-readiness.yml" in shell
|
||||||
|
|
@ -4,6 +4,8 @@ from pathlib import Path
|
||||||
def test_gcp_readiness_workflow_uses_uploadable_artifact_dir() -> None:
|
def test_gcp_readiness_workflow_uses_uploadable_artifact_dir() -> None:
|
||||||
workflow = Path(".github/workflows/gcp-readiness.yml").read_text()
|
workflow = Path(".github/workflows/gcp-readiness.yml").read_text()
|
||||||
|
|
||||||
|
assert "service_account:" in workflow
|
||||||
|
assert "READINESS_SERVICE_ACCOUNT:" in workflow
|
||||||
assert "READINESS_ARTIFACT_DIR: gcp-readiness-artifacts" in workflow
|
assert "READINESS_ARTIFACT_DIR: gcp-readiness-artifacts" in workflow
|
||||||
assert 'mkdir -p "${READINESS_ARTIFACT_DIR}"' in workflow
|
assert 'mkdir -p "${READINESS_ARTIFACT_DIR}"' in workflow
|
||||||
assert "path: ${{ env.READINESS_ARTIFACT_DIR }}/" in workflow
|
assert "path: ${{ env.READINESS_ARTIFACT_DIR }}/" in workflow
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue