Merge pull request #228 from living-ip/codex/gcp-nosend-wif-publish-20260722
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
Publish exact Leoclean no-send release through WIF
This commit is contained in:
commit
3569f4f2fb
5 changed files with 1808 additions and 86 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -100,6 +100,7 @@ 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_artifact_workflow.py \
|
tests/test_gcp_artifact_workflow.py \
|
||||||
|
tests/test_gcp_leoclean_nosend_release_workflow.py \
|
||||||
tests/test_capture_vps_canonical_postgres_snapshot.py \
|
tests/test_capture_vps_canonical_postgres_snapshot.py \
|
||||||
tests/test_gcp_infra_execute_canary.py \
|
tests/test_gcp_infra_execute_canary.py \
|
||||||
tests/test_gcp_infra_readiness_checker.py \
|
tests/test_gcp_infra_readiness_checker.py \
|
||||||
|
|
|
||||||
333
.github/workflows/gcp-leoclean-nosend-release.yml
vendored
Normal file
333
.github/workflows/gcp-leoclean-nosend-release.yml
vendored
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
name: gcp-leoclean-nosend-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: gcp-leoclean-nosend-release-main
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT_ID: teleo-501523
|
||||||
|
REGION: europe-west6
|
||||||
|
ARTIFACT_REPOSITORY: teleo
|
||||||
|
IMAGE_REPOSITORY: europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging
|
||||||
|
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
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
name: Build, smoke, and publish exact no-send release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 90
|
||||||
|
steps:
|
||||||
|
- name: Require manual main dispatch
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test "${GITHUB_EVENT_NAME}" = "workflow_dispatch"
|
||||||
|
test "${GITHUB_REF}" = "refs/heads/main"
|
||||||
|
test "${GITHUB_REF_TYPE}" = "branch"
|
||||||
|
test "${GITHUB_REPOSITORY}" = "living-ip/teleo-infrastructure"
|
||||||
|
test "${GITHUB_WORKFLOW_REF}" = \
|
||||||
|
"living-ip/teleo-infrastructure/.github/workflows/gcp-leoclean-nosend-release.yml@refs/heads/main"
|
||||||
|
|
||||||
|
- name: Check out exact dispatched revision
|
||||||
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
ref: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Verify exact clean main revision
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
test "$(git rev-parse --verify HEAD)" = "${GITHUB_SHA}"
|
||||||
|
test "$(git rev-parse --verify refs/remotes/origin/main^{commit})" = "${GITHUB_SHA}"
|
||||||
|
test -z "$(git status --porcelain=v1 --untracked-files=all)"
|
||||||
|
|
||||||
|
- name: Initialize private release paths
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
release_root="${RUNNER_TEMP}/leoclean-nosend-release"
|
||||||
|
install -d -m 0700 "${release_root}"
|
||||||
|
{
|
||||||
|
echo "RELEASE_ROOT=${release_root}"
|
||||||
|
echo "RUNTIME_ARTIFACT=${release_root}/runtime-artifact"
|
||||||
|
echo "RUNTIME_RECEIPT=${release_root}/evidence/runtime-receipt.json"
|
||||||
|
echo "OCI_SMOKE_RECEIPT=${release_root}/evidence/oci-smoke.json"
|
||||||
|
echo "IMAGE_CONTEXT=${release_root}/image-context"
|
||||||
|
echo "BUILD_PUSH_RECEIPT=${release_root}/receipts/build-push-receipt.json"
|
||||||
|
echo "PUBLISH_OUTCOME=${release_root}/evidence/publish-outcome.json"
|
||||||
|
echo "RELEASE_BUNDLE=${release_root}/final/release-v3"
|
||||||
|
echo "DOCKER_CONFIG=${release_root}/docker-config"
|
||||||
|
} >>"${GITHUB_ENV}"
|
||||||
|
|
||||||
|
- name: Set up exact Python
|
||||||
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
||||||
|
with:
|
||||||
|
python-version: "3.11.9"
|
||||||
|
|
||||||
|
- name: Install exact no-send build tools
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python -m pip install --disable-pip-version-check 'uv==0.9.30' 'PyYAML==6.0.3'
|
||||||
|
test "$(uv --version | awk '{print $2}')" = "0.9.30"
|
||||||
|
test "$(command -v docker)" = "/usr/bin/docker"
|
||||||
|
|
||||||
|
- name: Verify fixed staging package target
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python - <<'PY'
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ops import gcp_leoclean_nosend_package as package
|
||||||
|
|
||||||
|
assert package.PROJECT == "teleo-501523"
|
||||||
|
assert package.PLATFORM == "linux/amd64"
|
||||||
|
assert os.environ["IMAGE_REPOSITORY"] == package.IMAGE_REPOSITORY
|
||||||
|
assert package.IMAGE_REPOSITORY == "europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging"
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Pull digest-pinned OCI bases
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker pull --platform=linux/amd64 \
|
||||||
|
"ghcr.io/astral-sh/uv:0.9.30@sha256:538e0b39736e7feae937a65983e49d2ab75e1559d35041f9878b7b7e51de91e4"
|
||||||
|
docker pull --platform=linux/amd64 \
|
||||||
|
"docker.io/library/python:3.11.9-slim-bookworm@sha256:8fb099199b9f2d70342674bd9dbccd3ed03a258f26bbd1d556822c6dfc60c317"
|
||||||
|
docker pull --platform=linux/amd64 \
|
||||||
|
"docker.io/library/postgres:16.14-bookworm@sha256:92620daddcd947f8d5ab5ba66e848702fe443d87fed30c4cea8e389fd78dfc55"
|
||||||
|
|
||||||
|
- name: Compile and release-verify exact runtime artifact
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 "${RELEASE_ROOT}" "${RELEASE_ROOT}/evidence"
|
||||||
|
LEOCLEAN_NOSEND_RESULTS_DIR="${RELEASE_ROOT}/evidence" \
|
||||||
|
LEOCLEAN_NOSEND_RECEIPT="${RUNTIME_RECEIPT}" \
|
||||||
|
LEOCLEAN_NOSEND_ARTIFACT_OUTPUT="${RUNTIME_ARTIFACT}" \
|
||||||
|
scripts/run_leoclean_nosend_runtime_canary.sh
|
||||||
|
|
||||||
|
- name: Smoke exact no-send OCI surface
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python scripts/run_gcp_leoclean_nosend_oci_smoke.py \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--artifact "${RUNTIME_ARTIFACT}" \
|
||||||
|
--runtime-receipt "${RUNTIME_RECEIPT}" \
|
||||||
|
--output "${OCI_SMOKE_RECEIPT}"
|
||||||
|
|
||||||
|
- name: Prepare exact candidate context
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
identity_contract="${RELEASE_ROOT}/identity-runtime-contract.json"
|
||||||
|
identity_manifest="${RELEASE_ROOT}/identity-manifest.json"
|
||||||
|
identity_bundle="${RELEASE_ROOT}/identity"
|
||||||
|
|
||||||
|
python ops/gcp_leoclean_nosend_package.py identity-contract \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--artifact "${RUNTIME_ARTIFACT}" \
|
||||||
|
--receipt "${RUNTIME_RECEIPT}" \
|
||||||
|
--output "${identity_contract}"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py generate-identity-manifest \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--artifact "${RUNTIME_ARTIFACT}" \
|
||||||
|
--receipt "${RUNTIME_RECEIPT}" \
|
||||||
|
--database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \
|
||||||
|
--constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \
|
||||||
|
--database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \
|
||||||
|
--identity-source-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--output "${identity_manifest}"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py compile-identity \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--artifact "${RUNTIME_ARTIFACT}" \
|
||||||
|
--receipt "${RUNTIME_RECEIPT}" \
|
||||||
|
--identity-manifest "${identity_manifest}" \
|
||||||
|
--identity-source-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--output "${identity_bundle}"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py prepare \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--artifact "${RUNTIME_ARTIFACT}" \
|
||||||
|
--receipt "${RUNTIME_RECEIPT}" \
|
||||||
|
--identity "${identity_bundle}" \
|
||||||
|
--identity-source-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--output "${IMAGE_CONTEXT}"
|
||||||
|
|
||||||
|
python - "${OCI_SMOKE_RECEIPT}" "${IMAGE_CONTEXT}/image-input.json" "${GITHUB_SHA}" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
smoke = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||||
|
image_input = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
|
||||||
|
revision = sys.argv[3]
|
||||||
|
assert smoke["status"] == "pass"
|
||||||
|
assert smoke["cleanup"]["status"] == "pass"
|
||||||
|
assert smoke["teleo_git_head"] == revision
|
||||||
|
assert image_input["runtime"]["teleo_git_head"] == revision
|
||||||
|
assert smoke["input_sha256"] == image_input["input_sha256"]
|
||||||
|
assert smoke["identity_sha256"] == image_input["identity"]["bundle_sha256"]
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Initialize pre-mutation publish outcome
|
||||||
|
id: init_outcome
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 "$(dirname "${PUBLISH_OUTCOME}")"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py init-outcome \
|
||||||
|
--image-input "${IMAGE_CONTEXT}/image-input.json" \
|
||||||
|
--output-journal "${PUBLISH_OUTCOME}"
|
||||||
|
|
||||||
|
- name: Authenticate to Google Cloud through WIF
|
||||||
|
id: auth
|
||||||
|
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed
|
||||||
|
with:
|
||||||
|
workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
|
||||||
|
service_account: ${{ env.ARTIFACT_SERVICE_ACCOUNT }}
|
||||||
|
create_credentials_file: true
|
||||||
|
export_environment_variables: true
|
||||||
|
|
||||||
|
- name: Set up exact gcloud
|
||||||
|
uses: google-github-actions/setup-gcloud@e427ad8a34f8676edf47cf7d7925499adf3eb74f
|
||||||
|
with:
|
||||||
|
version: "550.0.0"
|
||||||
|
|
||||||
|
- name: Require immutable fixed Artifact Registry repository
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
repository_readback="${RUNNER_TEMP}/leoclean-nosend-repository.json"
|
||||||
|
umask 077
|
||||||
|
test "$(gcloud auth list --filter=status:ACTIVE --format='value(account)')" = \
|
||||||
|
"${ARTIFACT_SERVICE_ACCOUNT}"
|
||||||
|
gcloud artifacts repositories describe "${ARTIFACT_REPOSITORY}" \
|
||||||
|
--project "${PROJECT_ID}" \
|
||||||
|
--location "${REGION}" \
|
||||||
|
--format json >"${repository_readback}"
|
||||||
|
python - "${repository_readback}" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
repository = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||||
|
assert repository["name"] == (
|
||||||
|
"projects/teleo-501523/locations/europe-west6/repositories/teleo"
|
||||||
|
)
|
||||||
|
assert repository["format"] == "DOCKER"
|
||||||
|
assert repository["dockerConfig"]["immutableTags"] is True
|
||||||
|
PY
|
||||||
|
rm -f -- "${repository_readback}"
|
||||||
|
|
||||||
|
- name: Configure private Artifact Registry Docker auth
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 "${DOCKER_CONFIG}"
|
||||||
|
gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet
|
||||||
|
test -f "${DOCKER_CONFIG}/config.json"
|
||||||
|
test ! -L "${DOCKER_CONFIG}/config.json"
|
||||||
|
chmod 0600 "${DOCKER_CONFIG}/config.json"
|
||||||
|
|
||||||
|
- name: Build and push one immutable candidate
|
||||||
|
id: build_push
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 "$(dirname "${BUILD_PUSH_RECEIPT}")"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py build-push \
|
||||||
|
--repo-root "${GITHUB_WORKSPACE}" \
|
||||||
|
--image-context "${IMAGE_CONTEXT}" \
|
||||||
|
--docker-binary /usr/bin/docker \
|
||||||
|
--docker-host unix:///var/run/docker.sock \
|
||||||
|
--docker-config "${DOCKER_CONFIG}" \
|
||||||
|
--gcloud-binary "$(command -v gcloud)" \
|
||||||
|
--output-receipt "${BUILD_PUSH_RECEIPT}" \
|
||||||
|
--outcome-journal "${PUBLISH_OUTCOME}" \
|
||||||
|
--execute-staging-push
|
||||||
|
|
||||||
|
- name: Finalize and validate release v3 bundle
|
||||||
|
id: finalize
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
install -d -m 0700 "$(dirname "${RELEASE_BUNDLE}")"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py finalize \
|
||||||
|
--image-input "${IMAGE_CONTEXT}/image-input.json" \
|
||||||
|
--build-push-receipt "${BUILD_PUSH_RECEIPT}" \
|
||||||
|
--docker-binary /usr/bin/docker \
|
||||||
|
--docker-host unix:///var/run/docker.sock \
|
||||||
|
--docker-config "${DOCKER_CONFIG}" \
|
||||||
|
--output-bundle "${RELEASE_BUNDLE}"
|
||||||
|
python ops/gcp_leoclean_nosend_package.py validate \
|
||||||
|
--release "${RELEASE_BUNDLE}/release.json"
|
||||||
|
|
||||||
|
python - "${BUILD_PUSH_RECEIPT}" "${RELEASE_BUNDLE}/release.json" "${OCI_SMOKE_RECEIPT}" "${GITHUB_SHA}" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
receipt = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||||
|
release = json.loads(Path(sys.argv[2]).read_text(encoding="utf-8"))
|
||||||
|
smoke = json.loads(Path(sys.argv[3]).read_text(encoding="utf-8"))
|
||||||
|
revision = sys.argv[4]
|
||||||
|
assert receipt["schema"] == "livingip.leocleanNoSendBuildPushReceipt.v1"
|
||||||
|
assert release["schema"] == "livingip.leocleanNoSendRelease.v3"
|
||||||
|
assert release["build_push_receipt"] == receipt
|
||||||
|
assert release["image_input"]["runtime"]["teleo_git_head"] == revision
|
||||||
|
assert release["image_input"]["input_sha256"] == smoke["input_sha256"]
|
||||||
|
assert release["image"]["reference"].startswith(
|
||||||
|
"europe-west6-docker.pkg.dev/teleo-501523/teleo/leoclean-nosend-staging@sha256:"
|
||||||
|
)
|
||||||
|
PY
|
||||||
|
|
||||||
|
- name: Remove ephemeral Docker authentication
|
||||||
|
id: cleanup
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
rm -rf -- "${DOCKER_CONFIG}"
|
||||||
|
test ! -e "${DOCKER_CONFIG}"
|
||||||
|
test ! -L "${DOCKER_CONFIG}"
|
||||||
|
|
||||||
|
- name: Finalize publish outcome
|
||||||
|
if: always() && steps.init_outcome.outcome == 'success'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
python ops/gcp_leoclean_nosend_package.py complete-outcome \
|
||||||
|
--image-input "${IMAGE_CONTEXT}/image-input.json" \
|
||||||
|
--outcome-journal "${PUBLISH_OUTCOME}" \
|
||||||
|
--build-push-receipt "${BUILD_PUSH_RECEIPT}" \
|
||||||
|
--release "${RELEASE_BUNDLE}/release.json" \
|
||||||
|
--build-outcome "${{ steps.build_push.outcome }}" \
|
||||||
|
--finalize-outcome "${{ steps.finalize.outcome }}" \
|
||||||
|
--cleanup-outcome "${{ steps.cleanup.outcome }}"
|
||||||
|
|
||||||
|
- name: Upload exact available build and release evidence
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||||
|
with:
|
||||||
|
name: leoclean-nosend-release-${{ github.sha }}
|
||||||
|
path: |
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/evidence/runtime-receipt.json
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/evidence/oci-smoke.json
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/evidence/publish-outcome.json
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/receipts/build-push-receipt.json
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/final/release-v3/release.json
|
||||||
|
${{ runner.temp }}/leoclean-nosend-release/final/release-v3/leoclean-gcp-nosend.service
|
||||||
|
if-no-files-found: warn
|
||||||
|
retention-days: 30
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -19,6 +19,9 @@ from ops import gcp_leoclean_nosend_package as package
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
REVISION = package._git_commit(ROOT)
|
REVISION = package._git_commit(ROOT)
|
||||||
IMAGE_DIGEST = "2" * 64
|
IMAGE_DIGEST = "2" * 64
|
||||||
|
LOCAL_CONFIG_DIGEST = "sha256:" + "3" * 64
|
||||||
|
REMOTE_CONFIG_DIGEST = "sha256:" + "6" * 64
|
||||||
|
ROOTFS_LAYERS = ["sha256:" + "7" * 64, "sha256:" + "8" * 64]
|
||||||
CLAIM_CEILING = "Offline package proof only; live GCP identity, database access, restart, and parity remain unproven."
|
CLAIM_CEILING = "Offline package proof only; live GCP identity, database access, restart, and parity remain unproven."
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -394,7 +397,8 @@ def docker_inspection_fixture(
|
||||||
image_input: dict[str, object],
|
image_input: dict[str, object],
|
||||||
reference: str,
|
reference: str,
|
||||||
*,
|
*,
|
||||||
config_digest: str = "sha256:" + "3" * 64,
|
config_digest: str = LOCAL_CONFIG_DIGEST,
|
||||||
|
rootfs_layers: list[str] | None = None,
|
||||||
) -> dict[str, object]:
|
) -> dict[str, object]:
|
||||||
runtime = copy.deepcopy(package.IMAGE_RUNTIME_CONFIG)
|
runtime = copy.deepcopy(package.IMAGE_RUNTIME_CONFIG)
|
||||||
healthcheck = runtime["healthcheck"]
|
healthcheck = runtime["healthcheck"]
|
||||||
|
|
@ -404,6 +408,7 @@ def docker_inspection_fixture(
|
||||||
"Architecture": "amd64",
|
"Architecture": "amd64",
|
||||||
"Os": "linux",
|
"Os": "linux",
|
||||||
"Variant": None,
|
"Variant": None,
|
||||||
|
"RootFS": {"Type": "layers", "Layers": list(rootfs_layers or ROOTFS_LAYERS)},
|
||||||
"Config": {
|
"Config": {
|
||||||
"Labels": package.expected_image_labels(image_input),
|
"Labels": package.expected_image_labels(image_input),
|
||||||
"User": runtime["user"],
|
"User": runtime["user"],
|
||||||
|
|
@ -447,6 +452,32 @@ def docker_authority(tmp_path: Path) -> tuple[Path, str, Path]:
|
||||||
return docker_binary, "unix:///tmp/livingip-docker.sock", docker_config
|
return docker_binary, "unix:///tmp/livingip-docker.sock", docker_config
|
||||||
|
|
||||||
|
|
||||||
|
def publish_outcome_authority(
|
||||||
|
tmp_path: Path,
|
||||||
|
image_input: dict[str, object],
|
||||||
|
) -> tuple[Path, Path]:
|
||||||
|
gcloud_binary = tmp_path / "bin" / "gcloud"
|
||||||
|
gcloud_binary.parent.mkdir(exist_ok=True)
|
||||||
|
gcloud_binary.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
|
||||||
|
gcloud_binary.chmod(0o755)
|
||||||
|
outcome_journal = tmp_path / "publish-outcome.json"
|
||||||
|
package.publish_publish_outcome(
|
||||||
|
outcome_journal,
|
||||||
|
package.build_initial_publish_outcome(image_input),
|
||||||
|
create=True,
|
||||||
|
)
|
||||||
|
return gcloud_binary, outcome_journal
|
||||||
|
|
||||||
|
|
||||||
|
def mock_fresh_registry_reconcile(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
*,
|
||||||
|
manifest_digest: str = "sha256:" + IMAGE_DIGEST,
|
||||||
|
) -> None:
|
||||||
|
monkeypatch.setattr(package, "_registry_candidate_digest", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(package, "_wait_registry_candidate_digest", lambda *_args: manifest_digest)
|
||||||
|
|
||||||
|
|
||||||
def docker_inventory_fixture(
|
def docker_inventory_fixture(
|
||||||
reference: str,
|
reference: str,
|
||||||
*,
|
*,
|
||||||
|
|
@ -505,19 +536,28 @@ def mocked_build_push_docker(
|
||||||
*,
|
*,
|
||||||
post_push_references: list[str] | None = None,
|
post_push_references: list[str] | None = None,
|
||||||
fail_after_side_effect: str | None = None,
|
fail_after_side_effect: str | None = None,
|
||||||
|
remote_config_digest: str = LOCAL_CONFIG_DIGEST,
|
||||||
|
remote_rootfs_layers: list[str] | None = None,
|
||||||
|
remote_verification_valid: bool = True,
|
||||||
) -> tuple[object, dict[str, object], list[tuple[list[str], dict[str, object]]]]:
|
) -> tuple[object, dict[str, object], list[tuple[list[str], dict[str, object]]]]:
|
||||||
config_digest = "sha256:" + "3" * 64
|
config_digest = LOCAL_CONFIG_DIGEST
|
||||||
exact_reference = f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}"
|
exact_reference = f"{package.IMAGE_REPOSITORY}@sha256:{IMAGE_DIGEST}"
|
||||||
references = list(post_push_references if post_push_references is not None else [exact_reference])
|
references = list(post_push_references if post_push_references is not None else [exact_reference])
|
||||||
build_tag = f"input-{image_input['input_sha256']}"
|
build_tag = f"input-{image_input['input_sha256']}"
|
||||||
build_reference = f"{package.LOCAL_BUILD_REPOSITORY}:{build_tag}"
|
build_reference = f"{package.LOCAL_BUILD_REPOSITORY}:{build_tag}"
|
||||||
candidate_tag = f"candidate-{config_digest.removeprefix('sha256:')}"
|
candidate_tag = f"candidate-{image_input['input_sha256']}"
|
||||||
candidate_reference = f"{package.IMAGE_REPOSITORY}:{candidate_tag}"
|
candidate_reference = f"{package.IMAGE_REPOSITORY}:{candidate_tag}"
|
||||||
state: dict[str, object] = {"build": False, "candidate": False, "digests": False, "pushes": 0}
|
state: dict[str, object] = {"build": False, "candidate": False, "digests": False, "pushes": 0}
|
||||||
calls: list[tuple[list[str], dict[str, object]]] = []
|
calls: list[tuple[list[str], dict[str, object]]] = []
|
||||||
|
|
||||||
def inspection(reference: str) -> bytes:
|
def inspection(reference: str) -> bytes:
|
||||||
value = docker_inspection_fixture(image_input, reference, config_digest=config_digest)
|
is_remote = "@sha256:" in reference
|
||||||
|
value = docker_inspection_fixture(
|
||||||
|
image_input,
|
||||||
|
reference,
|
||||||
|
config_digest=remote_config_digest if is_remote else config_digest,
|
||||||
|
rootfs_layers=remote_rootfs_layers if is_remote else ROOTFS_LAYERS,
|
||||||
|
)
|
||||||
value["RepoDigests"] = references if state["digests"] else []
|
value["RepoDigests"] = references if state["digests"] else []
|
||||||
return json.dumps([value]).encode()
|
return json.dumps([value]).encode()
|
||||||
|
|
||||||
|
|
@ -576,6 +616,52 @@ def mocked_build_push_docker(
|
||||||
stdout=("sha256:" + "9" * 64 + "\n").encode(),
|
stdout=("sha256:" + "9" * 64 + "\n").encode(),
|
||||||
stderr=b"",
|
stderr=b"",
|
||||||
)
|
)
|
||||||
|
if operation[0] == "pull":
|
||||||
|
assert operation[1:4] == ["--quiet", "--platform", package.PLATFORM]
|
||||||
|
assert operation[4] in references
|
||||||
|
state["digests"] = True
|
||||||
|
return subprocess.CompletedProcess(arguments, 0, stdout=b"pulled\n", stderr=b"")
|
||||||
|
if operation[0] == "run":
|
||||||
|
assert operation == [
|
||||||
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"--pull=never",
|
||||||
|
"--platform",
|
||||||
|
package.PLATFORM,
|
||||||
|
"--network=none",
|
||||||
|
"--read-only",
|
||||||
|
"--cap-drop=ALL",
|
||||||
|
"--security-opt=no-new-privileges:true",
|
||||||
|
"--pids-limit=64",
|
||||||
|
f"--user={package.RUNTIME_UID}:{package.RUNTIME_GID}",
|
||||||
|
exact_reference,
|
||||||
|
"verify-build",
|
||||||
|
image_input["runtime"]["teleo_git_head"],
|
||||||
|
image_input["runtime"]["artifact_sha256"],
|
||||||
|
image_input["identity"]["bundle_sha256"],
|
||||||
|
image_input["input_sha256"],
|
||||||
|
]
|
||||||
|
verification = (
|
||||||
|
{
|
||||||
|
"schema": "livingip.leocleanNoSendContainerVerification.v1",
|
||||||
|
"status": "pass",
|
||||||
|
"teleo_git_head": image_input["runtime"]["teleo_git_head"],
|
||||||
|
"artifact_sha256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"identity_sha256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"input_sha256": image_input["input_sha256"],
|
||||||
|
"python_version": package.PYTHON_VERSION,
|
||||||
|
"postgres_version": package.POSTGRES_VERSION,
|
||||||
|
"cloudsql_ca_sha256": package.CA_SHA256,
|
||||||
|
}
|
||||||
|
if remote_verification_valid
|
||||||
|
else {"status": "fail"}
|
||||||
|
)
|
||||||
|
return subprocess.CompletedProcess(
|
||||||
|
arguments,
|
||||||
|
0,
|
||||||
|
stdout=(json.dumps(verification) + "\n").encode(),
|
||||||
|
stderr=b"",
|
||||||
|
)
|
||||||
if operation[:2] == ["image", "inspect"]:
|
if operation[:2] == ["image", "inspect"]:
|
||||||
reference = operation[2]
|
reference = operation[2]
|
||||||
present = (
|
present = (
|
||||||
|
|
@ -602,6 +688,98 @@ def mocked_build_push_docker(
|
||||||
return fake_run, state, calls
|
return fake_run, state, calls
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_candidate_lookup_uses_exact_resource_shape_and_command(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
candidate_tag = "candidate-" + "a" * 64
|
||||||
|
digest = "b" * 64
|
||||||
|
expected_tag = f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/{candidate_tag}"
|
||||||
|
fixture = [
|
||||||
|
{
|
||||||
|
"tag": expected_tag,
|
||||||
|
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{digest}",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
observed: dict[str, object] = {}
|
||||||
|
|
||||||
|
def fake_gcloud(
|
||||||
|
binary: Path,
|
||||||
|
arguments: list[str],
|
||||||
|
*,
|
||||||
|
timeout: int = 60,
|
||||||
|
) -> subprocess.CompletedProcess[bytes]:
|
||||||
|
observed.update({"binary": binary, "arguments": arguments, "timeout": timeout})
|
||||||
|
return subprocess.CompletedProcess(arguments, 0, stdout=json.dumps(fixture).encode(), stderr=b"")
|
||||||
|
|
||||||
|
monkeypatch.setattr(package, "_run_gcloud", fake_gcloud)
|
||||||
|
gcloud_binary = tmp_path / "gcloud"
|
||||||
|
|
||||||
|
assert package._registry_candidate_digest(gcloud_binary, candidate_tag) == f"sha256:{digest}"
|
||||||
|
assert observed == {
|
||||||
|
"binary": gcloud_binary,
|
||||||
|
"arguments": [
|
||||||
|
"artifacts",
|
||||||
|
"docker",
|
||||||
|
"tags",
|
||||||
|
"list",
|
||||||
|
package.IMAGE_REPOSITORY,
|
||||||
|
"--project",
|
||||||
|
package.PROJECT,
|
||||||
|
f"--filter=tag={expected_tag}",
|
||||||
|
"--format=json(tag,version)",
|
||||||
|
"--limit=2",
|
||||||
|
],
|
||||||
|
"timeout": 60,
|
||||||
|
}
|
||||||
|
assert "--location" not in observed["arguments"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_candidate_lookup_rejects_mixed_exact_and_unexpected_rows(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
candidate_tag = "candidate-" + "a" * 64
|
||||||
|
digest = "b" * 64
|
||||||
|
expected_tag = f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/{candidate_tag}"
|
||||||
|
rows = [
|
||||||
|
{
|
||||||
|
"tag": expected_tag,
|
||||||
|
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{digest}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": f"{package.ARTIFACT_PACKAGE_RESOURCE}/tags/candidate-{'c' * 64}",
|
||||||
|
"version": f"{package.ARTIFACT_PACKAGE_RESOURCE}/versions/sha256:{'d' * 64}",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
monkeypatch.setattr(
|
||||||
|
package,
|
||||||
|
"_run_gcloud",
|
||||||
|
lambda *_args, **_kwargs: subprocess.CompletedProcess(
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
stdout=json.dumps(rows).encode(),
|
||||||
|
stderr=b"",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="unexpected tag"):
|
||||||
|
package._registry_candidate_digest(tmp_path / "gcloud", candidate_tag)
|
||||||
|
|
||||||
|
|
||||||
|
def test_publish_outcome_loader_handles_short_reads(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
_gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
|
expected = package.build_initial_publish_outcome(image_input)
|
||||||
|
original_read = os.read
|
||||||
|
monkeypatch.setattr(package.os, "read", lambda descriptor, size: original_read(descriptor, min(size, 7)))
|
||||||
|
|
||||||
|
assert package.load_publish_outcome(outcome_journal, image_input=image_input) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: Path) -> None:
|
def test_image_input_binds_exact_runtime_identity_and_staging_target(tmp_path: Path) -> None:
|
||||||
value = image_input_fixture(tmp_path)
|
value = image_input_fixture(tmp_path)
|
||||||
package.validate_image_input(value)
|
package.validate_image_input(value)
|
||||||
|
|
@ -1065,6 +1243,7 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
output = tmp_path / "build-push-receipt.json"
|
output = tmp_path / "build-push-receipt.json"
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
fake_run, state, calls = mocked_build_push_docker(image_input)
|
fake_run, state, calls = mocked_build_push_docker(image_input)
|
||||||
reviewed: list[tuple[Path, Path, dict[str, object]]] = []
|
reviewed: list[tuple[Path, Path, dict[str, object]]] = []
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
|
@ -1074,6 +1253,7 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
|
||||||
lambda root, observed_context, value: reviewed.append((root, observed_context, value)),
|
lambda root, observed_context, value: reviewed.append((root, observed_context, value)),
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
mock_fresh_registry_reconcile(monkeypatch)
|
||||||
monkeypatch.setenv("DOCKER_TLS_VERIFY", "1")
|
monkeypatch.setenv("DOCKER_TLS_VERIFY", "1")
|
||||||
monkeypatch.setenv("BUILDKIT_PROGRESS", "plain")
|
monkeypatch.setenv("BUILDKIT_PROGRESS", "plain")
|
||||||
monkeypatch.setenv("BUILDX_BUILDER", "unreviewed")
|
monkeypatch.setenv("BUILDX_BUILDER", "unreviewed")
|
||||||
|
|
@ -1084,13 +1264,24 @@ def test_build_push_derives_receipt_and_retains_all_daemon_references(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert json.loads(output.read_text(encoding="utf-8")) == receipt
|
assert json.loads(output.read_text(encoding="utf-8")) == receipt
|
||||||
assert receipt["registry"]["manifest_digest"] == f"sha256:{IMAGE_DIGEST}"
|
assert receipt["registry"]["manifest_digest"] == f"sha256:{IMAGE_DIGEST}"
|
||||||
assert receipt["registry"]["manifest_digest"] != "sha256:" + "9" * 64
|
assert receipt["registry"]["manifest_digest"] != "sha256:" + "9" * 64
|
||||||
|
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
|
||||||
|
assert outcome["candidate"]["tag"] == f"candidate-{image_input['input_sha256']}"
|
||||||
|
assert outcome["registry"] == {
|
||||||
|
"repository": package.IMAGE_REPOSITORY,
|
||||||
|
"preexisting": False,
|
||||||
|
"push_attempted": True,
|
||||||
|
"action": "fresh_push",
|
||||||
|
"observed_digest": f"sha256:{IMAGE_DIGEST}",
|
||||||
|
}
|
||||||
assert reviewed == [(repo_root, context, image_input), (repo_root, context, image_input)]
|
assert reviewed == [(repo_root, context, image_input), (repo_root, context, image_input)]
|
||||||
assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1}
|
assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1}
|
||||||
build = next(operation for operation, _kwargs in calls if operation and operation[0] == "build")
|
build = next(operation for operation, _kwargs in calls if operation and operation[0] == "build")
|
||||||
|
|
@ -1134,7 +1325,9 @@ def test_build_push_requires_explicit_execution_flag_before_validation(tmp_path:
|
||||||
docker_binary=tmp_path / "docker",
|
docker_binary=tmp_path / "docker",
|
||||||
docker_host="unix:///tmp/docker.sock",
|
docker_host="unix:///tmp/docker.sock",
|
||||||
docker_config=tmp_path,
|
docker_config=tmp_path,
|
||||||
|
gcloud_binary=tmp_path / "gcloud",
|
||||||
output_receipt=tmp_path / "receipt.json",
|
output_receipt=tmp_path / "receipt.json",
|
||||||
|
outcome_journal=tmp_path / "outcome.json",
|
||||||
execute_staging_push=False,
|
execute_staging_push=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1156,6 +1349,7 @@ def test_build_push_rejects_writable_docker_config_before_docker(
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
output = tmp_path / "writable-config-build-push-receipt.json"
|
output = tmp_path / "writable-config-build-push-receipt.json"
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
docker_config.chmod(0o777)
|
docker_config.chmod(0o777)
|
||||||
docker_calls: list[list[str]] = []
|
docker_calls: list[list[str]] = []
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
|
@ -1173,7 +1367,9 @@ def test_build_push_rejects_writable_docker_config_before_docker(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1282,6 +1478,7 @@ def test_build_push_rejects_replaced_docker_config_before_mutation(
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
output = tmp_path / "replaced-config-build-push-receipt.json"
|
output = tmp_path / "replaced-config-build-push-receipt.json"
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
retained_config = tmp_path / "retained-docker-config"
|
retained_config = tmp_path / "retained-docker-config"
|
||||||
docker_calls: list[list[str]] = []
|
docker_calls: list[list[str]] = []
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
|
@ -1306,7 +1503,9 @@ def test_build_push_rejects_replaced_docker_config_before_mutation(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1335,6 +1534,7 @@ def test_build_push_rejects_receipt_parent_symlink_alias_before_docker(
|
||||||
repo_root = tmp_path / "repo"
|
repo_root = tmp_path / "repo"
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
docker_calls: list[list[str]] = []
|
docker_calls: list[list[str]] = []
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
|
|
@ -1351,7 +1551,9 @@ def test_build_push_rejects_receipt_parent_symlink_alias_before_docker(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1380,11 +1582,13 @@ def test_build_push_rechecks_receipt_context_separation_at_publication(
|
||||||
retained_parent = tmp_path / "retained-receipts"
|
retained_parent = tmp_path / "retained-receipts"
|
||||||
output = output_parent / "receipt.json"
|
output = output_parent / "receipt.json"
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
fake_run, state, calls = mocked_build_push_docker(image_input)
|
fake_run, state, calls = mocked_build_push_docker(image_input)
|
||||||
original_publish = package.publish_build_push_receipt
|
original_publish = package.publish_build_push_receipt
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
mock_fresh_registry_reconcile(monkeypatch)
|
||||||
|
|
||||||
def alias_parent_before_publish(*args: object, **kwargs: object) -> None:
|
def alias_parent_before_publish(*args: object, **kwargs: object) -> None:
|
||||||
output_parent.rename(retained_parent)
|
output_parent.rename(retained_parent)
|
||||||
|
|
@ -1400,7 +1604,9 @@ def test_build_push_rechecks_receipt_context_separation_at_publication(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1437,6 +1643,7 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
|
||||||
repo_root = tmp_path / "repo"
|
repo_root = tmp_path / "repo"
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
fake_run, state, _calls = mocked_build_push_docker(
|
fake_run, state, _calls = mocked_build_push_docker(
|
||||||
image_input,
|
image_input,
|
||||||
post_push_references=references,
|
post_push_references=references,
|
||||||
|
|
@ -1444,6 +1651,12 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
monkeypatch.setattr(package, "_registry_candidate_digest", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
package,
|
||||||
|
"_wait_registry_candidate_digest",
|
||||||
|
lambda *_args: f"sha256:{IMAGE_DIGEST}" if references else None,
|
||||||
|
)
|
||||||
|
|
||||||
output = tmp_path / "failed-build-push-receipt.json"
|
output = tmp_path / "failed-build-push-receipt.json"
|
||||||
with pytest.raises(package.PackageError, match="build/push failed"):
|
with pytest.raises(package.PackageError, match="build/push failed"):
|
||||||
|
|
@ -1453,7 +1666,9 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1462,7 +1677,7 @@ def test_build_push_fails_closed_without_one_exact_post_push_digest(
|
||||||
assert state["candidate"] is True
|
assert state["candidate"] is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("failure", ["build", "tag", "push"])
|
@pytest.mark.parametrize("failure", ["build", "tag"])
|
||||||
def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
|
@ -1480,6 +1695,7 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
||||||
repo_root = tmp_path / "repo"
|
repo_root = tmp_path / "repo"
|
||||||
repo_root.mkdir()
|
repo_root.mkdir()
|
||||||
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
fake_run, state, calls = mocked_build_push_docker(
|
fake_run, state, calls = mocked_build_push_docker(
|
||||||
image_input,
|
image_input,
|
||||||
fail_after_side_effect=failure,
|
fail_after_side_effect=failure,
|
||||||
|
|
@ -1487,6 +1703,7 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
||||||
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
mock_fresh_registry_reconcile(monkeypatch)
|
||||||
output = tmp_path / "failed-side-effect-receipt.json"
|
output = tmp_path / "failed-side-effect-receipt.json"
|
||||||
|
|
||||||
with pytest.raises(package.PackageError, match="build/push failed") as raised:
|
with pytest.raises(package.PackageError, match="build/push failed") as raised:
|
||||||
|
|
@ -1496,7 +1713,9 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
||||||
docker_binary=docker_binary,
|
docker_binary=docker_binary,
|
||||||
docker_host=docker_host,
|
docker_host=docker_host,
|
||||||
docker_config=docker_config,
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
output_receipt=output,
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
execute_staging_push=True,
|
execute_staging_push=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1504,14 +1723,188 @@ def test_build_push_retains_daemon_references_when_cli_fails_after_side_effect(
|
||||||
assert not output.exists()
|
assert not output.exists()
|
||||||
assert state == {
|
assert state == {
|
||||||
"build": True,
|
"build": True,
|
||||||
"candidate": failure in {"tag", "push"},
|
"candidate": failure == "tag",
|
||||||
"digests": failure == "push",
|
"digests": False,
|
||||||
"pushes": 1 if failure == "push" else 0,
|
"pushes": 0,
|
||||||
}
|
}
|
||||||
removals = [operation for operation, _kwargs in calls if operation[:2] == ["image", "rm"]]
|
removals = [operation for operation, _kwargs in calls if operation[:2] == ["image", "rm"]]
|
||||||
assert removals == []
|
assert removals == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_push_reuses_verified_candidate_despite_nondeterministic_rootfs(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
build_args = {
|
||||||
|
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"INPUT_SHA256": image_input["input_sha256"],
|
||||||
|
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
|
||||||
|
}
|
||||||
|
context = tmp_path / "context"
|
||||||
|
context.mkdir()
|
||||||
|
repo_root = tmp_path / "repo"
|
||||||
|
repo_root.mkdir()
|
||||||
|
output = tmp_path / "reused-build-push-receipt.json"
|
||||||
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
|
fake_run, state, calls = mocked_build_push_docker(
|
||||||
|
image_input,
|
||||||
|
remote_config_digest=REMOTE_CONFIG_DIGEST,
|
||||||
|
remote_rootfs_layers=["sha256:" + "9" * 64],
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
package,
|
||||||
|
"_registry_candidate_digest",
|
||||||
|
lambda *_args: f"sha256:{IMAGE_DIGEST}",
|
||||||
|
)
|
||||||
|
|
||||||
|
receipt = package.build_and_push_staging_image(
|
||||||
|
repo_root,
|
||||||
|
context,
|
||||||
|
docker_binary=docker_binary,
|
||||||
|
docker_host=docker_host,
|
||||||
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
|
execute_staging_push=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert receipt["local_image"]["config_digest"] == REMOTE_CONFIG_DIGEST
|
||||||
|
assert state == {"build": True, "candidate": False, "digests": True, "pushes": 0}
|
||||||
|
assert not any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
|
||||||
|
assert any(operation and operation[0] == "pull" for operation, _kwargs in calls)
|
||||||
|
assert any(operation and operation[0] == "run" for operation, _kwargs in calls)
|
||||||
|
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
|
||||||
|
assert outcome["candidate"] == {
|
||||||
|
"tag": f"candidate-{image_input['input_sha256']}",
|
||||||
|
"reference": f"{package.IMAGE_REPOSITORY}:candidate-{image_input['input_sha256']}",
|
||||||
|
"local_build_config_digest": LOCAL_CONFIG_DIGEST,
|
||||||
|
}
|
||||||
|
assert outcome["registry"] == {
|
||||||
|
"repository": package.IMAGE_REPOSITORY,
|
||||||
|
"preexisting": True,
|
||||||
|
"push_attempted": False,
|
||||||
|
"action": "reused",
|
||||||
|
"observed_digest": f"sha256:{IMAGE_DIGEST}",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_push_rejects_preexisting_failed_runtime_verification_without_claiming_reuse(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
build_args = {
|
||||||
|
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"INPUT_SHA256": image_input["input_sha256"],
|
||||||
|
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
|
||||||
|
}
|
||||||
|
context = tmp_path / "context"
|
||||||
|
context.mkdir()
|
||||||
|
repo_root = tmp_path / "repo"
|
||||||
|
repo_root.mkdir()
|
||||||
|
output = tmp_path / "mismatched-build-push-receipt.json"
|
||||||
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
|
fake_run, state, calls = mocked_build_push_docker(
|
||||||
|
image_input,
|
||||||
|
remote_config_digest=REMOTE_CONFIG_DIGEST,
|
||||||
|
remote_verification_valid=False,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
package,
|
||||||
|
"_registry_candidate_digest",
|
||||||
|
lambda *_args: f"sha256:{IMAGE_DIGEST}",
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="build/push failed"):
|
||||||
|
package.build_and_push_staging_image(
|
||||||
|
repo_root,
|
||||||
|
context,
|
||||||
|
docker_binary=docker_binary,
|
||||||
|
docker_host=docker_host,
|
||||||
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
|
execute_staging_push=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not output.exists()
|
||||||
|
assert state == {"build": True, "candidate": False, "digests": True, "pushes": 0}
|
||||||
|
assert not any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
|
||||||
|
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
|
||||||
|
assert outcome["status"] == "fail"
|
||||||
|
assert outcome["registry"]["preexisting"] is True
|
||||||
|
assert outcome["registry"]["observed_digest"] == f"sha256:{IMAGE_DIGEST}"
|
||||||
|
assert outcome["registry"]["action"] == "observed"
|
||||||
|
assert all(event["status"] != "reused" for event in outcome["events"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_push_validation_failure_retains_mutation_evidence(
|
||||||
|
tmp_path: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
image_input = image_input_fixture(tmp_path)
|
||||||
|
build_args = {
|
||||||
|
"ARTIFACT_SHA256": image_input["runtime"]["artifact_sha256"],
|
||||||
|
"IDENTITY_SHA256": image_input["identity"]["bundle_sha256"],
|
||||||
|
"INPUT_SHA256": image_input["input_sha256"],
|
||||||
|
"TELEO_REVISION": image_input["runtime"]["teleo_git_head"],
|
||||||
|
}
|
||||||
|
context = tmp_path / "context"
|
||||||
|
context.mkdir()
|
||||||
|
repo_root = tmp_path / "repo"
|
||||||
|
repo_root.mkdir()
|
||||||
|
output = tmp_path / "post-push-failure-receipt.json"
|
||||||
|
docker_binary, docker_host, docker_config = docker_authority(tmp_path)
|
||||||
|
gcloud_binary, outcome_journal = publish_outcome_authority(tmp_path, image_input)
|
||||||
|
fake_run, state, calls = mocked_build_push_docker(
|
||||||
|
image_input,
|
||||||
|
remote_verification_valid=False,
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(package, "validate_prepared_image_context", lambda _context: (image_input, build_args))
|
||||||
|
monkeypatch.setattr(package, "validate_reviewed_context_source", lambda *_args: None)
|
||||||
|
monkeypatch.setattr(package.subprocess, "run", fake_run)
|
||||||
|
mock_fresh_registry_reconcile(monkeypatch)
|
||||||
|
|
||||||
|
with pytest.raises(package.PackageError, match="build/push failed"):
|
||||||
|
package.build_and_push_staging_image(
|
||||||
|
repo_root,
|
||||||
|
context,
|
||||||
|
docker_binary=docker_binary,
|
||||||
|
docker_host=docker_host,
|
||||||
|
docker_config=docker_config,
|
||||||
|
gcloud_binary=gcloud_binary,
|
||||||
|
output_receipt=output,
|
||||||
|
outcome_journal=outcome_journal,
|
||||||
|
execute_staging_push=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert not output.exists()
|
||||||
|
assert state == {"build": True, "candidate": True, "digests": True, "pushes": 1}
|
||||||
|
assert any(operation[:2] == ["image", "push"] for operation, _kwargs in calls)
|
||||||
|
outcome = package.load_publish_outcome(outcome_journal, image_input=image_input)
|
||||||
|
assert outcome["status"] == "fail"
|
||||||
|
assert outcome["registry"] == {
|
||||||
|
"repository": package.IMAGE_REPOSITORY,
|
||||||
|
"preexisting": False,
|
||||||
|
"push_attempted": True,
|
||||||
|
"action": "indeterminate",
|
||||||
|
"observed_digest": f"sha256:{IMAGE_DIGEST}",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_build_push_receipt_publication_is_atomic_repeatable_and_no_clobber(tmp_path: Path) -> None:
|
def test_build_push_receipt_publication_is_atomic_repeatable_and_no_clobber(tmp_path: Path) -> None:
|
||||||
image_input = image_input_fixture(tmp_path)
|
image_input = image_input_fixture(tmp_path)
|
||||||
receipt = build_push_receipt_fixture(image_input)
|
receipt = build_push_receipt_fixture(image_input)
|
||||||
|
|
|
||||||
180
tests/test_gcp_leoclean_nosend_release_workflow.py
Normal file
180
tests/test_gcp_leoclean_nosend_release_workflow.py
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from ops import gcp_leoclean_nosend_package as package
|
||||||
|
|
||||||
|
WORKFLOW = Path(".github/workflows/gcp-leoclean-nosend-release.yml")
|
||||||
|
|
||||||
|
|
||||||
|
def load_workflow() -> dict[str, object]:
|
||||||
|
return yaml.load(WORKFLOW.read_text(encoding="utf-8"), Loader=yaml.BaseLoader)
|
||||||
|
|
||||||
|
|
||||||
|
def workflow_step(name: str) -> dict[str, object]:
|
||||||
|
workflow = load_workflow()
|
||||||
|
jobs = workflow["jobs"]
|
||||||
|
assert isinstance(jobs, dict)
|
||||||
|
publish = jobs["publish"]
|
||||||
|
assert isinstance(publish, dict)
|
||||||
|
steps = publish["steps"]
|
||||||
|
assert isinstance(steps, list)
|
||||||
|
return next(step for step in steps if step.get("name") == name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_is_manual_main_only_and_serialized() -> None:
|
||||||
|
workflow = load_workflow()
|
||||||
|
|
||||||
|
assert set(workflow["on"]) == {"workflow_dispatch"}
|
||||||
|
assert workflow["permissions"] == {"contents": "read", "id-token": "write"}
|
||||||
|
assert workflow["concurrency"] == {
|
||||||
|
"group": "gcp-leoclean-nosend-release-main",
|
||||||
|
"cancel-in-progress": "false",
|
||||||
|
}
|
||||||
|
guard = workflow_step("Require manual main dispatch")["run"]
|
||||||
|
assert 'test "${GITHUB_EVENT_NAME}" = "workflow_dispatch"' in guard
|
||||||
|
assert 'test "${GITHUB_REF}" = "refs/heads/main"' in guard
|
||||||
|
assert 'test "${GITHUB_REF_TYPE}" = "branch"' in guard
|
||||||
|
assert 'test "${GITHUB_REPOSITORY}" = "living-ip/teleo-infrastructure"' in guard
|
||||||
|
assert '"living-ip/teleo-infrastructure/.github/workflows/gcp-leoclean-nosend-release.yml@refs/heads/main"' in guard
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_checks_out_exact_clean_revision_without_persisted_token() -> None:
|
||||||
|
step = workflow_step("Check out exact dispatched revision")
|
||||||
|
|
||||||
|
assert step["uses"] == "actions/checkout@11d5960a326750d5838078e36cf38b85af677262"
|
||||||
|
assert step["with"] == {
|
||||||
|
"fetch-depth": "0",
|
||||||
|
"persist-credentials": "false",
|
||||||
|
"ref": "${{ github.sha }}",
|
||||||
|
}
|
||||||
|
revision_check = workflow_step("Verify exact clean main revision")["run"]
|
||||||
|
assert 'test "$(git rev-parse --verify HEAD)" = "${GITHUB_SHA}"' in revision_check
|
||||||
|
assert 'test "$(git rev-parse --verify refs/remotes/origin/main^{commit})" = "${GITHUB_SHA}"' in revision_check
|
||||||
|
assert "git status --porcelain=v1 --untracked-files=all" in revision_check
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_smokes_before_requesting_cloud_identity() -> None:
|
||||||
|
workflow = WORKFLOW.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
smoke = workflow.index("Smoke exact no-send OCI surface")
|
||||||
|
prepare = workflow.index("Prepare exact candidate context")
|
||||||
|
initialize = workflow.index("Initialize pre-mutation publish outcome")
|
||||||
|
auth = workflow.index("Authenticate to Google Cloud through WIF")
|
||||||
|
push = workflow.index("Build and push one immutable candidate")
|
||||||
|
finalize = workflow.index("Finalize and validate release v3 bundle")
|
||||||
|
cleanup = workflow.index("Remove ephemeral Docker authentication")
|
||||||
|
complete = workflow.index("Finalize publish outcome")
|
||||||
|
upload = workflow.index("Upload exact available build and release evidence")
|
||||||
|
assert smoke < prepare < initialize < auth < push < finalize < cleanup < complete < upload
|
||||||
|
assert "scripts/run_leoclean_nosend_runtime_canary.sh" in workflow
|
||||||
|
assert "scripts/run_gcp_leoclean_nosend_oci_smoke.py" in workflow
|
||||||
|
assert 'assert smoke["input_sha256"] == image_input["input_sha256"]' in workflow
|
||||||
|
assert 'assert smoke["cleanup"]["status"] == "pass"' in workflow
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_uses_exact_wif_artifact_builder_without_repository_secrets() -> None:
|
||||||
|
workflow = load_workflow()
|
||||||
|
text = WORKFLOW.read_text(encoding="utf-8")
|
||||||
|
env = workflow["env"]
|
||||||
|
|
||||||
|
assert env["PROJECT_ID"] == "teleo-501523"
|
||||||
|
assert env["REGION"] == "europe-west6"
|
||||||
|
assert env["ARTIFACT_REPOSITORY"] == "teleo"
|
||||||
|
assert env["IMAGE_REPOSITORY"] == package.IMAGE_REPOSITORY
|
||||||
|
assert env["WORKLOAD_IDENTITY_PROVIDER"] == (
|
||||||
|
"projects/785938879453/locations/global/workloadIdentityPools/github-actions/providers/living-ip-github"
|
||||||
|
)
|
||||||
|
assert env["ARTIFACT_SERVICE_ACCOUNT"] == "sa-artifact-builder@teleo-501523.iam.gserviceaccount.com"
|
||||||
|
assert "${{ secrets." not in text
|
||||||
|
assert "password" not in text.casefold()
|
||||||
|
|
||||||
|
auth = workflow_step("Authenticate to Google Cloud through WIF")
|
||||||
|
assert auth["uses"] == "google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed"
|
||||||
|
assert auth["with"] == {
|
||||||
|
"workload_identity_provider": "${{ env.WORKLOAD_IDENTITY_PROVIDER }}",
|
||||||
|
"service_account": "${{ env.ARTIFACT_SERVICE_ACCOUNT }}",
|
||||||
|
"create_credentials_file": "true",
|
||||||
|
"export_environment_variables": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_keeps_runner_temp_out_of_job_environment() -> None:
|
||||||
|
workflow = load_workflow()
|
||||||
|
jobs = workflow["jobs"]
|
||||||
|
assert isinstance(jobs, dict)
|
||||||
|
publish = jobs["publish"]
|
||||||
|
assert isinstance(publish, dict)
|
||||||
|
root_env = workflow["env"]
|
||||||
|
job_env = publish.get("env", {})
|
||||||
|
|
||||||
|
assert isinstance(root_env, dict)
|
||||||
|
assert isinstance(job_env, dict)
|
||||||
|
assert all("runner.temp" not in value for value in [*root_env.values(), *job_env.values()])
|
||||||
|
initialize = workflow_step("Initialize private release paths")["run"]
|
||||||
|
assert 'release_root="${RUNNER_TEMP}/leoclean-nosend-release"' in initialize
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_requires_immutable_repository_and_package_owned_candidate_push() -> None:
|
||||||
|
workflow = WORKFLOW.read_text(encoding="utf-8")
|
||||||
|
immutable = workflow_step("Require immutable fixed Artifact Registry repository")["run"]
|
||||||
|
push = workflow_step("Build and push one immutable candidate")["run"]
|
||||||
|
|
||||||
|
assert "gcloud artifacts repositories describe" in immutable
|
||||||
|
assert 'repository["dockerConfig"]["immutableTags"] is True' in immutable
|
||||||
|
assert "ops/gcp_leoclean_nosend_package.py build-push" in push
|
||||||
|
assert "--execute-staging-push" in push
|
||||||
|
assert "--docker-binary /usr/bin/docker" in push
|
||||||
|
assert "--docker-host unix:///var/run/docker.sock" in push
|
||||||
|
assert '--gcloud-binary "$(command -v gcloud)"' in push
|
||||||
|
assert '--outcome-journal "${PUBLISH_OUTCOME}"' in push
|
||||||
|
assert "docker push" not in workflow
|
||||||
|
assert "docker tag" not in workflow
|
||||||
|
assert "candidate-" not in workflow
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_always_finalizes_journal_and_uploads_only_allowlisted_evidence() -> None:
|
||||||
|
finalize = workflow_step("Finalize and validate release v3 bundle")["run"]
|
||||||
|
cleanup = workflow_step("Remove ephemeral Docker authentication")
|
||||||
|
complete = workflow_step("Finalize publish outcome")
|
||||||
|
upload = workflow_step("Upload exact available build and release evidence")
|
||||||
|
paths = upload["with"]["path"].splitlines()
|
||||||
|
|
||||||
|
assert "ops/gcp_leoclean_nosend_package.py finalize" in finalize
|
||||||
|
assert "ops/gcp_leoclean_nosend_package.py validate" in finalize
|
||||||
|
assert 'assert release["schema"] == "livingip.leocleanNoSendRelease.v3"' in finalize
|
||||||
|
assert cleanup["if"] == "always()"
|
||||||
|
assert complete["if"] == "always() && steps.init_outcome.outcome == 'success'"
|
||||||
|
assert "ops/gcp_leoclean_nosend_package.py complete-outcome" in complete["run"]
|
||||||
|
assert '--build-outcome "${{ steps.build_push.outcome }}"' in complete["run"]
|
||||||
|
assert '--finalize-outcome "${{ steps.finalize.outcome }}"' in complete["run"]
|
||||||
|
assert '--cleanup-outcome "${{ steps.cleanup.outcome }}"' in complete["run"]
|
||||||
|
assert upload["if"] == "always()"
|
||||||
|
assert paths == [
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/evidence/runtime-receipt.json",
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/evidence/oci-smoke.json",
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/evidence/publish-outcome.json",
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/receipts/build-push-receipt.json",
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/final/release-v3/release.json",
|
||||||
|
"${{ runner.temp }}/leoclean-nosend-release/final/release-v3/leoclean-gcp-nosend.service",
|
||||||
|
]
|
||||||
|
assert upload["with"]["if-no-files-found"] == "warn"
|
||||||
|
assert upload["with"]["retention-days"] == "30"
|
||||||
|
assert "*" not in upload["with"]["path"]
|
||||||
|
assert "google-github-actions" not in upload["with"]["path"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_workflow_has_no_deployment_or_runtime_mutation_surface() -> None:
|
||||||
|
workflow = WORKFLOW.read_text(encoding="utf-8").casefold()
|
||||||
|
forbidden = (
|
||||||
|
"--execute-restart",
|
||||||
|
"install_gcp_leoclean_nosend_release.py",
|
||||||
|
"gcloud compute",
|
||||||
|
"gcloud sql",
|
||||||
|
"gcloud secrets",
|
||||||
|
"kubectl",
|
||||||
|
"systemctl",
|
||||||
|
"terraform apply",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert all(marker not in workflow for marker in forbidden)
|
||||||
Loading…
Reference in a new issue