Skip to content

Commit

Permalink
Fail the build when a k8s deployment was requested by API server can'…
Browse files Browse the repository at this point in the history
…t be reached

Fixes: quarkusio#9129
  • Loading branch information
geoand authored and gsmet committed May 18, 2020
1 parent 103278b commit b2211c6
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import static io.quarkus.kubernetes.deployment.Constants.DEPLOY;

import java.util.function.BooleanSupplier;
import java.util.Optional;

import javax.net.ssl.SSLHandshakeException;

Expand All @@ -15,42 +15,108 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.quarkus.kubernetes.client.runtime.KubernetesClientUtils;

public class KubernetesDeploy implements BooleanSupplier {
public class KubernetesDeploy {

public static KubernetesDeploy INSTANCE = new KubernetesDeploy();

private static final Logger log = Logger.getLogger(KubernetesDeploy.class);
private static boolean serverFound = false;
private static boolean alreadyWarned = false;

@Override
public boolean getAsBoolean() {
private KubernetesDeploy() {
}

/**
* @return {@code true} iff @{code quarkus.kubernetes.deploy=true} AND the target Kubernetes API server is reachable
*
* It follows that this can only return {@code false} if the if @{code quarkus.kubernetes.deploy=false}
*
* @throws RuntimeException if communication to the Kubernetes API server errored
*/
public boolean check() {
Result result = doCheck();

if (result.getException().isPresent()) {
throw result.getException().get();
}

return result.isAllowed();
}

/**
* @return {@code true} iff @{code quarkus.kubernetes.deploy=true} AND the target Kubernetes API server is reachable
*
* Never throws an exception even in the face of a communication error with the API server, just returns
* {@code false}
* in that case
*/
public boolean checkSilently() {
return doCheck().isAllowed();
}

private Result doCheck() {
Config config = ConfigProvider.getConfig();
if (!config.getOptionalValue(DEPLOY, Boolean.class).orElse(false)) {
return false;
return Result.notConfigured();
}

// No need to perform the check multiple times.
if (serverFound) {
return true;
return Result.enabled();
}
try (final KubernetesClient client = KubernetesClientUtils.createClient()) {

KubernetesClient client = KubernetesClientUtils.createClient();
String masterURL = client.getConfiguration().getMasterUrl();
try {
masterURL = client.getConfiguration().getMasterUrl();
//Let's check id we can connect.
RootPaths paths = client.rootPaths();
log.info("Found kubernetes server.");
log.info("Kubernetes API Server at '" + masterURL + "' successfully contacted.");
serverFound = true;
return true;
client.close();
return Result.enabled();
} catch (Exception e) {
if (!alreadyWarned) {
if (e.getCause() instanceof SSLHandshakeException) {
String message = "Although a Kubernetes deployment was requested, it will however not take place because the API Server certificates are not trusted. The certificates can be configured using the relevant configuration propertiers under the 'quarkus.kubernetes-client' config root, or \"quarkus.kubernetes-client.trust-certs=true\" can be set to explicitly trust the certificates (not recommended)";
log.warn(message);
} else {
log.error(
"Although a Kubernetes deployment was requested, it will however not take place because there was an error during communication with the API Server: "
+ e.getMessage());
}
alreadyWarned = true;
if (e.getCause() instanceof SSLHandshakeException) {
return Result.exceptional(new RuntimeException(
"Although a Kubernetes deployment was requested, it however cannot take place because the API Server (at '"
+ masterURL
+ "') certificates are not trusted. The certificates can be configured using the relevant configuration propertiers under the 'quarkus.kubernetes-client' config root, or \"quarkus.kubernetes-client.trust-certs=true\" can be set to explicitly trust the certificates (not recommended)",
e));
} else {
return Result.exceptional(new RuntimeException(
"Although a Kubernetes deployment was requested, it however cannot take place because there was an error during communication with the API Server at '"
+ masterURL + "'",
e));
}
return false;
}
}

private static class Result {
private final boolean allowed;
private final Optional<RuntimeException> exception;

private Result(boolean allowed, Optional<RuntimeException> exception) {
this.allowed = allowed;
this.exception = exception;
}

static Result notConfigured() {
return new Result(false, Optional.empty());
}

static Result enabled() {
return new Result(true, Optional.empty());
}

static Result exceptional(RuntimeException e) {
return new Result(false, Optional.of(e));
}

public boolean isAllowed() {
return allowed;
}

public Optional<RuntimeException> getException() {
return exception;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,18 @@ public class KubernetesDeployer {
private static final String CONTAINER_IMAGE_EXTENSIONS_STR = Arrays.stream(CONTAINER_IMAGE_EXTENSIONS)
.map(s -> "\"" + s + "\"").collect(Collectors.joining(", "));

@BuildStep(onlyIf = { IsNormal.class, KubernetesDeploy.class })
@BuildStep(onlyIf = IsNormal.class)
public void deploy(KubernetesClientBuildItem kubernetesClient,
ContainerImageInfoBuildItem containerImageInfo,
List<ContainerImageResultBuildItem> containerImageResults,
List<KubernetesDeploymentTargetBuildItem> kubernetesDeploymentTargets,
OutputTargetBuildItem outputTarget,
BuildProducer<DeploymentResultBuildItem> deploymentResult) {

if (!KubernetesDeploy.INSTANCE.check()) {
return;
}

if (containerImageResults.isEmpty()) {
throw new RuntimeException(
"A Kubernetes deployment was requested but no extension was found to build a container image. Consider adding one of following extensions: "
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package io.quarkus.kubernetes.deployment;

import org.jboss.logging.Logger;

import io.quarkus.container.spi.ContainerImageBuildRequestBuildItem;
import io.quarkus.container.spi.ContainerImageInfoBuildItem;
import io.quarkus.container.spi.ContainerImagePushRequestBuildItem;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;

public class KubernetesDeployerPrerequisite {

private static final Logger log = Logger.getLogger(KubernetesDeploy.class);

@BuildStep(onlyIf = KubernetesDeploy.class)
@BuildStep(onlyIf = IsNormal.class)
public void prepare(ContainerImageInfoBuildItem containerImage,
BuildProducer<ContainerImageBuildRequestBuildItem> buildRequestProducer,
BuildProducer<ContainerImagePushRequestBuildItem> pushRequestProducer) {

// we don't want to throw an exception at this step and fail the build because it could prevent
// the Kubernetes resources from being generated
if (!KubernetesDeploy.INSTANCE.checkSilently()) {
return;
}

//Let's communicate to the container-image plugin that we need an image build and an image push.
buildRequestProducer.produce(new ContainerImageBuildRequestBuildItem());
if (containerImage.getRegistry().isPresent()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# invoker.goals=clean package -Dquarkus.container.build=true -Dquarkus.package.type=native
invoker.goals=clean package -Dquarkus.kubernetes.deploy=true
# expect a failure since there is no Kubernetes cluster to deploy to
invoker.buildResult = failure
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# invoker.goals=clean package -Dquarkus.container.build=true -Dquarkus.package.type=native
invoker.goals=clean package -Dquarkus.kubernetes.deploy=true
# expect a failure since there is no Kubernetes cluster to deploy to
invoker.buildResult = failure
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# invoker.goals=clean package -Dquarkus.container.build=true -Dquarkus.package.type=native
invoker.goals=clean package -Dquarkus.kubernetes.deploy=true
# expect a failure since there is no Kubernetes cluster to deploy to
invoker.buildResult = failure

0 comments on commit b2211c6

Please sign in to comment.