From 7816e69e38d314b1c38d74b06c64b53723dcfb0e Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Fri, 8 Apr 2022 09:22:01 +0100 Subject: [PATCH] Build the Olm bundle and test it in CI (#10949) * Building the OLM bundle * kustomize the main CRD for the OLM bundle * minor fixes --- .github/workflows/operator-ci.yml | 175 ++++++++++++++---- operator/.gitignore | 1 + operator/olm-base/bundle.Dockerfile | 14 ++ .../manifests/clusterserviceversion.yaml | 148 +++++++++++++++ operator/olm-base/metadata/annotations.yaml | 9 + .../scripts/build-testing-docker-images.sh | 6 +- operator/scripts/check-crds-installed.sh | 19 ++ operator/scripts/check-examples-installed.sh | 19 ++ operator/scripts/create-olm-bundle.sh | 40 ++++ operator/scripts/create-olm-test-catalog.sh | 34 ++++ operator/scripts/create-olm-test-resources.sh | 55 ++++++ operator/scripts/install-keycloak-operator.sh | 9 + operator/scripts/install-olm.sh | 7 + operator/scripts/prepare-olm-test.sh | 32 ++++ .../src/main/kubernetes/append_legacy_cr.yaml | 2 +- operator/src/main/kubernetes/kubernetes.yml | 3 + .../src/main/kubernetes/kustomization.yml | 1 + 17 files changed, 539 insertions(+), 35 deletions(-) create mode 100644 operator/.gitignore create mode 100644 operator/olm-base/bundle.Dockerfile create mode 100644 operator/olm-base/manifests/clusterserviceversion.yaml create mode 100644 operator/olm-base/metadata/annotations.yaml create mode 100755 operator/scripts/check-crds-installed.sh create mode 100755 operator/scripts/check-examples-installed.sh create mode 100755 operator/scripts/create-olm-bundle.sh create mode 100755 operator/scripts/create-olm-test-catalog.sh create mode 100755 operator/scripts/create-olm-test-resources.sh create mode 100755 operator/scripts/install-keycloak-operator.sh create mode 100755 operator/scripts/install-olm.sh create mode 100755 operator/scripts/prepare-olm-test.sh diff --git a/.github/workflows/operator-ci.yml b/.github/workflows/operator-ci.yml index c786f01a9b..7d59a404c8 100644 --- a/.github/workflows/operator-ci.yml +++ b/.github/workflows/operator-ci.yml @@ -17,30 +17,58 @@ concurrency: jobs: build: - name: Build + name: Build distribution if: ${{ ( github.event_name != 'schedule' ) || ( github.event_name == 'schedule' && github.repository == 'keycloak/keycloak' ) }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Set outputs + id: vars + run: echo "::set-output name=version::0.0.1-$(git rev-parse --short HEAD)" - name: Update maven settings run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ - uses: actions/setup-java@v2 with: distribution: 'temurin' java-version: ${{ env.JDK_VERSION }} - - name: Cache Maven packages - id: cache - uses: actions/cache@v3 - with: - path: | - ~/.m2/repository - key: cache-1-${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: cache-1-${{ runner.os }}-m2 - + cache: 'maven' - name: Create the Keycloak distribution run: | mvn clean install -Pdistribution -DskipTests -DskipExamples -DskipTestsuite + - name: Login to docker registry + uses: docker/login-action@v1.14.1 + with: + registry: ${{ secrets.TEST_DOCKER_REGISTRY }} + username: ${{ secrets.TEST_DOCKER_USERNAME }} + password: ${{ secrets.TEST_DOCKER_TOKEN }} + - name: Build and push the Keycloak Docker image + working-directory: quarkus/container + run: | + cp ../dist/target/keycloak-*.tar.gz ./ + docker build --label "quay.expires-after=20h" --build-arg KEYCLOAK_DIST=$(ls keycloak-*.tar.gz) . -t ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak:${{ steps.vars.outputs.version }} + docker push ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak:${{ steps.vars.outputs.version }} + - name: Build and push a custom pre-augmented Keycloak Docker image + working-directory: operator + run: | + ./scripts/build-testing-docker-images.sh ${{ steps.vars.outputs.version }} ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/custom-keycloak + docker push ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/custom-keycloak:${{ steps.vars.outputs.version }} + test-local: + name: Test local + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v3 + - name: Set outputs + id: vars + run: echo "::set-output name=version::0.0.1-$(git rev-parse --short HEAD)" + - name: Update maven settings + run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JDK_VERSION }} + cache: 'maven' - name: Setup Minikube-Kubernetes uses: manusa/actions-setup-minikube@v2.4.3 with: @@ -50,38 +78,121 @@ jobs: driver: docker start args: '--addons=ingress' - - name: Build the Keycloak Docker image - run: | - cd quarkus - cp dist/target/keycloak-*.tar.gz container/ - cd container - eval $(minikube -p minikube docker-env) - docker build --build-arg KEYCLOAK_DIST=$(ls keycloak-*.tar.gz) . -t keycloak:${GITHUB_SHA} - - - name: Build a custom pre-augmented Keycloak Docker image - working-directory: operator - run: | - eval $(minikube -p minikube docker-env) - ./scripts/build-testing-docker-images.sh ${GITHUB_SHA} keycloak - - name: Test operator running locally working-directory: operator run: | mvn clean verify \ -Dquarkus.kubernetes.deployment-target=kubernetes \ - -Doperator.keycloak.image=keycloak:${GITHUB_SHA} \ - -Doperator.keycloak.image-pull-policy=Never \ - -Dtest.operator.kubernetes.ip=$(minikube ip) \ - -Dtest.operator.custom.image=custom-keycloak:${GITHUB_SHA} + -Doperator.keycloak.image=${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak:${{ steps.vars.outputs.version }} \ + -Dtest.operator.custom.image=${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/custom-keycloak:${{ steps.vars.outputs.version }} \ + -Dtest.operator.kubernetes.ip=$(minikube ip) + + test-remote: + name: Test remote + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v3 + - name: Set outputs + id: vars + run: echo "::set-output name=version::0.0.1-$(git rev-parse --short HEAD)" + - name: Update maven settings + run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JDK_VERSION }} + cache: 'maven' + - name: Setup Minikube-Kubernetes + uses: manusa/actions-setup-minikube@v2.4.3 + with: + minikube version: v1.24.0 + kubernetes version: v1.22.3 + github token: ${{ secrets.GITHUB_TOKEN }} + driver: docker + start args: '--addons=ingress' - name: Test operator running in cluster working-directory: operator run: | eval $(minikube -p minikube docker-env) mvn clean verify \ - -Dquarkus.container-image.build=true -Dquarkus.container-image.tag=test \ + -Dquarkus.container-image.build=true \ -Dquarkus.kubernetes.deployment-target=kubernetes \ - -Dquarkus.jib.jvm-arguments="-Djava.util.logging.manager=org.jboss.logmanager.LogManager","-Doperator.keycloak.image=keycloak:${GITHUB_SHA}","-Doperator.keycloak.image-pull-policy=Never" \ + -Dquarkus.jib.jvm-arguments="-Djava.util.logging.manager=org.jboss.logmanager.LogManager","-Doperator.keycloak.image=${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak:${{ steps.vars.outputs.version }}" \ + -Dtest.operator.custom.image=${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/custom-keycloak:${{ steps.vars.outputs.version }} \ --no-transfer-progress -Dtest.operator.deployment=remote \ - -Dtest.operator.kubernetes.ip=$(minikube ip) \ - -Dtest.operator.custom.image=custom-keycloak:${GITHUB_SHA} + -Dtest.operator.kubernetes.ip=$(minikube ip) + + test-olm: + name: Test OLM installation + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v3 + - name: Set outputs + id: vars + run: echo "::set-output name=version::0.0.1-$(git rev-parse --short HEAD)" + - name: Update maven settings + run: mkdir -p ~/.m2 ; cp .github/settings.xml ~/.m2/ + - uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: ${{ env.JDK_VERSION }} + cache: 'maven' + - name: Setup Minikube-Kubernetes + uses: manusa/actions-setup-minikube@v2.4.3 + with: + minikube version: v1.24.0 + kubernetes version: v1.22.3 + github token: ${{ secrets.GITHUB_TOKEN }} + driver: docker + start args: '--addons=ingress' + - name: Login to docker registry + uses: docker/login-action@v1.14.1 + with: + registry: ${{ secrets.TEST_DOCKER_REGISTRY }} + username: ${{ secrets.TEST_DOCKER_USERNAME }} + password: ${{ secrets.TEST_DOCKER_TOKEN }} + + - name: Build and push the operator image + working-directory: operator + run: | + mvn clean package \ + -Dquarkus.container-image.build=true \ + -Dquarkus.container-image.push=true \ + -Dquarkus.container-image.image="${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }}/keycloak-operator:${{ steps.vars.outputs.version }}" \ + -Dquarkus.container-image.labels."\"quay.expires-after\""="20h" \ + -DskipTests + + - name: Install OPM + uses: redhat-actions/openshift-tools-installer@v1 + with: + source: "github" + opm: "1.21.0" + + - name: Install Yq + run: sudo snap install yq + + - name: Install OLM + working-directory: operator + run: ./scripts/install-olm.sh + + - name: Prepare resources for testing on OLM + working-directory: operator + run: | + ./scripts/prepare-olm-test.sh ${{ secrets.TEST_DOCKER_REGISTRY }}/${{ secrets.TEST_DOCKER_REPOSITORY }} ${{ steps.vars.outputs.version }} NONE + + - name: Install the operator with OLM + working-directory: operator + run: ./scripts/install-keycloak-operator.sh + + - name: Deploy an example Keycloak and wait for it to be ready + working-directory: operator + run: | + kubectl apply -f src/main/resources/example-postgres.yaml + ./scripts/check-crds-installed.sh + kubectl apply -f src/main/resources/example-keycloak.yml + kubectl apply -f src/main/resources/example-realm.yaml + # Wait for the CRs to be ready + ./scripts/check-examples-installed.sh diff --git a/operator/.gitignore b/operator/.gitignore new file mode 100644 index 0000000000..9dbf9263c7 --- /dev/null +++ b/operator/.gitignore @@ -0,0 +1 @@ +olm diff --git a/operator/olm-base/bundle.Dockerfile b/operator/olm-base/bundle.Dockerfile new file mode 100644 index 0000000000..8f50015979 --- /dev/null +++ b/operator/olm-base/bundle.Dockerfile @@ -0,0 +1,14 @@ +FROM scratch + +# Core bundle labels. +LABEL operators.operatorframework.io.bundle.mediatype.v1=registry+v1 +LABEL operators.operatorframework.io.bundle.manifests.v1=manifests/ +LABEL operators.operatorframework.io.bundle.metadata.v1=metadata/ +LABEL operators.operatorframework.io.bundle.package.v1=keycloak-operator +LABEL operators.operatorframework.io.bundle.channels.v1=candidate +LABEL operators.operatorframework.io.bundle.channel.default.v1=candidate +LABEL com.redhat.openshift.versions=v4.6 + +# Copy files to locations specified by labels. +COPY manifests /manifests/ +COPY metadata /metadata/ diff --git a/operator/olm-base/manifests/clusterserviceversion.yaml b/operator/olm-base/manifests/clusterserviceversion.yaml new file mode 100644 index 0000000000..b231be1706 --- /dev/null +++ b/operator/olm-base/manifests/clusterserviceversion.yaml @@ -0,0 +1,148 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + capabilities: Deep Insights + categories: Security + certified: 'False' + containerImage: 'quay.io/keycloak/keycloak-operator:REPLACE_ME_VERSION' + createdAt: REPLACE_ME_CREATED_AT + description: 'An Operator for installing and managing Keycloak' + repository: 'https://github.com/keycloak/keycloak' + support: Red Hat + alm-examples: |- + [ + { + "apiVersion": "keycloak.org/v2alpha1", + "kind": "Keycloak", + "metadata": { + "name": "example-keycloak", + "labels": { + "app": "sso" + } + }, + "spec": { + "instances": 1, + "hostname": "example.org", + "tlsSecret": "my-tls-secret" + } + }, + { + "apiVersion": "keycloak.org/v2alpha1", + "kind": "KeycloakRealmImport", + "metadata": { + "name": "example-keycloak-realm-import", + "labels": { + "app": "sso" + } + }, + "spec": { + "keycloakCRName": "example-keycloak", + "realm": {} + } + } + ] + name: keycloak-operator.vREPLACE_ME_VERSION + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: "Represents a Keycloak Instance" + displayName: "Keycloak" + kind: Keycloak + name: keycloaks.keycloak.org + version: v2alpha1 + - description: "Deprecated - Represents a Keycloak Instance" + displayName: "Deprecated - Keycloak" + kind: Keycloak + name: keycloaks.keycloak.org + version: v1alpha1 + - description: "Represents a Keycloak Realm Import" + displayName: "KeycloakRealmImport" + kind: KeycloakRealmImport + name: keycloakrealmimports.keycloak.org + version: v2alpha1 + description: | + A Kubernetes Operator based on the Operator SDK for installing and managing Keycloak. + + Keycloak lets you add authentication to applications and secure services with minimum fuss. No need to deal with storing users or authenticating users. It's all available out of the box. + + The operator can deploy and manage Keycloak instances on Kubernetes and OpenShift. + The following features are supported: + + * Install Keycloak to a namespace + * Import Keycloak Realms + displayName: Keycloak Operator + icon: + - base64data: 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 + mediatype: image/png + install: + spec: + deployments: + - name: keycloak-operator + spec: + replicas: 1 + selector: + matchLabels: + name: keycloak-operator + strategy: {} + template: + metadata: + labels: + name: keycloak-operator + spec: + containers: + - command: + - java + - -Djava.util.logging.manager=org.jboss.logmanager.LogManager + - -jar + - quarkus-run.jar + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: keycloak-operator + image: quay.io/keycloak/keycloak-operator:REPLACE_ME_VERSION + imagePullPolicy: Always + name: keycloak-operator + resources: {} + serviceAccountName: keycloak-operator + permissions: + - rules: [] # automatically generated + serviceAccountName: keycloak-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces + keywords: + - Keycloak + - Identity + - Access + links: + - name: Documentation + url: https://www.keycloak.org/docs/latest/server_installation/index.html#_operator + - name: Keycloak + url: https://www.keycloak.org/ + - name: Keycloak Discourse + url: https://keycloak.discourse.group/ + maintainers: + - email: keycloak-dev@lists.jboss.org + name: Keycloak DEV mailing list + maturity: alpha + provider: + name: Red Hat + version: REPLACE_ME_VERSION + replaces: keycloak-operator.vREPLACE_ME_LAST_VERSION diff --git a/operator/olm-base/metadata/annotations.yaml b/operator/olm-base/metadata/annotations.yaml new file mode 100644 index 0000000000..3b7d3e3add --- /dev/null +++ b/operator/olm-base/metadata/annotations.yaml @@ -0,0 +1,9 @@ +annotations: + # Core bundle annotations. + operators.operatorframework.io.bundle.mediatype.v1: registry+v1 + operators.operatorframework.io.bundle.manifests.v1: manifests/ + operators.operatorframework.io.bundle.metadata.v1: metadata/ + operators.operatorframework.io.bundle.package.v1: keycloak-operator + operators.operatorframework.io.bundle.channels.v1: candidate + operators.operatorframework.io.bundle.channel.default.v1: candidate + com.redhat.openshift.versions: "v4.6" diff --git a/operator/scripts/build-testing-docker-images.sh b/operator/scripts/build-testing-docker-images.sh index b7978c085a..5e24fd8101 100755 --- a/operator/scripts/build-testing-docker-images.sh +++ b/operator/scripts/build-testing-docker-images.sh @@ -1,7 +1,9 @@ -#!/usr/bin/env bash +#! /bin/bash +set -euxo pipefail VERSION="${1:-latest}" KEYCLOAK_IMAGE="${2:-quay.io/keycloak/keycloak}" +KEYCLOAK_CUSTOM_IMAGE="${3:-quay.io/keycloak/custom-keycloak}" echo "Using version: $VERSION" @@ -10,4 +12,4 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) docker build -f ${SCRIPT_DIR}/Dockerfile-custom-image \ --build-arg IMAGE=${KEYCLOAK_IMAGE} \ --build-arg VERSION=${VERSION} \ - -t custom-keycloak:${VERSION} ${SCRIPT_DIR} + -t ${KEYCLOAK_CUSTOM_IMAGE}:${VERSION} ${SCRIPT_DIR} diff --git a/operator/scripts/check-crds-installed.sh b/operator/scripts/check-crds-installed.sh new file mode 100755 index 0000000000..532e0a7098 --- /dev/null +++ b/operator/scripts/check-crds-installed.sh @@ -0,0 +1,19 @@ +#! /bin/bash +set -euxo pipefail + +max_retries=240 +c=0 +while ! kubectl get keycloaks +do + echo "waiting for Keycloak CRD" + ((c++)) && ((c==max_retries)) && exit -1 + sleep 1 +done + +c=0 +while ! kubectl get keycloakrealmimports +do + echo "waiting for Keycloak Realm Import CRD" + ((c++)) && ((c==max_retries)) && exit -1 + sleep 1 +done diff --git a/operator/scripts/check-examples-installed.sh b/operator/scripts/check-examples-installed.sh new file mode 100755 index 0000000000..8cdb67bbec --- /dev/null +++ b/operator/scripts/check-examples-installed.sh @@ -0,0 +1,19 @@ +#! /bin/bash +set -euxo pipefail + +max_retries=240 +c=0 +while [[ $(kubectl get keycloaks/example-kc -o jsonpath="{.status.conditions[?(@.type == 'Ready')].status}") != "true" ]] +do + echo "waiting for Keycloak example-kc status" + ((c++)) && ((c==max_retries)) && exit -1 + sleep 1 +done + +c=0 +while [[ $(kubectl get keycloakrealmimports/example-count0-kc -o jsonpath="{.status.conditions[?(@.type == 'Done')].status}") != "true" ]] +do + echo "waiting for Keycloak Realm Import example-count0-kc status" + ((c++)) && ((c==max_retries)) && exit -1 + sleep 1 +done diff --git a/operator/scripts/create-olm-bundle.sh b/operator/scripts/create-olm-bundle.sh new file mode 100755 index 0000000000..2cb811a53f --- /dev/null +++ b/operator/scripts/create-olm-bundle.sh @@ -0,0 +1,40 @@ +#! /bin/bash +set -euxo pipefail + +VERSION=$1 +REPLACES_VERSION=$2 +OPERATOR_DOCKER_IMAGE=$3 + +CREATED_AT=$(date "+%D %T") + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +echo "Creating OLM bundle for version $VERSION replacing version $REPLACES_VERSION" + +rm -rf $SCRIPT_DIR/../olm/$VERSION +mkdir -p $SCRIPT_DIR/../olm/$VERSION + +cp -r $SCRIPT_DIR/../olm-base/* $SCRIPT_DIR/../olm/$VERSION + +# Inject RBAC rules +yq ea '.rules as $item ireduce ({}; .rules += $item)' $SCRIPT_DIR/../target/kubernetes/kubernetes.yml | \ + yq ea -i 'select(fileIndex==0).spec.install.spec.permissions[0] = select(fileIndex==1) | select(fileIndex==0)' $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml - && \ +yq ea -i '.spec.install.spec.permissions[0].serviceAccountName = "keycloak-operator"' $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml && \ +yq ea -i ".metadata.annotations.containerImage = \"$OPERATOR_DOCKER_IMAGE:$VERSION\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml && \ +yq ea -i ".metadata.annotations.createdAt = \"$CREATED_AT\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml && \ +yq ea -i ".metadata.name = \"keycloak-operator.v$VERSION\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml && \ +yq ea -i ".spec.install.spec.deployments[0].spec.template.spec.containers[0].image = \"$OPERATOR_DOCKER_IMAGE:$VERSION\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml && \ +yq ea -i ".spec.version = \"$VERSION\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml + +if [[ $REPLACES_VERSION = "NONE" ]] +then + yq ea -i "del(.spec.replaces)" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml +else + yq ea -i ".spec.replaces = \"keycloak-operator.v$REPLACES_VERSION\"" $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml +fi + +mv $SCRIPT_DIR/../olm/$VERSION/manifests/clusterserviceversion.yaml "$SCRIPT_DIR/../olm/$VERSION/manifests/keycloak-operator.v$VERSION.clusterserviceversion.yaml" + +# Include the old CRD version +( cd $SCRIPT_DIR/../ && kubectl kustomize target | yq ea "select(.metadata.name == \"keycloaks.keycloak.org\")" > $SCRIPT_DIR/../olm/$VERSION/manifests/keycloaks.keycloak.org-v1.yml ) +cp $SCRIPT_DIR/../target/kubernetes/keycloakrealmimports.keycloak.org-v1.yml $SCRIPT_DIR/../olm/$VERSION/manifests diff --git a/operator/scripts/create-olm-test-catalog.sh b/operator/scripts/create-olm-test-catalog.sh new file mode 100755 index 0000000000..92d8adc90e --- /dev/null +++ b/operator/scripts/create-olm-test-catalog.sh @@ -0,0 +1,34 @@ +#! /bin/bash +set -euxo pipefail + +VERSION=$1 +BUNDLE_IMAGE=$2 + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +rm -rf $SCRIPT_DIR/../olm/catalog +mkdir -p $SCRIPT_DIR/../olm/catalog/test-catalog + +( + cd $SCRIPT_DIR/../olm/catalog + + opm generate dockerfile test-catalog + + opm init keycloak-operator \ + --default-channel=alpha \ + --output yaml > test-catalog/operator.yaml + + opm render $BUNDLE_IMAGE:$VERSION \ + --output=yaml >> test-catalog/operator.yaml + + cat << EOF >> test-catalog/operator.yaml +--- +schema: olm.channel +package: keycloak-operator +name: alpha +entries: + - name: keycloak-operator.v$VERSION +EOF + + opm validate test-catalog +) diff --git a/operator/scripts/create-olm-test-resources.sh b/operator/scripts/create-olm-test-resources.sh new file mode 100755 index 0000000000..5171029248 --- /dev/null +++ b/operator/scripts/create-olm-test-resources.sh @@ -0,0 +1,55 @@ +#! /bin/bash +set -euxo pipefail + +VERSION=$1 +DOCKER_REGISTRY=$2 + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +rm -rf $SCRIPT_DIR/../olm/testing-resources +mkdir -p $SCRIPT_DIR/../olm/testing-resources + +cat << EOF >> $SCRIPT_DIR/../olm/testing-resources/catalog.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: CatalogSource +metadata: + name: test-catalog + namespace: default +spec: + sourceType: grpc + image: $DOCKER_REGISTRY/keycloak-test-catalog:$VERSION + displayName: Keycloak Test Catalog + publisher: Me + updateStrategy: + registryPoll: + interval: 10m +EOF + +cat << EOF >> $SCRIPT_DIR/../olm/testing-resources/operatorgroup.yaml +kind: OperatorGroup +apiVersion: operators.coreos.com/v1 +metadata: + name: og-single + namespace: default +spec: + targetNamespaces: + - default +EOF + +cat << EOF >> $SCRIPT_DIR/../olm/testing-resources/subscription.yaml +apiVersion: operators.coreos.com/v1alpha1 +kind: Subscription +metadata: + name: keycloak-operator + namespace: default +spec: + installPlanApproval: Automatic + name: keycloak-operator + source: test-catalog + sourceNamespace: default + startingCSV: keycloak-operator.v$VERSION + config: + env: + - name: "OPERATOR_KEYCLOAK_IMAGE" + value: "$DOCKER_REGISTRY/keycloak:$VERSION" +EOF diff --git a/operator/scripts/install-keycloak-operator.sh b/operator/scripts/install-keycloak-operator.sh new file mode 100755 index 0000000000..d0f9bfe461 --- /dev/null +++ b/operator/scripts/install-keycloak-operator.sh @@ -0,0 +1,9 @@ +#! /bin/bash +set -euxo pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# Delete the default catalog if it exists +sh -c "kubectl delete catalogsources operatorhubio-catalog -n olm | true" + +kubectl apply -f $SCRIPT_DIR/../olm/testing-resources diff --git a/operator/scripts/install-olm.sh b/operator/scripts/install-olm.sh new file mode 100755 index 0000000000..3e3356db8e --- /dev/null +++ b/operator/scripts/install-olm.sh @@ -0,0 +1,7 @@ +#! /bin/bash +set -euxo pipefail + +mkdir -p /tmp/olm/ +curl -L https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.20.0/install.sh -o /tmp/olm/install.sh +chmod +x /tmp/olm/install.sh +/tmp/olm/install.sh v0.20.0 diff --git a/operator/scripts/prepare-olm-test.sh b/operator/scripts/prepare-olm-test.sh new file mode 100755 index 0000000000..e093517b67 --- /dev/null +++ b/operator/scripts/prepare-olm-test.sh @@ -0,0 +1,32 @@ +#! /bin/bash +set -euxo pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +DOCKER_REGISTRY="$1" + +VERSION="$2" +PREV_VERSION="$3" + +OPERATOR_IMAGE_NAME="keycloak-operator" +OPERATOR_DOCKER_IMAGE="$DOCKER_REGISTRY/$OPERATOR_IMAGE_NAME" + +# Create OLM bundle +$SCRIPT_DIR/create-olm-bundle.sh $VERSION $PREV_VERSION $OPERATOR_DOCKER_IMAGE + +(cd $SCRIPT_DIR/../olm/$VERSION && \ + docker build --label "quay.expires-after=20h" -t $DOCKER_REGISTRY/keycloak-operator-bundle:$VERSION -f bundle.Dockerfile . && \ + docker push $DOCKER_REGISTRY/keycloak-operator-bundle:$VERSION) + +# Verify the bundle +opm alpha bundle validate --tag $DOCKER_REGISTRY/keycloak-operator-bundle:$VERSION --image-builder docker + +# Create the test-catalog +$SCRIPT_DIR/create-olm-test-catalog.sh $VERSION $DOCKER_REGISTRY/keycloak-operator-bundle + +(cd $SCRIPT_DIR/../olm/catalog && \ + docker build --label "quay.expires-after=20h" -f test-catalog.Dockerfile -t $DOCKER_REGISTRY/keycloak-test-catalog:$VERSION . && \ + docker push $DOCKER_REGISTRY/keycloak-test-catalog:$VERSION) + +# Create testing resources +$SCRIPT_DIR/create-olm-test-resources.sh $VERSION $DOCKER_REGISTRY diff --git a/operator/src/main/kubernetes/append_legacy_cr.yaml b/operator/src/main/kubernetes/append_legacy_cr.yaml index c0a6227e1b..0034a6812c 100644 --- a/operator/src/main/kubernetes/append_legacy_cr.yaml +++ b/operator/src/main/kubernetes/append_legacy_cr.yaml @@ -1092,7 +1092,7 @@ - version type: object type: object - served: false + served: true storage: false subresources: status: {} diff --git a/operator/src/main/kubernetes/kubernetes.yml b/operator/src/main/kubernetes/kubernetes.yml index 25a1a3dbfd..dfb24ce4b7 100644 --- a/operator/src/main/kubernetes/kubernetes.yml +++ b/operator/src/main/kubernetes/kubernetes.yml @@ -5,6 +5,9 @@ metadata: rules: - apiGroups: - apps + # Extensions enabled for backward compatibility: + # https://github.com/fabric8io/kubernetes-client/issues/3996 + - extensions resources: - deployments verbs: diff --git a/operator/src/main/kubernetes/kustomization.yml b/operator/src/main/kubernetes/kustomization.yml index 39c304ef51..98407d6676 100644 --- a/operator/src/main/kubernetes/kustomization.yml +++ b/operator/src/main/kubernetes/kustomization.yml @@ -5,6 +5,7 @@ namespace: keycloak resources: - kubernetes/keycloaks.keycloak.org-v1.yml + - kubernetes/keycloakrealmimports.keycloak.org-v1.yml - kubernetes/kubernetes.yml # patchesStrategicMerge