From a329726709eea74402846efc39fdeaa1b7d53a73 Mon Sep 17 00:00:00 2001 From: Alex Creasy Date: Thu, 30 Jul 2026 11:24:24 +0100 Subject: [PATCH] Adds GitOps interactive lesson 3 - rollback a breaking change Signed-off-by: Alex Creasy --- lessons/03-lesson-3/README.md | 502 ++++++++++++++++++ .../lesson-manifests/combined-pool.yaml | 26 + .../03-lesson-3/lesson-manifests/kafka.yaml | 29 + .../lesson-manifests/kustomization.yaml | 7 + .../lesson-manifests/namespace.yaml | 6 + .../03-lesson-3/lesson-manifests/topic.yaml | 13 + lessons/03-lesson-3/prep.sh | 209 ++++++++ 7 files changed, 792 insertions(+) create mode 100644 lessons/03-lesson-3/README.md create mode 100644 lessons/03-lesson-3/lesson-manifests/combined-pool.yaml create mode 100644 lessons/03-lesson-3/lesson-manifests/kafka.yaml create mode 100644 lessons/03-lesson-3/lesson-manifests/kustomization.yaml create mode 100644 lessons/03-lesson-3/lesson-manifests/namespace.yaml create mode 100644 lessons/03-lesson-3/lesson-manifests/topic.yaml create mode 100755 lessons/03-lesson-3/prep.sh diff --git a/lessons/03-lesson-3/README.md b/lessons/03-lesson-3/README.md new file mode 100644 index 0000000..6b6b7f3 --- /dev/null +++ b/lessons/03-lesson-3/README.md @@ -0,0 +1,502 @@ +# Lesson 3: Rolling Back a Bad Change + +**Series:** GitOps with StreamsHub — 3-part series +**Prerequisites:** Complete [Getting Started](../00-setup/README.md) first +**Time:** ~25 minutes (plus ~5 minutes for prep) + +--- + +## What you will learn + +By the end of this lesson you will understand: + +- Why **ArgoCD Synced does not mean the change worked** — sync status and health status are separate concerns +- How Strimzi enforces Kafka's own constraints (partition count can only increase, never decrease) +- How to **roll back a breaking change** using `git revert` — a new commit that undoes the previous one, preserving the full audit trail +- Why `git revert` is the correct GitOps approach, and why rewriting history with `git reset --hard` is an anti-pattern + +You will do this by deploying a configuration change that ArgoCD accepts but Strimzi rejects, observing the failure, and performing a GitOps rollback that heals the cluster automatically. + +--- + +## Prerequisites + +You need the following tools installed on your machine: + +| Tool | Purpose | Install | +|------|---------|---------| +| **Docker** | Container runtime | [docker.com](https://www.docker.com/products/docker-desktop/) | +| **KinD** (v0.20+) | Local Kubernetes clusters | `brew install kind` or [kind.sigs.k8s.io](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) | +| **kubectl** | Kubernetes CLI | `brew install kubectl` or [kubernetes.io](https://kubernetes.io/docs/tasks/tools/) | +| **git** | Version control | `brew install git` or [git-scm.com](https://git-scm.com/) | +| **curl** | HTTP requests | Usually pre-installed | + +**System requirements:** ~4 GB of available memory for Docker (single Kafka cluster). + +You also need the shared tutorial infrastructure running. If you haven't done this yet: + +```bash +cd ../00-setup && ./setup.sh +``` + +--- + +## Background: Two things to understand before we start + +### Sync status vs health status + +ArgoCD tracks two independent statuses for every Application: + +- **Sync Status** (`Synced` / `OutOfSync`): Did ArgoCD successfully apply the manifests from Git to Kubernetes? This reflects whether the Kubernetes API accepted the YAML. A successful `kubectl apply` produces `Synced`. + +- **Health Status** (`Healthy` / `Degraded` / `Progressing`): Is the resource actually working? This is assessed by reading resource conditions — for a KafkaTopic, that means checking the `Ready` condition set by the Strimzi Topic Operator. + +These can disagree. A manifest can be `Synced` (Kubernetes stored the desired state) while the application is `Degraded` (the platform rejected the semantic change underneath). This lesson demonstrates that divergence concretely. + +### GitOps rollback + +Because Git is the source of truth, rolling back is not a special operation — it is just another Git commit. You revert the bad commit, push, and the reconciler heals the cluster automatically. No `kubectl rollout undo`, no deployment pipeline, no emergency access needed. + +--- + +## Setup + +Run the prep script from this directory: + +```bash +./prep.sh +``` + +This takes approximately 5 minutes. It: + +1. Removes any state from previous lessons +2. Configures the Strimzi operator to watch the `kafka-tutorial` namespace +3. Seeds the Gitea repository with a working Kafka cluster and topic +4. Waits for both the Kafka cluster and the topic to become ready + +When it finishes it prints the Gitea and ArgoCD credentials. + +You can re-run `./prep.sh` at any time to reset back to the lesson starting state. If you have already cloned the repo at `/tmp/gitops-lesson-3`, delete it first (`rm -rf /tmp/gitops-lesson-3`) because the reset rewrites the Gitea commit history. + +--- + +## Part 1: Explore the starting state + +### Clone the repository + +```bash +git clone http://tutorial-user:tutorial-password@localhost:3001/tutorial-user/streamshub-gitops.git /tmp/gitops-lesson-3 +cd /tmp/gitops-lesson-3 +``` + +### Check the running resources + +```bash +kubectl get kafka -n kafka-tutorial +``` + +Expected output: + +``` +NAME DESIRED KAFKA REPLICAS DESIRED ZK REPLICAS READY METADATA STATE WARNINGS +my-cluster 1 True KRaft +``` + +```bash +kubectl get kafkatopic -n kafka-tutorial +``` + +Expected output: + +``` +NAME CLUSTER PARTITIONS REPLICATION FACTOR READY +my-first-topic my-cluster 3 1 True +``` + +Both the Kafka cluster and the topic are `READY: True`. This is the known-good baseline you will return to at the end of the lesson. + +### Check the Git history + +```bash +git log --oneline +``` + +The most recent commit will be the lesson-3 reset: + +``` +abc1234 Reset to lesson-3 starting state +... +``` + +Watch this history grow as the lesson progresses. + +### Review the manifest + +```bash +cat manifests/topic.yaml +``` + +Confirm `partitions: 3`. That number is about to change. + +--- + +## Part 2: Deploy the breaking change + +**The scenario:** a team member decides to reduce `my-first-topic` from 3 partitions to 1 to save resources. It seems harmless. Kafka, however, does not allow partition counts to decrease — topics can gain partitions but never lose them. + +Open `manifests/topic.yaml` in your editor and change `partitions: 3` to `partitions: 1`: + +```yaml +spec: + partitions: 1 + replicas: 1 +``` + +Commit and push: + +```bash +git add manifests/topic.yaml +git commit -m "Reduce my-first-topic to 1 partition" +git push +``` + +Watch ArgoCD detect the change: + +```bash +kubectl get application kafka-tutorial -n argocd -w +``` + +The `SYNC STATUS` column will move from `Synced` → `OutOfSync` → `Synced`. Press `Ctrl+C` once it settles. + +ArgoCD reports `Synced`. From ArgoCD's perspective, its job is done — it applied the manifest to Kubernetes. But look more carefully at what that means. + +--- + +## Part 3: Detect the problem + +Check the topic status: + +```bash +kubectl get kafkatopic -n kafka-tutorial +``` + +Expected output: + +``` +NAME CLUSTER PARTITIONS REPLICATION FACTOR READY +my-first-topic my-cluster 1 1 False +``` + +`READY: False`. The manifest was applied, but the change is not working. Note that `PARTITIONS` now shows `1` — that is what the KafkaTopic CR specifies. But Strimzi could not actually change the partition count inside Kafka. + +Find out why: + +```bash +kubectl describe kafkatopic my-first-topic -n kafka-tutorial +``` + +Look at the `Conditions` section near the bottom: + +``` +Conditions: + Type: Ready + Status: False + Reason: NotSupported + Message: Decreasing partitions not supported +``` + +Strimzi's Topic Operator attempted to reconfigure the topic and Kafka itself rejected the operation. The actual topic inside the broker still has 3 partitions — nothing in Kafka changed. But the KafkaTopic CR is in an error state because what Git says and what the broker has are now out of agreement. + +Now check the ArgoCD application status again: + +```bash +kubectl get application kafka-tutorial -n argocd +``` + +``` +NAME SYNC STATUS HEALTH STATUS +kafka-tutorial Synced Progressing +``` + +This is the key distinction from the background section made real: + +- `SYNC STATUS: Synced` — ArgoCD applied the manifest to Kubernetes. It did its job. +- `HEALTH STATUS: Progressing` — ArgoCD is waiting for the resource to become ready, but it never will. Strimzi has enforced Kafka's constraint and the topic is stuck in a `READY: False` state. + +Sync tells you whether ArgoCD succeeded. Health tells you whether the underlying system is in a good state. **Both matter, and they can disagree.** A deployment is not complete until it is both Synced and Healthy — not Synced and Progressing indefinitely. + +This situation — synced but not healthy — is exactly when GitOps rollback is needed. + +--- + +## Part 4: Rollback with git revert + +First, look at the commit history to understand what to revert: + +```bash +git log --oneline +``` + +``` +def5678 Reduce my-first-topic to 1 partition +abc1234 Reset to lesson-3 starting state +``` + +The breaking change is the most recent commit — `HEAD`. You need to undo it. + +Perform the rollback: + +```bash +git revert HEAD --no-edit +``` + +`git revert HEAD` creates a new commit that applies the exact inverse of what `HEAD` introduced. If `HEAD` changed `partitions: 3` to `partitions: 1`, the revert changes it back to `partitions: 3`. The `--no-edit` flag accepts the default commit message without opening an editor. + +Look at what just happened: + +```bash +git log --oneline +``` + +``` +789abcd Revert "Reduce my-first-topic to 1 partition" +def5678 Reduce my-first-topic to 1 partition +abc1234 Reset to lesson-3 starting state +``` + +Three commits. The bad commit is still there — it was not erased. The revert commit sits on top of it, documenting exactly what was undone and when. This is the audit trail. + +Push the revert: + +```bash +git push +``` + +That is the rollback. No `kubectl` command was run. No deployment pipeline was triggered. You changed Git; the system will reconcile to match. + +### Why not git reset --hard? + +You could also roll back by rewriting history: + +```bash +git reset --hard HEAD~1 +git push --force # DO NOT DO THIS +``` + +This removes the bad commit as if it never existed. In a GitOps context this is an anti-pattern: + +- Anyone who pulled the bad commit now has a divergent history and will see errors on their next `git pull` +- The audit trail is gone — no record of what changed or when it was fixed +- Force-pushing to a shared branch is a coordination hazard in teams +- In regulated environments, history rewriting may violate compliance requirements + +`git revert` is additive and safe. The full sequence of events stays in history for anyone to inspect. + +--- + +## Part 5: Watch the recovery + +Watch ArgoCD pick up the revert: + +```bash +kubectl get application kafka-tutorial -n argocd -w +``` + +The application will move through `OutOfSync` → `Synced` as ArgoCD detects the new commit and applies the restored manifest. Press `Ctrl+C` once it settles. + +Verify the topic is healthy again: + +```bash +kubectl get kafkatopic my-first-topic -n kafka-tutorial +``` + +Expected output: + +``` +NAME CLUSTER PARTITIONS REPLICATION FACTOR READY +my-first-topic my-cluster 3 1 True +``` + +`READY: True`. `PARTITIONS: 3`. The topic is back to its working state. + +Check the application health: + +```bash +kubectl get application kafka-tutorial -n argocd +``` + +``` +NAME SYNC STATUS HEALTH STATUS +kafka-tutorial Synced Healthy +``` + +Both `Synced` and `Healthy`. The system has fully recovered. + +Review the full audit trail: + +```bash +git log --oneline +``` + +``` +789abcd Revert "Reduce my-first-topic to 1 partition" +def5678 Reduce my-first-topic to 1 partition +abc1234 Reset to lesson-3 starting state +``` + +The complete sequence is preserved: what was changed, when, and when it was reverted. + +--- + +## How it worked + +``` +git push (the revert commit) + └─▶ Gitea receives the new commit + +ArgoCD polls Gitea + └─▶ Detects HEAD has moved (new revert commit) + └─▶ Renders the kustomize manifests — topic.yaml now has partitions: 3 + └─▶ Compares live cluster state to desired state + └─▶ Applies the update — KafkaTopic CR updated back to partitions: 3 + +Strimzi Topic Operator + └─▶ Sees KafkaTopic CR updated to partitions: 3 + └─▶ Checks the actual Kafka topic — it already has 3 partitions (never changed) + └─▶ No Kafka operation needed; marks KafkaTopic Ready: True + +ArgoCD health check + └─▶ Sees KafkaTopic Ready condition is True + └─▶ Reports application Health: Healthy +``` + +Note that the actual Kafka topic inside the broker never had its partition count changed. Strimzi protected the broker from the invalid operation the entire time. What `git revert` did was bring Git and the KafkaTopic CR back in sync with the broker's actual state. + +--- + +## Optional: View the ArgoCD dashboard + +In a separate terminal, start the port-forward: + +```bash +kubectl port-forward svc/argocd-server -n argocd 8080:443 +``` + +Open [https://localhost:8080](https://localhost:8080) (accept the self-signed certificate warning). + +Retrieve the admin password: + +```bash +kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d; echo +``` + +Log in with username `admin`. + +The `kafka-tutorial` tile shows two distinct indicators: a sync badge and a health badge. After deploying the breaking change, you will see `Synced` alongside `Degraded`. After the revert, both badges turn green — `Synced` and `Healthy`. + +Click the Application to open the resource tree. Click on `my-first-topic` to see the KafkaTopic details, including the error condition from Strimzi. This view makes the sync/health distinction visible at a glance. + +--- + +## Optional: Force an immediate ArgoCD sync + +To trigger ArgoCD without waiting up to 3 minutes for its next poll: + +```bash +kubectl annotate application kafka-tutorial -n argocd \ + argocd.argoproj.io/refresh=normal --overwrite +``` + +--- + +## What you've learned + +- ArgoCD `Synced` means the Kubernetes API accepted the manifest — it does not mean the change was successful at the platform level +- A resource stuck in `Progressing` (never reaching `Healthy`) is a signal to investigate even when sync succeeds +- Strimzi enforces Kafka's own constraints; some invalid changes deploy silently through Kubernetes but are rejected by the operator +- `git revert` is the correct GitOps rollback tool: it creates a new commit, preserves history, and is safe for shared branches +- `git reset --hard` with force-push destroys the audit trail and causes problems for anyone who pulled the broken commit +- Rollback in GitOps requires no special tooling — change Git, and the reconciler heals the cluster automatically + +--- + +## The complete picture + +| Lesson | What you learned | +|--------|-----------------| +| **Lesson 1** | The GitOps core loop: edit Git, ArgoCD deploys it | +| **Lesson 2** | Kustomize overlays for multi-environment management; promotion is a Git change | +| **Lesson 3** | Rollback is a Git change too; sync and health are separate — check both | + +--- + +## Cleanup + +When you are done with all lessons, delete the cluster: + +```bash +../00-setup/teardown.sh +``` + +Clean up the cloned repo: + +```bash +rm -rf /tmp/gitops-lesson-3 +``` + +--- + +## Troubleshooting + +**Infrastructure is not running** +If `./prep.sh` reports that the cluster or Strimzi is not found, run the setup script first: `../00-setup/setup.sh`. + +**prep.sh fails waiting for KafkaTopic to be ready** +The Topic Operator only starts after the Kafka cluster is ready. If the Kafka wait succeeded but the KafkaTopic wait timed out, check the Entity Operator: + +```bash +kubectl get pods -n kafka-tutorial +kubectl logs -n kafka-tutorial -l strimzi.io/component-type=entity-operator +``` + +Re-running `./prep.sh` will reset and retry. + +**ArgoCD shows Synced but HEALTH is Progressing after the revert** +This is transient — ArgoCD's health check polls on a short interval. Wait 10–15 seconds and re-check. If it persists, check the topic status directly: + +```bash +kubectl describe kafkatopic my-first-topic -n kafka-tutorial +``` + +**git revert opens an editor and I don't know how to exit** +If you ran `git revert HEAD` without `--no-edit`, your default editor opens. In `vi`: press `Esc`, type `:wq`, press Enter. In `nano`: press `Ctrl+X`, then `Y`, then Enter. Alternatively, abort with `Ctrl+C` and rerun with `--no-edit`: + +```bash +git revert HEAD --no-edit +``` + +**The topic still shows PARTITIONS: 1 after the revert and ArgoCD sync** +Confirm the revert commit was pushed and ArgoCD has synced it: + +```bash +git log --oneline # confirm the revert commit is the latest +git status # confirm nothing is unpushed ("Your branch is up to date") +kubectl get application kafka-tutorial -n argocd # confirm SYNC STATUS is Synced +``` + +If the revert was not pushed, run `git push`. To trigger an immediate ArgoCD sync: + +```bash +kubectl annotate application kafka-tutorial -n argocd \ + argocd.argoproj.io/refresh=normal --overwrite +``` + +**Strimzi is not managing the kafka-tutorial namespace** +After Lesson 2, Strimzi was watching `kafka-staging,kafka-production`. Verify it was reconfigured by prep.sh: + +```bash +kubectl get deployment strimzi-cluster-operator -n strimzi-operator \ + -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="STRIMZI_NAMESPACE")].value}'; echo +``` + +It should show `kafka-tutorial`. If not, re-run `./prep.sh`. diff --git a/lessons/03-lesson-3/lesson-manifests/combined-pool.yaml b/lessons/03-lesson-3/lesson-manifests/combined-pool.yaml new file mode 100644 index 0000000..ce1ca8f --- /dev/null +++ b/lessons/03-lesson-3/lesson-manifests/combined-pool.yaml @@ -0,0 +1,26 @@ +apiVersion: kafka.strimzi.io/v1 +kind: KafkaNodePool +metadata: + name: combined + namespace: kafka-tutorial + labels: + strimzi.io/cluster: my-cluster +spec: + replicas: 1 + roles: + - controller + - broker + storage: + type: jbod + volumes: + - id: 0 + type: persistent-claim + size: 2Gi + deleteClaim: true + resources: + requests: + memory: 512Mi + cpu: 250m + limits: + memory: 1Gi + cpu: 500m diff --git a/lessons/03-lesson-3/lesson-manifests/kafka.yaml b/lessons/03-lesson-3/lesson-manifests/kafka.yaml new file mode 100644 index 0000000..9e110ec --- /dev/null +++ b/lessons/03-lesson-3/lesson-manifests/kafka.yaml @@ -0,0 +1,29 @@ +apiVersion: kafka.strimzi.io/v1 +kind: Kafka +metadata: + name: my-cluster + namespace: kafka-tutorial +spec: + kafka: + version: 4.2.0 + metadataVersion: "4.2-IV0" + listeners: + - name: plain + port: 9092 + type: internal + tls: false + config: + offsets.topic.replication.factor: 1 + transaction.state.log.replication.factor: 1 + transaction.state.log.min.isr: 1 + default.replication.factor: 1 + min.insync.replicas: 1 + entityOperator: + topicOperator: + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 250m + memory: 384Mi diff --git a/lessons/03-lesson-3/lesson-manifests/kustomization.yaml b/lessons/03-lesson-3/lesson-manifests/kustomization.yaml new file mode 100644 index 0000000..b9d288f --- /dev/null +++ b/lessons/03-lesson-3/lesson-manifests/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - namespace.yaml + - combined-pool.yaml + - kafka.yaml + - topic.yaml diff --git a/lessons/03-lesson-3/lesson-manifests/namespace.yaml b/lessons/03-lesson-3/lesson-manifests/namespace.yaml new file mode 100644 index 0000000..a6dde5e --- /dev/null +++ b/lessons/03-lesson-3/lesson-manifests/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kafka-tutorial + labels: + name: kafka-tutorial diff --git a/lessons/03-lesson-3/lesson-manifests/topic.yaml b/lessons/03-lesson-3/lesson-manifests/topic.yaml new file mode 100644 index 0000000..c95c6fa --- /dev/null +++ b/lessons/03-lesson-3/lesson-manifests/topic.yaml @@ -0,0 +1,13 @@ +apiVersion: kafka.strimzi.io/v1 +kind: KafkaTopic +metadata: + name: my-first-topic + namespace: kafka-tutorial + labels: + strimzi.io/cluster: my-cluster +spec: + partitions: 3 + replicas: 1 + config: + retention.ms: "86400000" + segment.bytes: "1073741824" diff --git a/lessons/03-lesson-3/prep.sh b/lessons/03-lesson-3/prep.sh new file mode 100755 index 0000000..79a049c --- /dev/null +++ b/lessons/03-lesson-3/prep.sh @@ -0,0 +1,209 @@ +#!/usr/bin/env bash +set -euo pipefail + +CLUSTER_NAME="gitops-tutorial" +GITEA_USER="tutorial-user" +GITEA_PASSWORD="tutorial-password" +GITEA_REPO="streamshub-gitops" +GITEA_HOST_PORT=3001 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +info() { echo -e "\033[1;34m[INFO]\033[0m $*"; } +warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; } +error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; } +b64decode() { echo "$1" | base64 -d 2>/dev/null || echo "$1" | base64 -D 2>/dev/null; } + +cleanup() { + if [[ -n "${WORK_DIR:-}" ]]; then + rm -rf "${WORK_DIR}" + fi +} + +# ─── Step 1: Validate infrastructure ────────────────────────────────────────── + +info "Validating tutorial infrastructure..." + +if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then + error "KinD cluster '${CLUSTER_NAME}' is not running." + error "Please run the setup script first: ../00-setup/setup.sh" + exit 1 +fi + +if ! kubectl get deployment strimzi-cluster-operator -n strimzi-operator &>/dev/null; then + error "Strimzi operator not found in namespace 'strimzi-operator'." + error "Please run the setup script first: ../00-setup/setup.sh" + exit 1 +fi + +if ! curl -sf "http://localhost:${GITEA_HOST_PORT}/api/v1/version" >/dev/null 2>&1; then + error "Gitea is not reachable on localhost:${GITEA_HOST_PORT}." + error "Please run the setup script first: ../00-setup/setup.sh" + exit 1 +fi + +info "Infrastructure checks passed." + +# ─── Step 2: Remove ArgoCD Applications ─────────────────────────────────────── + +info "Removing ArgoCD applications..." + +kubectl delete application kafka-tutorial kafka-staging kafka-production \ + -n argocd --ignore-not-found 2>/dev/null || true + +# ─── Step 3: Reconfigure Strimzi to watch kafka-tutorial ────────────────────── +# +# This must happen BEFORE deleting lesson-2 namespaces. While Strimzi watches +# kafka-staging/kafka-production it will re-add finalizers to any resources we +# try to clear, causing namespace deletion to hang. + +info "Configuring Strimzi operator for kafka-tutorial namespace..." + +kubectl create namespace kafka-tutorial 2>/dev/null || true + +for rb_name in strimzi-cluster-operator strimzi-cluster-operator-entity-operator-delegation strimzi-cluster-operator-watched; do + ROLE_REF=$(kubectl get rolebinding "${rb_name}" -n strimzi-operator -o jsonpath='{.roleRef.name}' 2>/dev/null || echo "") + if [[ -n "${ROLE_REF}" ]]; then + kubectl create rolebinding "${rb_name}" \ + --namespace kafka-tutorial \ + --clusterrole="${ROLE_REF}" \ + --serviceaccount=strimzi-operator:strimzi-cluster-operator 2>/dev/null || true + fi +done + +kubectl set env deployment/strimzi-cluster-operator -n strimzi-operator \ + STRIMZI_NAMESPACE='kafka-tutorial' + +info "Waiting for Strimzi operator to restart..." +kubectl rollout status deployment/strimzi-cluster-operator -n strimzi-operator --timeout=120s + +# ─── Step 4: Clean up lesson-2 namespaces ───────────────────────────────────── +# +# Now that Strimzi only watches kafka-tutorial, it will not re-add finalizers to +# resources in kafka-staging or kafka-production. + +info "Cleaning up previous lesson state..." + +for ns in kafka-staging kafka-production; do + for cr_type in kafka kafkanodepool kafkatopic; do + kubectl get "${cr_type}" -n "${ns}" -o name 2>/dev/null | \ + xargs -r -I{} kubectl patch {} -n "${ns}" \ + --type=merge -p '{"metadata":{"finalizers":null}}' 2>/dev/null || true + done +done + +kubectl delete namespace kafka-staging kafka-production --ignore-not-found + +info "Previous lesson state cleaned up." + +# ─── Step 5: Seed the Gitea repository with lesson-3 starting state ────────── + +info "Resetting Gitea repository to lesson-3 starting state..." + +WORK_DIR=$(mktemp -d) +trap cleanup EXIT + +git clone "http://${GITEA_USER}:${GITEA_PASSWORD}@localhost:${GITEA_HOST_PORT}/${GITEA_USER}/${GITEA_REPO}.git" "${WORK_DIR}/repo" 2>/dev/null + +rm -rf "${WORK_DIR}/repo/manifests" +mkdir -p "${WORK_DIR}/repo/manifests" +cp "${SCRIPT_DIR}/lesson-manifests/"* "${WORK_DIR}/repo/manifests/" + +pushd "${WORK_DIR}/repo" >/dev/null +git add . +if git diff --cached --quiet; then + info "Gitea repo is already in lesson-3 starting state, skipping commit." +else + git -c user.name="Tutorial Setup" -c user.email="setup@tutorial.local" commit -m "Reset to lesson-3 starting state" + git push + info "Lesson-3 starting state pushed to Gitea." +fi +TARGET_REVISION=$(git rev-parse HEAD) +popd >/dev/null + +# ─── Step 6: Create ArgoCD Application kafka-tutorial ───────────────────────── + +info "Creating ArgoCD application kafka-tutorial..." + +kubectl apply -f - </dev/null 2>&1 || true + +SYNC_STATUS="Unknown" +for i in $(seq 1 24); do + CURRENT_REVISION=$(kubectl get application kafka-tutorial -n argocd -o jsonpath='{.status.sync.revision}' 2>/dev/null || echo "") + SYNC_STATUS=$(kubectl get application kafka-tutorial -n argocd -o jsonpath='{.status.sync.status}' 2>/dev/null || echo "Unknown") + if [[ "${CURRENT_REVISION}" == "${TARGET_REVISION}" && "${SYNC_STATUS}" == "Synced" ]]; then + break + fi + sleep 5 +done + +if [[ "${SYNC_STATUS}" != "Synced" ]]; then + warn "ArgoCD has not synced yet (status: ${SYNC_STATUS})." + warn "Check status with: kubectl get application kafka-tutorial -n argocd" +else + info "ArgoCD application is synced." +fi + +# ─── Step 8: Wait for Kafka cluster to be ready ──────────────────────────────── + +info "Waiting for Kafka cluster to be ready (this may take a few minutes)..." +kubectl wait kafka/my-cluster --for=condition=Ready -n kafka-tutorial --timeout=600s 2>/dev/null || \ + warn "Kafka cluster is not yet ready. Check with: kubectl get kafka -n kafka-tutorial" + +# ─── Step 9: Wait for KafkaTopic to be ready ────────────────────────────────── + +info "Waiting for KafkaTopic to be ready..." +kubectl wait kafkatopic/my-first-topic --for=condition=Ready -n kafka-tutorial --timeout=120s 2>/dev/null || \ + warn "KafkaTopic is not yet ready. Check with: kubectl get kafkatopic -n kafka-tutorial" + +# ─── Step 10: Print starting instructions ───────────────────────────────────── + +ARGOCD_PASSWORD=$(b64decode "$(kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}')") + +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" +info "Lesson 3 is ready. Open README.md and follow the lesson steps." +echo "" +echo " Clone the repo to follow along:" +echo " git clone http://${GITEA_USER}:${GITEA_PASSWORD}@localhost:${GITEA_HOST_PORT}/${GITEA_USER}/${GITEA_REPO}.git /tmp/gitops-lesson-3" +echo "" +echo " Gitea (your Git server): http://localhost:${GITEA_HOST_PORT}" +echo " Username: ${GITEA_USER} Password: ${GITEA_PASSWORD}" +echo "" +echo " ArgoCD Dashboard (open in a separate terminal):" +echo " kubectl port-forward svc/argocd-server -n argocd 8080:443" +echo " URL: https://localhost:8080" +echo " Username: admin" +echo " Password: ${ARGOCD_PASSWORD}" +echo "" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"