001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.lang3; 019 020import java.io.IOException; 021import java.nio.charset.Charset; 022import java.nio.file.Files; 023import java.nio.file.Paths; 024import java.util.Arrays; 025 026/** 027 * Helps query the runtime environment. 028 * 029 * @since 3.15.0 030 */ 031public class RuntimeEnvironment { 032 033 private static boolean fileExists(final String path) { 034 return Files.exists(Paths.get(path)); 035 } 036 037 /** 038 * Tests whether we are running in a container like Docker or Podman. 039 * <p> 040 * <em>The following may change if we find better detection logic.</em> 041 * </p> 042 * <p> 043 * We roughly follow the logic in SystemD: 044 * </p> 045 * <p> 046 * <a href= 047 * "https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692</a> 048 * </p> 049 * <p> 050 * We check the `container` environment variable of process 1: 051 * </p> 052 * <ol> 053 * <li>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 054 * a container.</li> 055 * <li>If the variable is not empty, we return true.</li> 056 * <li>If the variable is absent, we continue.</li> 057 * <li>We check files in the container. According to SystemD:/ 058 * <ol> 059 * <li>/.dockerenv is used by Docker.</li> 060 * <li>/run/.containerenv is used by PodMan.</li> 061 * </ol> 062 * </li> 063 * </ol> 064 * 065 * @return whether we are running in a container like Docker or Podman. Never null. 066 * @see <a href="https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">SystemD virt.c</a> 067 */ 068 public static Boolean inContainer() { 069 return inContainer(StringUtils.EMPTY); 070 } 071 072 /** 073 * Tests whether we are running in a container like Docker or Podman. 074 * <p> 075 * <em>The following may change if we find better detection logic.</em> 076 * </p> 077 * <p> 078 * We roughly follow the logic in SystemD: 079 * </p> 080 * <p> 081 * <a href= 082 * "https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692</a> 083 * </p> 084 * <p> 085 * We check the `container` environment variable of process 1: 086 * </p> 087 * <ol> 088 * <li>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 089 * a container.</li> 090 * <li>If the variable is not empty, we return true.</li> 091 * <li>If the variable is absent, we continue.</li> 092 * <li>We check files in the container. According to SystemD:/ 093 * <ol> 094 * <li>/.dockerenv is used by Docker.</li> 095 * <li>/run/.containerenv is used by PodMan.</li> 096 * </ol> 097 * </li> 098 * </ol> 099 * 100 * @return Whether we are running in a container like Docker or Podman. 101 * @see <a href="https://github.com/systemd/systemd/blob/0747e3b60eb4496ee122066c844210ce818d76d9/src/basic/virt.c#L692">SystemD virt.c</a> 102 */ 103 static boolean inContainer(final String dirPrefix) { 104 final String value = readFile(dirPrefix + "/proc/1/environ", "container"); 105 if (value != null) { 106 return !value.isEmpty(); 107 } 108 return fileExists(dirPrefix + "/.dockerenv") || fileExists(dirPrefix + "/run/.containerenv"); 109 } 110 111 /** 112 * Tests whether the {@code /proc/N/environ} file at the given path string contains a specific line prefix. 113 * 114 * @param envVarFile The path to a /proc/N/environ file. 115 * @param key The env var key to find. 116 * @return value The env var value or null. 117 */ 118 private static String readFile(final String envVarFile, final String key) { 119 try { 120 final byte[] bytes = Files.readAllBytes(Paths.get(envVarFile)); 121 final String content = new String(bytes, Charset.defaultCharset()); 122 // Split by null byte character 123 final String[] lines = content.split(String.valueOf(CharUtils.NUL)); 124 final String prefix = key + "="; 125 // @formatter:off 126 return Arrays.stream(lines) 127 .filter(line -> line.startsWith(prefix)) 128 .map(line -> line.split("=", 2)) 129 .map(keyValue -> keyValue[1]) 130 .findFirst() 131 .orElse(null); 132 // @formatter:on 133 } catch (final IOException e) { 134 return null; 135 } 136 } 137 138 /** 139 * Constructs a new instance. 140 * 141 * @deprecated Will be removed in 4.0.0. 142 */ 143 @Deprecated 144 public RuntimeEnvironment() { 145 // empty 146 } 147}