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.jib;
17  
18  import com.google.cloud.tools.jib.api.buildplan.*;
19  import com.google.cloud.tools.jib.buildplan.UnixPathParser;
20  import com.google.cloud.tools.jib.maven.extension.JibMavenPluginExtension;
21  import com.google.cloud.tools.jib.maven.extension.MavenData;
22  import com.google.cloud.tools.jib.plugins.extension.ExtensionLogger;
23  import io.micronaut.build.AbstractDockerMojo;
24  import io.micronaut.build.MicronautRuntime;
25  import io.micronaut.build.services.ApplicationConfigurationService;
26  import io.micronaut.build.services.JibConfigurationService;
27  
28  import java.util.*;
29  
30  /**
31   * Jib extension to support building Docker images.
32   *
33   * @author Álvaro Sánchez-Mariscal
34   * @since 1.1
35   */
36  public class JibMicronautExtension implements JibMavenPluginExtension<Void> {
37  
38      public static final String DEFAULT_BASE_IMAGE = "openjdk:17-alpine";
39  
40      @Override
41      public Optional<Class<Void>> getExtraConfigType() {
42          return Optional.empty();
43      }
44  
45      @Override
46      public ContainerBuildPlan extendContainerBuildPlan(ContainerBuildPlan buildPlan, Map<String, String> properties,
47                                                         Optional<Void> extraConfig, MavenData mavenData,
48                                                         ExtensionLogger logger) {
49  
50          ContainerBuildPlan.Builder builder = buildPlan.toBuilder();
51          MicronautRuntime runtime = MicronautRuntime.valueOf(mavenData.getMavenProject().getProperties().getProperty(MicronautRuntime.PROPERTY, "none").toUpperCase());
52  
53          JibConfigurationService jibConfigurationService = new JibConfigurationService(mavenData.getMavenProject());
54          String from = jibConfigurationService.getFromImage().orElse(DEFAULT_BASE_IMAGE);
55  
56          builder.setBaseImage(from);
57  
58          ApplicationConfigurationService applicationConfigurationService = new ApplicationConfigurationService(mavenData.getMavenProject());
59          try {
60              int port = Integer.parseInt(applicationConfigurationService.getServerPort());
61              if (port > 0) {
62                  logger.log(ExtensionLogger.LogLevel.LIFECYCLE, "Exposing port: " + port);
63                  builder.addExposedPort(Port.tcp(port));
64              }
65          } catch (NumberFormatException e) {
66              // ignore, can't automatically expose port
67              logger.log(ExtensionLogger.LogLevel.LIFECYCLE, "Dynamically resolved port present. Ensure the port is correctly exposed in the <container> configuration. See https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#example for an example.");
68          }
69  
70          switch (runtime.getBuildStrategy()) {
71              case ORACLE_FUNCTION:
72                  List<? extends LayerObject> originalLayers = buildPlan.getLayers();
73                  builder.setLayers(Collections.emptyList());
74  
75                  for (LayerObject layer : originalLayers) {
76                      builder.addLayer(remapLayer(layer));
77                  }
78  
79                  List<String> cmd = jibConfigurationService.getArgs();
80                  if (cmd.isEmpty()) {
81                      cmd = Collections.singletonList("io.micronaut.oraclecloud.function.http.HttpFunction::handleRequest");
82                  }
83  
84                  String projectFnVersion = determineProjectFnVersion(System.getProperty("java.version"));
85                  builder.setBaseImage("fnproject/fn-java-fdk:" + projectFnVersion)
86                          .setWorkingDirectory(AbsoluteUnixPath.get(jibConfigurationService.getWorkingDirectory().orElse("/function")))
87                          .setEntrypoint(buildProjectFnEntrypoint(projectFnVersion))
88                          .setCmd(cmd);
89                  break;
90  
91              case LAMBDA:
92                  List<String> entrypoint = buildPlan.getEntrypoint();
93                  Objects.requireNonNull(entrypoint).set(entrypoint.size() - 1, "io.micronaut.function.aws.runtime.MicronautLambdaRuntime");
94                  builder.setEntrypoint(entrypoint);
95                  break;
96  
97              default:
98                  //no op
99          }
100         return builder.build();
101     }
102 
103     public static List<String> buildProjectFnEntrypoint(String projectFnVersion) {
104         List<String> entrypoint = new ArrayList<>(9);
105         if (AbstractDockerMojo.LATEST_TAG.equals(projectFnVersion)) { // Java 8
106             entrypoint.add("/usr/java/latest/bin/java");
107             entrypoint.add("-XX:+UnlockExperimentalVMOptions");
108             entrypoint.add("-XX:+UseCGroupMemoryLimitForHeap");
109             entrypoint.add("-XX:-UsePerfData");
110             entrypoint.add("-XX:MaxRAMFraction=2");
111             entrypoint.add("-XX:+UseSerialGC");
112             entrypoint.add("-Xshare:on");
113             entrypoint.add("-Djava.library.path=/function/runtime/lib");
114             entrypoint.add("-cp");
115             entrypoint.add("/function/app/classes:/function/app/libs/*:/function/app/resources:/function/runtime/*");
116             entrypoint.add("com.fnproject.fn.runtime.EntryPoint");
117         } else { // Java 11 onwards
118             entrypoint.add("/usr/java/latest/bin/java");
119             entrypoint.add("-XX:-UsePerfData");
120             entrypoint.add("-XX:+UseSerialGC");
121             entrypoint.add("-Xshare:on");
122             entrypoint.add("-Djava.awt.headless=true");
123             entrypoint.add("-Djava.library.path=/function/runtime/lib");
124             entrypoint.add("-cp");
125             entrypoint.add("/function/app/classes:/function/app/libs/*:/function/app/resources:/function/runtime/*");
126             entrypoint.add("com.fnproject.fn.runtime.EntryPoint");
127         }
128         return entrypoint;
129     }
130 
131     public static String determineProjectFnVersion(String javaVersion) {
132         int majorVersion = Integer.parseInt(javaVersion.split("\\.")[0]);
133         if (majorVersion == 1) {
134             majorVersion = Integer.parseInt(javaVersion.split("\\.")[1]);
135         }
136         if (majorVersion >= 17) {
137             return "jre17-latest";
138         } else if (majorVersion >= 11) {
139             return "jre11-latest";
140         } else {
141             return AbstractDockerMojo.LATEST_TAG;
142         }
143     }
144 
145     private LayerObject remapLayer(LayerObject layerObject) {
146         FileEntriesLayer originalLayer = (FileEntriesLayer) layerObject;
147         FileEntriesLayer.Builder builder = FileEntriesLayer.builder().setName(originalLayer.getName());
148         for (FileEntry originalEntry : originalLayer.getEntries()) {
149             builder.addEntry(remapEntry(originalEntry, layerObject.getName()));
150         }
151 
152         return builder.build();
153     }
154 
155     private FileEntry remapEntry(FileEntry originalEntry, String layerName) {
156         List<String> pathComponents = UnixPathParser.parse(originalEntry.getExtractionPath().toString());
157         AbsoluteUnixPath newPath;
158         if (layerName.contains("dependencies")) {
159             newPath = AbsoluteUnixPath.get("/function/app/libs/" + pathComponents.get(pathComponents.size() - 1));
160         } else {
161             //classes or resources
162             newPath = AbsoluteUnixPath.get("/function" + originalEntry.getExtractionPath());
163         }
164 
165         return new FileEntry(originalEntry.getSourceFile(), newPath, originalEntry.getPermissions(),
166                 originalEntry.getModificationTime(), originalEntry.getOwnership());
167     }
168 
169 }