A GCP test-bed for the Kosli snapshot cloud-run and artifact-attestation features. Exercises every deployment pattern a customer might use — Cloud Run services (image- and source-deployed), Cloud Run Jobs (tag- and digest-pinned, scheduled and idle), 2nd-gen Cloud Functions, and App Engine Standard — so the kosli CLI has variety to test against.
Each deployable unit lives in its own top-level directory; pushes to main trigger per-component Cloud Build deploys.
What kosli snapshot cloud-run reports today, broken down by GCP deploy method:
| Deploy method | Container? | Reported | In this repo | Notes |
|---|---|---|---|---|
| Cloud Run service (image-deployed) | Yes | ✓ Full | hello-world, friend-app |
|
| Cloud Run service (source / Buildpacks) | Yes (built for you) | ✓ Full | remote-neighbor |
|
| Cloud Run Job | Yes | ✓ Full | sandman-job, ghost-job, kosli-reporter |
|
| Cloud Run function (Cloud Functions 2nd gen) | Yes (Buildpacks) | ✓ Full | cousin-fn |
|
| Cloud Functions 1st gen | No (Google packages the source) | ✗ | — | |
| App Engine Standard | No (gVisor sandbox, not a container) | ✗ | hello-appengine |
Not currently supported in the CLI. |
| App Engine Flexible | Yes (containers on managed VMs) | ✗ | — | |
| GKE (Standard / Autopilot) | Yes | ✗ | — | Use kosli snapshot k8s instead. |
| Cloud Run for Anthos | Yes (knative on GKE) | ✗ | — | |
| Compute Engine + Container-Optimized OS | Yes (Docker on a VM) | ✗ | — |
| Directory | Resource | Deploy pattern | Logs | Notes |
|---|---|---|---|---|
hello-world/ |
Cloud Run service | image-deployed with :$COMMIT_SHA |
-t hello-world |
Has provenance. Baseline service. Runs a Kosli attest artifact step before deploy (the cloud-run-hello-world flow). |
friend-app/ |
Cloud Run service | image-deployed with :$COMMIT_SHA |
-t friend-app |
Has provenance. Second service to exercise multi-service snapshots. Runs kosli attest artifact before deploy (the cloud-run-friend-app flow). |
remote-neighbor/ |
Cloud Run service | source-deployed via Buildpacks (gcloud run deploy --source) |
-t remote-neighbor |
Has provenance. No Dockerfile in the repo; Cloud Build invokes Buildpacks. Image lands in the auto-created cloud-run-source-deploy AR repo. Runs kosli attest artifact after deploy (the cloud-run-remote-neighbor flow). |
cousin-fn/ |
Cloud Run function (2nd gen) | source-deployed via Buildpacks | -t cousin-fn |
Has provenance. Backed by a Cloud Run service; image lives in the Google-managed gcf-artifacts repo. Runs kosli attest artifact after deploy (the cloud-run-cousin-fn flow). |
sandman-job/ |
Cloud Run Job | digest-pinned (@sha256:...) |
-t sandman-job |
Has provenance. Resolves tag → digest at deploy time in cloudbuild.yaml. Scheduled every 5 min via Cloud Scheduler so it generates execution history. Runs kosli attest artifact before deploy (the cloud-run-sandman-job flow). |
ghost-job/ |
Cloud Run Job | tag-pinned (:$COMMIT_SHA) |
-t ghost-job |
Has provenance. The tag-pinned counterpart to sandman, deliberately exercising the most common customer pattern. Never invoked. Runs kosli attest artifact before deploy (the cloud-run-ghost-job flow). |
kosli-reporter/ |
Cloud Run Job | tracks moving :released tag |
-t kosli-reporter |
Runs kosli snapshot cloud-run every 5 min via Cloud Scheduler. See "Kosli integration" below. |
appengine/ |
App Engine Standard service | source-deployed | -t hello-appengine |
hello-appengine. Not reported by snapshot cloud-run; in the repo to exercise the build pipeline and the GIT_SHA env-var provenance pattern. |
appengine-bootstrap/ |
App Engine Standard default service |
one-time stub | — | Required because App Engine refuses to create named services before a default exists. Deploy once manually, then ignore. |
Tail logs for any component with ./bin/logs_gcp.sh <Logs> hello-world-cli-demo — append -f to follow, -m <minutes> to widen the time window. ./bin/logs_gcp.sh -h for full usage.
Run the hello-world containerized app locally (builds the image and starts the container in the background):
make demoThen open http://localhost:8090.
Other targets:
make logs_demo— show container logs.make follow_demo— follow container logs (Ctrl+C to detach).make down— stop the running container.make build— build the Docker image only.make run— start an already-built image (detached).
Prerequisites: gcloud, terraform, docker, make.
-
Authenticate with Google Cloud:
gcloud auth login gcloud auth application-default login gcloud config set project hello-world-cli-demo -
Connect GitHub to Cloud Build (one-time, manual — Terraform cannot do this):
In the GCP Console, go to Cloud Build → Triggers → Manage repositories → Connect repository, choose GitHub (Cloud Build GitHub App), authorize the
kosli-dev/cloud-run-examplerepo, and finish the wizard. Region:europe-west1. -
Apply Terraform:
cd terraform terraform init terraform applyThe first apply deploys the Cloud Run services with a public placeholder image (
gcr.io/cloudrun/hello). Your real apps appear after the first push tomaintriggers each component's build.
Push to main:
git push origin mainCloud Build picks up the push and runs each component's per-path trigger. Triggers are scoped by included_files:
| Trigger | Watches | Action |
|---|---|---|
deploy-hello-world |
hello-world/** |
Build → push → attest to Kosli (before deploy) → deploy Cloud Run service |
deploy-friend-app |
friend-app/** |
Build → push → attest to Kosli (before deploy) → deploy Cloud Run service |
deploy-remote-neighbor |
remote-neighbor/** |
gcloud run deploy --source (Buildpacks; no docker build/push step) → tag image with :$COMMIT_SHA → attest to Kosli (after deploy) |
deploy-cousin-fn |
cousin-fn/** |
gcloud functions deploy (re-uploads source) → attest to Kosli (after deploy) |
deploy-sandman-job |
sandman-job/** |
Build → push → attest to Kosli (before deploy) → resolve digest → deploy Job pinned to digest |
deploy-ghost-job |
ghost-job/** |
Build → push → attest to Kosli (before deploy) → deploy Job pinned to :$COMMIT_SHA |
deploy-kosli-reporter |
kosli-reporter/** |
Build → push (:$COMMIT_SHA + :released) → update Job to :released |
deploy-hello-appengine |
appengine/** |
sed $COMMIT_SHA into app.yaml → gcloud app deploy |
setup-kosli-flows |
kosli-flow-templates/** |
kosli create flow for each *.yml template (idempotent; create or update) |
setup-kosli-policies |
kosli-policies/** |
kosli attach-policy for each policy listed in kosli-policies/cloudbuild.yaml (policies themselves are org-managed) |
Watch builds at https://console.cloud.google.com/cloud-build/builds?project=hello-world-cli-demo.
Two flows are wired up:
Snapshot side — kosli-reporter Job. Runs kosli snapshot cloud-run cloud-run-example --project=hello-world-cli-demo --region=europe-west1 --resolve-names every 5 min via Cloud Scheduler. Reports running Services / Jobs / 2nd-gen Functions to the cloud-run-example environment in the kosli-public org. Required IAM:
roles/run.viewer(project-level)roles/secretmanager.secretAccessoron thekosli-api-tokensecretroles/artifactregistry.readeron thecontainersrepo AND on the Google-managedgcf-artifactsrepo (for 2nd-gen function image lookups)
Build side — per-service flows. Six components run kosli begin trail + kosli attest artifact from their cloudbuild.yaml: hello-world, friend-app, remote-neighbor, sandman-job, ghost-job, and cousin-fn. The CLI is curl-downloaded inside the step (no image dependency). The components attest at different points in the build depending on when the image first exists:
- Before deploy —
hello-world,friend-app,sandman-job,ghost-job. Images are built and pushed by our owndockersteps, so the digest is known up front. Build-success guarantees an attestation regardless of deploy outcome. - After deploy —
remote-neighbor,cousin-fn. Source/Buildpacks builds happen insidegcloud run deploy --source/gcloud functions deploy; the image doesn't exist until those commands finish. Trade-off: deploy failure means no attestation. Unavoidable with source-deployed components.
Components without attestation: kosli-reporter (meta-service running the snapshots; also filtered out of its own snapshot output via --exclude=kosli-reporter so it doesn't show up as a non-compliant artifact) and appengine (no image to attest, App Engine isn't reported anyway).
Flow naming convention: cloud-run-<service-name> (so cloud-run-hello-world). Flow templates live in kosli-flow-templates/. A dedicated Cloud Build trigger (setup-kosli-flows) iterates over every *.yml in that directory and runs kosli create flow for each (creating or updating idempotently). It fires automatically on push to main whenever any template changes — so "edit + push" is enough to roll out a new or changed flow. To run it on demand without a push:
gcloud builds triggers run setup-kosli-flows --branch=main --region=globalEnvironment policies are managed at the org level on Kosli (kosli-public); this repo only attaches them to the cloud-run-example environment. The setup-kosli-policies Cloud Build trigger runs kosli attach-policy <name> --environment cloud-run-example for each policy listed in kosli-policies/cloudbuild.yaml. Currently attached:
has-provenance— every running artifact must have provenance (be attested in some flow).trail-compliant— the trail the artifact comes from must itself be compliant.
To run it on demand:
gcloud builds triggers run setup-kosli-policies --branch=main --region=globalThe kosli-reporter Job tracks the :released tag, which Cloud Build keeps current on every push to main. To swap in a locally-built CLI without committing:
-
Build a linux/amd64 binary into
kosli-reporter/kosli(gitignored):cd /path/to/cli && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build \ -o /path/to/cloud-run-example/kosli-reporter/kosli .
-
make deploy-dev-reporter— builds withkosli-reporter/Dockerfile.dev, pushes a:dev-<timestamp>tag, points the Job at it. -
gcloud run jobs execute kosli-reporter --region=europe-west1 --waitto run it.
Swap back to the official CLI with make deploy-released-reporter. Per-commit :$COMMIT_SHA images stay in Artifact Registry for rollback to any prior release.
cd terraform
terraform output service_urlcd terraform
terraform destroy