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 com.github.dockerjava.api.command.BuildImageCmd;
19  import com.google.cloud.tools.jib.api.ImageReference;
20  import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
21  import com.google.common.io.FileWriteMode;
22  import com.google.common.io.Files;
23  import io.micronaut.build.services.ApplicationConfigurationService;
24  import io.micronaut.build.services.DockerService;
25  import io.micronaut.build.services.JibConfigurationService;
26  import io.micronaut.core.util.StringUtils;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugins.annotations.Mojo;
29  import org.apache.maven.plugins.annotations.ResolutionScope;
30  import org.apache.maven.project.MavenProject;
31  
32  import javax.inject.Inject;
33  import java.io.File;
34  import java.io.IOException;
35  import java.nio.charset.Charset;
36  import java.util.Set;
37  
38  /**
39   * <p>Implementation of the <code>docker-native</code> packaging.</p>
40   * <p><strong>WARNING</strong>: this goal is not intended to be executed directly. Instead, specify the packaging type
41   * using the <code>packaging</code> property, eg:</p>
42   *
43   * <pre>mvn package -Dpackaging=docker-native</pre>
44   *
45   * @author Álvaro Sánchez-Mariscal
46   * @author Iván López
47   * @since 1.1
48   */
49  @Mojo(name = DockerNativeMojo.DOCKER_NATIVE_PACKAGING, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
50  public class DockerNativeMojo extends AbstractDockerMojo {
51  
52      public static final String DOCKER_NATIVE_PACKAGING = "docker-native";
53      public static final String GRAALVM_ARGS = "GRAALVM_ARGS";
54      public static final String MICRONAUT_PARENT = "io.micronaut:micronaut-parent";
55      public static final String MICRONAUT_VERSION = "micronaut.version";
56  
57      @SuppressWarnings("CdiInjectionPointsInspection")
58      @Inject
59      public DockerNativeMojo(MavenProject mavenProject, JibConfigurationService jibConfigurationService,
60                              ApplicationConfigurationService applicationConfigurationService, DockerService dockerService) {
61          super(mavenProject, jibConfigurationService, applicationConfigurationService, dockerService);
62      }
63  
64      @Override
65      public void execute() throws MojoExecutionException {
66          checkJavaVersion();
67          checkGraalVm();
68  
69          try {
70              copyDependencies();
71  
72              MicronautRuntime runtime = MicronautRuntime.valueOf(micronautRuntime.toUpperCase());
73  
74              switch (runtime.getBuildStrategy()) {
75                  case LAMBDA:
76                      buildDockerNativeLambda();
77                      break;
78  
79                  case ORACLE_FUNCTION:
80                      buildOracleCloud();
81                      break;
82  
83                  case DEFAULT:
84                  default:
85                      buildDockerNative();
86                      break;
87              }
88  
89  
90          } catch (InvalidImageReferenceException iire) {
91              String message = "Invalid image reference "
92                      + iire.getInvalidReference()
93                      + ", perhaps you should check that the reference is formatted correctly according to " +
94                      "https://docs.docker.com/engine/reference/commandline/tag/#extended-description" +
95                      "\nFor example, slash-separated name components cannot have uppercase letters";
96              throw new MojoExecutionException(message);
97          } catch (IOException | IllegalArgumentException e) {
98              throw new MojoExecutionException(e.getMessage(), e);
99          }
100     }
101 
102     private void checkGraalVm() throws MojoExecutionException {
103         String micronautVersion = mavenProject.getProperties().getProperty(MICRONAUT_VERSION);
104         if (mavenProject.hasParent()) {
105             String ga = mavenProject.getParent().getGroupId() + ":" + mavenProject.getParent().getArtifactId();
106             if (MICRONAUT_PARENT.equals(ga)) {
107                 String micronautParentVersion = mavenProject.getModel().getParent().getVersion();
108                 if (micronautVersion.equals(micronautParentVersion)) {
109                     if (!mavenProject.getInjectedProfileIds().get("io.micronaut:micronaut-parent:" + micronautParentVersion).contains("graalvm")) {
110                         String javaVendor = System.getProperty("java.vendor", "");
111                         if (javaVendor.toLowerCase().contains("graalvm")) {
112                             throw new MojoExecutionException("The [graalvm] profile was not activated automatically because the native-image component is not installed (or not found in your path). Either activate the profile manually (-Pgraalvm) or install the native-image component (gu install native-image), and try again");
113                         } else {
114                             throw new MojoExecutionException("The [graalvm] profile was not activated automatically because you are not using a GraalVM JDK. Activate the profile manually (-Pgraalvm) and try again");
115                         }
116                     }
117                 } else {
118                     String message = String.format("The %s version (%s) differs from the %s property (%s). Please, make sure both refer to the same version", MICRONAUT_PARENT, micronautParentVersion, MICRONAUT_VERSION, micronautVersion);
119                     throw new MojoExecutionException(message);
120                 }
121             } else {
122                 getLog().warn("The parent POM of this project is not set to " + MICRONAUT_PARENT);
123             }
124         } else {
125             getLog().warn("This project has no parent POM defined. To avoid build problems, please set the parent to " + MICRONAUT_PARENT);
126         }
127     }
128 
129     private void checkJavaVersion() throws MojoExecutionException {
130         if (javaVersion().getMajorVersion() > 17) {
131             throw new MojoExecutionException("To build native images you must set the Java target byte code level to Java 17 or below");
132         }
133     }
134 
135     private void buildDockerNativeLambda() throws IOException {
136         BuildImageCmd buildImageCmd = dockerService.buildImageCmd(DockerfileMojo.DOCKERFILE_AWS_CUSTOM_RUNTIME)
137                 .withBuildArg("GRAALVM_VERSION", graalVmVersion())
138                 .withBuildArg("GRAALVM_JVM_VERSION", graalVmJvmVersion())
139                 .withBuildArg("GRAALVM_ARCH", graalVmArch());
140 
141         getLog().info("Using GRAALVM_VERSION: " + graalVmVersion());
142         getLog().info("Using GRAALVM_JVM_VERSION: " + graalVmJvmVersion());
143         getLog().info("Using GRAALVM_ARCH: " + graalVmArch());
144 
145         // Starter sets the right class in pom.xml:
146         //   - For applications: io.micronaut.function.aws.runtime.MicronautLambdaRuntime
147         //   - For function apps: com.example.BookLambdaRuntime
148         getLog().info("Using CLASS_NAME: " + mainClass);
149         buildImageCmd.withBuildArg("CLASS_NAME", mainClass);
150 
151         String graalVmBuildArgs = getGraalVmBuildArgs();
152         if (graalVmBuildArgs != null && !graalVmBuildArgs.isEmpty()) {
153             getLog().info("Using GRAALVM_ARGS: " + graalVmBuildArgs);
154             buildImageCmd = buildImageCmd.withBuildArg(GRAALVM_ARGS, graalVmBuildArgs);
155         }
156 
157         String imageId = dockerService.buildImage(buildImageCmd);
158         File functionZip = dockerService.copyFromContainer(imageId, "/function/function.zip");
159         getLog().info("AWS Lambda Custom Runtime ZIP: " + functionZip.getPath());
160     }
161 
162     private void buildDockerNative() throws IOException, InvalidImageReferenceException {
163         String dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE;
164         if (staticNativeImage) {
165             getLog().info("Generating a static native image");
166             dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE_STATIC;
167         } else if (baseImageRun.contains("distroless")) {
168             getLog().info("Generating a mostly static native image");
169             dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE_DISTROLESS;
170         }
171 
172         buildDockerfile(dockerfileName, true);
173     }
174 
175     private void buildOracleCloud() throws IOException, InvalidImageReferenceException {
176         buildDockerfile(DockerfileMojo.DOCKERFILE_NATIVE_ORACLE_CLOUD, false);
177     }
178 
179     private void buildDockerfile(String dockerfileName, boolean passClassName) throws IOException, InvalidImageReferenceException {
180         Set<String> tags = getTags();
181         for (String tag : tags) {
182             ImageReference.parse(tag);
183         }
184 
185         String from = getFrom();
186         String port = getPort();
187         getLog().info("Exposing port: " + port);
188 
189         File dockerfile = dockerService.loadDockerfileAsResource(dockerfileName);
190 
191         if (appArguments != null && !appArguments.isEmpty()) {
192             getLog().info("Using application arguments: " + appArguments);
193             Files.asCharSink(dockerfile, Charset.defaultCharset(), FileWriteMode.APPEND).write(System.lineSeparator() + getCmd());
194         }
195 
196         BuildImageCmd buildImageCmd = dockerService.buildImageCmd()
197                 .withDockerfile(dockerfile)
198                 .withTags(getTags())
199                 .withBuildArg("BASE_IMAGE", from)
200                 .withBuildArg("PORT", port);
201 
202         getLog().info("Using BASE_IMAGE: " + from);
203         if (StringUtils.isNotEmpty(baseImageRun) && !staticNativeImage) {
204             getLog().info("Using BASE_IMAGE_RUN: " + baseImageRun);
205             buildImageCmd.withBuildArg("BASE_IMAGE_RUN", baseImageRun);
206         }
207 
208         if (baseImageRun.contains("alpine-glibc")) {
209             buildImageCmd.withBuildArg("EXTRA_CMD", "apk update && apk add libstdc++");
210         } else {
211             buildImageCmd.withBuildArg("EXTRA_CMD", "");
212         }
213 
214         if (passClassName) {
215             getLog().info("Using CLASS_NAME: " + mainClass);
216             buildImageCmd = buildImageCmd.withBuildArg("CLASS_NAME", mainClass);
217         }
218 
219         String graalVmBuildArgs = getGraalVmBuildArgs();
220         if (baseImageRun.contains("distroless") && !graalVmBuildArgs.contains(MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG)) {
221             graalVmBuildArgs = MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG + " " + graalVmBuildArgs;
222         }
223 
224         if (graalVmBuildArgs != null && !graalVmBuildArgs.isEmpty()) {
225             getLog().info("Using GRAALVM_ARGS: " + graalVmBuildArgs);
226             buildImageCmd = buildImageCmd.withBuildArg(GRAALVM_ARGS, graalVmBuildArgs);
227         }
228 
229         dockerService.buildImage(buildImageCmd);
230     }
231 
232 }