Skip to content

Commit

Permalink
Merge pull request quarkusio#10434 from iocanel/issue-9663-openshift-…
Browse files Browse the repository at this point in the history
…deployments

Allow openshift to work with docker and jib container images
  • Loading branch information
geoand authored Jul 15, 2020
2 parents 4f16d1a + 9254fc1 commit 430ef0c
Show file tree
Hide file tree
Showing 20 changed files with 374 additions and 24 deletions.
11 changes: 11 additions & 0 deletions docs/src/main/asciidoc/container-image.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ To push a container image for your project, `quarkus.container-image.push=true`

NOTE: If no registry is set (using `quarkus.container-image.registry`) then `docker.io` will be used as the default.

== Selecting among multiple extensions

It does not make sense to use multiple extension as part of the same build. When multiple container image extensions are present, an error will be raised to inform the user. The user can either remove the uneeded extensions or select one using `application.properties`.

For example, if both `container-image-docker` and `container-image-s2i` are present and the user needs to use `container-image-docker`:

[source]
----
quarkus.container-image.builder=docker
----

== Customizing

The following properties can be used to customize the container image build process.
Expand Down
25 changes: 24 additions & 1 deletion docs/src/main/asciidoc/deploying-to-openshift.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,30 @@ Building is handled by the link:container-image#s2i[container-image-s2i] extensi
./mvnw clean package -Dquarkus.container-image.build=true
----

The command above will trigger an s2i binary build.
The build that will be performed is an s2i binary build. The input of the build is the jar that has been built locally and the output of the build is an ImageStream that is configured to automatically trigger a deployment.

=== Non S2i Builds

Out of the box the openshift extension is configured to use link:container-image#s2i[container-image-s2i]. However, it's still possible to use other container image extensions like:

- link:container-image#s2i[container-image-docker]
- link:container-image#s2i[container-image-jib]

When a non-s2i container image extension is used, an ImageStream is created that is pointing to an external `dockerImageRepository`. The image is built and pushed to the registry and the ImageStream populates the tags that are available in the `dockerImageRepository`.

To select which extension will be used for building the image:

[source]
---
quarkus.container-image.builder=docker
---

or

[source]
---
quarkus.container-image.builder=jib
---

=== Deploying

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

package io.quarkus.container.image.docker.deployment;

import java.util.function.BooleanSupplier;

import io.quarkus.container.image.deployment.ContainerImageConfig;

public class DockerBuild implements BooleanSupplier {

private final ContainerImageConfig containerImageConfig;

DockerBuild(ContainerImageConfig containerImageConfig) {
this.containerImageConfig = containerImageConfig;
}

@Override
public boolean getAsBoolean() {
return containerImageConfig.builder.map(b -> b.equals(DockerProcessor.DOCKER)).orElse(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@
public class DockerProcessor {

private static final Logger log = Logger.getLogger(DockerProcessor.class);
private static final String DOCKER = "docker";
private static final String DOCKERFILE_JVM = "Dockerfile.jvm";
private static final String DOCKERFILE_FAST_JAR = "Dockerfile.fast-jar";
private static final String DOCKERFILE_NATIVE = "Dockerfile.native";
public static final String DOCKER_BINARY_NAME = "docker";
public static final String DOCKER = "docker";

private final DockerWorking dockerWorking = new DockerWorking();

@BuildStep
@BuildStep(onlyIf = DockerBuild.class)
public CapabilityBuildItem capability() {
return new CapabilityBuildItem(Capability.CONTAINER_IMAGE_DOCKER);
}

@BuildStep(onlyIf = { IsNormal.class }, onlyIfNot = NativeBuild.class)
@BuildStep(onlyIf = { IsNormal.class, DockerBuild.class }, onlyIfNot = NativeBuild.class)
public void dockerBuildFromJar(DockerConfig dockerConfig,
ContainerImageConfig containerImageConfig, // TODO: use to check whether we need to also push to registry
OutputTargetBuildItem out,
Expand Down Expand Up @@ -89,7 +89,7 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,
artifactResultProducer.produce(new ArtifactResultBuildItem(null, "jar-container", Collections.emptyMap()));
}

@BuildStep(onlyIf = { IsNormal.class, NativeBuild.class })
@BuildStep(onlyIf = { IsNormal.class, NativeBuild.class, DockerBuild.class })
public void dockerBuildFromNativeImage(DockerConfig dockerConfig,
ContainerImageConfig containerImageConfig,
ContainerImageInfoBuildItem containerImage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public JibBuild(ContainerImageConfig containerImageConfig) {

@Override
public boolean getAsBoolean() {
return true;
return containerImageConfig.builder.map(b -> b.equals(JibProcessor.JIB)).orElse(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

package io.quarkus.container.image.jib.deployment;

import java.util.function.BooleanSupplier;

import io.quarkus.container.image.deployment.ContainerImageConfig;

public class JibBuildEnabled implements BooleanSupplier {

private final ContainerImageConfig containerImageConfig;

JibBuildEnabled(ContainerImageConfig containerImageConfig) {
this.containerImageConfig = containerImageConfig;
}

@Override
public boolean getAsBoolean() {
return containerImageConfig.builder.map(b -> b.equals(JibProcessor.JIB)).orElse(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ public class JibProcessor {

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

private static final String JIB = "jib";
public static final String JIB = "jib";
private static final IsClassPredicate IS_CLASS_PREDICATE = new IsClassPredicate();
private static final String BINARY_NAME_IN_CONTAINER = "application";

@BuildStep
@BuildStep(onlyIf = JibBuild.class)
public CapabilityBuildItem capability() {
return new CapabilityBuildItem(Capability.CONTAINER_IMAGE_JIB);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public class S2iBuild implements BooleanSupplier {

@Override
public boolean getAsBoolean() {
return true;
return containerImageConfig.builder.map(b -> b.equals(S2iProcessor.S2I)).orElse(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,19 @@

public class S2iProcessor {

private static final String S2I = "s2i";
public static final String S2I = "s2i";
private static final String OPENSHIFT = "openshift";
private static final String BUILD_CONFIG_NAME = "openshift.io/build-config.name";
private static final String RUNNING = "Running";

private static final Logger LOG = Logger.getLogger(S2iProcessor.class);

@BuildStep
@BuildStep(onlyIf = S2iBuild.class)
public CapabilityBuildItem capability() {
return new CapabilityBuildItem(Capability.CONTAINER_IMAGE_S2I);
}

@BuildStep(onlyIf = IsNormal.class, onlyIfNot = NativeBuild.class)
@BuildStep(onlyIf = { IsNormal.class, S2iBuild.class }, onlyIfNot = NativeBuild.class)
public void s2iRequirementsJvm(S2iConfig s2iConfig,
CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem out,
Expand Down Expand Up @@ -106,7 +106,7 @@ public void s2iRequirementsJvm(S2iConfig s2iConfig,
}
}

@BuildStep(onlyIf = { IsNormal.class, NativeBuild.class })
@BuildStep(onlyIf = { IsNormal.class, S2iBuild.class, NativeBuild.class })
public void s2iRequirementsNative(S2iConfig s2iConfig,
CurateOutcomeBuildItem curateOutcomeBuildItem,
OutputTargetBuildItem out,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public static Optional<String> getActiveContainerImageCapability(Capabilities ca
.getContainerImageCapabilities(capabilities);
if (activeContainerImageCapabilities.size() > 1) {
throw new IllegalStateException(String.join(" and ", activeContainerImageCapabilities)
+ " were detected, at most one container-image extension can be present. ");
+ " were detected, at most one container-image extension can be present.\n"
+ "Either remove the uneeded ones, or select one using the property 'quarkus.container-image-builder=<extension name (without the `container-image-` prefix)>'.");
}
return activeContainerImageCapabilities.isEmpty() ? Optional.empty()
: Optional.of(activeContainerImageCapabilities.iterator().next());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public class ContainerImageConfig {
@ConfigItem
public boolean push;

/**
* The name of the container image extension to use (e.g. docker, jib, s2i).
* The option will be used in case multiple extensions are present.
*/
@ConfigItem
public Optional<String> builder;

/**
* Since user.name which is default value can be uppercase and uppercase values are not allowed
* in the repository part of image references, we need to make the username lowercase.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@
import io.dekorate.kubernetes.annotation.ImagePullPolicy;
import io.dekorate.kubernetes.annotation.ServiceType;
import io.dekorate.kubernetes.config.Annotation;
import io.dekorate.kubernetes.config.Configurator;
import io.dekorate.kubernetes.config.EnvBuilder;
import io.dekorate.kubernetes.config.ImageConfigurationFluent;
import io.dekorate.kubernetes.config.Label;
import io.dekorate.kubernetes.config.LabelBuilder;
import io.dekorate.kubernetes.config.PortBuilder;
Expand Down Expand Up @@ -103,6 +105,7 @@
import io.dekorate.project.ScmInfo;
import io.dekorate.s2i.config.S2iBuildConfig;
import io.dekorate.s2i.config.S2iBuildConfigBuilder;
import io.dekorate.s2i.config.S2iBuildConfigFluent;
import io.dekorate.s2i.decorator.AddBuilderImageStreamResourceDecorator;
import io.dekorate.utils.Annotations;
import io.dekorate.utils.Maps;
Expand Down Expand Up @@ -321,7 +324,32 @@ public void build(ApplicationInfoBuildItem applicationInfo,
determineImagePullPolicy(knativeConfig, needToForceUpdateImagePullPolicy));

applyKnativeConfig(session, project, getResourceName(knativeConfig, applicationInfo), knativeConfig);
//When S2i is disabled we need to pass that information to dekorate.
//Also we need to make sure that the alternatives (instances of ImageConfiguration)
//are properly configured.
if (!capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_S2I)) {
session.configurators().add(new Configurator<ImageConfigurationFluent<?>>() {
@Override
public void visit(ImageConfigurationFluent<?> image) {
containerImage.ifPresent(i -> {
String group = ImageUtil.getRepository(i.getImage()).split("/")[0];
image.withGroup(group);
i.getRegistry().ifPresent(r -> {
image.withRegistry(r);
});
});
}
});

//JAVA_APP_JAR value is not compatible with our Dockerfiles, so its causing problems
session.resources().decorate(OPENSHIFT, new RemoveEnvVarDecorator("JAVA_APP_JAR"));
session.configurators().add(new Configurator<S2iBuildConfigFluent<?>>() {
@Override
public void visit(S2iBuildConfigFluent<?> s2i) {
s2i.withEnabled(false);
}
});
}
//apply build item configurations to the dekorate session.
applyBuildItems(session,
applicationInfo,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.quarkus.kubernetes.deployment;

import io.dekorate.deps.kubernetes.api.model.ContainerFluent;
import io.dekorate.kubernetes.decorator.AddEnvVarDecorator;
import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator;
import io.dekorate.kubernetes.decorator.Decorator;
import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator;

public class RemoveEnvVarDecorator extends ApplicationContainerDecorator<ContainerFluent<?>> {

private final String envVarName;

public RemoveEnvVarDecorator(String envVarName) {
this(ANY, envVarName);
}

public RemoveEnvVarDecorator(String name, String envVarName) {
super(name);
this.envVarName = envVarName;
}

public void andThenVisit(ContainerFluent<?> container) {
container.removeMatchingFromEnv(e -> e.getName().equals(envVarName));
}

public String getEnvVarKey() {
return this.envVarName;
}

public Class<? extends Decorator>[] after() {
return new Class[] { ResourceProvidingDecorator.class, AddEnvVarDecorator.class };
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((envVarName == null) ? 0 : envVarName.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RemoveEnvVarDecorator other = (RemoveEnvVarDecorator) obj;
if (envVarName == null) {
if (other.envVarName != null)
return false;
} else if (!envVarName.equals(other.envVarName))
return false;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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
Loading

0 comments on commit 430ef0c

Please sign in to comment.