Solution

Reference solution for the outer-delivery-loop lab.

Reference solution

A working solution consists of:

  1. A real GitHub pull request on OE_ENV_REPO whose head branch is sandcastle/hello-world-pico-<timestamp> and whose base is the repo’s default branch. The head branch carries the hand-off’s xr.yaml at envs/dev/xr.yaml.
  2. A merge commit for that PR on the default branch, whose SHA is captured in work/outer-delivery-loop/state/merge-sha.txt.
  3. Either:
    • a Flux GitRepository + Kustomization pair (oe-env-hello-world-pico in namespace flux-system) reconciling envs/dev/ on the merged branch, or
    • a fresh clone of the merged branch that a kubectl apply of envs/dev/xr.yaml succeeded from (equivalent to one Flux iteration).
  4. The Kubernetes Job composed by Crossplane from the reconciled XR, verified by the unchanged labs/hello-pico-on-kubernetes/downloads/verify.sh printing its own verify: OK line.
  5. A clean verify.sh exit (exit code 0) with one of the two documented final lines.

Merged XR shape (byte-equivalent to the hand-off XR)

apiVersion: oe.academy/v1alpha1
kind: XHelloWorldPico
metadata:
  name: hello
spec:
  value: "Hello, Pico!"

Commands

Run from the root of your local clone of the academy repository:

export OE_ENV_REPO=your-handle/oe-env-hello-world-pico
bash labs/outer-delivery-loop/downloads/verify.sh

Expected verify.sh output (final line)

Stages 1–2 only (no cluster):

verify: OK — outer loop stages 1–2 complete on <repo> PR #<n> (cluster stages skipped)

Full stages 1–4 (with a Crossplane cluster):

verify: OK — outer loop stages 1–4 complete on <repo> PR #<n> (merge sha <sha>)

Why this satisfies the objectives

  • Environment repo as source of truth. The merged commit on the default branch — not the Sandcastle, not the hand-off — is the cluster’s desired state. If you delete the local work/outer-delivery-loop/ directory the loop is still complete; the truth lives on GitHub.
  • Guardrails outside the Sandcastle boundary. Review, checks, and branch protection attach to the PR on GitHub. Policy (OPA, Kyverno, Gatekeeper) attaches at cluster admission before Crossplane sees the XR. Neither surface is inside the Sandcastle.
  • GitOps delivery is a pull, not a push from the Sandcastle. In Flux mode, Flux polls the env repo and applies the XR on every change. In kubectl mode, the same effect happens once, from a fresh clone of the merged branch. Both preserve the rule that the merged branch is the only outer-loop input.
  • Crossplane closes the loop. The composed Job runs and prints Hello, Pico!, verified by the existing Kubernetes lab’s verify.sh. The outer loop hands the XR off once and Crossplane keeps reconciling long after this lab exits.

Verification

You can verify the solution matches this reference by:

  1. Running bash labs/outer-delivery-loop/downloads/verify.sh and checking the final verify: OK line and exit status 0.

  2. Opening the PR URL from work/outer-delivery-loop/state/pr-url.txt on GitHub and confirming it is merged and the merge commit matches work/outer-delivery-loop/state/merge-sha.txt.

  3. Fetching the merged XR from GitHub and diffing it against the hand-off XR on the boundary fields:

    gh api "repos/$OE_ENV_REPO/contents/envs/dev/xr.yaml?ref=$(cat work/outer-delivery-loop/state/base.txt)" \
      --jq .content | base64 -d \
      | grep -E '^(apiVersion|kind|  name|  value):' > /tmp/remote-xr.txt
    grep -E '^(apiVersion|kind|  name|  value):' \
         work/handoff-sandcastle-to-kubernetes/build/xr.yaml > /tmp/local-xr.txt
    diff -u /tmp/local-xr.txt /tmp/remote-xr.txt

    An empty diff (exit code 0) is the pass.

  4. On the cluster, running the Kubernetes lab’s verify.sh and confirming its own verify: OK — job=... value="Hello, Pico!" line.

Variations

  • Different greeting: change the agent’s greeting per the Sandcastle lab’s Variations, rerun the hand-off, then rerun this lab’s verify.sh (EXPECTED_VALUE='Hello, World!' ...). Each run gets a fresh timestamped feature branch, so previous PRs stay in the history.
  • Realistic branch protection: on a shared env repo, remove --admin from MERGE_FLAGS and rely on required reviewers and required checks (for example a re-run of labs/handoff-sandcastle-to-kubernetes/downloads/verify.sh in GitHub Actions) to gate the merge.
  • Flux vs kubectl: force MODE=kubectl bash .../gitops-sync.sh to see the single-shot form of the reconciler, then reinstall Flux and re-run in Flux mode to see the continuous-poll form. The cluster-side outcome is identical.

Not covered by this lab (intentional)

  • Bootstrapping Flux or a cluster. Flux installation and the Crossplane cluster itself are operator-controlled prerequisites, documented under Objectives. The lab uses them when present but does not manage their lifecycle.
  • Multi-artifact / multi-environment promotion. This lab promotes a single XR to a single env directory (envs/dev/xr.yaml). Multi-env promotion (dev → staging → prod) and multi-artifact hand-offs are natural follow-on Part 3 lessons.
  • Full policy demo. The lab makes the policy guardrail slot concrete (cluster admission before Crossplane sees the XR) but does not ship an OPA/Kyverno/Gatekeeper policy. Adding one is a reasonable extension exercise.