diff --git a/.circleci/config.yml b/.circleci/config.yml index b715e2f..47e0035 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -70,7 +70,7 @@ jobs: timeout 300s bash -c 'while [[ $(oc get pod -o json | jq ".items[] | select(.metadata.name | contains(\"build\")) | .status " | jq -rs "sort_by(.startTme) | last | .phase") == "Running" ]]; do sleep 20; done; echo ""' # launch the tests without deploying the application - ./mvnw -s .circleci/settings.xml verify -Dfabric8.skip=true -Popenshift,openshift-it + ./mvnw -s .circleci/settings.xml verify -Popenshift,openshift-it -Dunmanaged-test=true - run: name: Cleanup s2i build @@ -82,10 +82,10 @@ jobs: echo "Cleaned up s2i build" - run: - name: run tests against fmp build + name: run tests against Dekorate build command: | - oc new-project fmp - .circleci/run_tests_with_fmp.sh -s .circleci/settings.xml + oc new-project dekorate + .circleci/run_tests_with_dekorate.sh -s .circleci/settings.xml no_output_timeout: 3600 workflows: diff --git a/.circleci/run_tests_with_fmp.sh b/.circleci/run_tests_with_dekorate.sh similarity index 91% rename from .circleci/run_tests_with_fmp.sh rename to .circleci/run_tests_with_dekorate.sh index 0e5b019..97acab4 100755 --- a/.circleci/run_tests_with_fmp.sh +++ b/.circleci/run_tests_with_dekorate.sh @@ -14,6 +14,8 @@ PROJECT_ABSOLUTE_DIR=$(dirname ${SCRIPT_ABSOLUTE_DIR}) pushd ${PROJECT_ABSOLUTE_DIR} > /dev/null +oc create -f .openshiftio/resource.configmap.yaml + ./mvnw clean verify -Popenshift,openshift-it "$@" popd > /dev/null diff --git a/pom.xml b/pom.xml index 88b5c15..7a2441f 100644 --- a/pom.xml +++ b/pom.xml @@ -29,17 +29,9 @@ <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> - <arquillian.version>1.4.0.Final</arquillian.version> - <arquillian-cube.version>1.18.2</arquillian-cube.version> - <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version> - <maven-failsafe-plugin.version>2.19.1</maven-failsafe-plugin.version> - <junit.version>4.13.2</junit.version> - <ubi.version>1.3</ubi.version> - <openshift-client.version>3.1.8</openshift-client.version> + <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version> + <maven-failsafe-plugin.version>2.22.2</maven-failsafe-plugin.version> <spring-boot.version>2.4.9</spring-boot.version> - <fabric8.generator.from> - registry.access.redhat.com/ubi8/openjdk-8:${ubi.version} - </fabric8.generator.from> </properties> <licenses> <license> @@ -73,62 +65,34 @@ <url>https://maven.repository.redhat.com/earlyaccess/all/</url> </pluginRepository> </pluginRepositories> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.jboss.arquillian</groupId> - <artifactId>arquillian-bom</artifactId> - <version>${arquillian.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - <dependency> - <groupId>org.arquillian.cube</groupId> - <artifactId>arquillian-cube-bom</artifactId> - <version>${arquillian-cube.version}</version> - <scope>import</scope> - <type>pom</type> - </dependency> - <dependency> - <groupId>io.fabric8</groupId> - <artifactId>openshift-client</artifactId> - <version>${openshift-client.version}</version> - </dependency> - </dependencies> - </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> - <dependency> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId> - </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.jboss.arquillian.junit</groupId> - <artifactId>arquillian-junit-standalone</artifactId> - <scope>test</scope> + <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> - <groupId>org.arquillian.cube</groupId> - <artifactId>arquillian-cube-openshift</artifactId> - <scope>test</scope> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId> + <!-- We need to exclude Kubernetes Client because it's in conflict with Dekorate dependency --> <exclusions> <exclusion> - <groupId>io.undertow</groupId> - <artifactId>undertow-core</artifactId> + <groupId>io.fabric8</groupId> + <artifactId>kubernetes-client</artifactId> </exclusion> </exclusions> </dependency> <dependency> - <groupId>io.fabric8</groupId> - <artifactId>openshift-client</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.dekorate</groupId> + <artifactId>openshift-junit</artifactId> <scope>test</scope> </dependency> <dependency> @@ -146,12 +110,6 @@ <artifactId>assertj-core</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>${junit.version}</version> - <scope>test</scope> - </dependency> </dependencies> <build> <resources> @@ -193,60 +151,12 @@ <profiles> <profile> <id>openshift</id> - <build> - <plugins> - <plugin> - <groupId>io.fabric8</groupId> - <artifactId>fabric8-maven-plugin</artifactId> - <version>4.4.0</version> - <executions> - <execution> - <id>fmp</id> - <goals> - <goal>resource</goal> - <goal>build</goal> - </goals> - </execution> - </executions> - <configuration> - <resources> - <labels> - <all> - <property> - <name>app.kubernetes.io/part-of</name> - <value>configmap-example</value> - </property> - <property> - <name>app.kubernetes.io/name</name> - <value>configmap-service</value> - </property> - <property> - <name>app.kubernetes.io/component</name> - <value>frontend</value> - </property> - <property> - <name>app.openshift.io/runtime</name> - <value>rh-spring-boot</value> - </property> - <property> - <name>app.openshift.io/runtime-version</name> - <value>${spring-boot.version}</value> - </property> - </all> - </labels> - <annotations> - <all> - <property> - <name>app.kubernetes.io/vcs-uri</name> - <value>git@github.com:snowdrop/configmap-example.git</value> - </property> - </all> - </annotations> - </resources> - </configuration> - </plugin> - </plugins> - </build> + <dependencies> + <dependency> + <groupId>io.dekorate</groupId> + <artifactId>openshift-spring-starter</artifactId> + </dependency> + </dependencies> </profile> <profile> <id>openshift-it</id> diff --git a/src/main/fabric8/route.yml b/src/main/fabric8/route.yml deleted file mode 100644 index 2eabef5..0000000 --- a/src/main/fabric8/route.yml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: route.openshift.io/v1 -kind: Route -metadata: - annotations: - app.kubernetes.io/vcs-uri: git@github.com:snowdrop/configmap-example.git - labels: - app.kubernetes.io/part-of: configmap-example - app.kubernetes.io/name: configmap-service - app.kubernetes.io/component: frontend - app.openshift.io/runtime: rh-spring-boot - app.openshift.io/runtime-version: ${spring-boot.version} -spec: - port: - targetPort: 8080 - to: - kind: Service - name: ${project.artifactId} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..984ea22 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# Dekorate +dekorate.openshift.expose=true +dekorate.s2i.builder-image=registry.access.redhat.com/ubi8/openjdk-8:1.3 diff --git a/src/test/java/dev/snowdrop/example/AbstractOpenShiftIT.java b/src/test/java/dev/snowdrop/example/AbstractOpenShiftIT.java new file mode 100644 index 0000000..dbfb60c --- /dev/null +++ b/src/test/java/dev/snowdrop/example/AbstractOpenShiftIT.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 Red Hat, Inc, and individual contributors. + * + * 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 dev.snowdrop.example; + +import static io.restassured.RestAssured.given; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.core.Is.is; + +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.openshift.client.OpenShiftClient; + +public abstract class AbstractOpenShiftIT { + + private static final String CONFIG_MAP_NAME = "app-config"; + private static final String GREETING_NAME = System.getProperty("app.name"); + private static final String GREETING_PATH = "api/greeting"; + + @Test + public void testConfigMapLifecycle() { + // Endpoint should say Hello at the beginning as ConfigMap must have been loaded before running the test + verifyEndpoint("Hello"); + + // Verify the name parameter is properly replaced in the greetings sentence. + given().param("name", "John") + .when() + .get(baseURL() + GREETING_PATH) + .then() + .statusCode(200) + .body("content", is("Hello John from a ConfigMap!")); + + // Verify the app is updated when the config map changes + updateConfigMap(); + rolloutChanges(); + waitForApp(); + verifyEndpoint("Bonjour"); + + // Verify the app is updated when the config map is deleted + deleteConfigMap(); + rolloutChanges(); + await().atMost(5, TimeUnit.MINUTES) + .catchUncaughtExceptions() + .untilAsserted(() -> given().get(baseURL() + GREETING_PATH) + .then().statusCode(500)); + } + + protected abstract String baseURL(); + protected abstract KubernetesClient kubernetesClient(); + + private void verifyEndpoint(final String greeting) { + given().get(baseURL() + GREETING_PATH) + .then() + .statusCode(200) + .body("content", is(String.format("%s World from a ConfigMap!", greeting))); + } + + private void updateConfigMap() { + kubernetesClient().configMaps() + .withName(CONFIG_MAP_NAME) + .edit(c -> new ConfigMapBuilder(c) + .addToData("application.yml", "greeting.message: Bonjour %s from a ConfigMap!") + .build()); + } + + private void deleteConfigMap() { + kubernetesClient().configMaps() + .withName(CONFIG_MAP_NAME) + .delete(); + } + + private void rolloutChanges() { + scale(0); + scale(1); + } + + private void scale(final int replicas) { + OpenShiftClient ocClient = kubernetesClient().adapt(OpenShiftClient.class); + ocClient.deploymentConfigs().withName(GREETING_NAME).scale(replicas); + + await().atMost(5, TimeUnit.MINUTES) + .until(() -> + ocClient.deploymentConfigs().withName(GREETING_NAME) + .get() + .getStatus() + .getAvailableReplicas() == replicas); + } + + private void waitForApp() { + await().atMost(5, TimeUnit.MINUTES) + .ignoreExceptions() + .untilAsserted( + () -> given() + .get(baseURL() + GREETING_PATH) + .then().statusCode(200) + ); + } + +} diff --git a/src/test/java/dev/snowdrop/example/ExampleApplicationTest.java b/src/test/java/dev/snowdrop/example/LocalTest.java similarity index 85% rename from src/test/java/dev/snowdrop/example/ExampleApplicationTest.java rename to src/test/java/dev/snowdrop/example/LocalTest.java index fba2816..0477e7b 100644 --- a/src/test/java/dev/snowdrop/example/ExampleApplicationTest.java +++ b/src/test/java/dev/snowdrop/example/LocalTest.java @@ -20,18 +20,21 @@ import static org.hamcrest.core.Is.is; import dev.snowdrop.example.service.GreetingProperties; -import org.junit.Test; -import org.junit.runner.RunWith; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class ExampleApplicationTest { +public class LocalTest { + + private static final String GREETING_PATH = "api/greeting"; - protected static final String GREETING_PATH = "api/greeting"; @Value("${local.server.port}") private int port; diff --git a/src/test/java/dev/snowdrop/example/ManagedOpenShiftIT.java b/src/test/java/dev/snowdrop/example/ManagedOpenShiftIT.java new file mode 100644 index 0000000..41f1033 --- /dev/null +++ b/src/test/java/dev/snowdrop/example/ManagedOpenShiftIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Red Hat, Inc, and individual contributors. + * + * 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 dev.snowdrop.example; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; + +import io.dekorate.testing.annotation.Inject; +import io.dekorate.testing.openshift.annotation.OpenshiftIntegrationTest; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.openshift.api.model.Route; +import io.fabric8.openshift.client.OpenShiftClient; + +@DisabledIfSystemProperty(named = "unmanaged-test", matches = "true") +@OpenshiftIntegrationTest +public class ManagedOpenShiftIT extends AbstractOpenShiftIT { + @Inject + KubernetesClient kubernetesClient; + + String baseUrl; + + @BeforeEach + public void setup() throws MalformedURLException { + // TODO: In Dekorate 1.7, we can inject Routes directly, so we won't need to do this: + Route route = kubernetesClient.adapt(OpenShiftClient.class).routes().withName("configmap").get(); + String protocol = route.getSpec().getTls() == null ? "http" : "https"; + int port = "http".equals(protocol) ? 80 : 443; + URL url = new URL(protocol, route.getSpec().getHost(), port, route.getSpec().getPath()); + baseUrl = url.toString(); + } + + @Override + protected String baseURL() { + return baseUrl; + } + + @Override + protected KubernetesClient kubernetesClient() { + return kubernetesClient; + } +} diff --git a/src/test/java/dev/snowdrop/example/OpenShiftIT.java b/src/test/java/dev/snowdrop/example/OpenShiftIT.java deleted file mode 100644 index c412fe8..0000000 --- a/src/test/java/dev/snowdrop/example/OpenShiftIT.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2016-2017 Red Hat, Inc, and individual contributors. - * - * 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 dev.snowdrop.example; - -import static io.restassured.RestAssured.given; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.core.Is.is; - -import io.fabric8.openshift.client.OpenShiftClient; -import java.net.URL; -import java.util.concurrent.TimeUnit; -import org.arquillian.cube.kubernetes.api.Session; -import org.arquillian.cube.openshift.impl.enricher.AwaitRoute; -import org.arquillian.cube.openshift.impl.enricher.RouteURL; -import org.jboss.arquillian.junit.Arquillian; -import org.jboss.arquillian.test.api.ArquillianResource; -import org.junit.Before; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.MethodSorters; - -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -@RunWith(Arquillian.class) -public class OpenShiftIT { - - private static final String CONFIG_MAP_NAME = "app-config"; - private static final String GREETING_NAME = System.getProperty("app.name"); - protected static final String GREETING_PATH = "api/greeting"; - - @ArquillianResource - private OpenShiftClient oc; - - @ArquillianResource - private Session session; - - @AwaitRoute(path = "/api/greeting") - @RouteURL("${app.name}") - private URL greetingServiceBase; - - @Before - public void setup() throws Exception { - waitForApp(); - } - - @Test - public void testAGreetingEndpoint() { - verifyEndpoint("Hello"); - } - - @Test - public void testBGreetingEndpointWithNameParameter() { - given() - .baseUri(greetingServiceBase.toString()) - .param("name", "John") - .when() - .get(GREETING_PATH) - .then() - .statusCode(200) - .body("content", is("Hello John from a ConfigMap!")); - } - - @Test - public void testCConfigMapUpdate() { - verifyEndpoint("Hello"); - updateConfigMap(); - rolloutChanges(); - waitForApp(); - verifyEndpoint("Bonjour"); - } - - @Test - public void testDConfigMapNotPresent() { - verifyEndpoint("Bonjour"); - deleteConfigMap(); - rolloutChanges(); - await().atMost(5, TimeUnit.MINUTES) - .catchUncaughtExceptions() - .until( - () -> given() - .baseUri(greetingServiceBase.toString()) - .get(GREETING_PATH) - .getStatusCode() == 500 - ); - } - - private void verifyEndpoint(final String greeting) { - given().baseUri(greetingServiceBase.toString()) - .get(GREETING_PATH) - .then() - .statusCode(200) - .body("content", is(String.format("%s World from a ConfigMap!", greeting))); - } - - private void updateConfigMap() { - oc.configMaps() - .withName(CONFIG_MAP_NAME) - .edit() - .addToData("application.yml", "greeting.message: Bonjour %s from a ConfigMap!") - .done(); - } - - private void deleteConfigMap() { - oc.configMaps() - .withName(CONFIG_MAP_NAME) - .delete(); - } - - private void rolloutChanges() { - scale(0); - scale(1); - } - - private void scale(final int replicas) { - oc.deploymentConfigs() - .inNamespace(session.getNamespace()) - .withName(GREETING_NAME) - .scale(replicas); - - await().atMost(5, TimeUnit.MINUTES) - .until(() -> { - return oc - .deploymentConfigs() - .inNamespace(session.getNamespace()) - .withName(GREETING_NAME) - .get() - .getStatus() - .getAvailableReplicas() == replicas; - }); - } - - private void waitForApp() { - await().atMost(5, TimeUnit.MINUTES) - .until( - () -> given() - .baseUri(greetingServiceBase.toString()) - .get(GREETING_PATH) - .getStatusCode() == 200 - ); - } - -} diff --git a/src/test/java/dev/snowdrop/example/UnmanagedOpenShiftIT.java b/src/test/java/dev/snowdrop/example/UnmanagedOpenShiftIT.java new file mode 100644 index 0000000..ff336f3 --- /dev/null +++ b/src/test/java/dev/snowdrop/example/UnmanagedOpenShiftIT.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Red Hat, Inc, and individual contributors. + * + * 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 dev.snowdrop.example; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + +import io.dekorate.testing.annotation.Inject; +import io.dekorate.testing.openshift.annotation.OpenshiftIntegrationTest; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.openshift.api.model.Route; +import io.fabric8.openshift.client.OpenShiftClient; + +@EnabledIfSystemProperty(named = "unmanaged-test", matches = "true") +@OpenshiftIntegrationTest(deployEnabled = false, buildEnabled = false, pushEnabled = false) +public class UnmanagedOpenShiftIT extends AbstractOpenShiftIT { + @Inject + KubernetesClient kubernetesClient; + + String baseUrl; + + @BeforeEach + public void setup() throws MalformedURLException { + // TODO: In Dekorate 1.7, we can inject Routes directly, so we won't need to do this: + Route route = kubernetesClient.adapt(OpenShiftClient.class).routes().withName("configmap").get(); + String protocol = route.getSpec().getTls() == null ? "http" : "https"; + int port = "http".equals(protocol) ? 80 : 443; + URL url = new URL(protocol, route.getSpec().getHost(), port, "/"); + baseUrl = url.toString(); + } + + @Override + protected String baseURL() { + return baseUrl; + } + + @Override + protected KubernetesClient kubernetesClient() { + return kubernetesClient; + } +} diff --git a/src/test/resources/arquillian.xml b/src/test/resources/arquillian.xml deleted file mode 100644 index fda3f9d..0000000 --- a/src/test/resources/arquillian.xml +++ /dev/null @@ -1,12 +0,0 @@ -<arquillian xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xmlns="http://jboss.org/schema/arquillian" - xsi:schemaLocation="http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd"> - - <extension qualifier="openshift"> - <property name="namespace.use.current">true</property> - <property name="env.init.enabled">true</property> - <property name="enableImageStreamDetection">false</property> - <property name="env.dependencies">file://${basedir}/target/test-classes/test-configmap.yml</property> - </extension> - -</arquillian>