From 8c46f737fe2254b1a4b4f86edd7d0a29e5fa9474 Mon Sep 17 00:00:00 2001 From: Tommasso Borgato Date: Thu, 11 May 2023 18:03:37 +0200 Subject: [PATCH] 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