From 5ebc41e46181d28bbbeadd327fa72b03882ac63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=BCning?= Date: Sat, 14 Feb 2026 20:02:43 +0100 Subject: [PATCH 1/2] fix component name inconsistencies and add ci test pipeline --- .github/workflows/ci.yml | 323 +++++++++++++++++++ .github/workflows/release-ocm-components.yml | 130 +++++++- README.md | 54 ++-- artifact-conduit/README.md | 15 +- artifact-conduit/bootstrap.yaml | 6 +- artifact-conduit/rgd-template.yaml | 18 +- cloudnative-pg/README.md | 88 +++-- cloudnative-pg/bootstrap.yaml | 6 +- cloudnative-pg/rgd-template.yaml | 6 +- keycloak/README.md | 18 +- 10 files changed, 538 insertions(+), 126 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f92a49e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,323 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ci-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + # --------------------------------------------------------------------------- + # Detect which component directories changed + # --------------------------------------------------------------------------- + detect-changes: + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + cloudnative-pg: ${{ steps.changes.outputs.cloudnative-pg }} + keycloak: ${{ steps.changes.outputs.keycloak }} + artifact-conduit: ${{ steps.changes.outputs.artifact-conduit }} + steps: + - uses: actions/checkout@v6 + + - name: Detect changed paths (PR only) + if: github.event_name == 'pull_request' + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + cloudnative-pg: + - 'cloudnative-pg/**' + keycloak: + - 'keycloak/**' + artifact-conduit: + - 'artifact-conduit/**' + + - name: Resolve changes + id: changes + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + { + echo "cloudnative-pg=${{ steps.filter.outputs.cloudnative-pg }}" + echo "keycloak=${{ steps.filter.outputs.keycloak }}" + echo "artifact-conduit=${{ steps.filter.outputs.artifact-conduit }}" + } >> "$GITHUB_OUTPUT" + else + # push to main or workflow_dispatch → test everything + { + echo "cloudnative-pg=true" + echo "keycloak=true" + echo "artifact-conduit=true" + } >> "$GITHUB_OUTPUT" + fi + + # --------------------------------------------------------------------------- + # Validate OCM component descriptors (always runs for all 3 components) + # --------------------------------------------------------------------------- + validate: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - component: artifact-conduit + path: ./artifact-conduit + - component: cloudnative-pg + path: ./cloudnative-pg + - component: keycloak + path: ./keycloak + steps: + - uses: actions/checkout@v4 + + - name: Set up OCM CLI + uses: open-component-model/ocm-setup-action@8c71929f38d3486e352e5d7aaf813f36accaaf43 + + - name: Build OCM component (dry-run) + run: | + cd "${{ matrix.path }}" + ocm add componentversion \ + --version 0.0.0-ci \ + --create \ + --file ./ctf \ + component-constructor.yaml + + - name: Verify resources resolve + run: | + cd "${{ matrix.path }}" + ocm get resources ./ctf + + - name: Clean up CTF + if: always() + run: rm -rf "${{ matrix.path }}/ctf" + + # --------------------------------------------------------------------------- + # Integration test: CloudNative-PG + # --------------------------------------------------------------------------- + test-cloudnative-pg: + needs: [detect-changes, validate] + if: needs.detect-changes.outputs.cloudnative-pg == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: cnpg-test + + - name: Install CloudNativePG operator + run: | + helm repo add cnpg https://cloudnative-pg.github.io/charts + helm repo update + helm upgrade --install cnpg-operator cnpg/cloudnative-pg \ + --namespace cnpg-system \ + --create-namespace \ + --wait \ + --timeout 5m + + - name: Apply minimal cluster config + run: kubectl apply -f cloudnative-pg/cluster-minimal.yaml + + - name: Wait for Cluster to become ready + run: | + echo "Waiting for CNPG cluster pods to start..." + kubectl wait --for=condition=Ready pod \ + -l cnpg.io/cluster=postgres-minimal \ + -n postgres \ + --timeout=300s + + - name: Verify database connectivity + run: | + kubectl exec -n postgres postgres-minimal-1 -- \ + psql -U app -d app -c "SELECT 1 AS health_check;" + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- CNPG Cluster ---" + kubectl get cluster -n postgres -o yaml || true + echo "--- Events ---" + kubectl get events -n postgres --sort-by='.lastTimestamp' || true + + # --------------------------------------------------------------------------- + # Integration test: Keycloak + # --------------------------------------------------------------------------- + test-keycloak: + needs: [detect-changes, validate] + if: needs.detect-changes.outputs.keycloak == 'true' + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: keycloak-test + + - name: Apply CRDs + run: | + kubectl apply -f keycloak/operator/keycloaks-crd.yml + kubectl apply -f keycloak/operator/keycloakrealmimports-crd.yml + + - name: Deploy Keycloak operator + run: | + kubectl apply -f keycloak/operator/operator.yml + kubectl wait --for=condition=Available deployment/keycloak-operator \ + -n keycloak \ + --timeout=120s + + - name: Generate self-signed TLS certificate + run: | + openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ + -keyout /tmp/tls.key -out /tmp/tls.crt \ + -subj "/CN=keycloak.local" + + - name: Apply minimal Keycloak config + run: kubectl apply -f keycloak/configs/minimal/keycloak.yml + + - name: Override placeholder TLS secret with real cert + run: | + kubectl create secret tls keycloak-tls-secret \ + --cert=/tmp/tls.crt --key=/tmp/tls.key \ + -n keycloak \ + --dry-run=client -o yaml | kubectl apply -f - + + - name: Wait for ephemeral PostgreSQL + run: | + kubectl wait --for=condition=Available deployment/postgres-db \ + -n keycloak \ + --timeout=120s + + - name: Wait for Keycloak pod + run: | + echo "Waiting for Keycloak pods to appear..." + for i in $(seq 1 30); do + if kubectl get pods -n keycloak -l app=keycloak --no-headers 2>/dev/null | grep -q .; then + break + fi + echo " attempt $i/30 — no pods yet" + sleep 10 + done + kubectl wait --for=condition=Ready pod \ + -l app=keycloak \ + -n keycloak \ + --timeout=300s + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- Keycloak CR ---" + kubectl get keycloak -n keycloak -o yaml || true + echo "--- Deployments ---" + kubectl get deployments -n keycloak -o wide || true + echo "--- Events ---" + kubectl get events -n keycloak --sort-by='.lastTimestamp' || true + echo "--- Operator logs ---" + kubectl logs deployment/keycloak-operator -n keycloak --tail=100 || true + + # --------------------------------------------------------------------------- + # Integration test: Artifact Conduit + # --------------------------------------------------------------------------- + test-artifact-conduit: + needs: [detect-changes, validate] + if: needs.detect-changes.outputs.artifact-conduit == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: arc-test + + - name: Install cert-manager + run: | + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml + echo "Waiting for cert-manager deployments..." + kubectl wait --for=condition=Available deployment/cert-manager \ + -n cert-manager --timeout=120s + kubectl wait --for=condition=Available deployment/cert-manager-webhook \ + -n cert-manager --timeout=120s + kubectl wait --for=condition=Available deployment/cert-manager-cainjector \ + -n cert-manager --timeout=120s + + - name: Login to GHCR (Helm OCI) + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | \ + helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Install Artifact Conduit via Helm + run: | + helm install arc \ + oci://ghcr.io/opendefensecloud/charts/arc \ + --version v0.2.1 \ + --namespace arc-system \ + --create-namespace \ + -f artifact-conduit/minimal-values.yaml \ + --wait \ + --timeout 5m + + - name: Verify deployments + run: | + kubectl wait --for=condition=Available deployment \ + -l app.kubernetes.io/instance=arc \ + -n arc-system \ + --timeout=180s + echo "--- Running pods ---" + kubectl get pods -n arc-system + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- Deployments ---" + kubectl get deployments -n arc-system -o wide || true + echo "--- Events ---" + kubectl get events -n arc-system --sort-by='.lastTimestamp' || true + echo "--- Helm status ---" + helm status arc -n arc-system || true + + # --------------------------------------------------------------------------- + # Gate job for branch protection (single required status check) + # --------------------------------------------------------------------------- + ci-success: + if: always() + needs: + - detect-changes + - validate + - test-cloudnative-pg + - test-keycloak + - test-artifact-conduit + runs-on: ubuntu-latest + steps: + - name: Evaluate job results + run: | + results=( \ + "${{ needs.detect-changes.result }}" \ + "${{ needs.validate.result }}" \ + "${{ needs.test-cloudnative-pg.result }}" \ + "${{ needs.test-keycloak.result }}" \ + "${{ needs.test-artifact-conduit.result }}" \ + ) + for r in "${results[@]}"; do + if [[ "$r" == "failure" || "$r" == "cancelled" ]]; then + echo "::error::Job failed or was cancelled: $r" + exit 1 + fi + done + echo "All jobs passed (success or skipped)." diff --git a/.github/workflows/release-ocm-components.yml b/.github/workflows/release-ocm-components.yml index 5f714b2..11ca950 100644 --- a/.github/workflows/release-ocm-components.yml +++ b/.github/workflows/release-ocm-components.yml @@ -6,19 +6,71 @@ on: - 'v*' jobs: + # --------------------------------------------------------------------------- + # Validate component descriptors before building + # --------------------------------------------------------------------------- + validate: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - name: artifact-conduit + path: ./artifact-conduit + - name: cloudnative-pg + path: ./cloudnative-pg + - name: keycloak + path: ./keycloak + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up OCM CLI + uses: open-component-model/ocm-setup-action@8c71929f38d3486e352e5d7aaf813f36accaaf43 + + - name: Extract version from tag + id: version + run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + + - name: Validate OCM component + run: | + cd "${{ matrix.path }}" + ocm add componentversion \ + --version "${{ steps.version.outputs.version }}" \ + --create \ + --file ./ctf \ + component-constructor.yaml + + - name: Verify resources resolve + run: | + cd "${{ matrix.path }}" + ocm get resources ./ctf + + - name: Clean up + if: always() + run: rm -rf "${{ matrix.path }}/ctf" + + # --------------------------------------------------------------------------- + # Build CTFs, push to GHCR, and upload artifacts + # --------------------------------------------------------------------------- build-and-push: + needs: validate runs-on: arc-scale-set permissions: - packages: write # Push to ghcr.io + packages: write strategy: matrix: - component: - - ./artifact-conduit - - ./cloudnative-pg - - ./keycloak + include: + - name: artifact-conduit + path: ./artifact-conduit + - name: cloudnative-pg + path: ./cloudnative-pg + - name: keycloak + path: ./keycloak steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Set up OCM CLI uses: open-component-model/ocm-setup-action@8c71929f38d3486e352e5d7aaf813f36accaaf43 @@ -32,22 +84,72 @@ jobs: - name: Extract version from tag id: version - run: | - VERSION=${GITHUB_REF#refs/tags/} - echo "version=${VERSION}" >> $GITHUB_OUTPUT + run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" - name: Build OCM Component run: | - cd ${{ matrix.component }} - ocm add componentversion --version ${{ steps.version.outputs.version }} --create --file ./ctf component-constructor.yaml + cd "${{ matrix.path }}" + ocm add componentversion \ + --version "${{ steps.version.outputs.version }}" \ + --create \ + --file ./ctf \ + component-constructor.yaml - name: Push to GHCR run: | - cd ${{ matrix.component }} + cd "${{ matrix.path }}" ocm transfer ctf --copy-local-resources ./ctf ghcr.io/${{ github.repository_owner }} + - name: Upload CTF artifact + uses: actions/upload-artifact@v4 + with: + name: ctf-${{ matrix.name }} + path: ${{ matrix.path }}/ctf + retention-days: 5 + - name: Clean up if: always() + run: rm -rf "${{ matrix.path }}/ctf" + + # --------------------------------------------------------------------------- + # Create GitHub Release with CTF archives and checksums + # --------------------------------------------------------------------------- + create-release: + needs: build-and-push + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Extract version from tag + id: version + run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + + - name: Download all CTF artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Package CTF archives + run: | + mkdir -p release + VERSION="${{ steps.version.outputs.version }}" + for component in artifact-conduit cloudnative-pg keycloak; do + tar -czf "release/${component}-ctf-${VERSION}.tar.gz" \ + -C "artifacts/ctf-${component}" . + done + + - name: Generate checksums + run: | + cd release + sha256sum -- *.tar.gz > checksums.sha256 + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - cd ${{ matrix.component }} - rm -rf ./ctf + VERSION="${{ steps.version.outputs.version }}" + gh release create "$VERSION" \ + --repo "${{ github.repository }}" \ + --title "Release $VERSION" \ + --generate-notes \ + release/* diff --git a/README.md b/README.md index 8affc43..43412b2 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,13 @@ PostgreSQL Operator for Kubernetes Quick Start: ```bash -# Install operator -kubectl apply --server-side -f cloudnative-pg/operator/cnpg-operator.yml +# Install operator via Helm +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm upgrade --install cnpg cnpg/cloudnative-pg \ + --namespace cnpg-system --create-namespace --wait # Deploy minimal cluster -kubectl apply -f cloudnative-pg/configs/minimal/cluster.yaml +kubectl apply -f cloudnative-pg/cluster-minimal.yaml ``` ### Artifact Conduit (ARC) (v0.1.0) @@ -85,18 +87,15 @@ kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/lat helm install artifact-conduit artifact-conduit/arc-0.1.0.tgz \ --namespace arc-system \ --create-namespace \ - --values artifact-conduit/configs/minimal/values.yaml + --values artifact-conduit/minimal-values.yaml ``` **Note**: Artifact Conduit is an early-stage project (356+ commits, 8 contributors) not yet recommended for production without thorough testing. It provides a declarative way to transfer artifacts across security boundaries with automated scanning and policy compliance. ## Suggested Components -See [suggested-components.md](suggested-components.md) for a list of additional components that are candidates for inclusion in this monorepo based on common dependencies and use cases. - -High Priority: +The following are candidates for inclusion in this monorepo based on common dependencies and use cases: -- CloudNativePG (PostgreSQL operator) - cert-manager (TLS certificate management) - External Secrets Operator - Prometheus Operator @@ -105,20 +104,30 @@ High Priority: ## Repository Structure ```text -ocm-monorepo/ -├── keycloak/ # Keycloak component +ocm-components/ +├── artifact-conduit/ # Artifact Conduit (ARC) component +│ ├── bootstrap.yaml # KRO bootstrap configuration +│ ├── component-constructor.yaml +│ ├── rgd-template.yaml # ResourceGraphDefinition for KRO +│ ├── minimal-values.yaml +│ └── production-values.yaml +├── cloudnative-pg/ # CloudNativePG component +│ ├── bootstrap.yaml +│ ├── component-constructor.yaml +│ ├── rgd-template.yaml +│ ├── cluster-minimal.yaml +│ └── cluster-production.yaml +├── keycloak/ # Keycloak component │ ├── operator/ # Operator manifests and CRDs │ ├── configs/ # Configuration examples -│ │ ├── minimal/ # Dev/test configuration -│ │ └── production/ # Production HA configuration -│ ├── tests/ # Test scripts -│ ├── component-constructor.yaml # OCM component descriptor -│ └── README.md # Component documentation +│ │ ├── minimal/ +│ │ └── production/ +│ ├── component-constructor.yaml +│ └── README.md ├── .github/ -│ └── workflows/ # CI/CD pipelines for releases -├── suggested-components.md # List of suggested components -├── CLAUDE.md # Development guidelines -└── README.md # This file +│ └── workflows/ # CI/CD pipelines for releases +├── CLAUDE.md # Development guidelines +└── README.md # This file ``` ## Working with OCM Components @@ -178,13 +187,6 @@ When adding a new component: ## Testing -Each component includes test scripts for validation: - -```bash -cd keycloak/tests -./test-minimal.sh -``` - Tests are designed to run on local kind clusters and verify: - Operator installation diff --git a/artifact-conduit/README.md b/artifact-conduit/README.md index ab33cd3..ae31d76 100644 --- a/artifact-conduit/README.md +++ b/artifact-conduit/README.md @@ -17,11 +17,9 @@ artifact-conduit/ ├── bootstrap.yaml # KRO bootstrap configuration ├── component-constructor.yaml # OCM component descriptor ├── rgd-template.yaml # ResourceGraphDefinition for KRO -└── configs/ - ├── minimal/ # Minimal configuration profile - │ └── values.yaml - └── production/ # Production HA configuration profile - └── values.yaml +├── minimal-values.yaml # Minimal configuration profile +├── production-values.yaml # Production HA configuration profile +└── README.md # This file ``` ## Prerequisites @@ -85,10 +83,9 @@ For air-gapped or restricted environments: 1. **Create OCM component archive**: ```bash -ocm add componentversion --create --file component-constructor.yaml . -ocm transfer componentarchive ./github.com/ocm/artifact-conduit \ - oci://your-registry.com/ocm-components \ - --copy-resources +ocm add componentversion --create --file ./ctf component-constructor.yaml +ocm transfer ctf --copy-local-resources ./ctf \ + oci://your-registry.com/ocm-components ``` 2. **In air-gapped environment**, apply bootstrap with internal registry: diff --git a/artifact-conduit/bootstrap.yaml b/artifact-conduit/bootstrap.yaml index 090d5c2..fc9a42f 100644 --- a/artifact-conduit/bootstrap.yaml +++ b/artifact-conduit/bootstrap.yaml @@ -40,7 +40,7 @@ metadata: name: artifact-conduit-component namespace: ocm-system spec: - component: github.com/ocm/artifact-conduit + component: opendefense.cloud/arc version: 0.1.0 repository: name: artifact-conduit-repo @@ -89,7 +89,7 @@ spec: url: ghcr.io # Replace with your registry if using a private one # Component reference - componentName: github.com/ocm/artifact-conduit + componentName: opendefense.cloud/arc componentVersion: 0.1.0 # Deployment configuration @@ -127,7 +127,7 @@ spec: # url: ghcr.io # # # Component reference -# componentName: github.com/ocm/artifact-conduit +# componentName: opendefense.cloud/arc # componentVersion: 0.1.0 # # # Deployment configuration diff --git a/artifact-conduit/rgd-template.yaml b/artifact-conduit/rgd-template.yaml index ced2813..30df2a2 100644 --- a/artifact-conduit/rgd-template.yaml +++ b/artifact-conduit/rgd-template.yaml @@ -15,7 +15,7 @@ spec: default: "ghcr.io" componentName: type: string - default: "github.com/ocm/artifact-conduit" + default: "opendefense.cloud/arc" componentVersion: type: string default: "0.1.0" @@ -77,7 +77,7 @@ spec: apiVersion: delivery.ocm.software/v1alpha1 kind: Repository metadata: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-")} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-")} namespace: ${schema.spec.namespace} spec: url: oci://${schema.spec.registry.url} @@ -91,13 +91,13 @@ spec: apiVersion: delivery.ocm.software/v1alpha1 kind: Component metadata: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} namespace: ${schema.spec.namespace} spec: component: ${schema.spec.componentName} version: ${schema.spec.componentVersion} repository: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-")} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-")} secretRef: name: ${schema.spec.ocmConfigSecretName} @@ -113,7 +113,7 @@ spec: spec: resource: arc-chart component: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} secretRef: name: ${schema.spec.ocmConfigSecretName} @@ -129,7 +129,7 @@ spec: spec: resource: arc-apiserver-image component: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} secretRef: name: ${schema.spec.ocmConfigSecretName} @@ -145,7 +145,7 @@ spec: spec: resource: arc-controller-manager-image component: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} secretRef: name: ${schema.spec.ocmConfigSecretName} @@ -161,7 +161,7 @@ spec: spec: resource: etcd-image component: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} secretRef: name: ${schema.spec.ocmConfigSecretName} @@ -177,7 +177,7 @@ spec: spec: resource: arc-${schema.spec.deploymentProfile}-config component: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-") + "-component"} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-") + "-component"} secretRef: name: ${schema.spec.ocmConfigSecretName} diff --git a/cloudnative-pg/README.md b/cloudnative-pg/README.md index 18c72da..7db5e62 100644 --- a/cloudnative-pg/README.md +++ b/cloudnative-pg/README.md @@ -18,21 +18,12 @@ This directory contains the OCM (Open Component Model) component for CloudNative ``` cloudnative-pg/ -├── operator/ # CloudNativePG operator manifests -│ ├── cnpg-operator.yml # Operator deployment -│ ├── values.yaml # Operator Helm chart values -│ └── cluster-values.yaml # Cluster Helm chart values -├── configs/ -│ ├── minimal/ # Minimal configuration -│ │ └── cluster.yaml # Single instance for dev/test -│ └── production/ # Production configuration -│ └── cluster.yaml # HA with 3 replicas, backups, monitoring -├── examples/ # Usage examples -├── tests/ # Test scripts -├── docs/ -│ └── HELM_CHART_CONFIG.md # Complete Helm chart configuration guide -├── component-constructor.yaml # OCM component descriptor -└── README.md # This file +├── cluster-minimal.yaml # Minimal cluster configuration (dev/test) +├── cluster-production.yaml # Production cluster configuration (HA) +├── bootstrap.yaml # KRO bootstrap configuration +├── component-constructor.yaml # OCM component descriptor +├── rgd-template.yaml # ResourceGraphDefinition for KRO +└── README.md # This file ``` ## Quick Start @@ -250,19 +241,22 @@ For manual deployment using Kubernetes manifests: 1. **Install the CloudNativePG Operator:** ```bash - kubectl apply --server-side -f operator/cnpg-operator.yml + # Install the operator using the official Helm chart + helm repo add cnpg https://cloudnative-pg.github.io/charts + helm upgrade --install cnpg cnpg/cloudnative-pg \ + --namespace cnpg-system --create-namespace --wait ``` 2. **Deploy PostgreSQL Cluster (Minimal):** ```bash - kubectl apply -f configs/minimal/cluster.yaml + kubectl apply -f cluster-minimal.yaml ``` Or for production: ```bash - kubectl apply -f configs/production/cluster.yaml + kubectl apply -f cluster-production.yaml ``` 3. **Get connection credentials:** @@ -289,7 +283,7 @@ For manual deployment using Kubernetes manifests: ### Minimal Configuration -**File**: [`configs/minimal/cluster.yaml`](configs/minimal/cluster.yaml) +**File**: [`cluster-minimal.yaml`](cluster-minimal.yaml) **Features**: - Single PostgreSQL instance @@ -308,12 +302,12 @@ For manual deployment using Kubernetes manifests: **Quick Deploy**: ```bash -kubectl apply -f configs/minimal/cluster.yaml +kubectl apply -f cluster-minimal.yaml ``` ### Production Configuration -**File**: [`configs/production/cluster.yaml`](configs/production/cluster.yaml) +**File**: [`cluster-production.yaml`](cluster-production.yaml) **Features**: - 3 PostgreSQL instances (HA) @@ -361,19 +355,14 @@ kubectl apply -f configs/minimal/cluster.yaml 4. Deploy: ```bash - kubectl apply -f configs/production/cluster.yaml + kubectl apply -f cluster-production.yaml ``` ## Advanced Configuration ### Using Helm Charts -CloudNativePG provides official Helm charts for both the operator and clusters. See [`docs/HELM_CHART_CONFIG.md`](docs/HELM_CHART_CONFIG.md) for complete documentation on: - -- All available operator parameters -- All available cluster parameters -- Custom configuration examples -- Hybrid OCM + Helm approach +CloudNativePG provides official Helm charts for both the operator and clusters. See the [upstream documentation](https://cloudnative-pg.io/documentation/) for complete parameter references. **Example - Custom Helm deployment**: @@ -392,7 +381,7 @@ helm install cnpg cnpg/cloudnative-pg \ # Deploy cluster with custom values helm install my-db cnpg/cluster \ --namespace my-app \ - --values configs/production/cluster.yaml \ + --values cluster-production.yaml \ --set cluster.instances=5 \ --set cluster.storage.size=500Gi ``` @@ -429,24 +418,14 @@ This ensures that when you transfer the component from `registry-a.com` to `regi ### Testing the RGD Deployment -A comprehensive test script is provided to verify the RGD deployment: - -```bash -cd tests -./test-rgd-bootstrap.sh -``` - -This test: - -- Creates a kind cluster -- Installs OCM K8s Toolkit, FluxCD, and KRO -- Builds and transfers the OCM component -- Deploys CloudNativePG via RGD bootstrap -- Verifies all resources are created correctly -- Tests PostgreSQL connectivity -- Validates image localization +To verify the RGD deployment: -See [tests/test-rgd-bootstrap.sh](tests/test-rgd-bootstrap.sh) for details. +1. Create a kind cluster with OCM K8s Toolkit, FluxCD, and KRO installed +2. Build and transfer the OCM component to a local registry +3. Apply the `bootstrap.yaml` to deploy CloudNativePG via RGD bootstrap +4. Verify all resources are created correctly +5. Test PostgreSQL connectivity +6. Validate image localization ## OCM Component @@ -681,21 +660,26 @@ Adjust based on your workload and available resources. - External Secrets Operator for credential management - cert-manager for TLS certificates -See [`../suggested-components.md`](../suggested-components.md) for details. +See the project root README for details on suggested components. ## Testing -See [`tests/`](tests/) directory for test scripts. - ### Local Testing with kind ```bash # Create kind cluster kind create cluster --name cnpg-test -# Run tests -cd tests -./test-minimal.sh +# Install operator +helm repo add cnpg https://cloudnative-pg.github.io/charts +helm upgrade --install cnpg cnpg/cloudnative-pg \ + --namespace cnpg-system --create-namespace --wait + +# Deploy minimal cluster +kubectl apply -f cluster-minimal.yaml + +# Verify +kubectl wait --for=condition=Ready cluster/pg-minimal --timeout=300s ``` ## Upgrading diff --git a/cloudnative-pg/bootstrap.yaml b/cloudnative-pg/bootstrap.yaml index 0116796..78a99f6 100644 --- a/cloudnative-pg/bootstrap.yaml +++ b/cloudnative-pg/bootstrap.yaml @@ -40,7 +40,7 @@ metadata: name: cloudnative-pg-component namespace: ocm-system spec: - component: github.com/ocm/cloudnative-pg + component: opendefense.cloud/cnpg version: 1.27.1 repository: name: cloudnative-pg-repo @@ -89,7 +89,7 @@ spec: url: ghcr.io # Replace with your registry if using a private one # Component reference - componentName: github.com/ocm/cloudnative-pg + componentName: opendefense.cloud/cnpg componentVersion: 1.27.1 # Deployment configuration @@ -129,7 +129,7 @@ spec: # url: ghcr.io # # # Component reference -# componentName: github.com/ocm/cloudnative-pg +# componentName: opendefense.cloud/cnpg # componentVersion: 1.27.1 # # # Deployment configuration diff --git a/cloudnative-pg/rgd-template.yaml b/cloudnative-pg/rgd-template.yaml index 09dca26..05fb3e8 100644 --- a/cloudnative-pg/rgd-template.yaml +++ b/cloudnative-pg/rgd-template.yaml @@ -15,7 +15,7 @@ spec: default: "ghcr.io" componentName: type: string - default: "github.com/ocm/cloudnative-pg" + default: "opendefense.cloud/cnpg" componentVersion: type: string default: "1.27.1" @@ -84,7 +84,7 @@ spec: apiVersion: delivery.ocm.software/v1alpha1 kind: Repository metadata: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-")} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-")} namespace: ${schema.spec.namespace} spec: url: oci://${schema.spec.registry.url} @@ -98,7 +98,7 @@ spec: apiVersion: delivery.ocm.software/v1alpha1 kind: Component metadata: - name: ${schema.spec.componentName | replace("github.com/ocm/", "") | replace("/", "-")} + name: ${schema.spec.componentName | replace("opendefense.cloud/", "") | replace("/", "-")} namespace: ${schema.spec.namespace} spec: component: ${schema.spec.componentName} diff --git a/keycloak/README.md b/keycloak/README.md index dd8d9bb..99bc1ac 100644 --- a/keycloak/README.md +++ b/keycloak/README.md @@ -29,8 +29,6 @@ keycloak/ │ ├── QUICK_REFERENCE.md # Quick lookup tables │ ├── PARAMETER_TREE.txt # Visual parameter hierarchy │ └── ARCHITECTURE.md # Architecture overview -├── examples/ # Usage examples -├── tests/ # Test scripts ├── component-constructor.yaml # OCM component descriptor └── README.md # This file ``` @@ -337,7 +335,7 @@ The following components are recommended for production deployments: - **External Secrets Operator**: Secure secret management - **Prometheus Operator**: Monitoring and metrics -See [`../suggested-components.md`](../suggested-components.md) for details. +See the project root README for details on suggested components. ## Monitoring @@ -462,16 +460,22 @@ The operator will perform a rolling update automatically. ## Testing -See [`tests/`](tests/) directory for test scripts. - ### Local Testing with kind ```bash # Create kind cluster kind create cluster --name keycloak-test -# Run tests -./tests/test-minimal.sh +# Install operator +kubectl apply -f operator/keycloaks-crd.yml +kubectl apply -f operator/keycloakrealmimports-crd.yml +kubectl apply -f operator/operator.yml + +# Deploy minimal config +kubectl apply -f configs/minimal/keycloak.yml + +# Verify +kubectl wait --for=condition=Ready keycloak/keycloak -n keycloak --timeout=300s ``` ## References From ef5174dddb679bf742aafec2d2f4b748318cd671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20H=C3=BCning?= Date: Wed, 18 Feb 2026 22:49:34 +0100 Subject: [PATCH 2/2] switch to arc-scale-test runner and single out component tests into separate files --- .github/workflows/ci.yml | 192 +------------------ .github/workflows/release-ocm-components.yml | 4 +- .github/workflows/test-artifact-conduit.yml | 67 +++++++ .github/workflows/test-cloudnative-pg.yml | 52 +++++ .github/workflows/test-keycloak.yml | 79 ++++++++ 5 files changed, 208 insertions(+), 186 deletions(-) create mode 100644 .github/workflows/test-artifact-conduit.yml create mode 100644 .github/workflows/test-cloudnative-pg.yml create mode 100644 .github/workflows/test-keycloak.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f92a49e..0b789e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: # Detect which component directories changed # --------------------------------------------------------------------------- detect-changes: - runs-on: ubuntu-latest + runs-on: arc-scale-set permissions: pull-requests: read outputs: @@ -61,7 +61,7 @@ jobs: # Validate OCM component descriptors (always runs for all 3 components) # --------------------------------------------------------------------------- validate: - runs-on: ubuntu-latest + runs-on: arc-scale-set timeout-minutes: 15 strategy: fail-fast: false @@ -98,199 +98,23 @@ jobs: run: rm -rf "${{ matrix.path }}/ctf" # --------------------------------------------------------------------------- - # Integration test: CloudNative-PG + # Integration tests (reusable workflows) # --------------------------------------------------------------------------- test-cloudnative-pg: needs: [detect-changes, validate] if: needs.detect-changes.outputs.cloudnative-pg == 'true' - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - - name: Create kind cluster - uses: helm/kind-action@v1 - with: - cluster_name: cnpg-test - - - name: Install CloudNativePG operator - run: | - helm repo add cnpg https://cloudnative-pg.github.io/charts - helm repo update - helm upgrade --install cnpg-operator cnpg/cloudnative-pg \ - --namespace cnpg-system \ - --create-namespace \ - --wait \ - --timeout 5m - - - name: Apply minimal cluster config - run: kubectl apply -f cloudnative-pg/cluster-minimal.yaml - - - name: Wait for Cluster to become ready - run: | - echo "Waiting for CNPG cluster pods to start..." - kubectl wait --for=condition=Ready pod \ - -l cnpg.io/cluster=postgres-minimal \ - -n postgres \ - --timeout=300s - - - name: Verify database connectivity - run: | - kubectl exec -n postgres postgres-minimal-1 -- \ - psql -U app -d app -c "SELECT 1 AS health_check;" - - - name: Collect debug info on failure - if: failure() - run: | - echo "--- Pods ---" - kubectl get pods -A - echo "--- CNPG Cluster ---" - kubectl get cluster -n postgres -o yaml || true - echo "--- Events ---" - kubectl get events -n postgres --sort-by='.lastTimestamp' || true + uses: ./.github/workflows/test-cloudnative-pg.yml - # --------------------------------------------------------------------------- - # Integration test: Keycloak - # --------------------------------------------------------------------------- test-keycloak: needs: [detect-changes, validate] if: needs.detect-changes.outputs.keycloak == 'true' - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - uses: actions/checkout@v4 - - - name: Create kind cluster - uses: helm/kind-action@v1 - with: - cluster_name: keycloak-test - - - name: Apply CRDs - run: | - kubectl apply -f keycloak/operator/keycloaks-crd.yml - kubectl apply -f keycloak/operator/keycloakrealmimports-crd.yml - - - name: Deploy Keycloak operator - run: | - kubectl apply -f keycloak/operator/operator.yml - kubectl wait --for=condition=Available deployment/keycloak-operator \ - -n keycloak \ - --timeout=120s - - - name: Generate self-signed TLS certificate - run: | - openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ - -keyout /tmp/tls.key -out /tmp/tls.crt \ - -subj "/CN=keycloak.local" - - - name: Apply minimal Keycloak config - run: kubectl apply -f keycloak/configs/minimal/keycloak.yml + uses: ./.github/workflows/test-keycloak.yml - - name: Override placeholder TLS secret with real cert - run: | - kubectl create secret tls keycloak-tls-secret \ - --cert=/tmp/tls.crt --key=/tmp/tls.key \ - -n keycloak \ - --dry-run=client -o yaml | kubectl apply -f - - - - name: Wait for ephemeral PostgreSQL - run: | - kubectl wait --for=condition=Available deployment/postgres-db \ - -n keycloak \ - --timeout=120s - - - name: Wait for Keycloak pod - run: | - echo "Waiting for Keycloak pods to appear..." - for i in $(seq 1 30); do - if kubectl get pods -n keycloak -l app=keycloak --no-headers 2>/dev/null | grep -q .; then - break - fi - echo " attempt $i/30 — no pods yet" - sleep 10 - done - kubectl wait --for=condition=Ready pod \ - -l app=keycloak \ - -n keycloak \ - --timeout=300s - - - name: Collect debug info on failure - if: failure() - run: | - echo "--- Pods ---" - kubectl get pods -A - echo "--- Keycloak CR ---" - kubectl get keycloak -n keycloak -o yaml || true - echo "--- Deployments ---" - kubectl get deployments -n keycloak -o wide || true - echo "--- Events ---" - kubectl get events -n keycloak --sort-by='.lastTimestamp' || true - echo "--- Operator logs ---" - kubectl logs deployment/keycloak-operator -n keycloak --tail=100 || true - - # --------------------------------------------------------------------------- - # Integration test: Artifact Conduit - # --------------------------------------------------------------------------- test-artifact-conduit: needs: [detect-changes, validate] if: needs.detect-changes.outputs.artifact-conduit == 'true' - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - - name: Create kind cluster - uses: helm/kind-action@v1 - with: - cluster_name: arc-test - - - name: Install cert-manager - run: | - kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml - echo "Waiting for cert-manager deployments..." - kubectl wait --for=condition=Available deployment/cert-manager \ - -n cert-manager --timeout=120s - kubectl wait --for=condition=Available deployment/cert-manager-webhook \ - -n cert-manager --timeout=120s - kubectl wait --for=condition=Available deployment/cert-manager-cainjector \ - -n cert-manager --timeout=120s - - - name: Login to GHCR (Helm OCI) - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | \ - helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Install Artifact Conduit via Helm - run: | - helm install arc \ - oci://ghcr.io/opendefensecloud/charts/arc \ - --version v0.2.1 \ - --namespace arc-system \ - --create-namespace \ - -f artifact-conduit/minimal-values.yaml \ - --wait \ - --timeout 5m - - - name: Verify deployments - run: | - kubectl wait --for=condition=Available deployment \ - -l app.kubernetes.io/instance=arc \ - -n arc-system \ - --timeout=180s - echo "--- Running pods ---" - kubectl get pods -n arc-system - - - name: Collect debug info on failure - if: failure() - run: | - echo "--- Pods ---" - kubectl get pods -A - echo "--- Deployments ---" - kubectl get deployments -n arc-system -o wide || true - echo "--- Events ---" - kubectl get events -n arc-system --sort-by='.lastTimestamp' || true - echo "--- Helm status ---" - helm status arc -n arc-system || true + uses: ./.github/workflows/test-artifact-conduit.yml + secrets: inherit # --------------------------------------------------------------------------- # Gate job for branch protection (single required status check) @@ -303,7 +127,7 @@ jobs: - test-cloudnative-pg - test-keycloak - test-artifact-conduit - runs-on: ubuntu-latest + runs-on: arc-scale-set steps: - name: Evaluate job results run: | diff --git a/.github/workflows/release-ocm-components.yml b/.github/workflows/release-ocm-components.yml index 11ca950..bcb1f2f 100644 --- a/.github/workflows/release-ocm-components.yml +++ b/.github/workflows/release-ocm-components.yml @@ -10,7 +10,7 @@ jobs: # Validate component descriptors before building # --------------------------------------------------------------------------- validate: - runs-on: ubuntu-latest + runs-on: arc-scale-test timeout-minutes: 15 strategy: fail-fast: false @@ -116,7 +116,7 @@ jobs: # --------------------------------------------------------------------------- create-release: needs: build-and-push - runs-on: ubuntu-latest + runs-on: arc-scale-set permissions: contents: write steps: diff --git a/.github/workflows/test-artifact-conduit.yml b/.github/workflows/test-artifact-conduit.yml new file mode 100644 index 0000000..6dd3d86 --- /dev/null +++ b/.github/workflows/test-artifact-conduit.yml @@ -0,0 +1,67 @@ +name: "Test: Artifact Conduit" + +on: + workflow_call: + secrets: + GITHUB_TOKEN: + required: true + +jobs: + test: + runs-on: arc-scale-set + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: arc-test + + - name: Install cert-manager + run: | + kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml + echo "Waiting for cert-manager deployments..." + kubectl wait --for=condition=Available deployment/cert-manager \ + -n cert-manager --timeout=120s + kubectl wait --for=condition=Available deployment/cert-manager-webhook \ + -n cert-manager --timeout=120s + kubectl wait --for=condition=Available deployment/cert-manager-cainjector \ + -n cert-manager --timeout=120s + + - name: Login to GHCR (Helm OCI) + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | \ + helm registry login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Install Artifact Conduit via Helm + run: | + helm install arc \ + oci://ghcr.io/opendefensecloud/charts/arc \ + --version v0.2.1 \ + --namespace arc-system \ + --create-namespace \ + -f artifact-conduit/minimal-values.yaml \ + --wait \ + --timeout 5m + + - name: Verify deployments + run: | + kubectl wait --for=condition=Available deployment \ + -l app.kubernetes.io/instance=arc \ + -n arc-system \ + --timeout=180s + echo "--- Running pods ---" + kubectl get pods -n arc-system + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- Deployments ---" + kubectl get deployments -n arc-system -o wide || true + echo "--- Events ---" + kubectl get events -n arc-system --sort-by='.lastTimestamp' || true + echo "--- Helm status ---" + helm status arc -n arc-system || true diff --git a/.github/workflows/test-cloudnative-pg.yml b/.github/workflows/test-cloudnative-pg.yml new file mode 100644 index 0000000..cda1fd0 --- /dev/null +++ b/.github/workflows/test-cloudnative-pg.yml @@ -0,0 +1,52 @@ +name: "Test: CloudNative-PG" + +on: + workflow_call: + +jobs: + test: + runs-on: arc-scale-set + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: cnpg-test + + - name: Install CloudNativePG operator + run: | + helm repo add cnpg https://cloudnative-pg.github.io/charts + helm repo update + helm upgrade --install cnpg-operator cnpg/cloudnative-pg \ + --namespace cnpg-system \ + --create-namespace \ + --wait \ + --timeout 5m + + - name: Apply minimal cluster config + run: kubectl apply -f cloudnative-pg/cluster-minimal.yaml + + - name: Wait for Cluster to become ready + run: | + echo "Waiting for CNPG cluster pods to start..." + kubectl wait --for=condition=Ready pod \ + -l cnpg.io/cluster=postgres-minimal \ + -n postgres \ + --timeout=300s + + - name: Verify database connectivity + run: | + kubectl exec -n postgres postgres-minimal-1 -- \ + psql -U app -d app -c "SELECT 1 AS health_check;" + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- CNPG Cluster ---" + kubectl get cluster -n postgres -o yaml || true + echo "--- Events ---" + kubectl get events -n postgres --sort-by='.lastTimestamp' || true diff --git a/.github/workflows/test-keycloak.yml b/.github/workflows/test-keycloak.yml new file mode 100644 index 0000000..6c294f0 --- /dev/null +++ b/.github/workflows/test-keycloak.yml @@ -0,0 +1,79 @@ +name: "Test: Keycloak" + +on: + workflow_call: + +jobs: + test: + runs-on: arc-scale-set + timeout-minutes: 25 + steps: + - uses: actions/checkout@v4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + cluster_name: keycloak-test + + - name: Apply CRDs + run: | + kubectl apply -f keycloak/operator/keycloaks-crd.yml + kubectl apply -f keycloak/operator/keycloakrealmimports-crd.yml + + - name: Deploy Keycloak operator + run: | + kubectl apply -f keycloak/operator/operator.yml + kubectl wait --for=condition=Available deployment/keycloak-operator \ + -n keycloak \ + --timeout=120s + + - name: Generate self-signed TLS certificate + run: | + openssl req -x509 -nodes -days 1 -newkey rsa:2048 \ + -keyout /tmp/tls.key -out /tmp/tls.crt \ + -subj "/CN=keycloak.local" + + - name: Apply minimal Keycloak config + run: kubectl apply -f keycloak/configs/minimal/keycloak.yml + + - name: Override placeholder TLS secret with real cert + run: | + kubectl create secret tls keycloak-tls-secret \ + --cert=/tmp/tls.crt --key=/tmp/tls.key \ + -n keycloak \ + --dry-run=client -o yaml | kubectl apply -f - + + - name: Wait for ephemeral PostgreSQL + run: | + kubectl wait --for=condition=Available deployment/postgres-db \ + -n keycloak \ + --timeout=120s + + - name: Wait for Keycloak pod + run: | + echo "Waiting for Keycloak pods to appear..." + for i in $(seq 1 30); do + if kubectl get pods -n keycloak -l app=keycloak --no-headers 2>/dev/null | grep -q .; then + break + fi + echo " attempt $i/30 — no pods yet" + sleep 10 + done + kubectl wait --for=condition=Ready pod \ + -l app=keycloak \ + -n keycloak \ + --timeout=300s + + - name: Collect debug info on failure + if: failure() + run: | + echo "--- Pods ---" + kubectl get pods -A + echo "--- Keycloak CR ---" + kubectl get keycloak -n keycloak -o yaml || true + echo "--- Deployments ---" + kubectl get deployments -n keycloak -o wide || true + echo "--- Events ---" + kubectl get events -n keycloak --sort-by='.lastTimestamp' || true + echo "--- Operator logs ---" + kubectl logs deployment/keycloak-operator -n keycloak --tail=100 || true