diff --git a/.ci/openshift-ci/build-root/e2e-test.sh b/.ci/openshift-ci/build-root/e2e-test.sh index dc66cf69..5e0cf5bc 100644 --- a/.ci/openshift-ci/build-root/e2e-test.sh +++ b/.ci/openshift-ci/build-root/e2e-test.sh @@ -90,4 +90,7 @@ cat test.properties mkdir local-repo mvn clean install -Dmaven.repo.local=./local-repo -DskipTests -mvn test -Dmaven.repo.local=./local-repo -pl testsuite/ +mvn test -Dmaven.repo.local=./local-repo -pl testsuite/ -Pts.openshift \ + -Dintersmash.olm.operators.catalog_source=redhat-operators \ + -Dintersmash.olm.operators.namespace=openshift-marketplace \ + -Dintersmash.hyperfoil.operators.catalog_source=community-operators diff --git a/.github/workflows/kubernetes-e2e.yml b/.github/workflows/kubernetes-e2e.yml new file mode 100644 index 00000000..f9c86997 --- /dev/null +++ b/.github/workflows/kubernetes-e2e.yml @@ -0,0 +1,78 @@ +name: Kubernetes E2E Tests + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + paths-ignore: + - 'doc/**' + - 'ide-config/**' + - '**.md' + +concurrency: + # Only run once for latest commit per ref and cancel other (previous) runs. + group: ci-e2e-intersmash-${{ github.ref }} + cancel-in-progress: true + +jobs: + minikube: + name: K8S + runs-on: ubuntu-latest + if: github.repository == 'Intersmash/intersmash' + strategy: + fail-fast: false + matrix: + kubernetes: [v1.25.0, v1.24.0] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Minikube-Kubernetes + uses: manusa/actions-setup-minikube@v2.7.2 + with: + minikube version: v1.25.0 + kubernetes version: ${{ matrix.kubernetes }} + github token: ${{ secrets.GITHUB_TOKEN }} + start args: '--force' + - name: Cache .m2 registry + uses: actions/cache@v3 + with: + path: ~/.m2/repository + key: cache-e2e-${{ github.sha }}-${{ github.run_id }} + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Install and Run Integration Tests + run: | + set -x + # cat our ~/.kube/config contents + # + cat ~/.kube/config + # + # operator-sdk must be installed manually, see + # + export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac) + export OS=$(uname | awk '{print tolower($0)}') + export OPERATOR_SDK_DL_URL=https://github.com/operator-framework/operator-sdk/releases/download/v1.28.0 + curl -LO ${OPERATOR_SDK_DL_URL}/operator-sdk_${OS}_${ARCH} + chmod +x operator-sdk_${OS}_${ARCH} && sudo mv operator-sdk_${OS}_${ARCH} /usr/local/bin/operator-sdk + operator-sdk olm install + # + # set the Kubernetes hostname that will resolve to the minikube IP + # + export KUBERNETES_HOSTNAME=host.minikube.internal + # + # run... + # + mkdir local-repo + mvn clean install -Dmaven.repo.local=./local-repo -DskipTests + mvn test -Dmaven.repo.local=./local-repo -pl testsuite/ -Pts.k8s \ + -Dintersmash.kubernetes.url=https://$( minikube ip):8443 \ + -Dintersmash.kubernetes.hostname=$KUBERNETES_HOSTNAME \ + -Dintersmash.kubernetes.namespace=test-k8s-1 \ + -Dintersmash.olm.operators.catalog_source=community-operators \ + -Dintersmash.olm.operators.namespace=olm \ + -Dintersmash.hyperfoil.operators.catalog_source=operatorhubio-catalog diff --git a/README.md b/README.md index c8411404..7f2a8db6 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,35 @@ simulate complex use-cases where multiple Runtime products are involved. ## Platforms -Although Intersmash is designed to allow executions on different platforms, at the moment, we fully focus on OpenShift -support, support for other platforms (Kubernetes, Bare-metal, kubernetes etc.) could be added later on demand. +Although Intersmash is designed to allow executions on different platforms, we fully focus on OpenShift support +currently while Kubernetes support is being introduced. Support for other platforms (Bare-metal etc.) could be added +later on demand. + +### Conditional test execution based on test environment platforms + +Each test that is annotated by the `@Intersmash` annotation will be managed by the Intersmash test execution engine, +which relies on JUnit5 Jupiter extensions. + +This way, any annotated test class which is managed by the Intersmash execution engine can either target an +OpenShift cluster or a Kubernetes one, which is definedby the `@Intersmash` annotation `target` field, allowing for +the following values to be set: `OpenShift`, `Kubernetes`. + +By default, the `@Intersmash` annotation will configure the test class for execution on an OpenShift cluster test +environment. Therefore, the test execution engine will try to connect to an existing OpenShift cluster and perform +operations on it. +Similarly, setting `target` to `Kubernetes` will cause the test execution engine to deal with an existing Kubernetes +cluster. + +When executing tests, Intersmash can be configured through the `intersmash.junit5.execution.targets` property to +define which platforms the actual target environment supports. The property accepts a comma-separated list +based on the following values: `OpenShift`, `Kubernetes`. +For example, a developer could have one Kubernetes and one OpenShift clusters available, and set the property value +to `OpenShift,Kubernetes`. Instead, it should be set to `OpenShift` for a given CI execution where just an OpenShift +cluster is available. + +Tests are enabled or disabled conditionally, based on such information - i.e. the `target` value on an Intersmash test +_and_ the actual targets that a test environment provides: _if an Intersmash test class is set to target a given +environment, then such target must be configured through the `intersmash.junit5.execution.targets` property_. ### Something more about OpenShift diff --git a/global-test.properties b/global-test.properties index cf7829b0..0c968b78 100644 --- a/global-test.properties +++ b/global-test.properties @@ -9,6 +9,9 @@ xtf.record.always=false # Avoid starting multiple builds at once as it might cause that builds starts to hang xtf.junit.prebuilder.synchronized=true +# Intersmash JUnit5 execution target environments +intersmash.junit5.execution.targets=OpenShift + # Bootable JAR OpenJDK base image intersmash.bootable.jar.image=registry.access.redhat.com/ubi8/openjdk-17 diff --git a/pom.xml b/pom.xml index 79c663e9..83b12cd9 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,8 @@ 2.13.1 6.6.0 5.12.2 + 6.6.0 + 5.12.2 + + org.junit.platform + junit-platform-launcher + ${version.junit5.platform} + org.junit.jupiter junit-jupiter-api @@ -338,22 +352,33 @@ jsr305 ${version.com.google.code.findbugs} - + uk.org.webcompere system-stubs-jupiter ${version.junit5.jupiter.system.stubs} + test io.fabric8 generator-annotations - ${version.io.fabric8} + ${version.io.fabric8.generator} + + + io.fabric8 + kubernetes-client + ${version.io.fabric8.kubernetes-client} io.fabric8 openshift-client ${version.openshift-client} + + org.assertj + assertj-core + ${version.assertj-core} + @@ -466,7 +491,7 @@ io.fabric8 java-generator-maven-plugin - ${version.io.fabric8} + ${version.io.fabric8.generator} diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 7f5c2e7f..f260b2ae 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -48,6 +48,48 @@ io.fabric8 openshift-client + + org.jboss.intersmash + intersmash-kubernetes-client + + + org.mockito + mockito-core + test + + + uk.org.webcompere + system-stubs-jupiter + - \ No newline at end of file + + + ts.k8s + + + + maven-surefire-plugin + + ts.k8s + + + + + + + ts.openshift + + + + maven-surefire-plugin + + ts.openshift + + + + + + + + diff --git a/testsuite/src/main/java/org/jboss/intersmash/testsuite/k8s/KubernetesTest.java b/testsuite/src/main/java/org/jboss/intersmash/testsuite/k8s/KubernetesTest.java new file mode 100644 index 00000000..8c8ca747 --- /dev/null +++ b/testsuite/src/main/java/org/jboss/intersmash/testsuite/k8s/KubernetesTest.java @@ -0,0 +1,17 @@ +package org.jboss.intersmash.testsuite.k8s; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +/** + * Mark test that runs against Kubernetes. + * Used per class. + */ +@Tag("ts.k8s") +@Retention(RetentionPolicy.RUNTIME) +@Target({ java.lang.annotation.ElementType.TYPE }) +public @interface KubernetesTest { +} diff --git a/testsuite/src/main/java/org/jboss/intersmash/testsuite/openshift/OpenShiftTest.java b/testsuite/src/main/java/org/jboss/intersmash/testsuite/openshift/OpenShiftTest.java new file mode 100644 index 00000000..094e9dbb --- /dev/null +++ b/testsuite/src/main/java/org/jboss/intersmash/testsuite/openshift/OpenShiftTest.java @@ -0,0 +1,17 @@ +package org.jboss.intersmash.testsuite.openshift; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +/** + * Mark test that runs against OpenShift. + * Used per class. + */ +@Tag("ts.openshift") +@Retention(RetentionPolicy.RUNTIME) +@Target({ java.lang.annotation.ElementType.TYPE }) +public @interface OpenShiftTest { +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/EmptyExtensionContext.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/EmptyExtensionContext.java new file mode 100644 index 00000000..19e441b9 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/EmptyExtensionContext.java @@ -0,0 +1,118 @@ +/** + * 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.junit5; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstances; +import org.junit.jupiter.engine.execution.ExtensionValuesStore; +import org.junit.jupiter.engine.execution.NamespaceAwareStore; + +public class EmptyExtensionContext implements ExtensionContext { + private final Class testClazz; + private final ExtensionValuesStore valuesStore = new ExtensionValuesStore(null); + + public EmptyExtensionContext(Class testClazz) { + this.testClazz = testClazz; + } + + @Override + public Optional getParent() { + return Optional.empty(); + } + + @Override + public ExtensionContext getRoot() { + return null; + } + + @Override + public String getUniqueId() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public Set getTags() { + return null; + } + + @Override + public Optional getElement() { + return Optional.empty(); + } + + @Override + public Optional> getTestClass() { + return Optional.of(testClazz); + } + + @Override + public Optional getTestInstanceLifecycle() { + return Optional.empty(); + } + + @Override + public Optional getTestInstance() { + return Optional.empty(); + } + + @Override + public Optional getTestInstances() { + return Optional.empty(); + } + + @Override + public Optional getTestMethod() { + return Optional.empty(); + } + + @Override + public Optional getExecutionException() { + return Optional.empty(); + } + + @Override + public Optional getConfigurationParameter(String s) { + return Optional.empty(); + } + + @Override + public Optional getConfigurationParameter(String s, Function function) { + return Optional.empty(); + } + + @Override + public void publishReportEntry(Map map) { + + } + + @Override + public Store getStore(Namespace namespace) { + return new NamespaceAwareStore(this.valuesStore, namespace); + } +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/IntersmashExecutionConditionTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/IntersmashExecutionConditionTest.java new file mode 100644 index 00000000..50f9f1fb --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/junit5/IntersmashExecutionConditionTest.java @@ -0,0 +1,150 @@ +/** + * 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.junit5; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jboss.intersmash.tools.annotations.Intersmash; +import org.jboss.intersmash.tools.annotations.Service; +import org.jboss.intersmash.tools.application.k8s.KubernetesApplication; +import org.jboss.intersmash.tools.application.openshift.OpenShiftApplication; +import org.jboss.intersmash.tools.junit5.IntersmashExecutionCondition; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +import cz.xtf.core.config.XTFConfig; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +@ExtendWith(SystemStubsExtension.class) +public class IntersmashExecutionConditionTest { + + @SystemStub + private SystemProperties systemProperties; + + private static final IntersmashExecutionCondition INTERSMASH_EXECUTION_CONDITION = new IntersmashExecutionCondition(); + private static final List ALL_SUPPORTED = Stream.of("OpenShift", "Kubernetes").collect(Collectors.toList()); + + @Intersmash(value = { + @Service(OpenShiftApplication.class) + }) + class OpenShiftTargetTestClass { + + } + + @Intersmash(value = { + @Service(KubernetesApplication.class) + }) + class KubernetesTargetTestClass { + + } + + @BeforeEach + void before() { + systemProperties.set("intersmash.junit5.execution.targets", ""); + } + + @Test + void evaluateExecutionCondition_targetingOpenShiftTestIsDisabledWhenJustKubernetesIsSupported() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", "Kubernetes"); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(OpenShiftTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertTrue(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the OpenShift environment - should be disabled"); + } + + @Test + void evaluateExecutionCondition_targetingOpenshiftTestIsEnabledWhenJustOpenShiftIsSupported() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", "OpenShift"); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(OpenShiftTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertFalse(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the OpenShift environment - should be enabled"); + } + + @Test + void evaluateExecutionCondition_targetingOpenshiftTestIsEnabledWhenOpenShiftIsSupportedToo() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", ALL_SUPPORTED.stream().collect(Collectors.joining(","))); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(OpenShiftTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertFalse(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the OpenShift environment - should be enabled"); + } + + @Test + void evaluateExecutionCondition_targetingKubernetesTestIsDisabledWhenJustOpenShiftIsSupported() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", "OpenShift"); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(KubernetesTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertTrue(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the Kubernetes environment - should be disabled"); + } + + @Test + void evaluateExecutionCondition_targetingKubernetesTestIsEnabledWhenJustKubernetesIsSupported() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", "Kubernetes"); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(KubernetesTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertFalse(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the Kubernetes environment - should be enabled"); + } + + @Test + void evaluateExecutionCondition_targetingKubernetesTestIsEnabledWhenKubernetesIsSupportedToo() { + // Arrange + systemProperties.set("intersmash.junit5.execution.targets", ALL_SUPPORTED.stream().collect(Collectors.joining(","))); + XTFConfig.loadConfig(); + ExtensionContext extensionContext = new EmptyExtensionContext(KubernetesTargetTestClass.class); + // Act + ConditionEvaluationResult conditionEvaluationResult = INTERSMASH_EXECUTION_CONDITION + .evaluateExecutionCondition(extensionContext); + // Assert + Assertions.assertFalse(conditionEvaluationResult.isDisabled(), + "The test - which is targeting the Kubernetes environment - should be enabled"); + } +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/NamespaceCreationCapable.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/NamespaceCreationCapable.java new file mode 100644 index 00000000..cf8bfd09 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/NamespaceCreationCapable.java @@ -0,0 +1,8 @@ +package org.jboss.intersmash.testsuite.k8s; + +import org.jboss.intersmash.tools.k8s.junit5.KubernetesNamespaceCreator; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(KubernetesNamespaceCreator.class) +public interface NamespaceCreationCapable { +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/client/binary/KubernetesClientBinaryTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/client/binary/KubernetesClientBinaryTest.java new file mode 100644 index 00000000..d667c24c --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/k8s/client/binary/KubernetesClientBinaryTest.java @@ -0,0 +1,23 @@ +package org.jboss.intersmash.testsuite.k8s.client.binary; + +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; +import org.jboss.intersmash.tools.k8s.client.binary.KubernetesClientBinary; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class KubernetesClientBinaryTest { + + private static final KubernetesClientBinary ADMIN_BINARY = Kuberneteses.adminBinary(); + + private static final KubernetesClientBinary MASTER_BINARY = Kuberneteses.masterBinary(); + + @Test + void getClusterInfoTest() { + String actual = ADMIN_BINARY.execute("cluster-info"); + Assertions.assertTrue(actual.contains("Kubernetes control plane")); + Assertions.assertTrue(actual.contains("is running at")); + actual = MASTER_BINARY.execute("cluster-info"); + Assertions.assertTrue(actual.contains("Kubernetes control plane")); + Assertions.assertTrue(actual.contains("is running at")); + } +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/openshift/ProjectCreationCapable.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/openshift/ProjectCreationCapable.java new file mode 100644 index 00000000..a92b49b2 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/openshift/ProjectCreationCapable.java @@ -0,0 +1,9 @@ +package org.jboss.intersmash.testsuite.openshift; + +import org.junit.jupiter.api.extension.ExtendWith; + +import cz.xtf.junit5.listeners.ProjectCreator; + +@ExtendWith(ProjectCreator.class) +public interface ProjectCreationCapable { +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/k8s/HyperfoilKubernetesOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/k8s/HyperfoilKubernetesOperatorProvisionerTest.java new file mode 100644 index 00000000..7546d5c8 --- /dev/null +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/k8s/HyperfoilKubernetesOperatorProvisionerTest.java @@ -0,0 +1,206 @@ +/** + * 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.k8s; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; + +import org.jboss.intersmash.testsuite.k8s.KubernetesTest; +import org.jboss.intersmash.testsuite.k8s.NamespaceCreationCapable; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; +import org.jboss.intersmash.tools.junit5.IntersmashExtension; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; +import org.jboss.intersmash.tools.provision.k8s.HyperfoilKubernetesOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.runschema.RunStatisticsWrapper; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.HyperfoilApi; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.invoker.ApiClient; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.invoker.ApiException; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.invoker.Configuration; +import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.model.Run; +import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.HyperfoilOperatorProvisioner; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.hyperfoil.v1alpha2.Hyperfoil; +import io.hyperfoil.v1alpha2.HyperfoilBuilder; +import io.hyperfoil.v1alpha2.HyperfoilSpec; +import io.hyperfoil.v1alpha2.hyperfoilspec.Route; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@KubernetesTest +public class HyperfoilKubernetesOperatorProvisionerTest implements NamespaceCreationCapable { + private static final Logger logger = LoggerFactory.getLogger(HyperfoilKubernetesOperatorProvisionerTest.class); + private static final String NAME = "hyperfoil"; + private static final HyperfoilKubernetesOperatorProvisioner hyperfoilOperatorProvisioner = initializeOperatorProvisioner(); + + private static HyperfoilKubernetesOperatorProvisioner initializeOperatorProvisioner() { + HyperfoilKubernetesOperatorProvisioner operatorProvisioner = new HyperfoilKubernetesOperatorProvisioner( + new HyperfoilOperatorApplication() { + @Override + public Hyperfoil getHyperfoil() { + Hyperfoil hyperfoil = new HyperfoilBuilder(getName()) + .build(); + HyperfoilSpec spec = new HyperfoilSpec(); + Route route = new Route(); + route.setHost(getName()); + route.setType("http"); + spec.setRoute(route); + hyperfoil.setSpec(spec); + return hyperfoil; + + } + + @Override + public String getName() { + return NAME; + } + }); + return operatorProvisioner; + } + + @BeforeAll + public static void createOperatorGroup() throws IOException { + hyperfoilOperatorProvisioner.configure(); + IntersmashExtension.operatorCleanup(true, false); + // create operator group - this should be done by InteropExtension + Kuberneteses.adminBinary().execute("apply", "-f", + new OperatorGroup(KubernetesConfig.namespace()).save().getAbsolutePath()); + // clean any leftovers + hyperfoilOperatorProvisioner.unsubscribe(); + } + + @AfterAll + public static void removeOperatorGroup() { + Kuberneteses.adminBinary().execute("delete", "operatorgroup", "--all"); + // there might be leftovers in case of failures + Kuberneteses.admin().pods().withLabel("role", "agent").delete(); + hyperfoilOperatorProvisioner.dismiss(); + } + + /** + * Test subscription of Hyperfoil operator + */ + @Test + @Order(1) + public void subscribe() { + hyperfoilOperatorProvisioner.subscribe(); + } + + /** + * Test deploy of Hyperfoil + */ + @Test + @Order(2) + public void deploy() { + hyperfoilOperatorProvisioner.deploy(); + Assertions.assertEquals(1, hyperfoilOperatorProvisioner.getPods().size(), + "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.operatorId() + + "' after deploy"); + } + + /** + * Test running a Benchmark on Hyperfoil + */ + @Test + @Order(3) + public void benchmark() throws ApiException, InterruptedException, IOException { + ApiClient defaultClient = Configuration.getDefaultApiClient(); + defaultClient.setBasePath(hyperfoilOperatorProvisioner.getURL().toString()); + defaultClient.setVerifyingSsl(false); + + HyperfoilApi apiInstance = new HyperfoilApi(defaultClient); + String storedFilesBenchmark = "k8s-hello-world"; // String | Name of previously uploaded benchmark where extra files should be loaded from during multi-part upload. Usually this is the same benchmark unless it is being renamed. + File body = new File( + this.getClass().getClassLoader().getResource("k8s-hello-world.hf.yaml").getPath()); + try { + apiInstance.addBenchmark(null, storedFilesBenchmark, body); + } catch (ApiException err) { + logger.error("Hyperfoil Benchmark add failed:", err); + Assertions.fail("Hyperfoil Benchmark add failed: " + err.getMessage()); + } + Run run = null; + try { + run = apiInstance.startBenchmark(storedFilesBenchmark, "Hello World Benchmark", null, null, + Arrays.asList("HOST_URL=http://hyperfoil:8090")); + } catch (ApiException err) { + if (run != null) { + apiInstance.killRun(run.getId()); + } + logger.error("Hyperfoil Benchmark run failed:", err); + Assertions.fail("Hyperfoil Benchmark run failed: " + err.getMessage()); + } + + int wait = 18; + try { + while (!run.getCompleted() && wait > 0) { + Thread.sleep(10000); + run = apiInstance.getRun(run.getId()); + wait--; + } + } catch (ApiException err) { + try { + apiInstance.killRun(run.getId()); + } catch (Exception ignore) { + } + logger.error("Hyperfoil Benchmark wait failed:", err); + Assertions.fail("Hyperfoil Benchmark wait failed: " + err.getMessage()); + } + Assertions.assertTrue(run.getErrors().stream().filter(e -> !e.toString().contains("Jitter watchdog was not invoked") + && !e.toString().matches(".*CPU [0-9]+ was used for.*")).count() == 0); + Assertions.assertTrue(wait > 0); + + // consume run statistics + File allStats = apiInstance.getAllStats(run.getId()); + String JSON = Files.readString(Paths.get(allStats.getAbsolutePath())); + logger.debug("JSON: {}", JSON); + RunStatisticsWrapper runStatisticsWrapper = new RunStatisticsWrapper(JSON); + Assertions.assertTrue(runStatisticsWrapper.getPhaseStats().size() > 0); + // } + } + + /** + * Test undeploy of Hyperfoil + */ + @Test + @Order(4) + public void undeploy() { + hyperfoilOperatorProvisioner.undeploy(false); + } + + /** + * Test unsubscribe of Hyperfoil operator + */ + @Test + @Order(5) + public void unsubscribe() { + hyperfoilOperatorProvisioner.unsubscribe(); + Assertions.assertEquals(0, hyperfoilOperatorProvisioner.getPods().size(), + "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.operatorId() + + "' after deploy"); + } +} diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOpenShiftOperatorProvisionerTest.java similarity index 88% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOpenShiftOperatorProvisionerTest.java index 4a4bcfb6..2f457f02 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ActiveMQOpenShiftOperatorProvisionerTest.java @@ -19,9 +19,11 @@ import java.util.Collections; import java.util.List; -import org.jboss.intersmash.tools.application.openshift.ActiveMQOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.ActiveMQOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.ActiveMQOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.activemq.address.ActiveMQArtemisAddressBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.activemq.broker.ActiveMQArtemisBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.activemq.broker.spec.DeploymentPlanBuilder; @@ -32,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; import cz.xtf.junit5.annotations.CleanBeforeAll; @@ -42,11 +45,12 @@ @Slf4j @CleanBeforeAll -public class ActiveMQOperatorProvisionerTest { - private static final ActiveMQOperatorProvisioner activeMQOperatorProvisioner = initializeOperatorProvisioner(); +@OpenShiftTest +public class ActiveMQOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { + private static final ActiveMQOpenShiftOperatorProvisioner activeMQOperatorProvisioner = initializeOperatorProvisioner(); - private static ActiveMQOperatorProvisioner initializeOperatorProvisioner() { - ActiveMQOperatorProvisioner operatorProvisioner = new ActiveMQOperatorProvisioner( + private static ActiveMQOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + ActiveMQOpenShiftOperatorProvisioner operatorProvisioner = new ActiveMQOpenShiftOperatorProvisioner( new ActiveMQOperatorApplication() { private static final String DEFAULT_ACTIVEMQ_APP_NAME = "example-amq-broker"; @@ -76,9 +80,10 @@ public String getName() { @BeforeAll public static void createOperatorGroup() throws IOException { activeMQOperatorProvisioner.configure(); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers activeMQOperatorProvisioner.unsubscribe(); // let's configure the provisioner diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOpenShiftOperatorProvisionerTest.java similarity index 85% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOpenShiftOperatorProvisionerTest.java index aaac0c1b..b2b2ccee 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/HyperfoilOpenShiftOperatorProvisionerTest.java @@ -21,9 +21,11 @@ import java.nio.file.Paths; import java.util.Arrays; -import org.jboss.intersmash.tools.application.openshift.HyperfoilOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.HyperfoilOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.HyperfoilOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.runschema.RunStatisticsWrapper; import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.HyperfoilApi; import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.invoker.ApiClient; @@ -31,6 +33,7 @@ import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.invoker.Configuration; import org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.v05.model.Run; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.HyperfoilOperatorProvisioner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -41,20 +44,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; -import cz.xtf.junit5.annotations.CleanBeforeAll; import io.hyperfoil.v1alpha2.Hyperfoil; import io.hyperfoil.v1alpha2.HyperfoilBuilder; -@CleanBeforeAll @TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class HyperfoilOperatorProvisionerTest { - private static final Logger logger = LoggerFactory.getLogger(HyperfoilOperatorProvisionerTest.class); +@OpenShiftTest +public class HyperfoilOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { + private static final Logger logger = LoggerFactory.getLogger(HyperfoilOpenShiftOperatorProvisionerTest.class); private static final String NAME = "hyperfoil"; - private static final HyperfoilOperatorProvisioner hyperfoilOperatorProvisioner = initializeOperatorProvisioner(); + private static final HyperfoilOpenShiftOperatorProvisioner hyperfoilOperatorProvisioner = initializeOperatorProvisioner(); - private static HyperfoilOperatorProvisioner initializeOperatorProvisioner() { - HyperfoilOperatorProvisioner operatorProvisioner = new HyperfoilOperatorProvisioner( + private static HyperfoilOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + HyperfoilOpenShiftOperatorProvisioner operatorProvisioner = new HyperfoilOpenShiftOperatorProvisioner( new HyperfoilOperatorApplication() { @Override public Hyperfoil getHyperfoil() { @@ -73,9 +76,10 @@ public String getName() { @BeforeAll public static void createOperatorGroup() throws IOException { hyperfoilOperatorProvisioner.configure(); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers hyperfoilOperatorProvisioner.unsubscribe(); } @@ -105,7 +109,7 @@ public void subscribe() { public void deploy() { hyperfoilOperatorProvisioner.deploy(); Assertions.assertEquals(1, hyperfoilOperatorProvisioner.getPods().size(), - "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.getOperatorId() + "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.operatorId() + "' after deploy"); } @@ -185,7 +189,7 @@ public void undeploy() { public void unsubscribe() { hyperfoilOperatorProvisioner.unsubscribe(); Assertions.assertEquals(0, hyperfoilOperatorProvisioner.getPods().size(), - "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.getOperatorId() + "Unexpected number of cluster operator pods for '" + HyperfoilOperatorProvisioner.operatorId() + "' after deploy"); } } diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOpenShiftOperatorProvisionerTest.java similarity index 92% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOpenShiftOperatorProvisionerTest.java index 188e3266..321cff9c 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/InfinispanOpenShiftOperatorProvisionerTest.java @@ -22,9 +22,11 @@ import java.util.Map; import java.util.Objects; -import org.jboss.intersmash.tools.application.openshift.InfinispanOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.InfinispanOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.InfinispanOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.InfinispanOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.Cache; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.CacheBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.Infinispan; @@ -32,6 +34,7 @@ import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.spec.AutoscaleBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.spec.InfinispanServiceSpecBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.InfinispanOperatorProvisioner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -41,6 +44,7 @@ import cz.xtf.builder.builders.SecretBuilder; import cz.xtf.builder.builders.secret.SecretType; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShiftWaiters; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; @@ -56,17 +60,18 @@ */ @Slf4j @CleanBeforeAll -public class InfinispanOperatorProvisionerTest { +@OpenShiftTest +public class InfinispanOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { static final String TEST_SECRET_USERNAME = "developer"; static final String TEST_SECRET_PASSWORD = "developer"; static final String TEST_SECRET_NAME = "test-secret"; static final Secret TEST_SECRET = new SecretBuilder(TEST_SECRET_NAME) .setType(SecretType.OPAQUE).addData(TEST_SECRET_USERNAME, TEST_SECRET_PASSWORD.getBytes()).build(); // Be aware that since we're using the static mock application, not all provisioner methods will work as expected! - private static final InfinispanOperatorProvisioner INFINISPAN_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); + private static final InfinispanOpenShiftOperatorProvisioner INFINISPAN_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); - private static InfinispanOperatorProvisioner initializeOperatorProvisioner() { - InfinispanOperatorProvisioner operatorProvisioner = new InfinispanOperatorProvisioner( + private static InfinispanOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + InfinispanOpenShiftOperatorProvisioner operatorProvisioner = new InfinispanOpenShiftOperatorProvisioner( new InfinispanOperatorApplication() { private static final String DEFAULT_INFINISPAN_APP_NAME = "example-infinispan"; @@ -99,9 +104,10 @@ public static void createOperatorGroup() throws IOException { // let's configure the provisioner INFINISPAN_OPERATOR_PROVISIONER.configure(); matchLabels.put("app", "datagrid"); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers INFINISPAN_OPERATOR_PROVISIONER.unsubscribe(); } @@ -255,22 +261,22 @@ public void testCacheWithBasicSecretFromTemplate() { * * Does subscribe/unsubscribe on its own, so no need to call explicitly here * - * This test adds no further checks after {@link InfinispanOperatorProvisioner#undeploy()} based on - * {@link InfinispanOperatorProvisioner#getPods()}, since it looks for a stateful set which would be null at this point. - * There is room for evaluating whether to revisit {@link InfinispanOperatorProvisioner} with respect to such logic + * This test adds no further checks after {@link InfinispanOpenShiftOperatorProvisioner#undeploy()} based on + * {@link InfinispanOpenShiftOperatorProvisioner#getPods()}, since it looks for a stateful set which would be null at this point. + * There is room for evaluating whether to revisit {@link InfinispanOpenShiftOperatorProvisioner} with respect to such logic */ @Test public void basicProvisioningTest() { INFINISPAN_OPERATOR_PROVISIONER.deploy(); try { Assertions.assertEquals(1, INFINISPAN_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + INFINISPAN_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + InfinispanOperatorProvisioner.operatorId() + "' after deploy"); int scaledNum = INFINISPAN_OPERATOR_PROVISIONER.getApplication().getInfinispan().getSpec().getReplicas() + 1; INFINISPAN_OPERATOR_PROVISIONER.scale(scaledNum, true); Assertions.assertEquals(scaledNum, INFINISPAN_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + INFINISPAN_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + InfinispanOperatorProvisioner.operatorId() + "' after scaling"); } finally { INFINISPAN_OPERATOR_PROVISIONER.undeploy(); @@ -279,7 +285,7 @@ public void basicProvisioningTest() { INFINISPAN_OPERATOR_PROVISIONER.getPods() .stream().filter(pod -> Objects.isNull(pod.getMetadata().getDeletionTimestamp())) .count(), - "Unexpected number of cluster operator pods for '" + INFINISPAN_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + InfinispanOperatorProvisioner.operatorId() + "' after undeploy"); } diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOpenShiftOperatorProvisionerTest.java similarity index 84% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOpenShiftOperatorProvisionerTest.java index 2497ba9f..d265287d 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KafkaOpenShiftOperatorProvisionerTest.java @@ -17,41 +17,47 @@ import java.io.IOException; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.KafkaOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KafkaOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.KafkaOperatorProvisioner; 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 cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.junit5.annotations.CleanBeforeAll; import io.fabric8.kubernetes.api.model.DeletionPropagation; import lombok.extern.slf4j.Slf4j; /** - * Simple class that tests basic features of {@link KafkaOperatorProvisioner}. + * Simple class that tests basic features of {@link KafkaOpenShiftOperatorProvisioner}. */ @Slf4j @CleanBeforeAll -public class KafkaOperatorProvisionerTest { +@OpenShiftTest +public class KafkaOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { private static KafkaOperatorApplication application = OpenShiftProvisionerTestBase.getKafkaApplication(); - private static final KafkaOperatorProvisioner operatorProvisioner = initializeOperatorProvisioner(); + private static final KafkaOpenShiftOperatorProvisioner operatorProvisioner = initializeOperatorProvisioner(); - private static KafkaOperatorProvisioner initializeOperatorProvisioner() { - KafkaOperatorProvisioner operatorProvisioner = new KafkaOperatorProvisioner(application); + private static KafkaOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + KafkaOpenShiftOperatorProvisioner operatorProvisioner = new KafkaOpenShiftOperatorProvisioner(application); return operatorProvisioner; } @BeforeAll public static void createOperatorGroup() throws IOException { operatorProvisioner.configure(); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers operatorProvisioner.unsubscribe(); // Let's skip subscribe operation here since we use regular deploy/undeploy where subscribe is called anyway. @@ -98,7 +104,7 @@ public void basicProvisioningTest() { private void verifyDeployed() { Assertions.assertEquals(1, operatorProvisioner.getClusterOperatorPods().size(), - "Unexpected number of cluster operator pods for '" + operatorProvisioner.getOperatorId() + "'"); + "Unexpected number of cluster operator pods for '" + KafkaOperatorProvisioner.operatorId() + "'"); int kafkaReplicas = operatorProvisioner.getApplication().getKafka().getSpec().getKafka().getReplicas(); int zookeeperReplicas = operatorProvisioner.getApplication().getKafka().getSpec().getZookeeper().getReplicas(); @@ -131,7 +137,7 @@ private void verifyDeployed() { private void verifyScaledDeployed(int scaledNum) { Assertions.assertEquals(1, operatorProvisioner.getClusterOperatorPods().size(), - "Unexpected number of cluster operator pods for '" + operatorProvisioner.getOperatorId() + "'"); + "Unexpected number of cluster operator pods for '" + KafkaOperatorProvisioner.operatorId() + "'"); int zookeeperReplicas = operatorProvisioner.getApplication().getKafka().getSpec().getZookeeper().getReplicas(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOpenShiftOperatorProvisionerTest.java similarity index 95% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOpenShiftOperatorProvisionerTest.java index 0bae54d6..382be9df 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakOpenShiftOperatorProvisionerTest.java @@ -21,9 +21,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jboss.intersmash.tools.application.openshift.KeycloakOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.KeycloakOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.KeycloakOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackup; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackupBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.spec.KeycloakAWSSpecBuilder; @@ -43,6 +45,7 @@ import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.user.spec.KeycloakCredential; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.user.spec.KeycloakCredentialBuilder; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.KeycloakOperatorProvisioner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -51,6 +54,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.event.Level; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShiftWaiters; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; @@ -73,12 +77,13 @@ @Slf4j @CleanBeforeAll @Disabled("WIP - Disabled until global-test.properties is configured with the required property") -public class KeycloakOperatorProvisionerTest { +@OpenShiftTest +public class KeycloakOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { // Be aware that since we're using the static mock application, not all provisioner methods will work as expected! - private static final KeycloakOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); + private static final KeycloakOpenShiftOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); - private static KeycloakOperatorProvisioner initializeOperatorProvisioner() { - KeycloakOperatorProvisioner operatorProvisioner = new KeycloakOperatorProvisioner( + private static KeycloakOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + KeycloakOpenShiftOperatorProvisioner operatorProvisioner = new KeycloakOpenShiftOperatorProvisioner( new KeycloakOperatorApplication() { private static final String DEFAULT_KEYCLOAK_APP_NAME = "example-sso"; @@ -106,9 +111,10 @@ public String getName() { public static void createOperatorGroup() throws IOException { KEYCLOAK_OPERATOR_PROVISIONER.configure(); matchLabels.put("app", "sso"); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers KEYCLOAK_OPERATOR_PROVISIONER.unsubscribe(); } @@ -590,22 +596,22 @@ public void userWithCredentials() { * * Does subscribe/unsubscribe on its own, so no need to call explicitly here. * - * This test adds no further checks after {@link KeycloakOperatorProvisioner#undeploy()} based on - * {@link KeycloakOperatorProvisioner#getPods()}, since it looks for a stateful set which would be null at this point. - * There is room for evaluating whether to revisit {@link KeycloakOperatorProvisioner} with respect to such logic + * This test adds no further checks after {@link KeycloakOpenShiftOperatorProvisioner#undeploy()} based on + * {@link KeycloakOpenShiftOperatorProvisioner#getPods()}, since it looks for a stateful set which would be null at this point. + * There is room for evaluating whether to revisit {@link KeycloakOpenShiftOperatorProvisioner} with respect to such logic */ @Test public void basicProvisioningTest() { KEYCLOAK_OPERATOR_PROVISIONER.deploy(); try { Assertions.assertEquals(1, KEYCLOAK_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + KEYCLOAK_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + KeycloakOperatorProvisioner.operatorId() + "' after deploy"); int scaledNum = KEYCLOAK_OPERATOR_PROVISIONER.getApplication().getKeycloak().getSpec().getInstances() + 1; KEYCLOAK_OPERATOR_PROVISIONER.scale(scaledNum, true); Assertions.assertEquals(scaledNum, KEYCLOAK_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + KEYCLOAK_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + KeycloakOperatorProvisioner.operatorId() + "' after scaling"); } finally { KEYCLOAK_OPERATOR_PROVISIONER.undeploy(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisionerTest.java similarity index 89% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisionerTest.java index 6fc30f5f..1bcfe634 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisionerTest.java @@ -22,10 +22,14 @@ import java.util.Map; import java.util.Objects; -import org.jboss.intersmash.tools.application.openshift.KeycloakRealmImportOperatorApplication; +import io.fabric8.kubernetes.api.model.Secret; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; +import org.jboss.intersmash.tools.application.operator.KeycloakRealmImportOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; import org.jboss.intersmash.tools.util.tls.CertificatesUtils; @@ -49,6 +53,7 @@ import org.keycloak.k8s.v2alpha1.keycloakspec.db.UsernameSecret; import org.slf4j.event.Level; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShiftWaiters; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; @@ -70,8 +75,9 @@ */ @Slf4j @CleanBeforeAll -public class KeycloakRealmImportOperatorProvisionerTest { - private static KeycloakRealmImportOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; +@OpenShiftTest +public class KeycloakRealmImportOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { + private static KeycloakRealmImportOpenShiftOperatorProvisioner KEYCLOAK_OPERATOR_PROVISIONER; private static final String POSTGRESQL_NAME = "postgresql"; private static final String POSTGRESQL_DATABASE = "keycloak"; @@ -102,9 +108,9 @@ public String getDbName() { private static final PostgreSQLImageOpenShiftProvisioner POSTGRESQL_IMAGE_PROVISIONER = new PostgreSQLImageOpenShiftProvisioner( pgSQLApplication); - private static KeycloakRealmImportOperatorProvisioner initializeOperatorProvisioner(final Keycloak keycloak, + private static KeycloakRealmImportOpenShiftOperatorProvisioner initializeOperatorProvisioner(final Keycloak keycloak, final String appName) { - KeycloakRealmImportOperatorProvisioner operatorProvisioner = new KeycloakRealmImportOperatorProvisioner( + KeycloakRealmImportOpenShiftOperatorProvisioner operatorProvisioner = new KeycloakRealmImportOpenShiftOperatorProvisioner( new KeycloakRealmImportOperatorApplication() { @Override @@ -128,9 +134,10 @@ public String getName() { @BeforeAll public static void createOperatorGroup() throws IOException { matchLabels.put("app", "sso"); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); } @AfterAll @@ -166,7 +173,7 @@ public void customResourcesCleanup() { *
- https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples/keycloak */ @Test - public void exampleSso() { + public void exampleSso() throws IOException { name = "example-sso"; final Keycloak keycloak = new Keycloak(); @@ -179,13 +186,17 @@ public void exampleSso() { spec.setIngress(ingress); Hostname hostname = new Hostname(); hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand String tlsSecretName = name + "-tls-secret"; CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); + Secret tlsSecret = OpenShiftProvisioner.createTlsSecret(OpenShifts.master().getNamespace(), tlsSecretName, certificateAndKey.key, + certificateAndKey.certificate); + // add TLS config to keycloak using the secret we just created Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + http.setTlsSecret(tlsSecret.getMetadata().getName()); spec.setHttp(http); spec.setHostname(hostname); keycloak.setSpec(spec); @@ -215,7 +226,7 @@ public void exampleSso() { *
- https://github.com/keycloak/keycloak-operator/tree/master/deploy/examples/keycloak */ @Test - public void exampleSsoWithDatabase() { + public void exampleSsoWithDatabase() throws IOException { POSTGRESQL_IMAGE_PROVISIONER.configure(); try { POSTGRESQL_IMAGE_PROVISIONER.preDeploy(); @@ -234,14 +245,18 @@ public void exampleSsoWithDatabase() { spec.setIngress(ingress); Hostname hostname = new Hostname(); hostname.setHostname(OpenShifts.master().generateHostname(name)); + // create key, certificate and tls secret: Keycloak expects the secret to be created beforehand String tlsSecretName = name + "-tls-secret"; CertificatesUtils.CertificateAndKey certificateAndKey = CertificatesUtils .generateSelfSignedCertificateAndKey(hostname.getHostname().replaceFirst("[.].*$", ""), tlsSecretName); + Secret tlsSecret = OpenShiftProvisioner.createTlsSecret(OpenShifts.master().getNamespace(), tlsSecretName, + certificateAndKey.key, certificateAndKey.certificate); + // add TLS config to keycloak using the secret we just created Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + http.setTlsSecret(tlsSecret.getMetadata().getName()); spec.setHttp(http); spec.setHostname(hostname); // database diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/MysqlImageTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/MysqlImageTestCase.java index 8c24a0a4..6fe50614 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/MysqlImageTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/MysqlImageTestCase.java @@ -16,6 +16,8 @@ package org.jboss.intersmash.testsuite.provision.openshift; import org.assertj.core.api.Assertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.MysqlImageOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.MysqlImageOpenShiftProvisioner; import org.junit.jupiter.api.AfterAll; @@ -29,7 +31,8 @@ @CleanBeforeAll @Slf4j -public class MysqlImageTestCase { +@OpenShiftTest +public class MysqlImageTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final MysqlImageOpenShiftApplication application = OpenShiftProvisionerTestBase .getMysqlOpenShiftApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/OpenShiftProvisionerTestBase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/OpenShiftProvisionerTestBase.java index 9bde9744..e8a2b20b 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/OpenShiftProvisionerTestBase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/OpenShiftProvisionerTestBase.java @@ -28,15 +28,16 @@ import org.assertj.core.util.Strings; import org.jboss.intersmash.deployments.IntersmashDelpoyableWildflyApplication; import org.jboss.intersmash.deployments.IntersmashSharedDeployments; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.input.BinarySource; +import org.jboss.intersmash.tools.application.input.BuildInput; +import org.jboss.intersmash.tools.application.input.BuildInputBuilder; import org.jboss.intersmash.tools.application.openshift.BootableJarOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; import org.jboss.intersmash.tools.application.openshift.MysqlImageOpenShiftApplication; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; import org.jboss.intersmash.tools.application.openshift.WildflyImageOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.input.BinarySource; -import org.jboss.intersmash.tools.application.openshift.input.BuildInput; -import org.jboss.intersmash.tools.application.openshift.input.BuildInputBuilder; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; import org.jboss.intersmash.tools.util.IntersmashToolsProvisionersProperties; import cz.xtf.builder.builders.SecretBuilder; @@ -59,6 +60,7 @@ import lombok.extern.slf4j.Slf4j; @Slf4j +@OpenShiftTest public class OpenShiftProvisionerTestBase { static final EnvVar TEST_ENV_VAR = new EnvVarBuilder().withName("test-evn-key").withValue("test-evn-value").build(); static final String TEST_SECRET_FOO = "foo"; diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/PostgreSQLImageTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/PostgreSQLImageTestCase.java index 4354b53f..bd09852c 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/PostgreSQLImageTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/PostgreSQLImageTestCase.java @@ -16,6 +16,8 @@ package org.jboss.intersmash.testsuite.provision.openshift; import org.assertj.core.api.Assertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; import org.junit.jupiter.api.AfterAll; @@ -29,7 +31,8 @@ @CleanBeforeAll @Slf4j -public class PostgreSQLImageTestCase { +@OpenShiftTest +public class PostgreSQLImageTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final PostgreSQLImageOpenShiftApplication application = OpenShiftProvisionerTestBase .getPostgreSQLOpenShiftApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ProvisionerCleanupTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ProvisionerCleanupTestCase.java index 93acfe5a..b605b63d 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ProvisionerCleanupTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/ProvisionerCleanupTestCase.java @@ -17,6 +17,8 @@ import java.util.stream.Stream; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.WildflyBootableJarImageOpenShiftProvisioner; import org.junit.jupiter.api.Assertions; @@ -29,7 +31,8 @@ import io.fabric8.kubernetes.api.model.ConfigMapBuilder; @CleanBeforeEach -public class ProvisionerCleanupTestCase { +@OpenShiftTest +public class ProvisionerCleanupTestCase implements ProjectCreationCapable { protected static final OpenShift openShift = OpenShifts.master(); private static Stream provisionerProvider() { diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyBootableJarTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyBootableJarTestCase.java index 6a9a518c..014484de 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyBootableJarTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyBootableJarTestCase.java @@ -16,6 +16,8 @@ package org.jboss.intersmash.testsuite.provision.openshift; import org.assertj.core.api.Assertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.BootableJarOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.BootableJarImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.WildflyBootableJarImageOpenShiftProvisioner; @@ -29,7 +31,8 @@ import cz.xtf.junit5.annotations.CleanBeforeAll; @CleanBeforeAll -public class WildflyBootableJarTestCase { +@OpenShiftTest +public class WildflyBootableJarTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final BootableJarOpenShiftApplication application = OpenShiftProvisionerTestBase .getWildflyBootableJarOpenShiftApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyHelmChartProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyHelmChartProvisionerTest.java index 9913ad88..9744066a 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyHelmChartProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyHelmChartProvisionerTest.java @@ -15,6 +15,8 @@ */ package org.jboss.intersmash.testsuite.provision.openshift; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.helm.WildflyHelmChartOpenShiftApplication; import org.jboss.intersmash.tools.provision.helm.HelmChartOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.helm.WildflyHelmChartOpenShiftProvisioner; @@ -29,7 +31,8 @@ * programmatically */ @CleanBeforeAll -public class WildflyHelmChartProvisionerTest { +@OpenShiftTest +public class WildflyHelmChartProvisionerTest implements ProjectCreationCapable { @Test //@Disabled("No artifacts on a Maven repo which is reachable by the Pod") diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyImageProvisionerTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyImageProvisionerTestCase.java index 08a9b54f..14bf1987 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyImageProvisionerTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyImageProvisionerTestCase.java @@ -23,6 +23,8 @@ import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.WildflyImageOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.WildflyImageOpenShiftProvisioner; import org.junit.jupiter.api.AfterAll; @@ -45,7 +47,8 @@ * inside the builder image. */ @CleanBeforeAll -public class WildflyImageProvisionerTestCase { +@OpenShiftTest +public class WildflyImageProvisionerTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final WildflyImageOpenShiftApplication application = OpenShiftProvisionerTestBase .getWildflyOpenShiftImageApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyJavaxBootableJarTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyJavaxBootableJarTestCase.java index 51d127fe..589c951c 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyJavaxBootableJarTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyJavaxBootableJarTestCase.java @@ -16,6 +16,8 @@ package org.jboss.intersmash.testsuite.provision.openshift; import org.assertj.core.api.Assertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.BootableJarOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.BootableJarImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.WildflyBootableJarImageOpenShiftProvisioner; @@ -29,7 +31,8 @@ import cz.xtf.junit5.annotations.CleanBeforeAll; @CleanBeforeAll -public class WildflyJavaxBootableJarTestCase { +@OpenShiftTest +public class WildflyJavaxBootableJarTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final BootableJarOpenShiftApplication application = OpenShiftProvisionerTestBase .getWildflyBootableJarJavaxOpenShiftApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyMavenProjectTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyMavenProjectTestCase.java index e5049241..fb58a3f5 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyMavenProjectTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyMavenProjectTestCase.java @@ -23,6 +23,8 @@ import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.provision.openshift.WildflyImageOpenShiftProvisioner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -44,7 +46,8 @@ * inside the builder image via the s2i binary build process. */ @CleanBeforeAll -public class WildflyMavenProjectTestCase { +@OpenShiftTest +public class WildflyMavenProjectTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final OpenShiftProvisionerTestBase.StaticWildflyImageOpenShiftApplication application = OpenShiftProvisionerTestBase .getWildflyOpenShiftLocalBinarySourceApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOperatorProvisionerTest.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOpenShiftOperatorProvisionerTest.java similarity index 71% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOperatorProvisionerTest.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOpenShiftOperatorProvisionerTest.java index 2502edd4..e9ace76c 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOperatorProvisionerTest.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyOpenShiftOperatorProvisionerTest.java @@ -17,30 +17,34 @@ import java.io.IOException; -import org.jboss.intersmash.tools.application.openshift.WildflyOperatorApplication; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; +import org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.WildflyOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.WildflyOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServer; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServerBuilder; +import org.jboss.intersmash.tools.provision.operator.WildflyOperatorProvisioner; 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 cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.junit5.annotations.CleanBeforeAll; import io.fabric8.kubernetes.api.model.DeletionPropagation; @CleanBeforeAll -public class WildflyOperatorProvisionerTest { +@OpenShiftTest +public class WildflyOpenShiftOperatorProvisionerTest implements ProjectCreationCapable { private static final String NAME = "wildfly-operator-test"; - private static final WildflyOperatorProvisioner WILDFLY_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); + private static final WildflyOpenShiftOperatorProvisioner WILDFLY_OPERATOR_PROVISIONER = initializeOperatorProvisioner(); - private static WildflyOperatorProvisioner initializeOperatorProvisioner() { - WildflyOperatorProvisioner operatorProvisioner = new WildflyOperatorProvisioner( + private static WildflyOpenShiftOperatorProvisioner initializeOperatorProvisioner() { + WildflyOpenShiftOperatorProvisioner operatorProvisioner = new WildflyOpenShiftOperatorProvisioner( new WildflyOperatorApplication() { @Override public WildFlyServer getWildflyServer() { @@ -61,9 +65,10 @@ public String getName() { @BeforeAll public static void createOperatorGroup() throws IOException { WILDFLY_OPERATOR_PROVISIONER.configure(); - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); // clean any leftovers WILDFLY_OPERATOR_PROVISIONER.unsubscribe(); } @@ -86,25 +91,25 @@ public void customResourcesCleanup() { * * Does subscribe/unsubscribe on its own, so no need to call explicitly here * - * This test adds no further checks here because {@link WildflyOperatorProvisioner#undeploy} does the only useful + * This test adds no further checks here because {@link WildflyOpenShiftOperatorProvisioner#undeploy} does the only useful * thing, i.e. check for 0 app pods, immediately after deleting, then unsubscribe the operator. - * Checking through {@link WildflyOperatorProvisioner#getPods()} here would intermittently find a resumed app pod + * Checking through {@link WildflyOpenShiftOperatorProvisioner#getPods()} here would intermittently find a resumed app pod * before unsubscribe (which doesn't wait ATM) finishes. - * Basically there could be room for revisiting {@link WildflyOperatorProvisioner#undeploy} and - * {@link OperatorProvisioner#unsubscribe()} + * Basically there could be room for revisiting {@link WildflyOpenShiftOperatorProvisioner#undeploy} and + * {@link org.jboss.intersmash.tools.provision.operator.OperatorProvisioner#unsubscribe()} */ @Test public void basicProvisioningTest() { WILDFLY_OPERATOR_PROVISIONER.deploy(); try { Assertions.assertEquals(1, WILDFLY_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + WILDFLY_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + WildflyOperatorProvisioner.operatorId() + "' after deploy"); int scaledNum = WILDFLY_OPERATOR_PROVISIONER.getApplication().getWildflyServer().getSpec().getReplicas() + 1; WILDFLY_OPERATOR_PROVISIONER.scale(scaledNum, true); Assertions.assertEquals(scaledNum, WILDFLY_OPERATOR_PROVISIONER.getPods().size(), - "Unexpected number of cluster operator pods for '" + WILDFLY_OPERATOR_PROVISIONER.getOperatorId() + "Unexpected number of cluster operator pods for '" + WildflyOperatorProvisioner.operatorId() + "' after scaling"); } finally { WILDFLY_OPERATOR_PROVISIONER.undeploy(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyTargetServerTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyTargetServerTestCase.java index 23e98938..7709f829 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyTargetServerTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/WildflyTargetServerTestCase.java @@ -23,6 +23,8 @@ import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; +import org.jboss.intersmash.testsuite.openshift.OpenShiftTest; +import org.jboss.intersmash.testsuite.openshift.ProjectCreationCapable; import org.jboss.intersmash.tools.application.openshift.WildflyImageOpenShiftApplication; import org.jboss.intersmash.tools.provision.openshift.WildflyImageOpenShiftProvisioner; import org.junit.jupiter.api.AfterAll; @@ -46,7 +48,8 @@ * which are compatible with a WildFly/EAP s2i v2 binary build. */ @CleanBeforeAll -public class WildflyTargetServerTestCase { +@OpenShiftTest +public class WildflyTargetServerTestCase implements ProjectCreationCapable { private static final OpenShift openShift = OpenShifts.master(); private static final WildflyImageOpenShiftApplication application = OpenShiftProvisionerTestBase .getWildflyOpenShiftLocalBinaryTargetServerApplication(); diff --git a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OperatorSubscriptionTestCase.java b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OpenShiftOperatorSubscriptionTestCase.java similarity index 62% rename from testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OperatorSubscriptionTestCase.java rename to testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OpenShiftOperatorSubscriptionTestCase.java index 34f69eb8..4251e1f1 100644 --- a/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OperatorSubscriptionTestCase.java +++ b/testsuite/src/test/java/org/jboss/intersmash/testsuite/provision/openshift/operator/OpenShiftOperatorSubscriptionTestCase.java @@ -18,29 +18,31 @@ import static org.mockito.Mockito.mock; import java.io.IOException; +import java.util.List; import java.util.stream.Stream; -import org.jboss.intersmash.tools.application.openshift.ActiveMQOperatorApplication; -import org.jboss.intersmash.tools.application.openshift.HyperfoilOperatorApplication; -import org.jboss.intersmash.tools.application.openshift.InfinispanOperatorApplication; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; -import org.jboss.intersmash.tools.application.openshift.KeycloakOperatorApplication; -import org.jboss.intersmash.tools.application.openshift.WildflyOperatorApplication; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; +import org.jboss.intersmash.tools.application.operator.InfinispanOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KeycloakOperatorApplication; +import org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; import org.jboss.intersmash.tools.junit5.IntersmashExtension; -import org.jboss.intersmash.tools.provision.openshift.ActiveMQOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.HyperfoilOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.InfinispanOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.KafkaOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.KeycloakOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.WildflyOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.ActiveMQOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.HyperfoilOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.InfinispanOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KafkaOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.WildflyOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; import cz.xtf.junit5.annotations.CleanBeforeAll; import io.fabric8.kubernetes.api.model.Pod; @@ -51,29 +53,30 @@ */ @Slf4j @CleanBeforeAll -public class OperatorSubscriptionTestCase { +public class OpenShiftOperatorSubscriptionTestCase { private static Stream provisionerProvider() { return Stream.of( - new ActiveMQOperatorProvisioner(mock(ActiveMQOperatorApplication.class)), - new HyperfoilOperatorProvisioner(mock(HyperfoilOperatorApplication.class)), - new InfinispanOperatorProvisioner(mock(InfinispanOperatorApplication.class)), - new KafkaOperatorProvisioner(mock(KafkaOperatorApplication.class)), - new KeycloakOperatorProvisioner(mock(KeycloakOperatorApplication.class)), - new WildflyOperatorProvisioner(mock(WildflyOperatorApplication.class))); + new ActiveMQOpenShiftOperatorProvisioner(mock(ActiveMQOperatorApplication.class)), + new HyperfoilOpenShiftOperatorProvisioner(mock(HyperfoilOperatorApplication.class)), + new InfinispanOpenShiftOperatorProvisioner(mock(InfinispanOperatorApplication.class)), + new KafkaOpenShiftOperatorProvisioner(mock(KafkaOperatorApplication.class)), + new KeycloakOpenShiftOperatorProvisioner(mock(KeycloakOperatorApplication.class)), + new WildflyOpenShiftOperatorProvisioner(mock(WildflyOperatorApplication.class))); } @BeforeAll public static void createOperatorGroup() throws IOException { - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); // create operator group - this should be done by InteropExtension - OpenShifts.adminBinary().execute("apply", "-f", OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); } @AfterAll public static void removeOperatorGroup() { // remove operator group - this should be done by InteropExtension - IntersmashExtension.operatorCleanup(); + IntersmashExtension.operatorCleanup(false, true); } @ParameterizedTest(name = "{displayName}#class({0})") @@ -85,7 +88,8 @@ public void operatorSubscriptionTest(OperatorProvisioner operatorProvisioner) { try { log.debug("Pods:"); OpenShifts.master().getPods().forEach(this::introducePod); - Assertions.assertTrue(operatorProvisioner.getCustomResourceDefinitions().size() > 0, + // TODO - fix cast + Assertions.assertTrue(((List) operatorProvisioner.retrieveCustomResourceDefinitions().list()).size() > 0, String.format("List of CRDs provided by operator [%s] should not be empty.", operatorProvisioner.getPackageManifestName())); } finally { diff --git a/tools/intersmash-tools-provisioners/src/main/resources/META-INF/services/org.jboss.intersmash.tools.provision.ProvisionerFactory b/testsuite/src/test/resources/META-INF/services/org.jboss.intersmash.tools.provision.ProvisionerFactory similarity index 100% rename from tools/intersmash-tools-provisioners/src/main/resources/META-INF/services/org.jboss.intersmash.tools.provision.ProvisionerFactory rename to testsuite/src/test/resources/META-INF/services/org.jboss.intersmash.tools.provision.ProvisionerFactory diff --git a/tools/intersmash-tools-provisioners/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/testsuite/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension similarity index 100% rename from tools/intersmash-tools-provisioners/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension rename to testsuite/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension diff --git a/testsuite/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/testsuite/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener index 63b7383d..cb9f8fa0 100644 --- a/testsuite/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener +++ b/testsuite/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -1,2 +1,2 @@ cz.xtf.junit5.listeners.TestExecutionLogger -cz.xtf.junit5.listeners.ProjectCreator + diff --git a/tools/intersmash-kubernetes-client/pom.xml b/tools/intersmash-kubernetes-client/pom.xml new file mode 100644 index 00000000..c85ededb --- /dev/null +++ b/tools/intersmash-kubernetes-client/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + org.jboss.intersmash + intersmash-tools + 0.0.1-SNAPSHOT + + + intersmash-kubernetes-client + + + ${project.parent.parent.basedir}/ide-config + + + + + io.fabric8 + kubernetes-client + + + org.apache.commons + commons-lang3 + + + com.google.code.gson + gson + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.projectlombok + lombok + + + + org.slf4j + jcl-over-slf4j + + + + cz.xtf + core + + + + org.junit.platform + junit-platform-launcher + + + org.junit.jupiter + junit-jupiter-api + + + + uk.org.webcompere + system-stubs-jupiter + test + + + + org.assertj + assertj-core + test + + + + + + \ No newline at end of file diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/KubernetesConfig.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/KubernetesConfig.java new file mode 100644 index 00000000..2f3e5c79 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/KubernetesConfig.java @@ -0,0 +1,163 @@ +package org.jboss.intersmash.tools.k8s; + +import java.nio.file.Paths; + +import cz.xtf.core.config.XTFConfig; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class KubernetesConfig { + public static final String KUBERNETES_URL = "intersmash.kubernetes.url"; + public static final String KUBERNETES_HOSTNAME = "intersmash.kubernetes.hostname"; + public static final String KUBERNETES_TOKEN = "intersmash.kubernetes.token"; + public static final String KUBERNETES_VERSION = "intersmash.kubernetes.version"; + public static final String KUBERNETES_NAMESPACE = "intersmash.kubernetes.namespace"; + public static final String KUBERNETES_BINARY_PATH = "intersmash.kubernetes.binary.path"; + public static final String KUBERNETES_BINARY_CACHE_ENABLED = "intersmash.kubernetes.binary.cache.enabled"; + public static final String KUBERNETES_BINARY_CACHE_PATH = "intersmash.kubernetes.binary.cache.path"; + public static final String KUBERNETES_BINARY_CACHE_DEFAULT_FOLDER = "kubectl-cache"; + public static final String KUBERNETES_ADMIN_USERNAME = "intersmash.kubernetes.admin.username"; + public static final String KUBERNETES_ADMIN_PASSWORD = "intersmash.kubernetes.admin.password"; + public static final String KUBERNETES_ADMIN_KUBECONFIG = "intersmash.kubernetes.admin.kubeconfig"; + public static final String KUBERNETES_ADMIN_TOKEN = "intersmash.kubernetes.admin.token"; + public static final String KUBERNETES_MASTER_USERNAME = "intersmash.kubernetes.master.username"; + public static final String KUBERNETES_MASTER_PASSWORD = "intersmash.kubernetes.master.password"; + public static final String KUBERNETES_MASTER_KUBECONFIG = "intersmash.kubernetes.master.kubeconfig"; + public static final String KUBERNETES_MASTER_TOKEN = "intersmash.kubernetes.master.token"; + public static final String KUBERNETES_ROUTE_DOMAIN = "intersmash.kubernetes.route_domain"; + public static final String KUBERNETES_PULL_SECRET = "intersmash.kubernetes.pullsecret"; + public static final String KUBERNETES_NAMESPACE_PER_TESTCASE = "intersmash.kubernetes.namespace.per.testcase"; + + /** + * Used only if intersmash.kubernetes.namespace.per.testcase=true - this property can configure its maximum length. This is useful + * in case + * where namespace is used in first part of URL of route which must have <64 chars length. + */ + public static final String KUBERNETES_NAMESPACE_NAME_LENGTH_LIMIT = "intersmash.kubernetes.namespace.per.testcase.length.limit"; + + /** + * Used only if intersmash.kubernetes.namespace.per.testcase=true - this property configures default maximum length of namespace + * name. + */ + private static final String DEFAULT_KUBERNETES_NAMESPACE_NAME_LENGTH_LIMIT = "25"; + + public static String url() { + return XTFConfig.get(KUBERNETES_URL); + } + + public static String getKubernetesHostname() { + return XTFConfig.get(KUBERNETES_HOSTNAME, "localhost"); + } + + private static final String CLEAN_KUBERNETES = "intersmash.junit.clean_namespace"; + + /** + * Used only if intersmash.kubernetes.namespace.per.testcase=true + * + * @return limit on namespace if it's set by -Dintersmash.kubernetes.namespace.per.testcase.length.limit property + */ + public static int getNamespaceLengthLimitForUniqueNamespacePerTest() { + return Integer.parseInt(XTFConfig.get(KUBERNETES_NAMESPACE_NAME_LENGTH_LIMIT, + DEFAULT_KUBERNETES_NAMESPACE_NAME_LENGTH_LIMIT)); + } + + public static boolean cleanKubernetes() { + return Boolean.valueOf(XTFConfig.get(CLEAN_KUBERNETES, "false")); + } + + /** + * @return if property xtf.openshift.namespace.per.testcase is empty or true then returns true otherwise false + */ + public static boolean useNamespacePerTestCase() { + return XTFConfig.get(KUBERNETES_NAMESPACE_PER_TESTCASE) != null + && (XTFConfig.get(KUBERNETES_NAMESPACE_PER_TESTCASE).equals("") + || XTFConfig.get(KUBERNETES_NAMESPACE_PER_TESTCASE).toLowerCase().equals("true")); + } + + /** + * @return returns token + * @deprecated Use masterToken {@link #masterToken()} + */ + @Deprecated + public static String token() { + String token = XTFConfig.get(KUBERNETES_TOKEN); + if (token == null) { + return XTFConfig.get(KUBERNETES_MASTER_TOKEN); + } + return token; + } + + public static String adminToken() { + return XTFConfig.get(KUBERNETES_ADMIN_TOKEN); + } + + public static String version() { + return XTFConfig.get(KUBERNETES_VERSION); + } + + /** + * Default namespace for currently running test. + * + * @return Returns namespace as defined in intersmash.kubernetes.namespace property + */ + public static String namespace() { + return XTFConfig.get(KUBERNETES_NAMESPACE); + } + + public static String binaryPath() { + return XTFConfig.get(KUBERNETES_BINARY_PATH); + } + + public static boolean isBinaryCacheEnabled() { + return Boolean.parseBoolean(XTFConfig.get(KUBERNETES_BINARY_CACHE_ENABLED, "true")); + } + + public static String binaryCachePath() { + return XTFConfig.get(KUBERNETES_BINARY_CACHE_PATH, Paths.get(System.getProperty("java.io.tmpdir"), + KUBERNETES_BINARY_CACHE_DEFAULT_FOLDER).toAbsolutePath().normalize().toString()); + } + + public static String adminUsername() { + return XTFConfig.get(KUBERNETES_ADMIN_USERNAME); + } + + public static String adminPassword() { + return XTFConfig.get(KUBERNETES_ADMIN_PASSWORD); + } + + public static String adminKubeconfig() { + return XTFConfig.get(KUBERNETES_ADMIN_KUBECONFIG); + } + + public static String masterUsername() { + return XTFConfig.get(KUBERNETES_MASTER_USERNAME); + } + + public static String masterPassword() { + return XTFConfig.get(KUBERNETES_MASTER_PASSWORD); + } + + public static String masterKubeconfig() { + return XTFConfig.get(KUBERNETES_MASTER_KUBECONFIG); + } + + public static String pullSecret() { + return XTFConfig.get(KUBERNETES_PULL_SECRET); + } + + /** + * @return For backwards-compatibility reasons, also returns the value of intersmash.kubernetes.token if + * intersmash.kubernetes.master.token not specified + */ + public static String masterToken() { + String masterToken = XTFConfig.get(KUBERNETES_MASTER_TOKEN); + if (masterToken == null) { + return XTFConfig.get(KUBERNETES_TOKEN); + } + return masterToken; + } + + public static String routeDomain() { + return XTFConfig.get(KUBERNETES_ROUTE_DOMAIN); + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kubernetes.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kubernetes.java new file mode 100644 index 00000000..f8cd3390 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kubernetes.java @@ -0,0 +1,406 @@ +package org.jboss.intersmash.tools.k8s.client; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + +import cz.xtf.core.config.WaitingConfig; +import cz.xtf.core.openshift.crd.CustomResourceDefinitionContextProvider; +import cz.xtf.core.waiting.SimpleWaiter; +import cz.xtf.core.waiting.Waiter; +import cz.xtf.core.waiting.failfast.FailFastCheck; +import io.fabric8.kubernetes.api.builder.Visitor; +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.GenericKubernetesResource; +import io.fabric8.kubernetes.api.model.GenericKubernetesResourceList; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; +import io.fabric8.kubernetes.api.model.ObjectReferenceBuilder; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.api.model.ServiceAccount; +import io.fabric8.kubernetes.api.model.ServiceAccountBuilder; +import io.fabric8.kubernetes.api.model.rbac.RoleBinding; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.DefaultKubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Kubernetes extends DefaultKubernetesClient { + + private static ServiceLoader crdContextProviderLoader; + + /** + * This label is supposed to be used for any resource created by Intersmash to easily distinguish which resources have + * been created by Intersmash automation. + * NOTE: at the moment only place where this is used is for labeling namespaces. Other usages may be added in the future. + */ + public static final String INTERSMASH_MANAGED_LABEL = "intersmash/managed"; + /** + * Used to cache created Kubernetes clients for given test case. + */ + public static final Multimap namespaceToKubernetesClientMap = Multimaps + .synchronizedListMultimap(ArrayListMultimap.create()); + private static final String KEEP_LABEL = "intersmash/keep"; + + /** + * Autoconfigures the client with the default fabric8 client rules + * + * @param namespace set namespace to the Kubernetes client instance + * @return this Kubernetes client instance + */ + public static Kubernetes get(String namespace) { + Config kubeconfig = Config.autoConfigure(null); + + setupTimeouts(kubeconfig); + + if (StringUtils.isNotEmpty(namespace)) { + kubeconfig.setNamespace(namespace); + } + + return get(kubeconfig); + } + + public static Kubernetes get(Path kubeconfigPath, String namespace) { + try { + String kubeconfigContents = new String(Files.readAllBytes(kubeconfigPath), StandardCharsets.UTF_8); + Config kubeconfig = Config.fromKubeconfig(null, kubeconfigContents, kubeconfigPath.toAbsolutePath().toString()); + + setupTimeouts(kubeconfig); + + if (StringUtils.isNotEmpty(namespace)) { + kubeconfig.setNamespace(namespace); + } + + return get(kubeconfig); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Kubernetes get(String masterUrl, String namespace, String username, String password) { + Config kubeconfig = new ConfigBuilder() + .withMasterUrl(masterUrl) + .withTrustCerts(true) + .withNamespace(namespace) + .withUsername(username) + .withPassword(password).build(); + + setupTimeouts(kubeconfig); + + return get(kubeconfig); + } + + public static Kubernetes get(String masterUrl, String namespace, String token) { + Config kubeconfig = Config.empty(); + kubeconfig.setMasterUrl(masterUrl); + kubeconfig.setTrustCerts(true); + kubeconfig.setNamespace(namespace); + kubeconfig.setOauthToken(token); + + setupTimeouts(kubeconfig); + + return get(kubeconfig); + } + + public Kubernetes(Config kubeconfig) { + super(kubeconfig); + } + + public void setupPullSecret(String secret) { + setupPullSecret("xtf-pull-secret", secret); + } + + /** + * Convenient method to create pull secret for authenticated image registries. + * The secret content must be provided in "dockerconfigjson" formar. + * + * E.g.: {@code {"auths":{"registry.redhat.io":{"auth":""}}}} + * + * TODO - Check Linking Secret to ServiceAccount + * + * @param name of the Secret to be created + * @param secret content of Secret in json format + */ + public void setupPullSecret(String name, String secret) { + Secret pullSecret = new SecretBuilder() + .withNewMetadata() + .withNewName(name) + .addToLabels(Kubernetes.KEEP_LABEL, "true") + .endMetadata() + .withNewType("kubernetes.io/dockerconfigjson") + .withData(Collections.singletonMap(".dockerconfigjson", Base64.getEncoder().encodeToString(secret.getBytes()))) + .build(); + secrets().createOrReplace(pullSecret); + serviceAccounts().withName("default").edit(new Visitor() { + @Override + public void visit(ServiceAccountBuilder builder) { + builder.addToImagePullSecrets( + new LocalObjectReferenceBuilder().withName(pullSecret.getMetadata().getName()).build()); + } + }); + + serviceAccounts().withName("builder").edit(new Visitor() { + @Override + public void visit(ServiceAccountBuilder builder) { + builder.addToSecrets(new ObjectReferenceBuilder().withName(pullSecret.getMetadata().getName()).build()); + } + }); + } + + private static Kubernetes get(Config kubeconfig) { + Kubernetes kubernetes; + + // check whether such a client already exists + Optional optionalKubernetes = namespaceToKubernetesClientMap + .get(kubeconfig.getNamespace()).stream() + .filter(kc -> isEqualConfig(kubeconfig, kc.getConfiguration())) + .findFirst(); + + if (optionalKubernetes.isPresent()) { + return optionalKubernetes.get(); + } else { + kubernetes = new Kubernetes(kubeconfig); + namespaceToKubernetesClientMap.put(kubeconfig.getNamespace(), kubernetes); + } + return kubernetes; + } + + private static void setupTimeouts(Config config) { + //___*** (ShipWright?)config.setBuildTimeout(10 * 60 * 1000); + config.setRequestTimeout(120_000); + config.setConnectionTimeout(120_000); + } + + protected static synchronized ServiceLoader getCRDContextProviders() { + if (crdContextProviderLoader == null) { + crdContextProviderLoader = ServiceLoader.load(CustomResourceDefinitionContextProvider.class); + } + return crdContextProviderLoader; + } + + private static boolean isEqualConfig(Config newConfig, Config existingConfig) { + return new EqualsBuilder() + .append(newConfig.getMasterUrl(), existingConfig.getMasterUrl()) + .append(newConfig.getNamespace(), existingConfig.getNamespace()) + .append(newConfig.getUsername(), existingConfig.getUsername()) + .append(newConfig.getPassword(), existingConfig.getPassword()) + .append(newConfig.getRequestConfig().getOauthToken(), existingConfig.getRequestConfig().getOauthToken()) + .append(newConfig.isTrustCerts(), existingConfig.isTrustCerts()) + .isEquals(); + } + + /** + * Retrieves all configmaps but "kube-root-ca.crt" and "openshift-service-ca.crt" which are created out of the box. + * + * @return List of configmaps created by user + */ + public List getUserConfigMaps() { + return configMaps().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems().stream() + .filter(cm -> !cm.getMetadata().getName().equals("kube-root-ca.crt")) + //.filter(cm -> !cm.getMetadata().getName().equals("openshift-service-ca.crt")) + .collect(Collectors.toList()); + } + + /** + * Retrieves secrets that aren't considered default. Secrets that are left out contain type starting with 'kubernetes.io/'. + * + * @return List of secrets that aren't considered default. + */ + public List getUserSecrets() { + return secrets().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems().stream() + .filter(s -> !s.getType().startsWith("kubernetes.io/")) + .collect(Collectors.toList()); + } + + /** + * Retrieves service accounts that aren't considered default. + * Service accounts that are left out from list: + *
    + *
  • builder
  • + *
  • default
  • + *
  • deployer
  • + *
+ * + * @return List of service accounts that aren't considered default. + */ + public List getUserServiceAccounts() { + return serviceAccounts().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems().stream() + .filter(sa -> !sa.getMetadata().getName().matches("builder|default|deployer")) + .collect(Collectors.toList()); + } + + /** + * Retrieves role bindings that aren't considered default. + * Role bindings that are left out from list: + *
    + *
  • admin
  • + *
  • system:deployers
  • + *
  • system:image-builders
  • + *
  • system:image-pullers
  • + *
+ * + * @return List of role bindings that aren't considered default. + */ + public List getUserRoleBindings() { + return rbac().roleBindings().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true") + .withLabelNotIn("olm.owner.kind", "ClusterServiceVersion").list().getItems().stream() + .filter(rb -> !rb.getMetadata().getName() + .matches("admin|system:deployers|system:image-builders|system:image-pullers")) + .collect(Collectors.toList()); + } + + public Waiter clean() { + for (CustomResourceDefinitionContextProvider crdContextProvider : Kubernetes.getCRDContextProviders()) { + try { + customResources(crdContextProvider.getContext(), GenericKubernetesResource.class, + GenericKubernetesResourceList.class) + .inNamespace(getNamespace()).delete(); + log.debug("DELETE :: " + crdContextProvider.getContext().getName() + " instances"); + } catch (KubernetesClientException kce) { + log.debug(crdContextProvider.getContext().getName() + " might not be installed on the cluster.", kce); + } + } + + /* Only OpenShift has the following ones, which are missing from k8s + templates().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + deploymentConfigs().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + buildConfigs().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + imageStreams().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + builds().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + routes().withLabelNotIn(KEEP_LABEL, "", "true").delete(); + */ + + // keep the order for deletion to prevent K8s creating resources again + apps().deployments().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + apps().replicaSets().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + apps().statefulSets().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + batch().jobs().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + replicationControllers().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + endpoints().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + services().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + pods().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").withGracePeriod(0).delete(); + persistentVolumeClaims().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + autoscaling().v1().horizontalPodAutoscalers().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").delete(); + + getUserConfigMaps().forEach(c -> configMaps().delete(c)); + getUserSecrets().forEach(s -> secrets().delete(s)); + getUserServiceAccounts().forEach((sa) -> { + serviceAccounts().delete(sa); + }); + getUserRoleBindings().forEach(r -> rbac().roleBindings().delete(r)); + rbac().roles().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true") + .withLabelNotIn("olm.owner.kind", "ClusterServiceVersion") + .delete(); + + for (HasMetadata hasMetadata : listRemovableResources()) { + log.warn("DELETE LEFTOVER :: " + hasMetadata.getKind() + "/" + hasMetadata.getMetadata().getName()); + resource(hasMetadata).withGracePeriod(0).cascading(true).delete(); + } + + FailFastCheck failFastCheck = () -> false; + return new SimpleWaiter( + () -> isNamespaceClean(), + TimeUnit.MILLISECONDS, WaitingConfig.timeoutCleanup(), "Cleaning project - " + getNamespace()) + .onTimeout(() -> log.info("Cleaning namespace: " + getNamespace() + " - timed out.")) + .onFailure(() -> log.info("Cleaning namespace: " + getNamespace() + " - failed.")) + .onSuccess(() -> log.info("Cleaning namespace: " + getNamespace() + " - finished.")) + .failFast(failFastCheck); + } + + List listRemovableResources() { + /* Only OpenShift has the following ones, which are missing from k8s + removables.addAll(templates().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(deploymentConfigs().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(buildConfigs().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(imageStreams().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(builds().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(routes().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + */ + + // keep the order for deletion to prevent K8s creating resources again + List removables = new ArrayList<>(); + removables.addAll(apps().deployments().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(apps().replicaSets().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(batch().jobs().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(apps().statefulSets().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(replicationControllers().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(endpoints().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(services().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(pods().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(persistentVolumeClaims().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list().getItems()); + removables.addAll(autoscaling().v1().horizontalPodAutoscalers().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true").list() + .getItems()); + removables.addAll(getUserConfigMaps()); + removables.addAll(getUserSecrets()); + removables.addAll(getUserServiceAccounts()); + removables.addAll(getUserRoleBindings()); + removables.addAll(rbac().roles().withLabelNotIn(Kubernetes.KEEP_LABEL, "", "true") + .withLabelNotIn("olm.owner.kind", "ClusterServiceVersion").list().getItems()); + + return removables; + } + + private boolean isNamespaceClean() { + int crdInstances = 0; + List customResourceDefinitionList = null; + for (CustomResourceDefinitionContextProvider crdContextProvider : Kubernetes.getCRDContextProviders()) { + try { + customResourceDefinitionList = customResources(crdContextProvider.getContext(), + GenericKubernetesResource.class, GenericKubernetesResourceList.class) + .inNamespace(getNamespace()) + .list().getItems(); + crdInstances += customResourceDefinitionList.size(); + } catch (KubernetesClientException kce) { + // CRD might not be installed on the cluster + } + } + + boolean isClean = false; + List listRemovableResources = listRemovableResources(); + if (crdInstances == 0 & listRemovableResources.isEmpty()) { + isClean = true; + } else { + StringBuilder strBuilderResourcesToDelete = new StringBuilder( + "Cleaning project - " + getNamespace() + + " Waiting for following resources to be deleted: \n"); + if (customResourceDefinitionList != null && !customResourceDefinitionList.isEmpty()) { + customResourceDefinitionList.stream().forEach((r) -> { + strBuilderResourcesToDelete.append(r + "\n"); + }); + } + if (!listRemovableResources.isEmpty()) { + listRemovableResources.stream().forEach((r) -> { + strBuilderResourcesToDelete.append(r + "\n"); + }); + } + log.debug(strBuilderResourcesToDelete.toString()); + } + return isClean; + } + + public String generateHostname() { + log.info("Kubernetes generateHostname returns: " + KubernetesConfig.getKubernetesHostname()); + return KubernetesConfig.getKubernetesHostname(); + } + +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kuberneteses.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kuberneteses.java new file mode 100644 index 00000000..4cde250d --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/Kuberneteses.java @@ -0,0 +1,223 @@ +package org.jboss.intersmash.tools.k8s.client; + +import java.io.File; +import java.nio.file.Paths; + +import org.apache.commons.lang3.StringUtils; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.binary.KubernetesClientBinary; +import org.jboss.intersmash.tools.k8s.client.binary.KubernetesClientBinaryManagerFactory; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Kuberneteses { + + public static Kubernetes admin() { + return Kuberneteses.admin(NamespaceManager.getNamespace()); + } + + public static Kubernetes admin(String namespace) { + if (StringUtils.isNotEmpty(KubernetesConfig.adminToken())) { + return Kubernetes.get(KubernetesConfig.url(), namespace, KubernetesConfig.adminToken()); + } + + if (StringUtils.isNotEmpty(KubernetesConfig.adminUsername())) { + return Kubernetes.get(KubernetesConfig.url(), namespace, KubernetesConfig.adminUsername(), + KubernetesConfig.adminPassword()); + } + + if (StringUtils.isNotEmpty(KubernetesConfig.adminKubeconfig())) { + return Kubernetes.get(Paths.get(KubernetesConfig.adminKubeconfig()), namespace); + } + + return Kubernetes.get(namespace); + } + + public static Kubernetes master() { + + return Kuberneteses.master(NamespaceManager.getNamespace()); + + } + + public static Kubernetes master(String namespace) { + if (StringUtils.isNotEmpty(KubernetesConfig.masterToken())) { + return Kubernetes.get(KubernetesConfig.url(), namespace, KubernetesConfig.masterToken()); + } + + if (StringUtils.isNotEmpty(KubernetesConfig.masterUsername())) { + return Kubernetes.get(KubernetesConfig.url(), namespace, KubernetesConfig.masterUsername(), + KubernetesConfig.masterPassword()); + } + + if (StringUtils.isNotEmpty(KubernetesConfig.masterKubeconfig())) { + return Kubernetes.get(Paths.get(KubernetesConfig.masterKubeconfig()), namespace); + } + + return Kubernetes.get(namespace); + } + + public static String getBinaryPath() { + return KubernetesClientBinaryManagerFactory.INSTANCE.getKubernetesClientBinaryManager().getBinaryPath(); + } + + public static KubernetesClientBinary masterBinary() { + return masterBinary(NamespaceManager.getNamespace()); + } + + public static KubernetesClientBinary masterBinary(String namespace) { + return KubernetesClientBinaryManagerFactory.INSTANCE.getKubernetesClientBinaryManager().masterBinary(namespace); + } + + public static KubernetesClientBinary adminBinary() { + return adminBinary(NamespaceManager.getNamespace()); + } + + public static KubernetesClientBinary adminBinary(String namespace) { + return KubernetesClientBinaryManagerFactory.INSTANCE.getKubernetesClientBinaryManager().adminBinary(namespace); + } + + private static String getHomeDir() { + String home = System.getenv("HOME"); + if (home != null && !home.isEmpty()) { + File f = new File(home); + if (f.exists() && f.isDirectory()) { + return home; + } + } + return System.getProperty("user.home", "."); + } + + // /** + // * Save oc binary in a folder to use as cache to avoid to download it again. + // * The folder path depends on the OCP version and the download url. + // * The file can be accessed using {@link #getOcFromCache(String, String, File)}. + // * It works only if {@link OpenShiftConfig#isBinaryCacheEnabled()}. + // * + // * @param version String, OCP cluster version. + // * @param ocUrl String, download URL. + // * @param ocTarFile String, workdir file. + // * @throws IOException + // * @deprecated this should have never been made public, can be removed in future versions. It is not used internally by XTF + // */ + // @Deprecated + // public static void saveOcOnCache(String version, String ocUrl, File ocTarFile) throws IOException { + // if (OpenShiftConfig.isBinaryCacheEnabled()) { + // File cacheRootFile = new File(OpenShiftConfig.binaryCachePath()); + // if (!cacheRootFile.exists() && !cacheRootFile.mkdirs()) { + // throw new IllegalStateException("Cannot mkdirs " + cacheRootFile); + // } + // Path cachePath = getOcCachePath(version, ocUrl); + // Files.createDirectories(cachePath); + // FileUtils.copyFile(ocTarFile, new File(cachePath.toFile(), ocTarFile.getName())); + // } + // } + // + // /** + // * Retrieve the file from the folder populated by {@link #saveOcOnCache(String, String, File)}. + // * + // * @param version String, OCP cluster version. + // * @param ocUrl String, download URL. + // * @param ocTarFile String, workdir file. + // * @return File, reference to the file, if the cache is not populated, the file is not null, but it doesn't exist. + // * @throws IOException + // * @deprecated this should have never been made public, can be removed in future versions. It is not used internally by XTF + // */ + // @Deprecated + // public static File getOcFromCache(String version, String ocUrl, File ocTarFile) throws IOException { + // return new File(getOcCachePath(version, ocUrl).toFile(), ocTarFile.getName()); + // } + // + // /** + // * * @deprecated this should have never been made public, can be removed in future versions. It is not used internally by + // * XTF + // */ + // @Deprecated + // private static Path getOcCachePath(String version, String ocUrl) { + // return Paths.get(OpenShiftConfig.binaryCachePath(), version, DigestUtils.md5Hex(ocUrl)); + // } + // + // /** + // * Returns {@link OpenShiftConfig#version()}. If not available then access OpenShift endpoint for a version. Be aware + // * that this operation requires admin role for OpenShift 4 unlike to OpenShift 3. + // * + // * @return Openshift cluster version if configured or detected from cluster, null otherwise + // */ + // public static String getVersion() { + // return ClusterVersionInfoFactory.INSTANCE.getClusterVersionInfo().getOpenshiftVersion(); + // } + // + // public static String getMasterToken() { + // return getToken(OpenShiftConfig.masterToken(), OpenShiftConfig.masterUsername(), OpenShiftConfig.masterPassword(), + // OpenShiftConfig.masterKubeconfig()); + // } + // + // public static String getAdminToken() { + // return getToken(OpenShiftConfig.adminToken(), OpenShiftConfig.adminUsername(), OpenShiftConfig.adminPassword(), + // OpenShiftConfig.adminKubeconfig()); + // } + // + // private static String getToken(String token, String username, String password, String kubeconfig) { + // if (StringUtils.isNotEmpty(token)) { + // return token; + // } + // + // // Attempt to get the token via HTTP basic auth: + // if (StringUtils.isNotEmpty(username)) { + // HttpsURLConnection connection = null; + // try { + // if (getVersion() != null && getVersion().startsWith("3")) { + // connection = Https.getHttpsConnection(new URL( + // OpenShiftConfig.url() + // + "/oauth/authorize?response_type=token&client_id=openshift-challenging-client")); + // } else { + // connection = Https.getHttpsConnection(new URL("https://oauth-openshift.apps." + + // StringUtils.substringBetween(OpenShiftConfig.url(), "api.", ":") + // + "/oauth/authorize?response_type=token&client_id=openshift-challenging-client")); + // } + // String encoded = Base64.getEncoder() + // .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)); + // connection.setRequestProperty("Authorization", "Basic " + encoded); + // connection.setInstanceFollowRedirects(false); + // + // connection.connect(); + // Map> headers = connection.getHeaderFields(); + // connection.disconnect(); + // + // List location = headers.get("Location"); + // if (location != null) { + // Optional acces_token = location.stream().filter(s -> s.contains("access_token")).findFirst(); + // return acces_token.map(s -> StringUtils.substringBetween(s, "#access_token=", "&")).orElse(null); + // } + // } catch (IOException ex) { + // log.error("Unable to retrieve token from Location header: {} ", ex.getMessage()); + // } finally { + // if (connection != null) + // connection.disconnect(); + // } + // return null; + // } + // + // if (StringUtils.isNotEmpty(kubeconfig)) { + // try { + // Config config = Config.fromKubeconfig(null, + // new String(Files.readAllBytes(Paths.get(kubeconfig)), StandardCharsets.UTF_8), kubeconfig); + // return config.getOauthToken(); + // } catch (IOException e) { + // log.error("Unable to retrieve token from kubeconfig: {} ", kubeconfig, e); + // } + // return null; + // } + // + // File defaultKubeConfig = Paths.get(getHomeDir(), ".kube", "config").toFile(); + // try { + // Config config = Config.fromKubeconfig(null, + // new String(Files.readAllBytes(defaultKubeConfig.toPath()), StandardCharsets.UTF_8), + // defaultKubeConfig.getAbsolutePath()); + // return config.getOauthToken(); + // } catch (IOException e) { + // log.error("Unable to retrieve token from default kubeconfig: {} ", defaultKubeConfig, e); + // } + // return null; + // } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/NamespaceManager.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/NamespaceManager.java new file mode 100644 index 00000000..0f6b91c7 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/NamespaceManager.java @@ -0,0 +1,235 @@ +package org.jboss.intersmash.tools.k8s.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.StringUtils; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; +import org.junit.platform.engine.TestDescriptor; + +import cz.xtf.core.context.TestCaseContext; +import cz.xtf.core.waiting.SimpleWaiter; +import io.fabric8.kubernetes.api.builder.Visitor; +import io.fabric8.kubernetes.api.model.Namespace; +import io.fabric8.kubernetes.api.model.NamespaceBuilder; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.client.KubernetesClientException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class NamespaceManager { + /** + * By default (xtf.openshift.namespace.per.testcase=false) all entries in map point to value returned by + * + * @see org.jboss.intersmash.tools.k8s.KubernetesConfig#namespace(). + * If intersmash.k8s.namespace.per.testcase=true then each entry points to namespace + * assigned to each test case by {@see NamespaceManager#getNamespaceForTestClass} + * + * Maps testcase -> namespace + */ + private static final Map testcaseToNamespaceMap = new HashMap(); + + /** + * @return Map testcase -> namespace + */ + private static Map getTestCaseToNamespaceMap() { + return testcaseToNamespaceMap; + } + + /** + * @return return namespace for testcase or null if not present + */ + private static String getNamespaceForTestCase(String testcase) { + return getTestCaseToNamespaceMap().get(testcase); + } + + /** + * Creates namespace with name returned by @see #getNamespace if it does not exist + * + * @return true if successful, false otherwise + */ + public static boolean createIfDoesNotExistsProject() { + return createIfDoesNotExistsProject(getNamespace()); + } + + /** + * Creates namespace if it does not exist + * + * @return true if newly created, false otherwise (failed or namespace already present) + */ + public static boolean createIfDoesNotExistsProject(String namespace) { + Kubernetes kubernetes = Kuberneteses.master(namespace); + + // in case namespace is terminating (means is being deleted) then wait + checkAndWaitIfNamespaceIsTerminating(namespace); + + if (kubernetes.namespaces().withName(namespace).get() == null) { + log.info("Creating namespace: " + kubernetes.getNamespace()); + + Namespace projectNamespace = new Namespace(); + ObjectMeta objectMeta = new ObjectMeta(); + objectMeta.setName(KubernetesConfig.namespace()); + projectNamespace.setMetadata(objectMeta); + + kubernetes.namespaces().create(projectNamespace); + + new SimpleWaiter(() -> kubernetes.namespaces().withName(kubernetes.getNamespace()) != null, + TimeUnit.MINUTES, 2, + "Waiting for " + namespace + " project deletion").waitFor(); + + try { + // Adding a label can be only done via 'namespace'. It cannot be set via 'project' API. Thus we do this + // separately. Also, to update namespace label, it's necessary to have 'patch resource "namespaces"' + // permission for current user and updated namespace, e.g. by having 'cluster-admin' role. + // Otherwise you can see: + // $ oc label namespace "label1=foo" + // Error from server (Forbidden): namespaces "" is forbidden: User "" cannot patch resource "namespaces" in API group "" in the namespace "" + Kuberneteses.admin(namespace).namespaces().withName(kubernetes.getNamespace()) + .edit(new Visitor() { + @Override + public void visit(NamespaceBuilder builder) { + builder.editMetadata() + .addToLabels(Kubernetes.INTERSMASH_MANAGED_LABEL, "true"); + } + }); + } catch (KubernetesClientException e) { + // We weren't able to assign a label to the new project. Let's just print warning since this information + // is not critical to the tests execution. Possible cause for this are insufficient permissions since + // some projects using XTF are executed on OCP instances without 'admin' accounts available. + log.warn("Couldn't assign label '" + Kubernetes.INTERSMASH_MANAGED_LABEL + "' to the new project '" + + kubernetes.getNamespace() + "'. Possible cause are insufficient permissions."); + log.debug(e.getMessage()); + } + + if (KubernetesConfig.pullSecret() != null) { + kubernetes.setupPullSecret(KubernetesConfig.pullSecret()); + } + log.info("Created namespace: " + kubernetes.getNamespace()); + return true; + } + return false; + } + + private static void checkAndWaitIfNamespaceIsTerminating(String namespace) { + Namespace n = Kuberneteses.admin(namespace).namespaces().withName(namespace).get(); + if (n != null && n.getStatus().getPhase().equals("Terminating")) { + waitForNamespaceToBeDeleted(namespace); + } + } + + /** + * Deletes namespace as returned by @see #getNamespace + * + * @param waitForDeletion whether to wait for deletion (timeout 2 min) + * + * @return true if successful, false otherwise + */ + public static boolean deleteProject(boolean waitForDeletion) { + return deleteProject(getNamespace(), waitForDeletion); + } + + /** + * Deletes namespace + * + * @param namespace namespace name to delete + * @param waitForDeletion whether to wait for deletion (timeout 2 min) + * + * @return true if successful, false otherwise + */ + public static boolean deleteProject(String namespace, boolean waitForDeletion) { + boolean deleted = false; + // problem with OpenShift.getProject() is that it might return null even if namespace still exists (is in terminating state) + // thus use Openshift.namespaces() which do not suffer by this problem + // openshift.namespaces() requires admin privileges otherwise following KubernetesClientException is thrown: + // ... User "xpaasqe" cannot get resource "namespaces" in API group "" in the namespace ... + if (Kuberneteses.admin(namespace).namespaces().withName(namespace).get() != null) { + Kubernetes kubernetes = Kuberneteses.master(namespace); + log.info("Start deleting namespace: " + kubernetes.getNamespace() + ", wait for deletion: " + waitForDeletion); + deleted = kubernetes.namespaces().withName(kubernetes.getNamespace()).delete(); + if (!deleted && waitForDeletion) { + waitForNamespaceToBeDeleted(namespace); + } + } + return deleted; + } + + /** + * + * Deletes namespace as returned by @see #getNamespace. + * Deletes namespace only if @see {@link KubernetesConfig#useNamespacePerTestCase()} is true. + * + * @param waitForDeletion wait for deletion of namespace + * @return true if successful, false otherwise + */ + public static boolean deleteProjectIfUsedNamespacePerTestCase(boolean waitForDeletion) { + if (KubernetesConfig.useNamespacePerTestCase()) { + return deleteProject(waitForDeletion); + } + return false; + } + + private static void waitForNamespaceToBeDeleted(String namespace) { + BooleanSupplier bs = () -> Kuberneteses.admin(namespace).namespaces().withName(namespace).get() == null; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, "Waiting for " + namespace + " project deletion") + .waitFor(); + } + + private static String getNamespaceForTestClass(TestDescriptor testDescriptor) { + if (KubernetesConfig.useNamespacePerTestCase()) { + // some test case names can be really long resulting in long namespace names. This can cause issues + // with routes which have 64 chars limit for prefix of domain name. In case of route like: + // galleon-provisioning-xml-prio-mnovak-galleonprovisioningxmltest.apps.eapqe-024-dryf.eapqe.psi.redhat.com + // route prefix is: galleon-provisioning-xml-prio-mnovak-galleonprovisioningxmltest + // route suffix is: .apps.eapqe-024-dryf.eapqe.psi.redhat.com + if ((KubernetesConfig.namespace() + "-" + + testDescriptor.getParent().get().getDisplayName().toLowerCase()) + .length() > KubernetesConfig.getNamespaceLengthLimitForUniqueNamespacePerTest()) { + + return KubernetesConfig.namespace() + "-" + + StringUtils.truncate(DigestUtils.sha256Hex(testDescriptor.getParent().get().getDisplayName() + .toLowerCase()), + KubernetesConfig.getNamespaceLengthLimitForUniqueNamespacePerTest() + - KubernetesConfig.namespace().length()); + } else { + return KubernetesConfig.namespace() + "-" + + testDescriptor.getParent().get().getDisplayName().toLowerCase(); // namespace must not have upper case letters + } + } else { + return KubernetesConfig.namespace(); + } + } + + /** + * Add mapping test case name -> (automatically generated) namespace for given test case if absent + * + * @param testDescriptor test descriptor + */ + public static void addTestCaseToNamespaceEntryIfAbsent(TestDescriptor testDescriptor) { + getTestCaseToNamespaceMap().putIfAbsent(((MethodBasedTestDescriptor) testDescriptor).getTestClass().getName(), + getNamespaceForTestClass(testDescriptor)); + } + + /** + * @return Returns default namespace as defined in xtf.openshift.namespace property or namespace for currently running test + * case when: + * -Dintersmash.kubernetes.namespace.per.testcase=true. + * In case when current thread does not have associated test case (for example when initializing Kubernetes instance + * in static variable or static block) then {@link java.lang.RuntimeException} exception is thrown. + */ + public static String getNamespace() { + if (KubernetesConfig.useNamespacePerTestCase()) { + String namespace = NamespaceManager.getNamespaceForTestCase(TestCaseContext.getRunningTestCaseName()); + if (StringUtils.isEmpty(namespace)) { + throw new RuntimeException( + "There is no namespace associated with current thread or test case. This can happen in case that OpenShift instance is created in static variable. In this case avoid using static. Or in thread which is not associated with any test case."); + } + return namespace; + } else { + return KubernetesConfig.namespace(); + } + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/TestCaseContext.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/TestCaseContext.java new file mode 100644 index 00000000..e6bc75a4 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/TestCaseContext.java @@ -0,0 +1,24 @@ +package org.jboss.intersmash.tools.k8s.client; + +public class TestCaseContext { + + /** + * + * This allows to track currently running test case for correct namespace mapping. This is used to automatically find + * namespace for running test case when + * creating {@link Kubernetes} instances. + */ + private static String runningTestCaseName; + + /** + * @return test case name associated with current thread or null if not such mapping exists, for example for com.SmokeTest + * returns SmokeTest + */ + public static String getRunningTestCaseName() { + return runningTestCaseName; + } + + public static void setRunningTestCase(String currentlyRunningTestCaseName) { + runningTestCaseName = currentlyRunningTestCaseName; + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolver.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolver.java new file mode 100644 index 00000000..56fefb73 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolver.java @@ -0,0 +1,130 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.util.Objects; +import java.util.Set; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.SystemUtils; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; + +import com.google.common.base.Strings; + +import cz.xtf.core.http.Https; +import io.fabric8.kubernetes.client.VersionInfo; +import lombok.extern.slf4j.Slf4j; + +/** + * Class for resolving Kubernetes client binary path based on the Kubernetes cluster version, either the + * one referenced by the configuration properties or the actual cluster one. + * + * Based on the configuration properties, users can configure the resolver to cache the binary client. + */ +@Slf4j +public class ClusterVersionBasedKubernetesClientBinaryPathResolver implements KubernetesClientBinaryPathResolver { + private static final String KUBERNETES_CLIENT_BINARY_DOWNLOAD_BASE_URL = "https://dl.k8s.io/release/"; + public static final int BINARY_DOWNLOAD_CONNECTION_TIMEOUT = 20_000; + public static final int BINARY_DOWNLOAD_READ_TIMEOUT = 300_000; + + @Override + public String resolve() { + // gets the cluster version from configuration + final String configuredClusterVersion = KubernetesConfig.version(); + // priority is given to the configuration, then fallback on k8s APIs for determining the actual cluster version + final VersionInfo clusterVersionInfo = !Strings.isNullOrEmpty(configuredClusterVersion) + ? new VersionInfo.Builder().withGitVersion(configuredClusterVersion).build() + : Kuberneteses.admin().getKubernetesVersion(); + // is a local cache configured to be used? + final boolean cacheEnabled = KubernetesConfig.isBinaryCacheEnabled(); + // let's compute the Kubernetes client binary path + Path binaryPath; + if (cacheEnabled) { + log.debug("Trying to load Kubernetes client binary from cache"); + Path cachePath = getCachePath(clusterVersionInfo); + binaryPath = cachePath.resolve(KubernetesClientBinaryPathResolver.BINARY_NAME); + if (Files.exists(binaryPath)) { + // the required binary is there already, let's skip the download + log.debug("Kubernetes client binary is already in cache: {}.", binaryPath.toAbsolutePath()); + } else { + log.debug("Kubernetes client binary not found in cache, downloading it."); + downloadKubernetesClient(clusterVersionInfo, binaryPath); + } + binaryPath = copyKubernetesClientBinaryToTemporaryRuntimeLocation(binaryPath); + } else { + binaryPath = ClusterVersionBasedKubernetesClientBinaryPathResolver.getRuntimeKubectl(); + log.debug("Cache is disabled, downloading Kubernetes client binary to {}.", binaryPath.toAbsolutePath()); + downloadKubernetesClient(clusterVersionInfo, binaryPath); + } + return binaryPath.toAbsolutePath().toString(); + } + + /** + * Get the proper URL for a Kubernetes client, based on required version and taking the OS into account as well. + * + * @param clusterVersion {@link VersionInfo} instance holding the required Kubernetes version. + * @return A string representing the required Kubernetes client URL + */ + private String getBinaryUrlBasedOnKubernetesVersion(final VersionInfo clusterVersion) { + Objects.requireNonNull(clusterVersion); + + String systemType = "linux"; + String arch = "amd64"; + if (SystemUtils.IS_OS_MAC) { + systemType = "darwin"; + } + return String.format("%s/%s/bin/%s/%s/%s", KUBERNETES_CLIENT_BINARY_DOWNLOAD_BASE_URL, clusterVersion.getGitVersion(), + systemType, arch, KubernetesClientBinaryPathResolver.BINARY_NAME); + } + + private void downloadKubernetesClient(final VersionInfo clusterVersionInfo, final Path binaryPath) { + final String url = getBinaryUrlBasedOnKubernetesVersion(clusterVersionInfo); + try { + Https.copyHttpsURLToFile(url, binaryPath.toFile(), BINARY_DOWNLOAD_CONNECTION_TIMEOUT, + BINARY_DOWNLOAD_READ_TIMEOUT); + } catch (IOException ioe) { + throw new IllegalStateException("Failed to download the kubectl binary from " + url, ioe); + } + } + + private Path copyKubernetesClientBinaryToTemporaryRuntimeLocation(final Path binaryPath) { + Objects.requireNonNull(binaryPath); + + final Path runtimeKubectl = ClusterVersionBasedKubernetesClientBinaryPathResolver.getRuntimeKubectl(); + final Set permissions = Set.of( + PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_WRITE, PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_EXECUTE); + try { + Files.copy(binaryPath, runtimeKubectl, StandardCopyOption.REPLACE_EXISTING); + Files.setPosixFilePermissions(runtimeKubectl, permissions); + } catch (IOException e) { + throw new IllegalStateException("Error when copying the Kubernetes client binary", e); + } + return runtimeKubectl; + } + + static Path getCachePath(VersionInfo clusterVersionInfo) { + return Paths.get(KubernetesConfig.binaryCachePath(), clusterVersionInfo.getGitVersion(), + DigestUtils.md5Hex(clusterVersionInfo.getGitVersion())); + } + + static Path getRuntimeKubectl() { + return getProjectKubernetesDir().resolve(BINARY_NAME); + } + + static Path getProjectKubernetesDir() { + Path dir = Paths.get(LOCAL_BINARY_CLIENT_TMP_DIR); + try { + Files.createDirectories(dir); + } catch (IOException ioe) { + throw new IllegalStateException("Failed to create directory " + dir.toAbsolutePath(), ioe); + } + return dir; + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolver.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolver.java new file mode 100644 index 00000000..288007a0 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolver.java @@ -0,0 +1,12 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import org.jboss.intersmash.tools.k8s.KubernetesConfig; + +public class ConfigurationBasedKubernetesClientBinaryPathResolver implements KubernetesClientBinaryPathResolver { + + @Override + public String resolve() { + + return KubernetesConfig.binaryPath(); + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinary.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinary.java new file mode 100644 index 00000000..209967eb --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinary.java @@ -0,0 +1,128 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; + +import cz.xtf.core.openshift.CLIUtils; +import io.fabric8.openshift.api.model.operatorhub.lifecyclemanager.v1.PackageManifest; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class KubernetesClientBinary { + private final String path; + + @Getter + private String configPath; + + public KubernetesClientBinary(String path) { + this.path = path; + } + + public KubernetesClientBinary(String path, String configPath) { + this(path); + this.configPath = configPath; + } + + public void login(String url, String token) { + this.execute("login", url, "--insecure-skip-tls-verify=true", "--token=" + token); + } + + public void login(String url, String username, String password) { + this.execute("login", url, "--insecure-skip-tls-verify=true", "-u", username, "-p", password); + } + + /** + * Apply configuration file in the specified namespace. + * Delegates to `oc apply --filename='sourcepath' --namespace='namespace'` + * + * @param sourcePath path to configration file + * @param namespace namespace + */ + public void apply(String namespace, String sourcePath) { + this.execute("apply", "--namespace=" + namespace, "--filename=" + sourcePath); + } + + /** + * Apply configuration file. Delegates to `oc apply --filename='sourcepath` + * + * @param sourcePath path to configration file + */ + public void apply(String sourcePath) { + this.execute("apply", "--filename=" + sourcePath); + } + + /** + * Apply configuration files in the order they appear in the list + * + * @param sourcePaths list of paths to configuration files + */ + public void apply(List sourcePaths) { + for (String sourcePath : sourcePaths) { + apply(sourcePath); + } + } + + /** + * Apply configuration files in the order they appear in the list, using supplied namespace. + * + * @param namespace namespace in which the configuration files should be applied + * @param sourcePaths list of paths to configuration files + */ + public void apply(String namespace, List sourcePaths) { + for (String sourcePath : sourcePaths) { + apply(namespace, sourcePath); + } + } + + public void namespace(String projectName) { + // see https://kubernetes.io/docs/reference/kubectl/cheatsheet/#kubectl-context-and-configuration + this.execute("config", "set-context", "--current", String.format("--namespace=%s", projectName)); + } + + // TODO? check ShipWrigth + // public void startBuild(String buildConfig, String sourcePath) { + // this.execute("start-build", buildConfig, "--from-dir=" + sourcePath); + // } + + // Common method for any oc command call + public String execute(String... args) { + if (configPath == null) { + return CLIUtils.executeCommand(ArrayUtils.addAll(new String[] { path }, args)); + } else { + return CLIUtils.executeCommand(ArrayUtils.addAll(new String[] { path, "--kubeconfig=" + configPath }, args)); + } + } + + public List packageManifests(final String operatorName, final String operatorNamespace) { + Type targetClassType = new TypeToken>() { + }.getType(); + return new Gson().fromJson(this.execute("get", "packagemanifest", operatorName, "-n", operatorNamespace, "-o", "json"), + targetClassType); + } + + public List catalogSources(final String operatorNamespace) { + Type targetClassType = new TypeToken>() { + }.getType(); + return new Gson().fromJson(this.execute("get", "catsrc", "-n", operatorNamespace, "-o", "json"), targetClassType); + } + + public CatalogSource catalogSource(final String operatorNamespace, final String catalogSourceName) { + io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource loaded = catalogSources(operatorNamespace).stream() + .filter(cs -> cs.getMetadata().getName().equalsIgnoreCase(catalogSourceName)) + .findFirst().orElseThrow( + () -> new IllegalStateException( + "Unable to retrieve CatalogSource " + catalogSourceName)); + CatalogSource catalogSource = new CatalogSource(); + catalogSource.setMetadata(loaded.getMetadata()); + catalogSource.setSpec(loaded.getSpec()); + return catalogSource; + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManager.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManager.java new file mode 100644 index 00000000..38e7eef2 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManager.java @@ -0,0 +1,125 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class KubernetesClientBinaryManager { + private final String kubernetesClientBinaryPath; + + KubernetesClientBinaryManager(final String kubernetesClientBinaryPath) { + this.kubernetesClientBinaryPath = kubernetesClientBinaryPath; + } + + public String getBinaryPath() { + return kubernetesClientBinaryPath; + } + + public KubernetesClientBinary masterBinary(final String namespace) { + Objects.requireNonNull(namespace); + + return getBinary(KubernetesConfig.masterToken(), KubernetesConfig.masterUsername(), KubernetesConfig.masterPassword(), + KubernetesConfig.masterKubeconfig(), namespace); + } + + public KubernetesClientBinary adminBinary(final String namespace) { + Objects.requireNonNull(namespace); + + return getBinary(KubernetesConfig.adminToken(), KubernetesConfig.adminUsername(), KubernetesConfig.adminPassword(), + KubernetesConfig.adminKubeconfig(), namespace); + } + + private KubernetesClientBinary getBinary(final String token, final String username, final String password, + final String kubeconfig, + String namespace) { + String configPath = createUniqueKubernetesConfigFolder().resolve("kube.config").toAbsolutePath().toString(); + KubernetesClientBinary kubernetesClientBinary; + + if (StringUtils.isNotEmpty(token) || StringUtils.isNotEmpty(username)) { + // If we are using a token or username/password, we start with a nonexisting kubeconfig and do a "kubectl login" + kubernetesClientBinary = new KubernetesClientBinary(Kuberneteses.getBinaryPath(), configPath); + if (StringUtils.isNotEmpty(token)) { + kubernetesClientBinary.login(KubernetesConfig.url(), token); + } else { + kubernetesClientBinary.login(KubernetesConfig.url(), username, password); + } + } else { + // If we are using an existing kubeconfig (or a default kubeconfig), we copy the original kubeconfig + final Path actualConfigPath = Paths.get(configPath); + if (StringUtils.isNotEmpty(kubeconfig)) { + // flatten kubeconfig in case it contains certs/keys + try { + Files.write(actualConfigPath, + Arrays.asList(new KubernetesClientBinary(Kuberneteses.getBinaryPath(), null) + .execute("config", "view", "--kubeconfig", kubeconfig, "--flatten")), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Couldn't create a copy of an existing Kubernetes configuration file", e); + } + } else { + // We copy the default ~/.kube/config + File defaultKubeConfig = Paths.get(getHomeDir(), ".kube", "config").toFile(); + if (defaultKubeConfig.isFile()) { + try { + Files.write(actualConfigPath, + Arrays.asList(new KubernetesClientBinary(Kuberneteses.getBinaryPath(), null) + .execute("config", "view", "--kubeconfig", defaultKubeConfig.getAbsolutePath(), + "--flatten")), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Couldn't create a copy of the default Kubernetes configuration file", + e); + } + } else { + throw new IllegalStateException(defaultKubeConfig.getAbsolutePath() + + " does not exist and no other Kubernetes master option specified"); + } + } + kubernetesClientBinary = new KubernetesClientBinary(Kuberneteses.getBinaryPath(), configPath); + } + + if (StringUtils.isNotEmpty(namespace)) { + kubernetesClientBinary.namespace(namespace); + } + + return kubernetesClientBinary; + } + + private Path createUniqueKubernetesConfigFolder() { + try { + return Files.createTempDirectory(getProjectKubernetesConfigDir(), "config"); + } catch (IOException e) { + throw new IllegalStateException("Temporary folder for kubectl config couldn't be created", e); + } + } + + // TODO: this code is duplicated from OpenShifts.getHomeDir + // it should be revised together with token management + // https://github.com/xtf-cz/xtf/issues/464 + private static String getHomeDir() { + String home = System.getenv("HOME"); + if (home != null && !home.isEmpty()) { + File f = new File(home); + if (f.exists() && f.isDirectory()) { + return home; + } + } + return System.getProperty("user.home", "."); + } + + private Path getProjectKubernetesConfigDir() { + return Paths.get(KubernetesClientBinaryPathResolver.LOCAL_BINARY_CLIENT_TMP_DIR); + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactory.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactory.java new file mode 100644 index 00000000..f00e0c7f --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactory.java @@ -0,0 +1,36 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public enum KubernetesClientBinaryManagerFactory { + INSTANCE; + + private volatile KubernetesClientBinaryManager kubernetesClientBinaryManager; + + public KubernetesClientBinaryManager getKubernetesClientBinaryManager() { + KubernetesClientBinaryManager localRef = kubernetesClientBinaryManager; + if (localRef == null) { + synchronized (KubernetesClientBinaryManager.class) { + localRef = kubernetesClientBinaryManager; + if (localRef == null) { + for (KubernetesClientBinaryPathResolver resolver : resolverList()) { + String path = resolver.resolve(); + if (path != null) { + kubernetesClientBinaryManager = localRef = new KubernetesClientBinaryManager(path); + break; + } + } + } + } + } + return localRef; + } + + private List resolverList() { + return Stream.of( + new ConfigurationBasedKubernetesClientBinaryPathResolver(), + new ClusterVersionBasedKubernetesClientBinaryPathResolver()).collect(Collectors.toList()); + } +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryPathResolver.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryPathResolver.java new file mode 100644 index 00000000..94e286ce --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryPathResolver.java @@ -0,0 +1,17 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +/** + * Defines a contract for resolving the path of a Kubernetes client binary, which concrete implementations will + * base upon different criteria. + */ +public interface KubernetesClientBinaryPathResolver { + String BINARY_NAME = "kubectl"; + String LOCAL_BINARY_CLIENT_TMP_DIR = "tmp/kubectl"; + + /** + * Resolves Kubernetes client binary path + * + * @return Kubernetes client binary path + */ + String resolve(); +} diff --git a/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/junit5/KubernetesNamespaceCreator.java b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/junit5/KubernetesNamespaceCreator.java new file mode 100644 index 00000000..a0bdd66f --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/main/java/org/jboss/intersmash/tools/k8s/junit5/KubernetesNamespaceCreator.java @@ -0,0 +1,72 @@ +package org.jboss.intersmash.tools.k8s.junit5; + +import java.util.Arrays; + +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; +import org.jboss.intersmash.tools.k8s.client.NamespaceManager; +import org.jboss.intersmash.tools.k8s.client.TestCaseContext; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor; +import org.junit.platform.engine.FilterResult; +import org.junit.platform.engine.TestDescriptor; +import org.junit.platform.launcher.PostDiscoveryFilter; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestPlan; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class KubernetesNamespaceCreator + implements TestExecutionListener, BeforeAllCallback, AfterAllCallback, PostDiscoveryFilter { + + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + NamespaceManager.createIfDoesNotExistsProject(KubernetesConfig.namespace()); + } + + @Override + public void beforeAll(ExtensionContext context) { + // todo this can be removed once TestCaseContextExtension is called always before ProjectCreator extension + setTestExecutionContext(context); + + log.debug("BeforeAll - Test case: " + context.getTestClass().get().getName() + " running in thread name: " + + Thread.currentThread().getName() + + " will use namespace: " + Kuberneteses.master().getNamespace() + " - thread context is: " + + TestCaseContext.getRunningTestCaseName()); + NamespaceManager.createIfDoesNotExistsProject(); + } + + private void setTestExecutionContext(ExtensionContext context) { + TestCaseContext.setRunningTestCase(context.getTestClass().get().getName()); + } + + @Override + public void afterAll(ExtensionContext context) { + if (KubernetesConfig.cleanKubernetes()) { + NamespaceManager.deleteProjectIfUsedNamespacePerTestCase(false); + } + } + + @Override + public void testPlanExecutionFinished(TestPlan testPlan) { + if (KubernetesConfig.cleanKubernetes()) { + NamespaceManager.deleteProject(KubernetesConfig.namespace(), true); + } + } + + @Override + public FilterResult apply(TestDescriptor testDescriptor) { + if (testDescriptor instanceof MethodBasedTestDescriptor) { + boolean disabled = Arrays.stream(((MethodBasedTestDescriptor) testDescriptor).getTestClass().getAnnotations()) + .filter(annotation -> annotation instanceof Disabled).count() > 0; + if (!disabled) { + NamespaceManager.addTestCaseToNamespaceEntryIfAbsent(testDescriptor); + } + } + return FilterResult.included(testDescriptor.getDisplayName()); + } +} diff --git a/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolverTest.java b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolverTest.java new file mode 100644 index 00000000..475dca5b --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ClusterVersionBasedKubernetesClientBinaryPathResolverTest.java @@ -0,0 +1,85 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import cz.xtf.core.config.XTFConfig; +import io.fabric8.kubernetes.client.VersionInfo; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +@ExtendWith(SystemStubsExtension.class) +public class ClusterVersionBasedKubernetesClientBinaryPathResolverTest { + + @SystemStub + private SystemProperties systemProperties; + + private ClusterVersionBasedKubernetesClientBinaryPathResolver resolver = new ClusterVersionBasedKubernetesClientBinaryPathResolver(); + + @Test + public void existingKubernetesVersionPathIsResolvedWhenCacheEnabledTest() throws IOException { + final VersionInfo clusterVersion = new VersionInfo.Builder().withGitVersion("v1.27.3").build(); + systemProperties.set("intersmash.kubernetes.version", clusterVersion.getGitVersion()); + XTFConfig.loadConfig(); + // resolve (which includes the binary client download if it is not cached already) should pass here + final String resolvedPath = resolver.resolve(); + try { + // make assertions now + assertBinaryPathIsProperlyResolved(clusterVersion, resolvedPath); + } finally { + Files.deleteIfExists(ClusterVersionBasedKubernetesClientBinaryPathResolver.getRuntimeKubectl()); + Files.deleteIfExists(ClusterVersionBasedKubernetesClientBinaryPathResolver.getProjectKubernetesDir()); + } + } + + @Test + public void existingKubernetesVersionPathIsResolvedWhenCacheDisabledTest() throws IOException { + final VersionInfo clusterVersion = new VersionInfo.Builder().withGitVersion("v1.27.3").build(); + systemProperties.set("intersmash.kubernetes.version", clusterVersion.getGitVersion()); + systemProperties.set("intersmash.kubernetes.binary.cache.enabled", "false"); + try { + XTFConfig.loadConfig(); + // resolve (which includes the binary client download in this very case) should pass here + final String resolvedPath = resolver.resolve(); + try { + assertBinaryPathIsProperlyResolved(clusterVersion, resolvedPath); + } finally { + Files.deleteIfExists(ClusterVersionBasedKubernetesClientBinaryPathResolver.getRuntimeKubectl()); + Files.deleteIfExists(ClusterVersionBasedKubernetesClientBinaryPathResolver.getProjectKubernetesDir()); + } + } finally { + systemProperties.set("intersmash.kubernetes.binary.cache.enabled", "true"); + } + } + + @Test + public void fakeKubernetesVersionPathIsNotResolvedTest() { + final VersionInfo clusterVersion = new VersionInfo.Builder().withGitVersion("vX.Y.Z").build(); + systemProperties.set("intersmash.kubernetes.version", clusterVersion.getGitVersion()); + XTFConfig.loadConfig(); + // resolve (which includes the binary client download) should fail here + Assertions.assertThrows(IllegalStateException.class, () -> resolver.resolve()); + } + + private static void assertBinaryPathIsProperlyResolved(VersionInfo clusterVersion, String resolvedPath) { + // make assertions now + SoftAssertions softAssertions = new SoftAssertions(); + // path is not null + softAssertions.assertThat(resolvedPath).isNotNull(); + // path is correct and binary file exists + Path tmpPath = ClusterVersionBasedKubernetesClientBinaryPathResolver.getRuntimeKubectl(); + softAssertions.assertThat(resolvedPath).isEqualTo(tmpPath.toAbsolutePath().toString()); + softAssertions.assertThat(Files.exists(tmpPath)).isTrue(); + // archive is in cache + Path cachedPath = ClusterVersionBasedKubernetesClientBinaryPathResolver.getCachePath(clusterVersion); + softAssertions.assertThat(Files.exists(cachedPath)).isTrue(); + softAssertions.assertAll(); + } +} diff --git a/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolverTest.java b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolverTest.java new file mode 100644 index 00000000..1bb57f67 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/ConfigurationBasedKubernetesClientBinaryPathResolverTest.java @@ -0,0 +1,33 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.google.common.base.Strings; + +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +@ExtendWith(SystemStubsExtension.class) +public class ConfigurationBasedKubernetesClientBinaryPathResolverTest { + @SystemStub + private SystemProperties systemProperties; + + private KubernetesClientBinaryPathResolver resolver = new ConfigurationBasedKubernetesClientBinaryPathResolver(); + + @Test + public void resolveTest() { + final String currentBinaryPath = System.getProperty("intersmash.kubernetes.binary.path"); + final String testedBinaryPath = "/tmp"; + systemProperties.set("intersmash.kubernetes.binary.path", testedBinaryPath); + try { + Assertions.assertEquals(testedBinaryPath, resolver.resolve()); + } finally { + if (!Strings.isNullOrEmpty(currentBinaryPath)) { + systemProperties.set("intersmash.kubernetes.binary.path", currentBinaryPath); + } + } + } +} diff --git a/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactoryTest.java b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactoryTest.java new file mode 100644 index 00000000..e40dabc6 --- /dev/null +++ b/tools/intersmash-kubernetes-client/src/test/java/org/jboss/intersmash/tools/k8s/client/binary/KubernetesClientBinaryManagerFactoryTest.java @@ -0,0 +1,23 @@ +package org.jboss.intersmash.tools.k8s.client.binary; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +@ExtendWith(SystemStubsExtension.class) +public class KubernetesClientBinaryManagerFactoryTest { + @SystemStub + private SystemProperties systemProperties; + + @Test + public void managerBinaryPathDefaultsToCachedBinaryPathTest() { + KubernetesClientBinaryManager binaryManager = KubernetesClientBinaryManagerFactory.INSTANCE + .getKubernetesClientBinaryManager(); + Assertions.assertNotNull(binaryManager); + Assertions.assertTrue(binaryManager.getBinaryPath().endsWith("tmp/kubectl/kubectl")); + } +} diff --git a/tools/intersmash-tools-core/pom.xml b/tools/intersmash-tools-core/pom.xml index b8ec02ca..45ce5b1f 100644 --- a/tools/intersmash-tools-core/pom.xml +++ b/tools/intersmash-tools-core/pom.xml @@ -69,5 +69,10 @@ http-client test + + org.jboss.intersmash + intersmash-kubernetes-client + ${project.version} + 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 78f0f73f..e6e9e877 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 @@ -15,8 +15,12 @@ */ package org.jboss.intersmash.tools; +import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import com.google.common.base.Strings; import cz.xtf.core.config.XTFConfig; import cz.xtf.core.openshift.OpenShift; @@ -29,10 +33,15 @@ public class IntersmashConfig { private static final String DEPLOYMENTS_REPOSITORY_REF = "intersmash.deployments.repository.ref"; // Default Catalog for Operators - private static final String DEFAULT_OPERATOR_CATALOG_SOURCE_NAMESPACE = "openshift-marketplace"; + private static final String KUBERNETES_OPERATOR_CATALOG_SOURCE_NAMESPACE = "olm"; + private static final String OPENSHIFT_OPERATOR_CATALOG_SOURCE_NAMESPACE = "openshift-marketplace"; + private static final String DEFAULT_OPERATOR_CATALOG_SOURCE_NAMESPACE = KUBERNETES_OPERATOR_CATALOG_SOURCE_NAMESPACE; private static final String REDHAT_OPERATOR_CATALOG_SOURCE_NAME = "redhat-operators"; private static final String COMMUNITY_OPERATOR_CATALOG_SOURCE_NAME = "community-operators"; + private static final String OPERATORHUB_IO_OPERATOR_CATALOG_SOURCE_NAME = "operatorhubio-catalog"; private static final String DEFAULT_OPERATOR_CATALOG_SOURCE_NAME = COMMUNITY_OPERATOR_CATALOG_SOURCE_NAME; + private static final String OLM_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.olm.operators.catalog_source"; + private static final String OLM_OPERATOR_CATALOG_SOURCE_NAMESPACE = "intersmash.olm.operators.namespace"; // Custom Catalogs for operators private static final String INFINISPAN_OPERATOR_CATALOG_SOURCE_NAME = "intersmash.infinispan.operators.catalog_source"; @@ -116,6 +125,10 @@ public class IntersmashConfig { private static final String MYSQL_IMAGE_URL = "intersmash.mysql.image"; private static final String PGSQL_IMAGE_URL = "intersmash.postgresql.image"; + private static final String JUNIT5_EXECUTION_TARGETS = "intersmash.junit5.execution.targets"; + private static final String JUNIT5_EXECUTION_TARGET_OPENSHIFT = "OpenShift"; + private static final String JUNIT5_EXECUTION_TARGET_KUBERNETES = "Kubernetes"; + public static boolean skipDeploy() { return XTFConfig.get(SKIP_DEPLOY, "false").equals("true"); } @@ -129,11 +142,11 @@ public static String[] getKnownCatalogSources() { } public static String defaultOperatorCatalogSourceName() { - return DEFAULT_OPERATOR_CATALOG_SOURCE_NAME; + return XTFConfig.get(OLM_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); } public static String defaultOperatorCatalogSourceNamespace() { - return DEFAULT_OPERATOR_CATALOG_SOURCE_NAMESPACE; + return XTFConfig.get(OLM_OPERATOR_CATALOG_SOURCE_NAMESPACE, DEFAULT_OPERATOR_CATALOG_SOURCE_NAMESPACE); } public static String infinispanOperatorCatalogSource() { @@ -217,7 +230,7 @@ public static String activeMQOperatorPackageManifest() { } public static String hyperfoilOperatorCatalogSource() { - return XTFConfig.get(HYPERFOIL_OPERATOR_CATALOG_SOURCE_NAME, COMMUNITY_OPERATOR_CATALOG_SOURCE_NAME); + return XTFConfig.get(HYPERFOIL_OPERATOR_CATALOG_SOURCE_NAME, DEFAULT_OPERATOR_CATALOG_SOURCE_NAME); } public static String hyperfoilOperatorIndexImage() { @@ -404,4 +417,22 @@ public static String keycloakRealmImportOperatorPackageManifest() { return XTFConfig.get(KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST, DEFAULT_KEYCLOAK_REALM_IMPORT_OPERATOR_PACKAGE_MANIFEST); } + + public static String[] getJunit5ExecutionTargets() { + final String propertyValue = XTFConfig.get(JUNIT5_EXECUTION_TARGETS); + if (Strings.isNullOrEmpty(propertyValue)) { + return new String[] { JUNIT5_EXECUTION_TARGET_OPENSHIFT }; + } + return XTFConfig.get(JUNIT5_EXECUTION_TARGETS).split(","); + } + + public static Boolean testEnvironmentSupportsOpenShift() { + return Arrays.stream(getJunit5ExecutionTargets()).collect(Collectors.toList()) + .contains(JUNIT5_EXECUTION_TARGET_OPENSHIFT); + } + + public static Boolean testEnvironmentSupportsKubernetes() { + return Arrays.stream(getJunit5ExecutionTargets()).collect(Collectors.toList()) + .contains(JUNIT5_EXECUTION_TARGET_KUBERNETES); + } } diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/k8s/KubernetesApplication.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/k8s/KubernetesApplication.java new file mode 100644 index 00000000..9c4ef454 --- /dev/null +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/k8s/KubernetesApplication.java @@ -0,0 +1,28 @@ +/** + * 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.k8s; + +import org.jboss.intersmash.tools.application.Application; + +/** + * Interface representing the Application on Kubernetes. + * + * This interface is not supposed to be implemented by user Applications. See the "Mapping of implemented provisioners" + * section of Intersmash README.md file for the up-to-date list of supported end users Applications. + */ +public interface KubernetesApplication extends Application { + +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/openshift/OperatorApplication.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/operator/OperatorApplication.java similarity index 77% rename from tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/openshift/OperatorApplication.java rename to tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/operator/OperatorApplication.java index 66a56154..c11f4889 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/openshift/OperatorApplication.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/application/operator/OperatorApplication.java @@ -13,11 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.Collections; import java.util.List; +import org.jboss.intersmash.tools.application.Application; +import org.jboss.intersmash.tools.application.openshift.HasConfigMaps; +import org.jboss.intersmash.tools.application.openshift.HasSecrets; + import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.api.model.Secret; @@ -25,7 +29,7 @@ * This interface is not supposed to be implemented by user Applications. See the "Mapping of implemented provisioners" * section of Intersmash README.md file for the up-to-date list of supported end users Applications. */ -public interface OperatorApplication extends OpenShiftApplication, HasSecrets, HasConfigMaps { +public interface OperatorApplication extends Application, HasSecrets, HasConfigMaps { @Override default List getSecrets() { diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExecutionCondition.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExecutionCondition.java index 79b6cd01..7012e33d 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExecutionCondition.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExecutionCondition.java @@ -16,12 +16,14 @@ package org.jboss.intersmash.tools.junit5; import java.util.Arrays; +import java.util.List; import java.util.function.Predicate; +import java.util.stream.Collectors; import org.jboss.intersmash.tools.IntersmashConfig; import org.jboss.intersmash.tools.annotations.Intersmash; import org.jboss.intersmash.tools.annotations.Service; -import org.jboss.intersmash.tools.application.openshift.OperatorApplication; +import org.jboss.intersmash.tools.application.operator.OperatorApplication; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -31,18 +33,37 @@ @Slf4j public class IntersmashExecutionCondition implements ExecutionCondition { - private static final Predicate isOperatorApplication = (application) -> OperatorApplication.class .isAssignableFrom(application.value()); @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + final List targets = Arrays.stream(IntersmashConfig.getJunit5ExecutionTargets()) + .collect(Collectors.toList()); + // log what the configured JUnit 5 target execution environment names are + log.debug("Configured JUnit 5 execution environments: {}", targets.stream().collect(Collectors.joining(","))); + Intersmash[] intersmashes = context.getRequiredTestClass().getAnnotationsByType(Intersmash.class); Intersmash intersmash; if (intersmashes.length > 0) { intersmash = intersmashes[0]; + + // Skip tests that don't fit the environments supported by the actual test execution environment + if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(context) + && !IntersmashConfig.testEnvironmentSupportsOpenShift()) { + return ConditionEvaluationResult.disabled( + "An @Intersmash service is set to target OpenShift which has not been configured for the current execution."); + } + if (IntersmashExtensionHelper.isIntersmashTargetingKubernetes(context) + && !IntersmashConfig.testEnvironmentSupportsKubernetes()) { + return ConditionEvaluationResult.disabled( + "An @Intersmash service is set to target Kubernetes which has not been configured for the current execution."); + } log.debug("Running: {}", context.getRequiredTestClass().getSimpleName()); - if (IntersmashConfig.isOcp3x(OpenShifts.admin()) + + // evaluate peculiar OpenShift/Kubernetes requirements + if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(context) + && IntersmashConfig.isOcp3x(OpenShifts.admin()) && Arrays.stream(intersmash.value()).anyMatch(isOperatorApplication)) { return ConditionEvaluationResult.disabled("OLM is not available on OCP 3.x clusters, " + "skip the tests due to OperatorApplication(s) involvement."); diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtension.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtension.java index 0924c29c..d4e4e260 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtension.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtension.java @@ -15,13 +15,11 @@ */ package org.jboss.intersmash.tools.junit5; +import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import org.jboss.intersmash.tools.IntersmashConfig; @@ -30,55 +28,53 @@ import org.jboss.intersmash.tools.annotations.ServiceProvisioner; import org.jboss.intersmash.tools.annotations.ServiceUrl; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.OpenShiftApplication; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; import org.jboss.intersmash.tools.provision.Provisioner; import org.jboss.intersmash.tools.provision.ProvisionerManager; import org.jboss.intersmash.tools.provision.openshift.operator.resources.OperatorGroup; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.support.AnnotationSupport; import org.opentest4j.AssertionFailedError; import org.opentest4j.TestAbortedException; +import cz.xtf.core.config.OpenShiftConfig; import cz.xtf.core.openshift.OpenShifts; import lombok.extern.slf4j.Slf4j; @Slf4j public class IntersmashExtension implements BeforeAllCallback, AfterAllCallback, TestInstancePostProcessor { - private static final Namespace NAMESPACE = Namespace.create("org", "jboss", "intersmash", "IntersmashExtension"); - private static final String INTERSMASH_SERVICES = "INTERSMASH_SERVICES"; - @Override public void beforeAll(ExtensionContext extensionContext) throws Exception { Optional tt = Optional.empty(); try { log.debug("beforeAll"); - // openShiftRecorderService().initFilters(extensionContext); - Intersmash[] intersmashes = extensionContext.getRequiredTestClass().getAnnotationsByType(Intersmash.class); - Intersmash intersmash; - if (intersmashes.length > 0) { - intersmash = intersmashes[0]; - } else { + // let's store the Intersmash definition in the extension context store + Intersmash intersmash = IntersmashExtensionHelper.getIntersmash(extensionContext); + if (intersmash == null) { + log.warn("No @Intersmah definition stored by Intersmash extensions"); return; } - // we don't want to touch anything if the deployment phase is skipped if (!IntersmashConfig.skipDeploy()) { - if (Arrays.stream(intersmash.value()) - .anyMatch(app -> OpenShiftApplication.class.isAssignableFrom(app.value()))) { - if (!IntersmashConfig.isOcp3x(OpenShifts.admin())) { - operatorCleanup(); - log.debug("Deploy operatorgroup [{}] to enable operators subscription into tested namespace", - OperatorGroup.SINGLE_NAMESPACE.getMetadata().getName()); - OpenShifts.adminBinary().execute("apply", "-f", - OperatorGroup.SINGLE_NAMESPACE.save().getAbsolutePath()); + if (IntersmashExtensionHelper.isIntersmashTargetingOperator(extensionContext)) { + final boolean cleanupKubernetes = IntersmashExtensionHelper + .isIntersmashTargetingKubernetes(extensionContext) && !IntersmashConfig.isOcp3x(OpenShifts.admin()), + cleanupOpenShift = IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext) + && !IntersmashConfig.isOcp3x(OpenShifts.admin()); + operatorCleanup(cleanupKubernetes, cleanupOpenShift); + deployOperatorGroup(extensionContext); + + if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext)) { + OpenShifts.master().clean().waitFor(); + } + if (IntersmashExtensionHelper.isIntersmashTargetingKubernetes(extensionContext)) { + Kuberneteses.master().clean().waitFor(); } - OpenShifts.master().clean().waitFor(); } } @@ -91,7 +87,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { // store provisioners right now, those might be needed in each phase independently Provisioner provisioner = ProvisionerManager.getProvisioner(application); // keep the provisioner in the JUpiter Extension Store - getProvisioners(extensionContext).put(application.getClass().getName(), provisioner); + IntersmashExtensionHelper.getProvisioners(extensionContext).put(application.getClass().getName(), provisioner); if (!IntersmashConfig.skipDeploy()) { deployApplication(provisioner); } @@ -104,6 +100,22 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { } } + private static void deployOperatorGroup(ExtensionContext extensionContext) throws IOException { + if (IntersmashExtensionHelper.isIntersmashTargetingKubernetes(extensionContext)) { + log.debug("Deploy operatorgroup [{}] to enable operators subscription into tested namespace", + new OperatorGroup(KubernetesConfig.namespace()).getMetadata().getName()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(KubernetesConfig.namespace()).save().getAbsolutePath()); + } + if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext) + && !IntersmashConfig.isOcp3x(OpenShifts.admin())) { + log.debug("Deploy operatorgroup [{}] to enable operators subscription into tested namespace", + new OperatorGroup(OpenShiftConfig.namespace()).getMetadata().getName()); + OpenShifts.adminBinary().execute("apply", "-f", + new OperatorGroup(OpenShiftConfig.namespace()).save().getAbsolutePath()); + } + } + private Application getApplicationFromService(Service service) { try { return service.value().getConstructor().newInstance(); @@ -141,32 +153,48 @@ public void afterAll(ExtensionContext extensionContext) { if (IntersmashConfig.skipUndeploy()) { log.info("Skipping the after test cleanup operations."); } else { - for (Provisioner provisioner : getProvisioners(extensionContext).values()) { + for (Provisioner provisioner : IntersmashExtensionHelper.getProvisioners(extensionContext).values()) { undeployApplication(provisioner); } // operator group is not bound to a specific product // no Operator support on OCP3 clusters, OLM doesn't run there - if (!IntersmashConfig.isOcp3x(OpenShifts.admin())) { - operatorCleanup(); + if (IntersmashExtensionHelper.isIntersmashTargetingOperator(extensionContext)) { + final boolean cleanupKubernetes = IntersmashExtensionHelper.isIntersmashTargetingKubernetes(extensionContext) + && !IntersmashConfig.isOcp3x(OpenShifts.admin()), + cleanupOpenShift = IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext) + && !IntersmashConfig.isOcp3x(OpenShifts.admin()); + operatorCleanup(cleanupKubernetes, cleanupOpenShift); } // let's cleanup once we're done - safetyCleanup(); + safetyCleanup(extensionContext); } } - private static void safetyCleanup() { + private static void safetyCleanup(ExtensionContext extensionContext) { log.info("Cleaning up the remaining resources on the cluster."); - OpenShifts.master().clean().waitFor(); + if (IntersmashExtensionHelper.isIntersmashTargetingOpenShift(extensionContext)) { + OpenShifts.master().clean().waitFor(); + } + if (IntersmashExtensionHelper.isIntersmashTargetingKubernetes(extensionContext)) { + Kuberneteses.master().clean().waitFor(); + } } /** * Clean all OLM related objects. *

*/ - public static void operatorCleanup() { - OpenShifts.adminBinary().execute("delete", "subscription", "--all"); - OpenShifts.adminBinary().execute("delete", "csvs", "--all"); - OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); + public static void operatorCleanup(final boolean cleanupKubernetes, final boolean cleanupOpenShift) { + if (cleanupKubernetes) { + Kuberneteses.adminBinary().execute("delete", "subscription", "--all"); + Kuberneteses.adminBinary().execute("delete", "csvs", "--all"); + Kuberneteses.adminBinary().execute("delete", "operatorgroup", "--all"); + } + if (cleanupOpenShift) { + OpenShifts.adminBinary().execute("delete", "subscription", "--all"); + OpenShifts.adminBinary().execute("delete", "csvs", "--all"); + OpenShifts.adminBinary().execute("delete", "operatorgroup", "--all"); + } } @Override @@ -175,23 +203,13 @@ public void postProcessTestInstance(Object o, ExtensionContext extensionContext) injectServiceProvisioner(o, extensionContext); } - private Map getProvisioners(ExtensionContext extensionContext) { - Store store = extensionContext.getStore(NAMESPACE); - Map provisioners = (Map) store.get(INTERSMASH_SERVICES); - if (provisioners != null) { - return provisioners; - } else { - store.put(INTERSMASH_SERVICES, new HashMap()); - return (Map) store.get(INTERSMASH_SERVICES); - } - } - private void injectServiceUrl(Object o, ExtensionContext extensionContext) throws Exception { log.debug("injectServiceUrl"); List annotatedFields = AnnotationSupport.findAnnotatedFields(o.getClass(), ServiceUrl.class); for (Field field : annotatedFields) { ServiceUrl serviceUrl = field.getAnnotation(ServiceUrl.class); - Provisioner provisioner = getProvisioners(extensionContext).get(serviceUrl.value().getName()); + Provisioner provisioner = IntersmashExtensionHelper.getProvisioners(extensionContext) + .get(serviceUrl.value().getName()); URL url = provisioner.getURL(); if (String.class.isAssignableFrom(field.getType())) { field.setAccessible(true); @@ -211,7 +229,8 @@ private void injectServiceProvisioner(Object o, ExtensionContext extensionContex List annotatedFields = AnnotationSupport.findAnnotatedFields(o.getClass(), ServiceProvisioner.class); for (Field field : annotatedFields) { ServiceProvisioner serviceProvisioner = field.getAnnotation(ServiceProvisioner.class); - Provisioner provisioner = getProvisioners(extensionContext).get(serviceProvisioner.value().getName()); + Provisioner provisioner = IntersmashExtensionHelper.getProvisioners(extensionContext) + .get(serviceProvisioner.value().getName()); if (Provisioner.class.isAssignableFrom(field.getType())) { field.setAccessible(true); field.set(o, provisioner); diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtensionHelper.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtensionHelper.java new file mode 100644 index 00000000..c6a4ff3f --- /dev/null +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/junit5/IntersmashExtensionHelper.java @@ -0,0 +1,76 @@ +/** + * 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.junit5; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.jboss.intersmash.tools.annotations.Intersmash; +import org.jboss.intersmash.tools.application.k8s.KubernetesApplication; +import org.jboss.intersmash.tools.application.openshift.OpenShiftApplication; +import org.jboss.intersmash.tools.application.operator.OperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class IntersmashExtensionHelper { + + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create("org", "jboss", "intersmash", + "IntersmashExtension"); + private static final String INTERSMASH_SERVICES = "INTERSMASH_SERVICES"; + private static final String INTERSMASH = "INTERSMASH"; + + public static Map getProvisioners(ExtensionContext extensionContext) { + ExtensionContext.Store store = extensionContext.getStore(NAMESPACE); + Map provisioners = (Map) store.get(INTERSMASH_SERVICES); + if (provisioners != null) { + return provisioners; + } else { + store.put(INTERSMASH_SERVICES, new HashMap()); + return (Map) store.get(INTERSMASH_SERVICES); + } + } + + public static Intersmash getIntersmash(ExtensionContext extensionContext) { + ExtensionContext.Store store = extensionContext.getStore(NAMESPACE); + Intersmash result = (Intersmash) store.get(INTERSMASH); + if (result != null) { + return result; + } else { + Intersmash[] intersmashes = extensionContext.getRequiredTestClass().getAnnotationsByType(Intersmash.class); + if (intersmashes.length > 0) { + store.put(INTERSMASH, intersmashes[0]); + return (Intersmash) store.get(INTERSMASH); + } + return null; + } + } + + public static Boolean isIntersmashTargetingOpenShift(ExtensionContext extensionContext) { + return Arrays.stream(getIntersmash(extensionContext).value()) + .anyMatch(app -> OpenShiftApplication.class.isAssignableFrom(app.value())); + } + + public static Boolean isIntersmashTargetingOperator(ExtensionContext extensionContext) { + return Arrays.stream(getIntersmash(extensionContext).value()) + .anyMatch(app -> OperatorApplication.class.isAssignableFrom(app.value())); + } + + public static Boolean isIntersmashTargetingKubernetes(ExtensionContext extensionContext) { + return Arrays.stream(getIntersmash(extensionContext).value()) + .anyMatch(app -> KubernetesApplication.class.isAssignableFrom(app.value())); + } +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/k8s/KubernetesProvisioner.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/k8s/KubernetesProvisioner.java new file mode 100644 index 00000000..c981d63b --- /dev/null +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/k8s/KubernetesProvisioner.java @@ -0,0 +1,102 @@ +/** + * 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.k8s; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.jboss.intersmash.tools.application.Application; +import org.jboss.intersmash.tools.application.openshift.HasConfigMaps; +import org.jboss.intersmash.tools.application.openshift.HasSecrets; +import org.jboss.intersmash.tools.k8s.client.Kubernetes; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.HasPods; +import org.jboss.intersmash.tools.provision.openshift.Scalable; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; + +/** + * Provisioner that is supposed to deploy an application on Kubernetes. + */ +public interface KubernetesProvisioner extends Provisioner, Scalable, HasPods { + + Kubernetes kubernetes = Kuberneteses.master(); + + static Secret createTlsSecret(final String namespace, final String secretName, final Path key, final 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 kubernetes.secrets().inNamespace(namespace).createOrReplace(secret); + } + + @Override + default void preDeploy() { + // create secrets + if (HasSecrets.class.isAssignableFrom(getApplication().getClass())) { + ((HasSecrets) getApplication()).getSecrets().forEach(s -> kubernetes.secrets().create(s)); + } + // create configMaps + if (HasConfigMaps.class.isAssignableFrom(getApplication().getClass())) { + ((HasConfigMaps) getApplication()).getConfigMaps().forEach(c -> kubernetes.configMaps().create(c)); + } + } + + @Override + default void postUndeploy() { + // delete secrets + if (HasSecrets.class.isAssignableFrom(getApplication().getClass())) { + ((HasSecrets) getApplication()).getSecrets().forEach(s -> kubernetes.secrets().delete(s)); + } + // delete configMaps + if (HasConfigMaps.class.isAssignableFrom(getApplication().getClass())) { + ((HasConfigMaps) getApplication()).getConfigMaps().forEach(c -> kubernetes.configMaps().delete(c)); + } + } + + // TODO - check (use a static class method like XTF OpenShift::generateHostName ?) + default String getUrl(String routeName, boolean secure) { + String protocol = secure ? "https" : "http"; + return protocol + "://" + kubernetes.getMasterUrl() + "-" + routeName + "-" + + kubernetes.getConfiguration().getNamespace(); + } + + @Override + default URL getURL() { + try { + return new URL(getUrl(getApplication().getName(), false)); + } catch (MalformedURLException ex) { + throw new RuntimeException( + String.format("Failed to get an URL for the \"%s\" route", this.getClass().getSimpleName()), ex); + } + } +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/OpenShiftProvisioner.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/OpenShiftProvisioner.java index d7f80a2b..6163c87e 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/OpenShiftProvisioner.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/OpenShiftProvisioner.java @@ -15,26 +15,50 @@ */ package org.jboss.intersmash.tools.provision.openshift; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import org.jboss.intersmash.tools.application.Application; import org.jboss.intersmash.tools.application.openshift.HasConfigMaps; import org.jboss.intersmash.tools.application.openshift.HasSecrets; -import org.jboss.intersmash.tools.application.openshift.OpenShiftApplication; import org.jboss.intersmash.tools.provision.Provisioner; import cz.xtf.core.openshift.OpenShift; import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; /** * Provisioner that is supposed to deploy an application on OpenShift. */ -public interface OpenShiftProvisioner extends Provisioner, Scalable, HasPods { +public interface OpenShiftProvisioner extends Provisioner, Scalable, HasPods { String SCRIPT_DEBUG = "SCRIPT_DEBUG"; String APP_LABEL_KEY = "intersmash.app"; OpenShift openShift = OpenShifts.master(); + static Secret createTlsSecret(final String namespace, final String secretName, final Path key, final 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 openShift.secrets().inNamespace(namespace).createOrReplace(secret); + } + @Override default void preDeploy() { // create secrets diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/WaitersUtil.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/WaitersUtil.java index 2a1c5765..d2b53779 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/WaitersUtil.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/WaitersUtil.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import cz.xtf.core.http.Https; +import cz.xtf.core.http.HttpsException; import cz.xtf.core.openshift.OpenShift; import cz.xtf.core.waiting.SimpleWaiter; import cz.xtf.core.waiting.Waiter; @@ -66,8 +67,13 @@ public static Waiter serviceEndpointsAreReady(OpenShift openShift, String servic } public static Waiter routeIsUp(String routeURL) { - return new SimpleWaiter( - () -> Https.getCode(routeURL) != 503) - .reason("Wait until the route is ready to serve."); + return new SimpleWaiter(() -> + { + try { + return Https.getCode(routeURL) != 503; + } catch (HttpsException ex) { + return false; + } + }).reason("Wait until the route is ready to serve."); } } diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/CatalogSource.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/CatalogSource.java index b7e30e59..48199818 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/CatalogSource.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/CatalogSource.java @@ -15,7 +15,6 @@ */ package org.jboss.intersmash.tools.provision.openshift.operator.resources; -import cz.xtf.core.openshift.OpenShifts; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSourceBuilder; public class CatalogSource extends io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource @@ -56,22 +55,9 @@ public CatalogSource load(CatalogSource loaded) { return this; } - /** - * Load CatalogSource by name from OpenShift cluster - * @param catalogSourceName name of the CatalogSource e.g. certified-operators, community-operators, - * redhat-marketplace, redhat-operators,... - * @return CatalogSource - */ - public CatalogSource load(String catalogSourceName, String catalogSourceNamespace) { - io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource catalogSource = OpenShifts - .admin(catalogSourceNamespace).operatorHub() - .catalogSources().list().getItems() - .stream().filter(cs -> cs.getMetadata().getName().equalsIgnoreCase(catalogSourceName)) - .findFirst().orElseThrow( - () -> new IllegalStateException( - "Unable to retrieve CatalogSource " + catalogSourceName)); - this.setMetadata(catalogSource.getMetadata()); - this.setSpec(catalogSource.getSpec()); + public CatalogSource load(io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource loaded) { + this.setMetadata(loaded.getMetadata()); + this.setSpec(loaded.getSpec()); return this; } } diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OperatorGroup.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OperatorGroup.java index 700033f9..17e55f06 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OperatorGroup.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OperatorGroup.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.Objects; -import cz.xtf.core.config.OpenShiftConfig; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; @@ -35,7 +34,6 @@ @Group("operators.coreos.com") @Version("v1") public class OperatorGroup extends CustomResource implements OpenShiftResource { - public static final OperatorGroup SINGLE_NAMESPACE = new OperatorGroup(OpenShiftConfig.namespace()); private Map> spec = new HashMap<>(); public OperatorGroup() { diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/Subscription.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/Subscription.java index 2515d18a..01313774 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/Subscription.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/Subscription.java @@ -20,7 +20,6 @@ import org.assertj.core.util.Strings; -import cz.xtf.core.config.OpenShiftConfig; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.SubscriptionBuilder; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.SubscriptionFluent; @@ -43,13 +42,13 @@ public Subscription() { super(); } - private SubscriptionFluent.SpecNested getConfiguredSubscriptionBuilder(String sourceNamespace, - String source, String name, String channel, - String installPlanApproval) { + private SubscriptionFluent.SpecNested getConfiguredSubscriptionBuilder(final String sourceNamespace, + final String source, final String name, final String channel, final String installPlanApproval, + final String targetNamespace) { return new SubscriptionBuilder() .withNewMetadata() .withName(name) - .withNamespace(OpenShiftConfig.namespace()) + .withNamespace(targetNamespace) .endMetadata() .withNewSpec() .withChannel(channel) @@ -59,11 +58,12 @@ private SubscriptionFluent.SpecNested getConfiguredSubscrip .withInstallPlanApproval(Strings.isNullOrEmpty(installPlanApproval) ? "Automatic" : installPlanApproval); } - public Subscription(String sourceNamespace, String source, String name, String channel, String installPlanApproval, - Map envVariables) { + public Subscription(final String sourceNamespace, final String source, final String name, final String channel, + final String installPlanApproval, + final Map envVariables, final String targetNamespace) { this(); io.fabric8.openshift.api.model.operatorhub.v1alpha1.Subscription loaded = getConfiguredSubscriptionBuilder( - sourceNamespace, source, name, channel, installPlanApproval) + sourceNamespace, source, name, channel, installPlanApproval, targetNamespace) .withNewConfig() .addAllToEnv( envVariables.entrySet().stream() @@ -76,10 +76,11 @@ public Subscription(String sourceNamespace, String source, String name, String c this.setSpec(loaded.getSpec()); } - public Subscription(String sourceNamespace, String source, String name, String channel, String installPlanApproval) { + public Subscription(final String sourceNamespace, final String source, final String name, final String channel, + final String installPlanApproval, final String targetNamespace) { this(); io.fabric8.openshift.api.model.operatorhub.v1alpha1.Subscription loaded = getConfiguredSubscriptionBuilder( - sourceNamespace, source, name, channel, installPlanApproval) + sourceNamespace, source, name, channel, installPlanApproval, targetNamespace) .endSpec() .build(); this.setMetadata(loaded.getMetadata()); diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisioner.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisioner.java new file mode 100644 index 00000000..388b7cec --- /dev/null +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisioner.java @@ -0,0 +1,67 @@ +package org.jboss.intersmash.tools.provision.operator; + +import java.util.List; + +import org.jboss.intersmash.tools.application.operator.OperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.HasPods; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.openshift.api.model.operatorhub.lifecyclemanager.v1.PackageManifest; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSourceList; + +public interface OlmOperatorProvisioner + extends Provisioner, OlmOperatorProvisionerClientBinary, HasPods { + + default PackageManifest getPackageManifest(String operatorName, String operatorNamespace) { + try { + return new ObjectMapper() + .readValue(this.execute("get", "packagemanifest", operatorName, "-n", operatorNamespace, "-o", "json"), + PackageManifest.class); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Couldn't deserialize package manifest data: " + operatorName, e); + } + } + + List retrievePods(); + + default List getCatalogSources(final String catalogSourceNamespace) { + try { + return new ObjectMapper().readValue(this.execute("get", "catsrc", "-n", catalogSourceNamespace, "-o", "json"), + CatalogSourceList.class).getItems(); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Couldn't deserialize catalog source data: " + catalogSourceNamespace, e); + } + } + + default CatalogSource getCatalogSource(final String catalogSourceNamespace, final String catalogSourceName) { + io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource loaded = getCatalogSources(catalogSourceNamespace) + .stream() + .filter(cs -> cs.getMetadata().getName().equalsIgnoreCase(catalogSourceName)) + .findFirst().orElseThrow( + () -> new IllegalStateException( + "Unable to retrieve CatalogSource " + catalogSourceName)); + CatalogSource catalogSource = new CatalogSource(); + catalogSource.setMetadata(loaded.getMetadata()); + catalogSource.setSpec(loaded.getSpec()); + return catalogSource; + } + + String execute(String... args); + + NonNamespaceOperation> retrieveCustomResourceDefinitions(); + + boolean isSubscribed(); + + void subscribe(); + + void unsubscribe(); +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisionerClientBinary.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisionerClientBinary.java new file mode 100644 index 00000000..795f2d96 --- /dev/null +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OlmOperatorProvisionerClientBinary.java @@ -0,0 +1,6 @@ +package org.jboss.intersmash.tools.provision.operator; + +public interface OlmOperatorProvisionerClientBinary { + + String execute(String... args); +} diff --git a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/OperatorProvisioner.java b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OperatorProvisioner.java similarity index 81% rename from tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/OperatorProvisioner.java rename to tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OperatorProvisioner.java index fd795d61..e8a921a4 100644 --- a/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/openshift/operator/OperatorProvisioner.java +++ b/tools/intersmash-tools-core/src/main/java/org/jboss/intersmash/tools/provision/operator/OperatorProvisioner.java @@ -13,32 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift.operator; +package org.jboss.intersmash.tools.provision.operator; import java.io.IOException; -import java.net.URL; import java.time.Duration; import java.util.Arrays; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BooleanSupplier; import java.util.stream.Collectors; import org.assertj.core.util.Strings; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.OperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; -import org.jboss.intersmash.tools.provision.openshift.operator.resources.CatalogSource; +import org.jboss.intersmash.tools.application.operator.OperatorApplication; import org.jboss.intersmash.tools.provision.openshift.operator.resources.Subscription; import org.slf4j.event.Level; -import cz.xtf.core.config.OpenShiftConfig; -import cz.xtf.core.openshift.OpenShift; -import cz.xtf.core.openshift.OpenShiftBinary; -import cz.xtf.core.openshift.OpenShiftWaiters; -import cz.xtf.core.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; import cz.xtf.core.waiting.failfast.FailFastCheck; import dev.failsafe.Failsafe; @@ -46,6 +39,8 @@ import io.fabric8.openshift.api.model.operatorhub.lifecyclemanager.v1.PackageChannel; import io.fabric8.openshift.api.model.operatorhub.lifecyclemanager.v1.PackageManifest; import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CRDDescription; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSource; +import io.fabric8.openshift.api.model.operatorhub.v1alpha1.CatalogSourceBuilder; import lombok.extern.slf4j.Slf4j; /** @@ -59,7 +54,7 @@ * oc get clusterserviceversion */ @Slf4j -public abstract class OperatorProvisioner implements OpenShiftProvisioner { +public abstract class OperatorProvisioner implements OlmOperatorProvisioner { // cache the current csv and list of provided custom resource definitions static String currentCSV; final String packageManifestName; @@ -68,8 +63,6 @@ public abstract class OperatorProvisioner impleme private PackageManifest packageManifest; private String operatorChannel; protected FailFastCheck ffCheck = () -> false; - private OpenShift adminShift; - private OpenShiftBinary adminBinary; private Set customResourceDefinitions; private static final RetryPolicy RETRY_POLICY_LOOKUP_MATCHING_PACKAGE_MANIFEST = RetryPolicy . builder() @@ -86,9 +79,6 @@ public OperatorProvisioner(T operatorApplication, String packageManifestName) { @Override public void configure() { - this.adminShift = OpenShifts.admin(); - this.adminBinary = OpenShifts.adminBinary(); - // custom catalog source initialization catalogSource = initCatalogSource(); @@ -123,17 +113,15 @@ public void configure() { protected abstract String getOperatorChannel(); + protected abstract String getOperatorNamespace(); + /** * The CatalogSource is in the "openshift-marketplace" namespace by default; * When a custom operator image must be used, then a custom CatalogSource will be created in the current namespace; * * @return namespace where the custom CatalogSource is located */ - private String getCatalogSourceNamespace() { - String namespace = IntersmashConfig.defaultOperatorCatalogSourceNamespace(); // default namespace for CatalogSources - if (!Strings.isNullOrEmpty(getOperatorIndexImage())) { - namespace = OpenShiftConfig.namespace(); - } - return namespace; + protected String getCatalogSourceNamespace() { + return IntersmashConfig.defaultOperatorCatalogSourceNamespace(); // default namespace for CatalogSources } /** @@ -185,20 +173,28 @@ private CatalogSource initCatalogSource() { catalogSourceName = operatorCatalogSource; } // create CatalogSource pointing to our custom IndexImage - catalogSource = new CatalogSource( - // a composite name is needed in order to avoid conflicts in case of multiple custom CatalogSources - catalogSourceName, - operatorCatalogSourceNamespace, - "grpc", - operatorIndexImage, - catalogSourceName, - "jboss-tests@redhat.com"); + catalogSource = new CatalogSourceBuilder() + .withNewMetadata() + .withName(catalogSourceName) + .withNamespace(operatorCatalogSourceNamespace) + .endMetadata() + .withNewSpec() + .withSourceType("grpc") + .withImage(operatorIndexImage) + .withDisplayName(catalogSourceName) + .withPublisher("intersmash@intersmash.org") + .endSpec() + .build(); try { - adminBinary.execute("apply", "-f", catalogSource.save().getAbsolutePath()); + this.execute("apply", "-f", + new org.jboss.intersmash.tools.provision.openshift.operator.resources.CatalogSource() + .load(catalogSource) + .save() + .getAbsolutePath()); AtomicReference catalogSourceStatus = new AtomicReference<>(); new SimpleWaiter(() -> { // oc get CatalogSource redhat-operators -n openshift-marketplace -o template --template {{.status.connectionState.lastObservedState}} - catalogSourceStatus.set(adminBinary.execute("get", "CatalogSource", catalogSource.getMetadata().getName(), + catalogSourceStatus.set(this.execute("get", "CatalogSource", catalogSource.getMetadata().getName(), "-n", operatorCatalogSourceNamespace, "-o", "template", "--template", "{{.status.connectionState.lastObservedState}}", @@ -220,27 +216,22 @@ private CatalogSource initCatalogSource() { } } else { // load CatalogSource by name from OpenShift cluster - catalogSource = new CatalogSource(); - catalogSource.load(operatorCatalogSource, - IntersmashConfig.defaultOperatorCatalogSourceNamespace()); + catalogSource = getCatalogSource(IntersmashConfig.defaultOperatorCatalogSourceNamespace(), operatorCatalogSource); } return catalogSource; } private PackageManifest initPackageManifest() { log.debug("Listing package manifests belonging to: " + this.catalogSource.getMetadata().getName()); - List catalogSourcePackageManifests = adminShift.operatorHub().packageManifests().list().getItems() - .stream() - .filter(pm -> this.catalogSource.getMetadata().getName().equals(pm.getStatus().getCatalogSource())) - .collect(Collectors.toList()); - catalogSourcePackageManifests.stream() - .forEach(pm -> log.debug("---> " + pm.getMetadata().getName())); - return catalogSourcePackageManifests.stream() - .filter(pm -> this.packageManifestName.equals(pm.getMetadata().getName())) - .findFirst().orElseThrow( - () -> new IllegalStateException( - "Unable to retrieve PackageManifest " + this.packageManifestName + " in CatalogSource " - + this.catalogSource.getMetadata().getName())); + PackageManifest catalogSourcePackageManifest = this.getPackageManifest(this.packageManifestName, + this.getOperatorNamespace()); + if (catalogSourcePackageManifest == null) { + throw new IllegalStateException( + "Unable to retrieve PackageManifest " + this.packageManifestName + " in CatalogSource " + + this.catalogSource.getMetadata().getName()); + } + log.debug("---> " + catalogSourcePackageManifest.getMetadata().getName()); + return catalogSourcePackageManifest; } private PackageChannel initPackageChannel(String channelName) { @@ -306,11 +297,11 @@ public void subscribe(String installPlanApproval, Map envVariabl // oc get packagemanifest wildfly -o template --template {{.status.defaultChannel}} Subscription operatorSubscription = (envVariables == null || envVariables.isEmpty()) ? new Subscription(getCatalogSourceNamespace(), getOperatorCatalogSource(), packageManifestName, - operatorChannel, installPlanApproval) + operatorChannel, installPlanApproval, getTargetNamespace()) : new Subscription(getCatalogSourceNamespace(), getOperatorCatalogSource(), packageManifestName, - operatorChannel, installPlanApproval, envVariables); + operatorChannel, installPlanApproval, envVariables, getTargetNamespace()); try { - adminBinary.execute("apply", "-f", operatorSubscription.save().getAbsolutePath()); + this.execute("apply", "-f", operatorSubscription.save().getAbsolutePath()); } catch (IOException e) { throw new RuntimeException(String.format("Failed to serialize the %s subscription object into a yaml file.", operatorSubscription.getMetadata().getName()), e); @@ -322,7 +313,7 @@ public void subscribe(String installPlanApproval, Map envVariabl // wait for installPlan to be attached to the subscription new SimpleWaiter(() -> { // oc get subscription rhsso-operator -o template --template="{{.status.installplan.name}}" - installPlan.set(adminBinary.execute("get", "subscription", operatorSubscription.getMetadata().getName(), + installPlan.set(this.execute("get", "subscription", operatorSubscription.getMetadata().getName(), "-o", "template", "--template", "{{ if .status.installPlanRef.name }}{{.status.installPlanRef.name}}{{ end }}", "--ignore-not-found")); @@ -336,7 +327,7 @@ public void subscribe(String installPlanApproval, Map envVariabl .level(Level.DEBUG) .failFast(getFailFastCheck()) .waitFor(); - String outcome = adminBinary.execute("patch", "InstallPlan", installPlan.get(), + String outcome = this.execute("patch", "InstallPlan", installPlan.get(), "--type", "merge", "--patch", "{\"spec\":{\"approved\":true}}"); if (!Strings.isNullOrEmpty(outcome) && outcome.contains("patched")) { log.info("Approved InstallPlan {} for subscription {}", @@ -350,7 +341,7 @@ public void subscribe(String installPlanApproval, Map envVariabl } // oc get clusterserviceversion wildfly-operator.v1.0.0 -o template --template {{.status.phase}} new SimpleWaiter(() -> { - String clusterServicePhase = adminBinary.execute("get", "csvs", currentCSV, "-o", "template", "--template", + String clusterServicePhase = this.execute("get", "csvs", currentCSV, "-o", "template", "--template", "{{.status.phase}}", "--ignore-not-found"); // this is the one where the operator image is pulled return clusterServicePhase != null && clusterServicePhase.equals("Succeeded"); @@ -361,6 +352,8 @@ public void subscribe(String installPlanApproval, Map envVariabl waitForOperatorPod(); } + protected abstract String getTargetNamespace(); + /** *

Waits for the operator's POD to be ready;

* @@ -397,7 +390,7 @@ public void subscribe(String installPlanApproval, Map envVariabl protected void waitForOperatorPod() { final String metadataNameLabelLegacyName = "name"; final String metadataNameLabelName = "app.kubernetes.io/name"; - String[] operatorSpecs = adminBinary.execute( + String[] operatorSpecs = this.execute( "get", "csvs", currentCSV, @@ -413,8 +406,14 @@ protected void waitForOperatorPod() { if (operatorSpec.length != 3) { throw new IllegalStateException("Failed to get operator deployment spec from csvs!"); } - OpenShiftWaiters.get(openShift, getFailFastCheck()) - .areExactlyNPodsReady(Integer.valueOf(operatorSpec[0]), operatorSpec[1], operatorSpec[2]).level(Level.DEBUG) + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> !com.google.common.base.Strings.isNullOrEmpty(p.getMetadata().getLabels().get(operatorSpec[1])) + && p.getMetadata().getLabels().get(operatorSpec[1]).equals(operatorSpec[2])) + .collect(Collectors.toList()).size() == Integer.valueOf(operatorSpec[0]); + String reason = "Waiting for exactly " + Integer.valueOf(operatorSpec[0]) + " pods with label \"app\"=" + + getApplication().getName() + " to be ready."; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, reason) + .level(Level.DEBUG) .waitFor(); } } @@ -428,45 +427,36 @@ protected FailFastCheck getFailFastCheck() { * Documentation: https://docs.openshift.com/container-platform/4.4/operators/olm-deleting-operators-from-cluster.html#olm-deleting-operator-from-a-cluster-using-cli_olm-deleting-operators-from-a-cluster */ public void unsubscribe() { - adminBinary.execute("delete", "subscription", packageManifestName, "--ignore-not-found"); - adminBinary.execute("delete", "csvs", currentCSV, "--ignore-not-found"); + this.execute("delete", "subscription", packageManifestName, "--ignore-not-found"); + this.execute("delete", "csvs", currentCSV, "--ignore-not-found"); for (String customResource : getCustomResourceDefinitions()) { - final String crds = adminBinary.execute("get", "crd", customResource, "--ignore-not-found"); + final String crds = this.execute("get", "crd", customResource, "--ignore-not-found"); if (crds != null && !crds.isEmpty()) { log.info("CRD: {} is still defined on the cluster", customResource); } } } - @Override - public URL getURL() { - throw new UnsupportedOperationException("To be implemented!"); - } - /** * @return true is there is an active subscription for the current operator */ - protected boolean isSubscribed() { - return !Strings.isNullOrEmpty(adminBinary.execute("get", "subscription", packageManifestName, + public boolean isSubscribed() { + return !Strings.isNullOrEmpty(this.execute("get", "subscription", packageManifestName, "-o", "template", "--template", "{{ .status.state }}", "--ignore-not-found")); } - protected static String getCurrentCSV() { + public String getCurrentCSV() { return currentCSV; } - protected OpenShiftBinary getAdminBinary() { - return adminBinary; - } - @Override public void dismiss() { // let's remove any custom catalog source if (Arrays.stream(IntersmashConfig.getKnownCatalogSources()) .noneMatch(cs -> this.catalogSource.getMetadata().getName().equals(cs))) { - adminBinary.execute("delete", "catalogsource", catalogSource.getMetadata().getName(), "--ignore-not-found"); + this.execute("delete", "catalogsource", catalogSource.getMetadata().getName(), "--ignore-not-found"); } } } diff --git a/tools/intersmash-tools-core/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/SubscriptionTest.java b/tools/intersmash-tools-core/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/SubscriptionTest.java index c35a6b19..05f2dac1 100644 --- a/tools/intersmash-tools-core/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/SubscriptionTest.java +++ b/tools/intersmash-tools-core/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/SubscriptionTest.java @@ -19,10 +19,11 @@ import java.io.IOException; import java.util.Map; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import cz.xtf.core.config.OpenShiftConfig; import io.fabric8.kubernetes.api.model.EnvVar; public class SubscriptionTest { @@ -43,7 +44,8 @@ public void testEnv() throws IOException { "PROP_1", propertyValue, "PROP_2", propertyValue, "PROP_3", propertyValue, - "PROP_4", "a different value")); + "PROP_4", "a different value"), + OpenShiftConfig.namespace()); File subscriptionFile = subscription.save(); Subscription loadedSubscription = new Subscription(); loadedSubscription.load(subscriptionFile); diff --git a/tools/intersmash-tools-provisioners/pom.xml b/tools/intersmash-tools-provisioners/pom.xml index 03f36a3c..c688c2ec 100644 --- a/tools/intersmash-tools-provisioners/pom.xml +++ b/tools/intersmash-tools-provisioners/pom.xml @@ -131,11 +131,6 @@ 2.0.1.Final - - uk.org.webcompere - system-stubs-jupiter - - org.mockito mockito-core diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySource.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySource.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySource.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySource.java index dd568e4b..13dfcf37 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySource.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; import java.nio.file.Path; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySourceBuilder.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySourceBuilder.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySourceBuilder.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySourceBuilder.java index 20aa3e78..b99d18e6 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BinarySourceBuilder.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BinarySourceBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; import java.nio.file.Path; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInput.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInput.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInput.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInput.java index 710f2cd2..04fce25d 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInput.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInput.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; /** * Use the {@link BuildInputBuilder} to get instances implementing the {@link BuildInput} interface. diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInputBuilder.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInputBuilder.java similarity index 97% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInputBuilder.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInputBuilder.java index 3d2ea565..07995310 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/BuildInputBuilder.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/BuildInputBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; import java.io.File; import java.io.IOException; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSource.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSource.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSource.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSource.java index 0602f152..1f3634d1 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSource.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSource.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; /** * {@link BuildInput} represented by Git URI and reference diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSourceBuilder.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSourceBuilder.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSourceBuilder.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSourceBuilder.java index 98ba9598..ba8285fb 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/input/GitSourceBuilder.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/input/GitSourceBuilder.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift.input; +package org.jboss.intersmash.tools.application.input; public interface GitSourceBuilder { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/BootableJarOpenShiftApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/BootableJarOpenShiftApplication.java index 57b08e49..24f1555a 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/BootableJarOpenShiftApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/BootableJarOpenShiftApplication.java @@ -18,7 +18,7 @@ import java.util.Collections; import java.util.List; -import org.jboss.intersmash.tools.application.openshift.input.BinarySource; +import org.jboss.intersmash.tools.application.input.BinarySource; import org.jboss.intersmash.tools.provision.openshift.WildflyBootableJarImageOpenShiftProvisioner; import io.fabric8.kubernetes.api.model.EnvVar; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyImageOpenShiftApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyImageOpenShiftApplication.java index d746313d..0222412c 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyImageOpenShiftApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyImageOpenShiftApplication.java @@ -21,10 +21,10 @@ import java.util.Map; import java.util.Set; -import org.jboss.intersmash.tools.application.openshift.input.BinarySourceBuilder; -import org.jboss.intersmash.tools.application.openshift.input.BuildInput; -import org.jboss.intersmash.tools.application.openshift.input.BuildInputBuilder; -import org.jboss.intersmash.tools.application.openshift.input.GitSourceBuilder; +import org.jboss.intersmash.tools.application.input.BinarySourceBuilder; +import org.jboss.intersmash.tools.application.input.BuildInput; +import org.jboss.intersmash.tools.application.input.BuildInputBuilder; +import org.jboss.intersmash.tools.application.input.GitSourceBuilder; import cz.xtf.builder.builders.pod.PersistentVolumeClaim; import cz.xtf.builder.builders.pod.VolumeMount; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/ActiveMQOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/ActiveMQOperatorApplication.java similarity index 85% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/ActiveMQOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/ActiveMQOperatorApplication.java index 700abe6d..f2ae7db3 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/ActiveMQOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/ActiveMQOperatorApplication.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.ActiveMQOperatorProvisioner; - import io.amq.broker.v1beta1.ActiveMQArtemis; import io.amq.broker.v1beta1.ActiveMQArtemisAddress; @@ -27,7 +25,7 @@ * * The application will be deployed by: *
    - *
  • {@link ActiveMQOperatorProvisioner}
  • + *
  • {@link org.jboss.intersmash.tools.provision.operator.ActiveMQOperatorProvisioner}
  • *
*/ public interface ActiveMQOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/HyperfoilOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/HyperfoilOperatorApplication.java similarity index 92% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/HyperfoilOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/HyperfoilOperatorApplication.java index b9c25b23..e862a688 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/HyperfoilOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/HyperfoilOperatorApplication.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import io.hyperfoil.v1alpha2.Hyperfoil; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/InfinispanOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/InfinispanOperatorApplication.java similarity index 85% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/InfinispanOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/InfinispanOperatorApplication.java index 7324fc61..072f26a0 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/InfinispanOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/InfinispanOperatorApplication.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.InfinispanOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.Cache; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.Infinispan; @@ -26,7 +25,7 @@ * * The application will be deployed by: *
    - *
  • {@link InfinispanOperatorProvisioner}
  • + *
  • {@link org.jboss.intersmash.tools.provision.operator.InfinispanOperatorProvisioner}
  • *
*/ public interface InfinispanOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KafkaOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KafkaOperatorApplication.java similarity index 91% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KafkaOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KafkaOperatorApplication.java index ab9c0d9b..81e21888 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KafkaOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KafkaOperatorApplication.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.KafkaOperatorProvisioner; - import io.strimzi.api.kafka.model.Kafka; import io.strimzi.api.kafka.model.KafkaTopic; import io.strimzi.api.kafka.model.KafkaUser; @@ -28,7 +26,7 @@ *

* The application will be deployed by: *

    - *
  • {@link KafkaOperatorProvisioner}
  • + *
  • {@link org.jboss.intersmash.tools.provision.operator.KafkaOperatorProvisioner}
  • *
*/ public interface KafkaOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakOperatorApplication.java similarity index 90% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakOperatorApplication.java index 728f2fd2..66b39f7a 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakOperatorApplication.java @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.Collections; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.KeycloakOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackup; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.client.KeycloakClient; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.keycloak.Keycloak; @@ -30,7 +29,7 @@ * * The application will be deployed by: *
    - *
  • {@link KeycloakOperatorProvisioner}
  • + *
  • {@link org.jboss.intersmash.tools.provision.operator.KeycloakOperatorProvisioner}
  • *
*/ public interface KeycloakOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakRealmImportOperatorApplication.java similarity index 87% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakRealmImportOperatorApplication.java index 2af2c7ef..2b556ad5 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/KeycloakRealmImportOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/KeycloakRealmImportOperatorApplication.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; import java.util.Collections; import java.util.List; -import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KeycloakRealmImportOpenShiftOperatorProvisioner; import org.keycloak.k8s.v2alpha1.Keycloak; import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; @@ -27,7 +27,7 @@ * * The application will be deployed by: *
    - *
  • {@link KeycloakRealmImportOperatorProvisioner}
  • + *
  • {@link KeycloakRealmImportOpenShiftOperatorProvisioner}
  • *
*/ public interface KeycloakRealmImportOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyOperatorApplication.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/WildflyOperatorApplication.java similarity index 84% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyOperatorApplication.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/WildflyOperatorApplication.java index 4104fdf5..63baff57 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/openshift/WildflyOperatorApplication.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/application/operator/WildflyOperatorApplication.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.application.openshift; +package org.jboss.intersmash.tools.application.operator; -import org.jboss.intersmash.tools.provision.openshift.WildflyOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServer; /** @@ -23,7 +22,7 @@ * * The application will be deployed by: *
    - *
  • {@link WildflyOperatorProvisioner}
  • + *
  • {@link org.jboss.intersmash.tools.provision.operator.WildflyOperatorProvisioner}
  • *
*/ public interface WildflyOperatorApplication extends OperatorApplication { diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/k8s/HyperfoilKubernetesOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/k8s/HyperfoilKubernetesOperatorProvisioner.java new file mode 100644 index 00000000..4afbdd20 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/k8s/HyperfoilKubernetesOperatorProvisioner.java @@ -0,0 +1,213 @@ +/** + * 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.k8s; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Objects; + +import org.assertj.core.util.Strings; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; +import org.jboss.intersmash.tools.k8s.KubernetesConfig; +import org.jboss.intersmash.tools.k8s.client.Kuberneteses; +import org.jboss.intersmash.tools.provision.operator.HyperfoilOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; + +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.hyperfoil.v1alpha2.Hyperfoil; +import io.hyperfoil.v1alpha2.HyperfoilList; +import lombok.NonNull; + +/** + *

@see io.hyperfoil.v1alpha2 package-info.java file, for details about how to create/update/delete an + * Hyperfoil Custom Resource

+ *

@see org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.release021 package-info.java + * file, for details about how to interact with the Hyperfoil Server which is started by the Hyperfoil Operator + * when an Hyperfoil Custom Resource is created

+ */ +public class HyperfoilKubernetesOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Hyperfoil common Operator based provisioner behavior + implements HyperfoilOperatorProvisioner, + // leverage common Kubernetes provisioning logic + KubernetesProvisioner { + + protected static NonNamespaceOperation> HYPERFOIL_CUSTOM_RESOURCE_CLIENT; + + public HyperfoilKubernetesOperatorProvisioner(@NonNull HyperfoilOperatorApplication hyperfoilOperatorApplication) { + super(hyperfoilOperatorApplication, HyperfoilOperatorProvisioner.operatorId()); + } + + public HasMetadataOperationsImpl hyperfoilCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return Kuberneteses + .master().customResources(crdc, Hyperfoil.class, HyperfoilList.class); + } + + /** + * Get a client capable of working with {@link HyperfoilOperatorProvisioner#hyperfoilCustomResourceDefinitionName()} custom resource. + * + * @return client for operations with {@link HyperfoilOperatorProvisioner#hyperfoilCustomResourceDefinitionName()} custom resource + */ + public NonNamespaceOperation> hyperfoilClient() { + if (HYPERFOIL_CUSTOM_RESOURCE_CLIENT == null) { + HYPERFOIL_CUSTOM_RESOURCE_CLIENT = buildHyperfoilClient().inNamespace(KubernetesConfig.namespace()); + } + return HYPERFOIL_CUSTOM_RESOURCE_CLIENT; + } + + @Override + public void deploy() { + // TODO routes/ingresses ? + HyperfoilOperatorProvisioner.super.deploy(); + } + + @Override + public void undeploy() { + // TODO routes/ingresses ? + HyperfoilOperatorProvisioner.super.undeploy(); + } + + @Override + protected String getCatalogSourceNamespace() { + String namespace = super.getCatalogSourceNamespace(); + if (!Strings.isNullOrEmpty(getOperatorIndexImage())) { + namespace = KubernetesConfig.namespace(); + } + return namespace; + } + + @Override + public String execute(String... args) { + return Kuberneteses.adminBinary().execute(args); + } + + @Override + public URL getURL() { + Ingress ingress = retrieveNamedIngress(getApplication().getHyperfoil().getMetadata().getName()); + if (Objects.nonNull(ingress)) { + if (ingress.getSpec().getRules().get(0) != null + && ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0) != null) { + String host = ingress.getSpec().getRules().get(0).getHost(); + String url = String.format("%s://%s:%s/%s", + ingress.getSpec().getTls() != null ? "https" : "http", + host, + ingress.getSpec().getTls() != null ? "80" : "8443", + ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0).getPath()); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(String.format("Hyperfoil operator route \"%s\"is malformed.", url), e); + } + } + } else { + Service service = KubernetesProvisioner.kubernetes.services() + .withName(getApplication().getHyperfoil().getMetadata().getName()).get(); + if (Objects.nonNull(service)) { + if (service.getSpec().getPorts().get(0) != null) { + // TODO - property for default hostname (e.g. INgresses can't have IP)? + final String url = String.format("http://%s:%d", + KubernetesProvisioner.kubernetes.generateHostname(), + service.getSpec().getPorts().get(0) != null ? service.getSpec().getPorts().get(0).getNodePort() + : "80"); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException( + String.format("Hyperfoil operator service NodePort \"%s\"is malformed.", url), e); + } + } + } + } + return null; + } + + @Override + public Ingress retrieveNamedIngress(final String ingressName) { + return KubernetesProvisioner.kubernetes.network().v1().ingresses().withName(ingressName).get(); + } + + @Override + public List getPods() { + return HyperfoilOperatorProvisioner.super.getPods(); + } + + @Override + public void waitForOperatorPod() { + HyperfoilOperatorProvisioner.super.waitForOperatorPod(); + } + + @Override + public List retrieveNamespacePods() { + return KubernetesProvisioner.kubernetes.pods().inNamespace(KubernetesProvisioner.kubernetes.getNamespace()).list() + .getItems(); + } + + @Override + public Pod retrieveNamedPod(final String podName) { + return KubernetesProvisioner.kubernetes.pods().withName(podName).get(); + } + + public List retrievePods() { + return KubernetesProvisioner.kubernetes.pods().inNamespace(KubernetesProvisioner.kubernetes.getNamespace()).list() + .getItems(); + } + + @Override + public String getOperatorCatalogSource() { + return HyperfoilOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return HyperfoilOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return HyperfoilOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + protected String getOperatorNamespace() { + return "olm"; + } + + @Override + protected String getTargetNamespace() { + return KubernetesConfig.namespace(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return Kuberneteses.admin().apiextensions().v1().customResourceDefinitions(); + } + + @Override + public void scale(int replicas, boolean wait) { + throw new UnsupportedOperationException("To be implemented!"); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..56f3d295 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOpenShiftOperatorProvisioner.java @@ -0,0 +1,168 @@ +/** + * 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.util.List; + +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.operator.activemq.address.ActiveMQArtemisAddressList; +import org.jboss.intersmash.tools.provision.openshift.operator.activemq.broker.ActiveMQArtemisList; +import org.jboss.intersmash.tools.provision.operator.ActiveMQOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.amq.broker.v1beta1.ActiveMQArtemis; +import io.amq.broker.v1beta1.ActiveMQArtemisAddress; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import lombok.NonNull; + +/** + * ActiveMQ Operator based provisioner + */ +public class ActiveMQOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage ActiveMQ Artemis common Operator based provisioner behavior + implements ActiveMQOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + private static NonNamespaceOperation> ACTIVE_MQ_ARTEMISES_CLIENT; + private static NonNamespaceOperation> ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT; + + // private final static String ACTIVE_MQ_ARTEMIS_SCALEDOWN_RESOURCE = "activemqartemisscaledowns.broker.amq.io"; // TODO add on demand + + private static final String OPERATOR_ID = IntersmashConfig.activeMQOperatorPackageManifest(); + + public ActiveMQOpenShiftOperatorProvisioner(@NonNull ActiveMQOperatorApplication activeMqOperatorApplication) { + super(activeMqOperatorApplication, ActiveMQOperatorProvisioner.operatorId()); + } + + @Override + public HasMetadataOperationsImpl activeMQArtemisAddressesCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, ActiveMQArtemisAddress.class, ActiveMQArtemisAddressList.class); + } + + @Override + public HasMetadataOperationsImpl activeMQArtemisCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, ActiveMQArtemis.class, ActiveMQArtemisList.class); + } + + /** + * Get a client capable of working with {@link ActiveMQOperatorProvisioner#activeMQCustomResourceDefinitionName()} custom resource. + * + * @return client for operations with {@link ActiveMQOperatorProvisioner#activeMQCustomResourceDefinitionName()} custom resource + */ + public NonNamespaceOperation> activeMQArtemisAddressesClient() { + if (ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT == null) { + ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT = buildActiveMQArtemisAddressesClient().inNamespace(OpenShiftConfig.namespace()); + } + return ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT; + } + + /** + * Get a client capable of working with {@link {@link ActiveMQOperatorProvisioner#activeMQCustomResourceDefinitionName()}} custom resource. + * + * @return client for operations with {@link {@link ActiveMQOperatorProvisioner#activeMQCustomResourceDefinitionName()}} custom resource + */ + public NonNamespaceOperation> activeMQArtemisesClient() { + if (ACTIVE_MQ_ARTEMISES_CLIENT == null) { + ACTIVE_MQ_ARTEMISES_CLIENT = buildActiveMQArtemisesClient(); + } + return ACTIVE_MQ_ARTEMISES_CLIENT; + } + + /** + * Get a reference to activeMQArtemis 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 ActiveMQArtemis} resource definition + */ + public Resource activeMQArtemis() { + return activeMQArtemisesClient().withName(getApplication().getActiveMQArtemis().getMetadata().getName()); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public void scale(int replicas, boolean wait) { + ActiveMQOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.pods().inNamespace(OpenShiftProvisioner.openShift.getNamespace()).list() + .getItems(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } + + /** + * Get the provisioned application service related Pods + *

+ * Currently blocked by the fact that Pod Status pod names do not reflect the reality + *

+ * Once these issues are resolved, we can use the ready pod names returned by + * {@code ActiveMQArtemisStatus.getPodStatus()} to create the List with pods maintained by the provisioner. + * + * @return A list of related {@link Pod} instances + */ + @Override + public List getPods() { + throw new UnsupportedOperationException("To be implemented!"); + } + + @Override + public String getOperatorCatalogSource() { + return ActiveMQOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return ActiveMQOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return ActiveMQOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisionerFactory.java index ecba25b6..5637c779 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisionerFactory.java @@ -16,18 +16,18 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.ActiveMQOperatorApplication; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class ActiveMQOperatorProvisionerFactory implements ProvisionerFactory { +public class ActiveMQOperatorProvisionerFactory implements ProvisionerFactory { @Override - public ActiveMQOperatorProvisioner getProvisioner(Application application) { + public ActiveMQOpenShiftOperatorProvisioner getProvisioner(Application application) { if (ActiveMQOperatorApplication.class.isAssignableFrom(application.getClass())) - return new ActiveMQOperatorProvisioner((ActiveMQOperatorApplication) application); + return new ActiveMQOpenShiftOperatorProvisioner((ActiveMQOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/BootableJarImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/BootableJarImageOpenShiftProvisioner.java index 01ede174..140c298c 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/BootableJarImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/BootableJarImageOpenShiftProvisioner.java @@ -23,9 +23,9 @@ import java.util.stream.Collectors; import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.input.BinarySource; +import org.jboss.intersmash.tools.application.input.BuildInput; import org.jboss.intersmash.tools.application.openshift.BootableJarOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.input.BinarySource; -import org.jboss.intersmash.tools.application.openshift.input.BuildInput; import org.slf4j.event.Level; import cz.xtf.builder.builders.ApplicationBuilder; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..24489358 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOpenShiftOperatorProvisioner.java @@ -0,0 +1,176 @@ +/** + * 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.Objects; + +import org.assertj.core.util.Strings; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; +import org.jboss.intersmash.tools.provision.operator.HyperfoilOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.fabric8.openshift.api.model.Route; +import io.hyperfoil.v1alpha2.Hyperfoil; +import io.hyperfoil.v1alpha2.HyperfoilList; +import lombok.NonNull; + +/** + *

@see io.hyperfoil.v1alpha2 package-info.java file, for details about how to create/update/delete an + * Hyperfoil Custom Resource

+ *

@see org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.release021 package-info.java + * file, for details about how to interact with the Hyperfoil Server which is started by the Hyperfoil Operator + * when an Hyperfoil Custom Resource is created

+ */ +public class HyperfoilOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Hyperfoil common Operator based provisioner behavior + implements HyperfoilOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + + protected static NonNamespaceOperation> HYPERFOIL_CUSTOM_RESOURCE_CLIENT; + + public HyperfoilOpenShiftOperatorProvisioner(@NonNull HyperfoilOperatorApplication hyperfoilOperatorApplication) { + super(hyperfoilOperatorApplication, HyperfoilOperatorProvisioner.operatorId()); + } + + public HasMetadataOperationsImpl hyperfoilCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, Hyperfoil.class, HyperfoilList.class); + } + + /** + * Get a client capable of working with {@link HyperfoilOperatorProvisioner#hyperfoilCustomResourceDefinitionName()} custom resource. + * + * @return client for operations with {@link HyperfoilOperatorProvisioner#hyperfoilCustomResourceDefinitionName()} custom resource + */ + public NonNamespaceOperation> hyperfoilClient() { + if (HYPERFOIL_CUSTOM_RESOURCE_CLIENT == null) { + HYPERFOIL_CUSTOM_RESOURCE_CLIENT = buildHyperfoilClient().inNamespace(OpenShiftConfig.namespace()); + } + return HYPERFOIL_CUSTOM_RESOURCE_CLIENT; + } + + @Override + public Ingress retrieveNamedIngress(final String ingressName) { + return OpenShiftProvisioner.openShift.network().v1().ingresses().withName(ingressName).get(); + } + + @Override + protected String getCatalogSourceNamespace() { + String namespace = super.getCatalogSourceNamespace(); + if (!Strings.isNullOrEmpty(getOperatorIndexImage())) { + namespace = OpenShiftConfig.namespace(); + } + return namespace; + } + + @Override + public List getPods() { + return HyperfoilOperatorProvisioner.super.getPods(); + } + + @Override + public void waitForOperatorPod() { + HyperfoilOperatorProvisioner.super.waitForOperatorPod(); + } + + @Override + public String getOperatorCatalogSource() { + return HyperfoilOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return HyperfoilOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return HyperfoilOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + public List retrieveNamespacePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public Pod retrieveNamedPod(final String podName) { + return OpenShiftProvisioner.openShift.pods().withName(podName).get(); + } + + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } + + @Override + public void scale(int replicas, boolean wait) { + throw new UnsupportedOperationException("To be implemented!"); + } + + @Override + public URL getURL() { + Route route = OpenShiftProvisioner.openShift.getRoute(getApplication().getName()); + if (Objects.nonNull(route)) { + String host = route.getSpec().getHost() != null ? route.getSpec().getHost() + : route.getStatus().getIngress().get(0).getHost(); + String url = String.format("https://%s", host); + try { + return new URL( + url); + } catch (MalformedURLException e) { + throw new RuntimeException(String.format("Hyperfoil operator route \"%s\"is malformed.", url), e); + } + } + return null; + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOperatorProvisioner.java deleted file mode 100644 index 2047bba9..00000000 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/HyperfoilOperatorProvisioner.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * 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.ArrayList; -import java.util.List; -import java.util.Objects; - -import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.HyperfoilOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; -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.openshift.OpenShifts; -import cz.xtf.core.waiting.SimpleWaiter; -import io.fabric8.kubernetes.api.model.DeletionPropagation; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; -import io.fabric8.kubernetes.client.dsl.MixedOperation; -import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; -import io.fabric8.kubernetes.client.dsl.Resource; -import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; -import io.fabric8.openshift.api.model.Route; -import io.hyperfoil.v1alpha2.Hyperfoil; -import io.hyperfoil.v1alpha2.HyperfoilList; -import lombok.NonNull; - -/** - *

@see io.hyperfoil.v1alpha2 package-info.java file, for details about how to create/update/delete an - * Hyperfoil Custom Resource

- *

@see org.jboss.intersmash.tools.provision.openshift.operator.hyperfoil.client.release021 package-info.java - * file, for details about how to interact with the Hyperfoil Server which is started by the Hyperfoil Operator - * when an Hyperfoil Custom Resource is created

- */ -public class HyperfoilOperatorProvisioner extends OperatorProvisioner { - // this is the name of the Hyperfoil CustomResourceDefinition - // you can get it with command: - // oc get crd hyperfoils.hyperfoil.io -o template --template='{{ .metadata.name }}' - private final static String HYPERFOIL_CUSTOM_RESOURCE_DEFINITION = "hyperfoils.hyperfoil.io"; - private static NonNamespaceOperation> HYPERFOIL_CUSTOM_RESOURCE_CLIENT; - // this is the packagemanifest for the hyperfoil operator; - // you can get it with command: - // oc get packagemanifest hyperfoil-bundle -o template --template='{{ .metadata.name }}' - private static final String OPERATOR_ID = IntersmashConfig.hyperfoilOperatorPackageManifest(); - - public HyperfoilOperatorProvisioner(@NonNull HyperfoilOperatorApplication hyperfoilOperatorApplication) { - super(hyperfoilOperatorApplication, OPERATOR_ID); - } - - public static String getOperatorId() { - return OPERATOR_ID; - } - - /** - * Get a client capable of working with {@link #HYPERFOIL_CUSTOM_RESOURCE_DEFINITION} custom resource. - * - * @return client for operations with {@link #HYPERFOIL_CUSTOM_RESOURCE_DEFINITION} custom resource - */ - NonNamespaceOperation> hyperfoilClient() { - if (HYPERFOIL_CUSTOM_RESOURCE_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(HYPERFOIL_CUSTOM_RESOURCE_DEFINITION).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(HYPERFOIL_CUSTOM_RESOURCE_DEFINITION)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - HYPERFOIL_CUSTOM_RESOURCE_DEFINITION, OPERATOR_ID)); - } - MixedOperation> hyperfoilCrClient = OpenShifts - .master().customResources(crdc, Hyperfoil.class, HyperfoilList.class); - HYPERFOIL_CUSTOM_RESOURCE_CLIENT = hyperfoilCrClient.inNamespace(OpenShiftConfig.namespace()); - } - return HYPERFOIL_CUSTOM_RESOURCE_CLIENT; - } - - /** - * Get a reference to Hyperfoil 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 Hyperfoil} resource definition - */ - public Resource hyperfoil() { - return hyperfoilClient().withName(getApplication().getName()); - } - - @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); - if (!isSubscribed()) { - subscribe(); - } - hyperfoilClient().createOrReplace(getApplication().getHyperfoil()); - new SimpleWaiter(() -> hyperfoil().get().getStatus() != null) - .failFast(ffCheck) - .reason("Wait for status field to be initialized.") - .level(Level.DEBUG) - .waitFor(); - new SimpleWaiter(() -> getPods().size() == 1) - .failFast(ffCheck) - .reason("Wait for expected number of replicas to be active.") - .level(Level.DEBUG) - .waitFor(); - WaitersUtil.routeIsUp(getURL().toExternalForm()) - .level(Level.DEBUG) - .waitFor(); - } - - @Override - public URL getURL() { - Route route = OpenShiftProvisioner.openShift.getRoute(getApplication().getName()); - if (Objects.nonNull(route)) { - String host = route.getSpec().getHost() != null ? route.getSpec().getHost() - : route.getStatus().getIngress().get(0).getHost(); - String url = String.format("https://%s", host); - try { - return new URL( - url); - } catch (MalformedURLException e) { - throw new RuntimeException(String.format("Hyperfoil operator route \"%s\"is malformed.", url), e); - } - } - return null; - } - - @Override - public void undeploy() { - undeploy(true); - } - - public void undeploy(boolean unsubscribe) { - hyperfoil().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck).areExactlyNPodsReady(0, "app", getApplication().getName()) - .level(Level.DEBUG).waitFor(); - if (unsubscribe) { - unsubscribe(); - } - } - - @Override - public List getPods() { - List pods = new ArrayList<>(); - Pod hyperfoilControllerPod = OpenShiftProvisioner.openShift - .getPod(String.format("%s-controller", getApplication().getName())); - if (isContainerReady(hyperfoilControllerPod, "controller")) { - pods.add(hyperfoilControllerPod); - } - return pods; - } - - /** - * This method checks if the Operator's POD is actually running; - * It's been tailored on the community-operators Cluster Service version format which is missing label - * spec.install.spec.deployments.spec.template.metadata.labels."app.kubernetes.io/name" which is used - * in @see OperatorProvisioner#waitForOperatorPod() (see - * https://github.com/operator-framework/community-operators/tree/master/community-operators/hyperfoil-bundle) - */ - @Override - protected void waitForOperatorPod() { - String[] operatorSpecs = getAdminBinary().execute("get", "csvs", getCurrentCSV(), "-o", "template", "--template", - "{{range .spec.install.spec.deployments}}{{printf \"%d|%s\\n\" .spec.replicas .name}}{{end}}") - .split(System.lineSeparator()); - for (String spec : operatorSpecs) { - String[] operatorSpec = spec.split("\\|"); - if (operatorSpec.length != 2) { - throw new RuntimeException("Failed to get operator deployment spec from csvs!"); - } - new SimpleWaiter(() -> OpenShiftProvisioner.openShift.getPods().stream().filter( - pod -> (pod.getMetadata() - .getName() - .startsWith(operatorSpec[1]) - && pod.getStatus().getPhase().equalsIgnoreCase("Running"))) - .count() == Integer.valueOf(operatorSpec[0])) - .failFast(ffCheck) - .reason("Wait for expected number of replicas to be active.") - .level(Level.DEBUG) - .waitFor(); - } - } - - /** - * Tells if a specific container inside the pod is ready - * - * @param pod - * @param containerName: name of the container - * @return - */ - private boolean isContainerReady(Pod pod, String containerName) { - if (Objects.nonNull(pod)) { - return pod.getStatus().getContainerStatuses().stream() - .filter(containerStatus -> containerStatus.getName().equalsIgnoreCase(containerName) - && containerStatus.getReady()) - .count() > 0; - } - return false; - } - - @Override - protected String getOperatorCatalogSource() { - return IntersmashConfig.hyperfoilOperatorCatalogSource(); - } - - @Override - protected String getOperatorIndexImage() { - return IntersmashConfig.hyperfoilOperatorIndexImage(); - } - - @Override - protected String getOperatorChannel() { - return IntersmashConfig.hyperfoilOperatorChannel(); - } - - @Override - public void scale(int replicas, boolean wait) { - throw new UnsupportedOperationException("To be implemented!"); - } -} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..4223016e --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOpenShiftOperatorProvisioner.java @@ -0,0 +1,156 @@ +/** + * 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.URL; +import java.util.List; + +import org.jboss.intersmash.tools.application.operator.InfinispanOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.Cache; +import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.CacheList; +import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.Infinispan; +import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.InfinispanList; +import org.jboss.intersmash.tools.provision.operator.InfinispanOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.fabric8.openshift.api.model.Route; +import lombok.NonNull; + +public class InfinispanOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Hyperfoil common Operator based provisioner behavior + implements InfinispanOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + private static NonNamespaceOperation> INFINISPAN_CLIENT; + private static NonNamespaceOperation> INFINISPAN_CACHES_CLIENT; + + public InfinispanOpenShiftOperatorProvisioner( + @NonNull InfinispanOperatorApplication infinispanOperatorApplication) { + super(infinispanOperatorApplication, InfinispanOperatorProvisioner.operatorId()); + } + + @Override + public URL getURL() { + return InfinispanOperatorProvisioner.super.getURL(); + } + + @Override + public StatefulSet retrieveNamedStatefulSet(final String statefulSetName) { + return OpenShiftProvisioner.openShift.getStatefulSet(statefulSetName); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public void scale(int replicas, boolean wait) { + InfinispanOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public List getPods() { + return InfinispanOperatorProvisioner.super.getPods(); + } + + @Override + public String getOperatorCatalogSource() { + return InfinispanOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return InfinispanOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return InfinispanOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + public Route retrieveNamedRoute(final String routeName) { + return OpenShiftProvisioner.openShift.getRoute(routeName); + } + + @Override + public Service retrieveNamedService(final String serviceName) { + return OpenShiftProvisioner.openShift.getService(serviceName); + } + + @Override + public HasMetadataOperationsImpl infinispanCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, Infinispan.class, InfinispanList.class); + } + + @Override + public HasMetadataOperationsImpl cacheCustomResourcesClient(CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, Cache.class, CacheList.class); + } + + @Override + public NonNamespaceOperation> infinispansClient() { + if (INFINISPAN_CLIENT == null) { + INFINISPAN_CLIENT = buildInfinispansClient().inNamespace(OpenShiftConfig.namespace()); + } + return INFINISPAN_CLIENT; + } + + @Override + public NonNamespaceOperation> cachesClient() { + if (INFINISPAN_CACHES_CLIENT == null) { + INFINISPAN_CACHES_CLIENT = buildCachesClient().inNamespace(OpenShiftConfig.namespace()); + } + return INFINISPAN_CACHES_CLIENT; + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisionerFactory.java index 2fee529d..af714f70 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisionerFactory.java @@ -16,18 +16,18 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.InfinispanOperatorApplication; +import org.jboss.intersmash.tools.application.operator.InfinispanOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class InfinispanOperatorProvisionerFactory implements ProvisionerFactory { +public class InfinispanOperatorProvisionerFactory implements ProvisionerFactory { @Override - public InfinispanOperatorProvisioner getProvisioner(Application application) { + public InfinispanOpenShiftOperatorProvisioner getProvisioner(Application application) { if (InfinispanOperatorApplication.class.isAssignableFrom(application.getClass())) - return new InfinispanOperatorProvisioner((InfinispanOperatorApplication) application); + return new InfinispanOpenShiftOperatorProvisioner((InfinispanOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..3689e115 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOpenShiftOperatorProvisioner.java @@ -0,0 +1,175 @@ +/** + * 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.util.List; + +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; +import org.jboss.intersmash.tools.provision.operator.KafkaOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; +import org.slf4j.event.Level; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.strimzi.api.kafka.Crds; +import io.strimzi.api.kafka.KafkaList; +import io.strimzi.api.kafka.KafkaTopicList; +import io.strimzi.api.kafka.KafkaUserList; +import io.strimzi.api.kafka.model.Kafka; +import io.strimzi.api.kafka.model.KafkaTopic; +import io.strimzi.api.kafka.model.KafkaUser; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +/** + * Deploys an application that implements {@link KafkaOperatorApplication} interface and which is extended by this + * class. + */ +@Slf4j +public class KafkaOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Kafka common Operator based provisioner behavior + implements KafkaOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + + private static final String OPERATOR_ID = IntersmashConfig.kafkaOperatorPackageManifest(); + + public KafkaOpenShiftOperatorProvisioner(@NonNull KafkaOperatorApplication kafkaOperatorApplication) { + super(kafkaOperatorApplication, KafkaOperatorProvisioner.operatorId()); + } + + /** + * Get a client capable of working with {@link Kafka} custom resource on our OpenShift instance. + * + * @return client for operations with {@link Kafka} custom resource on our OpenShift instance + */ + public NonNamespaceOperation> kafkasClient() { + return Crds.kafkaOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); + } + + /** + * Get a client capable of working with {@link KafkaUser} custom resource on our OpenShift instance. + * + * @return client for operations with {@link KafkaUser} custom resource on our OpenShift instance + */ + public NonNamespaceOperation> kafkasUserClient() { + return Crds.kafkaUserOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); + } + + /** + * Get a client capable of working with {@link KafkaTopic} custom resource on our OpenShift instance. + * + * @return client for operations with {@link KafkaTopic} custom resource on our OpenShift instance + */ + public NonNamespaceOperation> kafkasTopicClient() { + return Crds.topicOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public List getPods() { + return OpenShiftProvisioner.openShift.getLabeledPods("strimzi.io/cluster", getApplication().getName()); + } + + public List getClusterOperatorPods() { + return OpenShiftProvisioner.openShift.getLabeledPods("strimzi.io/kind", "cluster-operator"); + } + + @Override + public List retrieveKafkaPods() { + return OpenShiftProvisioner.openShift.getLabeledPods("app.kubernetes.io/name", "kafka"); + } + + @Override + public List retrieveKafkaZookeperPods() { + return OpenShiftProvisioner.openShift.getLabeledPods("app.kubernetes.io/name", "zookeeper"); + } + + @Override + public void scale(int replicas, boolean wait) { + KafkaOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public String getOperatorCatalogSource() { + return IntersmashConfig.kafkaOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return IntersmashConfig.kafkaOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return IntersmashConfig.kafkaOperatorChannel(); + } + + @Override + protected String getOperatorNamespace() { + return null; + } + + @Override + protected String getTargetNamespace() { + return null; + } + + @Override + public void logMessage(final String message, Level l) { + switch (l) { + case INFO: + log.info(message); + break; + case WARN: + log.warn(message); + break; + case ERROR: + log.error(message); + break; + case DEBUG: + log.debug(message); + break; + case TRACE: + log.trace(message); + break; + default: + throw new IllegalArgumentException(String.format("Unsupported log level: %s", l.name())); + } + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisionerFactory.java index 8a83b3c1..92d99500 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisionerFactory.java @@ -16,18 +16,18 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class KafkaOperatorProvisionerFactory implements ProvisionerFactory { +public class KafkaOperatorProvisionerFactory implements ProvisionerFactory { @Override - public KafkaOperatorProvisioner getProvisioner(Application application) { + public KafkaOpenShiftOperatorProvisioner getProvisioner(Application application) { if (KafkaOperatorApplication.class.isAssignableFrom(application.getClass())) - return new KafkaOperatorProvisioner((KafkaOperatorApplication) application); + return new KafkaOpenShiftOperatorProvisioner((KafkaOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..0ead3f98 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOpenShiftOperatorProvisioner.java @@ -0,0 +1,248 @@ +/** + * 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.URL; +import java.util.List; +import java.util.Map; + +import org.assertj.core.util.Strings; +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.operator.KeycloakOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackup; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackupList; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.client.KeycloakClient; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.client.KeycloakClientList; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.keycloak.Keycloak; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.keycloak.KeycloakList; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.realm.KeycloakRealm; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.realm.KeycloakRealmList; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.user.KeycloakUser; +import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.user.KeycloakUserList; +import org.jboss.intersmash.tools.provision.operator.KeycloakOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import lombok.NonNull; + +/** + * Keycloak operator provisioner + */ +public class KeycloakOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Wildfly common Operator based provisioner behavior + implements KeycloakOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + private static NonNamespaceOperation> KEYCLOAKS_CLIENT; + private static NonNamespaceOperation> KEYCLOAK_REALMS_CLIENT; + private static NonNamespaceOperation> KEYCLOAK_BACKUPS_CLIENT; + private static NonNamespaceOperation> KEYCLOAK_CLIENTS_CLIENT; + private static NonNamespaceOperation> KEYCLOAK_USERS_CLIENT; + + public KeycloakOpenShiftOperatorProvisioner(@NonNull KeycloakOperatorApplication keycloakOperatorApplication) { + super(keycloakOperatorApplication, KeycloakOperatorProvisioner.operatorId()); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public URL getURL() { + return KeycloakOperatorProvisioner.super.getURL(); + } + + @Override + public String getOperatorCatalogSource() { + return IntersmashConfig.keycloakOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return IntersmashConfig.keycloakOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return IntersmashConfig.keycloakOperatorChannel(); + } + + @Override + public void scale(int replicas, boolean wait) { + KeycloakOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public List getPods() { + return KeycloakOperatorProvisioner.super.getPods(); + } + + @Override + public HasMetadataOperationsImpl keycloaksCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, Keycloak.class, KeycloakList.class); + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } + + @Override + public void subscribe() { + if (Strings.isNullOrEmpty(IntersmashConfig.keycloakImageURL())) { + super.subscribe(); + } else { + // RELATED_IMAGE_RHSSO_OPENJ9 and RELATED_IMAGE_RHSSO_OPENJDK, determine the final value for RELATED_IMAGE_RHSSO + subscribe( + INSTALLPLAN_APPROVAL_MANUAL, + Map.of( + "RELATED_IMAGE_RHSSO", IntersmashConfig.keycloakImageURL(), + "PROFILE", "RHSSO")); + } + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } + + // keycloaks.keycloak.org + + /** + * Get a client capable of working with {@link #KEYCLOAK_RESOURCE} custom resource. + * + * @return client for operations with {@link #KEYCLOAK_RESOURCE} custom resource + */ + public NonNamespaceOperation> keycloaksClient() { + if (KEYCLOAKS_CLIENT == null) { + KEYCLOAKS_CLIENT = buildKeycloaksClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAKS_CLIENT; + } + + @Override + public HasMetadataOperationsImpl keycloakRealmsCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, KeycloakRealm.class, KeycloakRealmList.class); + } + + // keycloakrealms.keycloak.org + + /** + * Get a client capable of working with {@link #KEYCLOAK_REALM_RESOURCE} custom resource. + * + * @return client for operations with {@link #KEYCLOAK_REALM_RESOURCE} custom resource + */ + public NonNamespaceOperation> keycloakRealmsClient() { + if (KEYCLOAK_REALMS_CLIENT == null) { + KEYCLOAK_REALMS_CLIENT = buildKeycloakRealmsClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAK_REALMS_CLIENT; + } + + @Override + public HasMetadataOperationsImpl keycloakBackupsCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, KeycloakBackup.class, KeycloakBackupList.class); + } + + // keycloakbackups.keycloak.org + + /** + * Get a client capable of working with {@link #KEYCLOAK_BACKUP_RESOURCE} custom resource. + * + * @return client for operations with {@link #KEYCLOAK_BACKUP_RESOURCE} custom resource + */ + public NonNamespaceOperation> keycloakBackupsClient() { + if (KEYCLOAK_BACKUPS_CLIENT == null) { + KEYCLOAK_BACKUPS_CLIENT = buildKeycloakBackupsClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAK_BACKUPS_CLIENT; + } + + @Override + public HasMetadataOperationsImpl keycloakClientsCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, KeycloakClient.class, KeycloakClientList.class); + } + + // keycloakclients.keycloak.org + + /** + * Get a client capable of working with {@link #KEYCLOAK_CLIENT_RESOURCE} custom resource. + * + * @return client for operations with {@link #KEYCLOAK_CLIENT_RESOURCE} custom resource + */ + public NonNamespaceOperation> keycloakClientsClient() { + if (KEYCLOAK_CLIENTS_CLIENT == null) { + KEYCLOAK_CLIENTS_CLIENT = buildKeycloakClientsClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAK_CLIENTS_CLIENT; + } + + @Override + public HasMetadataOperationsImpl keycloakUsersCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, KeycloakUser.class, KeycloakUserList.class); + } + + // keycloakusers.keycloak.org + + /** + * Get a client capable of working with {@link #KEYCLOAK_USER_RESOURCE} custom resource. + * + * @return client for operations with {@link #KEYCLOAK_USER_RESOURCE} custom resource + */ + public NonNamespaceOperation> keycloakUsersClient() { + if (KEYCLOAK_USERS_CLIENT == null) { + KEYCLOAK_USERS_CLIENT = buildKeycloakUsersClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAK_USERS_CLIENT; + } + + @Override + public StatefulSet retrieveNamedStatefulSet(String statefulSetName) { + return OpenShiftProvisioner.openShift.getStatefulSet(statefulSetName); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisionerFactory.java index ae7982fa..9bd1cd1e 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisionerFactory.java @@ -16,18 +16,18 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.KeycloakOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KeycloakOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class KeycloakOperatorProvisionerFactory implements ProvisionerFactory { +public class KeycloakOperatorProvisionerFactory implements ProvisionerFactory { @Override - public KeycloakOperatorProvisioner getProvisioner(Application application) { + public KeycloakOpenShiftOperatorProvisioner getProvisioner(Application application) { if (KeycloakOperatorApplication.class.isAssignableFrom(application.getClass())) - return new KeycloakOperatorProvisioner((KeycloakOperatorApplication) application); + return new KeycloakOpenShiftOperatorProvisioner((KeycloakOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..8a0b5d00 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOpenShiftOperatorProvisioner.java @@ -0,0 +1,189 @@ +/** + * 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.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.Secret; +import org.assertj.core.util.Strings; +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.operator.KeycloakRealmImportOperatorApplication; +import org.jboss.intersmash.tools.provision.operator.KeycloakRealmImportOperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; +import org.keycloak.k8s.v2alpha1.Keycloak; +import org.keycloak.k8s.v2alpha1.KeycloakRealmImport; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.fabric8.openshift.api.model.Route; +import lombok.NonNull; + +/** + * Keycloak operator provisioner + */ +public class KeycloakRealmImportOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Wildfly common Operator based provisioner behavior + implements KeycloakRealmImportOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + + public KeycloakRealmImportOpenShiftOperatorProvisioner(@NonNull KeycloakRealmImportOperatorApplication application) { + super(application, KeycloakRealmImportOperatorProvisioner.OPERATOR_ID); + } + + @Override + public String getOperatorCatalogSource() { + + return KeycloakRealmImportOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + + return KeycloakRealmImportOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return KeycloakRealmImportOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + public Service retrieveNamedService(String serviceName) { + return OpenShiftProvisioner.openShift.getService(serviceName); + } + + @Override + public StatefulSet retrieveNamedStatefulSet(String statefulSetName) { + return OpenShiftProvisioner.openShift.getStatefulSet(statefulSetName); + } + + @Override + public List retrieveRoutes() { + return OpenShiftProvisioner.openShift.getRoutes(); + } + + @Override + public List retrieveNamespacePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public void scale(int replicas, boolean wait) { + KeycloakRealmImportOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public URL getURL() { + return KeycloakRealmImportOperatorProvisioner.super.getURL(); + } + + @Override + public HasMetadataOperationsImpl> keycloaksCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, Keycloak.class, KubernetesResourceList.class); + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } + + @Override + public Secret createTlsSecret(final String namespace, final String tlsSecretName, final Path key, final Path certificate) { + try { + return OpenShiftProvisioner.createTlsSecret(OpenShifts.master().getNamespace(), tlsSecretName, key, certificate); + } catch (IOException e) { + throw new IllegalStateException("Couldn't create the required secret: " + tlsSecretName, e); + } + } + + @Override + public void subscribe() { + if (Strings.isNullOrEmpty(IntersmashConfig.keycloakRealmImportImageURL())) { + super.subscribe(); + } else { + subscribe( + INSTALLPLAN_APPROVAL_MANUAL, + Map.of( + // Custom Keycloak image to be used: overrides the Keycloak image at the operator level: all + // Keycloak instances will be spun out of this image + // e.g. OPERATOR_KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:21.1.1 --> operator.keycloak.image + "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakRealmImportImageURL())); + } + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } + + private static NonNamespaceOperation, Resource> KEYCLOAKS_CLIENT; + private static NonNamespaceOperation, Resource> KEYCLOAK_REALM_IMPORTS_CLIENT; + + public NonNamespaceOperation, Resource> keycloakClient() { + if (KEYCLOAKS_CLIENT == null) { + KEYCLOAKS_CLIENT = buildKeycloakClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAKS_CLIENT; + } + + @Override + public HasMetadataOperationsImpl> keycloakRealmImportsCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return null; + } + + public NonNamespaceOperation, Resource> keycloakRealmImportClient() { + + if (KEYCLOAK_REALM_IMPORTS_CLIENT == null) { + KEYCLOAK_REALM_IMPORTS_CLIENT = buildKeycloakRealmImportClient().inNamespace(OpenShiftConfig.namespace()); + } + return KEYCLOAK_REALM_IMPORTS_CLIENT; + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java index 21906bc2..b8ae3f82 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisionerFactory.java @@ -16,19 +16,19 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.KeycloakRealmImportOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KeycloakRealmImportOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j public class KeycloakRealmImportOperatorProvisionerFactory - implements ProvisionerFactory { + implements ProvisionerFactory { @Override - public KeycloakRealmImportOperatorProvisioner getProvisioner(Application application) { + public KeycloakRealmImportOpenShiftOperatorProvisioner getProvisioner(Application application) { if (KeycloakRealmImportOperatorApplication.class.isAssignableFrom(application.getClass())) - return new KeycloakRealmImportOperatorProvisioner((KeycloakRealmImportOperatorApplication) application); + return new KeycloakRealmImportOpenShiftOperatorProvisioner((KeycloakRealmImportOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyImageOpenShiftProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyImageOpenShiftProvisioner.java index 0dee951b..bd24ef46 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyImageOpenShiftProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyImageOpenShiftProvisioner.java @@ -28,11 +28,11 @@ import org.assertj.core.util.Strings; import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.input.BinarySource; +import org.jboss.intersmash.tools.application.input.BuildInput; +import org.jboss.intersmash.tools.application.input.GitSource; import org.jboss.intersmash.tools.application.openshift.WildflyImageOpenShiftApplication; import org.jboss.intersmash.tools.application.openshift.WildflyOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.input.BinarySource; -import org.jboss.intersmash.tools.application.openshift.input.BuildInput; -import org.jboss.intersmash.tools.application.openshift.input.GitSource; import org.slf4j.event.Level; import cz.xtf.builder.builders.ApplicationBuilder; diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOpenShiftOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOpenShiftOperatorProvisioner.java new file mode 100644 index 00000000..9d0ef090 --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOpenShiftOperatorProvisioner.java @@ -0,0 +1,130 @@ +/** + * 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 org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServer; +import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServerList; +import org.jboss.intersmash.tools.provision.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.provision.operator.WildflyOperatorProvisioner; + +import cz.xtf.core.config.OpenShiftConfig; +import cz.xtf.core.openshift.OpenShifts; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionList; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import lombok.NonNull; + +public class WildflyOpenShiftOperatorProvisioner + // default Operator based provisioner behavior, which implements OLM workflow contract and leverage lifecycle as well + extends OperatorProvisioner + // leverage Wildfly common Operator based provisioner behavior + implements WildflyOperatorProvisioner, + // leverage common OpenShift provisioning logic + OpenShiftProvisioner { + private static NonNamespaceOperation> WILDFLY_SERVERS_CLIENT; + + public WildflyOpenShiftOperatorProvisioner(@NonNull WildflyOperatorApplication wildflyOperatorApplication) { + super(wildflyOperatorApplication, WildflyOperatorProvisioner.operatorId()); + } + + @Override + public HasMetadataOperationsImpl wildflyCustomResourcesClient( + CustomResourceDefinitionContext crdc) { + return OpenShifts + .master().customResources(crdc, WildFlyServer.class, WildFlyServerList.class); + } + + /** + * Get a client capable of working with {@link WildflyOperatorProvisioner#wildflyCustomResourceDefinitionName()} ()} custom resource. + * + * @return client for operations with {@link WildflyOperatorProvisioner#wildflyCustomResourceDefinitionName()} ()} custom resource + */ + public NonNamespaceOperation> wildflyServersClient() { + if (WILDFLY_SERVERS_CLIENT == null) { + WILDFLY_SERVERS_CLIENT = buildWildflyClient().inNamespace(OpenShiftConfig.namespace()); + } + return WILDFLY_SERVERS_CLIENT; + } + + @Override + public String execute(String... args) { + return OpenShifts.adminBinary().execute(args); + } + + @Override + public void scale(int replicas, boolean wait) { + WildflyOperatorProvisioner.super.scale(replicas, wait); + } + + @Override + public URL getURL() { + String url = "http://" + wildFlyServer().get().getStatus().getHosts().get(0); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(String.format("WILDFLY operator route \"%s\"is malformed.", url), e); + } + } + + @Override + public List retrievePods() { + return OpenShiftProvisioner.openShift.getPods(); + } + + @Override + public List getPods() { + return WildflyOperatorProvisioner.super.getPods(); + } + + @Override + public NonNamespaceOperation> retrieveCustomResourceDefinitions() { + return OpenShifts.admin().apiextensions().v1().customResourceDefinitions(); + } + + @Override + public String getOperatorCatalogSource() { + return WildflyOperatorProvisioner.super.getOperatorCatalogSource(); + } + + @Override + public String getOperatorIndexImage() { + return WildflyOperatorProvisioner.super.getOperatorIndexImage(); + } + + @Override + public String getOperatorChannel() { + return WildflyOperatorProvisioner.super.getOperatorChannel(); + } + + @Override + protected String getOperatorNamespace() { + return "openshift-marketplace"; + } + + @Override + protected String getTargetNamespace() { + return OpenShiftConfig.namespace(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisionerFactory.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisionerFactory.java index 3710d1a0..1f5125f1 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisionerFactory.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisionerFactory.java @@ -16,18 +16,18 @@ package org.jboss.intersmash.tools.provision.openshift; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.WildflyOperatorApplication; +import org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; import org.jboss.intersmash.tools.provision.ProvisionerFactory; import lombok.extern.slf4j.Slf4j; @Slf4j -public class WildflyOperatorProvisionerFactory implements ProvisionerFactory { +public class WildflyOperatorProvisionerFactory implements ProvisionerFactory { @Override - public WildflyOperatorProvisioner getProvisioner(Application application) { + public WildflyOpenShiftOperatorProvisioner getProvisioner(Application application) { if (WildflyOperatorApplication.class.isAssignableFrom(application.getClass())) - return new WildflyOperatorProvisioner((WildflyOperatorApplication) application); + return new WildflyOpenShiftOperatorProvisioner((WildflyOperatorApplication) application); return null; } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/ActiveMQOperatorProvisioner.java similarity index 56% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/ActiveMQOperatorProvisioner.java index 5bd009d7..909b26cc 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/ActiveMQOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/ActiveMQOperatorProvisioner.java @@ -13,23 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; import java.util.List; import java.util.stream.Collectors; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.ActiveMQOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.operator.activemq.address.ActiveMQArtemisAddressList; import org.jboss.intersmash.tools.provision.openshift.operator.activemq.broker.ActiveMQArtemisList; 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.openshift.OpenShifts; import cz.xtf.core.waiting.SimpleWaiter; +import cz.xtf.core.waiting.failfast.FailFastCheck; import io.amq.broker.v1beta1.ActiveMQArtemis; import io.amq.broker.v1beta1.ActiveMQArtemisAddress; import io.fabric8.kubernetes.api.model.DeletionPropagation; @@ -39,50 +38,52 @@ import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; -import lombok.NonNull; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; /** * ActiveMQ Operator based provisioner */ -public class ActiveMQOperatorProvisioner extends OperatorProvisioner { - private final static String ACTIVE_MQ_ARTEMIS_RESOURCE = "activemqartemises.broker.amq.io"; - private static NonNamespaceOperation> ACTIVE_MQ_ARTEMISES_CLIENT; +public interface ActiveMQOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { + + // this is the packagemanifest for the operator; + // you can get it with command: + // oc get packagemanifest -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.activeMQOperatorPackageManifest(); + } - private final static String ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE = "activemqartemisaddresses.broker.amq.io"; - private static NonNamespaceOperation> ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT; + // this is the name of the CustomResourceDefinition(s) + // you can get it with command: + // oc get crd > -o template --template='{{ .metadata.name }}' + default String activeMQCustomResourceDefinitionName() { + return "activemqartemises.broker.amq.io"; + } - // private final static String ACTIVE_MQ_ARTEMIS_SCALEDOWN_RESOURCE = "activemqartemisscaledowns.broker.amq.io"; // TODO add on demand + HasMetadataOperationsImpl activeMQArtemisAddressesCustomResourcesClient( + CustomResourceDefinitionContext crdc); - private static final String OPERATOR_ID = IntersmashConfig.activeMQOperatorPackageManifest(); + HasMetadataOperationsImpl activeMQArtemisCustomResourcesClient( + CustomResourceDefinitionContext crdc); - public ActiveMQOperatorProvisioner(@NonNull ActiveMQOperatorApplication activeMqOperatorApplication) { - super(activeMqOperatorApplication, OPERATOR_ID); - } + NonNamespaceOperation> activeMQArtemisAddressesClient(); - public static String getOperatorId() { - return OPERATOR_ID; - } + NonNamespaceOperation> activeMQArtemisesClient(); /** - * Get a client capable of working with {@link #ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE} custom resource. + * Get a client capable of working with {@link #activeMQCustomResourceDefinitionName()} custom resource. * - * @return client for operations with {@link #ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE} custom resource + * @return client for operations with {@link #activeMQCustomResourceDefinitionName()} custom resource */ - public NonNamespaceOperation> activeMQArtemisAddressesClient() { - if (ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - ACTIVE_MQ_ARTEMIS_ADDRESS_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> addressesClient = OpenShifts - .master().customResources(crdc, ActiveMQArtemisAddress.class, ActiveMQArtemisAddressList.class); - ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT = addressesClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildActiveMQArtemisAddressesClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(activeMQCustomResourceDefinitionName()).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(activeMQCustomResourceDefinitionName())) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + activeMQCustomResourceDefinitionName(), operatorId())); } - return ACTIVE_MQ_ARTEMIS_ADDRESSES_CLIENT; + return activeMQArtemisAddressesCustomResourcesClient(crdc); } /** @@ -92,7 +93,7 @@ public NonNamespaceOperation activeMQArtemisAddress(String name) { + default Resource activeMQArtemisAddress(String name) { return activeMQArtemisAddressesClient().withName(name); } @@ -103,7 +104,7 @@ public Resource activeMQArtemisAddress(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link ActiveMQArtemisAddress} resource definitions */ - public List> activeMQArtemisAddresses() { + default List> activeMQArtemisAddresses() { ActiveMQOperatorApplication activeMqOperatorApplication = getApplication(); return activeMqOperatorApplication.getActiveMQArtemisAddresses().stream() .map(activeMQArtemisAddress -> activeMQArtemisAddress.getMetadata().getName()) @@ -112,25 +113,20 @@ public List> activeMQArtemisAddresses() { } /** - * Get a client capable of working with {@link #ACTIVE_MQ_ARTEMIS_RESOURCE} custom resource. + * Get a client capable of working with {@link #activeMQCustomResourceDefinitionName()} custom resource. * - * @return client for operations with {@link #ACTIVE_MQ_ARTEMIS_RESOURCE} custom resource + * @return client for operations with {@link #activeMQCustomResourceDefinitionName()} custom resource */ - public NonNamespaceOperation> activeMQArtemisesClient() { - if (ACTIVE_MQ_ARTEMISES_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(ACTIVE_MQ_ARTEMIS_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(ACTIVE_MQ_ARTEMIS_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - ACTIVE_MQ_ARTEMIS_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> amqClient = OpenShifts - .master().customResources(crdc, ActiveMQArtemis.class, ActiveMQArtemisList.class); - ACTIVE_MQ_ARTEMISES_CLIENT = amqClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildActiveMQArtemisesClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(activeMQCustomResourceDefinitionName()).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(activeMQCustomResourceDefinitionName())) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + activeMQCustomResourceDefinitionName(), operatorId())); } - return ACTIVE_MQ_ARTEMISES_CLIENT; + return activeMQArtemisCustomResourcesClient(crdc); + } /** @@ -138,14 +134,13 @@ public NonNamespaceOperation activeMQArtemis() { + default Resource activeMQArtemis() { return activeMQArtemisesClient().withName(getApplication().getActiveMQArtemis().getMetadata().getName()); } @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { + FailFastCheck ffCheck = () -> false; subscribe(); int replicas = getApplication().getActiveMQArtemis().getSpec().getDeploymentPlan().getSize(); @@ -179,7 +174,8 @@ public void deploy() { } @Override - public void undeploy() { + default void undeploy() { + FailFastCheck ffCheck = () -> false; // delete the resources activeMQArtemisAddresses().forEach(address -> address.withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); @@ -196,8 +192,8 @@ public void undeploy() { unsubscribe(); } - @Override - public void scale(int replicas, boolean wait) { + default void scale(int replicas, boolean wait) { + FailFastCheck ffCheck = () -> false; ActiveMQArtemis tmpBroker = activeMQArtemis().get(); tmpBroker.getSpec().getDeploymentPlan().setSize(replicas); activeMQArtemis().replace(tmpBroker); @@ -219,33 +215,17 @@ public void scale(int replicas, boolean wait) { } } - /** - * Get the provisioned application service related Pods - *

- * Currently blocked by the fact that Pod Status pod names do not reflect the reality - *

- * Once these issues are resolved, we can use the ready pod names returned by - * {@code ActiveMQArtemisStatus.getPodStatus()} to create the List with pods maintained by the provisioner. - * - * @return A list of related {@link Pod} instances - */ - @Override - public List getPods() { - throw new UnsupportedOperationException("To be implemented!"); - } + List getPods(); - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.activeMQOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.activeMQOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.activeMQOperatorChannel(); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/HyperfoilOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/HyperfoilOperatorProvisioner.java new file mode 100644 index 00000000..6a5e17fd --- /dev/null +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/HyperfoilOperatorProvisioner.java @@ -0,0 +1,194 @@ +package org.jboss.intersmash.tools.provision.operator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import java.util.stream.Collectors; + +import org.jboss.intersmash.tools.IntersmashConfig; +import org.jboss.intersmash.tools.application.operator.HyperfoilOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.WaitersUtil; +import org.slf4j.event.Level; + +import com.google.common.base.Strings; + +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.Pod; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.hyperfoil.v1alpha2.Hyperfoil; +import io.hyperfoil.v1alpha2.HyperfoilList; + +public interface HyperfoilOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner, + OlmOperatorProvisionerClientBinary { + + // this is the packagemanifest for the hyperfoil operator; + // you can get it with command: + // oc get packagemanifest hyperfoil-bundle -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.hyperfoilOperatorPackageManifest(); + } + + // this is the name of the Hyperfoil CustomResourceDefinition + // you can get it with command: + // oc get crd hyperfoils.hyperfoil.io -o template --template='{{ .metadata.name }}' + default String hyperfoilCustomResourceDefinitionName() { + return "hyperfoils.hyperfoil.io"; + } + + HasMetadataOperationsImpl hyperfoilCustomResourcesClient(CustomResourceDefinitionContext crdc); + + /** + * Get a client capable of working with {@link #hyperfoilCustomResourceDefinitionName} custom resource. + * + * @return client for operations with {@link #hyperfoilCustomResourceDefinitionName} custom resource + */ + default MixedOperation> buildHyperfoilClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(hyperfoilCustomResourceDefinitionName()).get(); + if (crd == null) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + hyperfoilCustomResourceDefinitionName(), operatorId())); + } + return hyperfoilCustomResourcesClient(CustomResourceDefinitionContext.fromCrd(crd)); + } + + NonNamespaceOperation> hyperfoilClient(); + + /** + * Get a reference to Hyperfoil 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 Hyperfoil} resource definition + */ + default Resource hyperfoil() { + return hyperfoilClient().withName(getApplication().getName()); + } + + default void deploy() { + FailFastCheck ffCheck = () -> false; + if (!isSubscribed()) { + subscribe(); + } + hyperfoilClient().createOrReplace(getApplication().getHyperfoil()); + new SimpleWaiter(() -> hyperfoil().get().getStatus() != null) + .failFast(ffCheck) + .reason("Wait for status field to be initialized.") + .level(Level.DEBUG) + .waitFor(); + new SimpleWaiter(() -> getPods().size() == 1) + .failFast(ffCheck) + .reason("Wait for expected number of replicas to be active.") + .level(Level.DEBUG) + .waitFor(); + WaitersUtil.routeIsUp(getURL().toExternalForm()) + .level(Level.DEBUG) + .waitFor(); + } + + String getCurrentCSV(); + + List retrieveNamespacePods(); + + Pod retrieveNamedPod(final String podName); + + Ingress retrieveNamedIngress(final String ingressName); + + default List getPods() { + List pods = new ArrayList<>(); + Pod hyperfoilControllerPod = retrieveNamedPod(String.format("%s-controller", getApplication().getName())); + if (isContainerReady(hyperfoilControllerPod, "controller")) { + pods.add(hyperfoilControllerPod); + } + return pods; + } + + /** + * This method checks if the Operator's POD is actually running; + * It's been tailored on the community-operators Cluster Service version format which is missing label + * spec.install.spec.deployments.spec.template.metadata.labels."app.kubernetes.io/name" which is used + * in @see OperatorProvisioner#waitForOperatorPod() (see + * https://github.com/operator-framework/community-operators/tree/master/community-operators/hyperfoil-bundle) + */ + default void waitForOperatorPod() { + String[] operatorSpecs = this.execute("get", "csvs", getCurrentCSV(), "-o", "template", "--template", + "{{range .spec.install.spec.deployments}}{{printf \"%d|%s\\n\" .spec.replicas .name}}{{end}}") + .split(System.lineSeparator()); + for (String spec : operatorSpecs) { + String[] operatorSpec = spec.split("\\|"); + if (operatorSpec.length != 2) { + throw new RuntimeException("Failed to get operator deployment spec from csvs!"); + } + new SimpleWaiter(() -> retrievePods().stream().filter( + pod -> (pod.getMetadata() + .getName() + .startsWith(operatorSpec[1]) + && pod.getStatus().getPhase().equalsIgnoreCase("Running"))) + .count() == Integer.valueOf(operatorSpec[0])) + .failFast(() -> false) + .reason("Wait for expected number of replicas to be active.") + .level(Level.DEBUG) + .waitFor(); + } + } + + @Override + default void undeploy() { + undeploy(true); + } + + default void undeploy(boolean unsubscribe) { + hyperfoil().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); + BooleanSupplier bs = () -> retrieveNamespacePods().stream() + .filter(p -> !Strings.isNullOrEmpty(p.getMetadata().getLabels().get("app")) + && p.getMetadata().getLabels().get("app").equals(getApplication().getName())) + .collect(Collectors.toList()).size() == 0; + String reason = "Waiting for exactly 0 pods with label \"app\"=" + getApplication().getName() + " to be ready."; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, reason) + .level(Level.DEBUG) + .waitFor(); + if (unsubscribe) { + unsubscribe(); + } + } + + /** + * Tells if a specific container inside the pod is ready + * + * @param pod + * @param containerName: name of the container + * @return + */ + default boolean isContainerReady(Pod pod, String containerName) { + if (Objects.nonNull(pod)) { + return pod.getStatus().getContainerStatuses().stream() + .filter(containerStatus -> containerStatus.getName().equalsIgnoreCase(containerName) + && containerStatus.getReady()) + .count() > 0; + } + return false; + } + + default String getOperatorCatalogSource() { + return IntersmashConfig.hyperfoilOperatorCatalogSource(); + } + + default String getOperatorIndexImage() { + return IntersmashConfig.hyperfoilOperatorIndexImage(); + } + + default String getOperatorChannel() { + return IntersmashConfig.hyperfoilOperatorChannel(); + } +} diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/InfinispanOperatorProvisioner.java similarity index 56% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/InfinispanOperatorProvisioner.java index 5f25d3cf..4afc4c36 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/InfinispanOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/InfinispanOperatorProvisioner.java @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; import java.util.stream.Collectors; import org.assertj.core.util.Lists; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.InfinispanOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.InfinispanOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.Cache; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.cache.CacheList; import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.Infinispan; @@ -33,11 +35,8 @@ import org.jboss.intersmash.tools.provision.openshift.operator.infinispan.infinispan.spec.InfinispanConditionBuilder; 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.openshift.OpenShifts; 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.Pod; import io.fabric8.kubernetes.api.model.Service; @@ -47,31 +46,35 @@ import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; import io.fabric8.openshift.api.model.Route; -import lombok.NonNull; -public class InfinispanOperatorProvisioner extends OperatorProvisioner { - private static final String INFINISPAN_RESOURCE = "infinispans.infinispan.org"; - private static NonNamespaceOperation> INFINISPAN_CLIENT; +public interface InfinispanOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { - private static final String INFINISPAN_CACHE_RESOURCE = "caches.infinispan.org"; - private static NonNamespaceOperation> INFINISPAN_CACHES_CLIENT; - - // oc get packagemanifest datagrid -n openshift-marketplace - private static final String OPERATOR_ID = IntersmashConfig.infinispanOperatorPackageManifest(); + // this is the packagemanifest for the operator; + // you can get it with command: + // oc get packagemanifest -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.infinispanOperatorPackageManifest(); + } - public InfinispanOperatorProvisioner(@NonNull InfinispanOperatorApplication infinispanOperatorApplication) { - super(infinispanOperatorApplication, OPERATOR_ID); + // this is the name of the CustomResourceDefinition(s) + // you can get it with command: + // oc get crd -o template --template='{{ .metadata.name }}' + default String infinispanCustomResourceDefinitionName() { + return "infinispans.infinispan.org"; } - public static String getOperatorId() { - return OPERATOR_ID; + // this is the name of the CustomResourceDefinition(s) + // you can get it with command: + // oc get crd -o template --template='{{ .metadata.name }}' + default String cacheCustomResourceDefinitionName() { + return "caches.infinispan.org"; } - @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { + FailFastCheck ffCheck = () -> false; subscribe(); // create custom resources @@ -83,30 +86,39 @@ public void deploy() { // This might be a litle bit naive, but we need more use cases to see how will this behave and what other // use-cases we have to cover wait for infinispan pods - look for "clusterName" in infinispan pod if (replicas > 0) { - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck).areExactlyNPodsReady( - replicas, "clusterName", getApplication().getInfinispan().getMetadata().getName()).waitFor(); + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("clusterName") != null && p.getMetadata().getLabels() + .get("clusterName").equals(getApplication().getInfinispan().getMetadata().getName())) + .collect(Collectors.toList()).size() == replicas; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for " + replicas + " pods with label \"clusterName\"=" + + getApplication().getInfinispan().getMetadata().getName()) + .waitFor(); } // wait for all resources to be ready waitForResourceReadiness(); } - @Override - public void undeploy() { + default void undeploy() { + FailFastCheck ffCheck = () -> false; // delete custom resources caches().forEach(keycloakUser -> keycloakUser.withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); infinispan().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); - // wait for 0 pods - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) - .areExactlyNPodsReady(0, "clusterName", getApplication().getInfinispan().getMetadata().getName()) - .level(Level.DEBUG) + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("clusterName") != null && p.getMetadata().getLabels() + .get("clusterName").equals(getApplication().getInfinispan().getMetadata().getName())) + .collect(Collectors.toList()).size() == 0; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for 0 pods with label \"clusterName\"=" + getApplication().getInfinispan().getMetadata().getName()) .waitFor(); unsubscribe(); } - @Override - public void scale(int replicas, boolean wait) { - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(getApplication().getName()); + StatefulSet retrieveNamedStatefulSet(final String statefulSetName); + + default void scale(int replicas, boolean wait) { + StatefulSet statefulSet = retrieveNamedStatefulSet(getApplication().getName()); if (Objects.isNull(statefulSet)) { throw new IllegalStateException(String.format( "Impossible to scale non existent StatefulSet with name=\"%s\" to replicas=%d", @@ -118,9 +130,12 @@ public void scale(int replicas, boolean wait) { tmpInfinispan.getSpec().setReplicas(replicas); infinispan().replace(tmpInfinispan); if (wait) { - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) - .areExactlyNPodsReady(replicas, "controller-revision-hash", controllerRevisionHash) - .level(Level.DEBUG) + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("controller-revision-hash") != null + && p.getMetadata().getLabels().get("controller-revision-hash").equals(controllerRevisionHash)) + .collect(Collectors.toList()).size() == replicas; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for pods with label \"controller-revision-hash\"=" + controllerRevisionHash + " to be scaled") .waitFor(); } if (replicas > 0) { @@ -134,27 +149,26 @@ public void scale(int replicas, boolean wait) { } } - @Override - public List getPods() { - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(getApplication().getName()); + default List getPods() { + StatefulSet statefulSet = retrieveNamedStatefulSet(getApplication().getName()); return Objects.nonNull(statefulSet) - ? OpenShiftProvisioner.openShift.getLabeledPods("controller-revision-hash", - statefulSet.getStatus().getUpdateRevision()) + ? retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("controller-revision-hash") != null + && p.getMetadata().getLabels().get("controller-revision-hash") + .equals(statefulSet.getStatus().getUpdateRevision())) + .collect(Collectors.toList()) : Lists.emptyList(); } - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.infinispanOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.infinispanOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.infinispanOperatorChannel(); } @@ -164,9 +178,8 @@ protected String getOperatorChannel() { * route URL (i.e. for external access) when {@code .spec.expose.type} is set to {@code Route} * @return The URL for the provisioned Infinispan service */ - @Override - public URL getURL() { - final Service defaultInternalService = OpenShiftProvisioner.openShift.getService(getApplication().getName()); + default URL getURL() { + final Service defaultInternalService = retrieveNamedService(getApplication().getName()); String internalUrl = "http://" + defaultInternalService.getSpec().getClusterIP() + ":11222"; String externalUrl = null; if (getApplication().getInfinispan().getSpec().getExpose() != null) { @@ -176,7 +189,7 @@ public URL getURL() { // TODO - check // see see https://github.com/infinispan/infinispan-operator/blob/2.0.x/pkg/apis/infinispan/v1/infinispan_types.go#L107 externalUrl = "http://" - + OpenShiftProvisioner.openShift.getService(getApplication().getName() + "-external").getSpec() + + retrieveNamedService(getApplication().getName() + "-external").getSpec() .getClusterIP() + getApplication().getInfinispan().getSpec().getExpose().getNodePort(); break; @@ -184,13 +197,13 @@ public URL getURL() { // TODO - check // see https://github.com/infinispan/infinispan-operator/blob/2.0.x/pkg/apis/infinispan/v1/infinispan_types.go#L111 externalUrl = "http://" - + OpenShiftProvisioner.openShift.getService(getApplication().getName() + "-external").getSpec() + + retrieveNamedService(getApplication().getName() + "-external").getSpec() .getExternalIPs().get(0) + getApplication().getInfinispan().getSpec().getExpose().getNodePort(); break; case Route: // https://github.com/infinispan/infinispan-operator/blob/2.0.x/pkg/apis/infinispan/v1/infinispan_types.go#L116 - Route route = OpenShiftProvisioner.openShift.getRoute(getApplication().getName() + "-external"); + Route route = retrieveNamedRoute(getApplication().getName() + "-external"); externalUrl = "https://" + route.getSpec().getHost(); break; default: @@ -204,29 +217,32 @@ public URL getURL() { } } - // infinispans.infinispan.org + Route retrieveNamedRoute(final String routeName); + + Service retrieveNamedService(final String serviceName); + + HasMetadataOperationsImpl infinispanCustomResourcesClient(CustomResourceDefinitionContext crdc); + + HasMetadataOperationsImpl cacheCustomResourcesClient(CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> infinispansClient(); + + NonNamespaceOperation> cachesClient(); /** - * Get a client capable of working with {@link #INFINISPAN_RESOURCE} custom resource. + * Get a client capable of working with {@link InfinispanOperatorProvisioner#infinispanCustomResourceDefinitionName()} custom resource. * - * @return client for operations with {@link #INFINISPAN_RESOURCE} custom resource + * @return client for operations with {@link InfinispanOperatorProvisioner#infinispanCustomResourceDefinitionName()} custom resource */ - public NonNamespaceOperation> infinispansClient() { - if (INFINISPAN_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(INFINISPAN_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(INFINISPAN_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - INFINISPAN_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> infinispansClient = OpenShifts - .master() - .customResources(crdc, Infinispan.class, InfinispanList.class); - INFINISPAN_CLIENT = infinispansClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildInfinispansClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(infinispanCustomResourceDefinitionName()).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(infinispanCustomResourceDefinitionName())) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + infinispanCustomResourceDefinitionName(), operatorId())); } - return INFINISPAN_CLIENT; + return infinispanCustomResourcesClient(crdc); } /** @@ -234,33 +250,26 @@ public NonNamespaceOperation> i * exist on tested cluster. * @return A concrete {@link Resource} instance representing the {@link Infinispan} resource definition */ - public Resource infinispan() { + default Resource infinispan() { return infinispansClient().withName(getApplication().getInfinispan().getMetadata().getName()); } // caches.infinispan.org /** - * Get a client capable of working with {@link #INFINISPAN_CACHE_RESOURCE} custom resource. + * Get a client capable of working with {@link InfinispanOperatorProvisioner#cacheCustomResourceDefinitionName()} custom resource. * - * @return client for operations with {@link #INFINISPAN_CACHE_RESOURCE} custom resource + * @return client for operations with {@link InfinispanOperatorProvisioner#cacheCustomResourceDefinitionName()} custom resource */ - public NonNamespaceOperation> cachesClient() { - if (INFINISPAN_CACHES_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(INFINISPAN_CACHE_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(INFINISPAN_CACHE_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - INFINISPAN_CACHE_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> cachesClient = OpenShifts - .master() - .customResources(crdc, Cache.class, CacheList.class); - INFINISPAN_CACHES_CLIENT = cachesClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildCachesClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(cacheCustomResourceDefinitionName()).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(cacheCustomResourceDefinitionName())) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + cacheCustomResourceDefinitionName(), operatorId())); } - return INFINISPAN_CACHES_CLIENT; + return cacheCustomResourcesClient(crdc); } /** @@ -270,7 +279,7 @@ public NonNamespaceOperation> cachesClient() { * @param name name of the cache custom resource * @return A concrete {@link Resource} instance representing the {@link Cache} resource definition */ - public Resource cache(String name) { + default Resource cache(String name) { return cachesClient().withName(name); } @@ -281,7 +290,7 @@ public Resource cache(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link Cache} resource definitions */ - public List> caches() { + default List> caches() { InfinispanOperatorApplication infinispanOperatorApplication = getApplication(); return infinispanOperatorApplication.getCaches().stream() .map(cache -> cache.getMetadata().getName()) diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KafkaOperatorProvisioner.java similarity index 76% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KafkaOperatorProvisioner.java index 7df06e28..2d557975 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KafkaOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KafkaOperatorProvisioner.java @@ -13,53 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import java.util.stream.Collectors; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; 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.Pod; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; -import io.strimzi.api.kafka.Crds; import io.strimzi.api.kafka.KafkaList; import io.strimzi.api.kafka.KafkaTopicList; import io.strimzi.api.kafka.KafkaUserList; import io.strimzi.api.kafka.model.Kafka; import io.strimzi.api.kafka.model.KafkaTopic; import io.strimzi.api.kafka.model.KafkaUser; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; /** * Deploys an application that implements {@link KafkaOperatorApplication} interface and which is extended by this * class. */ -@Slf4j -public class KafkaOperatorProvisioner extends OperatorProvisioner { - private static final String OPERATOR_ID = IntersmashConfig.kafkaOperatorPackageManifest(); +public interface KafkaOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { - public KafkaOperatorProvisioner(@NonNull KafkaOperatorApplication kafkaOperatorApplication) { - super(kafkaOperatorApplication, OPERATOR_ID); + // this is the packagemanifest for the hyperfoil operator; + // you can get it with command: + // oc get packagemanifest hyperfoil-bundle -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.kafkaOperatorPackageManifest(); } - /** - * Get a client capable of working with {@link Kafka} custom resource on our OpenShift instance. - * - * @return client for operations with {@link Kafka} custom resource on our OpenShift instance - */ - public NonNamespaceOperation> kafkasClient() { - return Crds.kafkaOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); + // this is the name of the Wildfly CustomResourceDefinition + // you can get it with command: + // oc get crd wildflyservers.wildfly.org -o template --template='{{ .metadata.name }}' + default String wildflyCustomResourceDefinitionName() { + return "wildflyservers.wildfly.org"; } /** @@ -69,32 +67,32 @@ public NonNamespaceOperation> kafkasClient() { * * @return returns Kafka cluster resource on OpenShift instance that is tied with our relevant Application only */ - public Resource kafka() { + default Resource kafka() { return kafkasClient().withName(getApplication().getKafka().getMetadata().getName()); } + /** + * Get a client capable of working with {@link Kafka} custom resource on our OpenShift instance. + * + * @return client for operations with {@link Kafka} custom resource on our OpenShift instance + */ + NonNamespaceOperation> kafkasClient(); + /** * Get a client capable of working with {@link KafkaUser} custom resource on our OpenShift instance. * * @return client for operations with {@link KafkaUser} custom resource on our OpenShift instance */ - public NonNamespaceOperation> kafkasUserClient() { - return Crds.kafkaUserOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); - } + NonNamespaceOperation> kafkasUserClient(); /** * Get a client capable of working with {@link KafkaTopic} custom resource on our OpenShift instance. * * @return client for operations with {@link KafkaTopic} custom resource on our OpenShift instance */ - public NonNamespaceOperation> kafkasTopicClient() { - return Crds.topicOperation(OpenShiftProvisioner.openShift).inNamespace(OpenShiftConfig.namespace()); - } + NonNamespaceOperation> kafkasTopicClient(); - @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { subscribe(); @@ -125,7 +123,8 @@ public void deploy() { } } - private void waitForKafkaClusterCreation() { + default void waitForKafkaClusterCreation() { + FailFastCheck ffCheck = () -> false; int expectedReplicas = getApplication().getKafka().getSpec().getKafka().getReplicas(); new SimpleWaiter(() -> kafka().get() != null) .failFast(ffCheck) @@ -173,20 +172,22 @@ private void waitForKafkaClusterCreation() { .waitFor(); } + void logMessage(final String message, Level l); + private void listKafkaClusterCreationConditions(boolean success, String message) { String completeMessage = message + " Here is the list of instance conditions found there:"; if (success) { - log.info(completeMessage); + logMessage(completeMessage, Level.INFO); } else { - log.error(completeMessage); + logMessage(completeMessage, Level.ERROR); } kafka().get().getStatus().getConditions().stream().forEach(c -> { String conditionMessage = " |- " + c.getType() + ":" + c.getStatus() + ":" + c.getMessage(); if (success) { - log.info(conditionMessage); + logMessage(conditionMessage, Level.INFO); } else { - log.error(conditionMessage); + logMessage(conditionMessage, Level.ERROR); } }); } @@ -247,14 +248,15 @@ private void waitForKafkaUserCreation(KafkaUser user) { "Waiting for user '" + userName + "' condition to be 'Ready'").level(Level.DEBUG).waitFor(); } - @Override - public void undeploy() { + default void undeploy() { // delete the resources if (getApplication().getUsers() != null) { if (!kafkasUserClient().delete()) { - log.warn("Wasn't able to remove all relevant 'Kafka User' resources created for '" + getApplication().getName() - + "' instance!"); + logMessage( + "Wasn't able to remove all relevant 'Kafka User' resources created for '" + getApplication().getName() + + "' instance!", + Level.WARN); } new SimpleWaiter(() -> kafkasUserClient().list().getItems().isEmpty()).level(Level.DEBUG).waitFor(); @@ -262,8 +264,10 @@ public void undeploy() { if (getApplication().getTopics() != null) { if (!kafkasTopicClient().delete()) { - log.warn("Wasn't able to remove all relevant 'Kafka Topic' resources created for '" + getApplication().getName() - + "' instance!"); + logMessage( + "Wasn't able to remove all relevant 'Kafka Topic' resources created for '" + getApplication().getName() + + "' instance!", + Level.WARN); } new SimpleWaiter(() -> kafkasTopicClient().list().getItems().isEmpty()).level(Level.DEBUG).waitFor(); @@ -271,40 +275,33 @@ public void undeploy() { if (getApplication().getKafka() != null) { if (!kafka().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()) { - log.warn("Wasn't able to remove all relevant 'Kafka' resources created for '" + getApplication().getName() - + "' instance!"); + logMessage("Wasn't able to remove all relevant 'Kafka' resources created for '" + getApplication().getName() + + "' instance!", Level.WARN); } new SimpleWaiter(() -> getKafkaPods().size() == 0).level(Level.DEBUG).waitFor(); } - unsubscribe(); - - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) - .areExactlyNPodsReady(0, "name", getApplication().getName() + "-cluster-operator") - .level(Level.DEBUG).waitFor(); - } - - public static String getOperatorId() { - return OPERATOR_ID; + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("name") != null + && p.getMetadata().getLabels().get("name").equals(getApplication().getName() + "-cluster-operator")) + .collect(Collectors.toList()).size() == 0; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for 0 pods with label \"name\"=" + getApplication().getName() + "-cluster-operator") + .waitFor(); } - public KafkaUserList getUsers() { + default KafkaUserList getUsers() { return kafkasUserClient().list(); } - public KafkaTopicList getTopics() { + default KafkaTopicList getTopics() { return kafkasTopicClient().list(); } - @Override - public List getPods() { - return OpenShiftProvisioner.openShift.getLabeledPods("strimzi.io/cluster", getApplication().getName()); - } + List getPods(); - public List getClusterOperatorPods() { - return OpenShiftProvisioner.openShift.getLabeledPods("strimzi.io/kind", "cluster-operator"); - } + List getClusterOperatorPods(); /** * Get list of all Kafka pods on OpenShift instance with regards this Kafka cluster. @@ -313,8 +310,8 @@ public List getClusterOperatorPods() { * But we list only Kafka related pods here. * @return list of Kafka pods */ - public List getKafkaPods() { - List kafkaPods = OpenShiftProvisioner.openShift.getLabeledPods("app.kubernetes.io/name", "kafka"); + default List getKafkaPods() { + List kafkaPods = retrieveKafkaPods(); // Let's filter out just those who match particular naming for (Pod kafkaPod : kafkaPods) { if (!kafkaPod.getMetadata().getName().contains(getApplication().getName() + "-kafka-")) { @@ -325,6 +322,10 @@ public List getKafkaPods() { return kafkaPods; } + List retrieveKafkaPods(); + + List retrieveKafkaZookeperPods(); + /** * Get list of all Zookeeper pods on OpenShift instance with regards this Kafka cluster. *

@@ -332,8 +333,8 @@ public List getKafkaPods() { * But we list only Zookeeper related pods here. * @return list of Kafka pods */ - public List getZookeeperPods() { - List kafkaPods = OpenShiftProvisioner.openShift.getLabeledPods("app.kubernetes.io/name", "zookeeper"); + default List getZookeeperPods() { + List kafkaPods = retrieveKafkaZookeperPods(); // Let's filter out just those who match particular naming for (Pod kafkaPod : kafkaPods) { if (!kafkaPod.getMetadata().getName().contains(getApplication().getName() + "-zookeeper-")) { @@ -344,8 +345,7 @@ public List getZookeeperPods() { return kafkaPods; } - @Override - public void scale(int replicas, boolean wait) { + default void scale(int replicas, boolean wait) { Kafka kafka = getApplication().getKafka(); // Note we change replicas of Kafka instances only (no Zookeeper replicas number change). kafka.getSpec().getKafka().setReplicas(replicas); @@ -357,18 +357,15 @@ public void scale(int replicas, boolean wait) { } } - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.kafkaOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.kafkaOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.kafkaOperatorChannel(); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakOperatorProvisioner.java similarity index 66% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakOperatorProvisioner.java index f22b5471..585328a3 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakOperatorProvisioner.java @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; 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.KeycloakOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.KeycloakOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; +import org.jboss.intersmash.tools.provision.openshift.WaitersUtil; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackup; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.backup.KeycloakBackupList; import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.client.KeycloakClient; @@ -39,12 +40,10 @@ import org.jboss.intersmash.tools.provision.openshift.operator.keycloak.user.KeycloakUserList; 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.openshift.OpenShifts; import cz.xtf.core.openshift.helpers.ResourceParsers; 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.Pod; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; @@ -53,72 +52,46 @@ import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; -import lombok.NonNull; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; /** * Keycloak operator provisioner */ -public class KeycloakOperatorProvisioner extends OperatorProvisioner { - private static final String KEYCLOAK_RESOURCE = "keycloaks.keycloak.org"; - private static NonNamespaceOperation> KEYCLOAKS_CLIENT; +public interface KeycloakOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { + String KEYCLOAK_RESOURCE = "keycloaks.keycloak.org"; - private static final String KEYCLOAK_REALM_RESOURCE = "keycloakrealms.keycloak.org"; - private static NonNamespaceOperation> KEYCLOAK_REALMS_CLIENT; + String KEYCLOAK_REALM_RESOURCE = "keycloakrealms.keycloak.org"; - private static final String KEYCLOAK_BACKUP_RESOURCE = "keycloakbackups.keycloak.org"; - private static NonNamespaceOperation> KEYCLOAK_BACKUPS_CLIENT; + String KEYCLOAK_BACKUP_RESOURCE = "keycloakbackups.keycloak.org"; - private static final String KEYCLOAK_CLIENT_RESOURCE = "keycloakclients.keycloak.org"; - private static NonNamespaceOperation> KEYCLOAK_CLIENTS_CLIENT; + String KEYCLOAK_CLIENT_RESOURCE = "keycloakclients.keycloak.org"; - private static final String KEYCLOAK_USER_RESOURCE = "keycloakusers.keycloak.org"; - private static NonNamespaceOperation> KEYCLOAK_USERS_CLIENT; + String KEYCLOAK_USER_RESOURCE = "keycloakusers.keycloak.org"; - // oc get packagemanifest rhsso-operator -n openshift-marketplace - private static final String OPERATOR_ID = IntersmashConfig.keycloakOperatorPackageManifest(); - private static final String STATEFUL_SET_NAME = "keycloak"; + String STATEFUL_SET_NAME = "keycloak"; - public KeycloakOperatorProvisioner(@NonNull KeycloakOperatorApplication keycloakOperatorApplication) { - super(keycloakOperatorApplication, OPERATOR_ID); + // this is the packagemanifest for the hyperfoil operator; + // you can get it with command: + // oc get packagemanifest hyperfoil-bundle -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.keycloakOperatorPackageManifest(); } - public static String getOperatorId() { - return OPERATOR_ID; - } - - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.keycloakOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.keycloakOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.keycloakOperatorChannel(); } - @Override - public void subscribe() { - if (Strings.isNullOrEmpty(IntersmashConfig.keycloakImageURL())) { - super.subscribe(); - } else { - // RELATED_IMAGE_RHSSO_OPENJ9 and RELATED_IMAGE_RHSSO_OPENJDK, determine the final value for RELATED_IMAGE_RHSSO - subscribe( - INSTALLPLAN_APPROVAL_MANUAL, - Map.of( - "RELATED_IMAGE_RHSSO", IntersmashConfig.keycloakImageURL(), - "PROFILE", "RHSSO")); - } - } - - @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { + FailFastCheck ffCheck = () -> false; // 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(); @@ -154,7 +127,8 @@ public void deploy() { * * @param keycloak Concrete {@link Keycloak} instance which the method should be wait for */ - public void waitFor(Keycloak keycloak) { + default void waitFor(Keycloak keycloak) { + FailFastCheck ffCheck = () -> false; int replicas = keycloak.getSpec().getInstances(); if (replicas > 0) { // 1. check externalDatabase @@ -199,8 +173,7 @@ private void waitForKeycloakResourceReadiness() { .reason("Wait for keycloakbackups to be ready.").level(Level.DEBUG).waitFor(); } - @Override - public void undeploy() { + default void undeploy() { // delete custom resources keycloakBackups() .forEach(keycloakBackup -> keycloakBackup.withPropagationPolicy(DeletionPropagation.FOREGROUND).delete()); @@ -228,8 +201,8 @@ public void undeploy() { unsubscribe(); } - @Override - public void scale(int replicas, boolean wait) { + default void scale(int replicas, boolean wait) { + FailFastCheck ffCheck = () -> false; String controllerRevisionHash = getStatefulSet().getStatus().getUpdateRevision(); Keycloak tmpKeycloak = keycloak().get(); int originalReplicas = tmpKeycloak.getSpec().getInstances(); @@ -251,17 +224,15 @@ public void scale(int replicas, boolean wait) { } } - @Override - public List getPods() { - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + default List getPods() { + StatefulSet statefulSet = retrieveNamedStatefulSet(STATEFUL_SET_NAME); return Objects.nonNull(statefulSet) ? OpenShiftProvisioner.openShift.getLabeledPods("controller-revision-hash", statefulSet.getStatus().getUpdateRevision()) : Lists.emptyList(); } - @Override - public URL getURL() { + default URL getURL() { // https://github.com/keycloak/keycloak-operator/blob/15.0.2/pkg/apis/keycloak/v1alpha1/keycloak_types.go#L232 String externalUrl = keycloak().get().getStatus().getExternalURL(); try { @@ -271,29 +242,25 @@ public URL getURL() { } } - // keycloaks.keycloak.org + // keycloakrealms.keycloak.org + + HasMetadataOperationsImpl keycloaksCustomResourcesClient(CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> keycloaksClient(); /** * Get a client capable of working with {@link #KEYCLOAK_RESOURCE} custom resource. * * @return client for operations with {@link #KEYCLOAK_RESOURCE} custom resource */ - public NonNamespaceOperation> keycloaksClient() { - if (KEYCLOAKS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(KEYCLOAK_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(KEYCLOAK_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - KEYCLOAK_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> keycloaksClient = OpenShifts - .master() - .customResources(crdc, Keycloak.class, KeycloakList.class); - KEYCLOAKS_CLIENT = keycloaksClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildKeycloaksClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions().withName(KEYCLOAK_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } - return KEYCLOAKS_CLIENT; + return keycloaksCustomResourcesClient(crdc); } /** @@ -301,33 +268,30 @@ public NonNamespaceOperation> keycloa * exist on tested cluster. * @return A concrete {@link Resource} instance representing the {@link Keycloak} resource definition */ - public Resource keycloak() { + default Resource keycloak() { return keycloaksClient().withName(getApplication().getKeycloak().getMetadata().getName()); } // keycloakrealms.keycloak.org + HasMetadataOperationsImpl keycloakRealmsCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> keycloakRealmsClient(); /** * Get a client capable of working with {@link #KEYCLOAK_REALM_RESOURCE} custom resource. * * @return client for operations with {@link #KEYCLOAK_REALM_RESOURCE} custom resource */ - public NonNamespaceOperation> keycloakRealmsClient() { - if (KEYCLOAK_REALMS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(KEYCLOAK_REALM_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(KEYCLOAK_REALM_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - KEYCLOAK_REALM_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> keycloakRealmsClient = OpenShifts - .master() - .customResources(crdc, KeycloakRealm.class, KeycloakRealmList.class); - KEYCLOAK_REALMS_CLIENT = keycloakRealmsClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildKeycloakRealmsClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(KEYCLOAK_REALM_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_REALM_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_REALM_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } - return KEYCLOAK_REALMS_CLIENT; + return keycloakRealmsCustomResourcesClient(crdc); } /** @@ -337,7 +301,7 @@ public NonNamespaceOperation keycloakRealm(String name) { + default Resource keycloakRealm(String name) { return keycloakRealmsClient().withName(name); } @@ -348,7 +312,7 @@ public Resource keycloakRealm(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link KeycloakRealm} resource definitions */ - public List> keycloakRealms() { + default List> keycloakRealms() { KeycloakOperatorApplication keycloakOperatorApplication = getApplication(); return keycloakOperatorApplication.getKeycloakRealms().stream() .map(keycloakRealm -> keycloakRealm.getMetadata().getName()) @@ -357,28 +321,27 @@ public List> keycloakRealms() { } // keycloakbackups.keycloak.org + HasMetadataOperationsImpl keycloakBackupsCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> keycloakBackupsClient(); /** * Get a client capable of working with {@link #KEYCLOAK_BACKUP_RESOURCE} custom resource. * * @return client for operations with {@link #KEYCLOAK_BACKUP_RESOURCE} custom resource */ - public NonNamespaceOperation> keycloakBackupsClient() { - if (KEYCLOAK_BACKUPS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(KEYCLOAK_BACKUP_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(KEYCLOAK_BACKUP_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - KEYCLOAK_BACKUP_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> keycloakBackupsClient = OpenShifts - .master() - .customResources(crdc, KeycloakBackup.class, KeycloakBackupList.class); - KEYCLOAK_BACKUPS_CLIENT = keycloakBackupsClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildKeycloakBackupsClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(KEYCLOAK_BACKUP_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_BACKUP_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_BACKUP_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } - return KEYCLOAK_BACKUPS_CLIENT; + + return keycloakBackupsCustomResourcesClient(crdc); + } /** @@ -388,7 +351,7 @@ public NonNamespaceOperation keycloakBackup(String name) { + default Resource keycloakBackup(String name) { return keycloakBackupsClient().withName(name); } @@ -399,7 +362,7 @@ public Resource keycloakBackup(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link KeycloakBackup} resource definitions */ - public List> keycloakBackups() { + default List> keycloakBackups() { KeycloakOperatorApplication keycloakOperatorApplication = getApplication(); return keycloakOperatorApplication.getKeycloakBackups().stream() .map(keycloakBackup -> keycloakBackup.getMetadata().getName()) @@ -408,28 +371,26 @@ public List> keycloakBackups() { } // keycloakclients.keycloak.org + HasMetadataOperationsImpl keycloakClientsCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> keycloakClientsClient(); /** * Get a client capable of working with {@link #KEYCLOAK_CLIENT_RESOURCE} custom resource. * * @return client for operations with {@link #KEYCLOAK_CLIENT_RESOURCE} custom resource */ - public NonNamespaceOperation> keycloakClientsClient() { - if (KEYCLOAK_CLIENTS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(KEYCLOAK_CLIENT_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(KEYCLOAK_CLIENT_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - KEYCLOAK_CLIENT_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> keycloakClientsClient = OpenShifts - .master() - .customResources(crdc, KeycloakClient.class, KeycloakClientList.class); - KEYCLOAK_CLIENTS_CLIENT = keycloakClientsClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildKeycloakClientsClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(KEYCLOAK_CLIENT_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_CLIENT_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_CLIENT_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } - return KEYCLOAK_CLIENTS_CLIENT; + + return keycloakClientsCustomResourcesClient(crdc); } /** @@ -439,7 +400,7 @@ public NonNamespaceOperation keycloakClient(String name) { + default Resource keycloakClient(String name) { return keycloakClientsClient().withName(name); } @@ -450,7 +411,7 @@ public Resource keycloakClient(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link KeycloakClient} resource definitions */ - public List> keycloakClients() { + default List> keycloakClients() { KeycloakOperatorApplication keycloakOperatorApplication = getApplication(); return keycloakOperatorApplication.getKeycloakClients().stream() .map(keycloakClient -> keycloakClient.getMetadata().getName()) @@ -459,28 +420,25 @@ public List> keycloakClients() { } // keycloakusers.keycloak.org + HasMetadataOperationsImpl keycloakUsersCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> keycloakUsersClient(); /** * Get a client capable of working with {@link #KEYCLOAK_USER_RESOURCE} custom resource. * * @return client for operations with {@link #KEYCLOAK_USER_RESOURCE} custom resource */ - public NonNamespaceOperation> keycloakUsersClient() { - if (KEYCLOAK_USERS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(KEYCLOAK_USER_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(KEYCLOAK_USER_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - KEYCLOAK_USER_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> keycloakUsersClient = OpenShifts - .master() - .customResources(crdc, KeycloakUser.class, KeycloakUserList.class); - KEYCLOAK_USERS_CLIENT = keycloakUsersClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildKeycloakUsersClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(KEYCLOAK_USER_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_USER_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_USER_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } - return KEYCLOAK_USERS_CLIENT; + return keycloakUsersCustomResourcesClient(crdc); } /** @@ -490,7 +448,7 @@ public NonNamespaceOperation keycloakUser(String name) { + default Resource keycloakUser(String name) { return keycloakUsersClient().withName(name); } @@ -501,7 +459,7 @@ public Resource keycloakUser(String name) { * Use get() to get the actual object, or null in case it does not exist on tested cluster. * @return A list of {@link Resource} instances representing the {@link KeycloakUser} resource definitions */ - public List> keycloakUsers() { + default List> keycloakUsers() { KeycloakOperatorApplication keycloakOperatorApplication = getApplication(); return keycloakOperatorApplication.getKeycloakUsers().stream() .map(keycloakUser -> keycloakUser.getMetadata().getName()) @@ -509,11 +467,13 @@ public List> keycloakUsers() { .collect(Collectors.toList()); } + StatefulSet retrieveNamedStatefulSet(final String statefulSetName); + /** * @return the underlying StatefulSet which provisions the cluster */ private StatefulSet getStatefulSet() { - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + StatefulSet statefulSet = retrieveNamedStatefulSet(STATEFUL_SET_NAME); if (Objects.isNull(statefulSet)) { throw new IllegalStateException(String.format( "Impossible to find StatefulSet with name=\"%s\"!", diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakRealmImportOperatorProvisioner.java similarity index 61% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakRealmImportOperatorProvisioner.java index bdc934ab..fbb2a4b0 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/KeycloakRealmImportOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/KeycloakRealmImportOperatorProvisioner.java @@ -13,93 +13,71 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Path; import java.text.MessageFormat; import java.util.List; -import java.util.Map; import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; import java.util.stream.Collectors; +import io.fabric8.kubernetes.api.model.Secret; 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.KeycloakRealmImportOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.KeycloakRealmImportOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.WaitersUtil; 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.openshift.OpenShifts; 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.Service; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; 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.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; -import lombok.NonNull; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; +import io.fabric8.openshift.api.model.Route; /** * Keycloak operator provisioner */ -public class KeycloakRealmImportOperatorProvisioner extends OperatorProvisioner { +public interface KeycloakRealmImportOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { - private static final String OPERATOR_ID = IntersmashConfig.keycloakRealmImportOperatorPackageManifest(); - protected FailFastCheck ffCheck = () -> false; + String OPERATOR_ID = IntersmashConfig.keycloakRealmImportOperatorPackageManifest(); - public KeycloakRealmImportOperatorProvisioner(@NonNull KeycloakRealmImportOperatorApplication application) { - super(application, OPERATOR_ID); - } - - public static String getOperatorId() { - return OPERATOR_ID; - } - - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.keycloakRealmImportOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.keycloakRealmImportOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.keycloakRealmImportOperatorChannel(); } - @Override - public void subscribe() { - if (Strings.isNullOrEmpty(IntersmashConfig.keycloakRealmImportImageURL())) { - super.subscribe(); - } else { - subscribe( - INSTALLPLAN_APPROVAL_MANUAL, - Map.of( - // Custom Keycloak image to be used: overrides the Keycloak image at the operator level: all - // Keycloak instances will be spun out of this image - // e.g. OPERATOR_KEYCLOAK_IMAGE=quay.io/keycloak/keycloak:21.1.1 --> operator.keycloak.image - "OPERATOR_KEYCLOAK_IMAGE", IntersmashConfig.keycloakRealmImportImageURL())); - } - } + Service retrieveNamedService(final String serviceName); - @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { + FailFastCheck ffCheck = () -> false; // 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(); @@ -114,28 +92,32 @@ public void deploy() { // 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().replaceFirst("[.].*$", ""), tlsSecretName); + + Secret tlsSecret = createTlsSecret(OpenShifts.master().getNamespace(), tlsSecretName, certificateAndKey.key, + certificateAndKey.certificate); + // add config to keycloak if (getApplication().getKeycloak().getSpec().getHttp() == null) { Http http = new Http(); - http.setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + http.setTlsSecret(tlsSecret.getMetadata().getName()); getApplication().getKeycloak().getSpec().setHttp(http); } else { getApplication().getKeycloak().getSpec().getHttp() - .setTlsSecret(certificateAndKey.tlsSecret.getMetadata().getName()); + .setTlsSecret(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) + new SimpleWaiter(() -> retrieveNamedService(getApplication().getKeycloak().getSpec().getDb().getHost()) != null) .level(Level.DEBUG).waitFor(); } @@ -158,19 +140,24 @@ public void deploy() { } } - public void waitFor(Keycloak keycloak) { + Secret createTlsSecret(final String namespace, final String tlsSecretName, final Path key, final Path certificate); + + default 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) + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("controller-revision-hash") != null + && p.getMetadata().getLabels().get("controller-revision-hash").equals(controllerRevisionHash)) + .collect(Collectors.toList()).size() == replicas.intValue(); + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for pods with label \"controller-revision-hash\"=" + controllerRevisionHash + " to be scaled") .waitFor(); } } - public void waitFor(KeycloakRealmImport realmImport) { + default void waitFor(KeycloakRealmImport realmImport) { new SimpleWaiter(() -> { Resource res = keycloakRealmImportClient().withName(realmImport.getMetadata().getName()); if (Objects.nonNull(res) @@ -193,7 +180,7 @@ public void waitFor(KeycloakRealmImport realmImport) { }).reason("Wait for KeycloakRealmImport resource to be imported").level(Level.DEBUG).waitFor(); } - private void waitForKeycloakResourceReadiness() { + default void waitForKeycloakResourceReadiness() { new SimpleWaiter( () -> keycloak().get().getStatus().getConditions().stream().anyMatch( condition -> "Ready".equalsIgnoreCase(condition.getType()) @@ -212,12 +199,12 @@ private void waitForKeycloakResourceReadiness() { * 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() { + default Resource keycloak() { return keycloakClient() .withName(getApplication().getKeycloak().getMetadata().getName()); } - public List keycloakRealmImports() { + default List keycloakRealmImports() { return keycloakRealmImportClient().list().getItems() .stream().filter( realm -> getApplication().getKeycloakRealmImports().stream().map( @@ -226,24 +213,29 @@ public List keycloakRealmImports() { .collect(Collectors.toList()); } + StatefulSet retrieveNamedStatefulSet(final String statefulSetName); + + List retrieveRoutes(); + /** * @return the underlying StatefulSet which provisions the cluster */ - private StatefulSet getStatefulSet() { + default StatefulSet getStatefulSet() { final String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); new SimpleWaiter( - () -> Objects.nonNull(OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME))) + () -> Objects.nonNull(retrieveNamedStatefulSet(STATEFUL_SET_NAME))) .reason( MessageFormat.format( "Waiting for StatefulSet \"{0}\" to be created for Keycloak \"{1}\".", STATEFUL_SET_NAME, getApplication().getKeycloak().getMetadata().getName())) .level(Level.DEBUG).timeout(60000L).waitFor(); - return OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + return retrieveNamedStatefulSet(STATEFUL_SET_NAME); } - @Override - public void undeploy() { + List retrieveNamespacePods(); + + default void undeploy() { keycloakRealmImports() .forEach( keycloakRealm -> keycloakRealmImportClient() @@ -258,22 +250,33 @@ public void undeploy() { .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) + BooleanSupplier bs = () -> retrieveNamespacePods().stream() + .filter(p -> !com.google.common.base.Strings.isNullOrEmpty(p.getMetadata().getLabels().get("app")) + && p.getMetadata().getLabels().get("app") + .equals(getApplication().getKeycloak().getKind().toLowerCase())) + .collect(Collectors.toList()).size() == 0; + String reason = "Waiting for exactly 0 pods with label \"app\"=" + + getApplication().getKeycloak().getKind().toLowerCase() + " to be ready."; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, reason) + .level(Level.DEBUG) .waitFor(); + unsubscribe(); } - @Override - public void scale(int replicas, boolean wait) { + default 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) + BooleanSupplier bs = () -> retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("controller-revision-hash") != null + && p.getMetadata().getLabels().get("controller-revision-hash").equals(controllerRevisionHash)) + .collect(Collectors.toList()).size() == replicas; + new SimpleWaiter(bs, TimeUnit.MINUTES, 2, + "Waiting for pods with label \"controller-revision-hash\"=" + controllerRevisionHash + " to be scaled") .level(Level.DEBUG) .waitFor(); } @@ -290,19 +293,20 @@ public void scale(int replicas, boolean wait) { } } - @Override - public List getPods() { + default List getPods() { String STATEFUL_SET_NAME = getApplication().getKeycloak().getMetadata().getName(); - StatefulSet statefulSet = OpenShiftProvisioner.openShift.getStatefulSet(STATEFUL_SET_NAME); + StatefulSet statefulSet = retrieveNamedStatefulSet(STATEFUL_SET_NAME); return Objects.nonNull(statefulSet) - ? OpenShiftProvisioner.openShift.getLabeledPods("controller-revision-hash", - statefulSet.getStatus().getUpdateRevision()) + ? retrievePods().stream() + .filter(p -> p.getMetadata().getLabels().get("controller-revision-hash") != null + && p.getMetadata().getLabels().get("controller-revision-hash") + .equals(statefulSet.getStatus().getUpdateRevision())) + .collect(Collectors.toList()) : Lists.emptyList(); } - @Override - public URL getURL() { - String host = OpenShiftProvisioner.openShift.routes().list().getItems() + default URL getURL() { + String host = retrieveRoutes() .stream().filter( route -> route.getMetadata().getName().startsWith( keycloak().get().getMetadata().getName()) @@ -326,19 +330,37 @@ public URL getURL() { } } - public NonNamespaceOperation, Resource> keycloakClient() { - try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { - MixedOperation, Resource> keycloakClient = kubernetesClient - .resources(Keycloak.class); - return keycloakClient.inNamespace(OpenShiftConfig.namespace()); + String KEYCLOAK_RESOURCE = "keycloaks.k8s.keycloak.org"; + + String KEYCLOAK_REALM_RESOURCE = "keycloakrealmimports.k8s.keycloak.org"; + + HasMetadataOperationsImpl> keycloaksCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation, Resource> keycloakClient(); + + HasMetadataOperationsImpl> keycloakRealmImportsCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation, Resource> keycloakRealmImportClient(); + + default MixedOperation, Resource> buildKeycloakClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions().withName(KEYCLOAK_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } + return keycloaksCustomResourcesClient(crdc); } - public NonNamespaceOperation, Resource> keycloakRealmImportClient() { - try (KubernetesClient kubernetesClient = new DefaultKubernetesClient()) { - MixedOperation, Resource> keycloakRealmImportClient = kubernetesClient - .resources(KeycloakRealmImport.class); - return keycloakRealmImportClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation, Resource> buildKeycloakRealmImportClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions().withName(KEYCLOAK_REALM_RESOURCE).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(KEYCLOAK_REALM_RESOURCE)) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + KEYCLOAK_REALM_RESOURCE, KeycloakOperatorProvisioner.operatorId())); } + return keycloakRealmImportsCustomResourcesClient(crdc); } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisioner.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/WildflyOperatorProvisioner.java similarity index 61% rename from tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisioner.java rename to tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/WildflyOperatorProvisioner.java index 1b054682..5a9ebd32 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/openshift/WildflyOperatorProvisioner.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/provision/operator/WildflyOperatorProvisioner.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jboss.intersmash.tools.provision.openshift; +package org.jboss.intersmash.tools.provision.operator; import java.net.MalformedURLException; import java.net.URL; @@ -21,18 +21,18 @@ import java.util.stream.Collectors; import org.jboss.intersmash.tools.IntersmashConfig; -import org.jboss.intersmash.tools.application.openshift.WildflyOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.operator.OperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; +import org.jboss.intersmash.tools.provision.Provisioner; +import org.jboss.intersmash.tools.provision.openshift.OpenShiftProvisioner; +import org.jboss.intersmash.tools.provision.openshift.WaitersUtil; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServer; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.WildFlyServerList; import org.jboss.intersmash.tools.provision.openshift.operator.wildfly.status.PodStatus; 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.openshift.OpenShifts; 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.Pod; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; @@ -40,57 +40,58 @@ import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; -import lombok.NonNull; +import io.fabric8.kubernetes.client.dsl.internal.HasMetadataOperationsImpl; -public class WildflyOperatorProvisioner extends OperatorProvisioner { - private final static String WILDFLY_SERVER_RESOURCE = "wildflyservers.wildfly.org"; - private static NonNamespaceOperation> WILDFLY_SERVERS_CLIENT; - // oc get packagemanifest wildfly -n openshift-marketplace - private static final String OPERATOR_ID = IntersmashConfig.wildflyOperatorPackageManifest(); +public interface WildflyOperatorProvisioner extends + OlmOperatorProvisioner, Provisioner { - public WildflyOperatorProvisioner(@NonNull WildflyOperatorApplication wildflyOperatorApplication) { - super(wildflyOperatorApplication, OPERATOR_ID); + // this is the packagemanifest for the hyperfoil operator; + // you can get it with command: + // oc get packagemanifest hyperfoil-bundle -o template --template='{{ .metadata.name }}' + static String operatorId() { + return IntersmashConfig.wildflyOperatorPackageManifest(); } - public static String getOperatorId() { - return OPERATOR_ID; + // this is the name of the Wildfly CustomResourceDefinition + // you can get it with command: + // oc get crd wildflyservers.wildfly.org -o template --template='{{ .metadata.name }}' + default String wildflyCustomResourceDefinitionName() { + return "wildflyservers.wildfly.org"; } /** - * Get a client capable of working with {@link #WILDFLY_SERVER_RESOURCE} custom resource. + * Get a client capable of working with {@link #wildflyCustomResourceDefinitionName} custom resource. * - * @return client for operations with {@link #WILDFLY_SERVER_RESOURCE} custom resource + * @return client for operations with {@link #wildflyCustomResourceDefinitionName} custom resource */ - public NonNamespaceOperation> wildflyServersClient() { - if (WILDFLY_SERVERS_CLIENT == null) { - CustomResourceDefinition crd = OpenShifts.admin().apiextensions().v1().customResourceDefinitions() - .withName(WILDFLY_SERVER_RESOURCE).get(); - CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); - if (!getCustomResourceDefinitions().contains(WILDFLY_SERVER_RESOURCE)) { - throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", - WILDFLY_SERVER_RESOURCE, OPERATOR_ID)); - } - - MixedOperation> wildflyServersClient = OpenShifts - .master().customResources(crdc, WildFlyServer.class, WildFlyServerList.class); - WILDFLY_SERVERS_CLIENT = wildflyServersClient.inNamespace(OpenShiftConfig.namespace()); + default MixedOperation> buildWildflyClient() { + CustomResourceDefinition crd = retrieveCustomResourceDefinitions() + .withName(wildflyCustomResourceDefinitionName()).get(); + CustomResourceDefinitionContext crdc = CustomResourceDefinitionContext.fromCrd(crd); + if (!retrieveCustomResourceDefinitions().list().getItems().contains(wildflyCustomResourceDefinitionName())) { + throw new RuntimeException(String.format("[%s] custom resource is not provided by [%s] operator.", + wildflyCustomResourceDefinitionName(), operatorId())); } - return WILDFLY_SERVERS_CLIENT; + return wildflyCustomResourcesClient(crdc); } + HasMetadataOperationsImpl wildflyCustomResourcesClient( + CustomResourceDefinitionContext crdc); + + NonNamespaceOperation> wildflyServersClient(); + /** * Get a reference to wildFlyServer 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 WildFlyServer} resource definition */ - public Resource wildFlyServer() { + default Resource wildFlyServer() { return wildflyServersClient().withName(getApplication().getName()); } @Override - public void deploy() { - ffCheck = FailFastUtils.getFailFastCheck(EventHelper.timeOfLastEventBMOrTestNamespaceOrEpoch(), - getApplication().getName()); + default void deploy() { + FailFastCheck ffCheck = () -> false; subscribe(); wildflyServersClient().createOrReplace(getApplication().getWildflyServer()); int expected = getApplication().getWildflyServer().getSpec().getReplicas(); @@ -111,21 +112,11 @@ public void deploy() { } } - @Override - public void undeploy() { - wildFlyServer().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); - OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) - .areExactlyNPodsReady(0, "app.kubernetes.io/name", getApplication().getName()) - .level(Level.DEBUG).waitFor(); - unsubscribe(); - } - - @Override - public void scale(int replicas, boolean wait) { + default void scale(int replicas, boolean wait) { scale(wildFlyServer(), replicas, wait); } - private void scale(Resource wildFlyServer, int replicas, boolean wait) { + default void scale(Resource wildFlyServer, int replicas, boolean wait) { WildFlyServer tmpServer = wildFlyServer.get(); int originalReplicas = tmpServer.getSpec().getReplicas(); tmpServer.getSpec().setReplicas(replicas); @@ -141,42 +132,29 @@ private void scale(Resource wildFlyServer, int replicas, boolean } } - /** - * Return the ready pods managed by the operator. - * - * @return pods which are registered by operator as active and are in ready state - */ @Override - public List getPods() { - List pods = OpenShiftProvisioner.openShift.getPods(); - List activeOperatorPodNames = wildFlyServer().get().getStatus().getPods().stream() - .filter(podStatus -> podStatus.getState().equals("ACTIVE")) - .map(PodStatus::getName) - .collect(Collectors.toList()); - return pods.stream() - .filter(pod -> activeOperatorPodNames.contains(pod.getMetadata().getName())) - .filter(pod -> pod.getStatus().getContainerStatuses().size() > 0 - && pod.getStatus().getContainerStatuses().get(0).getReady()) - .collect(Collectors.toList()); + default void undeploy() { + FailFastCheck ffCheck = () -> false; + wildFlyServer().withPropagationPolicy(DeletionPropagation.FOREGROUND).delete(); + OpenShiftWaiters.get(OpenShiftProvisioner.openShift, ffCheck) + .areExactlyNPodsReady(0, "app.kubernetes.io/name", getApplication().getName()) + .level(Level.DEBUG).waitFor(); + unsubscribe(); } - @Override - protected String getOperatorCatalogSource() { + default String getOperatorCatalogSource() { return IntersmashConfig.wildflyOperatorCatalogSource(); } - @Override - protected String getOperatorIndexImage() { + default String getOperatorIndexImage() { return IntersmashConfig.wildflyOperatorIndexImage(); } - @Override - protected String getOperatorChannel() { + default String getOperatorChannel() { return IntersmashConfig.wildflyOperatorChannel(); } - @Override - public URL getURL() { + default URL getURL() { String url = "http://" + wildFlyServer().get().getStatus().getHosts().get(0); try { return new URL(url); @@ -184,4 +162,24 @@ public URL getURL() { throw new RuntimeException(String.format("WILDFLY operator route \"%s\"is malformed.", url), e); } } + + List retrievePods(); + + /** + * Return the ready pods managed by the operator. + * + * @return pods which are registered by operator as active and are in ready state + */ + default List getPods() { + List pods = retrievePods(); + List activeOperatorPodNames = wildFlyServer().get().getStatus().getPods().stream() + .filter(podStatus -> podStatus.getState().equals("ACTIVE")) + .map(PodStatus::getName) + .collect(Collectors.toList()); + return pods.stream() + .filter(pod -> activeOperatorPodNames.contains(pod.getMetadata().getName())) + .filter(pod -> pod.getStatus().getContainerStatuses().size() > 0 + && pod.getStatus().getContainerStatuses().get(0).getReady()) + .collect(Collectors.toList()); + } } diff --git a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java index eaae70c9..6a25f5be 100644 --- a/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java +++ b/tools/intersmash-tools-provisioners/src/main/java/org/jboss/intersmash/tools/util/tls/CertificatesUtils.java @@ -6,16 +6,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.text.MessageFormat; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -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; @@ -40,7 +32,6 @@ public static class CertificateAndKey { public String truststorePassword; public String truststoreAlias; public boolean existing = false; - public Secret tlsSecret; } /** @@ -68,11 +59,6 @@ public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostn caDir.resolve(key).toFile().exists() && caDir.resolve(truststore).toFile().exists()) { certificateAndKey.existing = true; - Secret tlsSecret = OpenShifts.master().getSecret(tlsSecretName); - if (Objects.isNull(tlsSecret)) { - throw new RuntimeException(MessageFormat.format("Secret {} doesn't exist!", tlsSecretName)); - } - certificateAndKey.tlsSecret = tlsSecret; return certificateAndKey; } @@ -84,17 +70,6 @@ public static CertificateAndKey generateSelfSignedCertificateAndKey(String hostn 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); - if (Objects.isNull(tlsSecret)) { - throw new RuntimeException(MessageFormat.format("Secret {} doesn't exist!", tlsSecretName)); - } - certificateAndKey.tlsSecret = tlsSecret; - } catch (IOException e) { - throw new RuntimeException("Failed to create secret " + tlsSecretName, e); - } - return certificateAndKey; } @@ -126,19 +101,4 @@ private static void processCall(Path cwd, String... args) { throw new RuntimeException("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 index ce95a5d5..c484953e 100644 --- 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 @@ -1,2277 +1,2249 @@ +# See https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/21.1.1/kubernetes/keycloakrealmimports.k8s.keycloak.org-v1.yml +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! 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: + - 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: + webAuthnPolicyAvoidSameAuthenticatorRegister: + type: boolean + federatedUsers: items: properties: - client: + id: type: string - clientScope: + clientConsents: + items: + properties: + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + createdDate: + type: integer + clientId: + type: string + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + requiredActions: + items: + type: string + type: array + enabled: + type: boolean + realmRoles: + items: + type: string + type: array + createdTimestamp: + type: integer + emailVerified: + type: boolean + disableableCredentialTypes: + items: + type: string + type: array + socialLinks: + items: + properties: + socialUserId: + type: string + socialProvider: + type: string + socialUsername: + type: string + type: object + type: array + username: + type: string + federationLink: + type: string + access: + additionalProperties: + type: boolean + type: object + totp: + type: boolean + serviceAccountClientId: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + federatedIdentities: + items: + properties: + userId: + type: string + identityProvider: + type: string + userName: + type: string + type: object + type: array + firstName: + type: string + self: + type: string + notBefore: + type: integer + groups: + items: + type: string + type: array + credentials: + items: + properties: + id: + type: string + period: + type: integer + counter: + type: integer + value: + type: string + hashIterations: + type: integer + algorithm: + type: string + hashedSaltedValue: + type: string + type: + type: string + priority: + type: integer + device: + type: string + temporary: + type: boolean + userLabel: + type: string + createdDate: + type: integer + secretData: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + credentialData: + type: string + salt: + type: string + digits: + type: integer + type: object + type: array + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + lastName: + type: string + email: + type: string + origin: + type: string + type: object + type: array + adminEventsEnabled: + type: boolean + registrationEmailAsUsername: + type: boolean + keycloakVersion: + type: string + oauth2DeviceCodeLifespan: + type: integer + sslRequired: + type: string + realm: + type: string + defaultGroups: + items: + type: string + type: array + enabled: + type: boolean + webAuthnPolicySignatureAlgorithms: + items: + type: string + type: array + ssoSessionMaxLifespanRememberMe: + type: integer + webAuthnPolicyRpId: + type: string + webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister: + type: boolean + users: + items: + properties: + id: + type: string + clientConsents: + items: + properties: + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + createdDate: + type: integer + clientId: + type: string + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + requiredActions: + items: + type: string + type: array + enabled: + type: boolean + realmRoles: + items: + type: string + type: array + createdTimestamp: + type: integer + emailVerified: + type: boolean + disableableCredentialTypes: + items: + type: string + type: array + socialLinks: + items: + properties: + socialUserId: + type: string + socialProvider: + type: string + socialUsername: + type: string + type: object + type: array + username: + type: string + federationLink: + type: string + access: + additionalProperties: + type: boolean + type: object + totp: + type: boolean + serviceAccountClientId: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + federatedIdentities: + items: + properties: + userId: + type: string + identityProvider: + type: string + userName: + type: string + type: object + type: array + firstName: + type: string + self: + type: string + notBefore: + type: integer + groups: + items: + type: string + type: array + credentials: + items: + properties: + id: + type: string + period: + type: integer + counter: + type: integer + value: + type: string + hashIterations: + type: integer + algorithm: + type: string + hashedSaltedValue: + type: string + type: + type: string + priority: + type: integer + device: + type: string + temporary: + type: boolean + userLabel: + type: string + createdDate: + type: integer + secretData: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + credentialData: + type: string + salt: + type: string + digits: + type: integer + type: object + type: array + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + lastName: + type: string + email: + type: string + origin: + type: string + type: object + type: array + clientTemplates: + items: + properties: + protocol: + type: string + id: + type: string + fullScopeAllowed: + type: boolean + frontchannelLogout: + type: boolean + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + description: + type: string + publicClient: + type: boolean + consentRequired: + type: boolean + bearerOnly: + type: boolean + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + name: + type: string + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + attributes: + additionalProperties: + type: string + type: object + type: object + type: array + webAuthnPolicyPasswordlessUserVerificationRequirement: + type: string + registrationFlow: + type: string + publicKey: + type: string + webAuthnPolicyPasswordlessCreateTimeout: + type: integer + authenticationFlows: + items: + properties: + id: + type: string + providerId: + type: string + authenticationExecutions: + items: + properties: + userSetupAllowed: + type: boolean + flowAlias: + type: string + autheticatorFlow: + type: boolean + authenticatorConfig: + type: string + authenticator: + type: string + priority: + type: integer + requirement: + type: string + authenticatorFlow: + type: boolean + type: object + type: array + topLevel: + type: boolean + alias: + type: string + builtIn: + type: boolean + description: + type: string + type: object + type: array + applicationScopeMappings: + additionalProperties: + items: + properties: + clientTemplate: + type: string + self: + type: string + clientScope: + type: string + client: + type: string + roles: + items: + type: string + type: array + type: object + type: array + type: object + offlineSessionMaxLifespan: + type: integer + codeSecret: + type: string + offlineSessionIdleTimeout: + type: integer + quickLoginCheckMilliSeconds: + type: integer + privateKey: + type: string + webAuthnPolicyRpEntityName: + type: string + emailTheme: + type: string + accessCodeLifespanLogin: + type: integer + passwordPolicy: + type: string + ssoSessionIdleTimeoutRememberMe: + type: integer + resetPasswordAllowed: + type: boolean + failureFactor: + type: integer + otpPolicyAlgorithm: + type: string + requiredActions: + items: + properties: + providerId: + type: string + alias: + type: string + defaultAction: + type: boolean + priority: + type: integer + name: + type: string + enabled: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + actionTokenGeneratedByUserLifespan: + type: integer + clientAuthenticationFlow: + type: string + webAuthnPolicyAuthenticatorAttachment: + type: string + actionTokenGeneratedByAdminLifespan: + type: integer + id: + type: string + clientPolicies: + type: object + x-kubernetes-preserve-unknown-fields: true + webAuthnPolicyUserVerificationRequirement: + type: string + loginTheme: + type: string + requiredCredentials: + items: + type: string + type: array + webAuthnPolicyPasswordlessAttestationConveyancePreference: + type: string + directGrantFlow: + type: string + identityProviderMappers: + items: + properties: + id: + type: string + name: + type: string + identityProviderMapper: + type: string + identityProviderAlias: + type: string + config: + additionalProperties: + type: string + type: object + type: object + type: array + dockerAuthenticationFlow: + type: string + browserFlow: + type: string + bruteForceProtected: + type: boolean + displayNameHtml: + type: string + ssoSessionIdleTimeout: + type: integer + browserSecurityHeaders: + additionalProperties: + type: string + type: object + eventsListeners: + items: + type: string + type: array + accessTokenLifespan: + type: integer + applications: + items: + properties: + name: + type: string + claims: + properties: + picture: + type: boolean + gender: + type: boolean + phone: + type: boolean + website: + type: boolean + email: + type: boolean + profile: + type: boolean + address: + type: boolean + name: + type: boolean + username: + type: boolean + locale: + type: boolean + type: object + id: type: string - clientTemplate: + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: type: string - roles: - items: - type: string - type: array - self: + baseUrl: type: string - type: object - type: array - type: object - applications: - items: - properties: - access: - additionalProperties: + serviceAccountsEnabled: type: boolean - type: object - adminUrl: - type: string - alwaysDisplayInConsole: - type: boolean - attributes: - additionalProperties: - type: string - type: object - authenticationFlowBindingOverrides: - additionalProperties: + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: 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: + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: + type: string + resources: + items: + properties: + _id: 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: + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: type: string - uris: - items: - type: string - type: array + type: array type: object - type: array - scopes: - items: + displayName: 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: + scopes: items: - type: string + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object 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: + owner: properties: - displayName: - type: string - iconUri: - type: string id: type: string name: type: string type: object - type: array - type: - type: string - uris: - items: + name: 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: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS 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: + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED type: string - authenticatorFlow: - type: boolean - autheticatorFlow: - type: boolean - flowAlias: - type: string - priority: - type: integer - requirement: + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: + items: + type: string + type: array + policies: + items: + type: string + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + description: + type: string + scopes: + items: + type: string + type: array + type: object + type: array + clientId: type: string - userSetupAllowed: + allowRemoteResourceManagement: 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: + clientId: type: string - clientTemplate: + enabled: + type: boolean + clientAuthenticatorType: type: string - roles: + surrogateAuthRequired: + type: boolean + webOrigins: items: type: string type: array - self: + authorizationServicesEnabled: + type: boolean + secret: type: string - type: object - type: array - type: object - clientScopes: - items: - properties: - attributes: - additionalProperties: + protocol: 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: + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: 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 + access: + additionalProperties: + type: boolean type: object - type: array - publicClient: - type: boolean - serviceAccountsEnabled: - type: boolean - standardFlowEnabled: - type: boolean - type: object - type: array - clients: - items: - properties: - access: - additionalProperties: + alwaysDisplayInConsole: type: boolean - type: object - adminUrl: - type: string - alwaysDisplayInConsole: - type: boolean - attributes: - additionalProperties: + rootUrl: type: string - type: object - authenticationFlowBindingOverrides: - additionalProperties: + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: + items: + type: string + type: array + adminUrl: type: string - type: object - authorizationServicesEnabled: - type: boolean - authorizationSettings: - properties: - allowRemoteResourceManagement: - type: boolean - clientId: + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: + items: type: string - decisionStrategy: - enum: - - AFFIRMATIVE - - stableIndex - - CONSENSUS - - UNANIMOUS + type: array + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: + type: string + attributes: + additionalProperties: type: string - id: + type: object + redirectUris: + items: type: string - name: + type: array + type: object + type: array + otpPolicyCodeReusable: + type: boolean + clientProfiles: + type: object + x-kubernetes-preserve-unknown-fields: true + userFederationMappers: + items: + properties: + id: + type: string + federationProviderDisplayName: + type: string + federationMapperType: + type: string + name: + type: string + config: + additionalProperties: 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: + type: object + type: object + type: array + enabledEventTypes: + items: + type: string + type: array + otpPolicyLookAheadWindow: + type: integer + displayName: + type: string + eventsEnabled: + type: boolean + clientSessionMaxLifespan: + type: integer + roles: + properties: + application: + additionalProperties: + items: + properties: + attributes: + additionalProperties: 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: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: 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: array + type: object + client: + additionalProperties: + items: type: string - uris: - items: - type: string - type: array + type: array type: object - type: array - scopes: + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + client: + additionalProperties: + items: + properties: + attributes: + additionalProperties: items: type: string type: array - scopesData: - items: - properties: - displayName: - type: string - iconUri: - type: string - id: + type: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: type: string - name: + type: array + type: object + client: + additionalProperties: + items: type: string + type: array type: object - type: array - type: + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + realm: + items: + properties: + attributes: + additionalProperties: + items: type: string + type: array type: object - type: array - policyEnforcementMode: - enum: - - stableIndex - - PERMISSIVE - - ENFORCING - - DISABLED - type: string - resources: - items: + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: properties: - _id: - type: string - attributes: + realm: + items: + type: string + type: array + application: additionalProperties: items: type: string type: array type: object - displayName: - type: string - icon_uri: - type: string - name: - type: string - owner: - properties: - id: - type: string - name: + client: + additionalProperties: + items: type: string + type: array type: object - ownerManagedAccess: - type: boolean - scopes: + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + groups: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + access: + additionalProperties: + type: boolean + type: object + realmRoles: + items: + type: string + type: array + path: + type: string + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + name: + type: string + subGroups: + items: + properties: + attributes: + additionalProperties: items: - properties: - displayName: - type: string - iconUri: - type: string - id: - type: string - name: - type: string - type: object + type: string type: array - type: + type: object + id: + type: string + access: + additionalProperties: + type: boolean + type: object + realmRoles: + items: type: string - uris: + type: array + path: + type: string + clientRoles: + additionalProperties: items: type: string type: array - type: object - type: array - scopes: + type: object + name: + type: string + type: object + type: array + type: object + type: array + webAuthnPolicyCreateTimeout: + type: integer + webAuthnPolicyAttestationConveyancePreference: + type: string + clientOfflineSessionIdleTimeout: + type: integer + notBefore: + type: integer + webAuthnPolicyPasswordlessRpEntityName: + type: string + verifyEmail: + type: boolean + clientScopeMappings: + additionalProperties: + items: + properties: + clientTemplate: + type: string + self: + type: string + clientScope: + type: string + client: + type: string + roles: items: - properties: - displayName: - type: string - iconUri: - type: string - id: - type: string - name: - type: string - type: object + type: string 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: array + type: object + identityProviders: + items: + properties: + storeToken: + type: boolean + trustEmail: + type: boolean + updateProfileFirstLoginMode: type: string - type: array - defaultRoles: - items: + authenticateByDefault: + type: boolean + displayName: 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: + providerId: 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 + linkOnly: + type: boolean + postBrokerLoginFlowAlias: + type: string + alias: + type: string + enabled: + type: boolean + firstBrokerLoginFlowAlias: + type: string + internalId: + type: string + addReadTokenRoleOnCreate: + type: boolean + config: + additionalProperties: + type: string type: object - type: array - publicClient: - type: boolean - redirectUris: - items: + type: object + type: array + resetCredentialsFlow: + type: string + duplicateEmailsAllowed: + type: boolean + maxDeltaTimeSeconds: + type: integer + offlineSessionMaxLifespanEnabled: + type: boolean + realmCacheEnabled: + type: boolean + attributes: + additionalProperties: + type: string + type: object + adminTheme: + type: string + loginWithEmailAllowed: + type: boolean + otpSupportedApplications: + items: + type: string + type: array + clientOfflineSessionMaxLifespan: + type: integer + userFederationProviders: + items: + properties: + id: 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: + providerName: type: string - type: array + displayName: + type: string + priority: + type: integer + fullSyncPeriod: + type: integer + lastSync: + type: integer + changedSyncPeriod: + type: integer + config: + additionalProperties: + type: string + type: object + type: object + type: array + internationalizationEnabled: + type: boolean + permanentLockout: + type: boolean + userManagedAccessAllowed: + type: boolean + smtpServer: + additionalProperties: + type: string + type: object + otpPolicyDigits: + type: integer + webAuthnPolicyPasswordlessSignatureAlgorithms: + items: + type: string + type: array + socialProviders: + additionalProperties: + type: string type: object - type: array - codeSecret: - type: string - components: - additionalProperties: + otpPolicyInitialCounter: + type: integer + defaultSignatureAlgorithm: + type: string + refreshTokenMaxReuse: + type: integer + revokeRefreshToken: + type: boolean + accountTheme: + type: string + webAuthnPolicyPasswordlessAcceptableAaguids: + items: + type: string + type: array + webAuthnPolicyPasswordlessAuthenticatorAttachment: + type: string + supportedLocales: + items: + type: string + type: array + defaultDefaultClientScopes: + items: + type: string + type: array + authenticatorConfig: items: properties: - config: - additionalProperties: - items: - type: string - type: array - type: object id: type: string - name: - type: string - providerId: + alias: type: string - subComponents: + config: 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: string type: object - subType: - type: string type: object type: array - type: object - defaultDefaultClientScopes: - items: + webAuthnPolicyPasswordlessRpId: 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: + scopeMappings: + items: properties: - application: - additionalProperties: - items: - type: string - type: array - type: object + clientTemplate: + type: string + self: + type: string + clientScope: + type: string client: - additionalProperties: - items: - type: string - type: array - type: object - realm: + type: string + roles: 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: + type: array + clientScopes: + items: + properties: + protocol: + type: string + id: + type: string + protocolMappers: items: - type: string + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object type: array - type: object + name: + type: string + description: + type: string + attributes: + additionalProperties: + type: string + type: object + type: object + type: array + oauth2DevicePollingInterval: + type: integer + eventsExpiration: + type: integer + certificate: + type: string + defaultRole: + properties: attributes: additionalProperties: items: type: string type: array type: object - clientConsents: - items: - properties: - clientId: + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: type: string - createdDate: - type: integer - grantedClientScopes: + type: array + application: + additionalProperties: items: type: string type: array - grantedRealmRoles: + type: object + client: + additionalProperties: items: type: string type: array - lastUpdatedDate: - type: integer - type: object - type: array - clientRoles: - additionalProperties: - items: - type: string - type: array + type: object type: object - createdTimestamp: - type: integer - credentials: - items: + containerId: + type: string + composite: + type: boolean + type: object + defaultOptionalClientScopes: + items: + type: string + type: array + editUsernameAllowed: + type: boolean + defaultLocale: + type: string + webAuthnPolicyRequireResidentKey: + type: string + oauthClients: + items: + properties: + name: + type: string + claims: properties: - algorithm: - type: string - config: - additionalProperties: - items: - type: string - type: array - type: object - counter: - type: integer - createdDate: - type: integer - credentialData: - type: string - device: + picture: + type: boolean + gender: + type: boolean + phone: + type: boolean + website: + type: boolean + email: + type: boolean + profile: + type: boolean + address: + type: boolean + name: + type: boolean + username: + type: boolean + locale: + type: boolean + type: object + id: + type: string + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: + type: string + baseUrl: + type: string + serviceAccountsEnabled: + type: boolean + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: + type: string + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: type: string - digits: - type: integer - hashIterations: - type: integer - hashedSaltedValue: + resources: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS type: string - id: + name: type: string - period: - type: integer - priority: - type: integer - salt: + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED type: string - secretData: + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: + items: + type: string + type: array + policies: + items: + type: string + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + description: + type: string + scopes: + items: + type: string + type: array + type: object + type: array + clientId: type: string - temporary: + allowRemoteResourceManagement: type: boolean - type: - type: string - userLabel: - type: string - value: - type: string type: object - type: array - disableableCredentialTypes: - items: + clientId: 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: + enabled: + type: boolean + clientAuthenticatorType: type: string - type: array - id: - type: string - lastName: - type: string - notBefore: - type: integer - origin: - type: string - realmRoles: - items: + surrogateAuthRequired: + type: boolean + webOrigins: + items: + type: string + type: array + authorizationServicesEnabled: + type: boolean + secret: type: string - type: array - requiredActions: - items: + protocol: type: string - type: array - self: - type: string - serviceAccountClientId: - type: string - socialLinks: - items: - properties: - socialProvider: - type: string - socialUserId: - type: string - socialUsername: - type: string + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: + type: string + access: + additionalProperties: + type: boolean type: object - type: array - totp: - type: boolean - username: - type: string - type: object - type: array - groups: - items: - properties: - access: - additionalProperties: + alwaysDisplayInConsole: type: boolean - type: object - attributes: - additionalProperties: + rootUrl: + type: string + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: items: type: string type: array - type: object - clientRoles: - additionalProperties: + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: items: type: string type: array - type: object - id: - type: string - name: - type: string - path: - type: string - realmRoles: - items: + adminUrl: type: string - type: array - subGroups: - items: - properties: - access: - additionalProperties: + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: type: boolean - type: object - attributes: - additionalProperties: - items: - type: string - type: array - type: object - clientRoles: - additionalProperties: - items: + config: + additionalProperties: type: string - type: array - type: object - id: - type: string - name: - type: string - path: - type: string - realmRoles: - items: - type: string - type: array + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: + items: + type: string + type: array + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: + type: string + attributes: + additionalProperties: + type: string type: object - type: array - type: object - type: array - id: - type: string - identityProviderMappers: - items: - properties: - config: - additionalProperties: + redirectUris: + items: + type: string + type: array + type: object + type: array + adminEventsDetailsEnabled: + type: boolean + ssoSessionMaxLifespan: + type: integer + accessCodeLifespanUserAction: + type: integer + registrationAllowed: + type: boolean + social: + type: boolean + accessTokenLifespanForImplicitFlow: + type: integer + rememberMe: + type: boolean + maxFailureWaitSeconds: + type: integer + defaultRoles: + items: + type: string + type: array + otpPolicyType: + type: string + otpPolicyPeriod: + type: integer + accessCodeLifespan: + type: integer + minimumQuickLoginWaitSeconds: + type: integer + webAuthnPolicyAcceptableAaguids: + items: + type: string + type: array + updateProfileOnInitialSocialLogin: + type: boolean + clientSessionIdleTimeout: + type: integer + webAuthnPolicyPasswordlessRequireResidentKey: + type: string + waitIncrementSeconds: + type: integer + protocolMappers: + items: + properties: + protocol: 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: + id: 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: + name: type: string - type: object - authenticationFlowBindingOverrides: - additionalProperties: + protocolMapper: 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: + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: 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: object + type: object + type: array + clients: + items: + properties: + id: + type: string + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: + type: string + baseUrl: + type: string + serviceAccountsEnabled: + type: boolean + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: + type: string + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: + type: string + resources: + items: + properties: + _id: type: string - type: array - scopesData: - items: - properties: - displayName: - type: string - iconUri: - type: string - id: - type: string - name: + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: type: string + type: array 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: + displayName: + type: string + scopes: items: - type: string + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object 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: + owner: properties: - displayName: - type: string - iconUri: - type: string id: type: string name: type: string type: object - type: array - type: - type: string - uris: - items: + name: 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: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS type: string name: type: string - protocol: - type: string - protocolMapper: + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED 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: + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: items: type: string type: array - type: object - client: - additionalProperties: + policies: items: type: string type: array - type: object - realm: - items: + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS 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: + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: items: - type: string + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object type: array - type: object - client: - additionalProperties: + description: + type: string + scopes: 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: object + type: array + clientId: type: string - scopeParamRequired: + allowRemoteResourceManagement: 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: + clientId: + type: string + enabled: + type: boolean + clientAuthenticatorType: + type: string + name: + type: string + surrogateAuthRequired: + type: boolean + webOrigins: + items: 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: array + authorizationServicesEnabled: + type: boolean + secret: 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: + protocol: 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: + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: 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: + access: + additionalProperties: + type: boolean + type: object + alwaysDisplayInConsole: type: boolean - type: object - applicationRoles: - additionalProperties: + rootUrl: + type: string + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: items: type: string type: array - type: object - attributes: - additionalProperties: + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: items: type: string type: array - type: object - clientConsents: - items: - properties: - clientId: - type: string - createdDate: - type: integer - grantedClientScopes: - items: + adminUrl: + type: string + protocolMappers: + items: + properties: + protocol: type: string - type: array - grantedRealmRoles: - items: + id: type: string - type: array - lastUpdatedDate: - type: integer - type: object - type: array - clientRoles: - additionalProperties: + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: 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: + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: type: string - type: array - self: - type: string - serviceAccountClientId: - type: string - socialLinks: - items: - properties: - socialProvider: - type: string - socialUserId: - type: string - socialUsername: - type: string + attributes: + additionalProperties: + type: string type: object - type: array - totp: - type: boolean - username: - type: string + redirectUris: + items: + type: string + type: array + type: object + type: array + components: + additionalProperties: + items: + properties: + id: + type: string + providerId: + type: string + subType: + type: string + subComponents: + additionalProperties: + items: + properties: + id: + type: string + providerId: + type: string + subType: + type: string + name: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + type: object + type: array + type: object + name: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + type: object + type: array 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: + passwordCredentialGrantAllowed: + type: boolean + userCacheEnabled: 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 + required: + - keycloakCRName + - realm + type: object + status: + properties: + conditions: + items: + properties: + status: + type: boolean + type: + type: string + message: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} 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 index 13f26c6a..93b55e81 100644 --- 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 @@ -1,2947 +1,2918 @@ +# See https://raw.githubusercontent.com/keycloak/keycloak-k8s-resources/21.1.1/kubernetes/keycloaks.k8s.keycloak.org-v1.yml +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! 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 + - 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: + - name: v2alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + 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: - name: - type: string - secret: - properties: - key: - type: string - name: - type: string - optional: - type: boolean - type: object - value: + xaEnabled: + description: Determine whether Keycloak should use a non-XA datasource + in case the database does not support XA transactions. + type: boolean + type: object + http: + description: In this section you can configure Keycloak features related + to HTTP and HTTPS + properties: + httpPort: + description: The used HTTP 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 + httpsPort: + description: The used HTTPS port. + type: integer + httpEnabled: + description: Enables the HTTP listener. + type: boolean 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: + hostname: + description: In this section you can configure Keycloak hostname and + related properties. + properties: + hostname: + description: Hostname for the Keycloak server. type: string - type: array - enabled: - description: Enabled Keycloak features - items: + 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 + admin: + description: The hostname for accessing the administration console. 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: + adminUrl: + description: "Set the base URL for accessing the administration\ + \ console, including scheme, host, port and path" 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: + 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: + generateName: type: string - type: object - clusterName: - type: string - creationTimestamp: - type: string - deletionGracePeriodSeconds: - type: integer - deletionTimestamp: - type: string - finalizers: - items: + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: type: string - type: array - generateName: - type: string - generation: - type: integer - labels: - additionalProperties: + clusterName: 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 + resourceVersion: + type: string + annotations: + additionalProperties: + 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 + selfLink: + type: string + creationTimestamp: + type: string + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + 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: + ownerReferences: + items: properties: - preferredDuringSchedulingIgnoredDuringExecution: - items: - properties: - preference: - properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array + blockOwnerDeletion: + type: boolean + uid: + type: string + apiVersion: + type: string + name: + type: string + kind: + type: string + controller: + type: boolean + type: object + type: array + uid: + type: string + generation: + type: integer + name: + type: string + managedFields: + items: + properties: + time: + type: string + apiVersion: + type: string + fieldsV1: + type: object + fieldsType: + type: string + manager: + type: string + operation: + type: string + subresource: + type: string + type: object + type: array + namespace: + type: string + type: object + spec: + properties: + volumes: + items: + properties: + hostPath: + properties: + path: + type: string + type: + type: string + type: object + flexVolume: + properties: + readOnly: + type: boolean + options: + additionalProperties: + type: string + type: object + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + driver: + type: string + type: object + gcePersistentDisk: + properties: + readOnly: + type: boolean + pdName: + type: string + partition: + type: integer + fsType: + type: string + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + properties: + generateName: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + clusterName: + type: string + resourceVersion: + type: string + annotations: + additionalProperties: + type: string type: object - type: array - matchFields: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array + selfLink: + type: string + creationTimestamp: + type: string + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string type: object - type: array - type: object - weight: - type: integer - type: object - type: array - requiredDuringSchedulingIgnoredDuringExecution: - properties: - nodeSelectorTerms: - items: - properties: - matchExpressions: - items: + ownerReferences: + items: + properties: + blockOwnerDeletion: + type: boolean + uid: + type: string + apiVersion: + type: string + name: + type: string + kind: + type: string + controller: + type: boolean + type: object + type: array + uid: + type: string + generation: + type: integer + name: + type: string + managedFields: + items: + properties: + time: + type: string + apiVersion: + type: string + fieldsV1: + type: object + fieldsType: + type: string + manager: + type: string + operation: + type: string + subresource: + type: string + type: object + type: array + namespace: + type: string + type: object + spec: + properties: + selector: properties: - key: - type: string - operator: - type: string - values: + matchExpressions: items: - type: string + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object type: array + matchLabels: + additionalProperties: + type: string + type: object type: object - type: array - matchFields: - items: + storageClassName: + type: string + dataSource: properties: - key: + name: type: string - operator: + kind: + type: string + apiGroup: 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 + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true 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 + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true 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: + type: object + dataSourceRef: properties: - key: + name: + type: string + kind: type: string - operator: + apiGroup: type: string - values: - items: - type: string - type: array type: object - type: array - matchLabels: - additionalProperties: + accessModes: + items: + type: string + type: array + volumeMode: 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: + volumeName: type: string - name: - type: string - optional: - type: boolean type: object type: object type: object - type: array - envFrom: - items: + scaleIO: properties: - configMapRef: + readOnly: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + gateway: + type: string + secretRef: properties: name: type: string - optional: - type: boolean type: object - prefix: + fsType: type: string - secretRef: + sslEnabled: + type: boolean + volumeName: + type: string + protectionDomain: + type: string + type: object + csi: + properties: + nodePublishSecretRef: properties: name: type: string - optional: - type: boolean type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + fsType: + type: string + driver: + type: string 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: + secret: + properties: + optional: + type: boolean + secretName: + type: string + items: + items: 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: + key: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + mode: + type: integer 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: array + defaultMode: type: integer - hostIP: + type: object + name: + type: string + vsphereVolume: + properties: + storagePolicyName: type: string - hostPort: - type: integer - name: + storagePolicyID: + type: string + volumePath: type: string - protocol: + fsType: 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: + gitRepo: + properties: + revision: + type: string + repository: + type: string + directory: + type: string + type: object + glusterfs: + properties: + path: + type: string + readOnly: + type: boolean + endpoints: + type: string + type: object + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + type: object + cinder: + properties: + readOnly: + type: boolean + secretRef: + properties: + name: 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: + type: object + fsType: + type: string + volumeID: + type: string + type: object + flocker: properties: - devicePath: + datasetUUID: type: string - name: + datasetName: type: string type: object - type: array - volumeMounts: - items: + quobyte: properties: - mountPath: + group: type: string - mountPropagation: + readOnly: + type: boolean + volume: type: string - name: + user: + type: string + registry: + type: string + tenant: + type: string + type: object + photonPersistentDisk: + properties: + pdID: + type: string + fsType: + type: string + type: object + persistentVolumeClaim: + properties: + readOnly: + type: boolean + claimName: type: string + type: object + awsElasticBlockStore: + properties: readOnly: type: boolean - subPath: + partition: + type: integer + fsType: type: string - subPathExpr: + volumeID: 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: + configMap: properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + defaultMode: + type: integer name: type: string - value: + type: object + storageos: + properties: + readOnly: + type: boolean + volumeNamespace: type: string - valueFrom: + secretRef: 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 + name: + type: string type: object + fsType: + type: string + volumeName: + type: string type: object - type: array - envFrom: - items: + portworxVolume: properties: - configMapRef: + readOnly: + type: boolean + fsType: + type: string + volumeID: + type: string + type: object + iscsi: + properties: + readOnly: + type: boolean + chapAuthSession: + type: boolean + lun: + type: integer + targetPortal: + type: string + iscsiInterface: + type: string + portals: + items: + type: string + type: array + initiatorName: + type: string + secretRef: properties: name: type: string - optional: - type: boolean type: object - prefix: + fsType: + type: string + iqn: + type: string + chapAuthDiscovery: + type: boolean + type: object + rbd: + properties: + readOnly: + type: boolean + pool: + type: string + keyring: + type: string + image: type: string secretRef: properties: name: type: string - optional: - type: boolean type: object + monitors: + items: + type: string + type: array + fsType: + type: string + user: + type: string 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: + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + type: object + downwardAPI: + properties: + items: + items: 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 + 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 + mode: + type: integer type: object - tcpSocket: + type: array + defaultMode: + type: integer + type: object + projected: + properties: + defaultMode: + type: integer + sources: + items: properties: - host: - type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + secret: + properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + name: + type: string + type: object + configMap: + properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + name: + type: string + type: object + serviceAccountToken: + properties: + path: + type: string + audience: + type: string + expirationSeconds: + type: integer + type: object + downwardAPI: + properties: + items: + items: + properties: + path: + type: string + 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 + mode: + type: integer + type: object + type: array + type: object type: object - type: object - type: object - livenessProbe: - properties: - exec: - properties: - command: - items: + type: array + type: object + azureDisk: + properties: + readOnly: + type: boolean + diskName: + type: string + cachingMode: + type: string + fsType: + type: string + kind: + type: string + diskURI: + type: string + type: object + cephfs: + properties: + path: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: 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: object + monitors: + items: type: string - port: - anyOf: + type: array + secretFile: + type: string + user: + type: string + type: object + emptyDir: + properties: + sizeLimit: + anyOf: - type: integer - type: string - x-kubernetes-int-or-string: true - scheme: + x-kubernetes-int-or-string: true + medium: + type: string + type: object + fc: + properties: + readOnly: + type: boolean + lun: + type: integer + wwids: + items: type: string - type: object - initialDelaySeconds: - type: integer - periodSeconds: - type: integer - successThreshold: - type: integer - tcpSocket: - properties: - host: + type: array + targetWWNs: + items: 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: + type: array + fsType: + type: string + type: object + type: object + type: array + restartPolicy: + type: string + terminationGracePeriodSeconds: + type: integer + setHostnameAsFQDN: + type: boolean + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + searches: + items: + type: string + type: array + options: items: properties: - containerPort: - type: integer - hostIP: + value: type: string - hostPort: - type: integer name: type: string - protocol: - type: string type: object type: array - readinessProbe: + type: object + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: 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 + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string type: object - resources: + sysctls: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + fsGroupChangePolicy: + type: string + seLinuxOptions: 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 + role: + type: string + type: + type: string + user: + type: string + level: + type: string type: object - securityContext: + fsGroup: + type: integer + supplementalGroups: + items: + type: integer + type: array + runAsUser: + type: integer + seccompProfile: 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: + type: string + localhostProfile: 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: + imagePullSecrets: + items: + properties: + name: type: string - type: array - env: - items: + type: object + type: array + subdomain: + type: string + serviceAccount: + type: string + activeDeadlineSeconds: + type: integer + priority: + type: integer + ephemeralContainers: + items: + properties: + lifecycle: properties: - name: - type: string - value: - type: string - valueFrom: + postStart: properties: - configMapKeyRef: + tcpSocket: properties: - key: - type: string - name: + host: type: string - optional: - type: boolean + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array type: object - fieldRef: + httpGet: properties: - apiVersion: + path: + type: string + scheme: type: string - fieldPath: + host: type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true type: object - resourceFieldRef: + type: object + preStop: + properties: + tcpSocket: properties: - containerName: + host: type: string - divisor: + port: anyOf: - - type: integer - - type: string + - type: integer + - type: string x-kubernetes-int-or-string: true - resource: - type: string type: object - secretKeyRef: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: properties: - key: + path: type: string - name: + scheme: type: string - optional: - type: boolean + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true type: object type: object type: object - type: array - envFrom: - items: + command: + items: + type: string + type: array + livenessProbe: properties: - configMapRef: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: properties: - name: + port: + type: integer + service: type: string - optional: - type: boolean type: object - prefix: - type: string - secretRef: + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: properties: - name: + host: 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: + port: + anyOf: - type: integer - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - type: object - tcpSocket: - properties: - host: + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: type: string - port: - anyOf: + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + 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: + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + targetContainerName: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: - type: integer - type: string - x-kubernetes-int-or-string: true - scheme: - type: string - type: object - tcpSocket: - properties: - host: + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: type: string - port: - anyOf: + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + 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: + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: 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: + value: type: string - type: object - initialDelaySeconds: - type: integer - periodSeconds: - type: integer - successThreshold: - type: integer - tcpSocket: - properties: - host: + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + 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: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + type: object + name: 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: + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: properties: - containerPort: + periodSeconds: type: integer - hostIP: - type: string - hostPort: + failureThreshold: type: integer - name: - type: string - protocol: - type: string - type: object - type: array - readinessProbe: - properties: - exec: - properties: - command: - items: + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: 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: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: type: string - type: array - drop: - items: + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: 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: + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: properties: - level: - type: string - role: + containerPort: + type: integer + hostPort: + type: integer + name: type: string - type: + protocol: type: string - user: + hostIP: type: string type: object - seccompProfile: + type: array + workingDir: + type: string + envFrom: + items: properties: - localhostProfile: - type: string - type: + prefix: type: string + configMapRef: + properties: + optional: + type: boolean + name: + type: string + type: object + secretRef: + properties: + optional: + type: boolean + name: + type: string + type: object type: object - windowsOptions: + type: array + volumeMounts: + items: properties: - gmsaCredentialSpec: - type: string - gmsaCredentialSpecName: - type: string - hostProcess: + readOnly: 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: + subPathExpr: type: string - type: object - httpGet: - properties: - host: + mountPath: type: string - httpHeaders: - items: - properties: - name: - type: string - value: - type: string - type: object - type: array - path: + mountPropagation: type: string - port: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true - scheme: + subPath: type: string - type: object - initialDelaySeconds: - type: integer - periodSeconds: - type: integer - successThreshold: - type: integer - tcpSocket: - properties: - host: + name: 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: + type: array + securityContext: properties: - mountPath: - type: string - mountPropagation: - type: string - name: - type: string - readOnly: + runAsGroup: + type: integer + runAsNonRoot: 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: + windowsOptions: properties: - key: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: type: string - operator: + runAsUserName: type: string - values: + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: 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: + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: 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: + seccompProfile: properties: - key: + type: type: string - mode: - type: integer - path: + localhostProfile: type: string type: object - type: array - name: - type: string - optional: - type: boolean - type: object - csi: - properties: - driver: - type: string - fsType: - type: string - nodePublishSecretRef: + type: object + name: + type: string + 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 + imagePullPolicy: + type: string + volumeDevices: + items: properties: + devicePath: + type: string name: type: string type: object - readOnly: - type: boolean - volumeAttributes: - additionalProperties: - type: string - type: object - type: object - downwardAPI: - properties: - defaultMode: - type: integer - items: - items: + type: array + type: object + type: array + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + lifecycle: + properties: + postStart: properties: - fieldRef: + tcpSocket: properties: - apiVersion: - type: string - fieldPath: + host: type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true type: object - mode: - type: integer - path: - type: string - resourceFieldRef: + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: properties: - containerName: + path: type: string - divisor: + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: anyOf: - - type: integer - - type: string + - 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: + preStop: + properties: + tcpSocket: + properties: + host: type: string - type: object - clusterName: - type: string - creationTimestamp: - type: string - deletionGracePeriodSeconds: - type: integer - deletionTimestamp: - type: string - finalizers: - items: + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: type: string - type: array - generateName: - type: string - generation: - type: integer - labels: - additionalProperties: + scheme: 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: + host: 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 + httpHeaders: + items: + properties: + value: + type: string + name: + type: string type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + command: + items: + type: string + type: array + livenessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string type: object - selector: + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: properties: - matchExpressions: - items: - properties: - key: - type: string - operator: - type: string - values: - items: - type: string - type: array - type: object - type: array - matchLabels: - additionalProperties: - type: string + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + 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: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + type: object + name: + type: string + type: object + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: + properties: + containerPort: + type: integer + hostPort: + type: integer + name: + type: string + protocol: + type: string + hostIP: + type: string + type: object + type: array + workingDir: + type: string + envFrom: + items: + properties: + prefix: + type: string + configMapRef: + properties: + optional: + type: boolean + name: + type: string + type: object + secretRef: + properties: + optional: + type: boolean + name: + type: string + type: object + type: object + type: array + volumeMounts: + items: + properties: + readOnly: + type: boolean + subPathExpr: + type: string + mountPath: + type: string + mountPropagation: + type: string + subPath: + type: string + name: + type: string + type: object + type: array + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: + type: string + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + name: + type: string + 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 + imagePullPolicy: + type: string + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + type: object + type: array + initContainers: + items: + properties: + lifecycle: + properties: + postStart: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + command: + items: + type: string + type: array + livenessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + 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: + optional: + type: boolean + key: + type: string + name: + type: string type: object - storageClassName: + type: object + name: + type: string + type: object + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: type: string - volumeMode: + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: + properties: + containerPort: + type: integer + hostPort: + type: integer + name: + type: string + protocol: + type: string + hostIP: + type: string + type: object + type: array + workingDir: + type: string + envFrom: + items: + properties: + prefix: + type: string + configMapRef: + properties: + optional: + type: boolean + name: type: string - volumeName: + type: object + secretRef: + properties: + optional: + type: boolean + name: 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: + type: array + volumeMounts: + items: properties: + readOnly: + type: boolean + subPathExpr: + type: string + mountPath: + type: string + mountPropagation: + type: string + subPath: + type: string 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: array + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: type: string - type: array - readOnly: - type: boolean - secretRef: + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + name: + type: string + 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 + imagePullPolicy: + type: string + volumeDevices: + items: properties: + devicePath: + type: string name: type: string type: object - targetPortal: - type: string - type: object + type: array + type: object + type: array + priorityClassName: + type: string + tolerations: + items: + properties: + key: + type: string + operator: + type: string + tolerationSeconds: + type: integer + value: + type: string + effect: + type: string + type: object + type: array + hostPID: + type: boolean + os: + properties: 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: + type: object + serviceAccountName: + type: string + shareProcessNamespace: + type: boolean + hostNetwork: + type: boolean + hostname: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + enableServiceLinks: + type: boolean + affinity: + properties: + podAntiAffinity: properties: - fsType: - type: string - readOnly: - type: boolean - volumeID: - type: string + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + weight: + type: integer + type: object + type: array type: object - projected: + nodeAffinity: properties: - defaultMode: - type: integer - sources: + preferredDuringSchedulingIgnoredDuringExecution: items: properties: - configMap: + weight: + type: integer + preference: properties: - items: + matchFields: items: properties: key: type: string - mode: - type: integer - path: + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: type: string type: object type: array - name: - type: string - optional: - type: boolean type: object - downwardAPI: + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: properties: - items: + matchFields: items: properties: - fieldRef: - properties: - apiVersion: - type: string - fieldPath: - type: string - type: object - mode: - type: integer - path: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: 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: + type: array + type: object + type: object + podAffinity: + properties: + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: properties: - items: + matchExpressions: items: properties: key: type: string - mode: - type: integer - path: + values: + items: + type: string + type: array + operator: type: string type: object type: array - name: - type: string - optional: - type: boolean + matchLabels: + additionalProperties: + type: string + type: object type: object - serviceAccountToken: + namespaceSelector: properties: - audience: - type: string - expirationSeconds: - type: integer - path: - type: string + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object 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: + preferredDuringSchedulingIgnoredDuringExecution: items: properties: - key: - type: string - mode: + podAffinityTerm: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + weight: 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: object + readinessGates: + items: + properties: + conditionType: + type: string + type: object + type: array + dnsPolicy: + type: string + hostIPC: + type: boolean + topologySpreadConstraints: + items: + properties: + topologyKey: + type: string + maxSkew: + type: integer + whenUnsatisfiable: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: 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: object + type: object + type: array + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true type: object - type: array + schedulerName: + type: string + nodeName: + type: string + preemptionPolicy: + type: string + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + runtimeClassName: + type: string + type: object + type: object + type: object + 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 + 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 + 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: + secret: + properties: + optional: + type: boolean + key: + type: string + name: + type: string type: object + value: + type: string + name: + type: string type: object - type: object - type: object - status: - properties: - conditions: - items: + type: array + db: + description: In this section you can find all properties related to + connect to a database. properties: - message: + passwordSecret: + description: The reference to a secret holding the password of + the database user. + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + usernameSecret: + description: The reference to a secret holding the username of + the database user. + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + 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 - status: - type: boolean - type: + 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 + 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 + poolInitialSize: + description: The initial size of the connection pool. + type: integer + poolMaxSize: + description: The maximum size of the connection pool. + type: integer + vendor: + description: The database vendor. + type: string + 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 + poolMinSize: + description: The minimal size of the connection pool. + type: integer 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 + 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 + type: object + status: + properties: + conditions: + items: + properties: + status: + type: boolean + type: + type: string + message: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/ProvisionerManagerTestCase.java b/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/ProvisionerManagerTestCase.java index 06bcb31a..1c5383e0 100644 --- a/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/ProvisionerManagerTestCase.java +++ b/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/ProvisionerManagerTestCase.java @@ -18,20 +18,20 @@ import static org.mockito.Mockito.mock; import org.jboss.intersmash.tools.application.Application; -import org.jboss.intersmash.tools.application.openshift.ActiveMQOperatorApplication; import org.jboss.intersmash.tools.application.openshift.BootableJarOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.KafkaOperatorApplication; import org.jboss.intersmash.tools.application.openshift.MysqlImageOpenShiftApplication; import org.jboss.intersmash.tools.application.openshift.PostgreSQLImageOpenShiftApplication; import org.jboss.intersmash.tools.application.openshift.WildflyImageOpenShiftApplication; -import org.jboss.intersmash.tools.application.openshift.WildflyOperatorApplication; -import org.jboss.intersmash.tools.provision.openshift.ActiveMQOperatorProvisioner; -import org.jboss.intersmash.tools.provision.openshift.KafkaOperatorProvisioner; +import org.jboss.intersmash.tools.application.operator.ActiveMQOperatorApplication; +import org.jboss.intersmash.tools.application.operator.KafkaOperatorApplication; +import org.jboss.intersmash.tools.application.operator.WildflyOperatorApplication; +import org.jboss.intersmash.tools.provision.openshift.ActiveMQOpenShiftOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.KafkaOpenShiftOperatorProvisioner; import org.jboss.intersmash.tools.provision.openshift.MysqlImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.PostgreSQLImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.WildflyBootableJarImageOpenShiftProvisioner; import org.jboss.intersmash.tools.provision.openshift.WildflyImageOpenShiftProvisioner; -import org.jboss.intersmash.tools.provision.openshift.WildflyOperatorProvisioner; +import org.jboss.intersmash.tools.provision.openshift.WildflyOpenShiftOperatorProvisioner; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -103,7 +103,7 @@ public void wildflyOperatorProvisioner() { application = mock(WildflyOperatorApplication.class); Provisioner actual = ProvisionerManager.getProvisioner(application); - Assertions.assertEquals(WildflyOperatorProvisioner.class, actual.getClass()); + Assertions.assertEquals(WildflyOpenShiftOperatorProvisioner.class, actual.getClass()); } /** @@ -114,7 +114,7 @@ public void activeMQOperatorProvisioner() { application = mock(ActiveMQOperatorApplication.class); Provisioner actual = ProvisionerManager.getProvisioner(application); - Assertions.assertEquals(ActiveMQOperatorProvisioner.class, actual.getClass()); + Assertions.assertEquals(ActiveMQOpenShiftOperatorProvisioner.class, actual.getClass()); } /** @@ -125,7 +125,7 @@ public void kafkaOperatorProvisioner() { application = mock(KafkaOperatorApplication.class); Provisioner actual = ProvisionerManager.getProvisioner(application); - Assertions.assertEquals(KafkaOperatorProvisioner.class, actual.getClass()); + Assertions.assertEquals(KafkaOpenShiftOperatorProvisioner.class, actual.getClass()); } @Test diff --git a/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OpenShiftResourceTestCase.java b/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OpenShiftResourceTestCase.java index f5efc599..2418a924 100644 --- a/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OpenShiftResourceTestCase.java +++ b/tools/intersmash-tools-provisioners/src/test/java/org/jboss/intersmash/tools/provision/openshift/operator/resources/OpenShiftResourceTestCase.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import cz.xtf.core.config.OpenShiftConfig; + /** * Verify the functionality provided by {@link OpenShiftResource} interface. */ @@ -32,12 +34,12 @@ public class OpenShiftResourceTestCase { @Test public void writeReadEqualsTest() throws IOException { // write test - File yaml = OperatorGroup.SINGLE_NAMESPACE.save(); + File yaml = new OperatorGroup(OpenShiftConfig.namespace()).save(); // read test OpenShiftResource testGroup = new OperatorGroup(); testGroup.load(yaml); // - Assertions.assertEquals(OperatorGroup.SINGLE_NAMESPACE, testGroup, + Assertions.assertEquals(new OperatorGroup(OpenShiftConfig.namespace()), testGroup, "OpenShift resource (OperatorGroup) does not equal after serialization into yaml file and deserialization back to an object."); } } diff --git a/tools/pom.xml b/tools/pom.xml index c164e84e..19c9ee5f 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -14,6 +14,7 @@ Intersmash Core: Aggregator + intersmash-kubernetes-client intersmash-tools-core intersmash-tools-provisioners