From 0c1ebd3d8f750783b95f78fdc37db5321853d44c Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Fri, 13 Mar 2026 13:04:53 +0000 Subject: [PATCH 1/5] Add Strimzi, Console and Registry installation * Add the Strimzi, StreamsHub Console and Apicurio Registry Operator install to the base kustomization * Add the single-node Kafka, Streamshub Console instance and Apircurio Registry instance installation to the stack kustomization * Add install and uninstall scripts * Add version update script * Add Readme with intall and usage instructions Signed-off-by: Thomas Cooper --- .github/workflows/validate.yaml | 58 +++ README.md | 186 ++++++++- .../kustomization.yaml | 47 +++ .../apicurio-registry-operator/namespace.yaml | 4 + base/kustomization.yaml | 7 + .../kustomization.yaml | 27 ++ .../namespace.yaml | 4 + .../strimzi-operator/clusterrolebindings.yaml | 35 ++ base/strimzi-operator/kustomization.yaml | 46 ++ base/strimzi-operator/namespace.yaml | 4 + install.sh | 136 ++++++ overlays/.gitkeep | 0 stack/apicurio-registry/kustomization.yaml | 12 + stack/apicurio-registry/registry.yaml | 6 + stack/kafka/kustomization.yaml | 27 ++ stack/kafka/namespace.yaml | 4 + stack/kustomization.yaml | 7 + stack/streamshub-console/console.yaml | 10 + stack/streamshub-console/kustomization.yaml | 12 + uninstall.sh | 283 +++++++++++++ update-version.sh | 394 ++++++++++++++++++ 21 files changed, 1307 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/validate.yaml create mode 100644 base/apicurio-registry-operator/kustomization.yaml create mode 100644 base/apicurio-registry-operator/namespace.yaml create mode 100644 base/kustomization.yaml create mode 100644 base/streamshub-console-operator/kustomization.yaml create mode 100644 base/streamshub-console-operator/namespace.yaml create mode 100644 base/strimzi-operator/clusterrolebindings.yaml create mode 100644 base/strimzi-operator/kustomization.yaml create mode 100644 base/strimzi-operator/namespace.yaml create mode 100755 install.sh create mode 100644 overlays/.gitkeep create mode 100644 stack/apicurio-registry/kustomization.yaml create mode 100644 stack/apicurio-registry/registry.yaml create mode 100644 stack/kafka/kustomization.yaml create mode 100644 stack/kafka/namespace.yaml create mode 100644 stack/kustomization.yaml create mode 100644 stack/streamshub-console/console.yaml create mode 100644 stack/streamshub-console/kustomization.yaml create mode 100755 uninstall.sh create mode 100755 update-version.sh diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..54c5c08 --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,58 @@ +name: Validate + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + kustomize-build: + name: Validate Kustomize configs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up kubectl + uses: azure/setup-kubectl@v4 + + - name: Build base layer + run: kubectl kustomize base/ + + - name: Build stack layer + run: kubectl kustomize stack/ + + - name: Verify quick-start labels in base + run: | + kubectl kustomize base/ | grep -q 'app.kubernetes.io/part-of: streamshub-developer-quickstart' + + - name: Verify quick-start labels in stack + run: | + kubectl kustomize stack/ | grep -q 'app.kubernetes.io/part-of: streamshub-developer-quickstart' + + shellcheck: + name: Lint shell scripts + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@2.0.0 + with: + version: v0.11.0 + scandir: "." + additional_files: "install.sh uninstall.sh update-version.sh" + + yamllint: + name: Lint YAML files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install yamllint + run: | + sudo apt-get update + sudo apt-get install -y yamllint + + - name: Run yamllint + run: yamllint -d relaxed . diff --git a/README.md b/README.md index d23bc93..dd26201 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,184 @@ -# developer-quickstart -A kustomize-based quick start setup for deploying the StreamsHub event stack in a local ephemeral or remote development kubernetes cluster. +# StreamsHub Developer Quick-Start + +A Kustomize-based repository for deploying the StreamsHub event-streaming stack on a local or development Kubernetes cluster using only `kubectl`. + +> **Note:** This is a **development-only** configuration. Resource limits, security settings and storage configurations are **not** suitable for production use. + +## What Gets Deployed + +| Component | Namespace | Description | +|-----------|-----------|-------------| +| Strimzi Kafka Operator | `strimzi` | Manages Kafka clusters via CRDs | +| Kafka cluster (`dev-cluster`) | `kafka` | Single-node Kafka for development | +| Apicurio Registry Operator | `apicurio-registry` | Manages schema registry instances | +| Apicurio Registry instance | `apicurio-registry` | In-memory schema registry | +| StreamsHub Console Operator | `streamshub-console` | Manages console instances | +| StreamsHub Console instance | `streamshub-console` | Web UI for Kafka management | + +## Prerequisites + +- `kubectl` v1.27 or later (for Kustomize v5.0 `labels` transformer support) +- A running Kubernetes cluster (minikube, KIND, etc.) +- An Ingress controller for StreamsHub Console access (e.g. `minikube addons enable ingress`) + +## Quick-Start Install + +Deploy the entire stack with a single command: + +```shell +curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | bash +``` + +This script installs operators, waits for them to become ready, then deploys the operands. + +### Configuration + +The install script accepts the following environment variables: + +| Variable | Default | Description | +|----------|---------|-------------| +| `REPO` | `streamshub/developer-quickstart` | GitHub repository path | +| `REF` | `main` | Git ref, branch, or tag | +| `TIMEOUT` | `120s` | `kubectl wait` timeout | + +Example with a pinned version: + +```shell +curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | REF=v1.0.0 bash +``` + +## Manual Install + +If you prefer to control each step, the stack is installed in two phases: + +### Phase 1 — Operators and CRDs + +```shell +kubectl apply -k 'https://github.com/streamshub/developer-quickstart//base?ref=main' +``` + +Wait for the operators to become ready: + +```shell +kubectl wait --for=condition=Available deployment/strimzi-cluster-operator -n strimzi --timeout=120s +kubectl wait --for=condition=Available deployment/apicurio-registry-operator -n apicurio-registry --timeout=120s +kubectl wait --for=condition=Available deployment/streamshub-console-operator -n streamshub-console --timeout=120s +``` + +### Phase 2 — Operands + +```shell +kubectl apply -k 'https://github.com/streamshub/developer-quickstart//stack?ref=main' +``` + +## Accessing the Console + +### Minikube + +When using minikube, (if you didn't eanble it when you created the minikube cluster) enable the ingress addon and run `minikube tunnel`: + +```bash +minikube addons enable ingress +minikube tunnel +``` + +Then use port-forwarding to access the console: + +```bash +kubectl port-forward -n streamshub-console svc/streamshub-console-console-service 8080:80 +``` + +Open [http://localhost:8080](http://localhost:8080) in your browser. + +## Teardown + +### Using the Uninstall Script + +The uninstall script handles safe teardown with shared-cluster safety checks: + +```shell +curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/uninstall.sh | bash +``` + +The script: +1. Deletes operand custom resources and waits for finalizers to complete +2. Checks each operator group for non-quick-start CRs on the cluster +3. Fully removes operator groups with no shared CRDs +4. For shared operator groups, removes only the operator deployment (retaining CRDs) +5. Reports any retained groups and remaining resources + +### Manual Teardown + +**Phase 1 — Delete operands:** + +```shell +kubectl delete -k 'https://github.com/streamshub/developer-quickstart//stack?ref=main' +``` + +Wait for all custom resources to be fully removed before proceeding. + +**Phase 2 — Delete operators and CRDs:** + +> **Warning:** On shared clusters, deleting CRDs will cascade-delete ALL custom resources of that type cluster-wide. Check for non-quick-start resources first: +> ```shell +> kubectl get kafkas -A --selector='!app.kubernetes.io/part-of=streamshub-developer-quickstart' +> ``` + +```shell +kubectl delete -k 'https://github.com/streamshub/developer-quickstart//base?ref=main' +``` + +### Finding Quick-Start Resources + +All resources carry the label `app.kubernetes.io/part-of=streamshub-developer-quickstart`: + +```shell +kubectl get all -A -l app.kubernetes.io/part-of=streamshub-developer-quickstart +kubectl get crds,clusterroles,clusterrolebindings -l app.kubernetes.io/part-of=streamshub-developer-quickstart +``` + +## Updating Component Versions + +Use the `update-version.sh` script to update component versions: + +```shell +# List available versions +./update-version.sh --list strimzi + +# Preview changes +./update-version.sh --dry-run strimzi 0.52.0 + +# Check if a release exists +./update-version.sh --check apicurio-registry 3.2.0 + +# Update a component +./update-version.sh strimzi 0.52.0 +``` + +Supported components: `strimzi`, `apicurio-registry`, `streamshub-console` + +## Repository Structure + +``` +base/ # Phase 1: Operators & CRDs +├── kustomization.yaml # Composes all operator sub-components +├── strimzi-operator/ # Strimzi Kafka Operator +├── apicurio-registry-operator/ # Apicurio Registry Operator +└── streamshub-console-operator/ # StreamsHub Console Operator + +stack/ # Phase 2: Operands (Custom Resources) +├── kustomization.yaml # Composes all operand sub-components +├── kafka/ # Single-node Kafka cluster +├── apicurio-registry/ # In-memory registry instance +└── streamshub-console/ # Console instance + +overlays/ # Future: variant configurations +``` + +## Current Component Versions + +| Component | Version | +|-----------|---------| +| Strimzi | 0.51.0 | +| Apicurio Registry | 3.1.7 | +| StreamsHub Console | 0.11.0 | diff --git a/base/apicurio-registry-operator/kustomization.yaml b/base/apicurio-registry-operator/kustomization.yaml new file mode 100644 index 0000000..9cefa5f --- /dev/null +++ b/base/apicurio-registry-operator/kustomization.yaml @@ -0,0 +1,47 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: apicurio-registry + +resources: + - https://raw.githubusercontent.com/Apicurio/apicurio-registry/3.1.7/operator/install/install.yaml + - namespace.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false + +patches: + - target: + kind: ClusterRoleBinding + patch: |- + - op: replace + path: /subjects/0/namespace + value: apicurio-registry + # Strip version suffix from operator Deployment name + - target: + kind: Deployment + labelSelector: app.kubernetes.io/name=apicurio-registry-operator + patch: |- + - op: replace + path: /metadata/name + value: apicurio-registry-operator + # Set watched namespaces (replace OLM valueFrom with direct value) + - target: + kind: Deployment + name: apicurio-registry-operator + patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: apicurio-registry-operator + spec: + template: + spec: + containers: + - name: apicurio-registry-operator + env: + - name: APICURIO_OPERATOR_WATCHED_NAMESPACES + value: '*' + valueFrom: null diff --git a/base/apicurio-registry-operator/namespace.yaml b/base/apicurio-registry-operator/namespace.yaml new file mode 100644 index 0000000..102c2fc --- /dev/null +++ b/base/apicurio-registry-operator/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: apicurio-registry diff --git a/base/kustomization.yaml b/base/kustomization.yaml new file mode 100644 index 0000000..2651cf0 --- /dev/null +++ b/base/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - strimzi-operator + - apicurio-registry-operator + - streamshub-console-operator diff --git a/base/streamshub-console-operator/kustomization.yaml b/base/streamshub-console-operator/kustomization.yaml new file mode 100644 index 0000000..4112aaa --- /dev/null +++ b/base/streamshub-console-operator/kustomization.yaml @@ -0,0 +1,27 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: streamshub-console + +resources: + - https://github.com/streamshub/console/releases/download/0.11.0/streamshub-console-operator.yaml + - namespace.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false + +patches: + - target: + kind: ClusterRoleBinding + patch: |- + - op: replace + path: /subjects/0/namespace + value: streamshub-console + - target: + kind: RoleBinding + patch: |- + - op: replace + path: /subjects/0/namespace + value: streamshub-console diff --git a/base/streamshub-console-operator/namespace.yaml b/base/streamshub-console-operator/namespace.yaml new file mode 100644 index 0000000..7985e05 --- /dev/null +++ b/base/streamshub-console-operator/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: streamshub-console diff --git a/base/strimzi-operator/clusterrolebindings.yaml b/base/strimzi-operator/clusterrolebindings.yaml new file mode 100644 index 0000000..998b858 --- /dev/null +++ b/base/strimzi-operator/clusterrolebindings.yaml @@ -0,0 +1,35 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: strimzi-cluster-operator-watched +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: strimzi-cluster-operator-watched +subjects: + - kind: ServiceAccount + name: strimzi-cluster-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: strimzi-cluster-operator-namespaced +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: strimzi-cluster-operator-namespaced +subjects: + - kind: ServiceAccount + name: strimzi-cluster-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: strimzi-cluster-operator-entity-operator-delegation +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: strimzi-entity-operator +subjects: + - kind: ServiceAccount + name: strimzi-cluster-operator diff --git a/base/strimzi-operator/kustomization.yaml b/base/strimzi-operator/kustomization.yaml new file mode 100644 index 0000000..b98890b --- /dev/null +++ b/base/strimzi-operator/kustomization.yaml @@ -0,0 +1,46 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: strimzi + +resources: + - https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.51.0/strimzi-cluster-operator-0.51.0.yaml + - namespace.yaml + - clusterrolebindings.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false + +patches: + - target: + kind: RoleBinding + patch: |- + - op: replace + path: /subjects/0/namespace + value: strimzi + - target: + kind: ClusterRoleBinding + patch: |- + - op: replace + path: /subjects/0/namespace + value: strimzi + # Watch all namespaces for Kafka resources + - target: + kind: Deployment + name: strimzi-cluster-operator + patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: strimzi-cluster-operator + spec: + template: + spec: + containers: + - name: strimzi-cluster-operator + env: + - name: STRIMZI_NAMESPACE + value: '*' + valueFrom: null diff --git a/base/strimzi-operator/namespace.yaml b/base/strimzi-operator/namespace.yaml new file mode 100644 index 0000000..4d6fcc5 --- /dev/null +++ b/base/strimzi-operator/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: strimzi diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..b222022 --- /dev/null +++ b/install.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# +# Install the StreamsHub developer quick-start stack +# +# Deploys the full event-streaming stack: Strimzi Kafka Operator, Apicurio +# Registry and StreamsHub Console on a Kubernetes cluster. +# +# Usage: +# # Install from remote repository +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | bash +# +# # Pin to a specific ref or fork +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | REF=v1.0.0 bash +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | REPO=myuser/developer-quickstart bash +# +# Environment variables: +# REPO - GitHub repo path (default: streamshub/developer-quickstart) +# REF - Git ref/branch/tag (default: main) +# TIMEOUT - kubectl wait timeout (default: 120s) +# + +set -euo pipefail + +# Defaults (overridable via environment variables) +REPO="${REPO:-streamshub/developer-quickstart}" +REF="${REF:-main}" +TIMEOUT="${TIMEOUT:-120s}" + +# Color output helpers +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Build the kustomize URL for a given sub-path +kustomize_url() { + local path="$1" + echo "https://github.com/${REPO}//${path}?ref=${REF}" +} + +# Check prerequisites +check_prerequisites() { + if ! command -v kubectl &> /dev/null; then + error "kubectl is not installed or not in PATH" + error "Install kubectl: https://kubernetes.io/docs/tasks/tools/" + exit 1 + fi + info "kubectl found: $(kubectl version --client 2>/dev/null | head -1)" + + # StreamsHub Console requires an Ingress controller + if ! kubectl get ingressclass &> /dev/null || [ -z "$(kubectl get ingressclass -o name 2>/dev/null)" ]; then + warn "No IngressClass found in the cluster" + warn "StreamsHub Console requires an Ingress controller (e.g. 'minikube addons enable ingress')" + fi +} + +main() { + echo "" + info "Installing StreamsHub developer quick-start stack" + info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + echo "" + + # --- Prerequisites --- + check_prerequisites + echo "" + + # --- Step 1: Install operators (base layer) --- + local base_url + base_url=$(kustomize_url "base") + info "Step 1/5: Installing operators and CRDs..." + info "Applying: ${base_url}" + kubectl apply -k "${base_url}" + echo "" + + # --- Step 2: Wait for Strimzi operator --- + info "Step 2/5: Waiting for strimzi-cluster-operator to be ready (timeout: ${TIMEOUT})..." + kubectl wait --for=condition=Available deployment/strimzi-cluster-operator \ + -n strimzi --timeout="${TIMEOUT}" + info "Strimzi operator is ready" + echo "" + + # --- Step 3: Wait for Apicurio Registry operator --- + info "Step 3/5: Waiting for apicurio-registry-operator to be ready (timeout: ${TIMEOUT})..." + kubectl wait --for=condition=Available deployment/apicurio-registry-operator \ + -n apicurio-registry --timeout="${TIMEOUT}" + info "Apicurio Registry operator is ready" + echo "" + + # --- Step 4: Wait for StreamsHub Console operator --- + info "Step 4/5: Waiting for console-operator to be ready (timeout: ${TIMEOUT})..." + kubectl wait --for=condition=Available deployment/console-operator \ + -n streamshub-console --timeout="${TIMEOUT}" + info "StreamsHub Console operator is ready" + echo "" + + # --- Step 5: Install operands (stack layer) --- + local stack_url + stack_url=$(kustomize_url "stack") + info "Step 5/5: Installing operands (Kafka cluster, Registry instance, Console)..." + info "Applying: ${stack_url}" + kubectl apply -k "${stack_url}" + echo "" + + # --- Summary --- + info "Dev stack installation complete!" + echo "" + echo "Deployed components:" + echo " - Strimzi operator (namespace: strimzi)" + echo " - Kafka cluster 'dev-cluster' (namespace: kafka)" + echo " - Apicurio Registry operator (namespace: apicurio-registry)" + echo " - Apicurio Registry instance (namespace: apicurio-registry, storage: in-memory)" + echo " - StreamsHub Console operator (namespace: streamshub-console)" + echo " - StreamsHub Console instance (namespace: streamshub-console)" + echo "" + echo "Verify with:" + echo " kubectl get deployment -n strimzi strimzi-cluster-operator" + echo " kubectl get kafka -n kafka" + echo " kubectl get deployment -n apicurio-registry apicurio-registry-operator" + echo " kubectl get apicurioregistry3 -n apicurio-registry" + echo " kubectl get deployment -n streamshub-console console-operator" + echo " kubectl get console -n streamshub-console" +} + +main diff --git a/overlays/.gitkeep b/overlays/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/stack/apicurio-registry/kustomization.yaml b/stack/apicurio-registry/kustomization.yaml new file mode 100644 index 0000000..2fbae9f --- /dev/null +++ b/stack/apicurio-registry/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: apicurio-registry + +resources: + - registry.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false diff --git a/stack/apicurio-registry/registry.yaml b/stack/apicurio-registry/registry.yaml new file mode 100644 index 0000000..b78a598 --- /dev/null +++ b/stack/apicurio-registry/registry.yaml @@ -0,0 +1,6 @@ +apiVersion: registry.apicur.io/v1 +kind: ApicurioRegistry3 +metadata: + name: apicurio-registry +spec: + app: {} diff --git a/stack/kafka/kustomization.yaml b/stack/kafka/kustomization.yaml new file mode 100644 index 0000000..d451826 --- /dev/null +++ b/stack/kafka/kustomization.yaml @@ -0,0 +1,27 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kafka + +resources: + - https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/refs/tags/0.51.0/examples/kafka/kafka-single-node.yaml + - namespace.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false + +patches: + - target: + kind: Kafka + patch: |- + - op: replace + path: /metadata/name + value: dev-cluster + - target: + kind: KafkaNodePool + patch: |- + - op: replace + path: /metadata/labels/strimzi.io~1cluster + value: dev-cluster diff --git a/stack/kafka/namespace.yaml b/stack/kafka/namespace.yaml new file mode 100644 index 0000000..f92e7e8 --- /dev/null +++ b/stack/kafka/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kafka diff --git a/stack/kustomization.yaml b/stack/kustomization.yaml new file mode 100644 index 0000000..ebc1b71 --- /dev/null +++ b/stack/kustomization.yaml @@ -0,0 +1,7 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - kafka + - apicurio-registry + - streamshub-console diff --git a/stack/streamshub-console/console.yaml b/stack/streamshub-console/console.yaml new file mode 100644 index 0000000..eaa1f6c --- /dev/null +++ b/stack/streamshub-console/console.yaml @@ -0,0 +1,10 @@ +apiVersion: console.streamshub.github.com/v1alpha1 +kind: Console +metadata: + name: streamshub-console +spec: + hostname: console.streamshub.local + kafkaClusters: + - name: dev-cluster + namespace: kafka + listener: plain diff --git a/stack/streamshub-console/kustomization.yaml b/stack/streamshub-console/kustomization.yaml new file mode 100644 index 0000000..1afaec3 --- /dev/null +++ b/stack/streamshub-console/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: streamshub-console + +resources: + - console.yaml + +labels: + - pairs: + app.kubernetes.io/part-of: streamshub-developer-quickstart + includeSelectors: false diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..cf7426c --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,283 @@ +#!/usr/bin/env bash +# +# Uninstall the StreamsHub developer quick-start stack +# +# Safely tears down the event-streaming stack with per-operator-group +# shared-cluster safety checks to avoid deleting CRDs that are in use +# by other deployments. +# +# Usage: +# # Uninstall from remote repository +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/uninstall.sh | bash +# +# # Pin to a specific ref or fork +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/uninstall.sh | REF=v1.0.0 bash +# curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/uninstall.sh | REPO=myuser/developer-quickstart bash +# +# Environment variables: +# REPO - GitHub repo path (default: streamshub/developer-quickstart) +# REF - Git ref/branch/tag (default: main) +# TIMEOUT - kubectl wait/poll timeout (default: 120s) +# + +set -euo pipefail + +# Defaults (overridable via environment variables) +REPO="${REPO:-streamshub/developer-quickstart}" +REF="${REF:-main}" +TIMEOUT="${TIMEOUT:-120s}" + +QUICKSTART_LABEL="app.kubernetes.io/part-of=streamshub-developer-quickstart" + +# Color output helpers +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Build the kustomize URL for a given sub-path +kustomize_url() { + local path="$1" + echo "https://github.com/${REPO}//${path}?ref=${REF}" +} + +# Extract timeout value in seconds from TIMEOUT variable +timeout_seconds() { + local val="${TIMEOUT}" + # Strip trailing 's' if present + val="${val%s}" + echo "$val" +} + +# Wait for all custom resources of a given type to be fully removed +# Returns 0 if all removed, 1 if timeout +wait_for_cr_removal() { + local cr_type="$1" + local namespace="$2" + local max_wait + max_wait=$(timeout_seconds) + local elapsed=0 + + while [ $elapsed -lt "$max_wait" ]; do + local count + count=$(kubectl get "$cr_type" -n "$namespace" -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | wc -l | tr -d ' ') + if [ "$count" -eq 0 ]; then + return 0 + fi + sleep 5 + elapsed=$((elapsed + 5)) + done + return 1 +} + +# Check if any CRs of the given types exist WITHOUT the quick-start label. +# This detects shared-cluster usage. +# Arguments: cr_types (space-separated list of CR type names) +# Returns 0 if unlabeled CRs found (shared), 1 if none found (safe to delete) +has_unlabeled_crs() { + local cr_types="$1" + for cr_type in $cr_types; do + local count + count=$(kubectl get "$cr_type" -A --selector="!app.kubernetes.io/part-of" --no-headers 2>/dev/null | wc -l | tr -d ' ') + if [ "$count" -gt 0 ]; then + return 0 + fi + # Also check for resources with the label set to a different value + count=$(kubectl get "$cr_type" -A --no-headers 2>/dev/null | wc -l | tr -d ' ') + local labeled_count + labeled_count=$(kubectl get "$cr_type" -A -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | wc -l | tr -d ' ') + if [ "$count" -gt "$labeled_count" ]; then + return 0 + fi + done + return 1 +} + +# Strimzi CR types to check for shared usage +STRIMZI_CR_TYPES="kafkas kafkanodepools kafkatopics kafkausers kafkaconnects kafkamirrormakers kafkamirrormaker2s kafkabridges kafkaconnectors kafkarebalances" + +# Apicurio Registry CR types +APICURIO_CR_TYPES="apicurioregistry3s" + +# StreamsHub Console CR types +CONSOLE_CR_TYPES="consoles" + +main() { + echo "" + info "Uninstalling StreamsHub developer quick-start stack" + info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + echo "" + + # Track which operator groups have shared CRDs + local strimzi_shared=false + local apicurio_shared=false + local console_shared=false + + # --- Phase 1: Delete operands (stack layer) --- + local stack_url + stack_url=$(kustomize_url "stack") + info "Phase 1: Deleting operands..." + info "Deleting: ${stack_url}" + kubectl delete -k "${stack_url}" --ignore-not-found=true 2>/dev/null || true + echo "" + + # --- Wait for CRs to be fully removed --- + info "Waiting for custom resources to be removed (finalizers may take time)..." + + local removal_failed=false + + # Wait for Kafka resources + if kubectl get kafka -n kafka -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then + info " Waiting for Kafka resources in namespace 'kafka'..." + if ! wait_for_cr_removal "kafka" "kafka"; then + warn " Kafka resources not fully removed within timeout" + removal_failed=true + else + info " Kafka resources removed" + fi + fi + + # Wait for Apicurio Registry resources + if kubectl get apicurioregistry3 -n apicurio-registry -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then + info " Waiting for Apicurio Registry resources in namespace 'apicurio-registry'..." + if ! wait_for_cr_removal "apicurioregistry3" "apicurio-registry"; then + warn " Apicurio Registry resources not fully removed within timeout" + removal_failed=true + else + info " Apicurio Registry resources removed" + fi + fi + + # Wait for Console resources + if kubectl get console.console.streamshub.github.com -n streamshub-console -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then + info " Waiting for Console resources in namespace 'streamshub-console'..." + if ! wait_for_cr_removal "console.console.streamshub.github.com" "streamshub-console"; then + warn " Console resources not fully removed within timeout" + removal_failed=true + else + info " Console resources removed" + fi + fi + + if [ "$removal_failed" = true ]; then + warn "Some custom resources were not fully removed. Proceeding with caution..." + fi + echo "" + + # --- Phase 2: Shared-cluster safety checks --- + info "Phase 2: Checking for shared CRD usage..." + + if has_unlabeled_crs "$STRIMZI_CR_TYPES"; then + strimzi_shared=true + warn " Strimzi: Found non-quick-start CRs — CRDs will be retained" + else + info " Strimzi: No shared CRs found — safe for full removal" + fi + + if has_unlabeled_crs "$APICURIO_CR_TYPES"; then + apicurio_shared=true + warn " Apicurio Registry: Found non-quick-start CRs — CRDs will be retained" + else + info " Apicurio Registry: No shared CRs found — safe for full removal" + fi + + if has_unlabeled_crs "$CONSOLE_CR_TYPES"; then + console_shared=true + warn " StreamsHub Console: Found non-quick-start CRs — CRDs will be retained" + else + info " StreamsHub Console: No shared CRs found — safe for full removal" + fi + echo "" + + # --- Phase 3: Delete operators and CRDs --- + if [ "$strimzi_shared" = false ] && [ "$apicurio_shared" = false ] && [ "$console_shared" = false ]; then + # No shared CRDs — safe to delete the entire base layer + local base_url + base_url=$(kustomize_url "base") + info "Phase 3: Deleting operators and CRDs (no shared usage detected)..." + info "Deleting: ${base_url}" + kubectl delete -k "${base_url}" --ignore-not-found=true 2>/dev/null || true + else + # Some operator groups have shared CRDs — selective cleanup + info "Phase 3: Selective operator removal (some CRDs are shared)..." + + if [ "$strimzi_shared" = false ]; then + info " Removing Strimzi operator (full removal including CRDs)..." + local strimzi_url + strimzi_url=$(kustomize_url "base/strimzi-operator") + kubectl delete -k "${strimzi_url}" --ignore-not-found=true 2>/dev/null || true + else + warn " Strimzi: Removing operator deployment only (retaining CRDs)..." + kubectl delete deployment strimzi-cluster-operator -n strimzi --ignore-not-found=true 2>/dev/null || true + kubectl delete serviceaccount strimzi-cluster-operator -n strimzi --ignore-not-found=true 2>/dev/null || true + kubectl delete configmap strimzi-cluster-operator -n strimzi --ignore-not-found=true 2>/dev/null || true + fi + + if [ "$apicurio_shared" = false ]; then + info " Removing Apicurio Registry operator (full removal including CRDs)..." + local apicurio_url + apicurio_url=$(kustomize_url "base/apicurio-registry-operator") + kubectl delete -k "${apicurio_url}" --ignore-not-found=true 2>/dev/null || true + else + warn " Apicurio Registry: Removing operator deployment only (retaining CRDs)..." + kubectl delete deployment apicurio-registry-operator -n apicurio-registry --ignore-not-found=true 2>/dev/null || true + kubectl delete serviceaccount apicurio-registry-operator -n apicurio-registry --ignore-not-found=true 2>/dev/null || true + fi + + if [ "$console_shared" = false ]; then + info " Removing StreamsHub Console operator (full removal including CRDs)..." + local console_url + console_url=$(kustomize_url "base/streamshub-console-operator") + kubectl delete -k "${console_url}" --ignore-not-found=true 2>/dev/null || true + else + warn " StreamsHub Console: Removing operator deployment only (retaining CRDs)..." + kubectl delete deployment console-operator -n streamshub-console --ignore-not-found=true 2>/dev/null || true + kubectl delete serviceaccount console-operator -n streamshub-console --ignore-not-found=true 2>/dev/null || true + fi + fi + echo "" + + # --- Summary --- + info "Uninstall complete!" + echo "" + + # Report retained groups + if [ "$strimzi_shared" = true ] || [ "$apicurio_shared" = true ] || [ "$console_shared" = true ]; then + echo "Retained operator groups (shared CRDs detected):" + [ "$strimzi_shared" = true ] && echo " - Strimzi CRDs (non-quick-start Kafka resources exist on the cluster)" + [ "$apicurio_shared" = true ] && echo " - Apicurio Registry CRDs (non-quick-start Registry resources exist on the cluster)" + [ "$console_shared" = true ] && echo " - StreamsHub Console CRDs (non-quick-start Console resources exist on the cluster)" + echo "" + echo "To manually remove retained CRDs after verifying no resources depend on them:" + echo " kubectl get crds -l ${QUICKSTART_LABEL}" + echo "" + fi + + # Check for any remaining quick-start resources + local remaining + remaining=$(kubectl get all -A -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | wc -l | tr -d ' ') + local remaining_cluster + remaining_cluster=$(kubectl get crds,clusterroles,clusterrolebindings -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | wc -l | tr -d ' ') + + if [ "$remaining" -gt 0 ] || [ "$remaining_cluster" -gt 0 ]; then + warn "Some quick-start resources remain on the cluster:" + [ "$remaining" -gt 0 ] && kubectl get all -A -l "$QUICKSTART_LABEL" 2>/dev/null + [ "$remaining_cluster" -gt 0 ] && kubectl get crds,clusterroles,clusterrolebindings -l "$QUICKSTART_LABEL" 2>/dev/null + else + info "All quick-start resources have been removed" + fi +} + +main diff --git a/update-version.sh b/update-version.sh new file mode 100755 index 0000000..517e601 --- /dev/null +++ b/update-version.sh @@ -0,0 +1,394 @@ +#!/usr/bin/env bash +# +# Update component versions across kustomization files +# +# Usage: +# ./update-version.sh +# ./update-version.sh --list +# ./update-version.sh --dry-run +# ./update-version.sh --check +# +# Examples: +# ./update-version.sh strimzi 0.51.0 +# ./update-version.sh apicurio-registry 3.1.8 +# ./update-version.sh streamshub-console 0.12.0 +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Color output helpers +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" >&2 +} + +# Component configuration +COMPONENT_LABEL="" +GITHUB_REPO="" +VERSION_REGEX="" +COMPONENT_FILES=() + +setup_strimzi() { + COMPONENT_LABEL="Strimzi" + GITHUB_REPO="strimzi/strimzi-kafka-operator" + VERSION_REGEX='releases/download/\K[0-9]+\.[0-9]+\.[0-9]+' + COMPONENT_FILES=( + "${SCRIPT_DIR}/base/strimzi-operator/kustomization.yaml" + "${SCRIPT_DIR}/stack/kafka/kustomization.yaml" + ) +} + +setup_apicurio_registry() { + COMPONENT_LABEL="Apicurio Registry" + GITHUB_REPO="Apicurio/apicurio-registry" + VERSION_REGEX='Apicurio/apicurio-registry/\K[0-9]+\.[0-9]+\.[0-9]+' + COMPONENT_FILES=( + "${SCRIPT_DIR}/base/apicurio-registry-operator/kustomization.yaml" + ) +} + +setup_streamshub_console() { + COMPONENT_LABEL="StreamsHub Console" + GITHUB_REPO="streamshub/console" + VERSION_REGEX='releases/download/\K[0-9]+\.[0-9]+\.[0-9]+' + COMPONENT_FILES=( + "${SCRIPT_DIR}/base/streamshub-console-operator/kustomization.yaml" + ) +} + +usage() { + cat < + +Update component versions in kustomization files. + +Components: + strimzi Strimzi Kafka Operator + apicurio-registry Apicurio Registry Operator + streamshub-console StreamsHub Console Operator + +Arguments: + component The component to update + new-version The version to update to (e.g., 0.51.0 or 3.1.8) + +Options: + -c, --check Only check if the release exists on GitHub (no changes made) + -d, --dry-run Show what would be changed without making changes + -l, --list [N|all] List available versions (default: 20, or 'all') + -h, --help Show this help message + +Examples: + $(basename "$0") --list strimzi # List latest 20 Strimzi versions + $(basename "$0") --list all strimzi # List all Strimzi versions + $(basename "$0") --check strimzi 0.51.0 # Verify release exists + $(basename "$0") --dry-run strimzi 0.51.0 # Preview changes + $(basename "$0") strimzi 0.51.0 # Update Strimzi to version + $(basename "$0") apicurio-registry 3.1.8 # Update Apicurio Registry + $(basename "$0") streamshub-console 0.12.0 # Update StreamsHub Console +EOF + exit 0 +} + +# Set up component configuration based on name +setup_component() { + local component="$1" + case "$component" in + strimzi) + setup_strimzi + ;; + apicurio-registry) + setup_apicurio_registry + ;; + streamshub-console) + setup_streamshub_console + ;; + *) + error "Unknown component: ${component}" + error "Valid components: strimzi, apicurio-registry, streamshub-console" + exit 1 + ;; + esac +} + +# Get current version from the first component file +get_current_version() { + grep -oP "$VERSION_REGEX" "${COMPONENT_FILES[0]}" | head -1 +} + +# List available releases from GitHub +list_releases() { + local limit="${1:-20}" + local is_all=false + local per_page="$limit" + + if [[ "$limit" == "all" ]]; then + is_all=true + per_page=100 + fi + + info "Fetching available ${COMPONENT_LABEL} releases from GitHub..." + echo "" + + if ! command -v curl &> /dev/null; then + error "curl is required to list releases" + exit 1 + fi + + local releases="" + + if [[ "$is_all" == "true" ]]; then + local page=1 + while true; do + local page_releases + page_releases=$(curl -s "https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=100&page=${page}" \ + | grep '"tag_name":' \ + | sed -E 's/.*"tag_name": "([^"]+)".*/\1/' || true) + + if [[ -z "$page_releases" ]]; then + break + fi + + if [[ -n "$releases" ]]; then + releases="${releases}"$'\n'"${page_releases}" + else + releases="$page_releases" + fi + ((page++)) || true + + if [[ $page -gt 10 ]]; then + break + fi + done + else + releases=$(curl -s "https://api.github.com/repos/${GITHUB_REPO}/releases?per_page=${per_page}" \ + | grep '"tag_name":' \ + | sed -E 's/.*"tag_name": "([^"]+)".*/\1/' \ + | head -n "$limit") + fi + + if [[ -z "$releases" ]]; then + error "Failed to fetch releases from GitHub" + exit 1 + fi + + local current_version + current_version=$(get_current_version) + + local count + count=$(echo "$releases" | wc -l | tr -d ' ') + + if [[ "$is_all" == "true" ]]; then + echo "Available ${COMPONENT_LABEL} versions (all ${count}):" + else + echo "Available ${COMPONENT_LABEL} versions (latest ${limit}):" + fi + echo "" + while IFS= read -r version; do + if [[ "$version" == "$current_version" ]]; then + echo " ${version} (current)" + else + echo " ${version}" + fi + done <<< "$releases" + echo "" + info "View all releases: https://github.com/${GITHUB_REPO}/releases" +} + +# Check if release exists on GitHub +check_release_exists() { + local version="$1" + local release_url="https://github.com/${GITHUB_REPO}/releases/tag/${version}" + + info "Checking if ${COMPONENT_LABEL} release ${version} exists..." + + if command -v curl &> /dev/null; then + local http_code + http_code=$(curl -s -o /dev/null -w "%{http_code}" -L "$release_url") + if [[ "$http_code" == "200" ]]; then + info "Release ${version} exists on GitHub" + return 0 + else + error "Release ${version} not found on GitHub (HTTP ${http_code})" + error "Check available releases at: https://github.com/${GITHUB_REPO}/releases" + return 1 + fi + else + warn "curl not available, skipping release check" + return 0 + fi +} + +# Cross-platform sed -i +sed_inplace() { + if [[ "$(uname)" == "Darwin" ]]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +# Update version in a file +update_file() { + local file="$1" + local old_version="$2" + local new_version="$3" + local dry_run="$4" + + if [[ ! -f "$file" ]]; then + warn "File not found: ${file}" + return 1 + fi + + if ! grep -q "$old_version" "$file"; then + warn "Version ${old_version} not found in ${file}" + return 1 + fi + + if [[ "$dry_run" == "true" ]]; then + echo "Would update: ${file}" + echo " Old version: ${old_version}" + echo " New version: ${new_version}" + echo " Changes:" + while IFS= read -r line; do + echo " - ${line}" + echo " + ${line//$old_version/$new_version}" + done < <(grep "$old_version" "$file") + else + sed_inplace "s/${old_version}/${new_version}/g" "$file" + info "Updated: ${file}" + fi +} + +main() { + local check_release=false + local dry_run=false + local list_releases_flag=false + local list_limit="20" + local new_version="" + local component_arg="" + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + -c|--check) + check_release=true + shift + ;; + -d|--dry-run) + dry_run=true + shift + ;; + -l|--list) + list_releases_flag=true + shift + if [[ $# -gt 0 && ( "$1" == "all" || "$1" =~ ^[0-9]+$ ) ]]; then + list_limit="$1" + shift + fi + ;; + -h|--help) + usage + ;; + -*) + error "Unknown option: $1" + usage + ;; + *) + if [[ -z "$component_arg" ]]; then + component_arg="$1" + elif [[ -z "$new_version" ]]; then + new_version="$1" + else + error "Too many arguments" + usage + fi + shift + ;; + esac + done + + if [[ -z "$component_arg" ]]; then + error "No component specified" + usage + fi + + setup_component "$component_arg" + + if [[ "$list_releases_flag" == "true" ]]; then + list_releases "$list_limit" + exit 0 + fi + + if [[ -z "$new_version" ]]; then + error "No version specified" + usage + fi + + if ! [[ "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + error "Invalid version format: ${new_version}" + error "Expected format: X.Y.Z (e.g., 0.51.0)" + exit 1 + fi + + local current_version + current_version=$(get_current_version) + + if [[ -z "$current_version" ]]; then + error "Could not determine current ${COMPONENT_LABEL} version" + exit 1 + fi + + info "Current ${COMPONENT_LABEL} version: ${current_version}" + info "Target ${COMPONENT_LABEL} version: ${new_version}" + + if [[ "$current_version" == "$new_version" ]]; then + warn "Already at version ${new_version}" + exit 0 + fi + + if [[ "$check_release" == "true" ]]; then + if check_release_exists "$new_version"; then + exit 0 + else + exit 1 + fi + fi + + echo "" + + local files_updated=0 + for file in "${COMPONENT_FILES[@]}"; do + if update_file "$file" "$current_version" "$new_version" "$dry_run"; then + ((files_updated++)) || true + fi + done + + echo "" + + if [[ "$dry_run" == "true" ]]; then + info "Dry run complete. ${files_updated} file(s) would be updated." + else + info "Updated ${files_updated} file(s) from ${current_version} to ${new_version}" + echo "" + info "Next steps:" + echo " 1. Review the changes: git diff" + echo " 2. Test the deployment: kubectl kustomize base/ && kubectl kustomize stack/" + echo " 3. Commit the changes: git add -A && git commit -m 'Update ${COMPONENT_LABEL} to ${new_version}'" + fi +} + +main "$@" From c5df21d09116e0c2d3a331d35a2a802e64c4c22d Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Tue, 17 Mar 2026 11:19:29 +0000 Subject: [PATCH 2/5] Fix log lines in uninstall script Signed-off-by: Thomas Cooper --- uninstall.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uninstall.sh b/uninstall.sh index cf7426c..c5d2372 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -140,7 +140,7 @@ main() { # Wait for Kafka resources if kubectl get kafka -n kafka -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then - info " Waiting for Kafka resources in namespace 'kafka'..." + info " Waiting for Kafka resource removal in namespace 'kafka'..." if ! wait_for_cr_removal "kafka" "kafka"; then warn " Kafka resources not fully removed within timeout" removal_failed=true @@ -151,7 +151,7 @@ main() { # Wait for Apicurio Registry resources if kubectl get apicurioregistry3 -n apicurio-registry -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then - info " Waiting for Apicurio Registry resources in namespace 'apicurio-registry'..." + info " Waiting for Apicurio Registry resource removal in namespace 'apicurio-registry'..." if ! wait_for_cr_removal "apicurioregistry3" "apicurio-registry"; then warn " Apicurio Registry resources not fully removed within timeout" removal_failed=true @@ -162,7 +162,7 @@ main() { # Wait for Console resources if kubectl get console.console.streamshub.github.com -n streamshub-console -l "$QUICKSTART_LABEL" --no-headers 2>/dev/null | grep -q .; then - info " Waiting for Console resources in namespace 'streamshub-console'..." + info " Waiting for Console resource removal in namespace 'streamshub-console'..." if ! wait_for_cr_removal "console.console.streamshub.github.com" "streamshub-console"; then warn " Console resources not fully removed within timeout" removal_failed=true From 0844babdddee79c21aa91f64b64dcc15c9290a28 Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Tue, 17 Mar 2026 11:59:22 +0000 Subject: [PATCH 3/5] Fix install error due to missing ServiceMonitor CRD Signed-off-by: Thomas Cooper --- README.md | 37 ++++++++++++++----- .../kustomization.yaml | 13 +++++++ install.sh | 24 ++++++++---- uninstall.sh | 22 ++++++++--- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index dd26201..c41cfd0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ kubectl apply -k 'https://github.com/streamshub/developer-quickstart//stack?ref= ### Minikube -When using minikube, (if you didn't eanble it when you created the minikube cluster) enable the ingress addon and run `minikube tunnel`: +When using minikube, (if you didn't enable it when you created the minikube cluster) enable the ingress addon and run `minikube tunnel`: ```bash minikube addons enable ingress @@ -137,7 +137,9 @@ kubectl get all -A -l app.kubernetes.io/part-of=streamshub-developer-quickstart kubectl get crds,clusterroles,clusterrolebindings -l app.kubernetes.io/part-of=streamshub-developer-quickstart ``` -## Updating Component Versions +## Development + +### Updating Component Versions Use the `update-version.sh` script to update component versions: @@ -157,6 +159,29 @@ Use the `update-version.sh` script to update component versions: Supported components: `strimzi`, `apicurio-registry`, `streamshub-console` +### Testing scripts locally + +When developing changes to the kustomization files, use the `LOCAL_DIR` environment +variable to point the install and uninstall scripts at your local checkout instead +of the remote GitHub repository: + +```shell +# Install from local repo +LOCAL_DIR=. ./install.sh + +# Uninstall from local repo +LOCAL_DIR=. ./uninstall.sh +``` + +When `LOCAL_DIR` is set, `REPO` and `REF` are ignored — the scripts resolve +kustomization paths relative to the given directory. + +You can also provide an absolute path: + +```shell +LOCAL_DIR=/home/user/repos/developer-quickstart ./install.sh +``` + ## Repository Structure ``` @@ -174,11 +199,3 @@ stack/ # Phase 2: Operands (Custom Resources) overlays/ # Future: variant configurations ``` - -## Current Component Versions - -| Component | Version | -|-----------|---------| -| Strimzi | 0.51.0 | -| Apicurio Registry | 3.1.7 | -| StreamsHub Console | 0.11.0 | diff --git a/base/streamshub-console-operator/kustomization.yaml b/base/streamshub-console-operator/kustomization.yaml index 4112aaa..f27257a 100644 --- a/base/streamshub-console-operator/kustomization.yaml +++ b/base/streamshub-console-operator/kustomization.yaml @@ -25,3 +25,16 @@ patches: - op: replace path: /subjects/0/namespace value: streamshub-console + # Remove ServiceMonitor — requires Prometheus Operator CRD which is not + # part of the base install. A metrics overlay will be added separately. + - target: + group: monitoring.coreos.com + version: v1 + kind: ServiceMonitor + name: streamshub-console-operator + patch: |- + $patch: delete + apiVersion: monitoring.coreos.com/v1 + kind: ServiceMonitor + metadata: + name: streamshub-console-operator diff --git a/install.sh b/install.sh index b222022..fdd2ae5 100755 --- a/install.sh +++ b/install.sh @@ -14,14 +14,16 @@ # curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/install.sh | REPO=myuser/developer-quickstart bash # # Environment variables: -# REPO - GitHub repo path (default: streamshub/developer-quickstart) -# REF - Git ref/branch/tag (default: main) -# TIMEOUT - kubectl wait timeout (default: 120s) +# LOCAL_DIR - Use local directory instead of GitHub (e.g. LOCAL_DIR=.) +# REPO - GitHub repo path (default: streamshub/developer-quickstart) +# REF - Git ref/branch/tag (default: main) +# TIMEOUT - kubectl wait timeout (default: 120s) # set -euo pipefail # Defaults (overridable via environment variables) +LOCAL_DIR="${LOCAL_DIR:-}" REPO="${REPO:-streamshub/developer-quickstart}" REF="${REF:-main}" TIMEOUT="${TIMEOUT:-120s}" @@ -44,10 +46,14 @@ error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } -# Build the kustomize URL for a given sub-path +# Build the kustomize URL (or local path) for a given sub-path kustomize_url() { local path="$1" - echo "https://github.com/${REPO}//${path}?ref=${REF}" + if [ -n "${LOCAL_DIR}" ]; then + echo "${LOCAL_DIR}/${path}" + else + echo "https://github.com/${REPO}//${path}?ref=${REF}" + fi } # Check prerequisites @@ -69,7 +75,11 @@ check_prerequisites() { main() { echo "" info "Installing StreamsHub developer quick-start stack" - info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + if [ -n "${LOCAL_DIR}" ]; then + info "Dir: ${LOCAL_DIR} | Timeout: ${TIMEOUT}" + else + info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + fi echo "" # --- Prerequisites --- @@ -100,7 +110,7 @@ main() { # --- Step 4: Wait for StreamsHub Console operator --- info "Step 4/5: Waiting for console-operator to be ready (timeout: ${TIMEOUT})..." - kubectl wait --for=condition=Available deployment/console-operator \ + kubectl wait --for=condition=Available deployment/streamshub-console-operator \ -n streamshub-console --timeout="${TIMEOUT}" info "StreamsHub Console operator is ready" echo "" diff --git a/uninstall.sh b/uninstall.sh index c5d2372..b1b6136 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -15,14 +15,16 @@ # curl -sL https://raw.githubusercontent.com/streamshub/developer-quickstart/main/uninstall.sh | REPO=myuser/developer-quickstart bash # # Environment variables: -# REPO - GitHub repo path (default: streamshub/developer-quickstart) -# REF - Git ref/branch/tag (default: main) -# TIMEOUT - kubectl wait/poll timeout (default: 120s) +# LOCAL_DIR - Use local directory instead of GitHub (e.g. LOCAL_DIR=.) +# REPO - GitHub repo path (default: streamshub/developer-quickstart) +# REF - Git ref/branch/tag (default: main) +# TIMEOUT - kubectl wait/poll timeout (default: 120s) # set -euo pipefail # Defaults (overridable via environment variables) +LOCAL_DIR="${LOCAL_DIR:-}" REPO="${REPO:-streamshub/developer-quickstart}" REF="${REF:-main}" TIMEOUT="${TIMEOUT:-120s}" @@ -47,10 +49,14 @@ error() { echo -e "${RED}[ERROR]${NC} $1" >&2 } -# Build the kustomize URL for a given sub-path +# Build the kustomize URL (or local path) for a given sub-path kustomize_url() { local path="$1" - echo "https://github.com/${REPO}//${path}?ref=${REF}" + if [ -n "${LOCAL_DIR}" ]; then + echo "${LOCAL_DIR}/${path}" + else + echo "https://github.com/${REPO}//${path}?ref=${REF}" + fi } # Extract timeout value in seconds from TIMEOUT variable @@ -117,7 +123,11 @@ CONSOLE_CR_TYPES="consoles" main() { echo "" info "Uninstalling StreamsHub developer quick-start stack" - info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + if [ -n "${LOCAL_DIR}" ]; then + info "Dir: ${LOCAL_DIR} | Timeout: ${TIMEOUT}" + else + info "Repo: ${REPO} | Ref: ${REF} | Timeout: ${TIMEOUT}" + fi echo "" # Track which operator groups have shared CRDs From 20035bb660acdc353b0b33f78a5566380e06bec7 Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Tue, 17 Mar 2026 17:15:08 +0000 Subject: [PATCH 4/5] Update uninstall script to prompt user to remove non-quickstart CRs Signed-off-by: Thomas Cooper --- uninstall.sh | 238 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 222 insertions(+), 16 deletions(-) diff --git a/uninstall.sh b/uninstall.sh index b1b6136..b6820d7 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -67,6 +67,95 @@ timeout_seconds() { echo "$val" } +# Discover CR type plural names for CRDs whose API group ends with the given suffix. +# Arguments: api_group_suffix (e.g. "strimzi.io"), all_crds (pre-fetched CRD data) +# Output: space-separated list of plural CR type names, or empty string +filter_cr_types() { + local suffix="$1" + local all_crds="$2" + echo "$all_crds" \ + | awk -v suf="$suffix" 'index($1, suf) == length($1) - length(suf) + 1 { printf "%s ", $2 }' \ + | sed 's/ $//' +} + +# Check if /dev/tty is available for interactive prompts. +# Needed for `curl | bash` where stdin is the pipe. +is_interactive() { + [ -c /dev/tty ] 2>/dev/null +} + +# Prompt user with a yes/no question via /dev/tty. Defaults to No. +# Arguments: prompt message +# Returns 0 for yes, 1 for no +prompt_yes_no() { + local prompt="$1" + local answer + echo -en "${YELLOW}${prompt} [y/N]: ${NC}" > /dev/tty + read -r answer < /dev/tty + case "$answer" in + [yY]|[yY][eE][sS]) return 0 ;; + *) return 1 ;; + esac +} + +# List unlabeled CRs for given CR types across all namespaces. +# Arguments: cr_types (space-separated list) +# Returns 0 if any unlabeled CRs found, 1 if none +list_unlabeled_crs() { + local cr_types="$1" + local found=false + for cr_type in $cr_types; do + local count + count=$(kubectl get "$cr_type" -A --selector='!app.kubernetes.io/part-of' --no-headers 2>/dev/null | wc -l | tr -d ' ') || count=0 + if [ "$count" -gt 0 ]; then + found=true + kubectl get "$cr_type" -A --selector='!app.kubernetes.io/part-of' 2>/dev/null || true + fi + done + [ "$found" = true ] +} + +# Delete unlabeled CRs for given CR types across all namespaces. +# Arguments: cr_types (space-separated list) +delete_unlabeled_crs() { + local cr_types="$1" + for cr_type in $cr_types; do + local count + count=$(kubectl get "$cr_type" -A --selector='!app.kubernetes.io/part-of' --no-headers 2>/dev/null | wc -l | tr -d ' ') || count=0 + if [ "$count" -gt 0 ]; then + kubectl delete "$cr_type" -A --selector='!app.kubernetes.io/part-of' --ignore-not-found=true --wait=false 2>/dev/null || true + fi + done +} + +# Wait for unlabeled CRs of given types to be fully removed. +# Arguments: cr_types (space-separated list) +# Returns 0 if all removed, 1 if timeout +wait_for_unlabeled_cr_removal() { + local cr_types="$1" + local max_wait + max_wait=$(timeout_seconds) + local elapsed=0 + + while [ $elapsed -lt "$max_wait" ]; do + local total=0 + for cr_type in $cr_types; do + local count + count=$(kubectl get "$cr_type" -A --selector='!app.kubernetes.io/part-of' --no-headers 2>/dev/null | wc -l | tr -d ' ') + total=$((total + count)) + done + if [ "$total" -eq 0 ]; then + return 0 + fi + if [ "${interrupted:-false}" = true ]; then + return 1 + fi + sleep 5 + elapsed=$((elapsed + 5)) + done + return 1 +} + # Wait for all custom resources of a given type to be fully removed # Returns 0 if all removed, 1 if timeout wait_for_cr_removal() { @@ -82,6 +171,9 @@ wait_for_cr_removal() { if [ "$count" -eq 0 ]; then return 0 fi + if [ "${interrupted:-false}" = true ]; then + return 1 + fi sleep 5 elapsed=$((elapsed + 5)) done @@ -111,14 +203,10 @@ has_unlabeled_crs() { return 1 } -# Strimzi CR types to check for shared usage -STRIMZI_CR_TYPES="kafkas kafkanodepools kafkatopics kafkausers kafkaconnects kafkamirrormakers kafkamirrormaker2s kafkabridges kafkaconnectors kafkarebalances" - -# Apicurio Registry CR types -APICURIO_CR_TYPES="apicurioregistry3s" - -# StreamsHub Console CR types -CONSOLE_CR_TYPES="consoles" +# API group suffixes used to discover CR types dynamically from installed CRDs +STRIMZI_API_GROUP_SUFFIX="strimzi.io" +APICURIO_API_GROUP_SUFFIX="apicur.io" +CONSOLE_API_GROUP_SUFFIX="streamshub.github.com" main() { echo "" @@ -130,17 +218,134 @@ main() { fi echo "" + # Discover CR types dynamically from installed CRDs + local all_crds + all_crds=$(kubectl get crds \ + -o jsonpath='{range .items[*]}{.spec.group}{" "}{.spec.names.plural}{"\n"}{end}' \ + 2>/dev/null || true) + + STRIMZI_CR_TYPES=$(filter_cr_types "$STRIMZI_API_GROUP_SUFFIX" "$all_crds") + APICURIO_CR_TYPES=$(filter_cr_types "$APICURIO_API_GROUP_SUFFIX" "$all_crds") + CONSOLE_CR_TYPES=$(filter_cr_types "$CONSOLE_API_GROUP_SUFFIX" "$all_crds") + + info "Discovered CR types:" + info " Strimzi: ${STRIMZI_CR_TYPES:-}" + info " Apicurio Registry: ${APICURIO_CR_TYPES:-}" + info " StreamsHub Console: ${CONSOLE_CR_TYPES:-}" + echo "" + + # Catch Ctrl+C so the shell doesn't silently exit before reaching interactive prompts + local interrupted=false + trap 'warn "Caught interrupt, continuing cleanup..."; interrupted=true' INT + # Track which operator groups have shared CRDs local strimzi_shared=false local apicurio_shared=false local console_shared=false - # --- Phase 1: Delete operands (stack layer) --- + # --- Phase 1: Interactive cleanup of user-created CRs --- + # Must run BEFORE stack deletion so operators are still alive to process finalizers + info "Phase 1: Checking for user-created custom resources..." + + local has_user_crs=false + + # Check each operator group for unlabeled CRs + local strimzi_user_crs=false + local apicurio_user_crs=false + local console_user_crs=false + + if list_unlabeled_crs "$STRIMZI_CR_TYPES" > /dev/null 2>&1; then + strimzi_user_crs=true + has_user_crs=true + fi + if list_unlabeled_crs "$APICURIO_CR_TYPES" > /dev/null 2>&1; then + apicurio_user_crs=true + has_user_crs=true + fi + if list_unlabeled_crs "$CONSOLE_CR_TYPES" > /dev/null 2>&1; then + console_user_crs=true + has_user_crs=true + fi + + if [ "$has_user_crs" = true ]; then + if is_interactive; then + warn "Found user-created custom resources (not part of the quick-start)." + warn "These resources will cause operator CRDs to be retained if not deleted." + echo "" + + if [ "$strimzi_user_crs" = true ]; then + info " Strimzi resources without quick-start label:" + list_unlabeled_crs "$STRIMZI_CR_TYPES" 2>/dev/null | sed 's/^/ /' + echo "" + if prompt_yes_no " Delete these Strimzi resources?"; then + info " Deleting unlabeled Strimzi resources..." + delete_unlabeled_crs "$STRIMZI_CR_TYPES" + if wait_for_unlabeled_cr_removal "$STRIMZI_CR_TYPES"; then + info " Strimzi resources removed" + strimzi_user_crs=false + else + warn " Some Strimzi resources were not fully removed within timeout" + fi + else + info " Skipping Strimzi resource deletion" + fi + echo "" + fi + + if [ "$apicurio_user_crs" = true ]; then + info " Apicurio Registry resources without quick-start label:" + list_unlabeled_crs "$APICURIO_CR_TYPES" 2>/dev/null | sed 's/^/ /' + echo "" + if prompt_yes_no " Delete these Apicurio Registry resources?"; then + info " Deleting unlabeled Apicurio Registry resources..." + delete_unlabeled_crs "$APICURIO_CR_TYPES" + if wait_for_unlabeled_cr_removal "$APICURIO_CR_TYPES"; then + info " Apicurio Registry resources removed" + apicurio_user_crs=false + else + warn " Some Apicurio Registry resources were not fully removed within timeout" + fi + else + info " Skipping Apicurio Registry resource deletion" + fi + echo "" + fi + + if [ "$console_user_crs" = true ]; then + info " StreamsHub Console resources without quick-start label:" + list_unlabeled_crs "$CONSOLE_CR_TYPES" 2>/dev/null | sed 's/^/ /' + echo "" + if prompt_yes_no " Delete these StreamsHub Console resources?"; then + info " Deleting unlabeled Console resources..." + delete_unlabeled_crs "$CONSOLE_CR_TYPES" + if wait_for_unlabeled_cr_removal "$CONSOLE_CR_TYPES"; then + info " Console resources removed" + console_user_crs=false + else + warn " Some Console resources were not fully removed within timeout" + fi + else + info " Skipping Console resource deletion" + fi + echo "" + fi + else + warn "Non-interactive mode: skipping user-created resource cleanup." + warn "User-created CRs were detected but cannot prompt for deletion." + warn "Phase 3 will treat these as shared resources and retain their CRDs." + echo "" + fi + else + info " No user-created custom resources found" + fi + echo "" + + # --- Phase 2: Delete operands (stack layer) --- local stack_url stack_url=$(kustomize_url "stack") - info "Phase 1: Deleting operands..." + info "Phase 2: Deleting operands..." info "Deleting: ${stack_url}" - kubectl delete -k "${stack_url}" --ignore-not-found=true 2>/dev/null || true + kubectl delete -k "${stack_url}" --ignore-not-found=true --wait=false 2>/dev/null || true echo "" # --- Wait for CRs to be fully removed --- @@ -184,10 +389,11 @@ main() { if [ "$removal_failed" = true ]; then warn "Some custom resources were not fully removed. Proceeding with caution..." fi + interrupted=false echo "" - # --- Phase 2: Shared-cluster safety checks --- - info "Phase 2: Checking for shared CRD usage..." + # --- Phase 3: Shared-cluster safety checks --- + info "Phase 3: Checking for shared CRD usage..." if has_unlabeled_crs "$STRIMZI_CR_TYPES"; then strimzi_shared=true @@ -211,17 +417,17 @@ main() { fi echo "" - # --- Phase 3: Delete operators and CRDs --- + # --- Phase 4: Delete operators and CRDs --- if [ "$strimzi_shared" = false ] && [ "$apicurio_shared" = false ] && [ "$console_shared" = false ]; then # No shared CRDs — safe to delete the entire base layer local base_url base_url=$(kustomize_url "base") - info "Phase 3: Deleting operators and CRDs (no shared usage detected)..." + info "Phase 4: Deleting operators and CRDs (no shared usage detected)..." info "Deleting: ${base_url}" kubectl delete -k "${base_url}" --ignore-not-found=true 2>/dev/null || true else # Some operator groups have shared CRDs — selective cleanup - info "Phase 3: Selective operator removal (some CRDs are shared)..." + info "Phase 4: Selective operator removal (some CRDs are shared)..." if [ "$strimzi_shared" = false ]; then info " Removing Strimzi operator (full removal including CRDs)..." From e1dd81b6f6e8bab323846c27fe9f3e5ff1e734ce Mon Sep 17 00:00:00 2001 From: Thomas Cooper Date: Tue, 17 Mar 2026 17:18:17 +0000 Subject: [PATCH 5/5] Run validation jobs for all PRs regardless of which branch they target Signed-off-by: Thomas Cooper --- .github/workflows/validate.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 54c5c08..42b3a61 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -4,7 +4,6 @@ on: push: branches: [main] pull_request: - branches: [main] jobs: kustomize-build: