Skip to content

Commit

Permalink
Allow configuring the deploy strategy to perform K8s deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
Sgitario committed Mar 10, 2023
1 parent d6e0af8 commit 5e44b81
Show file tree
Hide file tree
Showing 17 changed files with 236 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.container.image.openshift.deployment;

import static io.quarkus.container.image.openshift.deployment.OpenshiftUtils.getDeployStrategy;
import static io.quarkus.container.image.openshift.deployment.OpenshiftUtils.getNamespace;
import static io.quarkus.container.image.openshift.deployment.OpenshiftUtils.mergeConfig;
import static io.quarkus.container.util.PathsUtil.findMainSourcesRoot;
Expand Down Expand Up @@ -39,6 +40,9 @@
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.LogWatch;
import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
import io.fabric8.kubernetes.client.dsl.base.PatchType;
import io.fabric8.kubernetes.client.http.HttpClient;
import io.fabric8.openshift.api.model.Build;
import io.fabric8.openshift.api.model.BuildConfig;
Expand Down Expand Up @@ -68,6 +72,7 @@
import io.quarkus.kubernetes.client.deployment.KubernetesClientErrorHandler;
import io.quarkus.kubernetes.client.spi.KubernetesClientBuildItem;
import io.quarkus.kubernetes.spi.DecoratorBuildItem;
import io.quarkus.kubernetes.spi.DeployStrategy;
import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem;
import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem;

Expand Down Expand Up @@ -403,27 +408,7 @@ private static void applyOpenshiftResources(OpenShiftClient client, List<HasMeta
// Apply build resource requirements
try {
for (HasMetadata i : distinct(buildResources)) {
if (i instanceof BuildConfig) {
client.resource(i).cascading(true).delete();
try {
client.resource(i).waitUntilCondition(d -> d == null, 10, TimeUnit.SECONDS);
} catch (IllegalArgumentException e) {
// We should ignore that, as its expected to be thrown when item is actually
// deleted.
}
} else if (i instanceof ImageStream) {
ImageStream is = (ImageStream) i;
ImageStream existing = client.imageStreams().withName(i.getMetadata().getName()).get();
if (existing != null &&
existing.getSpec() != null &&
existing.getSpec().getDockerImageRepository() != null &&
existing.getSpec().getDockerImageRepository().equals(is.getSpec().getDockerImageRepository())) {
LOG.info("Found: " + i.getKind() + " " + i.getMetadata().getName() + " repository: "
+ existing.getSpec().getDockerImageRepository());
continue;
}
}
client.resource(i).createOrReplace();
deployResource(client, i);
LOG.info("Applied: " + i.getKind() + " " + i.getMetadata().getName());
}
OpenshiftUtils.waitForImageStreamTags(client, buildResources, 2, TimeUnit.MINUTES);
Expand Down Expand Up @@ -549,6 +534,55 @@ private static KubernetesClient buildClient(KubernetesClientBuildItem kubernetes
return kubernetesClientBuilder.buildClient();
}

private static void deployResource(OpenShiftClient client, HasMetadata metadata) {
DeployStrategy deployStrategy = getDeployStrategy();
var r = client.resource(metadata);
// Delete build config it already existed unless the deploy strategy is not create or update.
if (deployStrategy != DeployStrategy.CreateOrUpdate && r instanceof BuildConfig) {
deleteBuildConfig(client, metadata, r);
}

// If the image stream is already installed, we proceed with the next.
if (r instanceof ImageStream) {
ImageStream is = (ImageStream) r;
ImageStream existing = client.imageStreams().withName(metadata.getMetadata().getName()).get();
if (existing != null &&
existing.getSpec() != null &&
existing.getSpec().getDockerImageRepository() != null &&
existing.getSpec().getDockerImageRepository().equals(is.getSpec().getDockerImageRepository())) {
LOG.info("Found: " + metadata.getKind() + " " + metadata.getMetadata().getName() + " repository: "
+ existing.getSpec().getDockerImageRepository());
return;
}
}

// Deploy the current resource.
switch (deployStrategy) {
case Create:
r.create();
break;
case Replace:
r.replace();
break;
case ServerSideApply:
r.patch(PatchContext.of(PatchType.SERVER_SIDE_APPLY));
break;
default:
r.createOrReplace();
break;
}
}

private static void deleteBuildConfig(OpenShiftClient client, HasMetadata metadata, NamespaceableResource<HasMetadata> r) {
r.cascading(true).delete();
try {
client.resource(metadata).waitUntilCondition(d -> d == null, 10, TimeUnit.SECONDS);
} catch (IllegalArgumentException e) {
// We should ignore that, as its expected to be thrown when item is actually
// deleted.
}
}

// visible for test
static String concatUnixPaths(String... elements) {
StringBuilder result = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.fabric8.openshift.api.model.ImageStreamTag;
import io.fabric8.openshift.api.model.SourceBuildStrategyFluent;
import io.fabric8.openshift.client.OpenShiftClient;
import io.quarkus.kubernetes.spi.DeployStrategy;

/**
* This class is copied from Dekorate, with the difference that the {@code waitForImageStreamTags} method
Expand All @@ -30,6 +31,8 @@ public class OpenshiftUtils {

private static final String OPENSHIFT_NAMESPACE = "quarkus.openshift.namespace";
private static final String KUBERNETES_NAMESPACE = "quarkus.kubernetes.namespace";
private static final String OPENSHIFT_DEPLOY_STRATEGY = "quarkus.openshift.deploy-strategy";
private static final String KUBERNETES_DEPLOY_STRATEGY = "quarkus.kubernetes.deploy-strategy";

/**
* Wait for the references ImageStreamTags to become available.
Expand Down Expand Up @@ -153,4 +156,13 @@ public static Optional<String> getNamespace() {
return ConfigProvider.getConfig().getOptionalValue(OPENSHIFT_NAMESPACE, String.class)
.or(() -> ConfigProvider.getConfig().getOptionalValue(KUBERNETES_NAMESPACE, String.class));
}

/**
* @return the openshift deploy strategy set in the OpenShift/Kubernetes extensions.
*/
public static DeployStrategy getDeployStrategy() {
return ConfigProvider.getConfig().getOptionalValue(OPENSHIFT_DEPLOY_STRATEGY, DeployStrategy.class)
.or(() -> ConfigProvider.getConfig().getOptionalValue(KUBERNETES_DEPLOY_STRATEGY, DeployStrategy.class))
.orElse(DeployStrategy.CreateOrUpdate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public void checkKind(ApplicationInfoBuildItem applicationInfo, KubernetesConfig
BuildProducer<KubernetesResourceMetadataBuildItem> resourceMeta) {
deploymentTargets.produce(
new KubernetesDeploymentTargetBuildItem(KIND, DEPLOYMENT, DEPLOYMENT_GROUP, DEPLOYMENT_VERSION,
KIND_PRIORITY, true));
KIND_PRIORITY, true, config.getDeployStrategy()));

String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void checkMinikube(ApplicationInfoBuildItem applicationInfo, KubernetesCo
BuildProducer<KubernetesResourceMetadataBuildItem> resourceMeta) {
deploymentTargets.produce(
new KubernetesDeploymentTargetBuildItem(MINIKUBE, DEPLOYMENT, DEPLOYMENT_GROUP, DEPLOYMENT_VERSION,
MINIKUBE_PRIORITY, true));
MINIKUBE_PRIORITY, true, config.getDeployStrategy()));

String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public void checkOpenshift(ApplicationInfoBuildItem applicationInfo, Capabilitie
.produce(
new KubernetesDeploymentTargetBuildItem(OPENSHIFT, deploymentResourceKind.kind,
deploymentResourceKind.apiGroup,
deploymentResourceKind.apiVersion, true));
deploymentResourceKind.apiVersion, true,
config.getDeployStrategy()));

String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(OPENSHIFT, deploymentResourceKind.apiGroup,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.kubernetes.spi;

public enum DeployStrategy {
/**
* To create or replace the resources.
*/
CreateOrUpdate,
/**
* To create the resources if it does not exist. If resources already exist, it will fail.
*/
Create,
/**
* To update the existing resources in the target Kubernetes cluster. If no resources exist, it will fail.
*/
Replace,
/**
* To perform patch updates to the existing resources. If no resources exist, it will fail.
* More information in <a href="https://kubernetes.io/docs/reference/using-api/server-side-apply/">server-side-apply</a>.
*/
ServerSideApply;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,27 @@ public final class KubernetesDeploymentTargetBuildItem extends MultiBuildItem
private final String version;
private final int priority;
private final boolean enabled;
private final DeployStrategy deployStrategy;

public KubernetesDeploymentTargetBuildItem(String name, String kind, String group, String version) {
this(name, kind, group, version, DEFAULT_PRIORITY, false);
public KubernetesDeploymentTargetBuildItem(String name, String kind, String group, String version,
DeployStrategy deployStrategy) {
this(name, kind, group, version, DEFAULT_PRIORITY, false, deployStrategy);
}

public KubernetesDeploymentTargetBuildItem(String name, String kind, String group, String version, boolean enabled) {
this(name, kind, group, version, DEFAULT_PRIORITY, enabled);
public KubernetesDeploymentTargetBuildItem(String name, String kind, String group, String version, boolean enabled,
DeployStrategy deployStrategy) {
this(name, kind, group, version, DEFAULT_PRIORITY, enabled, deployStrategy);
}

public KubernetesDeploymentTargetBuildItem(String name, String kind, String group, String version, int priority,
boolean enabled) {
boolean enabled, DeployStrategy deployStrategy) {
this.name = Objects.requireNonNull(name, "'name' must not be null");
this.kind = Objects.requireNonNull(kind, "'kind' must not be null");
this.version = Objects.requireNonNull(version, "'version' must not be null");
this.group = group;
this.priority = priority;
this.enabled = enabled;
this.deployStrategy = deployStrategy;
}

public String getGroup() {
Expand All @@ -78,6 +82,10 @@ public boolean isEnabled() {
return enabled;
}

public DeployStrategy getDeployStrategy() {
return deployStrategy;
}

public boolean nameAndKindMatch(KubernetesDeploymentTargetBuildItem other) {
return this.name.equals(other.getName()) && this.kind.equals(other.getKind());
}
Expand All @@ -103,7 +111,8 @@ private KubernetesDeploymentTargetBuildItem merge(KubernetesDeploymentTargetBuil

return new KubernetesDeploymentTargetBuildItem(this.name, this.kind, this.group, this.version,
Math.max(this.priority, other.getPriority()),
this.enabled || other.isEnabled());
this.enabled || other.isEnabled(),
this.deployStrategy);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.kubernetes.spi;

import static io.quarkus.kubernetes.spi.DeployStrategy.CreateOrUpdate;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.Arrays;
Expand All @@ -12,21 +13,21 @@ class KubernetesDeploymentTargetBuildItemTest {
@Test
void testMergeList() {
List<KubernetesDeploymentTargetBuildItem> input = Arrays.asList(
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 0, false),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", 10, false),
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", -10, false),
new KubernetesDeploymentTargetBuildItem("n3", "k3", "g1", "v1", Integer.MIN_VALUE, false),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MIN_VALUE, true),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", -10, true),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MAX_VALUE, true),
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 100, false));
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 0, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", 10, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", -10, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n3", "k3", "g1", "v1", Integer.MIN_VALUE, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MIN_VALUE, true, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", -10, true, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MAX_VALUE, true, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 100, false, CreateOrUpdate));

List<KubernetesDeploymentTargetBuildItem> result = KubernetesDeploymentTargetBuildItem.mergeList(input);

assertThat(result).containsOnly(
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 100, false),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", 10, true),
new KubernetesDeploymentTargetBuildItem("n3", "k3", "g1", "v1", Integer.MIN_VALUE, false),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MAX_VALUE, true));
new KubernetesDeploymentTargetBuildItem("n1", "k1", "g1", "v1", 100, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n2", "k2", "g1", "v1", 10, true, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n3", "k3", "g1", "v1", Integer.MIN_VALUE, false, CreateOrUpdate),
new KubernetesDeploymentTargetBuildItem("n4", "k4", "g1", "v1", Integer.MAX_VALUE, true, CreateOrUpdate));
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package io.quarkus.kubernetes.deployment;

import io.quarkus.kubernetes.spi.DeployStrategy;

public class DeploymentTargetEntry {
private final String name;
private final String kind;
private final int priority;
private final DeployStrategy deployStrategy;

public DeploymentTargetEntry(String name, String kind, int priority) {
public DeploymentTargetEntry(String name, String kind, int priority, DeployStrategy deployStrategy) {
this.name = name;
this.kind = kind;
this.priority = priority;
this.deployStrategy = deployStrategy;
}

public String getName() {
Expand All @@ -22,4 +26,8 @@ public String getKind() {
public int getPriority() {
return priority;
}

public DeployStrategy getDeployStrategy() {
return deployStrategy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import io.dekorate.kubernetes.annotation.ImagePullPolicy;
import io.dekorate.kubernetes.annotation.ServiceType;
import io.quarkus.kubernetes.spi.DeployStrategy;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

Expand Down Expand Up @@ -498,6 +499,12 @@ public EnvVarsConfig getEnv() {
@ConfigItem(defaultValue = "false")
boolean deploy;

/**
* If deploy is enabled, it will follow this strategy to update the resources to the target Knative cluster.
*/
@ConfigItem(defaultValue = "CreateOrUpdate")
DeployStrategy deployStrategy;

public Optional<String> getAppSecret() {
return this.appSecret;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ public void checkKnative(ApplicationInfoBuildItem applicationInfo, KnativeConfig
boolean knativeEnabled = targets.contains(KNATIVE);
deploymentTargets.produce(
new KubernetesDeploymentTargetBuildItem(KNATIVE, KNATIVE_SERVICE, KNATIVE_SERVICE_GROUP,
KNATIVE_SERVICE_VERSION, KNATIVE_PRIORITY,
knativeEnabled));
KNATIVE_SERVICE_VERSION, KNATIVE_PRIORITY, knativeEnabled, config.deployStrategy));
if (knativeEnabled) {
String name = ResourceNameUtil.getResourceName(config, applicationInfo);
resourceMeta.produce(new KubernetesResourceMetadataBuildItem(KNATIVE, KNATIVE_SERVICE_GROUP,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io.dekorate.kubernetes.annotation.ServiceType;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.kubernetes.spi.DeployStrategy;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

Expand Down Expand Up @@ -303,6 +304,12 @@ public enum DeploymentResourceKind {
@ConfigItem(defaultValue = "false")
boolean deploy;

/**
* If deploy is enabled, it will follow this strategy to update the resources to the target Kubernetes cluster.
*/
@ConfigItem(defaultValue = "CreateOrUpdate")
DeployStrategy deployStrategy;

/**
* If set, the secret will mounted to the application container and its contents will be used for application configuration.
*/
Expand Down Expand Up @@ -564,6 +571,10 @@ public boolean isIdempotent() {
return idempotent;
}

public DeployStrategy getDeployStrategy() {
return deployStrategy;
}

public KubernetesConfig.DeploymentResourceKind getDeploymentResourceKind(Capabilities capabilities) {
if (deploymentKind.isPresent()) {
return deploymentKind.get();
Expand Down
Loading

0 comments on commit 5e44b81

Please sign in to comment.