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.google.cloud.tools.jib.api.ImageReference; 019import com.google.cloud.tools.jib.api.InvalidImageReferenceException; 020import com.google.common.io.FileWriteMode; 021import io.micronaut.core.util.StringUtils; 022import io.micronaut.maven.core.MicronautRuntime; 023import io.micronaut.maven.core.MojoUtils; 024import io.micronaut.maven.jib.JibConfigurationService; 025import io.micronaut.maven.jib.JibMicronautExtension; 026import io.micronaut.maven.services.ApplicationConfigurationService; 027import io.micronaut.maven.services.DockerService; 028import org.apache.maven.artifact.Artifact; 029import org.apache.maven.artifact.versioning.ArtifactVersion; 030import org.apache.maven.artifact.versioning.DefaultArtifactVersion; 031import org.apache.maven.execution.MavenSession; 032import org.apache.maven.plugin.MojoExecutionException; 033import org.apache.maven.plugin.MojoExecution; 034import org.apache.maven.plugin.PluginParameterExpressionEvaluator; 035import org.apache.maven.plugins.annotations.Parameter; 036import org.apache.maven.project.MavenProject; 037 038import java.io.File; 039import java.io.IOException; 040import java.net.URI; 041import java.net.URISyntaxException; 042import java.nio.charset.Charset; 043import java.nio.file.Files; 044import java.nio.file.StandardCopyOption; 045import java.util.ArrayList; 046import java.util.Arrays; 047import java.util.HashSet; 048import java.util.List; 049import java.util.Locale; 050import java.util.Map; 051import java.util.NavigableSet; 052import java.util.Optional; 053import java.util.Set; 054import java.util.TreeSet; 055import java.util.stream.Collectors; 056import java.util.regex.Matcher; 057import java.util.regex.Pattern; 058 059import static io.micronaut.maven.services.ApplicationConfigurationService.DEFAULT_PORT; 060 061/** 062 * Abstract base class for mojos related to Docker files and builds. 063 * 064 * @author Álvaro Sánchez-Mariscal 065 * @author Iván López 066 * @since 1.1 067 */ 068public abstract class AbstractDockerMojo extends AbstractMicronautMojo { 069 070 public static final String LATEST_TAG = "latest"; 071 public static final String DEFAULT_BASE_IMAGE_GRAALVM_RUN = "cgr.dev/chainguard/wolfi-base@sha256:52e71f61c6afd1f8d2625cff4465d8ecee156668ca665f7e9c582d1cc914eb6a"; 072 public static final String DEFAULT_BASE_IMAGE_GRAALVM_BUILD = "container-registry.oracle.com/graalvm/native-image"; 073 public static final String MOSTLY_STATIC_NATIVE_IMAGE_GRAALVM_FLAG = "-H:+StaticExecutableWithDynamicLibC"; 074 public static final String ARM_ARCH = "aarch64"; 075 public static final String X86_64_ARCH = "x64"; 076 public static final String ORACLE_CLOUD_FUNCTION_DEFAULT_CMD = "CMD [\"io.micronaut.oraclecloud.function.http.HttpFunction::handleRequest\"]"; 077 public static final String GDS_DOWNLOAD_URL = "https://gds.oracle.com/download/graal/%s/latest-gftc/graalvm-jdk-%s_linux-%s_bin.tar.gz"; 078 public static final String LAMBDA_BOOTSTRAP_DOCKER_COMMAND_PLACEHOLDER = "${LAMBDA_BOOTSTRAP_DOCKER_COMMAND}"; 079 static final String JIB_FROM_IMAGE_PROPERTY = "jib.from.image"; 080 private static final String DEPENDENCY_DIRECTORY = "dependency"; 081 private static final String RELEASE_DEPENDENCY_DIRECTORY = "release"; 082 private static final String SNAPSHOT_DEPENDENCY_DIRECTORY = "snapshot"; 083 private static final Pattern LEADING_MAJOR_VERSION = Pattern.compile("([1-9][0-9]*)(?:[.-].*)?"); 084 private static final NavigableSet<Integer> GRAALVM_VERSIONS = new TreeSet<>(Set.of(25)); 085 private static final List<String> DEFAULT_LAMBDA_BOOTSTRAP_ARGUMENTS = List.of( 086 "-XX:MaximumHeapSizePercent=80", 087 "-Dio.netty.allocator.numDirectArenas=0", 088 "-Dio.netty.noPreferDirect=true", 089 "-Djava.library.path=$(pwd)" 090 ); 091 092 protected final MavenProject mavenProject; 093 protected final MavenSession mavenSession; 094 protected final JibConfigurationService jibConfigurationService; 095 protected final ApplicationConfigurationService applicationConfigurationService; 096 protected final DockerService dockerService; 097 protected final PluginParameterExpressionEvaluator expressionEvaluator; 098 099 100 /** 101 * Additional arguments that will be passed to the <code>native-image</code> executable. Note that this will only 102 * be used when using a packaging of type <code>docker-native</code>. For <code>native-image</code> packaging 103 * you should use the 104 * <a href="https://www.graalvm.org/reference-manual/native-image/NativeImageMavenPlugin/#maven-plugin-customization"> 105 * Native Image Maven Plugin 106 * </a> configuration options. 107 */ 108 @Parameter(property = "micronaut.native-image.args") 109 protected List<String> nativeImageBuildArgs; 110 111 /** 112 * List of additional arguments that will be passed to the application. 113 */ 114 @Parameter(property = RunMojo.MN_APP_ARGS) 115 protected List<String> appArguments; 116 117 /** 118 * Additional arguments that will be appended to the generated AWS Lambda native bootstrap command. 119 * 120 * @since 5.0.0 121 */ 122 @Parameter(property = "micronaut.lambda.bootstrap.args") 123 protected List<String> lambdaBootstrapArguments; 124 125 /** 126 * The main class of the application, as defined in the 127 * <a href="https://www.mojohaus.org/exec-maven-plugin/java-mojo.html#mainClass">Exec Maven Plugin</a>. 128 */ 129 @Parameter(defaultValue = RunMojo.EXEC_MAIN_CLASS, required = true) 130 protected String mainClass; 131 132 /** 133 * Whether to produce a static native image when using <code>docker-native</code> packaging. 134 */ 135 @Parameter(defaultValue = "false", property = "micronaut.native-image.static") 136 protected Boolean staticNativeImage; 137 138 /** 139 * The target runtime of the application. 140 */ 141 @Parameter(property = MicronautRuntime.PROPERTY, defaultValue = "NONE") 142 protected String micronautRuntime; 143 144 /** 145 * The Docker image used to run the native image. 146 * 147 * @since 1.2 148 */ 149 @Parameter(property = "micronaut.native-image.base-image-run", defaultValue = DEFAULT_BASE_IMAGE_GRAALVM_RUN) 150 protected String baseImageRun; 151 152 /** 153 * The builder-stage base image used to build the native image for docker-native packaging variants. 154 * 155 * @since 5.0.0 156 */ 157 @Parameter(property = "micronaut.native-image.base-image") 158 protected String baseImage; 159 160 /** 161 * The version of Oracle Linux to use as a native-compile base when building a native image inside a Docker container. 162 */ 163 @Parameter(property = "micronaut.native-image.ol.version", defaultValue = "ol9") 164 protected String oracleLinuxVersion; 165 166 /** 167 * Networking mode for the RUN instructions during build. 168 * 169 * @since 4.0.0 170 */ 171 @Parameter(property = "docker.networkMode") 172 protected String networkMode; 173 174 /** 175 * <p> 176 * Jib goal used to build Docker images for {@code docker} packaging. 177 * </p> 178 * <p> 179 * Defaults to {@code dockerBuild}. Set it to {@code buildTar} or {@code build} to avoid talking to a local Docker daemon during {@code package}. 180 * </p> 181 * 182 * @since 5.0.0 183 */ 184 @Parameter(property = "jib.buildGoal", defaultValue = "dockerBuild") 185 protected String jibBuildGoal; 186 187 protected AbstractDockerMojo(MavenProject mavenProject, JibConfigurationService jibConfigurationService, 188 ApplicationConfigurationService applicationConfigurationService, 189 DockerService dockerService, MavenSession mavenSession, MojoExecution mojoExecution) { 190 this.mavenProject = mavenProject; 191 this.mavenSession = mavenSession; 192 this.jibConfigurationService = jibConfigurationService; 193 this.applicationConfigurationService = applicationConfigurationService; 194 this.dockerService = dockerService; 195 this.expressionEvaluator = new PluginParameterExpressionEvaluator(mavenSession, mojoExecution); 196 } 197 198 /** 199 * @return the Java version from either the <code>maven.compiler.target</code> property or the <code>java.version</code> property. 200 */ 201 protected ArtifactVersion javaVersion() { 202 return new DefaultArtifactVersion(getJdkVersion()); 203 } 204 205 private String getJdkVersion() { 206 var releaseVersion = getPropertyValue(mavenProject, "maven.compiler.release"); 207 var targetVersion = getPropertyValue(mavenProject, "maven.compiler.target"); 208 return releaseVersion.or(() -> targetVersion).orElseGet(() -> System.getProperty("java.version")); 209 } 210 211 private static Optional<String> getPropertyValue(MavenProject project, String propertName) { 212 var systemProperty = Optional.of(propertName).map(System::getProperty); 213 var properties = project.getProperties(); 214 var projectProperty = Optional.of(propertName).map(properties::getProperty); 215 return systemProperty.or(() -> projectProperty); 216 } 217 218 /** 219 * @return the JVM version to use for GraalVM. 220 */ 221 protected String graalVmJvmVersion() { 222 return Integer.toString(resolveGraalVersion()); 223 } 224 225 /** 226 * @return the GraalVM download URL depending on the Java version. 227 */ 228 protected String graalVmDownloadUrl() { 229 Integer version = resolveGraalVersion(); 230 231 return GDS_DOWNLOAD_URL.formatted(version, version, graalVmArch()); 232 } 233 234 private Integer resolveGraalVersion() { 235 int target = javaVersion().getMajorVersion(); 236 Integer version = GRAALVM_VERSIONS.floor(target); 237 238 return version != null ? version : GRAALVM_VERSIONS.first(); 239 } 240 241 /** 242 * @return the OS architecture to use for GraalVM depending on the <code>os.arch</code> system property. 243 */ 244 protected String graalVmArch() { 245 return isArm() ? ARM_ARCH : X86_64_ARCH; 246 } 247 248 /** 249 * @return the base FROM image for the native image. 250 */ 251 protected String getFrom() { 252 return getJibFromImageSystemProperty() 253 .or(() -> Optional.ofNullable(baseImage).filter(StringUtils::hasText)) 254 .or(() -> getFromImage().filter(StringUtils::hasText)) 255 .orElse(DEFAULT_BASE_IMAGE_GRAALVM_BUILD + ":" + graalVmTag(graalVmJvmVersion(), staticNativeImage, oracleLinuxVersion)); 256 } 257 258 /** 259 * @return whether the selected native-image builder is known to support {@code -H:+SharedArenaSupport}. 260 */ 261 protected boolean supportsSharedArena() { 262 return sharedArenaBuilderMajorVersion() 263 .map(MojoUtils::supportsSharedArena) 264 .orElse(false); 265 } 266 267 private Optional<Integer> sharedArenaBuilderMajorVersion() { 268 return getJibFromImageSystemProperty() 269 .or(() -> Optional.ofNullable(baseImage).filter(StringUtils::hasText)) 270 .or(() -> getFromImage().filter(StringUtils::hasText)) 271 .map(AbstractDockerMojo::graalVmNativeImageBuilderMajorVersion) 272 .orElseGet(() -> Optional.of(resolveGraalVersion())); 273 } 274 275 static Optional<Integer> graalVmNativeImageBuilderMajorVersion(String image) { 276 if (!StringUtils.hasText(image)) { 277 return Optional.empty(); 278 } 279 280 int digestStart = image.indexOf('@'); 281 String imageWithoutDigest = digestStart >= 0 ? image.substring(0, digestStart) : image; 282 int lastSlash = imageWithoutDigest.lastIndexOf('/'); 283 int tagSeparator = imageWithoutDigest.lastIndexOf(':'); 284 if (tagSeparator <= lastSlash) { 285 return Optional.empty(); 286 } 287 288 String repository = imageWithoutDigest.substring(0, tagSeparator).toLowerCase(Locale.ROOT); 289 if (!isGraalVmNativeImageRepository(repository)) { 290 return Optional.empty(); 291 } 292 293 String tag = imageWithoutDigest.substring(tagSeparator + 1); 294 Matcher matcher = LEADING_MAJOR_VERSION.matcher(tag); 295 if (!matcher.matches()) { 296 return Optional.empty(); 297 } 298 try { 299 return Optional.of(Integer.parseInt(matcher.group(1))); 300 } catch (NumberFormatException e) { 301 return Optional.empty(); 302 } 303 } 304 305 private static boolean isGraalVmNativeImageRepository(String repository) { 306 int lastSlash = repository.lastIndexOf('/'); 307 String imageName = lastSlash >= 0 ? repository.substring(lastSlash + 1) : repository; 308 return (repository.startsWith("graalvm/") || repository.contains("/graalvm/")) 309 && imageName.startsWith("native-image"); 310 } 311 312 /** 313 * @param graalVmJvmVersion the JVM version string 314 * @param staticNativeImage whether to produce a static native image 315 * @param oracleLinuxVersion the Oracle Linux version to use 316 * @return the GraalVM Docker image tag based on the provided parameters 317 */ 318 protected String graalVmTag(String graalVmJvmVersion, Boolean staticNativeImage, String oracleLinuxVersion) { 319 String suffix = Boolean.TRUE.equals(staticNativeImage) 320 ? "-muslib" + (StringUtils.hasText(oracleLinuxVersion) ? "-" + oracleLinuxVersion : "") 321 : (StringUtils.hasText(oracleLinuxVersion) ? "-" + oracleLinuxVersion : ""); 322 return graalVmJvmVersion + suffix; 323 } 324 325 /** 326 * Check os.arch against known ARM architecture identifiers. 327 * 328 * @return true if we think we're running on an arm JDK 329 */ 330 protected boolean isArm() { 331 return switch (System.getProperty("os.arch")) { 332 case ARM_ARCH, "arm64" -> true; 333 default -> false; 334 }; 335 } 336 337 /** 338 * @return the base image from the jib configuration (if any). 339 */ 340 protected Optional<String> getFromImage() { 341 return jibConfigurationService.getFromImage(); 342 } 343 344 /** 345 * @return the base image from the Jib system property override, if any. 346 */ 347 protected Optional<String> getJibFromImageSystemProperty() { 348 return Optional.ofNullable(System.getProperty(JIB_FROM_IMAGE_PROPERTY)) 349 .filter(StringUtils::hasText); 350 } 351 352 /** 353 * @return the Docker image tags by looking at the Jib plugin configuration. 354 */ 355 protected Set<String> getTags() { 356 var tags = new HashSet<String>(); 357 Optional<String> toImageOptional = jibConfigurationService.getToImage(); 358 String imageName = mavenProject.getArtifactId(); 359 if (toImageOptional.isPresent()) { 360 String toImage = toImageOptional.get(); 361 if (toImage.contains(":")) { 362 tags.add(toImage); 363 imageName = toImageOptional.get().split(":")[0]; 364 } else { 365 tags.add(toImage + ":" + LATEST_TAG); 366 imageName = toImage; 367 } 368 } else { 369 tags.add(imageName + ":" + LATEST_TAG); 370 } 371 for (String tag : jibConfigurationService.getTags()) { 372 if (LATEST_TAG.equals(tag) && tags.stream().anyMatch(t -> t.contains(LATEST_TAG))) { 373 continue; 374 } 375 tags.add(String.format("%s:%s", imageName, tag)); 376 } 377 return tags.stream() 378 .map(this::evaluateExpression) 379 .collect(Collectors.toSet()); 380 } 381 382 private String evaluateExpression(String expression) { 383 try { 384 return expressionEvaluator.evaluate(expression, String.class).toString(); 385 } catch (Exception e) { 386 return expression; 387 } 388 } 389 390 /** 391 * @return the application ports to expose by looking at the Jib configuration or the application configuration. 392 */ 393 protected String getPorts() { 394 return jibConfigurationService.getPorts().orElseGet(() -> { 395 String port = applicationConfigurationService.getServerPort(); 396 return "-1".equals(port) ? DEFAULT_PORT : port; 397 }); 398 } 399 400 /** 401 * Copy project dependencies to a <code>target/dependency</code> directory. 402 */ 403 protected void copyDependencies() throws IOException { 404 var imageClasspathScopes = Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME); 405 var target = new File(mavenProject.getBuild().getDirectory(), DEPENDENCY_DIRECTORY).toPath(); 406 Files.createDirectories(target); 407 Files.createDirectories(target.resolve(RELEASE_DEPENDENCY_DIRECTORY)); 408 Files.createDirectories(target.resolve(SNAPSHOT_DEPENDENCY_DIRECTORY)); 409 for (Artifact dependency : mavenProject.getArtifacts()) { 410 if (!imageClasspathScopes.contains(dependency.getScope())) { 411 continue; 412 } 413 var dependencyFile = dependency.getFile().toPath(); 414 var dependencyName = dependency.getFile().getName(); 415 var layeredPath = target.resolve(dependencyLayerDirectory(dependency)).resolve(dependencyName); 416 Files.copy(dependencyFile, layeredPath, StandardCopyOption.REPLACE_EXISTING); 417 Files.copy(dependencyFile, target.resolve(dependencyName), StandardCopyOption.REPLACE_EXISTING); 418 } 419 } 420 421 private static String dependencyLayerDirectory(Artifact dependency) { 422 return dependency.isSnapshot() ? SNAPSHOT_DEPENDENCY_DIRECTORY : RELEASE_DEPENDENCY_DIRECTORY; 423 } 424 425 /** 426 * @return the Docker CMD command. 427 */ 428 protected String getCmd() throws MojoExecutionException { 429 var escapedArguments = new ArrayList<String>(appArguments.size()); 430 for (String argument : appArguments) { 431 escapedArguments.add(jsonStringLiteral("mn.app.args", argument)); 432 } 433 return "CMD [" + String.join(", ", escapedArguments) + "]"; 434 } 435 436 /** 437 * @return the generated AWS Lambda bootstrap command. 438 */ 439 protected String getLambdaBootstrapCommand() throws MojoExecutionException { 440 var command = new StringBuilder("./func"); 441 for (String bootstrapArgument : DEFAULT_LAMBDA_BOOTSTRAP_ARGUMENTS) { 442 command.append(' ').append(bootstrapArgument); 443 } 444 if (lambdaBootstrapArguments != null && !lambdaBootstrapArguments.isEmpty()) { 445 for (String lambdaBootstrapArgument : lambdaBootstrapArguments) { 446 command.append(' ').append(escapeBootstrapArgument(lambdaBootstrapArgument)); 447 } 448 } 449 return command.toString(); 450 } 451 452 /** 453 * Applies the generated AWS Lambda bootstrap script to a dockerfile template. 454 * 455 * @param dockerfile the docker file 456 */ 457 protected void lambdaBootstrapCommand(File dockerfile) throws IOException, MojoExecutionException { 458 if (dockerfile == null) { 459 return; 460 } 461 String lambdaBootstrapDockerCommand = getLambdaBootstrapDockerCommand(); 462 if (lambdaBootstrapArguments != null && !lambdaBootstrapArguments.isEmpty()) { 463 getLog().info("Using AWS Lambda bootstrap arguments: " + lambdaBootstrapArguments); 464 } 465 var allLines = Files.readAllLines(dockerfile.toPath()); 466 var result = new ArrayList<String>(allLines.size()); 467 for (String line : allLines) { 468 if (line.contains(LAMBDA_BOOTSTRAP_DOCKER_COMMAND_PLACEHOLDER)) { 469 result.add(line.replace(LAMBDA_BOOTSTRAP_DOCKER_COMMAND_PLACEHOLDER, lambdaBootstrapDockerCommand)); 470 } else { 471 result.add(line); 472 } 473 } 474 Files.write(dockerfile.toPath(), result); 475 } 476 477 private String getLambdaBootstrapDockerCommand() throws MojoExecutionException { 478 var quotedLines = new ArrayList<String>(); 479 for (String line : List.of("#!/bin/sh", "set -euo pipefail", getLambdaBootstrapCommand())) { 480 quotedLines.add(quoteShellLiteral("AWS Lambda bootstrap command", line)); 481 } 482 return "printf '%s\\n' " + String.join(" ", quotedLines) + " > bootstrap"; 483 } 484 485 private static String escapeBootstrapArgument(String argument) throws MojoExecutionException { 486 String sanitized = validateDockerfileValue("micronaut.lambda.bootstrap.args", argument); 487 if (isShellSafe(sanitized)) { 488 return sanitized; 489 } 490 return quoteShellLiteral("micronaut.lambda.bootstrap.args", sanitized); 491 } 492 493 private static boolean isShellSafe(String argument) { 494 if (argument == null || argument.isEmpty()) { 495 return false; 496 } 497 for (int i = 0; i < argument.length(); i++) { 498 char c = argument.charAt(i); 499 if (!Character.isLetterOrDigit(c) 500 && "_@%+=:,./-".indexOf(c) == -1) { 501 return false; 502 } 503 } 504 return true; 505 } 506 507 static String quoteShellLiteral(String source, String value) throws MojoExecutionException { 508 validateDockerfileValue(source, value); 509 return "'" + value.replace("'", "'\"'\"'") + "'"; 510 } 511 512 protected static String validateDockerfileValue(String source, String value) throws MojoExecutionException { 513 if (value == null) { 514 throw new MojoExecutionException(source + " must not be null when generating a Dockerfile"); 515 } 516 for (int i = 0; i < value.length(); i++) { 517 char c = value.charAt(i); 518 if (Character.isISOControl(c)) { 519 throw new MojoExecutionException(source + " contains an unsupported control character at index " + i 520 + " and cannot be written into a generated Dockerfile"); 521 } 522 } 523 return value; 524 } 525 526 protected static String validateImageReference(String source, String value) throws MojoExecutionException { 527 String sanitized = validateDockerfileValue(source, value); 528 try { 529 ImageReference.parse(sanitized); 530 } catch (InvalidImageReferenceException e) { 531 throw new MojoExecutionException(source + " is not a valid Docker image reference: " + sanitized, e); 532 } 533 return sanitized; 534 } 535 536 protected static String validateExposedPorts(String source, String value) throws MojoExecutionException { 537 String sanitized = validateDockerfileValue(source, value); 538 for (String token : sanitized.split("\\s+")) { 539 if (token.isEmpty()) { 540 continue; 541 } 542 if (!token.matches("\\d+(/(?:tcp|udp))?")) { 543 throw new MojoExecutionException(source + " contains an invalid exposed port token: " + token); 544 } 545 } 546 return sanitized; 547 } 548 549 protected static String validateDownloadUrl(String source, String value) throws MojoExecutionException { 550 String sanitized = validateDockerfileValue(source, value); 551 try { 552 URI uri = new URI(sanitized); 553 String scheme = uri.getScheme(); 554 if (!"https".equalsIgnoreCase(scheme) && !"http".equalsIgnoreCase(scheme)) { 555 throw new MojoExecutionException(source + " must use an http or https URL: " + sanitized); 556 } 557 if (!uri.isAbsolute()) { 558 throw new MojoExecutionException(source + " must be an absolute URL: " + sanitized); 559 } 560 } catch (URISyntaxException e) { 561 throw new MojoExecutionException(source + " is not a valid URL: " + sanitized, e); 562 } 563 return sanitized; 564 } 565 566 protected static String jsonStringLiteral(String source, String value) throws MojoExecutionException { 567 return "\"" + escapeJsonString(source, value) + "\""; 568 } 569 570 protected static String escapeJsonString(String source, String value) throws MojoExecutionException { 571 String sanitized = validateDockerfileValue(source, value); 572 var result = new StringBuilder(sanitized.length() + 8); 573 for (int i = 0; i < sanitized.length(); i++) { 574 char c = sanitized.charAt(i); 575 switch (c) { 576 case '"' -> result.append("\\\""); 577 case '\\' -> result.append("\\\\"); 578 case '\b' -> result.append("\\b"); 579 case '\f' -> result.append("\\f"); 580 case '\n' -> result.append("\\n"); 581 case '\r' -> result.append("\\r"); 582 case '\t' -> result.append("\\t"); 583 default -> { 584 if (c < 0x20) { 585 result.append(String.format("\\u%04x", (int) c)); 586 } else { 587 result.append(c); 588 } 589 } 590 } 591 } 592 return result.toString(); 593 } 594 595 protected static String shellLiteral(String source, String value) throws MojoExecutionException { 596 return quoteShellLiteral(source, validateDockerfileValue(source, value)); 597 } 598 599 protected static String escapeShellDoubleQuoted(String source, String value) throws MojoExecutionException { 600 return validateDockerfileValue(source, value) 601 .replace("\\", "\\\\") 602 .replace("\"", "\\\"") 603 .replace("$", "\\$") 604 .replace("`", "\\`"); 605 } 606 607 /** 608 * @return Networking mode for the RUN instructions during build (if any). 609 */ 610 protected Optional<String> getNetworkMode() { 611 return Optional.ofNullable(networkMode); 612 } 613 614 /** 615 * @return Map of proxy-related build arguments for Docker builds. 616 */ 617 protected Map<String, String> getProxyBuildArgs() { 618 var proxyArgs = new java.util.HashMap<String, String>(); 619 620 // HTTP proxy configuration from standard JVM properties 621 String httpProxyHost = System.getProperty("http.proxyHost"); 622 String httpProxyPort = System.getProperty("http.proxyPort", "80"); 623 if (StringUtils.hasText(httpProxyHost)) { 624 String httpProxy = "http://" + httpProxyHost + ":" + httpProxyPort; 625 proxyArgs.put("HTTP_PROXY", httpProxy); 626 proxyArgs.put("http_proxy", httpProxy); 627 } 628 629 // HTTPS proxy configuration from standard JVM properties 630 String httpsProxyHost = System.getProperty("https.proxyHost"); 631 String httpsProxyPort = System.getProperty("https.proxyPort", "443"); 632 if (StringUtils.hasText(httpsProxyHost)) { 633 String httpsProxy = "http://" + httpsProxyHost + ":" + httpsProxyPort; 634 proxyArgs.put("HTTPS_PROXY", httpsProxy); 635 proxyArgs.put("https_proxy", httpsProxy); 636 } 637 638 // No proxy configuration from standard JVM properties 639 String nonProxyHosts = System.getProperty("http.nonProxyHosts"); 640 if (StringUtils.hasText(nonProxyHosts)) { 641 // Convert Java format (e.g., "*.company.com|localhost") to standard format (e.g., "*.company.com,localhost") 642 String noProxy = nonProxyHosts.replace("|", ","); 643 proxyArgs.put("NO_PROXY", noProxy); 644 proxyArgs.put("no_proxy", noProxy); 645 } 646 647 return proxyArgs; 648 } 649 650 /** 651 * @return the base image to use for the Dockerfile. 652 */ 653 protected String getBaseImage() { 654 return JibMicronautExtension.determineBaseImage(JibMicronautExtension.getJdkVersion(mavenSession), MicronautRuntime.valueOf(micronautRuntime.toUpperCase()).getBuildStrategy()); 655 } 656 657 /** 658 * Adds cmd to docker oracle cloud function file. 659 * 660 * @param dockerfile the docker file 661 */ 662 protected void oracleCloudFunctionCmd(File dockerfile) throws IOException, MojoExecutionException { 663 if (appArguments != null && !appArguments.isEmpty()) { 664 getLog().info("Using application arguments: " + appArguments); 665 com.google.common.io.Files.asCharSink(dockerfile, Charset.defaultCharset(), FileWriteMode.APPEND) 666 .write(System.lineSeparator() + getCmd()); 667 } else { 668 com.google.common.io.Files.asCharSink(dockerfile, Charset.defaultCharset(), FileWriteMode.APPEND).write(System.lineSeparator() + ORACLE_CLOUD_FUNCTION_DEFAULT_CMD); 669 } 670 } 671 672}