From fb049e5f5bdf670a6b39eecf06a5d55401d83987 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Fri, 2 Aug 2024 13:48:38 +0300 Subject: [PATCH] feat: Introduce a new extension for generating Dockerfiles --- bom/application/pom.xml | 16 +- devtools/bom-descriptor-json/pom.xml | 13 ++ docs/pom.xml | 13 ++ extensions/dockerfiles/cli/pom.xml | 110 +++++++++++++ .../quarkus/dockerfiles/cli/Dockerfiles.java | 155 ++++++++++++++++++ .../cli/GenerateDockerfilesHandler.java | 32 ++++ .../src/main/resources/application.properties | 2 + .../cli/src/main/resources/version | 1 + extensions/dockerfiles/deployment/pom.xml | 61 +++++++ .../deployment/DockerfileContent.java | 53 ++++++ .../deployment/DockerfilesConfiguration.java | 43 +++++ .../deployment/DockerfilesProcessor.java | 57 +++++++ .../main/resources/Dockerfile.tpl.qute.jvm | 95 +++++++++++ .../main/resources/Dockerfile.tpl.qute.native | 27 +++ .../deployment/DockerfileContentTest.java | 26 +++ .../deployment/DockerfilesDevModeTest.java | 23 +++ extensions/dockerfiles/pom.xml | 23 +++ extensions/dockerfiles/runtime/pom.xml | 56 +++++++ .../resources/META-INF/quarkus-extension.yaml | 9 + extensions/dockerfiles/spi/pom.xml | 33 ++++ .../dockerfiles/spi/GeneratedDockerfile.java | 32 ++++ .../dockerfiles/spi/JvmDockerfileFrom.java | 32 ++++ .../dockerfiles/spi/NativeDockerfileFrom.java | 32 ++++ extensions/pom.xml | 3 + 24 files changed, 946 insertions(+), 1 deletion(-) create mode 100644 extensions/dockerfiles/cli/pom.xml create mode 100644 extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/Dockerfiles.java create mode 100644 extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/GenerateDockerfilesHandler.java create mode 100644 extensions/dockerfiles/cli/src/main/resources/application.properties create mode 100644 extensions/dockerfiles/cli/src/main/resources/version create mode 100644 extensions/dockerfiles/deployment/pom.xml create mode 100644 extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfileContent.java create mode 100644 extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesConfiguration.java create mode 100644 extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesProcessor.java create mode 100644 extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.jvm create mode 100644 extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.native create mode 100644 extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfileContentTest.java create mode 100644 extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfilesDevModeTest.java create mode 100644 extensions/dockerfiles/pom.xml create mode 100644 extensions/dockerfiles/runtime/pom.xml create mode 100644 extensions/dockerfiles/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 extensions/dockerfiles/spi/pom.xml create mode 100644 extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/GeneratedDockerfile.java create mode 100644 extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/JvmDockerfileFrom.java create mode 100644 extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/NativeDockerfileFrom.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index e23619d5dbf06..be7077845589f 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -2929,6 +2929,21 @@ quarkus-container-image-util ${project.version} + + io.quarkus + quarkus-dockerfiles + ${project.version} + + + io.quarkus + quarkus-dockerfiles-spi + ${project.version} + + + io.quarkus + quarkus-dockerfiles-deployment + ${project.version} + io.quarkus quarkus-kubernetes @@ -6538,7 +6553,6 @@ ${project.version} - diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml index d93441834129b..8ff9aeff2f56d 100644 --- a/devtools/bom-descriptor-json/pom.xml +++ b/devtools/bom-descriptor-json/pom.xml @@ -473,6 +473,19 @@ + + io.quarkus + quarkus-dockerfiles + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-elasticsearch-java-client diff --git a/docs/pom.xml b/docs/pom.xml index 8dd5d654a0a7c..73e810aa9bd91 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -484,6 +484,19 @@ + + io.quarkus + quarkus-dockerfiles-deployment + ${project.version} + pom + test + + + * + * + + + io.quarkus quarkus-elasticsearch-java-client-deployment diff --git a/extensions/dockerfiles/cli/pom.xml b/extensions/dockerfiles/cli/pom.xml new file mode 100644 index 0000000000000..5de264e1c08cd --- /dev/null +++ b/extensions/dockerfiles/cli/pom.xml @@ -0,0 +1,110 @@ + + + quarkus-dockerfiles-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-dockerfiles-cli + Quarkus - Dockerfiles - CLI + CLI plugin that provides commands for Dockerfile genration + + + uber-jar + + + + + io.quarkus + quarkus-picocli + + + + io.quarkus + quarkus-arc + + + + io.quarkus + quarkus-devtools-common + + + + io.quarkus + quarkus-bootstrap-maven-resolver + + + + io.quarkus + quarkus-dockerfiles-spi + + + + + io.quarkus + quarkus-picocli-deployment + pom + test + ${project.version} + + + * + * + + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + + + + + src/main/resources + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + generate-code + generate-code-tests + + + true + + ${settings.localRepository} + ${env.MAVEN_OPTS} + + + + + + + maven-compiler-plugin + + + -parameters + + + + + + diff --git a/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/Dockerfiles.java b/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/Dockerfiles.java new file mode 100644 index 0000000000000..6f840f4ad2fc6 --- /dev/null +++ b/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/Dockerfiles.java @@ -0,0 +1,155 @@ +package io.quarkus.dockerfiles.cli; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import io.quarkus.bootstrap.BootstrapException; +import io.quarkus.bootstrap.app.AugmentAction; +import io.quarkus.bootstrap.app.CuratedApplication; +import io.quarkus.bootstrap.app.QuarkusBootstrap; +import io.quarkus.devtools.project.BuildTool; +import io.quarkus.devtools.project.QuarkusProjectHelper; +import io.quarkus.dockerfiles.spi.GeneratedDockerfile; +import io.quarkus.maven.dependency.ArtifactDependency; +import io.quarkus.picocli.runtime.annotations.TopCommand; +import picocli.CommandLine.Command; +import picocli.CommandLine.ExitCode; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +@TopCommand +@Command(name = "dockerfiles", sortOptions = false, mixinStandardHelpOptions = false, header = "Generate Dockerfiles/Containerfiles.", headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "%nOptions:%n") +public class Dockerfiles implements Callable { + + private static final ArtifactDependency QUARKUS_DOCKERFILES = new ArtifactDependency("io.quarkus", "quarkus-dockerfiles", + null, "jar", Dockerfiles.getVersion()); + private static final ArtifactDependency QUARKUS_DOCKERFILES_SPI = new ArtifactDependency("io.quarkus", + "quarkus-dockerfiles-spi", null, "jar", Dockerfiles.getVersion()); + + @Option(names = { + "--jvm" }, paramLabel = "", order = 5, description = "Flag to enable JVM Dockerfile generation. By default a JVM dockerfile is generated unless options imply native.") + boolean generateJvmDockerfile; + + @Option(names = { "--native" }, paramLabel = "", order = 5, description = "Flag to enable Native Dockerfile generation") + boolean generateNativeDockerfile; + + @Parameters(arity = "0..1", paramLabel = "GENERATION_PATH", description = "The path to generate Dockerfiles") + Optional generationPath; + + public Integer call() { + Path projectRoot = getWorkingDirectory(); + BuildTool buildTool = QuarkusProjectHelper.detectExistingBuildTool(projectRoot); + if (buildTool == null) { + System.out.println("Unable to determine the build tool used for the project at " + projectRoot); + return ExitCode.USAGE; + } + Path targetDirecotry = projectRoot.resolve(buildTool.getBuildDirectory()); + QuarkusBootstrap quarkusBootstrap = QuarkusBootstrap.builder() + .setMode(QuarkusBootstrap.Mode.PROD) + .setApplicationRoot(getWorkingDirectory()) + .setProjectRoot(getWorkingDirectory()) + .setTargetDirectory(targetDirecotry) + .setLocalProjectDiscovery(true) + .setIsolateDeployment(false) + .setForcedDependencies(List.of(QUARKUS_DOCKERFILES, QUARKUS_DOCKERFILES_SPI)) + .setBaseClassLoader(ClassLoader.getSystemClassLoader()) + .build(); + + List resultBuildItemFQCNs = new ArrayList<>(); + + boolean hasJvmSuffix = generationPath.map(p -> p.endsWith(".jvm")).orElse(false); + boolean hasNativeSuffix = generationPath.map(p -> p.endsWith(".native")).orElse(false); + boolean isDirectory = generationPath.map(p -> Paths.get(p).toFile().isDirectory()) + .orElse(Paths.get("").toFile().isDirectory()); + + // Checking + if (generateJvmDockerfile && hasNativeSuffix) { + System.out.println("Cannot generate JVM Dockerfile when the path has a .native suffix"); + return ExitCode.USAGE; + } + if (generateNativeDockerfile && hasJvmSuffix) { + System.out.println("Cannot generate Native Dockerfile when the path has a .jvm suffix"); + return ExitCode.USAGE; + } else if (generateJvmDockerfile && generateNativeDockerfile && !isDirectory) { + + } + + if (generateJvmDockerfile || hasJvmSuffix) { + resultBuildItemFQCNs.add(GeneratedDockerfile.Jvm.class.getName()); + } + + if (generateNativeDockerfile || hasNativeSuffix) { + resultBuildItemFQCNs.add(GeneratedDockerfile.Native.class.getName()); + } + + if (resultBuildItemFQCNs.isEmpty()) { + generateJvmDockerfile = true; + resultBuildItemFQCNs.add(GeneratedDockerfile.Jvm.class.getName()); + } + + Path jvmDockerfile = (isDirectory + ? generationPath.map(p -> Paths.get(p)) + : generationPath.map(Paths::get)) + .orElse(Paths.get("Dockerfile.jvm")); + + Path nativeDockerfile = (isDirectory + ? generationPath.map(p -> Paths.get(p)) + : generationPath.map(Paths::get)) + .orElse(Paths.get("Dockerfile.native")); + + try (CuratedApplication curatedApplication = quarkusBootstrap.bootstrap()) { + AugmentAction action = curatedApplication.createAugmentor(); + + action.performCustomBuild(GenerateDockerfilesHandler.class.getName(), new Consumer>() { + @Override + public void accept(List dockerfiles) { + for (GeneratedDockerfile dockerfile : dockerfiles) { + if (dockerfile instanceof GeneratedDockerfile.Jvm) { + writeStringSafe(jvmDockerfile, dockerfile.getContent()); + System.out.println("Generated JVM Dockerfile: " + jvmDockerfile); + } else if (dockerfile instanceof GeneratedDockerfile.Native) { + writeStringSafe(nativeDockerfile, dockerfile.getContent()); + System.out.println("Generated Native Dockerfile: " + nativeDockerfile); + } + } + } + }, resultBuildItemFQCNs.toArray(new String[resultBuildItemFQCNs.size()])); + + } catch (BootstrapException e) { + throw new RuntimeException(e); + } + return ExitCode.OK; + } + + private void writeStringSafe(Path p, String content) { + try { + Files.writeString(p, content); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private Path getWorkingDirectory() { + return Paths.get(System.getProperty("user.dir")); + } + + private static String getVersion() { + return read(Dockerfiles.class.getClassLoader().getResourceAsStream("version")); + } + + private static String read(InputStream is) { + try { + return new String(is.readAllBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/GenerateDockerfilesHandler.java b/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/GenerateDockerfilesHandler.java new file mode 100644 index 0000000000000..3283b3fd818c3 --- /dev/null +++ b/extensions/dockerfiles/cli/src/main/java/io/quarkus/dockerfiles/cli/GenerateDockerfilesHandler.java @@ -0,0 +1,32 @@ +package io.quarkus.dockerfiles.cli; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import io.quarkus.builder.BuildResult; +import io.quarkus.dockerfiles.spi.GeneratedDockerfile; +import io.quarkus.dockerfiles.spi.GeneratedDockerfile.Jvm; +import io.quarkus.dockerfiles.spi.GeneratedDockerfile.Native; + +public class GenerateDockerfilesHandler implements BiConsumer { + + @Override + public void accept(Object context, BuildResult buildResult) { + List dockerfiles = new ArrayList<>(); + + GeneratedDockerfile.Jvm jvmDockerfile = buildResult.consumeOptional(GeneratedDockerfile.Jvm.class); + GeneratedDockerfile.Native nativeDockerfile = buildResult.consumeOptional(GeneratedDockerfile.Native.class); + + if (jvmDockerfile != null) { + dockerfiles.add(jvmDockerfile); + } + + if (nativeDockerfile != null) { + dockerfiles.add(nativeDockerfile); + } + Consumer> consumer = (Consumer>) context; + consumer.accept(dockerfiles); + } +} diff --git a/extensions/dockerfiles/cli/src/main/resources/application.properties b/extensions/dockerfiles/cli/src/main/resources/application.properties new file mode 100644 index 0000000000000..5a1d581bb1394 --- /dev/null +++ b/extensions/dockerfiles/cli/src/main/resources/application.properties @@ -0,0 +1,2 @@ +quarkus.log.level=WARN +quarkus.banner.enabled=false diff --git a/extensions/dockerfiles/cli/src/main/resources/version b/extensions/dockerfiles/cli/src/main/resources/version new file mode 100644 index 0000000000000..f2ab45c3b0ef0 --- /dev/null +++ b/extensions/dockerfiles/cli/src/main/resources/version @@ -0,0 +1 @@ +${project.version} \ No newline at end of file diff --git a/extensions/dockerfiles/deployment/pom.xml b/extensions/dockerfiles/deployment/pom.xml new file mode 100644 index 0000000000000..c90207ed2b816 --- /dev/null +++ b/extensions/dockerfiles/deployment/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + + io.quarkus + quarkus-dockerfiles-parent + 999-SNAPSHOT + ../pom.xml + + quarkus-dockerfiles-deployment + Quarkus - Dockerfiles - Deployment + + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-qute-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-dockerfiles-spi + + + io.quarkus + quarkus-dockerfiles + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + -AlegacyConfigRoot=true + + + + + + diff --git a/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfileContent.java b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfileContent.java new file mode 100644 index 0000000000000..047e1f7847113 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfileContent.java @@ -0,0 +1,53 @@ +package io.quarkus.dockerfiles.deployment; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Map; + +import io.quarkus.qute.Qute; +import io.quarkus.qute.Qute.Fmt; + +public class DockerfileContent { + + public static final String FROM = "from"; + public static final String TYPE = "type"; + public static final String APPLICATION_NAME = "application-name"; + public static final String OUTPUT_DIR = "output-dir"; + + public static String getJvmDockerfileContent(Map data) { + Fmt fmt = Qute.fmt(readResource("Dockerfile.tpl.qute.jvm")); + for (Map.Entry entry : data.entrySet()) { + fmt = fmt.data(entry.getKey(), entry.getValue()); + } + return fmt.render(); + } + + public static String getJvmDockerfileContent(String from, String name, Path outputDir) { + return getJvmDockerfileContent( + Map.of(FROM, from, TYPE, "jvm", APPLICATION_NAME, name, OUTPUT_DIR, outputDir.toString())); + } + + public static String getNativeDockerfileContent(Map data) { + Fmt fmt = Qute.fmt(readResource("Dockerfile.tpl.qute.native")); + for (Map.Entry entry : data.entrySet()) { + fmt = fmt.data(entry.getKey(), entry.getValue()); + } + return fmt.render(); + } + + public static String getNativeDockerfileContent(String from, String name, Path outputDir) { + return getNativeDockerfileContent( + Map.of(FROM, from, TYPE, "native", APPLICATION_NAME, name, OUTPUT_DIR, outputDir.toString())); + } + + private static String readResource(String resource) { + try (InputStream in = DockerfileContent.class.getClassLoader().getResourceAsStream(resource)) { + return new String(in.readAllBytes(), Charset.defaultCharset()); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } +} diff --git a/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesConfiguration.java b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesConfiguration.java new file mode 100644 index 0000000000000..5e0dd77142e23 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesConfiguration.java @@ -0,0 +1,43 @@ +package io.quarkus.dockerfiles.deployment; + +import java.util.Optional; + +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot +public class DockerfilesConfiguration { + + static final String DEFAULT_JVM_FROM = "registry.access.redhat.com/ubi8/openjdk-21:1.20"; + static final String DEFAULT_NATIVE_FROM = "registry.access.redhat.com/ubi8/ubi-minimal:8.10"; + + /** + * The from image to use for JVM based Dockerfiles + */ + @ConfigItem(defaultValue = DEFAULT_JVM_FROM) + String jvmFrom; + + /** + * The from image to use for native based Dockerfiles + */ + @ConfigItem(defaultValue = DEFAULT_NATIVE_FROM) + String nativeFrom; + + static String getDefaultJvmFrom() { + return DEFAULT_JVM_FROM; + } + + Optional getConfiguredJvmFrom() { + return ConfigProvider.getConfig().getOptionalValue("quarkus.dockerfiles.jvm-from", String.class); + } + + static String getDefaultNativeFrom() { + return DEFAULT_NATIVE_FROM; + } + + Optional getConfiguredNativeFrom() { + return ConfigProvider.getConfig().getOptionalValue("quarkus.dockerfiles.native-from", String.class); + } +} diff --git a/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesProcessor.java b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesProcessor.java new file mode 100644 index 0000000000000..3318b3c48e667 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/main/java/io/quarkus/dockerfiles/deployment/DockerfilesProcessor.java @@ -0,0 +1,57 @@ +package io.quarkus.dockerfiles.deployment; + +import java.util.Optional; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.dockerfiles.spi.GeneratedDockerfile; +import io.quarkus.dockerfiles.spi.JvmDockerfileFrom; +import io.quarkus.dockerfiles.spi.NativeDockerfileFrom; + +class DockerfilesProcessor { + + private static final String FEATURE = "dockerfiles"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + JvmDockerfileFrom.Effective effectiveJvmDockerfile(DockerfilesConfiguration config, + Optional selected) { + // 1. Get FROM explicitly configured + // 2. Get FROM using BuildItems + // 3. Get FROM using default value + String from = config.getConfiguredJvmFrom() + .orElse(selected.map(JvmDockerfileFrom.Selected::getFrom).orElse(DockerfilesConfiguration.getDefaultJvmFrom())); + return new JvmDockerfileFrom.Effective(from); + } + + @BuildStep + NativeDockerfileFrom.Effective effectiveNativeDockerfile(DockerfilesConfiguration config, + Optional selected) { + // 1. Get FROM explicitly configured + // 2. Get FROM using BuildItems + // 3. Get FROM using default value + String from = config.getConfiguredNativeFrom().orElse( + selected.map(NativeDockerfileFrom.Selected::getFrom).orElse(DockerfilesConfiguration.getDefaultNativeFrom())); + return new NativeDockerfileFrom.Effective(from); + } + + @BuildStep + GeneratedDockerfile.Jvm buildJvmDockerfile(JvmDockerfileFrom.Effective effective, ApplicationInfoBuildItem applicationInfo, + OutputTargetBuildItem outputTarget) { + return new GeneratedDockerfile.Jvm(DockerfileContent.getJvmDockerfileContent(effective.getFrom(), + applicationInfo.getName(), outputTarget.getOutputDirectory())); + } + + @BuildStep + GeneratedDockerfile.Native buildNativeDockerfile(NativeDockerfileFrom.Effective effective, + ApplicationInfoBuildItem applicationInfo, OutputTargetBuildItem outputTarget) { + return new GeneratedDockerfile.Native(DockerfileContent.getNativeDockerfileContent(effective.getFrom(), + applicationInfo.getName(), outputTarget.getOutputDirectory())); + } +} diff --git a/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.jvm b/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.jvm new file mode 100644 index 0000000000000..27bba2fabd248 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.jvm @@ -0,0 +1,95 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# {#insert quarkusbuild /} +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.{type} -t quarkus/{application-name}-{type} . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/{application-name}-{type} +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/{application-name}-{type} +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM {from} + +ENV LANGUAGE='en_US:en' + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 {output-dir}/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 {output-dir}/quarkus-app/*.jar /deployments/ +COPY --chown=185 {output-dir}/quarkus-app/app/ /deployments/app/ +COPY --chown=185 {output-dir}/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 + +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.native b/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.native new file mode 100644 index 0000000000000..dd2a17091aa4a --- /dev/null +++ b/extensions/dockerfiles/deployment/src/main/resources/Dockerfile.tpl.qute.native @@ -0,0 +1,27 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# {buildtool.cli} {buildtool.cmd.package-native} +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/{project.artifact-id} . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/{project.artifact-id} +# +### +FROM {dockerfile.native.from} +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root {buildtool.build-dir}/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfileContentTest.java b/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfileContentTest.java new file mode 100644 index 0000000000000..645f5d36c1a63 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfileContentTest.java @@ -0,0 +1,26 @@ +package io.quarkus.dockerfiles.deployment; + +import java.nio.file.Paths; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class DockerfileContentTest { + + // Start unit test with your extension loaded + @RegisterExtension + static final QuarkusUnitTest unitTest = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void shouldRenderDockerfileJvm() { + Assertions.assertNotNull( + DockerfileContent.getJvmDockerfileContent("registry.access.redhat.com/ubi8/openjdk-21:1.19", "my-app", + Paths.get("target"))); + } +} diff --git a/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfilesDevModeTest.java b/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfilesDevModeTest.java new file mode 100644 index 0000000000000..8faa2a8286c53 --- /dev/null +++ b/extensions/dockerfiles/deployment/src/test/java/io/quarkus/dockerfiles/deployment/DockerfilesDevModeTest.java @@ -0,0 +1,23 @@ +package io.quarkus.dockerfiles.deployment; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; + +public class DockerfilesDevModeTest { + + // Start hot reload (DevMode) test with your extension loaded + @RegisterExtension + static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void writeYourOwnDevModeTest() { + // Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information + Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName()); + } +} diff --git a/extensions/dockerfiles/pom.xml b/extensions/dockerfiles/pom.xml new file mode 100644 index 0000000000000..1a3e6e45f16a0 --- /dev/null +++ b/extensions/dockerfiles/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + + io.quarkus + quarkus-extensions-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-dockerfiles-parent + pom + Quarkus - DockerFiles + + + spi + deployment + runtime + cli + + + diff --git a/extensions/dockerfiles/runtime/pom.xml b/extensions/dockerfiles/runtime/pom.xml new file mode 100644 index 0000000000000..88ac2d4a742c5 --- /dev/null +++ b/extensions/dockerfiles/runtime/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + + io.quarkus + quarkus-dockerfiles-parent + 999-SNAPSHOT + ../pom.xml + + quarkus-dockerfiles + Quakrus - Dockerfiles - Runtime + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-qute + + + + + + + io.quarkus + quarkus-extension-maven-plugin + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/dockerfiles/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/dockerfiles/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000000..fdb7124cef1c2 --- /dev/null +++ b/extensions/dockerfiles/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +--- +artifact: ${project.groupId}:${project.artifactId}:${project.version} +name: "Dokcerfiles" +metadata: + keywords: + - "dockerfiles" + categories: + - "cloud" + status: "stable" diff --git a/extensions/dockerfiles/spi/pom.xml b/extensions/dockerfiles/spi/pom.xml new file mode 100644 index 0000000000000..f448f21c69d03 --- /dev/null +++ b/extensions/dockerfiles/spi/pom.xml @@ -0,0 +1,33 @@ + + + quarkus-dockerfiles-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-dockerfiles-spi + Quarkus - Dockerfiles - SPI + SPI for Dockerfile genration + + + + io.quarkus + quarkus-core-deployment + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + + diff --git a/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/GeneratedDockerfile.java b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/GeneratedDockerfile.java new file mode 100644 index 0000000000000..871e95ca6f1e1 --- /dev/null +++ b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/GeneratedDockerfile.java @@ -0,0 +1,32 @@ +package io.quarkus.dockerfiles.spi; + +import io.quarkus.builder.item.SimpleBuildItem; + +public interface GeneratedDockerfile { + + String getContent(); + + public static final class Jvm extends SimpleBuildItem implements GeneratedDockerfile { + private final String content; + + public Jvm(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + } + + public static final class Native extends SimpleBuildItem implements GeneratedDockerfile { + private final String content; + + public Native(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + } +} diff --git a/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/JvmDockerfileFrom.java b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/JvmDockerfileFrom.java new file mode 100644 index 0000000000000..13e877536776c --- /dev/null +++ b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/JvmDockerfileFrom.java @@ -0,0 +1,32 @@ +package io.quarkus.dockerfiles.spi; + +import io.quarkus.builder.item.SimpleBuildItem; + +public interface JvmDockerfileFrom { + + String getFrom(); + + public static final class Selected extends SimpleBuildItem implements JvmDockerfileFrom { + private final String from; + + public Selected(String from) { + this.from = from; + } + + public String getFrom() { + return from; + } + } + + public static final class Effective extends SimpleBuildItem implements JvmDockerfileFrom { + private final String from; + + public Effective(String from) { + this.from = from; + } + + public String getFrom() { + return from; + } + } +} diff --git a/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/NativeDockerfileFrom.java b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/NativeDockerfileFrom.java new file mode 100644 index 0000000000000..4902a16e7d5b8 --- /dev/null +++ b/extensions/dockerfiles/spi/src/main/java/io/quarkus/dockerfiles/spi/NativeDockerfileFrom.java @@ -0,0 +1,32 @@ +package io.quarkus.dockerfiles.spi; + +import io.quarkus.builder.item.SimpleBuildItem; + +public interface NativeDockerfileFrom { + + String getFrom(); + + public static final class Selected extends SimpleBuildItem implements NativeDockerfileFrom { + private final String from; + + public Selected(String from) { + this.from = from; + } + + public String getFrom() { + return from; + } + } + + public static final class Effective extends SimpleBuildItem implements NativeDockerfileFrom { + private final String from; + + public Effective(String from) { + this.from = from; + } + + public String getFrom() { + return from; + } + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index 7f86c1174b30f..62675b0f78876 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -191,6 +191,9 @@ openshift-client kubernetes-service-binding + + dockerfiles + flyway flyway-postgresql