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