001/* 002 * Copyright 2017-2022 original authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.micronaut.maven; 017 018import com.github.dockerjava.api.command.BuildImageCmd; 019import com.github.dockerjava.api.exception.DockerClientException; 020import com.google.cloud.tools.jib.api.ImageReference; 021import com.google.cloud.tools.jib.api.InvalidImageReferenceException; 022import io.micronaut.core.util.StringUtils; 023import io.micronaut.maven.core.MicronautRuntime; 024import io.micronaut.maven.jib.JibConfigurationService; 025import io.micronaut.maven.services.ApplicationConfigurationService; 026import io.micronaut.maven.services.DockerService; 027import org.apache.maven.execution.MavenSession; 028import org.apache.maven.plugin.MojoExecution; 029import org.apache.maven.plugin.MojoExecutionException; 030import org.apache.maven.plugins.annotations.Mojo; 031import org.apache.maven.plugins.annotations.ResolutionScope; 032import org.apache.maven.project.MavenProject; 033import org.graalvm.buildtools.utils.NativeImageUtils; 034 035import javax.inject.Inject; 036import java.io.File; 037import java.io.IOException; 038import java.nio.file.Files; 039import java.nio.file.Paths; 040import java.util.HashMap; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044import java.util.function.Supplier; 045 046/** 047 * <p>Implementation of the <code>docker-native</code> packaging.</p> 048 * <p><strong>WARNING</strong>: this goal is not intended to be executed directly. Instead, specify the packaging type 049 * using the <code>packaging</code> property, eg:</p> 050 * 051 * <pre>mvn package -Dpackaging=docker-native</pre> 052 * 053 * @author Álvaro Sánchez-Mariscal 054 * @author Iván López 055 * @since 1.1 056 */ 057@Mojo(name = DockerNativeMojo.DOCKER_NATIVE_PACKAGING, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) 058public class DockerNativeMojo extends AbstractDockerMojo { 059 060 public static final String DOCKER_NATIVE_PACKAGING = "docker-native"; 061 public static final String MICRONAUT_PARENT = "io.micronaut.platform:micronaut-parent"; 062 public static final String MICRONAUT_VERSION = "micronaut.version"; 063 public static final String ARGS_FILE_PROPERTY_NAME = "graalvm.native-image.args-file"; 064 static final int AWS_LAMBDA_MAX_ALLOWED_VERSION = 21; 065 static final int ORACLE_FUNCTION_MAX_ALLOWED_VERSION = 21; 066 static final int MAX_ALLOWED_VERSION = 21; 067 private MicronautRuntime runtime; 068 069 @SuppressWarnings("CdiInjectionPointsInspection") 070 @Inject 071 public DockerNativeMojo(MavenProject mavenProject, JibConfigurationService jibConfigurationService, 072 ApplicationConfigurationService applicationConfigurationService, DockerService dockerService, 073 MavenSession mavenSession, MojoExecution mojoExecution) { 074 super(mavenProject, jibConfigurationService, applicationConfigurationService, dockerService, mavenSession, mojoExecution); 075 } 076 077 @Override 078 public void execute() throws MojoExecutionException { 079 checkGraalVm(); 080 081 try { 082 copyDependencies(); 083 084 this.runtime = MicronautRuntime.valueOf(micronautRuntime.toUpperCase()); 085 086 switch (runtime.getBuildStrategy()) { 087 case LAMBDA -> { 088 checkJavaVersion(AWS_LAMBDA_MAX_ALLOWED_VERSION); 089 buildDockerNativeLambda(); 090 } 091 case ORACLE_FUNCTION -> { 092 checkJavaVersion(ORACLE_FUNCTION_MAX_ALLOWED_VERSION); 093 buildOracleCloud(); 094 } 095 case DEFAULT -> { 096 checkJavaVersion(MAX_ALLOWED_VERSION); 097 buildDockerNative(); 098 } 099 default -> throw new IllegalStateException("Unexpected value: " + runtime.getBuildStrategy()); 100 } 101 102 103 } catch (InvalidImageReferenceException iire) { 104 String message = "Invalid image reference " 105 + iire.getInvalidReference() 106 + ", perhaps you should check that the reference is formatted correctly according to " + 107 "https://docs.docker.com/engine/reference/commandline/tag/#extended-description" + 108 "\nFor example, slash-separated name components cannot have uppercase letters"; 109 throw new MojoExecutionException(message); 110 } catch (IOException | IllegalArgumentException e) { 111 throw new MojoExecutionException(e.getMessage(), e); 112 } 113 } 114 115 private void checkGraalVm() throws MojoExecutionException { 116 String micronautVersion = mavenProject.getProperties().getProperty(MICRONAUT_VERSION); 117 if (mavenProject.hasParent()) { 118 String ga = mavenProject.getParent().getGroupId() + ":" + mavenProject.getParent().getArtifactId(); 119 if (MICRONAUT_PARENT.equals(ga)) { 120 String micronautParentVersion = mavenProject.getModel().getParent().getVersion(); 121 if (micronautVersion.equals(micronautParentVersion)) { 122 if (!mavenProject.getInjectedProfileIds().get(MICRONAUT_PARENT + ":" + micronautParentVersion).contains("graalvm")) { 123 String javaVendor = System.getProperty("java.vendor", ""); 124 if (javaVendor.toLowerCase().contains("graalvm")) { 125 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"); 126 } else { 127 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"); 128 } 129 } 130 } else { 131 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); 132 throw new MojoExecutionException(message); 133 } 134 } else { 135 getLog().warn("The parent POM of this project is not set to " + MICRONAUT_PARENT); 136 } 137 } else { 138 getLog().warn("This project has no parent POM defined. To avoid build problems, please set the parent to " + MICRONAUT_PARENT); 139 } 140 } 141 142 private void checkJavaVersion(int maxAllowedVersion) throws MojoExecutionException { 143 if (javaVersion().getMajorVersion() > maxAllowedVersion) { 144 throw new MojoExecutionException("To build native images you must set the Java target byte code level to Java %s or below".formatted(maxAllowedVersion)); 145 } 146 } 147 148 private void buildDockerNativeLambda() throws IOException { 149 var buildImageCmdArguments = new HashMap<String, String>(); 150 151 // Starter sets the right class in pom.xml: 152 // - For applications: io.micronaut.function.aws.runtime.MicronautLambdaRuntime 153 // - For function apps: com.example.BookLambdaRuntime 154 BuildImageCmd buildImageCmd = addNativeImageBuildArgs(buildImageCmdArguments, () -> { 155 try { 156 return dockerService.buildImageCmd(DockerfileMojo.DOCKERFILE_AWS_CUSTOM_RUNTIME) 157 .withBuildArg("GRAALVM_DOWNLOAD_URL", graalVmDownloadUrl()); 158 } catch (IOException e) { 159 throw new DockerClientException(e.getMessage(), e); 160 } 161 }); 162 buildImageCmd.withBuildArg("CLASS_NAME", mainClass); 163 String imageId = dockerService.buildImage(buildImageCmd); 164 File functionZip = dockerService.copyFromContainer(imageId, "/function/function.zip"); 165 getLog().info("AWS Lambda Custom Runtime ZIP: " + functionZip.getPath()); 166 } 167 168 private void buildDockerNative() throws IOException, InvalidImageReferenceException { 169 String dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE; 170 if (Boolean.TRUE.equals(staticNativeImage)) { 171 getLog().info("Generating a static native image"); 172 dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE_STATIC; 173 } else if (baseImageRun.contains("distroless")) { 174 getLog().info("Generating a mostly static native image"); 175 dockerfileName = DockerfileMojo.DOCKERFILE_NATIVE_DISTROLESS; 176 } 177 178 buildDockerfile(dockerfileName, true); 179 } 180 181 private void buildOracleCloud() throws IOException, InvalidImageReferenceException { 182 buildDockerfile(DockerfileMojo.DOCKERFILE_NATIVE_ORACLE_CLOUD, false); 183 } 184 185 private void buildDockerfile(String dockerfileName, boolean passClassName) throws IOException, InvalidImageReferenceException { 186 Set<String> tags = getTags(); 187 for (String tag : tags) { 188 ImageReference.parse(tag); 189 } 190 191 String from = getFrom(); 192 String ports = getPorts(); 193 getLog().info("Exposing port(s): " + ports); 194 195 File dockerfile = dockerService.loadDockerfileAsResource(dockerfileName); 196 197 oracleCloudFunctionCmd(dockerfile); 198 199 var buildImageCmdArguments = new HashMap<String, String>(); 200 201 if (StringUtils.isNotEmpty(baseImageRun) && Boolean.FALSE.equals(staticNativeImage)) { 202 buildImageCmdArguments.put("BASE_IMAGE_RUN", baseImageRun); 203 } 204 205 if (passClassName) { 206 buildImageCmdArguments.put("CLASS_NAME", mainClass); 207 } 208 209 BuildImageCmd buildImageCmd = addNativeImageBuildArgs(buildImageCmdArguments, () -> dockerService.buildImageCmd() 210 .withDockerfile(dockerfile) 211 .withTags(getTags()) 212 .withBuildArg("BASE_IMAGE", from) 213 .withBuildArg("PORTS", ports)); 214 215 dockerService.buildImage(buildImageCmd); 216 } 217 218 private BuildImageCmd addNativeImageBuildArgs(Map<String, String> buildImageCmdArguments, Supplier<BuildImageCmd> buildImageCmdSupplier) throws IOException { 219 String argsFile = mavenProject.getProperties().getProperty(ARGS_FILE_PROPERTY_NAME); 220 List<String> allNativeImageBuildArgs = MojoUtils.computeNativeImageArgs(nativeImageBuildArgs, baseImageRun, argsFile); 221 //Remove extra main class argument 222 allNativeImageBuildArgs.remove(mainClass); 223 getLog().info("GraalVM native image build args: " + allNativeImageBuildArgs); 224 List<String> conversionResult = NativeImageUtils.convertToArgsFile(allNativeImageBuildArgs, Paths.get(mavenProject.getBuild().getDirectory())); 225 if (conversionResult.size() == 1) { 226 Files.delete(Paths.get(argsFile)); 227 228 BuildImageCmd buildImageCmd = buildImageCmdSupplier.get(); 229 230 for (Map.Entry<String, String> buildArg : buildImageCmdArguments.entrySet()) { 231 buildImageCmd.withBuildArg(buildArg.getKey(), buildArg.getValue()); 232 } 233 234 getNetworkMode().ifPresent(buildImageCmd::withNetworkMode); 235 return buildImageCmd; 236 } else { 237 throw new IOException("Unable to convert native image build args to args file"); 238 } 239 } 240 241}