From 8c46f737fe2254b1a4b4f86edd7d0a29e5fa9474 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Thu, 11 May 2023 18:03:37 +0200 Subject: [PATCH 01/11] Fix https://github.com/Intersmash/intersmash/issues/37 --- global-test.properties | 6 + pom.xml | 5 + testsuite/pom.xml | 5 + ...eycloakQuarkusOperatorProvisionerTest.java | 345 ++ .../intersmash/tools/IntersmashConfig.java | 32 + tools/intersmash-tools-provisioners/pom.xml | 4 + .../builders/pod/ContainerBuilder.java | 419 +++ .../KeycloakQuarkusOperatorApplication.java | 40 + .../PostgreSQLImageOpenShiftApplication.java | 3 + .../DBImageOpenShiftProvisioner.java | 21 + .../KeycloakQuarkusOperatorProvisioner.java | 322 ++ ...loakQuarkusOperatorProvisionerFactory.java | 33 + .../PostgreSQLImageOpenShiftProvisioner.java | 38 + .../tools/util/tls/CertificatesUtils.java | 138 + ...keycloakrealmimports.k8s.keycloak.org.yaml | 2277 +++++++++++++ .../crds/keycloaks.k8s.keycloak.org.yaml | 2947 +++++++++++++++++ 16 files changed, 6635 insertions(+) create mode 100644 testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java create mode 100644 tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java create mode 100644 tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java create mode 100644 tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java create mode 100644 tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java create mode 100644 tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java create mode 100644 tools/intersmash-tools-provisioners/src/main/resources/crds/keycloakrealmimports.k8s.keycloak.org.yaml create mode 100644 tools/intersmash-tools-provisioners/src/main/resources/crds/keycloaks.k8s.keycloak.org.yaml diff --git a/global-test.properties b/global-test.properties index 08baf91d3..a5afd45c5 100644 --- a/global-test.properties +++ b/global-test.properties @@ -41,3 +41,9 @@ intersmash.kafka.operators.channel=strimzi-0.29.x # DB intersmash.mysql.image=quay.io/centos7/mysql-80-centos7 intersmash.postgresql.image=quay.io/centos7/postgresql-13-centos7 + +# Keycloak (new Quarkus based version) settings +#intersmash.keycloak.quarks.image=quay.io/keycloak/keycloak:21.1.1 +#intersmash.keycloak.quarkus.operators.catalog_source=community-operators +#intersmash.keycloak.quarkus.operators.index_image=registry.redhat.io/redhat/community-operator-index:v4.12 +#intersmash.keycloak.quarkus.operators.channel=fast diff --git a/pom.xml b/pom.xml index 4048ae729..fd7353905 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,7 @@ 0.28.0 2.13.1 6.6.0 + 5.12.2 diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 4487f0965..00e8684a5 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -44,6 +44,11 @@ org.jboss.intersmash intersmash-deployments-provider + + io.fabric8 + openshift-client + ${version.openshift-client} + \ No newline at end of file diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java new file mode 100644 index 000000000..3602456d9 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java @@ -0,0 +1,345 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.intersmash.testsuite.provision.openshift; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; +import org.jboss.intersmash.tools.application.openshift.PostgreSQLTemplateOpenShiftApplication; +import org.jboss.intersmash.tools.junit5.IntersmashExtension; +import org.jboss.intersmash.tools.provision.openshift.KeycloakQuarkusOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; +import org.jboss.intersmash.tools.provision.openshift.PostgreSQLTemplateOpenShiftProvisioner; +import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.openshift.template.PostgreSQLTemplate; +import org.jboss.intersmash.tools.util.tls.CertificatesUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.keycloak.k8s.v2alpha1.Keycloak; +import org.keycloak.k8s.v2alpha1.KeycloakSpec; +import org.keycloak.k8s.v2alpha1.keycloakspec.Db; +import org.keycloak.k8s.v2alpha1.keycloakspec.Hostname; +import org.keycloak.k8s.v2alpha1.keycloakspec.Http; +import org.keycloak.k8s.v2alpha1.keycloakspec.Ingress; +import org.keycloak.k8s.v2alpha1.keycloakspec.db.PasswordSecret; +import org.keycloak.k8s.v2alpha1.keycloakspec.db.UsernameSecret; +import org.slf4j.event.Level; + +import cz.xtf.core.openshift.OpenShiftWaiters; +import cz.xtf.core.openshift.OpenShifts; +import cz.xtf.core.waiting.SimpleWaiter; +import cz.xtf.junit5.annotations.CleanBeforeAll; +import io.fabric8.kubernetes.api.model.DeletionPropagation; +import lombok.extern.slf4j.Slf4j; + +/** + * Test the Keycloak Operator provisioning model and APIs + * + * See
+ * - https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples + *

+ * Not all examples are actually run, but these tests are more about the framework functionality verification than + * about the Keycloak operator testing. + */ +@Slf4j +@CleanBeforeAll +//@Disabled("WIP - Disabled until global-test.properties is configured with the required property") +public class KeycloakQuarkusOperatorProvisionerTest { + private static KeycloakQuarkusOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; + + private static final String POSTGRESQL_NAME = "postgresql"; + private static final String POSTGRESQL_DATABASE = "keycloak"; + private static final String POSTGRESQL_PASSWORD = "pippobaudo1234"; + private static final String POSTGRESQL_USER = "user09M"; + + private static PostgreSQLTemplateOpenShiftProvisioner initializePostgreSQLTemplateProvisioner() { + PostgreSQLTemplateOpenShiftProvisioner templateProvisioner = new PostgreSQLTemplateOpenShiftProvisioner( + new PostgreSQLTemplateOpenShiftApplication() { + @Override + public String getName() { + return POSTGRESQL_NAME; + } + + @Override + public Map getParameters() { + Map parameters = new HashMap<>(); + parameters.put("POSTGRESQL_DATABASE", POSTGRESQL_DATABASE); + parameters.put("POSTGRESQL_PASSWORD", POSTGRESQL_PASSWORD); + parameters.put("POSTGRESQL_USER", POSTGRESQL_USER); + return parameters; + } + + @Override + public PostgreSQLTemplate getTemplate() { + return PostgreSQLTemplate.POSTGRESQL_EPHEMERAL; + } + }); + return templateProvisioner; + } + + private static final PostgreSQLImageOpenShiftApplication pgSQLApplication = new PostgreSQLImageOpenShiftApplication() { + @Override + public String getName() { + return POSTGRESQL_NAME; + } + + @Override + public String getUser() { + return POSTGRESQL_USER; + } + + @Override + public String getPassword() { + return POSTGRESQL_PASSWORD; + } + + @Override + public String getDbName() { + return POSTGRESQL_DATABASE; + } + }; + private static final PostgreSQLImageOpenShiftProvisioner POSTGRESQL_IMAGE_PROVISIONER = new PostgreSQLImageOpenShiftProvisioner( + pgSQLApplication); + + private static KeycloakQuarkusOperatorProvisioner initializeOperatorProvisioner() { + KeycloakQuarkusOperatorProvisioner operatorProvisioner = new KeycloakQuarkusOperatorProvisioner( + new KeycloakQuarkusOperatorApplication() { + private static final String DEFAULT_KEYCLOAK_APP_NAME = "example-sso"; + + @Override + public Keycloak getKeycloak() { + Keycloak keycloak = new Keycloak(); + keycloak.getMetadata().setName(DEFAULT_KEYCLOAK_APP_NAME); + KeycloakSpec spec = new KeycloakSpec(); + spec.setInstances(1L); + Ingress ingress = new Ingress(); + ingress.setEnabled(true); + spec.setIngress(ingress); + Hostname hostname = new Hostname(); + hostname.setHostname(OpenShifts.master().generateHostname(DEFAULT_KEYCLOAK_APP_NAME)); + spec.setHostname(hostname); + String tlsSecretName = DEFAULT_KEYCLOAK_APP_NAME + "-tls-secret"; + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); + // add TLS config to keycloak using the secret we just created + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + spec.setHttp(http); + return keycloak; + } + + @Override + public String getName() { + return DEFAULT_KEYCLOAK_APP_NAME; + } + }); + return operatorProvisioner; + } + + private String name; + + private static final Map matchLabels = new HashMap<>(); + + @BeforeAll + public static void createOperatorGroup() throws IOException { + // Keycloak + KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); + + KEYCLOAK_OPERATOR_PROVISIONER.configure(); + matchLabels.put("app", "sso"); + IntersmashExtension.operatorCleanup(); + // create operator group - this should be done by InteropExtension + OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + // clean any leftovers + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + } + + @AfterAll + public static void removeOperatorGroup() { + OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); + KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); + POSTGRESQL_IMAGE_PROVISIONER.undeploy(); + POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); + } + + @AfterEach + public void customResourcesCleanup() { + + // delete keycloaks + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().list().getItems().stream() + .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER + .keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); + // delete realms + KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().list().getItems().stream() + .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER + .keycloakRealmImportClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) + .delete()); + } + + /** + * This test case creates and validates a basic {@link Keycloak} CR + * + * This is not an integration test, the goal here is to assess that the created CRs are configured as per the + * model specification. + * + * See + *
- https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples/keycloak + */ + @Test + public void exampleSso() { + KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); + try { + name = "example-sso"; + + Keycloak keycloak = new Keycloak(); + keycloak.getMetadata().setName(name); + keycloak.getMetadata().setLabels(matchLabels); + KeycloakSpec spec = new KeycloakSpec(); + spec.setInstances(1L); + Ingress ingress = new Ingress(); + ingress.setEnabled(true); + spec.setIngress(ingress); + Hostname hostname = new Hostname(); + hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand + String tlsSecretName = name + "-tls-secret"; + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); + // add TLS config to keycloak using the secret we just created + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + spec.setHttp(http); + spec.setHostname(hostname); + keycloak.setSpec(spec); + + verifyKeycloak(keycloak, true); + } finally { + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + } + } + + /** + * This test case creates and validates a {@link Keycloak} CR which uses a PostgreSQL databse + * + * This is not an integration test, the goal here is to assess that the created CRs are configured as per the + * model specification. + * + * See + *
- https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples/keycloak + */ + @Test + public void exampleSsoWithDatabase() { + KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); + try { + POSTGRESQL_IMAGE_PROVISIONER.preDeploy(); + POSTGRESQL_IMAGE_PROVISIONER.deploy(); + + name = "example-sso"; + + Keycloak keycloak = new Keycloak(); + keycloak.getMetadata().setName(name); + keycloak.getMetadata().setLabels(matchLabels); + KeycloakSpec spec = new KeycloakSpec(); + spec.setInstances(1L); + Ingress ingress = new Ingress(); + ingress.setEnabled(true); + spec.setIngress(ingress); + Hostname hostname = new Hostname(); + hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand + String tlsSecretName = name + "-tls-secret"; + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); + // add TLS config to keycloak using the secret we just created + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + spec.setHttp(http); + spec.setHostname(hostname); + // database + Db db = new Db(); + db.setDatabase(POSTGRESQL_IMAGE_PROVISIONER.getApplication().getDbName()); + db.setHost(POSTGRESQL_IMAGE_PROVISIONER.getServiceName()); + db.setPort(Integer.toUnsignedLong(POSTGRESQL_IMAGE_PROVISIONER.getPort())); + UsernameSecret usernameSecret = new UsernameSecret(); + usernameSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); + usernameSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_USER_KEY); + db.setUsernameSecret(usernameSecret); + PasswordSecret passwordSecret = new PasswordSecret(); + passwordSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); + passwordSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_PASSWORD_KEY); + db.setPasswordSecret(passwordSecret); + spec.setDb(db); + keycloak.setSpec(spec); + + verifyKeycloak(keycloak, true); + } finally { + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + POSTGRESQL_IMAGE_PROVISIONER.undeploy(); + POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); + } + } + + private void verifyKeycloak(Keycloak keycloak, boolean waitForPods) { + // create and verify that object exists + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().createOrReplace(keycloak); + KEYCLOAK_OPERATOR_PROVISIONER.waitFor(keycloak); + // two pods expected keycloak-0 and keycloak-postgresql-*, keycloak-0 won't start unless keycloak-postgresql-* is ready + if (waitForPods) { + OpenShiftWaiters.get(OpenShifts.master(), () -> false) + .areExactlyNPodsReady(keycloak.getSpec().getInstances().intValue(), "app", keycloak.getKind().toLowerCase()) + .level(Level.DEBUG).waitFor(); + log.debug(KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getStatus().toString()); + } + Assertions.assertEquals(keycloak.getSpec().getHostname().getHostname(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getHostname().getHostname()); + if (!Objects.isNull(keycloak.getSpec().getDb())) { + Assertions.assertEquals(keycloak.getSpec().getDb().getHost(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getHost()); + Assertions.assertEquals(keycloak.getSpec().getDb().getDatabase(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getDatabase()); + Assertions.assertEquals(keycloak.getSpec().getDb().getUsernameSecret().getName(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getUsernameSecret() + .getName()); + Assertions.assertEquals(keycloak.getSpec().getDb().getUsernameSecret().getKey(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getUsernameSecret() + .getKey()); + Assertions.assertEquals(keycloak.getSpec().getDb().getPasswordSecret().getName(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getPasswordSecret() + .getName()); + Assertions.assertEquals(keycloak.getSpec().getDb().getPasswordSecret().getKey(), + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getPasswordSecret() + .getKey()); + } + + // delete and verify that object was removed + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) + .delete(); + new SimpleWaiter(() -> KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().list().getItems().size() == 0).level(Level.DEBUG) + .waitFor(); + if (waitForPods) { + OpenShiftWaiters.get(OpenShifts.master(), () -> false) + .areExactlyNPodsReady(0, "app", keycloak.getKind().toLowerCase()).level(Level.DEBUG).waitFor(); + } + } +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java index d2985301b..50338b34f 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java @@ -43,12 +43,18 @@ public class IntersmashConfig { private static final String PRODUCT_INFINISPAN_OPERATOR_PACKAGE_MANIFEST = "datagrid"; private static final String DEFAULT_INFINISPAN_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_INFINISPAN_OPERATOR_PACKAGE_MANIFEST; private static final String KEYCLOAK_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.keycloak.operators.catalog_source"; + private static final String KEYCLOAK_QUARKUS_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.keycloak.quarkus.operators.catalog_source"; + private static final String KEYCLOAK_OPERATOR_INDEX_IMAGE = "intersmash.keycloak.operators.index_image"; + private static final String KEYCLOAK_QUARKUS_OPERATOR_INDEX_IMAGE = "intersmash.keycloak.quarkus.operators.index_image"; private static final String KEYCLOAK_OPERATOR_CHANNEL = "intersmash.keycloak.operators.channel"; + private static final String KEYCLOAK_QUARKUS_OPERATOR_CHANNEL = "intersmash.keycloak.quarkus.operators.channel"; private static final String KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "intersmash.keycloak.operators.package_manifest"; + private static final String KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST = "intersmash.keycloak.quarkus.operators.package_manifest"; private static final String COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "keycloak-operator"; private static final String PRODUCT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "rhsso-operator"; private static final String DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST; + private static final String DEFAULT_KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST; private static final String WILDFLY_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.wildfly.operators.catalog_source"; private static final String WILDFLY_OPERATOR_INDEX_IMAGE = "intersmash.wildfly.operators.index_image"; private static final String WILDFLY_OPERATOR_CHANNEL = "intersmash.wildfly.operators.channel"; @@ -101,6 +107,8 @@ public class IntersmashConfig { // KEYCLOAK private static final String KEYCLOAK_IMAGE_URL = "intersmash.keycloak.image"; + private static final String KEYCLOAK_QUARKUS_IMAGE_URL = "intersmash.keycloak.quarks.image"; + private static final String KEYCLOAK_TEMPLATES = "intersmash.keycloak.templates"; // ACTIVEMQ private static final String ACTIVEMQ_IMAGE_URL = "intersmash.activemq.image"; @@ -161,6 +169,10 @@ public static String keycloakOperatorPackageManifest() { return XTFConfig.get(KEYCLOAK_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST); } + public static String keycloakOperatorQuarkusPackageManifest() { + return XTFConfig.get(KEYCLOAK_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST); + } + public static String wildflyOperatorCatalogSource() { return XTFConfig.get(WILDFLY_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); } @@ -271,6 +283,10 @@ public static String keycloakImageURL() { return XTFConfig.get(KEYCLOAK_IMAGE_URL); } + public static String keycloakQuarkusImageURL() { + return XTFConfig.get(KEYCLOAK_QUARKUS_IMAGE_URL); + } + public static String keycloakProductCode() { return getProductCode(keycloakImageURL()); } @@ -376,4 +392,20 @@ public static String getWildflyHelmChartsRepo() { public static String getWildflyHelmChartsBranch() { return XTFConfig.get(WILDFLY_HELM_CHARTS_BRANCH); } + + public static String keycloakQuarkusOperatorCatalogSource() { + return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); + } + + public static String keycloakQuarkusOperatorIndexImage() { + return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_INDEX_IMAGE); + } + + public static String keycloakQuarkusOperatorChannel() { + return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_CHANNEL); + } + + public static String keycloakQuarkusOperatorPackageManifest() { + return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST); + } } diff --git a/tools/intersmash-tools-provisioners/pom.xml b/tools/intersmash-tools-provisioners/pom.xml index 6b1c62094..03f36a3cf 100644 --- a/tools/intersmash-tools-provisioners/pom.xml +++ b/tools/intersmash-tools-provisioners/pom.xml @@ -189,9 +189,13 @@ target/generated-sources + https://raw.githubusercontent.com/artemiscloud/activemq-artemis-operator/${version.intersmash.activemq.operators}/bundle/manifests/broker.amq.io_activemqartemises.yaml https://raw.githubusercontent.com/artemiscloud/activemq-artemis-operator/${version.intersmash.activemq.operators}/bundle/manifests/broker.amq.io_activemqartemisaddresses.yaml https://raw.githubusercontent.com/artemiscloud/activemq-artemis-operator/${version.intersmash.activemq.operators}/bundle/manifests/broker.amq.io_activemqartemisscaledowns.yaml + + https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/21.1.1/kubernetes/keycloaks.k8s.keycloak.org-v1.yml + https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/21.1.1/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml diff --git a/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java b/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java new file mode 100644 index 000000000..c01b92d94 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java @@ -0,0 +1,419 @@ +//TODO: remove once https://github.com/xtf-cz/xtf/pull/546 and https://github.com/xtf-cz/xtf/pull/547 are merged and xtf upgraded +package cz.xtf.builder.builders.pod; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +import cz.xtf.builder.builders.EnvironmentConfiguration; +import cz.xtf.builder.builders.PodBuilder; +import cz.xtf.builder.builders.deployment.AbstractProbe; +import cz.xtf.builder.builders.deployment.Handler; +import cz.xtf.builder.builders.deployment.LivenessProbe; +import cz.xtf.builder.builders.deployment.ReadinessProbe; +import cz.xtf.builder.builders.deployment.StartupProbe; +import cz.xtf.builder.builders.limits.CPUResource; +import cz.xtf.builder.builders.limits.ComputingResource; +import cz.xtf.builder.builders.limits.MemoryResource; +import cz.xtf.builder.builders.limits.ResourceLimitBuilder; +import cz.xtf.builder.builders.route.TransportProtocol; +import io.fabric8.kubernetes.api.model.ConfigMapKeySelectorBuilder; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerFluent.ResourcesNested; +import io.fabric8.kubernetes.api.model.ContainerPortBuilder; +import io.fabric8.kubernetes.api.model.EnvVar; +import io.fabric8.kubernetes.api.model.EnvVarSource; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; +import io.fabric8.kubernetes.api.model.VolumeMountBuilder; +import lombok.AllArgsConstructor; +import lombok.Getter; + +public class ContainerBuilder implements EnvironmentConfiguration, ResourceLimitBuilder { + private final PodBuilder pod; + private final String name; + + private final Map envVars = new HashMap<>(); + private final Map referredEnvVars = new HashMap<>(); + private final Set ports = new HashSet<>(); + private final Set volumeMounts = new HashSet<>(); + private String imageName; + private String imageNamespace; + private boolean privileged = false; + private AbstractProbe livenessProbe; + private AbstractProbe readinessProbe; + private StartupProbe startupProbe; + private Handler preStopHandler; + private String[] command; + + private Map computingResources = new HashMap<>(); + + public ContainerBuilder(PodBuilder podBuilder, String name) { + if (podBuilder == null) { + throw new IllegalArgumentException("PodBuilder must not be null"); + } + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Name must not be null nor empty"); + } + this.pod = podBuilder; + this.name = name; + } + + public String getName() { + return name; + } + + public ContainerBuilder fromImage(String imageName) { + this.imageName = imageName; + return this; + } + + public ContainerBuilder fromImage(String imageNamespace, String imageName) { + this.imageNamespace = imageNamespace; + this.imageName = imageName; + return this; + } + + public String getImageName() { + return imageName; + } + + public String getImageNamespace() { + return imageNamespace; + } + + public ContainerBuilder port(int port) { + return port(port, null, null); + } + + public ContainerBuilder port(int port, String name) { + return port(port, null, name); + } + + public ContainerBuilder port(int port, TransportProtocol protocol, String name) { + ports.add(new ContainerPort(port, protocol, name)); + return this; + } + + public ContainerBuilder envVar(String key, String value) { + return configEntry(key, value); + } + + public ContainerBuilder envVars(Map vars) { + return (ContainerBuilder) configEntries(vars); + } + + public ContainerBuilder cleanEnvVars() { + envVars.clear(); + return this; + } + + public Map getEnvVars() { + return Collections.unmodifiableMap(envVars); + } + + public ContainerBuilder privileged() { + this.privileged = true; + return this; + } + + public ContainerBuilder addVolumeMount(String name, String mountPath, boolean readOnly) { + this.volumeMounts.add(new VolumeMount(name, mountPath, readOnly)); + return this; + } + + public ContainerBuilder addVolumeMount(String name, String mountPath, boolean readOnly, String subPath) { + this.volumeMounts.add(new VolumeMount(name, mountPath, readOnly, subPath)); + return this; + } + + public ContainerBuilder addVolumeMount(VolumeMount volumeMount) { + this.volumeMounts.add(volumeMount); + return this; + } + + public LivenessProbe addLivenessProbe() { + this.livenessProbe = new LivenessProbe(); + return (LivenessProbe) this.livenessProbe; + } + + public ReadinessProbe addReadinessProbe() { + this.readinessProbe = new ReadinessProbe(); + return (ReadinessProbe) this.readinessProbe; + } + + public StartupProbe addStartupProbe() { + this.startupProbe = new StartupProbe(); + return (StartupProbe) this.startupProbe; + } + + public ContainerBuilder addReadinessProbe(AbstractProbe readinessProbe) { + this.readinessProbe = readinessProbe; + return this; + } + + public ContainerBuilder addCommand(String... cmd) { + this.command = cmd; + return this; + } + + public PodBuilder pod() { + return pod; + } + + public Container build() { + io.fabric8.kubernetes.api.model.ContainerBuilder builder = new io.fabric8.kubernetes.api.model.ContainerBuilder(); + + Stream definedVars = envVars.entrySet().stream() + .map(entry -> new EnvVar(entry.getKey(), entry.getValue(), null)); + Stream referredVars = referredEnvVars.entrySet().stream() + .map(entry -> (entry instanceof ConfigMapEntry) ? new EnvVar(entry.getKey(), null, + new EnvVarSource( + new ConfigMapKeySelectorBuilder() + .withKey(entry.getValue().getKey()) + .withName(entry.getValue().getValue()) + .build(), + null, + null, + null)) + : new EnvVar(entry.getKey(), null, + new EnvVarSource( + null, + null, + null, + new SecretKeySelectorBuilder() + .withKey(entry.getValue().getKey()) + .withName(entry.getValue().getValue()) + .build()))); + builder.withEnv(Stream.concat(definedVars, referredVars).collect(Collectors.toList())); + builder.withImage(imageName); + builder.withImagePullPolicy("Always"); + + if (command != null) { + builder.withCommand(command); + } + + if (livenessProbe != null) { + builder.withLivenessProbe(livenessProbe.build()); + } + + builder.withName(name); + + builder.withPorts(ports.stream().map(port -> { + ContainerPortBuilder portBuilder = new ContainerPortBuilder(); + portBuilder.withContainerPort(port.getContainerPort()); + if (port.getProtocol() != null) { + portBuilder.withProtocol(port.getProtocol().uppercase()); + } + if (port.getName() != null) { + portBuilder.withName(port.getName()); + } + + return portBuilder.build(); + }).collect(Collectors.toList())); + + if (preStopHandler != null) { + builder.withNewLifecycle() + .withPreStop(preStopHandler.build()) + .endLifecycle(); + } + + if (privileged) { + builder.withNewSecurityContext().withPrivileged(true).endSecurityContext(); + } + + if (readinessProbe != null) { + builder.withReadinessProbe(readinessProbe.build()); + } + + if (startupProbe != null) { + builder.withStartupProbe(startupProbe.build()); + } + + builder.withVolumeMounts(volumeMounts.stream().map(item -> new VolumeMountBuilder() + .withName(item.getName()) + .withMountPath(item.getMountPath()) + .withReadOnly(item.isReadOnly()) + .withSubPath(item.getSubPath()) + .build()).collect(Collectors.toList())); + + final List requests = computingResources.values().stream().filter(x -> x.getRequests() != null) + .collect(Collectors.toList()); + final List limits = computingResources.values().stream().filter(x -> x.getLimits() != null) + .collect(Collectors.toList()); + if (!requests.isEmpty() || !limits.isEmpty()) { + ResourcesNested resources = builder.withNewResources(); + if (!requests.isEmpty()) { + resources.withRequests( + requests.stream().collect(Collectors.toMap( + ComputingResource::resourceIdentifier, x -> new Quantity(x.getRequests())))); + } + if (!limits.isEmpty()) { + resources.withLimits( + limits.stream().collect(Collectors.toMap( + ComputingResource::resourceIdentifier, x -> new Quantity(x.getLimits())))); + } + resources.endResources(); + } + // args + // capabilities + // command + // lifecycle + // resources + // securityContext + // terminationMessagePath + // workingDir + return builder.build(); + } + + @Override + public ComputingResource addCPUResource() { + final ComputingResource r = new CPUResource(); + computingResources.put(r.resourceIdentifier(), r); + return r; + } + + @Override + public ComputingResource addMemoryResource() { + final ComputingResource r = new MemoryResource(); + computingResources.put(r.resourceIdentifier(), r); + return r; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o.getClass() == getClass())) + return false; + + ContainerBuilder that = (ContainerBuilder) o; + + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + public void addPreStopHandler(Handler handler) { + preStopHandler = handler; + } + + private static class ContainerPort { + private final int containerPort; + private final TransportProtocol protocol; + private String name; + + public ContainerPort(int containerPort, TransportProtocol protocol, String name) { + if (containerPort < 1 || containerPort > 65538) { + throw new IllegalArgumentException("Wrong port number"); + } + this.containerPort = containerPort; + this.protocol = protocol; + this.name = name; + } + + public int getContainerPort() { + return containerPort; + } + + public TransportProtocol getProtocol() { + return protocol; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof ContainerPort)) + return false; + + ContainerPort that = (ContainerPort) o; + + if (containerPort != that.containerPort) + return false; + return protocol == that.protocol; + + } + + @Override + public int hashCode() { + int result = containerPort; + result = 31 * result + (protocol != null ? protocol.hashCode() : 0); + return result; + } + } + + @Override + public ContainerBuilder configEntry(String key, String value) { + envVars.put(key, value); + return this; + } + + @Override + public Map getConfigEntries() { + return envVars; + } + + public ContainerBuilder configFromConfigMap(String configMapName, String... configMapKeys) { + return configFromConfigMap(configMapName, Arrays.asList(configMapKeys)); + } + + public ContainerBuilder configFromConfigMap(String configMapName, Collection configMapKeys) { + return configFromConfigMap(configMapName, Function.identity(), configMapKeys); + } + + public ContainerBuilder configFromConfigMap(String configMapName, Function nameMapping, + String... configMapKeys) { + return configFromConfigMap(configMapName, nameMapping, Arrays.asList(configMapKeys)); + } + + public ContainerBuilder configFromConfigMap(String configMapName, Function nameMapping, + Collection configMapKeys) { + configMapKeys.forEach(x -> referredEnvVars.put(nameMapping.apply(x), new ConfigMapEntry(x, configMapName))); + return this; + } + + public ContainerBuilder configFromSecret(String secretName, Function nameMapping, + Collection configMapKeys) { + configMapKeys.forEach(x -> referredEnvVars.put(nameMapping.apply(x), new SecretEntry(x, secretName))); + return this; + } + + @Getter + @AllArgsConstructor + private class Entry { + private String key; + private String value; + } + + @Getter + private class ConfigMapEntry extends Entry { + public ConfigMapEntry(String x, String configMapName) { + super(x, configMapName); + } + } + + @Getter + private class SecretEntry extends Entry { + public SecretEntry(String x, String secretName) { + super(x, secretName); + } + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java new file mode 100644 index 000000000..b32402ec6 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java @@ -0,0 +1,40 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.intersmash.tools.application.openshift; + +import java.util.Collections; +import java.util.List; + +import org.jboss.intersmash.tools.provision.openshift.KeycloakQuarkusOperatorProvisioner; +import org.keycloak.k8s.v2alpha1.Keycloak; +import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; + +/** + * End user Application interface which presents Keycloak operator application on OpenShift Container Platform. + * + * The application will be deployed by: + *

+ */ +public interface KeycloakQuarkusOperatorApplication extends OperatorApplication { + + Keycloak getKeycloak(); + + default List getKeycloakRealmImports() { + return Collections.emptyList(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/PostgreSQLImageOpenShiftApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/PostgreSQLImageOpenShiftApplication.java index 95d45052f..c1537a2f2 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/PostgreSQLImageOpenShiftApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/PostgreSQLImageOpenShiftApplication.java @@ -39,4 +39,7 @@ default String getName() { return "postgresql"; } + default String getAdminPassword() { + return "admin123"; + } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java index 6cea3f4b3..821bb1492 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java @@ -68,6 +68,9 @@ public Map getImageVariables() { return vars; } + public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { + } + public abstract String getSymbolicName(); @Override @@ -96,6 +99,8 @@ public void deploy() { .addContainerSelector("deploymentconfig", dbApplication.getName()) .addContainerSelector("app", dbApplication.getName()); + customizeApplicationBuilder(appBuilder); + appBuilder.buildApplication(openShift).deploy(); OpenShiftWaiters.get(openShift, ffCheck).isDcReady(appBuilder.getName()).waitFor(); @@ -130,4 +135,20 @@ public URL getURL() { public String getUrl(String routeName, boolean secure) { throw new UnsupportedOperationException("Route is not created for DB applications."); } + + /** + * When using {@link ApplicationBuilder} to build the application, then the service name defaults to the application nane + * @return service name to access the database + */ + public String getServiceName() { + return dbApplication.getName(); + } + + /** + * @return name of the secret containing username and password for the database + */ + public String getSecretName() { + return dbApplication.getName(); + } + } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java new file mode 100644 index 000000000..7533efc4d --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java @@ -0,0 +1,322 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.intersmash.tools.provision.openshift; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.assertj.core.util.Lists; +import org.assertj.core.util.Strings; +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.util.tls.CertificatesUtils; +import org.keycloak.k8s.v2alpha1.Keycloak; +import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; +import org.keycloak.k8s.v2alpha1.keycloakspec.Http; +import org.slf4j.event.Level; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.event.helpers.EventHelper; +import cz.xtf.core.openshift.OpenShiftWaiters; +import cz.xtf.core.waiting.SimpleWaiter; +import cz.xtf.core.waiting.failfast.FailFastCheck; +import io.fabric8.kubernetes.api.model.DeletionPropagation; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import lombok.NonNull; + +/** + * Keycloak operator provisioner + */ +public class KeycloakQuarkusOperatorProvisioner extends OperatorProvisioner { + + //private MixedOperation, Resource> keycloakRealmImportClient; + //private MixedOperation, Resource> keycloakClient; + private static final String OPERATOR_ID = IntersmashConfig.keycloakQuarkusOperatorPackageManifest(); + protected FailFastCheck ffCheck = () -> false; + + public KeycloakQuarkusOperatorProvisioner(@NonNull KeycloakQuarkusOperatorApplication application) { + super(application, OPERATOR_ID); + } + + public static String getOperatorId() { + return OPERATOR_ID; + } + + @Override + protected String getOperatorCatalogSource() { + return IntersmashConfig.keycloakQuarkusOperatorCatalogSource(); + } + + @Override + protected String getOperatorIndexImage() { + return IntersmashConfig.keycloakQuarkusOperatorIndexImage(); + } + + @Override + protected String getOperatorChannel() { + return IntersmashConfig.keycloakQuarkusOperatorChannel(); + } + + @Override + public void subscribe() { + if (Strings.isNullOrEmpty(IntersmashConfig.keycloakQuarkusImageURL())) { + super.subscribe(); + } else { + // RELATED_IMAGE_RHSSO_OPENJ9 and RELATED_IMAGE_RHSSO_OPENJDK, determine the final value for RELATED_IMAGE_RHSSO + subscribe( + INSTALLPLAN_APPROVAL_MANUAL, + // TODO: check if these env variables still make sense in the new quarkus operator + Map.of( + // Custom Keycloak image to be used: overrides the Keycloak image at the operator level: all + // Keycloak instances will be spun out of this image + // e.g. OPERATOR_KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:21.1.1 --> operator.keycloak.image + "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakQuarkusImageURL() + // "PROFILE", "RHSSO" + )); + } + } + + @Override + public void deploy() { + ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), + getApplication().getName()); + // Keycloak Operator codebase contains the name of the Keycloak image to deploy: user can override Keycloak image to + // deploy using environment variables in Keycloak Operator Subscription + subscribe(); + + // Custom Keycloak image to be used: overrides the Keycloak image at the Keycloak level: just this Keycloak + // instance will be spun out of this image + if (!Strings.isNullOrEmpty(IntersmashConfig.keycloakQuarkusImageURL())) { + getApplication().getKeycloak().getSpec().setImage(IntersmashConfig.keycloakQuarkusImageURL()); + } + + // create keys/certificates and add them to the Keycloak resource: + // TODO: https://www.keycloak.org/operator/basic-deployment or ~/projects/keycloak/docs/guides/operator/basic-deployment.adoc + if (getApplication().getKeycloak().getSpec().getHttp() == null + || getApplication().getKeycloak().getSpec().getHttp().getTlsSecret() == null) { + // create key, certificate and tls secret + String tlsSecretName = getApplication().getKeycloak().getMetadata().getName() + "-tls-secret"; + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(getApplication().getKeycloak().getSpec().getHostname().getHostname(), + tlsSecretName); + // add config to keycloak + if (getApplication().getKeycloak().getSpec().getHttp() == null) { + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + getApplication().getKeycloak().getSpec().setHttp(http); + } else { + getApplication().getKeycloak().getSpec().getHttp() + .setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + } + } + + // 1. check externalDatabase exists + if (getApplication().getKeycloak().getSpec().getDb() != null) { + // 2. Service "spec.db.host" must be installed beforehand + new SimpleWaiter(() -> OpenShiftProvisioner.openShift + .getService(getApplication().getKeycloak().getSpec().getDb().getHost()) != null) + .level(Level.DEBUG).waitFor(); + } + + // create custom resources + keycloakClient().createOrReplace(getApplication().getKeycloak()); + if (getApplication().getKeycloakRealmImports().size() > 0) + keycloakRealmImportClient() + .createOrReplace(getApplication().getKeycloakRealmImports().stream().toArray(KeycloakRealmImport[]::new)); + + // Wait for Keycloak (and PostgreSQL) to be ready + waitFor(getApplication().getKeycloak()); + // wait for all resources to be ready + waitForKeycloakResourceReadiness(); + // check that route is up, only if there's a valid external URL available + URL externalUrl = getURL(); + if ((getApplication().getKeycloak().getSpec().getInstances() > 0) && (externalUrl != null)) { + WaitersUtil.routeIsUp(externalUrl.toExternalForm()) + .level(Level.DEBUG) + .waitFor(); + } + } + + public void waitFor(Keycloak keycloak) { + Long replicas = keycloak.getSpec().getInstances(); + if (replicas > 0) { + // wait for >= 1 pods with label controller-revision-hash=keycloak-d86bb6ddc + String controllerRevisionHash = getStatefulSet().getStatus().getUpdateRevision(); + OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) + .areExactlyNPodsReady(replicas.intValue(), "controller-revision-hash", + controllerRevisionHash) + .waitFor(); + } + } + + private void waitForKeycloakResourceReadiness() { + new SimpleWaiter( + () -> keycloak().get().getStatus().getConditions().stream().anyMatch( + condition -> "Ready".equalsIgnoreCase(condition.getType()) + && condition.getStatus())) + .reason("Wait for Keycloak resource to be ready").level(Level.DEBUG).waitFor(); + if (getApplication().getKeycloakRealmImports().size() > 0) + new SimpleWaiter(() -> keycloakRealmImports().stream().allMatch( + realmImport -> realmImport.getStatus().getConditions().stream().anyMatch( + condition -> "Done".equalsIgnoreCase(condition.getType()) + && condition.getStatus()))) + .reason("Wait for KeycloakRealmImports to be done.").level(Level.DEBUG).waitFor(); + } + + /** + * Get a reference to keycloak object. Use get() to get the actual object, or null in case it does not + * exist on tested cluster. + * @return A concrete {@link Resource} instance representing the {@link org.jboss.intersmash.tools.provision.openshift.operator.keycloak.keycloak.Keycloak} resource definition + */ + public Resource keycloak() { + return keycloakClient().inNamespace(OpenShiftConfig.namespace()) + .withName(getApplication().getKeycloak().getMetadata().getName()); + } + + public List keycloakRealmImports() { + return keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()).list().getItems() + .stream().filter( + realm -> getApplication().getKeycloakRealmImports().stream().map( + ri -> ri.getMetadata().getName()) + .anyMatch(riName -> riName.equalsIgnoreCase(realm.getMetadata().getName()))) + .collect(Collectors.toList()); + } + + /** + * @return the underlying StatefulSet which provisions the cluster + */ + private StatefulSet getStatefulSet() { + String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); + StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + if (Objects.isNull(statefulSet)) { + throw new IllegalStateException(String.format( + "Impossible to find StatefulSet with name=\"%s\"!", + STATEFUL_SET_NAME)); + } + return statefulSet; + } + + @Override + public void undeploy() { + keycloakRealmImports() + .forEach( + keycloakRealm -> keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()) + .withName(keycloakRealm.getMetadata().getName()) + .withPropagationPolicy(DeletionPropagation.FOREGROUND) + .delete()); + new SimpleWaiter( + () -> keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()).list().getItems().size() == 0) + .reason("Wait for all keycloakRealmImports instances to be deleted.").level(Level.DEBUG).waitFor(); + keycloak().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); + new SimpleWaiter(() -> keycloakClient().inNamespace(OpenShiftConfig.namespace()).list().getItems().size() == 0) + .reason("Wait for Keycloak instances to be deleted.").level(Level.DEBUG).waitFor(); + + // wait for 0 pods + OpenShiftWaiters.get(OpenShiftProvisioner.openShift, () -> false) + .areExactlyNPodsReady(0, "app", getApplication().getKeycloak().getKind().toLowerCase()).level(Level.DEBUG) + .waitFor(); + unsubscribe(); + } + + @Override + public void scale(int replicas, boolean wait) { + String controllerRevisionHash = getStatefulSet().getStatus().getUpdateRevision(); + Keycloak tmpKeycloak = keycloak().get(); + Long originalReplicas = tmpKeycloak.getSpec().getInstances(); + tmpKeycloak.getSpec().setInstances(Integer.toUnsignedLong(replicas)); + keycloak().replace(tmpKeycloak); + if (wait) { + OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) + .areExactlyNPodsReady(replicas, "controller-revision-hash", controllerRevisionHash) + .level(Level.DEBUG) + .waitFor(); + } + new SimpleWaiter( + () -> keycloak().get().getStatus().getConditions().stream().anyMatch( + condition -> "Ready".equalsIgnoreCase(condition.getType()) + && condition.getStatus())) + .reason("Wait for Keycloak resource to be ready").level(Level.DEBUG).waitFor(); + // check that route is up + if (originalReplicas == 0 && replicas > 0) { + WaitersUtil.routeIsUp(getURL().toExternalForm()) + .level(Level.DEBUG) + .waitFor(); + } + } + + @Override + public List getPods() { + String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); + StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + return Objects.nonNull(statefulSet) + ? OpenShiftProvisioner.openShift.getLabeledPods("controller-revision-hash", + statefulSet.getStatus().getUpdateRevision()) + : Lists.emptyList(); + } + + @Override + public URL getURL() { + String host = OpenShiftProvisioner.openShift.routes().list().getItems() + .stream().filter( + route -> route.getMetadata().getName().startsWith( + keycloak().get().getMetadata().getName()) + && + route.getMetadata().getLabels().entrySet() + .stream().filter( + label -> label.getKey().equalsIgnoreCase("app") + && + label.getValue().equalsIgnoreCase( + keycloak().get().getMetadata().getLabels().get("app"))) + .count() == 1 + + ).findFirst() + .orElseThrow(() -> new RuntimeException( + String.format("No route for Keycloak %s!", keycloak().get().getMetadata().getName()))) + .getSpec().getHost(); + try { + return Strings.isNullOrEmpty(host) ? null : new URL(String.format("https://%s", host)); + } catch (MalformedURLException e) { + throw new RuntimeException(String.format("Keycloak operator External URL \"%s\" is malformed.", host), e); + } + } + + public MixedOperation, Resource> keycloakClient() { + try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { + MixedOperation, Resource> keycloakClient = kubernetesClient + .resources(Keycloak.class); + return keycloakClient; + } + } + + public MixedOperation, Resource> keycloakRealmImportClient() { + try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { + MixedOperation, Resource> keycloakRealmImportClient = kubernetesClient + .resources(KeycloakRealmImport.class); + return keycloakRealmImportClient; + } + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java new file mode 100644 index 000000000..76b90a45a --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jboss.intersmash.tools.provision.openshift; + +import org.jboss.intersmash.tools.application.Application; +import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.provision.ProvisionerFactory; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class KeycloakQuarkusOperatorProvisionerFactory implements ProvisionerFactory { + + @Override + public KeycloakQuarkusOperatorProvisioner getProvisioner(Application application) { + if (KeycloakQuarkusOperatorApplication.class.isAssignableFrom(application.getClass())) + return new KeycloakQuarkusOperatorProvisioner((KeycloakQuarkusOperatorApplication) application); + return null; + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java index 0c578dbd0..8365c08b1 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java @@ -20,12 +20,22 @@ import org.jboss.intersmash.tools.IntersmashConfig; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; +import cz.xtf.builder.builders.ApplicationBuilder; import cz.xtf.builder.builders.pod.ContainerBuilder; +import cz.xtf.builder.builders.secret.SecretType; import lombok.extern.slf4j.Slf4j; @Slf4j public class PostgreSQLImageOpenShiftProvisioner extends DBImageOpenShiftProvisioner { + public static final String POSTGRESQL_USER = "POSTGRESQL_USER"; + public static final String POSTGRESQL_PASSWORD = "POSTGRESQL_PASSWORD"; + public static final String POSTGRESQL_ADMIN_PASSWORD = "POSTGRESQL_ADMIN_PASSWORD"; + + public static final String POSTGRESQL_USER_KEY = POSTGRESQL_USER.replace("_", "-").toLowerCase(); + public static final String POSTGRESQL_PASSWORD_KEY = POSTGRESQL_PASSWORD.replace("_", "-").toLowerCase(); + public static final String POSTGRESQL_ADMIN_PASSWORD_KEY = POSTGRESQL_ADMIN_PASSWORD.replace("_", "-").toLowerCase(); + public PostgreSQLImageOpenShiftProvisioner(PostgreSQLImageOpenShiftApplication pgSQLApplication) { super(pgSQLApplication); } @@ -60,6 +70,10 @@ protected void configureContainer(ContainerBuilder containerBuilder) { @Override public Map getImageVariables() { Map vars = super.getImageVariables(); + vars.remove(POSTGRESQL_USER); + vars.remove(POSTGRESQL_PASSWORD); + vars.remove(POSTGRESQL_ADMIN_PASSWORD); + vars.remove("POSTGRESQL_USERNAME"); vars.put("POSTGRESQL_MAX_CONNECTIONS", "100"); vars.put("POSTGRESQL_SHARED_BUFFERS", "16MB"); vars.put("POSTGRESQL_MAX_PREPARED_TRANSACTIONS", "90"); @@ -69,4 +83,28 @@ public Map getImageVariables() { vars.put("PGCTLTIMEOUT", "300"); return vars; } + + @Override + public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { + //TODO: use the secret and remove the configMap once xtf has been fixed + + // the secret is supposed to be used by applications connecting to the database + appBuilder.secret(dbApplication.getName()) + .setType(SecretType.OPAQUE) + .addData(POSTGRESQL_USER_KEY, dbApplication.getUser().getBytes()) + .addData(POSTGRESQL_PASSWORD_KEY, dbApplication.getPassword().getBytes()) + .addData(POSTGRESQL_ADMIN_PASSWORD_KEY, + dbApplication.getAdminPassword().getBytes()); + // configMap is temporarily supposed to be used by database POD + /*appBuilder.configMap(dbApplication.getName() + "-tmp") + .configEntry(DATABASE_USER_KEY, dbApplication.getUser()) + .configEntry(DATABASE_PASSWORD_KEY, dbApplication.getPassword()) + .configEntry(DATABASE_ADMIN_PASSWORD_KEY, dbApplication.getAdminPassword());*/ + appBuilder.deploymentConfig().podTemplate().container().configFromConfigMap( + dbApplication.getName(), + (String t) -> t.replace("-", "_").toUpperCase(), + POSTGRESQL_USER_KEY, + POSTGRESQL_PASSWORD_KEY, + POSTGRESQL_ADMIN_PASSWORD_KEY); + } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java new file mode 100644 index 000000000..251d11025 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java @@ -0,0 +1,138 @@ +package org.jboss.intersmash.tools.util.tls; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CertificatesUtils { + + @Getter + private static Path caDir; + + static { + try { + caDir = Files.createTempDirectory(CertificatesUtils.class.getSimpleName().toLowerCase()); + } catch (IOException e) { + throw new RuntimeException("Failed to create temp directory!"); + } + } + + public static class CertificateAndKey { + public Path certificate; + public Path key; + public Path truststore; + public String truststorePassword; + public String truststoreAlias; + public boolean existing = false; + public Secret tlsSecret; + } + + /** + * Generates a key and self-signed certificate for that key; it also generates a truststore containing the certificate; + * @return + */ + public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostname, String tlsSecretName) { + CertificateAndKey certificateAndKey = new CertificateAndKey(); + + String certificate = hostname + "-certificate.pem"; + String key = hostname + "-key.pem"; + String truststoreFormat = "jks"; + String truststorePassword = "1234PIPPOBAUDO"; + String truststore = hostname + "-truststore." + truststoreFormat; + + certificateAndKey.key = Paths.get(caDir.toFile().getAbsolutePath(), key); + certificateAndKey.certificate = Paths.get(caDir.toFile().getAbsolutePath(), certificate); + certificateAndKey.truststore = Paths.get(caDir.toFile().getAbsolutePath(), truststore); + certificateAndKey.truststoreAlias = hostname; + certificateAndKey.truststorePassword = truststorePassword; + + if (caDir.resolve(certificate).toFile().exists() && + caDir.resolve(key).toFile().exists() && + caDir.resolve(truststore).toFile().exists()) { + certificateAndKey.existing = true; + Secret tlsSecret = OpenShifts.master().getSecret(tlsSecretName); + Assertions.assertNotNull(tlsSecret); + certificateAndKey.tlsSecret = tlsSecret; + return certificateAndKey; + } + + // create key + self-signed certificate: they are typically used by the server exposing the endpoints over TLS + processCall(caDir, "openssl", "req", "-subj", "/CN=" + hostname + "/OU=TF/O=XTF/L=Milan/C=IT", + "-newkey", "rsa:2048", "-nodes", "-keyout", key, "-x509", "-days", "365", "-out", certificate); + + // add self-signed certificate to keystore: it's typically used by the clients contacting the endpoints over TLS + processCall(caDir, "keytool", "-import", "-noprompt", "-alias", hostname, "-keystore", + truststore, "-file", certificate, "-storetype", "JKS", "-storepass", truststorePassword); + + // create secret + try { + Secret tlsSecret = createTlsSecret(tlsSecretName, certificateAndKey.key, certificateAndKey.certificate); + Assertions.assertNotNull(tlsSecret); + certificateAndKey.tlsSecret = tlsSecret; + } catch (IOException e) { + throw new RuntimeException("Failed to create secret " + tlsSecretName, e); + } + + return certificateAndKey; + } + + private static void processCall(Path cwd, String... args) { + ProcessBuilder pb = new ProcessBuilder(args); + + pb.directory(cwd.toFile()); + pb.redirectErrorStream(true); + + int result = -1; + Process process = null; + try { + process = pb.start(); + result = process.waitFor(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + while (reader.ready()) { + if (result == 0) { + log.debug(reader.readLine()); + } else { + log.error(reader.readLine()); + } + } + } + } catch (IOException | InterruptedException e) { + throw new IllegalStateException("Failed executing " + String.join(" ", args)); + } + + if (result != 0) { + throw new IllegalStateException("Failed executing " + String.join(" ", args)); + } + } + + public static Secret createTlsSecret(String secretName, Path key, Path certificate) throws IOException { + Map data = new HashMap<>(); + String keyDerData = Files.readString(key); + String crtDerData = Files.readString(certificate); + data.put("tls.key", Base64.getEncoder().encodeToString(keyDerData.getBytes())); + data.put("tls.crt", Base64.getEncoder().encodeToString(crtDerData.getBytes())); + final Secret secret = new SecretBuilder() + .withNewMetadata().withName(secretName).endMetadata() + .withType("kubernetes.io/tls") + .withImmutable(false) + .addToData(data) + .build(); + return OpenShifts.master().secrets().inNamespace(OpenShiftConfig.namespace()).createOrReplace(secret); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloakrealmimports.k8s.keycloak.org.yaml b/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloakrealmimports.k8s.keycloak.org.yaml new file mode 100644 index 000000000..ce95a5d51 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloakrealmimports.k8s.keycloak.org.yaml @@ -0,0 +1,2277 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + operatorframework.io/installed-alongside-20040cf531a3cb53: eap7-1928/keycloak-operator.v21.1.1 + creationTimestamp: "2023-05-11T15:48:37Z" + generation: 1 + labels: + operators.coreos.com/keycloak-operator.eap7-1928: "" + name: keycloakrealmimports.k8s.keycloak.org + resourceVersion: "11420423" + uid: 84d6611e-e4c5-45a6-bb6d-309fdcea3072 +spec: + conversion: + strategy: None + group: k8s.keycloak.org + names: + kind: KeycloakRealmImport + listKind: KeycloakRealmImportList + plural: keycloakrealmimports + singular: keycloakrealmimport + scope: Namespaced + versions: + - name: v2alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + keycloakCRName: + description: The name of the Keycloak CR to reference, in the same + namespace. + type: string + realm: + description: The RealmRepresentation to import into Keycloak. + properties: + accessCodeLifespan: + type: integer + accessCodeLifespanLogin: + type: integer + accessCodeLifespanUserAction: + type: integer + accessTokenLifespan: + type: integer + accessTokenLifespanForImplicitFlow: + type: integer + accountTheme: + type: string + actionTokenGeneratedByAdminLifespan: + type: integer + actionTokenGeneratedByUserLifespan: + type: integer + adminEventsDetailsEnabled: + type: boolean + adminEventsEnabled: + type: boolean + adminTheme: + type: string + applicationScopeMappings: + additionalProperties: + items: + properties: + client: + type: string + clientScope: + type: string + clientTemplate: + type: string + roles: + items: + type: string + type: array + self: + type: string + type: object + type: array + type: object + applications: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + adminUrl: + type: string + alwaysDisplayInConsole: + type: boolean + attributes: + additionalProperties: + type: string + type: object + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + authorizationServicesEnabled: + type: boolean + authorizationSettings: + properties: + allowRemoteResourceManagement: + type: boolean + clientId: + type: string + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + id: + type: string + name: + type: string + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + description: + type: string + id: + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + name: + type: string + owner: + type: string + policies: + items: + type: string + type: array + resources: + items: + type: string + type: array + resourcesData: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + type: string + type: array + scopesData: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + type: object + type: array + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + resources: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: object + baseUrl: + type: string + bearerOnly: + type: boolean + claims: + properties: + address: + type: boolean + email: + type: boolean + gender: + type: boolean + locale: + type: boolean + name: + type: boolean + phone: + type: boolean + picture: + type: boolean + profile: + type: boolean + username: + type: boolean + website: + type: boolean + type: object + clientAuthenticatorType: + type: string + clientId: + type: string + clientTemplate: + type: string + consentRequired: + type: boolean + defaultClientScopes: + items: + type: string + type: array + defaultRoles: + items: + type: string + type: array + description: + type: string + directAccessGrantsEnabled: + type: boolean + directGrantsOnly: + type: boolean + enabled: + type: boolean + frontchannelLogout: + type: boolean + fullScopeAllowed: + type: boolean + id: + type: string + implicitFlowEnabled: + type: boolean + name: + type: string + nodeReRegistrationTimeout: + type: integer + notBefore: + type: integer + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + origin: + type: string + protocol: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + publicClient: + type: boolean + redirectUris: + items: + type: string + type: array + registeredNodes: + additionalProperties: + type: integer + type: object + registrationAccessToken: + type: string + rootUrl: + type: string + secret: + type: string + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + surrogateAuthRequired: + type: boolean + useTemplateConfig: + type: boolean + useTemplateMappers: + type: boolean + useTemplateScope: + type: boolean + webOrigins: + items: + type: string + type: array + type: object + type: array + attributes: + additionalProperties: + type: string + type: object + authenticationFlows: + items: + properties: + alias: + type: string + authenticationExecutions: + items: + properties: + authenticator: + type: string + authenticatorConfig: + type: string + authenticatorFlow: + type: boolean + autheticatorFlow: + type: boolean + flowAlias: + type: string + priority: + type: integer + requirement: + type: string + userSetupAllowed: + type: boolean + type: object + type: array + builtIn: + type: boolean + description: + type: string + id: + type: string + providerId: + type: string + topLevel: + type: boolean + type: object + type: array + authenticatorConfig: + items: + properties: + alias: + type: string + config: + additionalProperties: + type: string + type: object + id: + type: string + type: object + type: array + browserFlow: + type: string + browserSecurityHeaders: + additionalProperties: + type: string + type: object + bruteForceProtected: + type: boolean + certificate: + type: string + clientAuthenticationFlow: + type: string + clientOfflineSessionIdleTimeout: + type: integer + clientOfflineSessionMaxLifespan: + type: integer + clientPolicies: + type: object + x-kubernetes-preserve-unknown-fields: true + clientProfiles: + type: object + x-kubernetes-preserve-unknown-fields: true + clientScopeMappings: + additionalProperties: + items: + properties: + client: + type: string + clientScope: + type: string + clientTemplate: + type: string + roles: + items: + type: string + type: array + self: + type: string + type: object + type: array + type: object + clientScopes: + items: + properties: + attributes: + additionalProperties: + type: string + type: object + description: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + type: object + type: array + clientSessionIdleTimeout: + type: integer + clientSessionMaxLifespan: + type: integer + clientTemplates: + items: + properties: + attributes: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + consentRequired: + type: boolean + description: + type: string + directAccessGrantsEnabled: + type: boolean + frontchannelLogout: + type: boolean + fullScopeAllowed: + type: boolean + id: + type: string + implicitFlowEnabled: + type: boolean + name: + type: string + protocol: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + publicClient: + type: boolean + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + type: object + type: array + clients: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + adminUrl: + type: string + alwaysDisplayInConsole: + type: boolean + attributes: + additionalProperties: + type: string + type: object + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + authorizationServicesEnabled: + type: boolean + authorizationSettings: + properties: + allowRemoteResourceManagement: + type: boolean + clientId: + type: string + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + id: + type: string + name: + type: string + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + description: + type: string + id: + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + name: + type: string + owner: + type: string + policies: + items: + type: string + type: array + resources: + items: + type: string + type: array + resourcesData: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + type: string + type: array + scopesData: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + type: object + type: array + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + resources: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: object + baseUrl: + type: string + bearerOnly: + type: boolean + clientAuthenticatorType: + type: string + clientId: + type: string + clientTemplate: + type: string + consentRequired: + type: boolean + defaultClientScopes: + items: + type: string + type: array + defaultRoles: + items: + type: string + type: array + description: + type: string + directAccessGrantsEnabled: + type: boolean + directGrantsOnly: + type: boolean + enabled: + type: boolean + frontchannelLogout: + type: boolean + fullScopeAllowed: + type: boolean + id: + type: string + implicitFlowEnabled: + type: boolean + name: + type: string + nodeReRegistrationTimeout: + type: integer + notBefore: + type: integer + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + origin: + type: string + protocol: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + publicClient: + type: boolean + redirectUris: + items: + type: string + type: array + registeredNodes: + additionalProperties: + type: integer + type: object + registrationAccessToken: + type: string + rootUrl: + type: string + secret: + type: string + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + surrogateAuthRequired: + type: boolean + useTemplateConfig: + type: boolean + useTemplateMappers: + type: boolean + useTemplateScope: + type: boolean + webOrigins: + items: + type: string + type: array + type: object + type: array + codeSecret: + type: string + components: + additionalProperties: + items: + properties: + config: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + name: + type: string + providerId: + type: string + subComponents: + additionalProperties: + items: + properties: + config: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + name: + type: string + providerId: + type: string + subType: + type: string + type: object + type: array + type: object + subType: + type: string + type: object + type: array + type: object + defaultDefaultClientScopes: + items: + type: string + type: array + defaultGroups: + items: + type: string + type: array + defaultLocale: + type: string + defaultOptionalClientScopes: + items: + type: string + type: array + defaultRole: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRole: + type: boolean + composite: + type: boolean + composites: + properties: + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + realm: + items: + type: string + type: array + type: object + containerId: + type: string + description: + type: string + id: + type: string + name: + type: string + scopeParamRequired: + type: boolean + type: object + defaultRoles: + items: + type: string + type: array + defaultSignatureAlgorithm: + type: string + directGrantFlow: + type: string + displayName: + type: string + displayNameHtml: + type: string + dockerAuthenticationFlow: + type: string + duplicateEmailsAllowed: + type: boolean + editUsernameAllowed: + type: boolean + emailTheme: + type: string + enabled: + type: boolean + enabledEventTypes: + items: + type: string + type: array + eventsEnabled: + type: boolean + eventsExpiration: + type: integer + eventsListeners: + items: + type: string + type: array + failureFactor: + type: integer + federatedUsers: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientConsents: + items: + properties: + clientId: + type: string + createdDate: + type: integer + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + createdTimestamp: + type: integer + credentials: + items: + properties: + algorithm: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + counter: + type: integer + createdDate: + type: integer + credentialData: + type: string + device: + type: string + digits: + type: integer + hashIterations: + type: integer + hashedSaltedValue: + type: string + id: + type: string + period: + type: integer + priority: + type: integer + salt: + type: string + secretData: + type: string + temporary: + type: boolean + type: + type: string + userLabel: + type: string + value: + type: string + type: object + type: array + disableableCredentialTypes: + items: + type: string + type: array + email: + type: string + emailVerified: + type: boolean + enabled: + type: boolean + federatedIdentities: + items: + properties: + identityProvider: + type: string + userId: + type: string + userName: + type: string + type: object + type: array + federationLink: + type: string + firstName: + type: string + groups: + items: + type: string + type: array + id: + type: string + lastName: + type: string + notBefore: + type: integer + origin: + type: string + realmRoles: + items: + type: string + type: array + requiredActions: + items: + type: string + type: array + self: + type: string + serviceAccountClientId: + type: string + socialLinks: + items: + properties: + socialProvider: + type: string + socialUserId: + type: string + socialUsername: + type: string + type: object + type: array + totp: + type: boolean + username: + type: string + type: object + type: array + groups: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + name: + type: string + path: + type: string + realmRoles: + items: + type: string + type: array + subGroups: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + name: + type: string + path: + type: string + realmRoles: + items: + type: string + type: array + type: object + type: array + type: object + type: array + id: + type: string + identityProviderMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + identityProviderAlias: + type: string + identityProviderMapper: + type: string + name: + type: string + type: object + type: array + identityProviders: + items: + properties: + addReadTokenRoleOnCreate: + type: boolean + alias: + type: string + authenticateByDefault: + type: boolean + config: + additionalProperties: + type: string + type: object + displayName: + type: string + enabled: + type: boolean + firstBrokerLoginFlowAlias: + type: string + internalId: + type: string + linkOnly: + type: boolean + postBrokerLoginFlowAlias: + type: string + providerId: + type: string + storeToken: + type: boolean + trustEmail: + type: boolean + updateProfileFirstLoginMode: + type: string + type: object + type: array + internationalizationEnabled: + type: boolean + keycloakVersion: + type: string + loginTheme: + type: string + loginWithEmailAllowed: + type: boolean + maxDeltaTimeSeconds: + type: integer + maxFailureWaitSeconds: + type: integer + minimumQuickLoginWaitSeconds: + type: integer + notBefore: + type: integer + oauth2DeviceCodeLifespan: + type: integer + oauth2DevicePollingInterval: + type: integer + oauthClients: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + adminUrl: + type: string + alwaysDisplayInConsole: + type: boolean + attributes: + additionalProperties: + type: string + type: object + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + authorizationServicesEnabled: + type: boolean + authorizationSettings: + properties: + allowRemoteResourceManagement: + type: boolean + clientId: + type: string + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + id: + type: string + name: + type: string + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + description: + type: string + id: + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + name: + type: string + owner: + type: string + policies: + items: + type: string + type: array + resources: + items: + type: string + type: array + resourcesData: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + type: string + type: array + scopesData: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + type: object + type: array + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + resources: + items: + properties: + _id: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + icon_uri: + type: string + name: + type: string + owner: + properties: + id: + type: string + name: + type: string + type: object + ownerManagedAccess: + type: boolean + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: + type: string + uris: + items: + type: string + type: array + type: object + type: array + scopes: + items: + properties: + displayName: + type: string + iconUri: + type: string + id: + type: string + name: + type: string + type: object + type: array + type: object + baseUrl: + type: string + bearerOnly: + type: boolean + claims: + properties: + address: + type: boolean + email: + type: boolean + gender: + type: boolean + locale: + type: boolean + name: + type: boolean + phone: + type: boolean + picture: + type: boolean + profile: + type: boolean + username: + type: boolean + website: + type: boolean + type: object + clientAuthenticatorType: + type: string + clientId: + type: string + clientTemplate: + type: string + consentRequired: + type: boolean + defaultClientScopes: + items: + type: string + type: array + defaultRoles: + items: + type: string + type: array + description: + type: string + directAccessGrantsEnabled: + type: boolean + directGrantsOnly: + type: boolean + enabled: + type: boolean + frontchannelLogout: + type: boolean + fullScopeAllowed: + type: boolean + id: + type: string + implicitFlowEnabled: + type: boolean + name: + type: string + nodeReRegistrationTimeout: + type: integer + notBefore: + type: integer + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + origin: + type: string + protocol: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + publicClient: + type: boolean + redirectUris: + items: + type: string + type: array + registeredNodes: + additionalProperties: + type: integer + type: object + registrationAccessToken: + type: string + rootUrl: + type: string + secret: + type: string + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + surrogateAuthRequired: + type: boolean + useTemplateConfig: + type: boolean + useTemplateMappers: + type: boolean + useTemplateScope: + type: boolean + webOrigins: + items: + type: string + type: array + type: object + type: array + offlineSessionIdleTimeout: + type: integer + offlineSessionMaxLifespan: + type: integer + offlineSessionMaxLifespanEnabled: + type: boolean + otpPolicyAlgorithm: + type: string + otpPolicyCodeReusable: + type: boolean + otpPolicyDigits: + type: integer + otpPolicyInitialCounter: + type: integer + otpPolicyLookAheadWindow: + type: integer + otpPolicyPeriod: + type: integer + otpPolicyType: + type: string + otpSupportedApplications: + items: + type: string + type: array + passwordCredentialGrantAllowed: + type: boolean + passwordPolicy: + type: string + permanentLockout: + type: boolean + privateKey: + type: string + protocolMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + consentRequired: + type: boolean + consentText: + type: string + id: + type: string + name: + type: string + protocol: + type: string + protocolMapper: + type: string + type: object + type: array + publicKey: + type: string + quickLoginCheckMilliSeconds: + type: integer + realm: + type: string + realmCacheEnabled: + type: boolean + refreshTokenMaxReuse: + type: integer + registrationAllowed: + type: boolean + registrationEmailAsUsername: + type: boolean + registrationFlow: + type: string + rememberMe: + type: boolean + requiredActions: + items: + properties: + alias: + type: string + config: + additionalProperties: + type: string + type: object + defaultAction: + type: boolean + enabled: + type: boolean + name: + type: string + priority: + type: integer + providerId: + type: string + type: object + type: array + requiredCredentials: + items: + type: string + type: array + resetCredentialsFlow: + type: string + resetPasswordAllowed: + type: boolean + revokeRefreshToken: + type: boolean + roles: + properties: + application: + additionalProperties: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRole: + type: boolean + composite: + type: boolean + composites: + properties: + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + realm: + items: + type: string + type: array + type: object + containerId: + type: string + description: + type: string + id: + type: string + name: + type: string + scopeParamRequired: + type: boolean + type: object + type: array + type: object + client: + additionalProperties: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRole: + type: boolean + composite: + type: boolean + composites: + properties: + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + realm: + items: + type: string + type: array + type: object + containerId: + type: string + description: + type: string + id: + type: string + name: + type: string + scopeParamRequired: + type: boolean + type: object + type: array + type: object + realm: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientRole: + type: boolean + composite: + type: boolean + composites: + properties: + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + realm: + items: + type: string + type: array + type: object + containerId: + type: string + description: + type: string + id: + type: string + name: + type: string + scopeParamRequired: + type: boolean + type: object + type: array + type: object + scopeMappings: + items: + properties: + client: + type: string + clientScope: + type: string + clientTemplate: + type: string + roles: + items: + type: string + type: array + self: + type: string + type: object + type: array + smtpServer: + additionalProperties: + type: string + type: object + social: + type: boolean + socialProviders: + additionalProperties: + type: string + type: object + sslRequired: + type: string + ssoSessionIdleTimeout: + type: integer + ssoSessionIdleTimeoutRememberMe: + type: integer + ssoSessionMaxLifespan: + type: integer + ssoSessionMaxLifespanRememberMe: + type: integer + supportedLocales: + items: + type: string + type: array + updateProfileOnInitialSocialLogin: + type: boolean + userCacheEnabled: + type: boolean + userFederationMappers: + items: + properties: + config: + additionalProperties: + type: string + type: object + federationMapperType: + type: string + federationProviderDisplayName: + type: string + id: + type: string + name: + type: string + type: object + type: array + userFederationProviders: + items: + properties: + changedSyncPeriod: + type: integer + config: + additionalProperties: + type: string + type: object + displayName: + type: string + fullSyncPeriod: + type: integer + id: + type: string + lastSync: + type: integer + priority: + type: integer + providerName: + type: string + type: object + type: array + userManagedAccessAllowed: + type: boolean + users: + items: + properties: + access: + additionalProperties: + type: boolean + type: object + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + attributes: + additionalProperties: + items: + type: string + type: array + type: object + clientConsents: + items: + properties: + clientId: + type: string + createdDate: + type: integer + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + createdTimestamp: + type: integer + credentials: + items: + properties: + algorithm: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + counter: + type: integer + createdDate: + type: integer + credentialData: + type: string + device: + type: string + digits: + type: integer + hashIterations: + type: integer + hashedSaltedValue: + type: string + id: + type: string + period: + type: integer + priority: + type: integer + salt: + type: string + secretData: + type: string + temporary: + type: boolean + type: + type: string + userLabel: + type: string + value: + type: string + type: object + type: array + disableableCredentialTypes: + items: + type: string + type: array + email: + type: string + emailVerified: + type: boolean + enabled: + type: boolean + federatedIdentities: + items: + properties: + identityProvider: + type: string + userId: + type: string + userName: + type: string + type: object + type: array + federationLink: + type: string + firstName: + type: string + groups: + items: + type: string + type: array + id: + type: string + lastName: + type: string + notBefore: + type: integer + origin: + type: string + realmRoles: + items: + type: string + type: array + requiredActions: + items: + type: string + type: array + self: + type: string + serviceAccountClientId: + type: string + socialLinks: + items: + properties: + socialProvider: + type: string + socialUserId: + type: string + socialUsername: + type: string + type: object + type: array + totp: + type: boolean + username: + type: string + type: object + type: array + verifyEmail: + type: boolean + waitIncrementSeconds: + type: integer + webAuthnPolicyAcceptableAaguids: + items: + type: string + type: array + webAuthnPolicyAttestationConveyancePreference: + type: string + webAuthnPolicyAuthenticatorAttachment: + type: string + webAuthnPolicyAvoidSameAuthenticatorRegister: + type: boolean + webAuthnPolicyCreateTimeout: + type: integer + webAuthnPolicyPasswordlessAcceptableAaguids: + items: + type: string + type: array + webAuthnPolicyPasswordlessAttestationConveyancePreference: + type: string + webAuthnPolicyPasswordlessAuthenticatorAttachment: + type: string + webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister: + type: boolean + webAuthnPolicyPasswordlessCreateTimeout: + type: integer + webAuthnPolicyPasswordlessRequireResidentKey: + type: string + webAuthnPolicyPasswordlessRpEntityName: + type: string + webAuthnPolicyPasswordlessRpId: + type: string + webAuthnPolicyPasswordlessSignatureAlgorithms: + items: + type: string + type: array + webAuthnPolicyPasswordlessUserVerificationRequirement: + type: string + webAuthnPolicyRequireResidentKey: + type: string + webAuthnPolicyRpEntityName: + type: string + webAuthnPolicyRpId: + type: string + webAuthnPolicySignatureAlgorithms: + items: + type: string + type: array + webAuthnPolicyUserVerificationRequirement: + type: string + type: object + required: + - keycloakCRName + - realm + type: object + status: + properties: + conditions: + items: + properties: + message: + type: string + status: + type: boolean + type: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: KeycloakRealmImport + listKind: KeycloakRealmImportList + plural: keycloakrealmimports + singular: keycloakrealmimport + conditions: + - lastTransitionTime: "2023-05-11T15:48:37Z" + message: no conflicts found + reason: NoConflicts + status: "True" + type: NamesAccepted + - lastTransitionTime: "2023-05-11T15:48:37Z" + message: the initial names have been accepted + reason: InitialNamesAccepted + status: "True" + type: Established + storedVersions: + - v2alpha1 diff --git a/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloaks.k8s.keycloak.org.yaml b/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloaks.k8s.keycloak.org.yaml new file mode 100644 index 000000000..13f26c6a3 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/resources/crds/keycloaks.k8s.keycloak.org.yaml @@ -0,0 +1,2947 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + operatorframework.io/installed-alongside-20040cf531a3cb53: eap7-1928/keycloak-operator.v21.1.1 + creationTimestamp: "2023-05-11T15:48:37Z" + generation: 1 + labels: + operators.coreos.com/keycloak-operator.eap7-1928: "" + name: keycloaks.k8s.keycloak.org + resourceVersion: "11420431" + uid: f75865e7-3909-42cc-a8f0-60ef48261ad2 +spec: + conversion: + strategy: None + group: k8s.keycloak.org + names: + kind: Keycloak + listKind: KeycloakList + plural: keycloaks + shortNames: + - kc + singular: keycloak + scope: Namespaced + versions: + - name: v2alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + additionalOptions: + description: |- + Configuration of the Keycloak server. + expressed as a keys (reference: https://www.keycloak.org/server/all-config) and values that can be either direct values or references to secrets. + items: + properties: + name: + type: string + secret: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + value: + type: string + type: object + type: array + db: + description: In this section you can find all properties related to + connect to a database. + properties: + database: + description: Sets the database name of the default JDBC URL of + the chosen vendor. If the `url` option is set, this option is + ignored. + type: string + host: + description: Sets the hostname of the default JDBC URL of the + chosen vendor. If the `url` option is set, this option is ignored. + type: string + passwordSecret: + description: The reference to a secret holding the password of + the database user. + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + poolInitialSize: + description: The initial size of the connection pool. + type: integer + poolMaxSize: + description: The maximum size of the connection pool. + type: integer + poolMinSize: + description: The minimal size of the connection pool. + type: integer + port: + description: Sets the port of the default JDBC URL of the chosen + vendor. If the `url` option is set, this option is ignored. + type: integer + schema: + description: The database schema to be used. + type: string + url: + description: 'The full database JDBC URL. If not provided, a default + URL is set based on the selected database vendor. For instance, + if using ''postgres'', the default JDBC URL would be ''jdbc:postgresql://localhost/keycloak''. ' + type: string + usernameSecret: + description: The reference to a secret holding the username of + the database user. + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + vendor: + description: The database vendor. + type: string + type: object + features: + description: In this section you can configure Keycloak features, + which should be enabled/disabled. + properties: + disabled: + description: Disabled Keycloak features + items: + type: string + type: array + enabled: + description: Enabled Keycloak features + items: + type: string + type: array + type: object + hostname: + description: In this section you can configure Keycloak hostname and + related properties. + properties: + admin: + description: The hostname for accessing the administration console. + type: string + adminUrl: + description: Set the base URL for accessing the administration + console, including scheme, host, port and path + type: string + hostname: + description: Hostname for the Keycloak server. + type: string + strict: + description: Disables dynamically resolving the hostname from + request headers. + type: boolean + strictBackchannel: + description: By default backchannel URLs are dynamically resolved + from request headers to allow internal and external applications. + type: boolean + type: object + http: + description: In this section you can configure Keycloak features related + to HTTP and HTTPS + properties: + httpEnabled: + description: Enables the HTTP listener. + type: boolean + httpPort: + description: The used HTTP port. + type: integer + httpsPort: + description: The used HTTPS port. + type: integer + tlsSecret: + description: 'A secret containing the TLS configuration for HTTPS. + Reference: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets.' + type: string + type: object + image: + description: Custom Keycloak image to be used. + type: string + imagePullSecrets: + description: Secret(s) that might be used when pulling an image from + a private container image registry or repository. + items: + properties: + name: + type: string + type: object + type: array + ingress: + description: |- + The deployment is, by default, exposed through a basic ingress. + You can change this behaviour by setting the enabled property to false. + properties: + enabled: + type: boolean + type: object + instances: + description: Number of Keycloak instances in HA mode. Default is 1. + type: integer + transaction: + description: In this section you can find all properties related to + the settings of transaction behavior. + properties: + xaEnabled: + description: Determine whether Keycloak should use a non-XA datasource + in case the database does not support XA transactions. + type: boolean + type: object + unsupported: + description: |- + In this section you can configure podTemplate advanced features, not production-ready, and not supported settings. + Use at your own risk and open an issue with your use-case if you don't find an alternative way. + properties: + podTemplate: + description: |- + You can configure that will be merged with the one configured by default by the operator. + Use at your own risk, we reserve the possibility to remove/change the way any field gets merged in future releases without notice. + Reference: https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + clusterName: + type: string + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + managedFields: + items: + properties: + apiVersion: + type: string + fieldsType: + type: string + fieldsV1: + type: object + manager: + type: string + operation: + type: string + subresource: + type: string + time: + type: string + type: object + type: array + name: + type: string + namespace: + type: string + ownerReferences: + items: + properties: + apiVersion: + type: string + blockOwnerDeletion: + type: boolean + controller: + type: boolean + kind: + type: string + name: + type: string + uid: + type: string + type: object + type: array + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + properties: + activeDeadlineSeconds: + type: integer + affinity: + properties: + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + preference: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + type: object + weight: + type: integer + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchFields: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + type: object + type: array + type: object + type: object + podAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + type: object + weight: + type: integer + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + type: object + type: array + type: object + podAntiAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + type: object + weight: + type: integer + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaces: + items: + type: string + type: array + topologyKey: + type: string + type: object + type: array + type: object + type: object + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + type: object + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + type: integer + hostIP: + type: string + hostPort: + type: integer + name: + type: string + protocol: + type: string + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + runAsUser: + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + type: object + type: array + workingDir: + type: string + type: object + type: array + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + options: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + searches: + items: + type: string + type: array + type: object + dnsPolicy: + type: string + enableServiceLinks: + type: boolean + ephemeralContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + type: object + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + type: integer + hostIP: + type: string + hostPort: + type: integer + name: + type: string + protocol: + type: string + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + runAsUser: + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + targetContainerName: + type: string + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + type: object + type: array + workingDir: + type: string + type: object + type: array + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + hostIPC: + type: boolean + hostNetwork: + type: boolean + hostPID: + type: boolean + hostname: + type: string + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + initContainers: + items: + properties: + args: + items: + type: string + type: array + command: + items: + type: string + type: array + env: + items: + properties: + name: + type: string + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + key: + type: string + name: + type: string + optional: + type: boolean + type: object + type: object + type: object + type: array + envFrom: + items: + properties: + configMapRef: + properties: + name: + type: string + optional: + type: boolean + type: object + prefix: + type: string + secretRef: + properties: + name: + type: string + optional: + type: boolean + type: object + type: object + type: array + image: + type: string + imagePullPolicy: + type: string + lifecycle: + properties: + postStart: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + livenessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + name: + type: string + ports: + items: + properties: + containerPort: + type: integer + hostIP: + type: string + hostPort: + type: integer + name: + type: string + protocol: + type: string + type: object + type: array + readinessProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + securityContext: + properties: + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + privileged: + type: boolean + procMount: + type: string + readOnlyRootFilesystem: + type: boolean + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + runAsUser: + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + type: object + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + startupProbe: + properties: + exec: + properties: + command: + items: + type: string + type: array + type: object + failureThreshold: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + httpGet: + properties: + host: + type: string + httpHeaders: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + path: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + scheme: + type: string + type: object + initialDelaySeconds: + type: integer + periodSeconds: + type: integer + successThreshold: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + terminationGracePeriodSeconds: + type: integer + timeoutSeconds: + type: integer + type: object + stdin: + type: boolean + stdinOnce: + type: boolean + terminationMessagePath: + type: string + terminationMessagePolicy: + type: string + tty: + type: boolean + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + volumeMounts: + items: + properties: + mountPath: + type: string + mountPropagation: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + subPathExpr: + type: string + type: object + type: array + workingDir: + type: string + type: object + type: array + nodeName: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + os: + properties: + name: + type: string + type: object + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + preemptionPolicy: + type: string + priority: + type: integer + priorityClassName: + type: string + readinessGates: + items: + properties: + conditionType: + type: string + type: object + type: array + restartPolicy: + type: string + runtimeClassName: + type: string + schedulerName: + type: string + securityContext: + properties: + fsGroup: + type: integer + fsGroupChangePolicy: + type: string + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + runAsUser: + type: integer + seLinuxOptions: + properties: + level: + type: string + role: + type: string + type: + type: string + user: + type: string + type: object + seccompProfile: + properties: + localhostProfile: + type: string + type: + type: string + type: object + supplementalGroups: + items: + type: integer + type: array + sysctls: + items: + properties: + name: + type: string + value: + type: string + type: object + type: array + windowsOptions: + properties: + gmsaCredentialSpec: + type: string + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + runAsUserName: + type: string + type: object + type: object + serviceAccount: + type: string + serviceAccountName: + type: string + setHostnameAsFQDN: + type: boolean + shareProcessNamespace: + type: boolean + subdomain: + type: string + terminationGracePeriodSeconds: + type: integer + tolerations: + items: + properties: + effect: + type: string + key: + type: string + operator: + type: string + tolerationSeconds: + type: integer + value: + type: string + type: object + type: array + topologySpreadConstraints: + items: + properties: + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + maxSkew: + type: integer + topologyKey: + type: string + whenUnsatisfiable: + type: string + type: object + type: array + volumes: + items: + properties: + awsElasticBlockStore: + properties: + fsType: + type: string + partition: + type: integer + readOnly: + type: boolean + volumeID: + type: string + type: object + azureDisk: + properties: + cachingMode: + type: string + diskName: + type: string + diskURI: + type: string + fsType: + type: string + kind: + type: string + readOnly: + type: boolean + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + type: object + cephfs: + properties: + monitors: + items: + type: string + type: array + path: + type: string + readOnly: + type: boolean + secretFile: + type: string + secretRef: + properties: + name: + type: string + type: object + user: + type: string + type: object + cinder: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeID: + type: string + type: object + configMap: + properties: + defaultMode: + type: integer + items: + items: + properties: + key: + type: string + mode: + type: integer + path: + type: string + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + csi: + properties: + driver: + type: string + fsType: + type: string + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + type: object + downwardAPI: + properties: + defaultMode: + type: integer + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + mode: + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + type: object + type: array + type: object + emptyDir: + properties: + medium: + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + properties: + annotations: + additionalProperties: + type: string + type: object + clusterName: + type: string + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + managedFields: + items: + properties: + apiVersion: + type: string + fieldsType: + type: string + fieldsV1: + type: object + manager: + type: string + operation: + type: string + subresource: + type: string + time: + type: string + type: object + type: array + name: + type: string + namespace: + type: string + ownerReferences: + items: + properties: + apiVersion: + type: string + blockOwnerDeletion: + type: boolean + controller: + type: boolean + kind: + type: string + name: + type: string + uid: + type: string + type: object + type: array + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + properties: + accessModes: + items: + type: string + type: array + dataSource: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + type: object + dataSourceRef: + properties: + apiGroup: + type: string + kind: + type: string + name: + type: string + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + volumeMode: + type: string + volumeName: + type: string + type: object + type: object + type: object + fc: + properties: + fsType: + type: string + lun: + type: integer + readOnly: + type: boolean + targetWWNs: + items: + type: string + type: array + wwids: + items: + type: string + type: array + type: object + flexVolume: + properties: + driver: + type: string + fsType: + type: string + options: + additionalProperties: + type: string + type: object + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + type: object + flocker: + properties: + datasetName: + type: string + datasetUUID: + type: string + type: object + gcePersistentDisk: + properties: + fsType: + type: string + partition: + type: integer + pdName: + type: string + readOnly: + type: boolean + type: object + gitRepo: + properties: + directory: + type: string + repository: + type: string + revision: + type: string + type: object + glusterfs: + properties: + endpoints: + type: string + path: + type: string + readOnly: + type: boolean + type: object + hostPath: + properties: + path: + type: string + type: + type: string + type: object + iscsi: + properties: + chapAuthDiscovery: + type: boolean + chapAuthSession: + type: boolean + fsType: + type: string + initiatorName: + type: string + iqn: + type: string + iscsiInterface: + type: string + lun: + type: integer + portals: + items: + type: string + type: array + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + targetPortal: + type: string + type: object + name: + type: string + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + type: object + persistentVolumeClaim: + properties: + claimName: + type: string + readOnly: + type: boolean + type: object + photonPersistentDisk: + properties: + fsType: + type: string + pdID: + type: string + type: object + portworxVolume: + properties: + fsType: + type: string + readOnly: + type: boolean + volumeID: + type: string + type: object + projected: + properties: + defaultMode: + type: integer + sources: + items: + properties: + configMap: + properties: + items: + items: + properties: + key: + type: string + mode: + type: integer + path: + type: string + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + downwardAPI: + properties: + items: + items: + properties: + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + mode: + type: integer + path: + type: string + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + type: object + type: array + type: object + secret: + properties: + items: + items: + properties: + key: + type: string + mode: + type: integer + path: + type: string + type: object + type: array + name: + type: string + optional: + type: boolean + type: object + serviceAccountToken: + properties: + audience: + type: string + expirationSeconds: + type: integer + path: + type: string + type: object + type: object + type: array + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + registry: + type: string + tenant: + type: string + user: + type: string + volume: + type: string + type: object + rbd: + properties: + fsType: + type: string + image: + type: string + keyring: + type: string + monitors: + items: + type: string + type: array + pool: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + user: + type: string + type: object + scaleIO: + properties: + fsType: + type: string + gateway: + type: string + protectionDomain: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + sslEnabled: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + volumeName: + type: string + type: object + secret: + properties: + defaultMode: + type: integer + items: + items: + properties: + key: + type: string + mode: + type: integer + path: + type: string + type: object + type: array + optional: + type: boolean + secretName: + type: string + type: object + storageos: + properties: + fsType: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + volumeName: + type: string + volumeNamespace: + type: string + type: object + vsphereVolume: + properties: + fsType: + type: string + storagePolicyID: + type: string + storagePolicyName: + type: string + volumePath: + type: string + type: object + type: object + type: array + type: object + type: object + type: object + type: object + status: + properties: + conditions: + items: + properties: + message: + type: string + status: + type: boolean + type: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: Keycloak + listKind: KeycloakList + plural: keycloaks + shortNames: + - kc + singular: keycloak + conditions: + - lastTransitionTime: "2023-05-11T15:48:37Z" + message: no conflicts found + reason: NoConflicts + status: "True" + type: NamesAccepted + - lastTransitionTime: "2023-05-11T15:48:37Z" + message: the initial names have been accepted + reason: InitialNamesAccepted + status: "True" + type: Established + storedVersions: + - v2alpha1 From 8637709bed49013c49ebea284860c3976c1e1609 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Mon, 29 May 2023 18:08:55 +0200 Subject: [PATCH 02/11] Fix https://github.com/Intersmash/intersmash/issues/37: add KeycloakRealmImport test --- pom.xml | 2 +- ...eycloakQuarkusOperatorProvisionerTest.java | 148 ++++--- .../builders/pod/ContainerBuilder.java | 419 ------------------ .../KeycloakQuarkusOperatorProvisioner.java | 21 + .../PostgreSQLImageOpenShiftProvisioner.java | 9 +- 5 files changed, 106 insertions(+), 493 deletions(-) delete mode 100644 tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java diff --git a/pom.xml b/pom.xml index fd7353905..f6992e71f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ ${project.basedir}/ide-config eclipse-format.xml - 0.29 + 0.31-SNAPSHOT 5.7.0 diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java index 3602456d9..e33343cc5 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java @@ -16,27 +16,32 @@ package org.jboss.intersmash.testsuite.provision.openshift; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.PostgreSQLTemplateOpenShiftApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; import org.jboss.intersmash.tools.provision.openshift.KeycloakQuarkusOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; -import org.jboss.intersmash.tools.provision.openshift.PostgreSQLTemplateOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; -import org.jboss.intersmash.tools.provision.openshift.template.PostgreSQLTemplate; import org.jboss.intersmash.tools.util.tls.CertificatesUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.keycloak.k8s.v2alpha1.Keycloak; +import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; +import org.keycloak.k8s.v2alpha1.KeycloakRealmImportSpec; import org.keycloak.k8s.v2alpha1.KeycloakSpec; +import org.keycloak.k8s.v2alpha1.keycloakrealmimportspec.Realm; +import org.keycloak.k8s.v2alpha1.keycloakrealmimportspec.realm.Users; +import org.keycloak.k8s.v2alpha1.keycloakrealmimportspec.realm.users.Credentials; import org.keycloak.k8s.v2alpha1.keycloakspec.Db; import org.keycloak.k8s.v2alpha1.keycloakspec.Hostname; import org.keycloak.k8s.v2alpha1.keycloakspec.Http; @@ -45,6 +50,7 @@ import org.keycloak.k8s.v2alpha1.keycloakspec.db.UsernameSecret; import org.slf4j.event.Level; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShiftWaiters; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; @@ -72,31 +78,6 @@ public class KeycloakQuarkusOperatorProvisionerTest { private static final String POSTGRESQL_PASSWORD = "pippobaudo1234"; private static final String POSTGRESQL_USER = "user09M"; - private static PostgreSQLTemplateOpenShiftProvisioner initializePostgreSQLTemplateProvisioner() { - PostgreSQLTemplateOpenShiftProvisioner templateProvisioner = new PostgreSQLTemplateOpenShiftProvisioner( - new PostgreSQLTemplateOpenShiftApplication() { - @Override - public String getName() { - return POSTGRESQL_NAME; - } - - @Override - public Map getParameters() { - Map parameters = new HashMap<>(); - parameters.put("POSTGRESQL_DATABASE", POSTGRESQL_DATABASE); - parameters.put("POSTGRESQL_PASSWORD", POSTGRESQL_PASSWORD); - parameters.put("POSTGRESQL_USER", POSTGRESQL_USER); - return parameters; - } - - @Override - public PostgreSQLTemplate getTemplate() { - return PostgreSQLTemplate.POSTGRESQL_EPHEMERAL; - } - }); - return templateProvisioner; - } - private static final PostgreSQLImageOpenShiftApplication pgSQLApplication = new PostgreSQLImageOpenShiftApplication() { @Override public String getName() { @@ -121,58 +102,41 @@ public String getDbName() { private static final PostgreSQLImageOpenShiftProvisioner POSTGRESQL_IMAGE_PROVISIONER = new PostgreSQLImageOpenShiftProvisioner( pgSQLApplication); - private static KeycloakQuarkusOperatorProvisioner initializeOperatorProvisioner() { + private static KeycloakQuarkusOperatorProvisioner initializeOperatorProvisioner(final Keycloak keycloak, + final String appName) { KeycloakQuarkusOperatorProvisioner operatorProvisioner = new KeycloakQuarkusOperatorProvisioner( new KeycloakQuarkusOperatorApplication() { - private static final String DEFAULT_KEYCLOAK_APP_NAME = "example-sso"; @Override public Keycloak getKeycloak() { - Keycloak keycloak = new Keycloak(); - keycloak.getMetadata().setName(DEFAULT_KEYCLOAK_APP_NAME); - KeycloakSpec spec = new KeycloakSpec(); - spec.setInstances(1L); - Ingress ingress = new Ingress(); - ingress.setEnabled(true); - spec.setIngress(ingress); - Hostname hostname = new Hostname(); - hostname.setHostname(OpenShifts.master().generateHostname(DEFAULT_KEYCLOAK_APP_NAME)); - spec.setHostname(hostname); - String tlsSecretName = DEFAULT_KEYCLOAK_APP_NAME + "-tls-secret"; - // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand - CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); - // add TLS config to keycloak using the secret we just created - Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); - spec.setHttp(http); return keycloak; } @Override public String getName() { - return DEFAULT_KEYCLOAK_APP_NAME; + return appName; } }); return operatorProvisioner; } private String name; + private String realmName; private static final Map matchLabels = new HashMap<>(); @BeforeAll public static void createOperatorGroup() throws IOException { - // Keycloak - KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); - - KEYCLOAK_OPERATOR_PROVISIONER.configure(); matchLabels.put("app", "sso"); IntersmashExtension.operatorCleanup(); // create operator group - this should be done by InteropExtension OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); - // clean any leftovers - KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + } + + @BeforeEach + public void cleanup() throws IOException { + if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); } @AfterAll @@ -208,11 +172,10 @@ public void customResourcesCleanup() { */ @Test public void exampleSso() { - KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); try { name = "example-sso"; - Keycloak keycloak = new Keycloak(); + final Keycloak keycloak = new Keycloak(); keycloak.getMetadata().setName(name); keycloak.getMetadata().setLabels(matchLabels); KeycloakSpec spec = new KeycloakSpec(); @@ -233,6 +196,10 @@ public void exampleSso() { spec.setHostname(hostname); keycloak.setSpec(spec); + KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); + KEYCLOAK_OPERATOR_PROVISIONER.configure(); + KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); + verifyKeycloak(keycloak, true); } finally { KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); @@ -240,17 +207,17 @@ public void exampleSso() { } /** - * This test case creates and validates a {@link Keycloak} CR which uses a PostgreSQL databse + * This test case creates and validates a {@link Keycloak} CR which uses a PostgreSQL database * - * This is not an integration test, the goal here is to assess that the created CRs are configured as per the - * model specification. + * Using a database is needed if you want to perform any {@link KeycloakRealmImport} import: after the import is + * completed, Keycloak is automatically restarted by the operator and, unless connected to a database, Keycloak + * would lose all the information just imported! * * See *
- https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples/keycloak */ @Test public void exampleSsoWithDatabase() { - KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); try { POSTGRESQL_IMAGE_PROVISIONER.preDeploy(); POSTGRESQL_IMAGE_PROVISIONER.deploy(); @@ -261,6 +228,7 @@ public void exampleSsoWithDatabase() { keycloak.getMetadata().setName(name); keycloak.getMetadata().setLabels(matchLabels); KeycloakSpec spec = new KeycloakSpec(); + keycloak.setSpec(spec); spec.setInstances(1L); Ingress ingress = new Ingress(); ingress.setEnabled(true); @@ -278,7 +246,7 @@ public void exampleSsoWithDatabase() { spec.setHostname(hostname); // database Db db = new Db(); - db.setDatabase(POSTGRESQL_IMAGE_PROVISIONER.getApplication().getDbName()); + db.setVendor("postgres"); db.setHost(POSTGRESQL_IMAGE_PROVISIONER.getServiceName()); db.setPort(Integer.toUnsignedLong(POSTGRESQL_IMAGE_PROVISIONER.getPort())); UsernameSecret usernameSecret = new UsernameSecret(); @@ -290,9 +258,35 @@ public void exampleSsoWithDatabase() { passwordSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_PASSWORD_KEY); db.setPasswordSecret(passwordSecret); spec.setDb(db); - keycloak.setSpec(spec); - verifyKeycloak(keycloak, true); + realmName = "saml-basic-auth"; + KeycloakRealmImport realmImport = new KeycloakRealmImport(); + realmImport.getMetadata().setName(realmName); + realmImport.getMetadata().setLabels(matchLabels); + KeycloakRealmImportSpec spec1 = new KeycloakRealmImportSpec(); + realmImport.setSpec(spec1); + spec1.setKeycloakCRName(name); + Realm realm = new Realm(); + spec1.setRealm(realm); + realm.setId(realmName); + realm.setRealm(realmName); + realm.setEnabled(true); + List users = new ArrayList<>(); + realm.setUsers(users); + Users user1 = new Users(); + users.add(user1); + user1.setUsername("user"); + user1.setEnabled(true); + Credentials credentials = new Credentials(); + user1.setCredentials(List.of(credentials)); + credentials.setType("password"); + credentials.setValue("LOREDANABERTE1234"); + + KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); + KEYCLOAK_OPERATOR_PROVISIONER.configure(); + KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); + + verifyKeycloak(keycloak, realmImport, true); } finally { KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); POSTGRESQL_IMAGE_PROVISIONER.undeploy(); @@ -301,8 +295,12 @@ public void exampleSsoWithDatabase() { } private void verifyKeycloak(Keycloak keycloak, boolean waitForPods) { + verifyKeycloak(keycloak, null, waitForPods); + } + + private void verifyKeycloak(Keycloak keycloak, KeycloakRealmImport realmImport, boolean waitForPods) { // create and verify that object exists - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().createOrReplace(keycloak); + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().inNamespace(OpenShiftConfig.namespace()).createOrReplace(keycloak); KEYCLOAK_OPERATOR_PROVISIONER.waitFor(keycloak); // two pods expected keycloak-0 and keycloak-postgresql-*, keycloak-0 won't start unless keycloak-postgresql-* is ready if (waitForPods) { @@ -332,6 +330,26 @@ private void verifyKeycloak(Keycloak keycloak, boolean waitForPods) { .getKey()); } + // import new realm + if (realmImport != null) { + KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()) + .createOrReplace(realmImport); + KEYCLOAK_OPERATOR_PROVISIONER.waitFor(realmImport); + KEYCLOAK_OPERATOR_PROVISIONER.waitFor(keycloak); + + Assertions.assertEquals( + KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().withName(realmImport.getMetadata().getName()) + .get().getSpec().getRealm().getRealm(), + realmName); + + KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().withName(realmImport.getMetadata().getName()) + .withPropagationPolicy(DeletionPropagation.FOREGROUND) + .delete(); + new SimpleWaiter(() -> KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().list().getItems() + .stream() + .noneMatch(ri -> realmImport.getMetadata().getName().equalsIgnoreCase(ri.getMetadata().getName()))); + } + // delete and verify that object was removed KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) .delete(); diff --git a/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java b/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java deleted file mode 100644 index c01b92d94..000000000 --- a/tools/intersmash-tools-provisioners/src/main/java/cz/xtf/builder/builders/pod/ContainerBuilder.java +++ /dev/null @@ -1,419 +0,0 @@ -//TODO: remove once https://github.com/xtf-cz/xtf/pull/546 and https://github.com/xtf-cz/xtf/pull/547 are merged and xtf upgraded -package cz.xtf.builder.builders.pod; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.commons.lang3.StringUtils; - -import cz.xtf.builder.builders.EnvironmentConfiguration; -import cz.xtf.builder.builders.PodBuilder; -import cz.xtf.builder.builders.deployment.AbstractProbe; -import cz.xtf.builder.builders.deployment.Handler; -import cz.xtf.builder.builders.deployment.LivenessProbe; -import cz.xtf.builder.builders.deployment.ReadinessProbe; -import cz.xtf.builder.builders.deployment.StartupProbe; -import cz.xtf.builder.builders.limits.CPUResource; -import cz.xtf.builder.builders.limits.ComputingResource; -import cz.xtf.builder.builders.limits.MemoryResource; -import cz.xtf.builder.builders.limits.ResourceLimitBuilder; -import cz.xtf.builder.builders.route.TransportProtocol; -import io.fabric8.kubernetes.api.model.ConfigMapKeySelectorBuilder; -import io.fabric8.kubernetes.api.model.Container; -import io.fabric8.kubernetes.api.model.ContainerFluent.ResourcesNested; -import io.fabric8.kubernetes.api.model.ContainerPortBuilder; -import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.EnvVarSource; -import io.fabric8.kubernetes.api.model.Quantity; -import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; -import io.fabric8.kubernetes.api.model.VolumeMountBuilder; -import lombok.AllArgsConstructor; -import lombok.Getter; - -public class ContainerBuilder implements EnvironmentConfiguration, ResourceLimitBuilder { - private final PodBuilder pod; - private final String name; - - private final Map envVars = new HashMap<>(); - private final Map referredEnvVars = new HashMap<>(); - private final Set ports = new HashSet<>(); - private final Set volumeMounts = new HashSet<>(); - private String imageName; - private String imageNamespace; - private boolean privileged = false; - private AbstractProbe livenessProbe; - private AbstractProbe readinessProbe; - private StartupProbe startupProbe; - private Handler preStopHandler; - private String[] command; - - private Map computingResources = new HashMap<>(); - - public ContainerBuilder(PodBuilder podBuilder, String name) { - if (podBuilder == null) { - throw new IllegalArgumentException("PodBuilder must not be null"); - } - if (StringUtils.isBlank(name)) { - throw new IllegalArgumentException("Name must not be null nor empty"); - } - this.pod = podBuilder; - this.name = name; - } - - public String getName() { - return name; - } - - public ContainerBuilder fromImage(String imageName) { - this.imageName = imageName; - return this; - } - - public ContainerBuilder fromImage(String imageNamespace, String imageName) { - this.imageNamespace = imageNamespace; - this.imageName = imageName; - return this; - } - - public String getImageName() { - return imageName; - } - - public String getImageNamespace() { - return imageNamespace; - } - - public ContainerBuilder port(int port) { - return port(port, null, null); - } - - public ContainerBuilder port(int port, String name) { - return port(port, null, name); - } - - public ContainerBuilder port(int port, TransportProtocol protocol, String name) { - ports.add(new ContainerPort(port, protocol, name)); - return this; - } - - public ContainerBuilder envVar(String key, String value) { - return configEntry(key, value); - } - - public ContainerBuilder envVars(Map vars) { - return (ContainerBuilder) configEntries(vars); - } - - public ContainerBuilder cleanEnvVars() { - envVars.clear(); - return this; - } - - public Map getEnvVars() { - return Collections.unmodifiableMap(envVars); - } - - public ContainerBuilder privileged() { - this.privileged = true; - return this; - } - - public ContainerBuilder addVolumeMount(String name, String mountPath, boolean readOnly) { - this.volumeMounts.add(new VolumeMount(name, mountPath, readOnly)); - return this; - } - - public ContainerBuilder addVolumeMount(String name, String mountPath, boolean readOnly, String subPath) { - this.volumeMounts.add(new VolumeMount(name, mountPath, readOnly, subPath)); - return this; - } - - public ContainerBuilder addVolumeMount(VolumeMount volumeMount) { - this.volumeMounts.add(volumeMount); - return this; - } - - public LivenessProbe addLivenessProbe() { - this.livenessProbe = new LivenessProbe(); - return (LivenessProbe) this.livenessProbe; - } - - public ReadinessProbe addReadinessProbe() { - this.readinessProbe = new ReadinessProbe(); - return (ReadinessProbe) this.readinessProbe; - } - - public StartupProbe addStartupProbe() { - this.startupProbe = new StartupProbe(); - return (StartupProbe) this.startupProbe; - } - - public ContainerBuilder addReadinessProbe(AbstractProbe readinessProbe) { - this.readinessProbe = readinessProbe; - return this; - } - - public ContainerBuilder addCommand(String... cmd) { - this.command = cmd; - return this; - } - - public PodBuilder pod() { - return pod; - } - - public Container build() { - io.fabric8.kubernetes.api.model.ContainerBuilder builder = new io.fabric8.kubernetes.api.model.ContainerBuilder(); - - Stream definedVars = envVars.entrySet().stream() - .map(entry -> new EnvVar(entry.getKey(), entry.getValue(), null)); - Stream referredVars = referredEnvVars.entrySet().stream() - .map(entry -> (entry instanceof ConfigMapEntry) ? new EnvVar(entry.getKey(), null, - new EnvVarSource( - new ConfigMapKeySelectorBuilder() - .withKey(entry.getValue().getKey()) - .withName(entry.getValue().getValue()) - .build(), - null, - null, - null)) - : new EnvVar(entry.getKey(), null, - new EnvVarSource( - null, - null, - null, - new SecretKeySelectorBuilder() - .withKey(entry.getValue().getKey()) - .withName(entry.getValue().getValue()) - .build()))); - builder.withEnv(Stream.concat(definedVars, referredVars).collect(Collectors.toList())); - builder.withImage(imageName); - builder.withImagePullPolicy("Always"); - - if (command != null) { - builder.withCommand(command); - } - - if (livenessProbe != null) { - builder.withLivenessProbe(livenessProbe.build()); - } - - builder.withName(name); - - builder.withPorts(ports.stream().map(port -> { - ContainerPortBuilder portBuilder = new ContainerPortBuilder(); - portBuilder.withContainerPort(port.getContainerPort()); - if (port.getProtocol() != null) { - portBuilder.withProtocol(port.getProtocol().uppercase()); - } - if (port.getName() != null) { - portBuilder.withName(port.getName()); - } - - return portBuilder.build(); - }).collect(Collectors.toList())); - - if (preStopHandler != null) { - builder.withNewLifecycle() - .withPreStop(preStopHandler.build()) - .endLifecycle(); - } - - if (privileged) { - builder.withNewSecurityContext().withPrivileged(true).endSecurityContext(); - } - - if (readinessProbe != null) { - builder.withReadinessProbe(readinessProbe.build()); - } - - if (startupProbe != null) { - builder.withStartupProbe(startupProbe.build()); - } - - builder.withVolumeMounts(volumeMounts.stream().map(item -> new VolumeMountBuilder() - .withName(item.getName()) - .withMountPath(item.getMountPath()) - .withReadOnly(item.isReadOnly()) - .withSubPath(item.getSubPath()) - .build()).collect(Collectors.toList())); - - final List requests = computingResources.values().stream().filter(x -> x.getRequests() != null) - .collect(Collectors.toList()); - final List limits = computingResources.values().stream().filter(x -> x.getLimits() != null) - .collect(Collectors.toList()); - if (!requests.isEmpty() || !limits.isEmpty()) { - ResourcesNested resources = builder.withNewResources(); - if (!requests.isEmpty()) { - resources.withRequests( - requests.stream().collect(Collectors.toMap( - ComputingResource::resourceIdentifier, x -> new Quantity(x.getRequests())))); - } - if (!limits.isEmpty()) { - resources.withLimits( - limits.stream().collect(Collectors.toMap( - ComputingResource::resourceIdentifier, x -> new Quantity(x.getLimits())))); - } - resources.endResources(); - } - // args - // capabilities - // command - // lifecycle - // resources - // securityContext - // terminationMessagePath - // workingDir - return builder.build(); - } - - @Override - public ComputingResource addCPUResource() { - final ComputingResource r = new CPUResource(); - computingResources.put(r.resourceIdentifier(), r); - return r; - } - - @Override - public ComputingResource addMemoryResource() { - final ComputingResource r = new MemoryResource(); - computingResources.put(r.resourceIdentifier(), r); - return r; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o.getClass() == getClass())) - return false; - - ContainerBuilder that = (ContainerBuilder) o; - - return name.equals(that.name); - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - public void addPreStopHandler(Handler handler) { - preStopHandler = handler; - } - - private static class ContainerPort { - private final int containerPort; - private final TransportProtocol protocol; - private String name; - - public ContainerPort(int containerPort, TransportProtocol protocol, String name) { - if (containerPort < 1 || containerPort > 65538) { - throw new IllegalArgumentException("Wrong port number"); - } - this.containerPort = containerPort; - this.protocol = protocol; - this.name = name; - } - - public int getContainerPort() { - return containerPort; - } - - public TransportProtocol getProtocol() { - return protocol; - } - - public String getName() { - return name; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof ContainerPort)) - return false; - - ContainerPort that = (ContainerPort) o; - - if (containerPort != that.containerPort) - return false; - return protocol == that.protocol; - - } - - @Override - public int hashCode() { - int result = containerPort; - result = 31 * result + (protocol != null ? protocol.hashCode() : 0); - return result; - } - } - - @Override - public ContainerBuilder configEntry(String key, String value) { - envVars.put(key, value); - return this; - } - - @Override - public Map getConfigEntries() { - return envVars; - } - - public ContainerBuilder configFromConfigMap(String configMapName, String... configMapKeys) { - return configFromConfigMap(configMapName, Arrays.asList(configMapKeys)); - } - - public ContainerBuilder configFromConfigMap(String configMapName, Collection configMapKeys) { - return configFromConfigMap(configMapName, Function.identity(), configMapKeys); - } - - public ContainerBuilder configFromConfigMap(String configMapName, Function nameMapping, - String... configMapKeys) { - return configFromConfigMap(configMapName, nameMapping, Arrays.asList(configMapKeys)); - } - - public ContainerBuilder configFromConfigMap(String configMapName, Function nameMapping, - Collection configMapKeys) { - configMapKeys.forEach(x -> referredEnvVars.put(nameMapping.apply(x), new ConfigMapEntry(x, configMapName))); - return this; - } - - public ContainerBuilder configFromSecret(String secretName, Function nameMapping, - Collection configMapKeys) { - configMapKeys.forEach(x -> referredEnvVars.put(nameMapping.apply(x), new SecretEntry(x, secretName))); - return this; - } - - @Getter - @AllArgsConstructor - private class Entry { - private String key; - private String value; - } - - @Getter - private class ConfigMapEntry extends Entry { - public ConfigMapEntry(String x, String configMapName) { - super(x, configMapName); - } - } - - @Getter - private class SecretEntry extends Entry { - public SecretEntry(String x, String secretName) { - super(x, secretName); - } - } -} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java index 7533efc4d..2b2dd09be 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java @@ -173,6 +173,27 @@ public void waitFor(Keycloak keycloak) { } } + public void waitFor(KeycloakRealmImport realmImport) { + new SimpleWaiter(() -> { + Resource res = keycloakRealmImportClient().withName(realmImport.getMetadata().getName()); + if (res != null && res.get() != null) { + KeycloakRealmImport imp = res.get(); + return imp.getStatus().getConditions().stream().filter( + cond -> cond.getStatus() + && "Done".equalsIgnoreCase(cond.getType()) + && com.google.common.base.Strings.isNullOrEmpty(cond.getMessage())) + .count() == 1 + && + imp.getStatus().getConditions().stream().filter( + cond -> !cond.getStatus() + && "HasErrors".equalsIgnoreCase(cond.getType()) + && com.google.common.base.Strings.isNullOrEmpty(cond.getMessage())) + .count() == 1; + } + return false; + }); + } + private void waitForKeycloakResourceReadiness() { new SimpleWaiter( () -> keycloak().get().getStatus().getConditions().stream().anyMatch( diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java index 8365c08b1..10202bc0b 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java @@ -86,20 +86,13 @@ public Map getImageVariables() { @Override public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { - //TODO: use the secret and remove the configMap once xtf has been fixed - // the secret is supposed to be used by applications connecting to the database - appBuilder.secret(dbApplication.getName()) + appBuilder.secret(getSecretName()) .setType(SecretType.OPAQUE) .addData(POSTGRESQL_USER_KEY, dbApplication.getUser().getBytes()) .addData(POSTGRESQL_PASSWORD_KEY, dbApplication.getPassword().getBytes()) .addData(POSTGRESQL_ADMIN_PASSWORD_KEY, dbApplication.getAdminPassword().getBytes()); - // configMap is temporarily supposed to be used by database POD - /*appBuilder.configMap(dbApplication.getName() + "-tmp") - .configEntry(DATABASE_USER_KEY, dbApplication.getUser()) - .configEntry(DATABASE_PASSWORD_KEY, dbApplication.getPassword()) - .configEntry(DATABASE_ADMIN_PASSWORD_KEY, dbApplication.getAdminPassword());*/ appBuilder.deploymentConfig().podTemplate().container().configFromConfigMap( dbApplication.getName(), (String t) -> t.replace("-", "_").toUpperCase(), From d0393fbaed211ca4394190adedec11ca7c84b164 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Tue, 30 May 2023 15:41:31 +0200 Subject: [PATCH 03/11] Fix https://github.com/Intersmash/intersmash/issues/37: fix service labels --- .../openshift/DBImageOpenShiftProvisioner.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java index 821bb1492..c5bf40de2 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java @@ -80,7 +80,7 @@ public void deploy() { ApplicationBuilder appBuilder = ApplicationBuilder.fromImage(dbApplication.getName(), getImage(), Collections.singletonMap(APP_LABEL_KEY, dbApplication.getName())); - final DeploymentConfigBuilder builder = appBuilder.deploymentConfig(); + final DeploymentConfigBuilder builder = appBuilder.deploymentConfig(dbApplication.getName()); builder.podTemplate().container().envVars(getImageVariables()).port(getPort()); configureContainer(builder.podTemplate().container()); @@ -95,9 +95,9 @@ public void deploy() { new PVCBuilder(pvc.getClaimName()).accessRWX().storageSize("100Mi").build()); } - appBuilder.service().port(getPort()) + appBuilder.service(getServiceName()).port(getPort()) .addContainerSelector("deploymentconfig", dbApplication.getName()) - .addContainerSelector("app", dbApplication.getName()); + .addContainerSelector("name", dbApplication.getName()); customizeApplicationBuilder(appBuilder); @@ -141,14 +141,14 @@ public String getUrl(String routeName, boolean secure) { * @return service name to access the database */ public String getServiceName() { - return dbApplication.getName(); + return dbApplication.getName() + "-service"; } /** * @return name of the secret containing username and password for the database */ public String getSecretName() { - return dbApplication.getName(); + return dbApplication.getName() + "-credentials"; } } From d3a6a4d6947ed424a28be65c88a569ed3ecaad6a Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Tue, 30 May 2023 15:42:18 +0200 Subject: [PATCH 04/11] Fix https://github.com/Intersmash/intersmash/issues/37: fix postgres database provisioner --- ...eycloakQuarkusOperatorProvisionerTest.java | 70 +++++++++++-------- .../KeycloakQuarkusOperatorProvisioner.java | 21 +++--- .../PostgreSQLImageOpenShiftProvisioner.java | 4 +- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java index e33343cc5..1859bfa58 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java @@ -50,12 +50,14 @@ import org.keycloak.k8s.v2alpha1.keycloakspec.db.UsernameSecret; import org.slf4j.event.Level; -import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShiftWaiters; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; import cz.xtf.junit5.annotations.CleanBeforeAll; import io.fabric8.kubernetes.api.model.DeletionPropagation; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; import lombok.extern.slf4j.Slf4j; /** @@ -142,23 +144,26 @@ public void cleanup() throws IOException { @AfterAll public static void removeOperatorGroup() { OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); - KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); + if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) + KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); POSTGRESQL_IMAGE_PROVISIONER.undeploy(); POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); } @AfterEach public void customResourcesCleanup() { - - // delete keycloaks - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().list().getItems().stream() - .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER - .keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); - // delete realms - KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().list().getItems().stream() - .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER - .keycloakRealmImportClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) - .delete()); + if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) { + // delete keycloaks + KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().list().getItems().stream() + .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER + .keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); + // delete realms + KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().list().getItems() + .stream() + .map(resource -> resource.getMetadata().getName()).forEach(name -> KEYCLOAK_OPERATOR_PROVISIONER + .keycloakRealmImportClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) + .delete()); + } } /** @@ -202,7 +207,8 @@ public void exampleSso() { verifyKeycloak(keycloak, true); } finally { - KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); } } @@ -257,6 +263,7 @@ public void exampleSsoWithDatabase() { passwordSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); passwordSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_PASSWORD_KEY); db.setPasswordSecret(passwordSecret); + db.setDatabase(POSTGRESQL_IMAGE_PROVISIONER.getApplication().getDbName()); spec.setDb(db); realmName = "saml-basic-auth"; @@ -288,7 +295,8 @@ public void exampleSsoWithDatabase() { verifyKeycloak(keycloak, realmImport, true); } finally { - KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); POSTGRESQL_IMAGE_PROVISIONER.undeploy(); POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); } @@ -299,61 +307,63 @@ private void verifyKeycloak(Keycloak keycloak, boolean waitForPods) { } private void verifyKeycloak(Keycloak keycloak, KeycloakRealmImport realmImport, boolean waitForPods) { + NonNamespaceOperation, Resource> keycloakClient = KEYCLOAK_OPERATOR_PROVISIONER + .keycloakClient(); + NonNamespaceOperation, Resource> keycloakRealmImportClient = KEYCLOAK_OPERATOR_PROVISIONER + .keycloakRealmImportClient(); // create and verify that object exists - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().inNamespace(OpenShiftConfig.namespace()).createOrReplace(keycloak); + keycloakClient.createOrReplace(keycloak); KEYCLOAK_OPERATOR_PROVISIONER.waitFor(keycloak); // two pods expected keycloak-0 and keycloak-postgresql-*, keycloak-0 won't start unless keycloak-postgresql-* is ready if (waitForPods) { OpenShiftWaiters.get(OpenShifts.master(), () -> false) .areExactlyNPodsReady(keycloak.getSpec().getInstances().intValue(), "app", keycloak.getKind().toLowerCase()) .level(Level.DEBUG).waitFor(); - log.debug(KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getStatus().toString()); } Assertions.assertEquals(keycloak.getSpec().getHostname().getHostname(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getHostname().getHostname()); + keycloakClient.withName(name).get().getSpec().getHostname().getHostname()); if (!Objects.isNull(keycloak.getSpec().getDb())) { Assertions.assertEquals(keycloak.getSpec().getDb().getHost(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getHost()); + keycloakClient.withName(name).get().getSpec().getDb().getHost()); Assertions.assertEquals(keycloak.getSpec().getDb().getDatabase(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getDatabase()); + keycloakClient.withName(name).get().getSpec().getDb().getDatabase()); Assertions.assertEquals(keycloak.getSpec().getDb().getUsernameSecret().getName(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getUsernameSecret() + keycloakClient.withName(name).get().getSpec().getDb().getUsernameSecret() .getName()); Assertions.assertEquals(keycloak.getSpec().getDb().getUsernameSecret().getKey(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getUsernameSecret() + keycloakClient.withName(name).get().getSpec().getDb().getUsernameSecret() .getKey()); Assertions.assertEquals(keycloak.getSpec().getDb().getPasswordSecret().getName(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getPasswordSecret() + keycloakClient.withName(name).get().getSpec().getDb().getPasswordSecret() .getName()); Assertions.assertEquals(keycloak.getSpec().getDb().getPasswordSecret().getKey(), - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).get().getSpec().getDb().getPasswordSecret() + keycloakClient.withName(name).get().getSpec().getDb().getPasswordSecret() .getKey()); } // import new realm if (realmImport != null) { - KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()) - .createOrReplace(realmImport); + keycloakRealmImportClient.createOrReplace(realmImport); KEYCLOAK_OPERATOR_PROVISIONER.waitFor(realmImport); KEYCLOAK_OPERATOR_PROVISIONER.waitFor(keycloak); Assertions.assertEquals( - KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().withName(realmImport.getMetadata().getName()) + keycloakRealmImportClient.withName(realmImport.getMetadata().getName()) .get().getSpec().getRealm().getRealm(), realmName); - KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().withName(realmImport.getMetadata().getName()) + keycloakRealmImportClient.withName(realmImport.getMetadata().getName()) .withPropagationPolicy(DeletionPropagation.FOREGROUND) .delete(); - new SimpleWaiter(() -> KEYCLOAK_OPERATOR_PROVISIONER.keycloakRealmImportClient().list().getItems() + new SimpleWaiter(() -> keycloakRealmImportClient.list().getItems() .stream() .noneMatch(ri -> realmImport.getMetadata().getName().equalsIgnoreCase(ri.getMetadata().getName()))); } // delete and verify that object was removed - KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) + keycloakClient.withName(name).withPropagationPolicy(DeletionPropagation.FOREGROUND) .delete(); - new SimpleWaiter(() -> KEYCLOAK_OPERATOR_PROVISIONER.keycloakClient().list().getItems().size() == 0).level(Level.DEBUG) + new SimpleWaiter(() -> keycloakClient.list().getItems().size() == 0).level(Level.DEBUG) .waitFor(); if (waitForPods) { OpenShiftWaiters.get(OpenShifts.master(), () -> false) diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java index 2b2dd09be..e75958edc 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java @@ -45,6 +45,7 @@ import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import lombok.NonNull; @@ -191,7 +192,7 @@ public void waitFor(KeycloakRealmImport realmImport) { .count() == 1; } return false; - }); + }).reason("Wait for KeycloakRealmImport resource to be imported").level(Level.DEBUG).waitFor(); } private void waitForKeycloakResourceReadiness() { @@ -214,12 +215,12 @@ private void waitForKeycloakResourceReadiness() { * @return A concrete {@link Resource} instance representing the {@link org.jboss.intersmash.tools.provision.openshift.operator.keycloak.keycloak.Keycloak} resource definition */ public Resource keycloak() { - return keycloakClient().inNamespace(OpenShiftConfig.namespace()) + return keycloakClient() .withName(getApplication().getKeycloak().getMetadata().getName()); } public List keycloakRealmImports() { - return keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()).list().getItems() + return keycloakRealmImportClient().list().getItems() .stream().filter( realm -> getApplication().getKeycloakRealmImports().stream().map( ri -> ri.getMetadata().getName()) @@ -245,15 +246,15 @@ private StatefulSet getStatefulSet() { public void undeploy() { keycloakRealmImports() .forEach( - keycloakRealm -> keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()) + keycloakRealm -> keycloakRealmImportClient() .withName(keycloakRealm.getMetadata().getName()) .withPropagationPolicy(DeletionPropagation.FOREGROUND) .delete()); new SimpleWaiter( - () -> keycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()).list().getItems().size() == 0) + () -> keycloakRealmImportClient().list().getItems().size() == 0) .reason("Wait for all keycloakRealmImports instances to be deleted.").level(Level.DEBUG).waitFor(); keycloak().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); - new SimpleWaiter(() -> keycloakClient().inNamespace(OpenShiftConfig.namespace()).list().getItems().size() == 0) + new SimpleWaiter(() -> keycloakClient().list().getItems().size() == 0) .reason("Wait for Keycloak instances to be deleted.").level(Level.DEBUG).waitFor(); // wait for 0 pods @@ -325,19 +326,19 @@ public URL getURL() { } } - public MixedOperation, Resource> keycloakClient() { + public NonNamespaceOperation, Resource> keycloakClient() { try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { MixedOperation, Resource> keycloakClient = kubernetesClient .resources(Keycloak.class); - return keycloakClient; + return keycloakClient.inNamespace(OpenShiftConfig.namespace()); } } - public MixedOperation, Resource> keycloakRealmImportClient() { + public NonNamespaceOperation, Resource> keycloakRealmImportClient() { try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { MixedOperation, Resource> keycloakRealmImportClient = kubernetesClient .resources(KeycloakRealmImport.class); - return keycloakRealmImportClient; + return keycloakRealmImportClient.inNamespace(OpenShiftConfig.namespace()); } } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java index 10202bc0b..86b8f3229 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java @@ -77,6 +77,7 @@ public Map getImageVariables() { vars.put("POSTGRESQL_MAX_CONNECTIONS", "100"); vars.put("POSTGRESQL_SHARED_BUFFERS", "16MB"); vars.put("POSTGRESQL_MAX_PREPARED_TRANSACTIONS", "90"); + vars.put("POSTGRESQL_DATABASE", dbApplication.getDbName()); // Temporary workaround for https://github.com/sclorg/postgresql-container/issues/297 // Increase the "set_passwords.sh" timeout from the default 60s to 300s to give the // PostgreSQL server chance properly to start under high OCP cluster load @@ -93,8 +94,9 @@ public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { .addData(POSTGRESQL_PASSWORD_KEY, dbApplication.getPassword().getBytes()) .addData(POSTGRESQL_ADMIN_PASSWORD_KEY, dbApplication.getAdminPassword().getBytes()); + // the secret is also used to configure POSTGRESQL_USER, POSTGRESQL_PASSWORD, POSTGRESQL_ADMIN_PASSWORD appBuilder.deploymentConfig().podTemplate().container().configFromConfigMap( - dbApplication.getName(), + getSecretName(), (String t) -> t.replace("-", "_").toUpperCase(), POSTGRESQL_USER_KEY, POSTGRESQL_PASSWORD_KEY, From 866c4ddb9e65233d3e83e94074370495b9ec9230 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Tue, 30 May 2023 15:42:37 +0200 Subject: [PATCH 05/11] Fix https://github.com/Intersmash/intersmash/issues/37: upgrade xtf version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f6992e71f..696298b92 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ ${project.basedir}/ide-config eclipse-format.xml - 0.31-SNAPSHOT + 0.31-202305291230-SNAPSHOT 5.7.0 From 891ed013e736b2e219c2636beee4263d4e63ea95 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Wed, 31 May 2023 09:50:01 +0200 Subject: [PATCH 06/11] Fix https://github.com/Intersmash/intersmash/issues/37: reanamed KeycloakQuarkus* into KeycloakRealmImport* --- ...akRealmImportOperatorProvisionerTest.java} | 14 +++++------ ...ycloakRealmImportOperatorApplication.java} | 6 ++--- ...ycloakRealmImportOperatorProvisioner.java} | 25 +++++++++++-------- ...ealmImportOperatorProvisionerFactory.java} | 11 ++++---- 4 files changed, 30 insertions(+), 26 deletions(-) rename testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/{KeycloakQuarkusOperatorProvisionerTest.java => KeycloakRealmImportOperatorProvisionerTest.java} (96%) rename tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/{KeycloakQuarkusOperatorApplication.java => KeycloakRealmImportOperatorApplication.java} (82%) rename tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/{KeycloakQuarkusOperatorProvisioner.java => KeycloakRealmImportOperatorProvisioner.java} (94%) rename tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/{KeycloakQuarkusOperatorProvisionerFactory.java => KeycloakRealmImportOperatorProvisionerFactory.java} (67%) diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java similarity index 96% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java index 1859bfa58..44cff3b04 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakQuarkusOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java @@ -22,10 +22,10 @@ import java.util.Map; import java.util.Objects; -import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.application.openshift.KeycloakRealmImportOperatorApplication; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.KeycloakQuarkusOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; import org.jboss.intersmash.tools.util.tls.CertificatesUtils; @@ -72,8 +72,8 @@ @Slf4j @CleanBeforeAll //@Disabled("WIP - Disabled until global-test.properties is configured with the required property") -public class KeycloakQuarkusOperatorProvisionerTest { - private static KeycloakQuarkusOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; +public class KeycloakRealmImportOperatorProvisionerTest { + private static KeycloakRealmImportOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; private static final String POSTGRESQL_NAME = "postgresql"; private static final String POSTGRESQL_DATABASE = "keycloak"; @@ -104,10 +104,10 @@ public String getDbName() { private static final PostgreSQLImageOpenShiftProvisioner POSTGRESQL_IMAGE_PROVISIONER = new PostgreSQLImageOpenShiftProvisioner( pgSQLApplication); - private static KeycloakQuarkusOperatorProvisioner initializeOperatorProvisioner(final Keycloak keycloak, + private static KeycloakRealmImportOperatorProvisioner initializeOperatorProvisioner(final Keycloak keycloak, final String appName) { - KeycloakQuarkusOperatorProvisioner operatorProvisioner = new KeycloakQuarkusOperatorProvisioner( - new KeycloakQuarkusOperatorApplication() { + KeycloakRealmImportOperatorProvisioner operatorProvisioner = new KeycloakRealmImportOperatorProvisioner( + new KeycloakRealmImportOperatorApplication() { @Override public Keycloak getKeycloak() { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java similarity index 82% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java index b32402ec6..2af2c7efd 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakQuarkusOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java @@ -18,7 +18,7 @@ import java.util.Collections; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.KeycloakQuarkusOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOperatorProvisioner; import org.keycloak.k8s.v2alpha1.Keycloak; import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; @@ -27,10 +27,10 @@ * * The application will be deployed by: *
    - *
  • {@link KeycloakQuarkusOperatorProvisioner}
  • + *
  • {@link KeycloakRealmImportOperatorProvisioner}
  • *
*/ -public interface KeycloakQuarkusOperatorApplication extends OperatorApplication { +public interface KeycloakRealmImportOperatorApplication extends OperatorApplication { Keycloak getKeycloak(); diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java similarity index 94% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java index e75958edc..42d66140e 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java @@ -17,6 +17,7 @@ import java.net.MalformedURLException; import java.net.URL; +import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Objects; @@ -25,7 +26,7 @@ import org.assertj.core.util.Lists; import org.assertj.core.util.Strings; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.application.openshift.KeycloakRealmImportOperatorApplication; import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; import org.jboss.intersmash.tools.util.tls.CertificatesUtils; import org.keycloak.k8s.v2alpha1.Keycloak; @@ -52,14 +53,14 @@ /** * Keycloak operator provisioner */ -public class KeycloakQuarkusOperatorProvisioner extends OperatorProvisioner { +public class KeycloakRealmImportOperatorProvisioner extends OperatorProvisioner { //private MixedOperation, Resource> keycloakRealmImportClient; //private MixedOperation, Resource> keycloakClient; private static final String OPERATOR_ID = IntersmashConfig.keycloakQuarkusOperatorPackageManifest(); protected FailFastCheck ffCheck = () -> false; - public KeycloakQuarkusOperatorProvisioner(@NonNull KeycloakQuarkusOperatorApplication application) { + public KeycloakRealmImportOperatorProvisioner(@NonNull KeycloakRealmImportOperatorApplication application) { super(application, OPERATOR_ID); } @@ -232,14 +233,16 @@ public List keycloakRealmImports() { * @return the underlying StatefulSet which provisions the cluster */ private StatefulSet getStatefulSet() { - String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); - if (Objects.isNull(statefulSet)) { - throw new IllegalStateException(String.format( - "Impossible to find StatefulSet with name=\"%s\"!", - STATEFUL_SET_NAME)); - } - return statefulSet; + final String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); + new SimpleWaiter( + () -> Objects.nonNull(OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME))) + .reason( + MessageFormat.format( + "Waiting for StatefulSet \"{0}\" to be created for Keycloak \"{1}\".", + STATEFUL_SET_NAME, + getApplication().getKeycloak().getMetadata().getName())) + .level(Level.DEBUG).timeout(60000L).waitFor(); + return OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); } @Override diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java similarity index 67% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java index 76b90a45a..21906bc2c 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakQuarkusOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java @@ -16,18 +16,19 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.KeycloakQuarkusOperatorApplication; +import org.jboss.intersmash.tools.application.openshift.KeycloakRealmImportOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class KeycloakQuarkusOperatorProvisionerFactory implements ProvisionerFactory { +public class KeycloakRealmImportOperatorProvisionerFactory + implements ProvisionerFactory { @Override - public KeycloakQuarkusOperatorProvisioner getProvisioner(Application application) { - if (KeycloakQuarkusOperatorApplication.class.isAssignableFrom(application.getClass())) - return new KeycloakQuarkusOperatorProvisioner((KeycloakQuarkusOperatorApplication) application); + public KeycloakRealmImportOperatorProvisioner getProvisioner(Application application) { + if (KeycloakRealmImportOperatorApplication.class.isAssignableFrom(application.getClass())) + return new KeycloakRealmImportOperatorProvisioner((KeycloakRealmImportOperatorApplication) application); return null; } } From ead2eaf8b491bc7688d591fa142c496dd7c01dc1 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Wed, 31 May 2023 14:38:13 +0200 Subject: [PATCH 07/11] Fix https://github.com/Intersmash/intersmash/issues/37: remove domanin from hostname --- .../openshift/KeycloakRealmImportOperatorProvisionerTest.java | 4 ++-- .../openshift/KeycloakRealmImportOperatorProvisioner.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java index 44cff3b04..ab9d54759 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java @@ -193,7 +193,7 @@ public void exampleSso() { // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand String tlsSecretName = name + "-tls-secret"; CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); + .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); // add TLS config to keycloak using the secret we just created Http http = new Http(); http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); @@ -244,7 +244,7 @@ public void exampleSsoWithDatabase() { // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand String tlsSecretName = name + "-tls-secret"; CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(hostname.getHostname(), tlsSecretName); + .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); // add TLS config to keycloak using the secret we just created Http http = new Http(); http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java index 42d66140e..ca50aa662 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java @@ -123,7 +123,8 @@ public void deploy() { // create key, certificate and tls secret String tlsSecretName = getApplication().getKeycloak().getMetadata().getName() + "-tls-secret"; CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(getApplication().getKeycloak().getSpec().getHostname().getHostname(), + .generateSelfSignedCertificateAndKey( + getApplication().getKeycloak().getSpec().getHostname().getHostname().replaceFirst("[.].*$", ""), tlsSecretName); // add config to keycloak if (getApplication().getKeycloak().getSpec().getHttp() == null) { From 9d4e6c314afc903dff8388a545790ff8fb5aa816 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Wed, 31 May 2023 17:51:43 +0200 Subject: [PATCH 08/11] Fix https://github.com/Intersmash/intersmash/issues/37: KeycloakRealmImport status mght be null --- .../openshift/KeycloakRealmImportOperatorProvisioner.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java index ca50aa662..b0c00de54 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java @@ -179,7 +179,9 @@ public void waitFor(Keycloak keycloak) { public void waitFor(KeycloakRealmImport realmImport) { new SimpleWaiter(() -> { Resource res = keycloakRealmImportClient().withName(realmImport.getMetadata().getName()); - if (res != null && res.get() != null) { + if (Objects.nonNull(res) + && Objects.nonNull(res.get()) + && Objects.nonNull(res.get().getStatus())) { KeycloakRealmImport imp = res.get(); return imp.getStatus().getConditions().stream().filter( cond -> cond.getStatus() From 8b80b74205d46a9c77e76aec512da8ac3e40d733 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Thu, 1 Jun 2023 12:06:29 +0200 Subject: [PATCH 09/11] Fix https://github.com/Intersmash/intersmash/issues/37: eliminated quarkus from naming --- global-test.properties | 8 ++-- .../intersmash/tools/IntersmashConfig.java | 37 +++++++++---------- ...eycloakRealmImportOperatorProvisioner.java | 18 ++++----- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/global-test.properties b/global-test.properties index a5afd45c5..cf7829b05 100644 --- a/global-test.properties +++ b/global-test.properties @@ -43,7 +43,7 @@ intersmash.mysql.image=quay.io/centos7/mysql-80-centos7 intersmash.postgresql.image=quay.io/centos7/postgresql-13-centos7 # Keycloak (new Quarkus based version) settings -#intersmash.keycloak.quarks.image=quay.io/keycloak/keycloak:21.1.1 -#intersmash.keycloak.quarkus.operators.catalog_source=community-operators -#intersmash.keycloak.quarkus.operators.index_image=registry.redhat.io/redhat/community-operator-index:v4.12 -#intersmash.keycloak.quarkus.operators.channel=fast +#intersmash.keycloak.realm_import.image=quay.io/keycloak/keycloak:21.1.1 +#intersmash.keycloak.realm_import.operators.catalog_source=community-operators +#intersmash.keycloak.realm_import.operators.index_image=registry.redhat.io/redhat/community-operator-index:v4.12 +#intersmash.keycloak.realm_import.operators.channel=fast diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java index 50338b34f..a38370352 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java @@ -43,18 +43,18 @@ public class IntersmashConfig { private static final String PRODUCT_INFINISPAN_OPERATOR_PACKAGE_MANIFEST = "datagrid"; private static final String DEFAULT_INFINISPAN_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_INFINISPAN_OPERATOR_PACKAGE_MANIFEST; private static final String KEYCLOAK_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.keycloak.operators.catalog_source"; - private static final String KEYCLOAK_QUARKUS_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.keycloak.quarkus.operators.catalog_source"; + private static final String KEYCLOAK_REALM_IMPORT_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.keycloak.realm_import.operators.catalog_source"; private static final String KEYCLOAK_OPERATOR_INDEX_IMAGE = "intersmash.keycloak.operators.index_image"; - private static final String KEYCLOAK_QUARKUS_OPERATOR_INDEX_IMAGE = "intersmash.keycloak.quarkus.operators.index_image"; + private static final String KEYCLOAK_REALM_IMPORT_OPERATOR_INDEX_IMAGE = "intersmash.keycloak.realm_import.operators.index_image"; private static final String KEYCLOAK_OPERATOR_CHANNEL = "intersmash.keycloak.operators.channel"; - private static final String KEYCLOAK_QUARKUS_OPERATOR_CHANNEL = "intersmash.keycloak.quarkus.operators.channel"; + private static final String KEYCLOAK_REALM_IMPORT_OPERATOR_CHANNEL = "intersmash.keycloak.realm_import.operators.channel"; private static final String KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "intersmash.keycloak.operators.package_manifest"; - private static final String KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST = "intersmash.keycloak.quarkus.operators.package_manifest"; + private static final String KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST = "intersmash.keycloak.realm_import.operators.package_manifest"; private static final String COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "keycloak-operator"; private static final String PRODUCT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = "rhsso-operator"; private static final String DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST; - private static final String DEFAULT_KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST; + private static final String DEFAULT_KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST = COMMUNITY_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST; private static final String WILDFLY_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.wildfly.operators.catalog_source"; private static final String WILDFLY_OPERATOR_INDEX_IMAGE = "intersmash.wildfly.operators.index_image"; private static final String WILDFLY_OPERATOR_CHANNEL = "intersmash.wildfly.operators.channel"; @@ -107,7 +107,7 @@ public class IntersmashConfig { // KEYCLOAK private static final String KEYCLOAK_IMAGE_URL = "intersmash.keycloak.image"; - private static final String KEYCLOAK_QUARKUS_IMAGE_URL = "intersmash.keycloak.quarks.image"; + private static final String KEYCLOAK_REALM_IMPORT_IMAGE_URL = "intersmash.keycloak.realm_import.image"; private static final String KEYCLOAK_TEMPLATES = "intersmash.keycloak.templates"; // ACTIVEMQ @@ -169,10 +169,6 @@ public static String keycloakOperatorPackageManifest() { return XTFConfig.get(KEYCLOAK_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST); } - public static String keycloakOperatorQuarkusPackageManifest() { - return XTFConfig.get(KEYCLOAK_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_OPERATOR_PACKAGE_MANIFEST); - } - public static String wildflyOperatorCatalogSource() { return XTFConfig.get(WILDFLY_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); } @@ -283,8 +279,8 @@ public static String keycloakImageURL() { return XTFConfig.get(KEYCLOAK_IMAGE_URL); } - public static String keycloakQuarkusImageURL() { - return XTFConfig.get(KEYCLOAK_QUARKUS_IMAGE_URL); + public static String keycloakRealmImportImageURL() { + return XTFConfig.get(KEYCLOAK_REALM_IMPORT_IMAGE_URL); } public static String keycloakProductCode() { @@ -393,19 +389,20 @@ public static String getWildflyHelmChartsBranch() { return XTFConfig.get(WILDFLY_HELM_CHARTS_BRANCH); } - public static String keycloakQuarkusOperatorCatalogSource() { - return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); + public static String keycloakRealmImportOperatorCatalogSource() { + return XTFConfig.get(KEYCLOAK_REALM_IMPORT_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); } - public static String keycloakQuarkusOperatorIndexImage() { - return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_INDEX_IMAGE); + public static String keycloakRealmImportOperatorIndexImage() { + return XTFConfig.get(KEYCLOAK_REALM_IMPORT_OPERATOR_INDEX_IMAGE); } - public static String keycloakQuarkusOperatorChannel() { - return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_CHANNEL); + public static String keycloakRealmImportOperatorChannel() { + return XTFConfig.get(KEYCLOAK_REALM_IMPORT_OPERATOR_CHANNEL); } - public static String keycloakQuarkusOperatorPackageManifest() { - return XTFConfig.get(KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_QUARKUS_OPERATOR_PACKAGE_MANIFEST); + public static String keycloakRealmImportOperatorPackageManifest() { + return XTFConfig.get(KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST, + DEFAULT_KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java index b0c00de54..c199b64cc 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java @@ -55,9 +55,7 @@ */ public class KeycloakRealmImportOperatorProvisioner extends OperatorProvisioner { - //private MixedOperation, Resource> keycloakRealmImportClient; - //private MixedOperation, Resource> keycloakClient; - private static final String OPERATOR_ID = IntersmashConfig.keycloakQuarkusOperatorPackageManifest(); + private static final String OPERATOR_ID = IntersmashConfig.keycloakRealmImportOperatorPackageManifest(); protected FailFastCheck ffCheck = () -> false; public KeycloakRealmImportOperatorProvisioner(@NonNull KeycloakRealmImportOperatorApplication application) { @@ -70,22 +68,22 @@ public static String getOperatorId() { @Override protected String getOperatorCatalogSource() { - return IntersmashConfig.keycloakQuarkusOperatorCatalogSource(); + return IntersmashConfig.keycloakRealmImportOperatorCatalogSource(); } @Override protected String getOperatorIndexImage() { - return IntersmashConfig.keycloakQuarkusOperatorIndexImage(); + return IntersmashConfig.keycloakRealmImportOperatorIndexImage(); } @Override protected String getOperatorChannel() { - return IntersmashConfig.keycloakQuarkusOperatorChannel(); + return IntersmashConfig.keycloakRealmImportOperatorChannel(); } @Override public void subscribe() { - if (Strings.isNullOrEmpty(IntersmashConfig.keycloakQuarkusImageURL())) { + if (Strings.isNullOrEmpty(IntersmashConfig.keycloakRealmImportImageURL())) { super.subscribe(); } else { // RELATED_IMAGE_RHSSO_OPENJ9 and RELATED_IMAGE_RHSSO_OPENJDK, determine the final value for RELATED_IMAGE_RHSSO @@ -96,7 +94,7 @@ public void subscribe() { // Custom Keycloak image to be used: overrides the Keycloak image at the operator level: all // Keycloak instances will be spun out of this image // e.g. OPERATOR_KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:21.1.1 --> operator.keycloak.image - "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakQuarkusImageURL() + "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakRealmImportImageURL() // "PROFILE", "RHSSO" )); } @@ -112,8 +110,8 @@ public void deploy() { // Custom Keycloak image to be used: overrides the Keycloak image at the Keycloak level: just this Keycloak // instance will be spun out of this image - if (!Strings.isNullOrEmpty(IntersmashConfig.keycloakQuarkusImageURL())) { - getApplication().getKeycloak().getSpec().setImage(IntersmashConfig.keycloakQuarkusImageURL()); + if (!Strings.isNullOrEmpty(IntersmashConfig.keycloakRealmImportImageURL())) { + getApplication().getKeycloak().getSpec().setImage(IntersmashConfig.keycloakRealmImportImageURL()); } // create keys/certificates and add them to the Keycloak resource: From bd5a77932c1f38bb57e78d1f76af833da319f02c Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Tue, 6 Jun 2023 16:48:52 +0200 Subject: [PATCH 10/11] Fix https://github.com/Intersmash/intersmash/issues/37: modifications after review --- README.md | 19 ++++++++++--------- ...oakRealmImportOperatorProvisionerTest.java | 9 +-------- .../intersmash/tools/IntersmashConfig.java | 1 - .../DBImageOpenShiftProvisioner.java | 7 ++++--- ...eycloakRealmImportOperatorProvisioner.java | 6 +----- .../PostgreSQLImageOpenShiftProvisioner.java | 2 +- .../tools/util/tls/CertificatesUtils.java | 8 +++++--- 7 files changed, 22 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 7db4bb806..ac1fbe687 100644 --- a/README.md +++ b/README.md @@ -245,15 +245,16 @@ public class PostgresqlProvisionTest { Mapping of implemented provisioners: -| Product | Application | Provisioner | Notes | -|:-----------|:-------------------------------------|:-------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ActiveMQ | ActiveMQOperatorApplication* | ActiveMQOperatorProvisioner | | -| Kafka | KafkaOperatorApplication | KafkaOperatorProvisioner | | -| Wildfly | WildflyImageOpenShiftApplication | WildflyImageOpenShiftProvisioner | Available both for Git sources and binary based s2i v2 build (either a pre-built deployment or a filesystem resource like a Maven project folder) | -| Wildfly | WildflyHelmChartOpenShiftApplication | WildflyHelmChartOpenShiftProvisioner | The `wildfly-2.3.2` tag of https://github.com/wildfly/wildfly-charts is used and the model is generated based on the https://raw.githubusercontent.com/wildfly/wildfly-charts/main/charts/wildfly/values.schema.json value schema file | | -| Infinispan | InfinispanOperatorApplication | InfinispanOperatorProvisioner | | -| Keycloak | KeycloakOpenShiftApplication | KeycloakTemplateOpenShiftProvisioner | | -| Keycloak | KeycloakOperatorApplication | KeycloakOperatorProvisioner | | +| Product | Application | Provisioner | Notes | +|:-----------|:-------------------------------------|:-------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ActiveMQ | ActiveMQOperatorApplication* | ActiveMQOperatorProvisioner | | +| Kafka | KafkaOperatorApplication | KafkaOperatorProvisioner | | +| Wildfly | WildflyImageOpenShiftApplication | WildflyImageOpenShiftProvisioner | Available both for Git sources and binary based s2i v2 build (either a pre-built deployment or a filesystem resource like a Maven project folder) | +| Wildfly | WildflyHelmChartOpenShiftApplication | WildflyHelmChartOpenShiftProvisioner | The `wildfly-2.3.2` tag of https://github.com/wildfly/wildfly-charts is used and the model is generated based on the https://raw.githubusercontent.com/wildfly/wildfly-charts/main/charts/wildfly/values.schema.json value schema file | | +| Infinispan | InfinispanOperatorApplication | InfinispanOperatorProvisioner | | +| Keycloak | KeycloakOpenShiftApplication | KeycloakTemplateOpenShiftProvisioner | | +| Keycloak | KeycloakOperatorApplication | KeycloakOperatorProvisioner | | +| Keycloak | KeycloakRealmImportOperatorApplication | KeycloakRealmImportOperatorProvisioner | The latest Quarkus based Keycloak Operator, doesn't provide stable CRDs yet (see https://www.keycloak.org/2022/09/operator-crs); this operator offers a temporary solution which supports the `Keycloak` and `KeycloakRealmImport` Custom Resources: these are the only supported CR at the time of writing | Additional services provisioners: diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java index ab9d54759..e1b2c14ce 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java @@ -33,7 +33,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.keycloak.k8s.v2alpha1.Keycloak; import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; @@ -71,7 +70,6 @@ */ @Slf4j @CleanBeforeAll -//@Disabled("WIP - Disabled until global-test.properties is configured with the required property") public class KeycloakRealmImportOperatorProvisionerTest { private static KeycloakRealmImportOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; @@ -135,12 +133,6 @@ public static void createOperatorGroup() throws IOException { OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); } - @BeforeEach - public void cleanup() throws IOException { - if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) - KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); - } - @AfterAll public static void removeOperatorGroup() { OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); @@ -225,6 +217,7 @@ public void exampleSso() { @Test public void exampleSsoWithDatabase() { try { + POSTGRESQL_IMAGE_PROVISIONER.configure(); POSTGRESQL_IMAGE_PROVISIONER.preDeploy(); POSTGRESQL_IMAGE_PROVISIONER.deploy(); diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java index a38370352..457f15546 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/IntersmashConfig.java @@ -108,7 +108,6 @@ public class IntersmashConfig { // KEYCLOAK private static final String KEYCLOAK_IMAGE_URL = "intersmash.keycloak.image"; private static final String KEYCLOAK_REALM_IMPORT_IMAGE_URL = "intersmash.keycloak.realm_import.image"; - private static final String KEYCLOAK_TEMPLATES = "intersmash.keycloak.templates"; // ACTIVEMQ private static final String ACTIVEMQ_IMAGE_URL = "intersmash.activemq.image"; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java index c5bf40de2..600a2263d 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/DBImageOpenShiftProvisioner.java @@ -68,7 +68,7 @@ public Map getImageVariables() { return vars; } - public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { + public void customizeApplication(ApplicationBuilder appBuilder) { } public abstract String getSymbolicName(); @@ -99,7 +99,7 @@ public void deploy() { .addContainerSelector("deploymentconfig", dbApplication.getName()) .addContainerSelector("name", dbApplication.getName()); - customizeApplicationBuilder(appBuilder); + customizeApplication(appBuilder); appBuilder.buildApplication(openShift).deploy(); @@ -137,7 +137,8 @@ public String getUrl(String routeName, boolean secure) { } /** - * When using {@link ApplicationBuilder} to build the application, then the service name defaults to the application nane + * Returns the service name used to expose the database functionality inside OpenShift; + * When using {@link ApplicationBuilder} to build the application (which is always the case here), then the service name defaults to the application nane * @return service name to access the database */ public String getServiceName() { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java index c199b64cc..bdc934abc 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java @@ -86,17 +86,13 @@ public void subscribe() { if (Strings.isNullOrEmpty(IntersmashConfig.keycloakRealmImportImageURL())) { super.subscribe(); } else { - // RELATED_IMAGE_RHSSO_OPENJ9 and RELATED_IMAGE_RHSSO_OPENJDK, determine the final value for RELATED_IMAGE_RHSSO subscribe( INSTALLPLAN_APPROVAL_MANUAL, - // TODO: check if these env variables still make sense in the new quarkus operator Map.of( // Custom Keycloak image to be used: overrides the Keycloak image at the operator level: all // Keycloak instances will be spun out of this image // e.g. OPERATOR_KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:21.1.1 --> operator.keycloak.image - "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakRealmImportImageURL() - // "PROFILE", "RHSSO" - )); + "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakRealmImportImageURL())); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java index 86b8f3229..834f9dbcc 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/PostgreSQLImageOpenShiftProvisioner.java @@ -86,7 +86,7 @@ public Map getImageVariables() { } @Override - public void customizeApplicationBuilder(ApplicationBuilder appBuilder) { + public void customizeApplication(ApplicationBuilder appBuilder) { // the secret is supposed to be used by applications connecting to the database appBuilder.secret(getSecretName()) .setType(SecretType.OPAQUE) diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java index 251d11025..b3d1773e9 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java @@ -45,7 +45,9 @@ public static class CertificateAndKey { /** * Generates a key and self-signed certificate for that key; it also generates a truststore containing the certificate; - * @return + * @param hostname: to be used as Common Name (CN) for the certificate + * @param tlsSecretName: name of the secret to be created in OpenShift containing key and certificate + * @return wrapper object {@link CertificateAndKey} containing details about the newly created key, certificate and secret */ public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostname, String tlsSecretName) { CertificateAndKey certificateAndKey = new CertificateAndKey(); @@ -113,11 +115,11 @@ private static void processCall(Path cwd, String... args) { } } } catch (IOException | InterruptedException e) { - throw new IllegalStateException("Failed executing " + String.join(" ", args)); + throw new RuntimeException("Failed executing " + String.join(" ", args)); } if (result != 0) { - throw new IllegalStateException("Failed executing " + String.join(" ", args)); + throw new RuntimeException("Failed executing " + String.join(" ", args)); } } From 85b22d60ec0a25236de1adef8e9ec7b1943515af Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Fri, 9 Jun 2023 12:03:01 +0200 Subject: [PATCH 11/11] Fix https://github.com/Intersmash/intersmash/issues/37: modifications after review - part 2 --- pom.xml | 10 + testsuite/pom.xml | 1 - ...oakRealmImportOperatorProvisionerTest.java | 219 +++++++++--------- .../tools/util/tls/CertificatesUtils.java | 12 +- 4 files changed, 133 insertions(+), 109 deletions(-) diff --git a/pom.xml b/pom.xml index 696298b92..79c663e92 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,11 @@ ${project.basedir}/ide-config eclipse-format.xml + 0.31-202305291230-SNAPSHOT 5.7.0 @@ -344,6 +349,11 @@ generator-annotations ${version.io.fabric8} + + io.fabric8 + openshift-client + ${version.openshift-client} + diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 00e8684a5..7f5c2e7f3 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -47,7 +47,6 @@ io.fabric8 openshift-client - ${version.openshift-client} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java index e1b2c14ce..6fc30f5f0 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java @@ -138,8 +138,6 @@ public static void removeOperatorGroup() { OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); - POSTGRESQL_IMAGE_PROVISIONER.undeploy(); - POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); } @AfterEach @@ -169,38 +167,40 @@ public void customResourcesCleanup() { */ @Test public void exampleSso() { + name = "example-sso"; + + final Keycloak keycloak = new Keycloak(); + keycloak.getMetadata().setName(name); + keycloak.getMetadata().setLabels(matchLabels); + KeycloakSpec spec = new KeycloakSpec(); + spec.setInstances(1L); + Ingress ingress = new Ingress(); + ingress.setEnabled(true); + spec.setIngress(ingress); + Hostname hostname = new Hostname(); + hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand + String tlsSecretName = name + "-tls-secret"; + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); + // add TLS config to keycloak using the secret we just created + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + spec.setHttp(http); + spec.setHostname(hostname); + keycloak.setSpec(spec); + + KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); + KEYCLOAK_OPERATOR_PROVISIONER.configure(); try { - name = "example-sso"; - - final Keycloak keycloak = new Keycloak(); - keycloak.getMetadata().setName(name); - keycloak.getMetadata().setLabels(matchLabels); - KeycloakSpec spec = new KeycloakSpec(); - spec.setInstances(1L); - Ingress ingress = new Ingress(); - ingress.setEnabled(true); - spec.setIngress(ingress); - Hostname hostname = new Hostname(); - hostname.setHostname(OpenShifts.master().generateHostname(name)); - // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand - String tlsSecretName = name + "-tls-secret"; - CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); - // add TLS config to keycloak using the secret we just created - Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); - spec.setHttp(http); - spec.setHostname(hostname); - keycloak.setSpec(spec); - - KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); - KEYCLOAK_OPERATOR_PROVISIONER.configure(); KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); - - verifyKeycloak(keycloak, true); - } finally { - if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) + try { + verifyKeycloak(keycloak, true); + } finally { KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + } + } finally { + KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); } } @@ -216,82 +216,93 @@ public void exampleSso() { */ @Test public void exampleSsoWithDatabase() { + POSTGRESQL_IMAGE_PROVISIONER.configure(); try { - POSTGRESQL_IMAGE_PROVISIONER.configure(); POSTGRESQL_IMAGE_PROVISIONER.preDeploy(); - POSTGRESQL_IMAGE_PROVISIONER.deploy(); - - name = "example-sso"; - - Keycloak keycloak = new Keycloak(); - keycloak.getMetadata().setName(name); - keycloak.getMetadata().setLabels(matchLabels); - KeycloakSpec spec = new KeycloakSpec(); - keycloak.setSpec(spec); - spec.setInstances(1L); - Ingress ingress = new Ingress(); - ingress.setEnabled(true); - spec.setIngress(ingress); - Hostname hostname = new Hostname(); - hostname.setHostname(OpenShifts.master().generateHostname(name)); - // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand - String tlsSecretName = name + "-tls-secret"; - CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils - .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); - // add TLS config to keycloak using the secret we just created - Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); - spec.setHttp(http); - spec.setHostname(hostname); - // database - Db db = new Db(); - db.setVendor("postgres"); - db.setHost(POSTGRESQL_IMAGE_PROVISIONER.getServiceName()); - db.setPort(Integer.toUnsignedLong(POSTGRESQL_IMAGE_PROVISIONER.getPort())); - UsernameSecret usernameSecret = new UsernameSecret(); - usernameSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); - usernameSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_USER_KEY); - db.setUsernameSecret(usernameSecret); - PasswordSecret passwordSecret = new PasswordSecret(); - passwordSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); - passwordSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_PASSWORD_KEY); - db.setPasswordSecret(passwordSecret); - db.setDatabase(POSTGRESQL_IMAGE_PROVISIONER.getApplication().getDbName()); - spec.setDb(db); - - realmName = "saml-basic-auth"; - KeycloakRealmImport realmImport = new KeycloakRealmImport(); - realmImport.getMetadata().setName(realmName); - realmImport.getMetadata().setLabels(matchLabels); - KeycloakRealmImportSpec spec1 = new KeycloakRealmImportSpec(); - realmImport.setSpec(spec1); - spec1.setKeycloakCRName(name); - Realm realm = new Realm(); - spec1.setRealm(realm); - realm.setId(realmName); - realm.setRealm(realmName); - realm.setEnabled(true); - List users = new ArrayList<>(); - realm.setUsers(users); - Users user1 = new Users(); - users.add(user1); - user1.setUsername("user"); - user1.setEnabled(true); - Credentials credentials = new Credentials(); - user1.setCredentials(List.of(credentials)); - credentials.setType("password"); - credentials.setValue("LOREDANABERTE1234"); - - KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); - KEYCLOAK_OPERATOR_PROVISIONER.configure(); - KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); - - verifyKeycloak(keycloak, realmImport, true); + try { + POSTGRESQL_IMAGE_PROVISIONER.deploy(); + try { + name = "example-sso"; + Keycloak keycloak = new Keycloak(); + keycloak.getMetadata().setName(name); + keycloak.getMetadata().setLabels(matchLabels); + KeycloakSpec spec = new KeycloakSpec(); + keycloak.setSpec(spec); + spec.setInstances(1L); + Ingress ingress = new Ingress(); + ingress.setEnabled(true); + spec.setIngress(ingress); + Hostname hostname = new Hostname(); + hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand + String tlsSecretName = name + "-tls-secret"; + CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils + .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), + tlsSecretName); + // add TLS config to keycloak using the secret we just created + Http http = new Http(); + http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + spec.setHttp(http); + spec.setHostname(hostname); + // database + Db db = new Db(); + db.setVendor("postgres"); + db.setHost(POSTGRESQL_IMAGE_PROVISIONER.getServiceName()); + db.setPort(Integer.toUnsignedLong(POSTGRESQL_IMAGE_PROVISIONER.getPort())); + UsernameSecret usernameSecret = new UsernameSecret(); + usernameSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); + usernameSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_USER_KEY); + db.setUsernameSecret(usernameSecret); + PasswordSecret passwordSecret = new PasswordSecret(); + passwordSecret.setName(POSTGRESQL_IMAGE_PROVISIONER.getSecretName()); + passwordSecret.setKey(PostgreSQLImageOpenShiftProvisioner.POSTGRESQL_PASSWORD_KEY); + db.setPasswordSecret(passwordSecret); + db.setDatabase(POSTGRESQL_IMAGE_PROVISIONER.getApplication().getDbName()); + spec.setDb(db); + + realmName = "saml-basic-auth"; + KeycloakRealmImport realmImport = new KeycloakRealmImport(); + realmImport.getMetadata().setName(realmName); + realmImport.getMetadata().setLabels(matchLabels); + KeycloakRealmImportSpec spec1 = new KeycloakRealmImportSpec(); + realmImport.setSpec(spec1); + spec1.setKeycloakCRName(name); + Realm realm = new Realm(); + spec1.setRealm(realm); + realm.setId(realmName); + realm.setRealm(realmName); + realm.setEnabled(true); + List users = new ArrayList<>(); + realm.setUsers(users); + Users user1 = new Users(); + users.add(user1); + user1.setUsername("user"); + user1.setEnabled(true); + Credentials credentials = new Credentials(); + user1.setCredentials(List.of(credentials)); + credentials.setType("password"); + credentials.setValue("LOREDANABERTE1234"); + + KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(keycloak, name); + KEYCLOAK_OPERATOR_PROVISIONER.configure(); + try { + KEYCLOAK_OPERATOR_PROVISIONER.subscribe(); + try { + verifyKeycloak(keycloak, true); + } finally { + KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); + } + } finally { + KEYCLOAK_OPERATOR_PROVISIONER.dismiss(); + } + } finally { + POSTGRESQL_IMAGE_PROVISIONER.undeploy(); + } + } finally { + POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); + } } finally { - if (!Objects.isNull(KEYCLOAK_OPERATOR_PROVISIONER)) - KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); - POSTGRESQL_IMAGE_PROVISIONER.undeploy(); - POSTGRESQL_IMAGE_PROVISIONER.postUndeploy(); + POSTGRESQL_IMAGE_PROVISIONER.dismiss(); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java index b3d1773e9..eaae70c93 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java @@ -6,11 +6,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.MessageFormat; import java.util.Base64; import java.util.HashMap; import java.util.Map; - -import org.junit.jupiter.api.Assertions; +import java.util.Objects; import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; @@ -69,7 +69,9 @@ public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostn caDir.resolve(truststore).toFile().exists()) { certificateAndKey.existing = true; Secret tlsSecret = OpenShifts.master().getSecret(tlsSecretName); - Assertions.assertNotNull(tlsSecret); + if (Objects.isNull(tlsSecret)) { + throw new RuntimeException(MessageFormat.format("Secret {} doesn't exist!", tlsSecretName)); + } certificateAndKey.tlsSecret = tlsSecret; return certificateAndKey; } @@ -85,7 +87,9 @@ public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostn // create secret try { Secret tlsSecret = createTlsSecret(tlsSecretName, certificateAndKey.key, certificateAndKey.certificate); - Assertions.assertNotNull(tlsSecret); + if (Objects.isNull(tlsSecret)) { + throw new RuntimeException(MessageFormat.format("Secret {} doesn't exist!", tlsSecretName)); + } certificateAndKey.tlsSecret = tlsSecret; } catch (IOException e) { throw new RuntimeException("Failed to create secret " + tlsSecretName, e);