Skip to content

Commit 2f4ddd7

Browse files
Add container support for executable bundles
1 parent 820cbc0 commit 2f4ddd7

File tree

3 files changed

+372
-9
lines changed

3 files changed

+372
-9
lines changed

substratevm/src/com.oracle.svm.driver.launcher/src/com/oracle/svm/driver/launcher/BundleLauncher.java

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
import java.util.jar.JarFile;
4848
import java.util.stream.Stream;
4949

50+
import static com.oracle.svm.driver.launcher.ContainerSupport.CONTAINER_GRAAL_VM_HOME;
51+
5052

5153
public class BundleLauncher {
5254

@@ -63,6 +65,9 @@ public class BundleLauncher {
6365
public static final String IMAGE_PATH_OUTPUT_DIR_NAME = "default";
6466
public static final String AUXILIARY_OUTPUT_DIR_NAME = "other";
6567

68+
private static Path rootDir;
69+
private static Path inputDir;
70+
private static Path outputDir;
6671
private static Path stageDir;
6772
private static Path classPathDir;
6873
private static Path modulePathDir;
@@ -76,6 +81,8 @@ public class BundleLauncher {
7681

7782
public static boolean verbose = false;
7883

84+
public static ContainerSupport containerSupport;
85+
7986

8087
public static void main(String[] args) {
8188
bundleFilePath = Paths.get(BundleLauncher.class.getProtectionDomain().getCodeSource().getLocation().getPath());
@@ -93,16 +100,32 @@ public static void main(String[] args) {
93100
ProcessBuilder pb = new ProcessBuilder(command);
94101

95102
Path environmentFile = stageDir.resolve("environment.json");
103+
Map<String, String> launcherEnvironment = new HashMap<>();
96104
if (Files.isReadable(environmentFile)) {
97105
try (Reader reader = Files.newBufferedReader(environmentFile)) {
98-
Map<String, String> launcherEnvironment = new HashMap<>();
99106
new BundleEnvironmentParser(launcherEnvironment).parseAndRegister(reader);
100107
pb.environment().putAll(launcherEnvironment);
101108
} catch (IOException e) {
102109
throw new RuntimeException("Failed to read bundle-file " + environmentFile, e);
103110
}
104111
}
105112

113+
if (useContainer()) {
114+
Path javaHome = getJavaExecutable().getParent().getParent();
115+
ContainerSupport.replaceContainerPaths(command, javaHome, rootDir);
116+
117+
// TODO also mount agentDir if necessary
118+
119+
Map<Path, ContainerSupport.TargetPath> mountMapping = new HashMap<>();
120+
Path containerRoot = Paths.get("/");
121+
mountMapping.put(javaHome, new ContainerSupport.TargetPath(containerRoot.resolve(CONTAINER_GRAAL_VM_HOME),true));
122+
mountMapping.put(inputDir, new ContainerSupport.TargetPath(containerRoot.resolve(INPUT_DIR_NAME),true));
123+
mountMapping.put(outputDir, new ContainerSupport.TargetPath(containerRoot.resolve(OUTPUT_DIR_NAME),false));
124+
125+
containerSupport.initializeContainerImage();
126+
command.addAll(0, containerSupport.createContainerCommand(launcherEnvironment, mountMapping));
127+
}
128+
106129
if (verbose) {
107130
List<String> environmentList = pb.environment()
108131
.entrySet()
@@ -136,6 +159,10 @@ public static void main(String[] args) {
136159
System.exit(exitCode);
137160
}
138161

162+
public static boolean useContainer() {
163+
return containerSupport != null;
164+
}
165+
139166
private static List<String> createLaunchCommand(String[] args) {
140167
List<String> command = new ArrayList<>();
141168

@@ -247,13 +274,43 @@ private static List<String> parseBundleLauncherArgs(String[] args, List<String>
247274
} catch (IOException e) {
248275
System.out.println("Failed to create native image agent output dir");
249276
}
250-
} else if (arg.equals("--verbose")) {
251-
verbose = true;
252-
} else if (arg.equals("--")) {
253-
applicationArgs.addAll(argQueue);
254-
argQueue.clear();
277+
} else if (arg.startsWith("--container")) {
278+
if (useContainer()) {
279+
throw new RuntimeException("native-image bundle allows option container to be specified only once.");
280+
}
281+
Path dockerfile;
282+
if (arg.indexOf(',') != -1) {
283+
String option = arg.substring(arg.indexOf(',') + 1);
284+
arg = arg.substring(0, arg.indexOf(','));
285+
286+
if (option.startsWith("dockerfile")) {
287+
if (option.indexOf('=') != -1) {
288+
dockerfile = Paths.get(option.substring(option.indexOf('=') + 1));
289+
if (!Files.isReadable(dockerfile)) {
290+
throw new Error(String.format("Dockerfile '%s' is not readable", dockerfile.toAbsolutePath()));
291+
}
292+
} else {
293+
throw new Error("container option dockerfile requires a dockerfile argument. E.g. dockerfile=path/to/Dockerfile.");
294+
}
295+
} else {
296+
throw new Error(String.format("Unknown option %s. Valid option is: dockerfile=path/to/Dockerfile.", option));
297+
}
298+
} else {
299+
dockerfile = stageDir.resolve("Dockerfile");
300+
}
301+
containerSupport = new ContainerSupport(dockerfile, stageDir);
302+
if (arg.indexOf('=') != -1) {
303+
containerSupport.containerTool = arg.substring(arg.indexOf('=') + 1);
304+
}
255305
} else {
256-
applicationArgs.add(arg);
306+
switch (arg) {
307+
case "--verbose" -> verbose = true;
308+
case "--" -> {
309+
applicationArgs.addAll(argQueue);
310+
argQueue.clear();
311+
}
312+
default -> applicationArgs.add(arg);
313+
}
257314
}
258315
}
259316

@@ -334,9 +391,8 @@ private static void deleteAllFiles(Path toDelete) {
334391
}
335392

336393
private static void unpackBundle(Path bundleFilePath) {
337-
Path inputDir;
338394
try {
339-
Path rootDir = createBundleRootDir();
395+
rootDir = createBundleRootDir();
340396
inputDir = rootDir.resolve(INPUT_DIR_NAME);
341397

342398
try (JarFile archive = new JarFile(bundleFilePath.toFile())) {
@@ -369,6 +425,7 @@ private static void unpackBundle(Path bundleFilePath) {
369425
Path classesDir = inputDir.resolve(CLASSES_DIR_NAME);
370426
classPathDir = Files.createDirectories(classesDir.resolve(CLASSPATH_DIR_NAME));
371427
modulePathDir = Files.createDirectories(classesDir.resolve(MODULE_PATH_DIR_NAME));
428+
outputDir = Files.createDirectories(rootDir.resolve(OUTPUT_DIR_NAME));
372429
} catch (IOException e) {
373430
throw new RuntimeException("Unable to create bundle directory layout", e);
374431
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.driver.launcher;
26+
27+
import java.nio.charset.StandardCharsets;
28+
import java.security.MessageDigest;
29+
import java.security.NoSuchAlgorithmException;
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
32+
33+
public class BundleLauncherUtil {
34+
35+
private static final char[] HEX = "0123456789abcdef".toCharArray();
36+
public static String digest(String value) {
37+
try {
38+
MessageDigest md = MessageDigest.getInstance("SHA-1");
39+
md.update(value.getBytes(StandardCharsets.UTF_8));
40+
return toHex(md.digest());
41+
} catch (NoSuchAlgorithmException ex) {
42+
throw new Error(ex);
43+
}
44+
}
45+
public static String toHex(byte[] data) {
46+
StringBuilder r = new StringBuilder(data.length * 2);
47+
for (byte b : data) {
48+
r.append(HEX[(b >> 4) & 0xf]);
49+
r.append(HEX[b & 0xf]);
50+
}
51+
return r.toString();
52+
}
53+
54+
private static final Pattern SAFE_SHELL_ARG = Pattern.compile("[A-Za-z0-9@%_\\-+=:,./]+");
55+
public static String quoteShellArg(String arg) {
56+
if (arg.isEmpty()) {
57+
return "''";
58+
}
59+
Matcher m = SAFE_SHELL_ARG.matcher(arg);
60+
if (m.matches()) {
61+
return arg;
62+
}
63+
return "'" + arg.replace("'", "'\"'\"'") + "'";
64+
}
65+
}

0 commit comments

Comments
 (0)