This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push:
new 7e0d6dbd2 Improve container detection by mimicing systemd (#1323)
7e0d6dbd2 is described below
commit 7e0d6dbd2a65d8b36a4c9042565d06af34d489a0
Author: maxxedev <[email protected]>
AuthorDate: Mon Dec 2 05:28:28 2024 -0800
Improve container detection by mimicing systemd (#1323)
* Improve container detection by checking for special files
* check for FreeBSD file
* mimic SystemD logic to detect container
* Fix RAT report complaint
* Various refactors and improvements
* Fix checkstyle import order
---
.../apache/commons/lang3/RuntimeEnvironment.java | 88 +++++++++++-----------
.../commons/lang3/RuntimeEnvironmentTest.java | 70 ++++++++++++++---
2 files changed, 104 insertions(+), 54 deletions(-)
diff --git a/src/main/java/org/apache/commons/lang3/RuntimeEnvironment.java
b/src/main/java/org/apache/commons/lang3/RuntimeEnvironment.java
index 66b93d86c..3ee8f723c 100644
--- a/src/main/java/org/apache/commons/lang3/RuntimeEnvironment.java
+++ b/src/main/java/org/apache/commons/lang3/RuntimeEnvironment.java
@@ -18,9 +18,10 @@
package org.apache.commons.lang3;
import java.io.IOException;
+import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
-import java.util.stream.Stream;
+import java.util.Arrays;
/**
* Helps query the runtime environment.
@@ -30,66 +31,63 @@ import java.util.stream.Stream;
public class RuntimeEnvironment {
/**
- * Tests whether the file at the given path string contains a specific
line.
+ * Tests whether the /proc/N/environ file at the given path string
contains a specific line prefix.
*
- * @param path The path to a file.
- * @param line The line to find.
- * @return whether the file at the given path string contains a specific
line.
+ * @param envVarFile The path to a /proc/N/environ file.
+ * @param key The env var key to find.
+ * @return value The env var value or null
*/
- private static Boolean containsLine(final String path, final String line) {
- try (Stream<String> stream = Files.lines(Paths.get(path))) {
- return stream.anyMatch(test -> test.contains(line));
+ private static String getenv(final String envVarFile, final String key) {
+ try {
+ byte[] bytes = Files.readAllBytes(Paths.get(envVarFile));
+ String content = new String(bytes, Charset.defaultCharset());
+ // Split by null byte character
+ String[] lines = content.split("\u0000");
+ String prefix = key + "=";
+ return Arrays.stream(lines)
+ .filter(line -> line.startsWith(prefix))
+ .map(line -> line.split("=", 2))
+ .map(keyValue -> keyValue[1])
+ .findFirst()
+ .orElse(null);
} catch (final IOException e) {
- return false;
+ return null;
}
}
/**
* Tests whether we are running in a container like Docker or Podman.
*
- * @return whether we are running in a container like Docker or Podman.
+ * @return whether we are running in a container like Docker or Podman.
Never null
*/
public static Boolean inContainer() {
- return inDocker() || inPodman();
+ return inContainer("");
}
- /**
- * Tests whether we are running in a Docker container.
- * <p>
- * Package-private for testing.
- * </p>
- *
- * @return whether we are running in a Docker container.
- */
- // Could be public at a later time.
- static Boolean inDocker() {
- return containsLine("/proc/1/cgroup", "/docker");
- }
+ static boolean inContainer(final String dirPrefix) {
+ /*
+ Roughly follow the logic in SystemD:
+
https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692
- /**
- * Tests whether we are running in a Podman container.
- * <p>
- * Package-private for testing.
- * </p>
- *
- * @return whether we are running in a Podman container.
- */
- // Could be public at a later time.
- static Boolean inPodman() {
- return containsLine("/proc/1/environ", "container=podman");
+ We check the `container` environment variable of process 1:
+ If the variable is empty, we return false. This includes the case,
where the container developer wants to hide the fact that the application runs
in a container.
+ If the variable is not empty, we return true.
+ If the variable is absent, we continue.
+
+ We check files in the container. According to SystemD:
+ /.dockerenv is used by Docker.
+ /run/.containerenv is used by PodMan.
+
+ */
+ String value = getenv(dirPrefix + "/proc/1/environ", "container");
+ if (value != null) {
+ return !value.isEmpty();
+ }
+ return fileExists(dirPrefix + "/.dockerenv") || fileExists(dirPrefix +
"/run/.containerenv");
}
- /**
- * Tests whether we are running in a Windows Subsystem for Linux (WSL).
- * <p>
- * Package-private for testing.
- * </p>
- *
- * @return whether we are running in a Windows Subsystem for Linux (WSL).
- */
- // Could be public at a later time.
- static Boolean inWsl() {
- return containsLine("/proc/1/environ",
"container=wslcontainer_host_id");
+ private static boolean fileExists(String path) {
+ return Files.exists(Paths.get(path));
}
/**
diff --git a/src/test/java/org/apache/commons/lang3/RuntimeEnvironmentTest.java
b/src/test/java/org/apache/commons/lang3/RuntimeEnvironmentTest.java
index af0461f75..dc3be2ba7 100644
--- a/src/test/java/org/apache/commons/lang3/RuntimeEnvironmentTest.java
+++ b/src/test/java/org/apache/commons/lang3/RuntimeEnvironmentTest.java
@@ -17,21 +17,73 @@
package org.apache.commons.lang3;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.api.Test;
/**
* Tests {@link RuntimeEnvironment}.
*/
public class RuntimeEnvironmentTest {
- @Test
- public void testIsContainer() {
- // At least make sure it does not blow up.
- assertDoesNotThrow(RuntimeEnvironment::inContainer);
- assertDoesNotThrow(RuntimeEnvironment::inDocker);
- assertDoesNotThrow(RuntimeEnvironment::inPodman);
- assertDoesNotThrow(RuntimeEnvironment::inWsl);
+ private static final String simpleEnviron =
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0000" +
+ "HOSTNAME=d62718b69f37\u0000TERM=xterm\u0000HOME=/root\u0000";
+
+ private static final String podmanEnviron =
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0000" +
+
"HOSTNAME=d62718b69f37\u0000TERM=xterm\u0000container=podman\u0000HOME=/root\u0000";
+
+ private static final String emptyContainer =
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\u0000" +
+
"HOSTNAME=d62718b69f37\u0000TERM=xterm\u0000container=\u0000HOME=/root\u0000";
+
+ @TempDir
+ private Path tempDir;
+
+ private static Arguments[] testIsContainer() {
+ return new Arguments[]{
+ Arguments.of("in docker no file", simpleEnviron, null, false),
+ Arguments.of("in docker with file", simpleEnviron,
".dockerenv", true),
+ Arguments.of("in podman no file", podmanEnviron,
"run/.containerenv", true),
+ Arguments.of("in podman with file", simpleEnviron,
"run/.containerenv", true),
+ Arguments.of("in podman empty env var no file",
emptyContainer, null, false),
+ Arguments.of("in podman empty env var with file",
emptyContainer, "run/.containerenv", false),
+ Arguments.of("not in container", simpleEnviron, null, false),
+ Arguments.of("pid1 error no file", null, null, false),
+ Arguments.of("pid1 error docker file", null, ".dockerenv",
true),
+ Arguments.of("pid1 error podman file", null, ".dockerenv",
true),
+ };
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ public void testIsContainer(String label, String environ, String
fileToCreate, boolean expected) throws IOException {
+ assertEquals(expected, doTestInContainer(environ, fileToCreate),
label);
+ }
+
+ private boolean doTestInContainer(String environ, String fileToCreate)
throws IOException {
+ Path testDir = tempDir.resolve(UUID.randomUUID().toString());
+ Path pid1EnvironFile = testDir.resolve("proc/1/environ");
+ Files.createDirectories(pid1EnvironFile.getParent());
+
+ if (fileToCreate != null) {
+ Path file = testDir.resolve(fileToCreate);
+ Files.createDirectories(file.getParent());
+ Files.createFile(file);
+ }
+
+ if (environ != null) {
+ Files.write(pid1EnvironFile,
environ.getBytes(StandardCharsets.UTF_8));
+ }
+
+ return RuntimeEnvironment.inContainer(testDir.toString());
}
}