View Javadoc
1   /*
2    * Copyright 2017-2022 original authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package io.micronaut.build;
17  
18  import io.micronaut.build.services.ApplicationConfigurationService;
19  import io.micronaut.build.services.DockerService;
20  import io.micronaut.build.services.JibConfigurationService;
21  import org.apache.maven.artifact.Artifact;
22  import org.apache.maven.artifact.versioning.ArtifactVersion;
23  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
24  import org.apache.maven.plugin.AbstractMojo;
25  import org.apache.maven.plugins.annotations.Parameter;
26  import org.apache.maven.project.MavenProject;
27  
28  import java.io.File;
29  import java.io.IOException;
30  import java.nio.file.Files;
31  import java.nio.file.StandardCopyOption;
32  import java.util.*;
33  import java.util.stream.Collectors;
34  
35  import static io.micronaut.build.services.ApplicationConfigurationService.DEFAULT_PORT;
36  
37  /**
38   * Abstract base class for mojos related to Docker files and builds.
39   *
40   * @author Álvaro Sánchez-Mariscal
41   * @author Iván López
42   * @since 1.1
43   */
44  public abstract class AbstractDockerMojo extends AbstractMojo {
45  
46      public static final String LATEST_TAG = "latest";
47      public static final String DEFAULT_BASE_IMAGE_GRAALVM_RUN = "frolvlad/alpine-glibc:alpine-3.12";
48      public static final String MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG = "-H:+StaticExecutableWithDynamicLibC";
49      public static final String ARM_ARCH = "aarch64";
50      public static final String X86_64_ARCH = "amd64";
51  
52      protected final MavenProject mavenProject;
53      protected final JibConfigurationService jibConfigurationService;
54      protected final ApplicationConfigurationService applicationConfigurationService;
55      protected final DockerService dockerService;
56  
57  
58      /**
59       * Additional arguments that will be passed to the <code>native-image</code> executable. Note that this will only
60       * be used when using a packaging of type <code>docker-native</code>. For <code>native-image</code> packaging
61       * you should use the
62       * <a href="https://www.graalvm.org/reference-manual/native-image/NativeImageMavenPlugin/#maven-plugin-customization">
63       * Native Image Maven Plugin
64       * </a> configuration options.
65       */
66      @Parameter(property = "micronaut.native-image.args")
67      protected List<String> nativeImageBuildArgs;
68  
69      /**
70       * List of additional arguments that will be passed to the application.
71       */
72      @Parameter(property = RunMojo.MN_APP_ARGS)
73      protected List<String> appArguments;
74  
75      /**
76       * The main class of the application, as defined in the
77       * <a href="https://www.mojohaus.org/exec-maven-plugin/java-mojo.html#mainClass">Exec Maven Plugin</a>.
78       */
79      @Parameter(defaultValue = RunMojo.EXEC_MAIN_CLASS, required = true)
80      protected String mainClass;
81  
82      /**
83       * Whether to produce a static native image when using <code>docker-native</code> packaging.
84       */
85      @Parameter(defaultValue = "false", property = "micronaut.native-image.static")
86      protected Boolean staticNativeImage;
87  
88      /**
89       * The target runtime of the application.
90       */
91      @Parameter(property = MicronautRuntime.PROPERTY, defaultValue = "NONE")
92      protected String micronautRuntime;
93  
94      /**
95       * The Docker image used to run the native image.
96       * @since 1.2
97       */
98      @Parameter(property = "micronaut.native-image.base-image-run", defaultValue = DEFAULT_BASE_IMAGE_GRAALVM_RUN)
99      protected String baseImageRun;
100 
101     protected AbstractDockerMojo(MavenProject mavenProject, JibConfigurationService jibConfigurationService, ApplicationConfigurationService applicationConfigurationService, DockerService dockerService) {
102         this.mavenProject = mavenProject;
103         this.jibConfigurationService = jibConfigurationService;
104         this.applicationConfigurationService = applicationConfigurationService;
105         this.dockerService = dockerService;
106     }
107 
108     /**
109      * Returns the Java version from either the <code>maven.compiler.target</code> property or the <code>java.version</code> property.
110      */
111     protected ArtifactVersion javaVersion() {
112         return new DefaultArtifactVersion(Optional.ofNullable(mavenProject.getProperties().getProperty("maven.compiler.target")).orElse(System.getProperty("java.version")));
113     }
114 
115     /**
116      * Returns the GraalVM version from the <code>graalvm.version</code> property, which is expected to come from the
117      * Micronaut Parent POM.
118      */
119     protected String graalVmVersion() {
120         return mavenProject.getProperties().getProperty("graal.version");
121     }
122 
123     /**
124      * Calculates the JVM version to use for GraalVM.
125      */
126     protected String graalVmJvmVersion() {
127         String graalVmJvmVersion = "java11";
128         if (javaVersion().getMajorVersion() >= 17) {
129             graalVmJvmVersion = "java17";
130         }
131         return graalVmJvmVersion;
132     }
133 
134     /**
135      * Detects the OS architecture to use for GraalVM depending on the <code>os.arch</code> system property.
136      */
137     protected String graalVmArch() {
138         String osArch = System.getProperty("os.arch");
139         if (ARM_ARCH.equals(osArch)) {
140             return ARM_ARCH;
141         } else {
142             return X86_64_ARCH;
143         }
144     }
145 
146     /**
147      * Determines the base FROM image for the native image.
148      */
149     protected String getFrom() {
150         if (Boolean.TRUE.equals(staticNativeImage)) {
151             // For building a static native image we need a base image with tools (cc, make,...) already installed
152             return getFromImage().orElse("ghcr.io/graalvm/graalvm-ce:ol7-" + graalVmJvmVersion() + "-" + graalVmVersion());
153         } else {
154             return getFromImage().orElse("ghcr.io/graalvm/native-image:ol7-" + graalVmJvmVersion() + "-" + graalVmVersion());
155         }
156     }
157 
158     /**
159      * Returns the base image from the jib configuration (if any).
160      */
161     protected Optional<String> getFromImage() {
162         return jibConfigurationService.getFromImage();
163     }
164 
165     /**
166      * Calculates the Docker image tags by looking at the Jib plugin configuration.
167      */
168     protected Set<String> getTags() {
169         Set<String> tags = new HashSet<>();
170         Optional<String> toImageOptional = jibConfigurationService.getToImage();
171         String imageName = mavenProject.getArtifactId();
172         if (toImageOptional.isPresent()) {
173             String toImage = toImageOptional.get();
174             if (toImage.contains(":")) {
175                 tags.add(toImage);
176                 imageName = toImageOptional.get().split(":")[0];
177             } else {
178                 tags.add(toImage + ":" + LATEST_TAG);
179                 imageName = toImage;
180             }
181         } else {
182             tags.add(imageName + ":" + LATEST_TAG);
183         }
184         for (String tag : jibConfigurationService.getTags()) {
185             if (LATEST_TAG.equals(tag) && tags.stream().anyMatch(t -> t.contains(LATEST_TAG))) {
186                 continue;
187             }
188             tags.add(String.format("%s:%s", imageName, tag));
189         }
190         return tags;
191     }
192 
193     /**
194      * Determines the application port to expose by looking at the application configuration.
195      */
196     protected String getPort() {
197         String port = applicationConfigurationService.getServerPort();
198         return "-1".equals(port) ? DEFAULT_PORT : port;
199     }
200 
201     /**
202      * Copy project dependencies to a <code>target/dependency</code> directory.
203      */
204     @SuppressWarnings("ResultOfMethodCallIgnored")
205     protected void copyDependencies() throws IOException {
206         List<String> imageClasspathScopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME);
207         mavenProject.setArtifactFilter(artifact -> imageClasspathScopes.contains(artifact.getScope()));
208         File target = new File(mavenProject.getBuild().getDirectory(), "dependency");
209         if (!target.exists()) {
210             target.mkdirs();
211         }
212         for (Artifact dependency : mavenProject.getArtifacts()) {
213             Files.copy(dependency.getFile().toPath(), target.toPath().resolve(dependency.getFile().getName()), StandardCopyOption.REPLACE_EXISTING);
214         }
215     }
216 
217     /**
218      * Returns the Docker CMD command.
219      */
220     protected String getCmd() {
221         return "CMD [" +
222                 appArguments.stream()
223                         .map(s -> "\"" + s + "\"")
224                         .collect(Collectors.joining(", ")) +
225                 "]";
226     }
227 
228     /**
229      * Returns any additional GraalVM arguments.
230      */
231     protected String getGraalVmBuildArgs() {
232         if (nativeImageBuildArgs != null && !nativeImageBuildArgs.isEmpty()) {
233             return String.join(" ", nativeImageBuildArgs);
234         } else {
235             return "";
236         }
237     }
238 
239 }