diff --git a/aot-plugin/src/main/java/io/micronaut/gradle/aot/MicronautAotPlugin.java b/aot-plugin/src/main/java/io/micronaut/gradle/aot/MicronautAotPlugin.java index 7a265ee0..4eb003d8 100644 --- a/aot-plugin/src/main/java/io/micronaut/gradle/aot/MicronautAotPlugin.java +++ b/aot-plugin/src/main/java/io/micronaut/gradle/aot/MicronautAotPlugin.java @@ -238,16 +238,12 @@ private void registerDockerImage(Project project, TaskProvider optimizedJar }); } else { dockerImages.create("optimized", image -> { + MicronautDockerPlugin.createDependencyLayers(image, project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME)); image.addLayer(layer -> { layer.getLayerKind().set(LayerKind.APP); layer.getRuntimeKind().set(runtime == OptimizerIO.TargetRuntime.JIT ? RuntimeKind.JIT : RuntimeKind.NATIVE); layer.getFiles().from(optimizedRunnerJar); }); - image.addLayer(layer -> { - layer.getLayerKind().set(LayerKind.LIBS); - layer.getFiles().from(layer.getFiles().from(project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME)) - ); - }); }); } } diff --git a/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCCheckpointDockerfile.java b/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCCheckpointDockerfile.java index 28d497aa..6a30eb2e 100644 --- a/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCCheckpointDockerfile.java +++ b/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCCheckpointDockerfile.java @@ -2,8 +2,11 @@ import com.bmuschko.gradle.docker.tasks.image.Dockerfile; import io.micronaut.gradle.docker.DockerBuildStrategy; +import io.micronaut.gradle.docker.MicronautDockerfile; +import io.micronaut.gradle.docker.model.Layer; import org.gradle.api.GradleException; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; @@ -16,6 +19,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.jvm.toolchain.JavaLanguageVersion; +import javax.inject.Inject; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -24,6 +28,7 @@ import static io.micronaut.gradle.crac.MicronautCRaCPlugin.ARM_ARCH; import static io.micronaut.gradle.crac.MicronautCRaCPlugin.X86_64_ARCH; +import static io.micronaut.gradle.docker.MicronautDockerfile.applyStandardTransforms; @CacheableTask public abstract class CRaCCheckpointDockerfile extends Dockerfile { @@ -59,6 +64,25 @@ public abstract class CRaCCheckpointDockerfile extends Dockerfile { @Input public abstract Property getJavaVersion(); + /** + * The layers to copy to the image. + * @return the layers + */ + @Input + public abstract ListProperty getLayers(); + + /** + * If true, the COPY command will use --link option when copying files from the build context. + * Defaults to false. + * @return The use copy link property + */ + @Input + @Optional + public abstract Property getUseCopyLink(); + + @Inject + protected abstract ObjectFactory getObjects(); + @SuppressWarnings("java:S5993") // Gradle API public CRaCCheckpointDockerfile() { setGroup(BasePlugin.BUILD_GROUP); @@ -86,9 +110,11 @@ public void create() throws IOException { } } super.create(); + applyStandardTransforms(getUseCopyLink(), getObjects(), this); getProject().getLogger().lifecycle("Checkpoint Dockerfile written to: {}", getDestFile().get().getAsFile().getAbsolutePath()); } + @SuppressWarnings("java:S5738") // Using deprecated method still, until it's removal in 4.0.0 private void setupInstructions(List additionalInstructions) { DockerBuildStrategy strategy = this.getBuildStrategy().getOrElse(DockerBuildStrategy.DEFAULT); @@ -177,10 +203,7 @@ static void setupResources(CRaCCheckpointDockerfile task) { " && rm \"$name\""); task.instruction("# Copy layers"); - task.copyFile("layers/libs", workDir + "/libs"); - task.copyFile("layers/classes", workDir + "/classes"); - task.copyFile("layers/resources", workDir + "/resources"); - task.copyFile("layers/application.jar", workDir + "/application.jar"); + MicronautDockerfile.setupResources(task, task.getLayers().get(), workDir); task.instruction("# Add build scripts"); task.copyFile("scripts/checkpoint.sh", workDir + "/checkpoint.sh"); diff --git a/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCFinalDockerfile.java b/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCFinalDockerfile.java index b6e685d4..6e23afbc 100644 --- a/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCFinalDockerfile.java +++ b/crac-plugin/src/main/java/io/micronaut/gradle/crac/CRaCFinalDockerfile.java @@ -99,10 +99,7 @@ private void setupResources() { copyFile("--from=" + createCheckpointImageName(getProject()) + " /azul-crac-jdk", "/azul-crac-jdk"); instruction("# Copy layers"); copyFile("cr", workDir + "/cr"); - copyFile("layers/libs", workDir + "/libs"); - copyFile("layers/classes", workDir + "/classes"); - copyFile("layers/resources", workDir + "/resources"); - copyFile("layers/application.jar", workDir + "/application.jar"); + MicronautDockerfile.setupResources(this, getLayers().get(), workDir); copyFile("scripts/run.sh", workDir + "/run.sh"); } } diff --git a/crac-plugin/src/main/java/io/micronaut/gradle/crac/MicronautCRaCPlugin.java b/crac-plugin/src/main/java/io/micronaut/gradle/crac/MicronautCRaCPlugin.java index 0f606eb2..b84933f5 100644 --- a/crac-plugin/src/main/java/io/micronaut/gradle/crac/MicronautCRaCPlugin.java +++ b/crac-plugin/src/main/java/io/micronaut/gradle/crac/MicronautCRaCPlugin.java @@ -152,6 +152,7 @@ private CheckpointTasksOfNote configureCheckpointDockerBuild(Project project, task.getArch().set(configuration.getArch()); task.getJavaVersion().set(configuration.getJavaVersion()); task.setupDockerfileInstructions(); + task.getLayers().convention(buildLayersTask.flatMap(BuildLayersTask::getLayers)); }); TaskProvider dockerBuildTask = tasks.register(adaptTaskName("checkpointBuildImage", imageName), DockerBuildImage.class, task -> { diff --git a/docker-plugin/build.gradle b/docker-plugin/build.gradle index 0dbf2332..4917fce8 100644 --- a/docker-plugin/build.gradle +++ b/docker-plugin/build.gradle @@ -15,4 +15,5 @@ dependencies { compileOnly libs.graalvmPlugin testImplementation testFixtures(project(":micronaut-minimal-plugin")) + testImplementation libs.mockserver.netty } diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/DockerfileEditor.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/DockerfileEditor.java index 53b3b431..7b2e50ff 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/DockerfileEditor.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/DockerfileEditor.java @@ -30,12 +30,12 @@ import java.util.List; import java.util.Optional; -abstract class DockerfileEditor { +public abstract class DockerfileEditor { private DockerfileEditor() { } - static void apply(ObjectFactory objects, Dockerfile task, List> actions) { + public static void apply(ObjectFactory objects, Dockerfile task, List> actions) { try { Path dockerFile = task.getDestFile().get().getAsFile().toPath(); List lines = Files.readAllLines( diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerPlugin.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerPlugin.java index 9b6aaa30..3b13692d 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerPlugin.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerPlugin.java @@ -25,6 +25,8 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.component.ProjectComponentIdentifier; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.BasePlugin; @@ -63,19 +65,54 @@ public void apply(Project project) { dockerImages.all(image -> createDockerImage(project, image)); TaskProvider runnerJar = createMainRunnerJar(project, tasks); dockerImages.create("main", image -> { + createDependencyLayers(image, project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME)); image.addLayer(layer -> { layer.getLayerKind().set(LayerKind.APP); layer.getFiles().from(runnerJar); }); - image.addLayer(layer -> { - layer.getLayerKind().set(LayerKind.LIBS); - layer.getFiles().from(project.getConfigurations().getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME)); - }); - image.addLayer(layer -> { - layer.getLayerKind().set(LayerKind.EXPANDED_RESOURCES); - layer.getFiles().from(project.getExtensions().getByType(SourceSetContainer.class) - .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getOutput().getResourcesDir()); - }); + }); + } + + public static void createDependencyLayers(MicronautDockerImage image, Configuration configuration) { + var projectLibs = configuration.getIncoming() + .artifactView(view -> { + view.lenient(true); + view.componentFilter(ProjectComponentIdentifier.class::isInstance); + }).getFiles(); + var snapshotLibs = configuration.getIncoming() + .artifactView(view -> { + view.lenient(true); + view.componentFilter(component -> { + if (component instanceof ModuleComponentIdentifier module) { + return module.getVersion().endsWith("-SNAPSHOT"); + } + return !(component instanceof ProjectComponentIdentifier); + }); + }).getFiles(); + var allOtherLibs = configuration.getIncoming() + .artifactView(view -> { + view.lenient(true); + view.componentFilter(component -> { + if (component instanceof ModuleComponentIdentifier module) { + return !module.getVersion().endsWith("-SNAPSHOT"); + } + return !(component instanceof ProjectComponentIdentifier); + }); + }).getFiles(); + // First, all dependencies that are not snapshots + image.addLayer(layer -> { + layer.getLayerKind().set(LayerKind.LIBS); + layer.getFiles().from(allOtherLibs); + }); + // Then all snapshots + image.addLayer(layer -> { + layer.getLayerKind().set(LayerKind.SNAPSHOT_LIBS); + layer.getFiles().from(snapshotLibs); + }); + // Finally, all project dependencies + image.addLayer(layer -> { + layer.getLayerKind().set(LayerKind.PROJECT_LIBS); + layer.getFiles().from(projectLibs); }); } @@ -149,10 +186,10 @@ private TaskProvider createMainRunnerJar(Project project, TaskContainer tas jar.dependsOn(tasks.findByName("classes")); jar.getArchiveClassifier().set("runner"); SourceSetContainer sourceSets = project - .getExtensions().getByType(SourceSetContainer.class); + .getExtensions().getByType(SourceSetContainer.class); SourceSet mainSourceSet = sourceSets - .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); FileCollection dirs = mainSourceSet.getOutput().getClassesDirs(); @@ -164,7 +201,7 @@ private TaskProvider createMainRunnerJar(Project project, TaskContainer tas attrs.put("Class-Path", project.getProviders().provider(() -> { List classpath = new ArrayList<>(); Configuration runtimeClasspath = project.getConfigurations() - .getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME); + .getByName(RUNTIME_CLASSPATH_CONFIGURATION_NAME); classpath.add("resources/"); classpath.add("classes/"); @@ -204,6 +241,7 @@ private Optional> configureDockerBuild(Project task.setDescription("Builds a Docker File for image " + imageName); task.getDestFile().set(targetDockerFile); task.setupDockerfileInstructions(); + task.getLayers().convention(buildLayersTask.flatMap(BuildLayersTask::getLayers)); }); } TaskProvider dockerBuildTask = tasks.register(adaptTaskName("dockerBuild", imageName), DockerBuildImage.class, task -> { @@ -246,12 +284,14 @@ private TaskProvider configureNativeDockerBuild(Project p throw new GradleException("Unable to configure docker task for image " + imageName, e); } task.getDestFile().set(targetDockerFile); + task.getLayers().convention(buildLayersTask.flatMap(BuildLayersTask::getLayers)); }); } else { dockerFileTask = tasks.register(dockerfileNativeTaskName, NativeImageDockerfile.class, task -> { task.setGroup(BasePlugin.BUILD_GROUP); task.setDescription("Builds a Native Docker File for image " + imageName); task.getDestFile().set(targetDockerFile); + task.getLayers().convention(buildLayersTask.flatMap(BuildLayersTask::getLayers)); }); } TaskProvider prepareContext = tasks.register(adaptTaskName("dockerPrepareContext", imageName), PrepareDockerContext.class, context -> { @@ -259,7 +299,7 @@ private TaskProvider configureNativeDockerBuild(Project p // copy the configuration file directories into the build context context.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("docker/native-" + imageName + "/config-dirs")); context.getInputDirectories().from(dockerFileTask.map(t -> t.getNativeImageOptions() - .map(NativeImageOptions::getConfigurationFileDirectories).get() // drop dependency on building image + .map(NativeImageOptions::getConfigurationFileDirectories).get() // drop dependency on building image )); }); TaskProvider dockerBuildTask = tasks.register(adaptTaskName("dockerBuildNative", imageName), DockerBuildImage.class, task -> { @@ -290,20 +330,20 @@ private TaskProvider configureNativeDockerBuild(Project p }); TaskProvider buildLambdaZip = taskContainer.register(adaptTaskName("buildNativeLambda", imageName), DockerCopyFileFromContainer.class); Provider lambdaZip = project.getLayout() - .getBuildDirectory() - .dir("libs") - .map(dir -> dir.file(project.getName() + "-" + project.getVersion() + "-" + simpleNameOf("lambda", imageName) + ".zip").getAsFile().getAbsolutePath()); + .getBuildDirectory() + .dir("libs") + .map(dir -> dir.file(project.getName() + "-" + project.getVersion() + "-" + simpleNameOf("lambda", imageName) + ".zip").getAsFile().getAbsolutePath()); TaskProvider removeContainer = taskContainer.register(adaptTaskName("destroyLambdaContainer", imageName), DockerRemoveContainer.class); removeContainer.configure(task -> { task.mustRunAfter(buildLambdaZip); task.getContainerId().set( - createLambdaContainer.flatMap(DockerCreateContainer::getContainerId) + createLambdaContainer.flatMap(DockerCreateContainer::getContainerId) ); }); buildLambdaZip.configure(task -> { task.dependsOn(createLambdaContainer); task.getContainerId().set( - createLambdaContainer.flatMap(DockerCreateContainer::getContainerId) + createLambdaContainer.flatMap(DockerCreateContainer::getContainerId) ); task.getRemotePath().set("/function/function.zip"); task.getHostPath().set(lambdaZip); diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerfile.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerfile.java index dfc83c80..2dab7bf3 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerfile.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/MicronautDockerfile.java @@ -1,6 +1,7 @@ package io.micronaut.gradle.docker; import com.bmuschko.gradle.docker.tasks.image.Dockerfile; +import io.micronaut.gradle.docker.model.Layer; import org.gradle.api.JavaVersion; import org.gradle.api.Project; import org.gradle.api.model.ObjectFactory; @@ -39,6 +40,22 @@ public abstract class MicronautDockerfile extends Dockerfile implements DockerBu @Input private final Property targetWorkingDirectory; + /** + * The layers to copy to the image. + * @return the layers + */ + @Input + public abstract ListProperty getLayers(); + + /** + * If true, the COPY command will use --link option when copying files from the build context. + * Defaults to false. + * @return The use copy link property + */ + @Input + @Optional + public abstract Property getUseCopyLink(); + public MicronautDockerfile() { Project project = getProject(); setGroup(BasePlugin.BUILD_GROUP); @@ -77,12 +94,21 @@ protected Provider> getTweaks() { @Override public void create() throws IOException { super.create(); + applyStandardTransforms(getUseCopyLink(), getObjects(), this); if (getDockerfileTweaks().isPresent()) { DockerfileEditor.apply(getObjects(), this, getDockerfileTweaks().get()); } getLogger().lifecycle("Dockerfile written to: {}", getDestFile().get().getAsFile().getAbsolutePath()); } + public static void applyStandardTransforms(Provider useCopyLink, ObjectFactory objects, Dockerfile task) { + if (Boolean.TRUE.equals(useCopyLink.getOrElse(true))) { + DockerfileEditor.apply(objects, task, List.of( + editor -> editor.replaceRegex("COPY (?!--link)(.*)", "COPY --link $1") + )); + } + } + protected void setupInstructions(List additionalInstructions) { String workDir = getTargetWorkingDirectory().get(); DockerBuildStrategy buildStrategy = this.buildStrategy.getOrElse(DockerBuildStrategy.DEFAULT); @@ -96,11 +122,7 @@ protected void setupInstructions(List additionalInstructions) { javaApplication.getMainClass().set("com.fnproject.fn.runtime.EntryPoint"); from(new Dockerfile.From(from != null ? from : "fnproject/fn-java-fdk:" + getProjectFnVersion())); workingDir("/function"); - runCommand("mkdir -p /function/app/resources"); - copyFile("layers/libs/*.jar", "/function/app/"); - copyFile("layers/classes", "/function/app/classes"); - copyFile("layers/resources", "/function/app/resources"); - copyFile("layers/application.jar", "/function/app/"); + setupResources(this, getLayers().get(), "function"); String cmd = this.defaultCommand.get(); if ("none".equals(cmd)) { super.defaultCommand("io.micronaut.oraclecloud.function.http.HttpFunction::handleRequest"); @@ -112,7 +134,7 @@ protected void setupInstructions(List additionalInstructions) { javaApplication.getMainClass().set("io.micronaut.function.aws.runtime.MicronautLambdaRuntime"); default: from(new Dockerfile.From(from != null ? from : DEFAULT_BASE_IMAGE)); - setupResources(this); + setupResources(this, getLayers().get(), null); exposePort(exposedPorts); getInstructions().addAll(additionalInstructions); if (getInstructions().get().stream().noneMatch(instruction -> instruction.getKeyword().equals(EntryPointInstruction.KEYWORD))) { @@ -198,15 +220,25 @@ private String getProjectFnVersion() { return "latest"; } - static void setupResources(Dockerfile task) { + public static void setupResources(Dockerfile task, List layers, String workDir) { + if (workDir == null) { + workDir = determineWorkingDir(task); + } + task.workingDir(workDir); + for (Layer layer : layers) { + var files = layer.getFiles(); + if (!files.isEmpty()) { + var kind = layer.getLayerKind().get(); + task.copyFile("layers/" + kind.sourceDirName(), workDir + "/" + kind.targetDirName()); + } + } + } + + private static String determineWorkingDir(Dockerfile task) { String workDir = DEFAULT_WORKING_DIR; if (task instanceof DockerBuildOptions dbo) { workDir = dbo.getTargetWorkingDirectory().get(); } - task.workingDir(workDir); - task.copyFile("layers/libs", workDir + "/libs"); - task.copyFile("layers/classes", workDir + "/classes"); - task.copyFile("layers/resources", workDir + "/resources"); - task.copyFile("layers/application.jar", workDir + "/application.jar"); + return workDir; } } diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/NativeImageDockerfile.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/NativeImageDockerfile.java index dfe8d1ff..2c981a6a 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/NativeImageDockerfile.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/NativeImageDockerfile.java @@ -3,6 +3,7 @@ import com.bmuschko.gradle.docker.tasks.image.Dockerfile; import io.micronaut.gradle.DefaultVersions; import io.micronaut.gradle.PluginsHelper; +import io.micronaut.gradle.docker.model.Layer; import io.micronaut.gradle.docker.tasks.DockerResourceConfigDirectoryNamer; import io.micronaut.gradle.graalvm.NativeLambdaExtension; import org.graalvm.buildtools.gradle.NativeImagePlugin; @@ -46,6 +47,7 @@ import static io.micronaut.gradle.PluginsHelper.findMicronautExtension; import static io.micronaut.gradle.docker.MicronautDockerfile.DEFAULT_WORKING_DIR; +import static io.micronaut.gradle.docker.MicronautDockerfile.applyStandardTransforms; /** * Specialization of {@link Dockerfile} for building native images. @@ -144,10 +146,26 @@ public abstract class NativeImageDockerfile extends Dockerfile implements Docker @Input @Optional - protected Provider> getTweaks() { + public Provider> getTweaks() { return getDockerfileTweaks().map(tweaks -> DockerfileEditor.fingerprintOf(getObjects(), tweaks)); } + /** + * The layers to copy to the image. + * @return the layers + */ + @Input + public abstract ListProperty getLayers(); + + /** + * If true, the COPY command will use --link option when copying files from the build context. + * Defaults to false. + * @return The use copy link property + */ + @Input + @Optional + public abstract Property getUseCopyLink(); + public NativeImageDockerfile() { Project project = getProject(); JavaPluginExtension javaExtension = PluginsHelper.javaPluginExtensionOf(project); @@ -398,6 +416,7 @@ private static int toSupportedJavaVersion(int version) { @Override public void create() throws IOException { super.create(); + applyStandardTransforms(getUseCopyLink(), getObjects(), this); if (getDockerfileTweaks().isPresent()) { DockerfileEditor.apply(getObjects(), this, getDockerfileTweaks().get()); } @@ -432,7 +451,7 @@ private void setupInstructions(List additionalInstructions) { from(new From(getGraalImage().get()).withStage("graalvm")); } - MicronautDockerfile.setupResources(this); + MicronautDockerfile.setupResources(this, getLayers().get(), null); Property executable = getObjects().property(String.class); executable.set("application"); String workDir = getTargetWorkingDirectory().get(); diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/DefaultEditor.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/DefaultEditor.java index 6db0e543..8fa519b1 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/DefaultEditor.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/DefaultEditor.java @@ -107,6 +107,17 @@ public void replace(String line, String replacement) { } } + @Override + public void replaceRegex(String regex, String replacement) { + Boundaries boundaries = getBoundaries(); + int startIndex = boundaries.getStart().orElse(0); + int endIndex = boundaries.getEnd().orElse(lines.size()); + for (int idx = startIndex; idx < endIndex; idx++) { + var line = lines.get(idx); + lines.set(idx, line.replaceAll(regex, replacement)); + } + } + @Override public void insert(String... lines) { int startIndex = getBoundaries().getStart().orElse(0); diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/Editor.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/Editor.java index e9e539be..831321a5 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/Editor.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/Editor.java @@ -52,6 +52,15 @@ public interface Editor { */ void replace(String line, String replacement); + /** + * Replaces strings matching the current lower and upper bounds with the + * replacement, if they match the line pattern. + * + * @param regex the regex to look for + * @param replacement the replacement + */ + void replaceRegex(String regex, String replacement); + /** * Inserts the provided lines after the current lower bound. * @param lines the lines to be inserted diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/FingerprintingEditor.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/FingerprintingEditor.java index 10750e46..5a4a507f 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/FingerprintingEditor.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/editor/FingerprintingEditor.java @@ -70,6 +70,13 @@ public void replace(String line, String replacement) { fingerprint.add(replacement); } + @Override + public void replaceRegex(String regex, String replacement) { + fingerprint.add("REPLACE_REGEX"); + fingerprint.add(regex); + fingerprint.add(replacement); + } + @Override public void insert(String... lines) { fingerprint.add("INSERT"); diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/model/LayerKind.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/model/LayerKind.java index 6081bc10..c2f1508c 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/model/LayerKind.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/model/LayerKind.java @@ -16,8 +16,24 @@ package io.micronaut.gradle.docker.model; public enum LayerKind { - LIBS, - APP, - EXPANDED_CLASSES, - EXPANDED_RESOURCES + PROJECT_LIBS("project_libs", "libs"), + SNAPSHOT_LIBS("snapshot_libs", "libs"), + LIBS("libs", "libs"), + APP("app", ""); + + private final String sourceDirName; + private final String targetDirName; + + LayerKind(String sourceDirName, String targetDirName) { + this.sourceDirName = sourceDirName; + this.targetDirName = targetDirName; + } + + public String targetDirName() { + return targetDirName; + } + + public String sourceDirName() { + return sourceDirName; + } } diff --git a/docker-plugin/src/main/java/io/micronaut/gradle/docker/tasks/BuildLayersTask.java b/docker-plugin/src/main/java/io/micronaut/gradle/docker/tasks/BuildLayersTask.java index 0425ac3d..23168ca6 100644 --- a/docker-plugin/src/main/java/io/micronaut/gradle/docker/tasks/BuildLayersTask.java +++ b/docker-plugin/src/main/java/io/micronaut/gradle/docker/tasks/BuildLayersTask.java @@ -19,6 +19,8 @@ import org.gradle.api.tasks.TaskAction; import javax.inject.Inject; +import java.io.IOException; +import java.nio.file.Files; @CacheableTask public abstract class BuildLayersTask extends DefaultTask { @@ -41,19 +43,13 @@ public void action() { FileOperations fileOperations = getFileOperations(); fileOperations.delete(getOutputDir()); // Create folders if case there are no resources/libs in project - Provider libsDir = getOutputDir().dir("libs"); - fileOperations.mkdir(libsDir); - Provider resourcesDir = getOutputDir().dir("resources"); - fileOperations.mkdir(resourcesDir); - Provider classesDir = getOutputDir().dir("classes"); - fileOperations.mkdir(classesDir); for (Layer layer : getLayers().get()) { - final Provider layerDir = layerDirectoryOf(layer, getOutputDir(), libsDir, resourcesDir, classesDir); + final Provider layerDir = layerDirectoryOf(layer, getOutputDir()); if (layer.getLayerKind().get() == LayerKind.APP) { // special case for now fileOperations.copy(copy -> { configureDuplicatesStrategy(copy); - copy.from(layer.getFiles()).into(getOutputDir()).rename(s -> "application.jar"); + copy.from(layer.getFiles()).into(getOutputDir().dir("app")).rename(s -> "application.jar"); }); } else { fileOperations.copy(copy -> { @@ -71,15 +67,14 @@ private void configureDuplicatesStrategy(CopySpec copy) { } private static Provider layerDirectoryOf(Layer layer, - Provider appDir, - Provider libsDir, - Provider resourcesDir, - Provider classesDir) { - return switch (layer.getLayerKind().get()) { - case APP -> appDir; - case LIBS -> libsDir; - case EXPANDED_CLASSES -> classesDir; - case EXPANDED_RESOURCES -> resourcesDir; - }; + DirectoryProperty outputDir) { + var kind = layer.getLayerKind().get(); + var dir = outputDir.dir(kind.sourceDirName()); + try { + Files.createDirectories(dir.get().getAsFile().toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return dir; } } diff --git a/docker-plugin/src/test/groovy/io/micronaut/gradle/DockerBuildTaskSpec.groovy b/docker-plugin/src/test/groovy/io/micronaut/gradle/DockerBuildTaskSpec.groovy index ec52ef5e..fa13452d 100644 --- a/docker-plugin/src/test/groovy/io/micronaut/gradle/DockerBuildTaskSpec.groovy +++ b/docker-plugin/src/test/groovy/io/micronaut/gradle/DockerBuildTaskSpec.groovy @@ -1,11 +1,18 @@ package io.micronaut.gradle import org.gradle.testkit.runner.TaskOutcome +import org.mockserver.integration.ClientAndServer +import org.mockserver.model.MediaType import spock.lang.IgnoreIf import spock.lang.Issue +import static org.mockserver.model.HttpRequest.request +import static org.mockserver.model.HttpResponse.response + @IgnoreIf({ os.windows }) class DockerBuildTaskSpec extends AbstractGradleBuildSpec { + private final String today = new Date().format("yyyyMMdd") + private final String now = new Date().format("HHmmss") def "test build docker image"() { given: @@ -82,10 +89,8 @@ class Application { testProjectDir.newFile("Dockerfile") << """ FROM eclipse-temurin:17-jre-focal WORKDIR /home/alternate -COPY layers/libs /home/alternate/libs -COPY layers/classes /home/alternate/classes -COPY layers/resources /home/alternate/resources -COPY layers/application.jar /home/alternate/application.jar +COPY --link layers/libs /home/alternate/libs +COPY --link layers/app/application.jar /home/alternate/application.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/alternate/application.jar"] """ @@ -191,13 +196,231 @@ class Application { def dockerfile = new File(testProjectDir.root, 'build/docker/main/Dockerfile').text dockerfile == """FROM eclipse-temurin:17-jre-focal WORKDIR /home/alternate -COPY layers/libs /home/alternate/libs -COPY layers/classes /home/alternate/classes -COPY layers/resources /home/alternate/resources -COPY layers/application.jar /home/alternate/application.jar +COPY --link layers/libs /home/alternate/libs +COPY --link layers/app /home/alternate/ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/alternate/application.jar"] """ } + def "can disable the use of the COPY --link option"() { + given: + settingsFile << "rootProject.name = 'hello-world'" + buildFile << """import io.micronaut.gradle.docker.MicronautDockerfile + + plugins { + id "io.micronaut.minimal.application" + id "io.micronaut.docker" + } + + micronaut { + version "$micronautVersion" + } + + $repositoriesBlock + + mainClassName="example.Application" + + tasks.withType(MicronautDockerfile).configureEach { + useCopyLink = false + } + + """ + testProjectDir.newFolder("src", "main", "java", "example") + def javaFile = testProjectDir.newFile("src/main/java/example/Application.java") + javaFile.parentFile.mkdirs() + javaFile << """ +package example; + +class Application { + public static void main(String... args) { + + } +} +""" + + when: + def result = build('dockerfile', '-s') + + then: + def dockerfileTask = result.task(":dockerfile") + dockerfileTask.outcome == TaskOutcome.SUCCESS + + and: + def dockerfile = new File(testProjectDir.root, 'build/docker/main/Dockerfile').text + dockerfile == """FROM eclipse-temurin:17-jre-focal +WORKDIR /home/app +COPY layers/libs /home/app/libs +COPY layers/app /home/app/ +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] +""" + } + + def "includes project dependencies in different layer"() { + given: + settingsFile << """rootProject.name = 'hello-world' + include "project-lib" + """ + buildFile << """ + plugins { + id "io.micronaut.minimal.application" + id "io.micronaut.docker" + } + + micronaut { + version "$micronautVersion" + } + + $repositoriesBlock + + mainClassName="example.Application" + + dependencies { + implementation project(":project-lib") + } + + """ + file("project-lib").mkdir() + file("project-lib/build.gradle") << """ + plugins { + id "java-library" + } + """ + testProjectDir.newFolder("src", "main", "java", "example") + def javaFile = testProjectDir.newFile("src/main/java/example/Application.java") + javaFile.parentFile.mkdirs() + javaFile << """ +package example; + +class Application { + public static void main(String... args) { + + } +} +""" + + testProjectDir.newFolder("project-lib", "src", "main", "java", "example") + def javaFile2 = testProjectDir.newFile("project-lib/src/main/java/example/ProjectLib.java") + javaFile2.parentFile.mkdirs() + javaFile2 << """package example; +public class ProjectLib {} +""" + when: + def result = build('dockerfile') + + def task = result.task(":dockerfile") + + then: + task.outcome == TaskOutcome.SUCCESS + def dockerFile = normalizeLineEndings(file("build/docker/main/Dockerfile").text) + dockerFile.contains("COPY --link layers/libs /home/app/libs") + dockerFile.contains("COPY --link layers/project_libs /home/app/libs") + !dockerFile.contains("COPY --link layers/snapshot_libs /home/app/libs") + } + + def "includes snapshot dependencies in different layer"() { + def snapshotRepository = ClientAndServer.startClientAndServer() + snapshotRepository.when(request() + .withPath("/dummy/dummy/5.0.0-SNAPSHOT/maven-metadata.xml")) + .respond( + response() + .withStatusCode(200) + .withContentType(MediaType.XML_UTF_8) + .withBody(snapshotList.bytes) + ) + + snapshotRepository.when(request() + .withPath("/dummy/dummy/5.0.0-SNAPSHOT/dummy-5.0.0-${today}.${now}-1.pom")) + .respond( + response() + .withStatusCode(200) + .withContentType(MediaType.XML_UTF_8) + .withBody(snapshotMetadata.bytes) + ) + + snapshotRepository.when(request() + .withPath("/dummy/dummy/5.0.0-SNAPSHOT/dummy-5.0.0-${today}.${now}-1.jar")) + .respond( + response() + .withStatusCode(200) + .withContentType(MediaType.APPLICATION_BINARY) + .withBody(new byte[0]) + ) + + + given: + settingsFile << """rootProject.name = 'hello-world'""" + buildFile << """ + plugins { + id "io.micronaut.minimal.application" + id "io.micronaut.docker" + } + + micronaut { + version "$micronautVersion" + } + + $repositoriesBlock + + repositories { + maven { + allowInsecureProtocol = true + url "http://localhost:${snapshotRepository.port}" + } + } + + mainClassName="example.Application" + + dependencies { + implementation "dummy:dummy:5.0.0-SNAPSHOT" + } + + configurations.all { + resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds' + } + + """ + testProjectDir.newFolder("src", "main", "java", "example") + def javaFile = testProjectDir.newFile("src/main/java/example/Application.java") + javaFile.parentFile.mkdirs() + javaFile << """ +package example; + +class Application { + public static void main(String... args) { + + } +} +""" + + testProjectDir.newFolder("project-lib", "src", "main", "java", "example") + + when: + def result = build('dockerfile') + + def task = result.task(":dockerfile") + + then: + task.outcome == TaskOutcome.SUCCESS + def dockerFile = normalizeLineEndings(file("build/docker/main/Dockerfile").text) + dockerFile.contains("COPY --link layers/libs /home/app/libs") + dockerFile.contains("COPY --link layers/snapshot_libs /home/app/libs") + !dockerFile.contains("COPY --link layers/project_libs /home/app/libs") + + cleanup: + snapshotRepository.stop() + + } + + private static String getSnapshotMetadata() { + DockerBuildTaskSpec.getResourceAsStream("/dummy-metadata.xml").text + } + + private String getSnapshotList() { + DockerBuildTaskSpec.getResourceAsStream("/test-maven-metadata.xml").text + .replace('xxxxxxxx', today) + .replace('tttttt', now) + + } } diff --git a/docker-plugin/src/test/resources/dummy-metadata.xml b/docker-plugin/src/test/resources/dummy-metadata.xml new file mode 100644 index 00000000..7672227d --- /dev/null +++ b/docker-plugin/src/test/resources/dummy-metadata.xml @@ -0,0 +1,18 @@ + + + 4.0.0 + dummy + dummy + 5.0.0-SNAPSHOT + Micronaut Dummy + Dummy + https://micronaut.io + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + diff --git a/docker-plugin/src/test/resources/test-maven-metadata.xml b/docker-plugin/src/test/resources/test-maven-metadata.xml new file mode 100644 index 00000000..93ba083f --- /dev/null +++ b/docker-plugin/src/test/resources/test-maven-metadata.xml @@ -0,0 +1,13 @@ + + + dummy + dummy + + xxxxxxxxtttttt + + xxxxxxxx.tttttt + 1 + + + 5.0.0-SNAPSHOT + diff --git a/functional-tests/src/test/groovy/io/micronaut/gradle/aot/BasicMicronautAOTSpec.groovy b/functional-tests/src/test/groovy/io/micronaut/gradle/aot/BasicMicronautAOTSpec.groovy index 44dbb543..1cfed816 100644 --- a/functional-tests/src/test/groovy/io/micronaut/gradle/aot/BasicMicronautAOTSpec.groovy +++ b/functional-tests/src/test/groovy/io/micronaut/gradle/aot/BasicMicronautAOTSpec.groovy @@ -250,6 +250,13 @@ class BasicMicronautAOTSpec extends AbstractAOTPluginSpec { withSample("aot/basic-app") withPlugins(Plugins.MINIMAL_APPLICATION) file("gradle.properties") << "org.gradle.caching=true" + // Add a random seed to the build.gradle file so that the task is not from cache + // when a previous test execution has already cached the result + file("build.gradle") << """ + tasks.named("prepareJitOptimizations") { + inputs.property("seed", "${System.currentTimeMillis()}") + } + """ when: def result = build "prepareJitOptimizations" diff --git a/functional-tests/src/test/groovy/io/micronaut/gradle/aot/MicronautAOTDockerSpec.groovy b/functional-tests/src/test/groovy/io/micronaut/gradle/aot/MicronautAOTDockerSpec.groovy index 5b725566..3f9902ed 100644 --- a/functional-tests/src/test/groovy/io/micronaut/gradle/aot/MicronautAOTDockerSpec.groovy +++ b/functional-tests/src/test/groovy/io/micronaut/gradle/aot/MicronautAOTDockerSpec.groovy @@ -22,10 +22,8 @@ class MicronautAOTDockerSpec extends AbstractAOTPluginSpec { def dockerFile = normalizeLineEndings(file("build/docker/optimized/Dockerfile").text) dockerFile == """FROM eclipse-temurin:17-jre-focal WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link layers/app /home/app/ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] """ @@ -49,10 +47,8 @@ ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] def dockerFile = normalizeLineEndings(file("build/docker/optimized/Dockerfile").text) dockerFile == """FROM eclipse-temurin:17-jre-focal WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link layers/app /home/app/ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] """ @@ -71,22 +67,20 @@ ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] then: dockerFile == """FROM ghcr.io/graalvm/native-image-community:17-ol${DefaultVersions.ORACLELINUX} AS graalvm WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link layers/app /home/app/ RUN mkdir /home/app/config-dirs RUN mkdir -p /home/app/config-dirs/generateResourcesConfigFile RUN mkdir -p /home/app/config-dirs/io.netty/netty-common/4.0.0.Final RUN mkdir -p /home/app/config-dirs/ch.qos.logback/logback-classic/4.0.0 -COPY config-dirs/generateResourcesConfigFile /home/app/config-dirs/generateResourcesConfigFile -COPY config-dirs/io.netty/netty-common/4.0.0.Final /home/app/config-dirs/io.netty/netty-common/4.0.0.Final -COPY config-dirs/ch.qos.logback/logback-classic/4.0.0 /home/app/config-dirs/ch.qos.logback/logback-classic/4.0.0 +COPY --link config-dirs/generateResourcesConfigFile /home/app/config-dirs/generateResourcesConfigFile +COPY --link config-dirs/io.netty/netty-common/4.0.0.Final /home/app/config-dirs/io.netty/netty-common/4.0.0.Final +COPY --link config-dirs/ch.qos.logback/logback-classic/4.0.0 /home/app/config-dirs/ch.qos.logback/logback-classic/4.0.0 RUN native-image --exclude-config .*/libs/netty-transport-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-buffer-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-codec-http-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-handler-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-common-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-codec-http2-4.0.0.Final.jar ^/META-INF/native-image/.* -cp /home/app/libs/*.jar:/home/app/resources:/home/app/application.jar --no-fallback -o application -H:ConfigurationFileDirectories=/home/app/config-dirs/generateResourcesConfigFile,/home/app/config-dirs/io.netty/netty-buffer/4.0.0.Final,/home/app/config-dirs/io.netty/netty-common/4.0.0.Final,/home/app/config-dirs/io.netty/netty-codec-http/4.0.0.Final,/home/app/config-dirs/io.netty/netty-transport/4.0.0.Final,/home/app/config-dirs/io.netty/netty-handler/4.0.0.Final,/home/app/config-dirs/io.netty/netty-codec-http2/4.0.0.Final,/home/app/config-dirs/ch.qos.logback/logback-classic/4.0.0 demo.app.Application FROM frolvlad/alpine-glibc:alpine-${DefaultVersions.ALPINE} RUN apk --no-cache update && apk add libstdc++ EXPOSE 8080 -COPY --from=graalvm /home/app/application /app/application +COPY --link --from=graalvm /home/app/application /app/application ENTRYPOINT ["/app/application"] """ diff --git a/functional-tests/src/test/groovy/io/micronaut/gradle/docker/DockerNativeFunctionalTest.groovy b/functional-tests/src/test/groovy/io/micronaut/gradle/docker/DockerNativeFunctionalTest.groovy index 00fd1776..15d09bfd 100644 --- a/functional-tests/src/test/groovy/io/micronaut/gradle/docker/DockerNativeFunctionalTest.groovy +++ b/functional-tests/src/test/groovy/io/micronaut/gradle/docker/DockerNativeFunctionalTest.groovy @@ -583,20 +583,18 @@ micronaut: dockerFile == """ FROM ghcr.io/graalvm/native-image-community:17-ol${DefaultVersions.ORACLELINUX} AS graalvm WORKDIR /home/alternate -COPY layers/libs /home/alternate/libs -COPY layers/classes /home/alternate/classes -COPY layers/resources /home/alternate/resources -COPY layers/application.jar /home/alternate/application.jar +COPY --link layers/libs /home/alternate/libs +COPY --link layers/app /home/alternate/ RUN mkdir /home/alternate/config-dirs RUN mkdir -p /home/alternate/config-dirs/generateResourcesConfigFile RUN mkdir -p /home/alternate/config-dirs/io.netty/netty-common/4.0.0.Final -COPY config-dirs/generateResourcesConfigFile /home/alternate/config-dirs/generateResourcesConfigFile -COPY config-dirs/io.netty/netty-common/4.0.0.Final /home/alternate/config-dirs/io.netty/netty-common/4.0.0.Final +COPY --link config-dirs/generateResourcesConfigFile /home/alternate/config-dirs/generateResourcesConfigFile +COPY --link config-dirs/io.netty/netty-common/4.0.0.Final /home/alternate/config-dirs/io.netty/netty-common/4.0.0.Final RUN native-image --exclude-config .*/libs/netty-transport-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-buffer-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-codec-http-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-handler-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-common-4.0.0.Final.jar ^/META-INF/native-image/.* --exclude-config .*/libs/netty-codec-http2-4.0.0.Final.jar ^/META-INF/native-image/.* -cp /home/alternate/libs/*.jar:/home/alternate/resources:/home/alternate/application.jar --no-fallback -o application -H:ConfigurationFileDirectories=/home/alternate/config-dirs/generateResourcesConfigFile,/home/alternate/config-dirs/io.netty/netty-buffer/4.0.0.Final,/home/alternate/config-dirs/io.netty/netty-common/4.0.0.Final,/home/alternate/config-dirs/io.netty/netty-codec-http/4.0.0.Final,/home/alternate/config-dirs/io.netty/netty-transport/4.0.0.Final,/home/alternate/config-dirs/io.netty/netty-handler/4.0.0.Final,/home/alternate/config-dirs/io.netty/netty-codec-http2/4.0.0.Final example.Application ${defaultDockerFrom} EXPOSE 8080 HEALTHCHECK CMD curl -s localhost:8090/health | grep '"status":"UP"' -COPY --from=graalvm /home/alternate/application /app/application +COPY --link --from=graalvm /home/alternate/application /app/application ENTRYPOINT ["/app/application", "-Xmx64m"] """.trim() @@ -664,8 +662,8 @@ afterEvaluate { tasks.withType(io.micronaut.gradle.docker.DockerBuildOptions).configureEach { editDockerfile { - after('COPY layers/libs /home/app/libs') { - insert('COPY server.iprof /home/app/server.iprof') + after('COPY --link layers/libs /home/app/libs') { + insert('COPY --link server.iprof /home/app/server.iprof') } } } @@ -691,11 +689,9 @@ class Application { def dockerfile = new File(testProjectDir.root, 'build/docker/main/Dockerfile').text dockerfile == """FROM eclipse-temurin:17-jre-focal WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY server.iprof /home/app/server.iprof -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link server.iprof /home/app/server.iprof +COPY --link layers/app /home/app/ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] """ @@ -707,18 +703,16 @@ ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] def dockerfileNative = new File(testProjectDir.root, 'build/docker/native-main/DockerfileNative').text dockerfileNative == """FROM ghcr.io/graalvm/native-image-community:17-ol${DefaultVersions.ORACLELINUX} AS graalvm WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY server.iprof /home/app/server.iprof -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link server.iprof /home/app/server.iprof +COPY --link layers/app /home/app/ RUN mkdir /home/app/config-dirs RUN mkdir -p /home/app/config-dirs/generateResourcesConfigFile -COPY config-dirs/generateResourcesConfigFile /home/app/config-dirs/generateResourcesConfigFile +COPY --link config-dirs/generateResourcesConfigFile /home/app/config-dirs/generateResourcesConfigFile RUN native-image -cp /home/app/libs/*.jar:/home/app/resources:/home/app/application.jar --no-fallback -o application -H:ConfigurationFileDirectories=/home/app/config-dirs/generateResourcesConfigFile example.Application ${defaultDockerFrom} EXPOSE 8080 -COPY --from=graalvm /home/app/application /app/application +COPY --link --from=graalvm /home/app/application /app/application ENTRYPOINT ["/app/application"] """ } @@ -744,8 +738,8 @@ ENTRYPOINT ["/app/application"] tasks.withType(io.micronaut.gradle.docker.DockerBuildOptions).configureEach { editDockerfile { - after('COPY layers/libs /home/app/libs') { - insert('COPY server.iprof /home/app/server.iprof') + after('COPY --link layers/libs /home/app/libs') { + insert('COPY --link server.iprof /home/app/server.iprof') } } } @@ -771,11 +765,9 @@ class Application { def dockerfile = new File(testProjectDir.root, 'build/docker/main/Dockerfile').text dockerfile == """FROM eclipse-temurin:17-jre-focal WORKDIR /home/app -COPY layers/libs /home/app/libs -COPY server.iprof /home/app/server.iprof -COPY layers/classes /home/app/classes -COPY layers/resources /home/app/resources -COPY layers/application.jar /home/app/application.jar +COPY --link layers/libs /home/app/libs +COPY --link server.iprof /home/app/server.iprof +COPY --link layers/app /home/app/ EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] """ @@ -790,8 +782,8 @@ ENTRYPOINT ["java", "-jar", "/home/app/application.jar"] buildFile << """ tasks.withType(io.micronaut.gradle.docker.DockerBuildOptions).configureEach { editDockerfile { - after('COPY server.iprof /home/app/server.iprof') { - insert('COPY README.TXT /home/app/README.TXT') + after('COPY --link server.iprof /home/app/server.iprof') { + insert('COPY --link README.TXT /home/app/README.TXT') } } } diff --git a/gradle.properties b/gradle.properties index 0840ffd9..dd0f91f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -projectVersion=4.2.2-SNAPSHOT +projectVersion=4.3.0-SNAPSHOT projectGroup=io.micronaut.gradle githubBranch=master org.gradle.caching=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22d5da89..b652503c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ micronaut-platform = "4.2.0" # This is the platform version, used in our tests micronaut-aot = "2.1.1" micronaut-testresources = "2.3.0" micronaut-openapi = "6.3.0" +mockserver = "5.15.0" log4j2 = { require = "2.17.1", reject = ["]0, 2.17["] } jetbrains-annotations = "24.1.0" tomlj = "1.1.0" @@ -44,6 +45,9 @@ micronaut-openapi-generator = { module = "io.micronaut.openapi:micronaut-openapi micronaut-testresources = { module = "io.micronaut.testresources:micronaut-test-resources-build-tools", version.ref = "micronaut-testresources" } +mockserver-netty = { module = "org.mock-server:mockserver-netty", version.ref = "mockserver" } +mockserver-client = { module = "org.mock-server:mockserver-client-java", version.ref = "mockserver" } + jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } tomlj = { module = "org.tomlj:tomlj", version.ref = "tomlj" } diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 0179bdf4..5987c866 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -914,8 +914,8 @@ For example, to customize the file generated by the `dockerfile` task, you can d ---- tasks.named("dockerfile") { editDockerfile { - after('COPY layers/libs /home/app/libs') { - insert('COPY server.iprof /home/app/server.iprof') + after('COPY --link layers/libs /home/app/libs') { + insert('COPY --link server.iprof /home/app/server.iprof') } } } @@ -925,8 +925,8 @@ tasks.named("dockerfile") { ---- tasks.named("dockerfile") { editDockerfile { - after("COPY layers/libs /home/app/libs") { - insert("COPY server.iprof /home/app/server.iprof") + after("COPY --link layers/libs /home/app/libs") { + insert("COPY --link server.iprof /home/app/server.iprof") } } }