diff --git a/examples/database-postgresql/pom.xml b/examples/database-postgresql/pom.xml
index c6632a662..c8487a3cf 100644
--- a/examples/database-postgresql/pom.xml
+++ b/examples/database-postgresql/pom.xml
@@ -46,6 +46,11 @@
quarkus-test-openshift
test
+
+ io.quarkus.qe
+ quarkus-test-kubernetes
+ test
+
diff --git a/examples/database-postgresql/src/test/java/io/quarkus/qe/database/postgresql/KubernetesPostgresqlIT.java b/examples/database-postgresql/src/test/java/io/quarkus/qe/database/postgresql/KubernetesPostgresqlIT.java
new file mode 100644
index 000000000..14d437632
--- /dev/null
+++ b/examples/database-postgresql/src/test/java/io/quarkus/qe/database/postgresql/KubernetesPostgresqlIT.java
@@ -0,0 +1,28 @@
+package io.quarkus.qe.database.postgresql;
+
+import io.quarkus.test.bootstrap.PostgresqlService;
+import io.quarkus.test.bootstrap.RestService;
+import io.quarkus.test.scenarios.KubernetesScenario;
+import io.quarkus.test.services.Container;
+import io.quarkus.test.services.QuarkusApplication;
+
+@KubernetesScenario
+public class KubernetesPostgresqlIT extends AbstractSqlDatabaseIT {
+
+ private static final int POSTGRESQL_PORT = 5432;
+
+ @Container(image = "${postgresql.image}", port = POSTGRESQL_PORT, expectedLog = "is ready")
+ static PostgresqlService database = new PostgresqlService()
+ .withProperty("PGDATA", "/tmp/psql");
+
+ @QuarkusApplication
+ static RestService app = new RestService()
+ .withProperty("quarkus.datasource.username", database.getUser())
+ .withProperty("quarkus.datasource.password", database.getPassword())
+ .withProperty("quarkus.datasource.jdbc.url", database::getJdbcUrl);
+
+ @Override
+ protected RestService getApp() {
+ return app;
+ }
+}
diff --git a/examples/pingpong/src/test/java/io/quarkus/qe/KubernetesUsingExtensionPingPongResourceIT.java b/examples/pingpong/src/test/java/io/quarkus/qe/KubernetesUsingExtensionPingPongResourceIT.java
index 7de45a394..f76ebd9d9 100644
--- a/examples/pingpong/src/test/java/io/quarkus/qe/KubernetesUsingExtensionPingPongResourceIT.java
+++ b/examples/pingpong/src/test/java/io/quarkus/qe/KubernetesUsingExtensionPingPongResourceIT.java
@@ -11,8 +11,7 @@
public class KubernetesUsingExtensionPingPongResourceIT {
@QuarkusApplication
- static final RestService pingpong = new RestService()
- .withProperty("quarkus.kubernetes.service-type", "LoadBalancer");
+ static final RestService pingpong = new RestService();
@Test
public void shouldPingPongIsUpAndRunning() {
diff --git a/pom.xml b/pom.xml
index 83c50e08c..a0be16f39 100644
--- a/pom.xml
+++ b/pom.xml
@@ -501,6 +501,23 @@
**/Kubernetes*IT.java
no
+
+
+
+ maven-failsafe-plugin
+
+
+
+
+
+ true
+
+
+
+
+
+
+
validate-format
diff --git a/quarkus-test-kubernetes/pom.xml b/quarkus-test-kubernetes/pom.xml
index ce2c3d075..620de64c4 100644
--- a/quarkus-test-kubernetes/pom.xml
+++ b/quarkus-test-kubernetes/pom.xml
@@ -19,7 +19,14 @@
io.fabric8
- openshift-client
+ kubernetes-client
+
+
+
+ com.squareup.okhttp3
+ okhttp
+
+
io.rest-assured
diff --git a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/bootstrap/inject/KubectlClient.java b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/bootstrap/inject/KubectlClient.java
index 7243229ac..ae6e38bca 100644
--- a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/bootstrap/inject/KubectlClient.java
+++ b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/bootstrap/inject/KubectlClient.java
@@ -12,8 +12,9 @@
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
@@ -24,6 +25,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Stream;
@@ -40,11 +42,13 @@
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
import io.fabric8.kubernetes.api.model.apps.Deployment;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.dsl.NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable;
+import io.fabric8.kubernetes.client.dsl.NonDeletingOperation;
+import io.fabric8.kubernetes.client.impl.KubernetesClientImpl;
import io.fabric8.kubernetes.client.utils.Serialization;
-import io.fabric8.openshift.client.DefaultOpenShiftClient;
-import io.fabric8.openshift.client.NamespacedOpenShiftClient;
-import io.fabric8.openshift.client.OpenShiftConfig;
-import io.fabric8.openshift.client.OpenShiftConfigBuilder;
import io.quarkus.test.bootstrap.Service;
import io.quarkus.test.configuration.PropertyLookup;
import io.quarkus.test.logging.Log;
@@ -58,29 +62,37 @@ public final class KubectlClient {
public static final String LABEL_SCENARIO_ID = "scenarioId";
public static final PropertyLookup ENABLED_EPHEMERAL_NAMESPACES = new PropertyLookup(
"ts.kubernetes.ephemeral.namespaces.enabled", Boolean.TRUE.toString());
-
private static final String RESOURCE_MNT_FOLDER = "/resource";
private static final int NAMESPACE_NAME_SIZE = 10;
private static final int NAMESPACE_CREATION_RETRIES = 5;
+ private static final int DEPLOYMENT_CREATION_TIMEOUT = 30;
+
private static final String KUBECTL = "kubectl";
private static final int HTTP_PORT_DEFAULT = 80;
-
private final String currentNamespace;
- private final DefaultOpenShiftClient masterClient;
- private final NamespacedOpenShiftClient client;
+ private final KubernetesClientImpl client;
private final String scenarioId;
private KubectlClient(String scenarioUniqueName) {
this.scenarioId = scenarioUniqueName;
- String activeNamespace = new DefaultOpenShiftClient().getNamespace();
- currentNamespace = ENABLED_EPHEMERAL_NAMESPACES.getAsBoolean() ? createNamespace() : activeNamespace;
- OpenShiftConfig config = new OpenShiftConfigBuilder().withTrustCerts(true).withNamespace(currentNamespace).build();
- masterClient = new DefaultOpenShiftClient(config);
- client = masterClient.inNamespace(currentNamespace);
+ if (ENABLED_EPHEMERAL_NAMESPACES.getAsBoolean()) {
+ currentNamespace = createNamespace();
+ Config config = new ConfigBuilder().withTrustCerts(true).withNamespace(currentNamespace).build();
+ client = createClient(config);
+ } else {
+ Config config = new ConfigBuilder().withTrustCerts(true).build();
+ client = createClient(config);
+ currentNamespace = client.getNamespace();
+ }
setCurrentSessionNamespace(currentNamespace);
}
+ private static KubernetesClientImpl createClient(Config config) {
+ return new KubernetesClientBuilder().withConfig(config).build()
+ .adapt(KubernetesClientImpl.class);
+ }
+
public static KubectlClient create(String scenarioName) {
return new KubectlClient(scenarioName);
}
@@ -121,7 +133,8 @@ public void applyServicePropertiesUsingDeploymentConfig(Service service) {
envVar -> container.getEnv().add(new EnvVar(envVar.getKey(), envVar.getValue(), null)));
});
- client.apps().deployments().createOrReplace(deployment);
+ client.apps().deployments().withTimeout(DEPLOYMENT_CREATION_TIMEOUT, TimeUnit.SECONDS).delete();
+ client.apps().deployments().resource(deployment).create();
}
/**
@@ -152,7 +165,7 @@ public void applyServiceProperties(Service service, String file, UnaryOperator logs(Service service) {
}
/**
- * Resolve the url by the service.
- *
- * @param service
- * @return
+ * Get node host IP.
*/
- public String host(Service service) {
- String serviceName = service.getName();
- io.fabric8.kubernetes.api.model.Service serviceModel = client.services().withName(serviceName).get();
- if (serviceModel == null
- || serviceModel.getStatus() == null
- || serviceModel.getStatus().getLoadBalancer() == null
- || serviceModel.getStatus().getLoadBalancer().getIngress() == null) {
- printServiceInfo(service);
- fail("Service " + serviceName + " not found");
- }
-
- // IP detection rules:
- // 1.- Try Ingress IP
- // 2.- Try Ingress Hostname
- Optional ip = serviceModel.getStatus().getLoadBalancer().getIngress().stream()
- .map(ingress -> StringUtils.defaultIfBlank(ingress.getIp(), ingress.getHostname()))
- .filter(StringUtils::isNotEmpty)
- .findFirst();
-
- if (ip.isEmpty()) {
- printServiceInfo(service);
- fail("Service " + serviceName + " host not found");
+ public String host() {
+ String nodeURL = client.network().getConfiguration().getMasterUrl();
+ try {
+ URI uri = new URI(nodeURL);
+ return uri.getHost();
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException(e);
}
-
- return ip.get();
}
/**
@@ -260,7 +253,7 @@ public int port(Service service) {
}
return serviceModel.getSpec().getPorts().stream()
- .map(ServicePort::getPort)
+ .map(ServicePort::getNodePort)
.filter(Objects::nonNull)
.findFirst()
.orElse(HTTP_PORT_DEFAULT);
@@ -276,7 +269,7 @@ public void deleteNamespace() {
} catch (Exception e) {
fail("Project failed to be deleted. Caused by " + e.getMessage());
} finally {
- masterClient.close();
+ client.close();
}
} else {
deleteResourcesByLabel(LABEL_SCENARIO_ID, getScenarioId());
@@ -297,7 +290,7 @@ private void deleteResourcesByLabel(String labelName, String labelValue) {
} catch (Exception e) {
fail("Project failed to be deleted. Caused by " + e.getMessage());
} finally {
- masterClient.close();
+ client.close();
}
}
@@ -306,13 +299,14 @@ private boolean isPodRunning(Pod pod) {
}
private List loadYaml(String template) {
- return client.load(new ByteArrayInputStream(template.getBytes())).get();
+ NamespaceListVisitFromServerGetDeleteRecreateWaitApplicable load = client
+ .load(new ByteArrayInputStream(template.getBytes()));
+ return load.items();
}
private String enrichTemplate(Service service, String template, Map extraTemplateProperties) {
- List objs = loadYaml(template);
- for (HasMetadata obj : objs) {
- // set namespace
+ List objects = loadYaml(template);
+ for (HasMetadata obj : objects) {
obj.getMetadata().setNamespace(namespace());
Map objMetadataLabels = Optional.ofNullable(obj.getMetadata().getLabels())
.orElse(new HashMap<>());
@@ -321,23 +315,23 @@ private String enrichTemplate(Service service, String template, Map templateMetadataLabels = d.getSpec().getTemplate().getMetadata().getLabels();
+ Map templateMetadataLabels = deployment.getSpec().getTemplate().getMetadata().getLabels();
templateMetadataLabels.put(LABEL_TO_WATCH_FOR_LOGS, service.getName());
templateMetadataLabels.put(LABEL_SCENARIO_ID, getScenarioId());
// add env var properties
- Map enrichProperties = enrichProperties(service.getProperties(), d);
+ Map enrichProperties = enrichProperties(service.getProperties(), deployment);
enrichProperties.putAll(extraTemplateProperties);
- d.getSpec().getTemplate().getSpec().getContainers()
+ deployment.getSpec().getTemplate().getSpec().getContainers()
.forEach(container -> enrichProperties.entrySet().forEach(property -> {
String key = property.getKey();
EnvVar envVar = getEnvVarByKey(key, container);
@@ -351,16 +345,8 @@ private String enrichTemplate(Service service, String template, Map "\"" + cmd + "\"").collect(Collectors.joining(", "));
return content.replaceAll(quote("${IMAGE}"), model.getImage())
.replaceAll(quote("${SERVICE_NAME}"), model.getContext().getName())
.replaceAll(quote("${INTERNAL_PORT}"), "" + model.getPort())
- .replaceAll(quote("${ARGS}"), String.join(" ", model.getCommand()));
+ .replaceAll(quote("${ARGS}"), args);
}
private boolean useInternalServiceAsUrl() {
diff --git a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ContainerRegistryKubernetesQuarkusApplicationManagedResource.java b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ContainerRegistryKubernetesQuarkusApplicationManagedResource.java
index 21fc73004..e5cd32edf 100644
--- a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ContainerRegistryKubernetesQuarkusApplicationManagedResource.java
+++ b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ContainerRegistryKubernetesQuarkusApplicationManagedResource.java
@@ -64,7 +64,6 @@ private String replaceDeploymentContent(String content) {
return content
.replaceAll(quote("${IMAGE}"), image)
.replaceAll(quote("${SERVICE_NAME}"), model.getContext().getName())
- .replaceAll(quote("${ARTIFACT}"), model.getArtifact().getFileName().toString())
.replaceAll(quote("${INTERNAL_PORT}"),
model.getContext().getOwner().getProperty(QUARKUS_HTTP_PORT_PROPERTY, "" + HTTP_PORT_DEFAULT));
}
diff --git a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ExtensionKubernetesQuarkusApplicationManagedResource.java b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ExtensionKubernetesQuarkusApplicationManagedResource.java
index f4fe0edcb..76a5f1dd1 100644
--- a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ExtensionKubernetesQuarkusApplicationManagedResource.java
+++ b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/ExtensionKubernetesQuarkusApplicationManagedResource.java
@@ -34,7 +34,7 @@ public class ExtensionKubernetesQuarkusApplicationManagedResource
private static final String USING_EXTENSION_PROFILE = "-Pdeploy-to-kubernetes-using-extension";
private static final String QUARKUS_PLUGIN_DEPLOY = "-Dquarkus.kubernetes.deploy=true";
- private static final String QUARKUS_PLUGIN_INGRESS_EXPOSE = "-Dquarkus.kubernetes.ingress.expose=true";
+ private static final String USING_SERVICE_TYPE_NODE_PORT = "-Dquarkus.kubernetes.service-type=NodePort";
private static final String QUARKUS_CONTAINER_NAME = "quarkus.application.name";
private static final String QUARKUS_CONTAINER_IMAGE_REGISTRY = "quarkus.container-image.registry";
private static final String QUARKUS_CONTAINER_IMAGE_GROUP = "quarkus.container-image.group";
@@ -100,7 +100,7 @@ private void deployProjectUsingMavenCommand() {
List args = mvnCommand(model.getContext());
args.addAll(Arrays.asList(USING_EXTENSION_PROFILE, BATCH_MODE, DISPLAY_VERSION, PACKAGE_GOAL,
- QUARKUS_PLUGIN_DEPLOY, QUARKUS_PLUGIN_INGRESS_EXPOSE,
+ QUARKUS_PLUGIN_DEPLOY, USING_SERVICE_TYPE_NODE_PORT,
SKIP_TESTS, SKIP_ITS, SKIP_CHECKSTYLE, ENSURE_QUARKUS_BUILD));
propagateContainerRegistryIfSet(args);
args.add(withContainerName());
diff --git a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/KubernetesQuarkusApplicationManagedResource.java b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/KubernetesQuarkusApplicationManagedResource.java
index 2d1431839..0f250fdcd 100644
--- a/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/KubernetesQuarkusApplicationManagedResource.java
+++ b/quarkus-test-kubernetes/src/main/java/io/quarkus/test/services/quarkus/KubernetesQuarkusApplicationManagedResource.java
@@ -71,13 +71,9 @@ public void stop() {
@Override
public URILike getURI(Protocol protocol) {
- if (protocol == Protocol.HTTPS) {
- fail("SSL is not supported for Kubernetes tests yet");
- } else if (protocol == Protocol.GRPC) {
- fail("gRPC is not supported for Kubernetes tests yet");
- }
+ validateProtocol(protocol);
return createURI(protocol.getValue(),
- client.host(model.getContext().getOwner()),
+ client.host(),
client.port(model.getContext().getOwner()));
}
diff --git a/quarkus-test-kubernetes/src/main/resources/quarkus-app-kubernetes-template.yml b/quarkus-test-kubernetes/src/main/resources/quarkus-app-kubernetes-template.yml
index 723b2cbfa..4cd4246e0 100644
--- a/quarkus-test-kubernetes/src/main/resources/quarkus-app-kubernetes-template.yml
+++ b/quarkus-test-kubernetes/src/main/resources/quarkus-app-kubernetes-template.yml
@@ -2,40 +2,46 @@
apiVersion: "v1"
kind: "List"
items:
-- apiVersion: "v1"
- kind: "Service"
- metadata:
- name: "${SERVICE_NAME}"
- spec:
- type: LoadBalancer
- ports:
- - port: ${INTERNAL_PORT}
- selector:
- app.kubernetes.io/name: "${SERVICE_NAME}"
-- apiVersion: "apps/v1"
- kind: "Deployment"
- metadata:
- labels:
- app.kubernetes.io/runtime: "quarkus"
- app.kubernetes.io/name: "${SERVICE_NAME}"
- name: "${SERVICE_NAME}"
- spec:
- replicas: 1
- selector:
- matchLabels:
+ - apiVersion: "v1"
+ kind: "Service"
+ metadata:
+ name: "${SERVICE_NAME}"
+ labels:
+ app.kubernetes.io/runtime: "quarkus"
app.kubernetes.io/name: "${SERVICE_NAME}"
- template:
- metadata:
- labels:
- app.kubernetes.io/runtime: "quarkus"
+ spec:
+ type: NodePort
+ ports:
+ - name: "http"
+ port: ${INTERNAL_PORT}
+ targetPort: 8080
+ protocol: TCP
+ selector:
+ app.kubernetes.io/name: "${SERVICE_NAME}"
+
+ - apiVersion: "apps/v1"
+ kind: "Deployment"
+ metadata:
+ labels:
+ app.kubernetes.io/runtime: "quarkus"
+ app.kubernetes.io/name: "${SERVICE_NAME}"
+ name: "${SERVICE_NAME}"
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
app.kubernetes.io/name: "${SERVICE_NAME}"
- deployment: "${SERVICE_NAME}"
- spec:
- containers:
- - image: "${IMAGE}"
- name: "${SERVICE_NAME}"
- ports:
- - containerPort: ${INTERNAL_PORT}
- name: "http"
- protocol: "TCP"
-
\ No newline at end of file
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/runtime: "quarkus"
+ app.kubernetes.io/name: "${SERVICE_NAME}"
+ deployment: "${SERVICE_NAME}"
+ spec:
+ containers:
+ - image: "${IMAGE}"
+ name: "${SERVICE_NAME}"
+ ports:
+ - containerPort: ${INTERNAL_PORT}
+ name: "http"
+ protocol: "TCP"
diff --git a/quarkus-test-service-keycloak/pom.xml b/quarkus-test-service-keycloak/pom.xml
index ea47c636e..51be3d7ca 100644
--- a/quarkus-test-service-keycloak/pom.xml
+++ b/quarkus-test-service-keycloak/pom.xml
@@ -27,5 +27,11 @@
true
provided
+
+ io.quarkus.qe
+ quarkus-test-kubernetes
+ true
+ provided
+
diff --git a/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResource.java b/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResource.java
new file mode 100644
index 000000000..80ae03fbf
--- /dev/null
+++ b/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResource.java
@@ -0,0 +1,11 @@
+package io.quarkus.test.services.containers;
+
+public class KubernetesKeycloakContainerManagedResource extends KubernetesContainerManagedResource {
+
+ private final KeycloakContainerManagedResourceBuilder model;
+
+ protected KubernetesKeycloakContainerManagedResource(KeycloakContainerManagedResourceBuilder model) {
+ super(model);
+ this.model = model;
+ }
+}
diff --git a/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResourceBinding.java b/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResourceBinding.java
new file mode 100644
index 000000000..524659e62
--- /dev/null
+++ b/quarkus-test-service-keycloak/src/main/java/io/quarkus/test/services/containers/KubernetesKeycloakContainerManagedResourceBinding.java
@@ -0,0 +1,16 @@
+package io.quarkus.test.services.containers;
+
+import io.quarkus.test.bootstrap.ManagedResource;
+import io.quarkus.test.scenarios.KubernetesScenario;
+
+public class KubernetesKeycloakContainerManagedResourceBinding implements KeycloakContainerManagedResourceBinding {
+ @Override
+ public boolean appliesFor(KeycloakContainerManagedResourceBuilder builder) {
+ return builder.getContext().getTestContext().getRequiredTestClass().isAnnotationPresent(KubernetesScenario.class);
+ }
+
+ @Override
+ public ManagedResource init(KeycloakContainerManagedResourceBuilder builder) {
+ return new KubernetesKeycloakContainerManagedResource(builder);
+ }
+}
diff --git a/quarkus-test-service-keycloak/src/main/resources/META-INF/services/io.quarkus.test.services.containers.KeycloakContainerManagedResourceBinding b/quarkus-test-service-keycloak/src/main/resources/META-INF/services/io.quarkus.test.services.containers.KeycloakContainerManagedResourceBinding
index 2f62a1c0f..814b2ddf2 100644
--- a/quarkus-test-service-keycloak/src/main/resources/META-INF/services/io.quarkus.test.services.containers.KeycloakContainerManagedResourceBinding
+++ b/quarkus-test-service-keycloak/src/main/resources/META-INF/services/io.quarkus.test.services.containers.KeycloakContainerManagedResourceBinding
@@ -1 +1,2 @@
-io.quarkus.test.services.containers.OpenShiftKeycloakContainerManagedResourceBinding
\ No newline at end of file
+io.quarkus.test.services.containers.OpenShiftKeycloakContainerManagedResourceBinding
+io.quarkus.test.services.containers.KubernetesKeycloakContainerManagedResourceBinding
\ No newline at end of file