55import java .io .InputStream ;
66import java .io .InputStreamReader ;
77import java .nio .charset .StandardCharsets ;
8+ import java .nio .file .Files ;
9+ import java .nio .file .Path ;
10+ import java .nio .file .StandardOpenOption ;
811import java .util .function .Predicate ;
912import java .util .stream .Collectors ;
1013
@@ -18,53 +21,118 @@ public final class ContainerRuntimeUtil {
1821 private static final Logger log = Logger .getLogger (ContainerRuntimeUtil .class );
1922 private static final String DOCKER_EXECUTABLE = ConfigProvider .getConfig ().unwrap (SmallRyeConfig .class )
2023 .getOptionalValue ("quarkus.native.container-runtime" , String .class ).orElse (null );
24+ /*
25+ * Caching the value in a file helps us only as much as one JVM execution is concerned.
26+ * Test suite's pom.xml sets things like <argLine>-Djava.io.tmpdir="${project.build.directory}"</argLine>,
27+ * so the file could appear in /tmp/ or C:\Users\karm\AppData\Local\Temp\ or in fact in
28+ * quarkus/integration-tests/something/target.
29+ * There is no point in reaching it in `Path.of(Paths.get("").toAbsolutePath().toString(), "target",
30+ * "quarkus_container_runtime.txt")`
31+ * as the file is deleted between JVM executions anyway.
32+ */
33+ static final Path CONTAINER_RUNTIME = Path .of (System .getProperty ("java.io.tmpdir" ), "quarkus_container_runtime.txt" );
2134
2235 private ContainerRuntimeUtil () {
2336 }
2437
2538 /**
2639 * @return {@link ContainerRuntime#DOCKER} if it's available, or {@link ContainerRuntime#PODMAN}
2740 * if the podman
28- * executable exists in the environment or if the docker executable is an alias to podman
41+ * executable exists in the environment or if the docker executable is an alias to podman,
42+ * or {@link ContainerRuntime#UNAVAILABLE} if no container runtime is available and the required arg is false.
2943 * @throws IllegalStateException if no container runtime was found to build the image
3044 */
3145 public static ContainerRuntime detectContainerRuntime () {
32- // Docker version 19.03.14, build 5eb3275d40
33- String dockerVersionOutput = getVersionOutputFor (ContainerRuntime .DOCKER );
34- boolean dockerAvailable = dockerVersionOutput .contains ("Docker version" );
35- // Check if Podman is installed
36- // podman version 2.1.1
37- String podmanVersionOutput = getVersionOutputFor (ContainerRuntime .PODMAN );
38- boolean podmanAvailable = podmanVersionOutput .startsWith ("podman version" );
39- if (DOCKER_EXECUTABLE != null ) {
40- if (DOCKER_EXECUTABLE .trim ().equalsIgnoreCase ("docker" ) && dockerAvailable ) {
46+ return detectContainerRuntime (true );
47+ }
48+
49+ public static ContainerRuntime detectContainerRuntime (boolean required ) {
50+ final ContainerRuntime containerRuntime = loadConfig ();
51+ if (containerRuntime != null ) {
52+ return containerRuntime ;
53+ } else {
54+ // Docker version 19.03.14, build 5eb3275d40
55+ final String dockerVersionOutput = getVersionOutputFor (ContainerRuntime .DOCKER );
56+ boolean dockerAvailable = dockerVersionOutput .contains ("Docker version" );
57+ // Check if Podman is installed
58+ // podman version 2.1.1
59+ final String podmanVersionOutput = getVersionOutputFor (ContainerRuntime .PODMAN );
60+ boolean podmanAvailable = podmanVersionOutput .startsWith ("podman version" );
61+ if (DOCKER_EXECUTABLE != null ) {
62+ if (DOCKER_EXECUTABLE .trim ().equalsIgnoreCase ("docker" ) && dockerAvailable ) {
63+ storeConfig (ContainerRuntime .DOCKER );
64+ return ContainerRuntime .DOCKER ;
65+ } else if (DOCKER_EXECUTABLE .trim ().equalsIgnoreCase ("podman" ) && podmanAvailable ) {
66+ storeConfig (ContainerRuntime .PODMAN );
67+ return ContainerRuntime .PODMAN ;
68+ } else {
69+ log .warn ("quarkus.native.container-runtime config property must be set to either podman or docker " +
70+ "and the executable must be available. Ignoring it." );
71+ }
72+ }
73+ if (dockerAvailable ) {
74+ // Check if "docker" is an alias to "podman"
75+ if (dockerVersionOutput .equals (podmanVersionOutput )) {
76+ storeConfig (ContainerRuntime .PODMAN );
77+ return ContainerRuntime .PODMAN ;
78+ }
79+ storeConfig (ContainerRuntime .DOCKER );
4180 return ContainerRuntime .DOCKER ;
42- } else if (DOCKER_EXECUTABLE .trim ().equalsIgnoreCase ("podman" ) && podmanAvailable ) {
81+ } else if (podmanAvailable ) {
82+ storeConfig (ContainerRuntime .PODMAN );
4383 return ContainerRuntime .PODMAN ;
4484 } else {
45- log .warn ("quarkus.native.container-runtime config property must be set to either podman or docker " +
46- "and the executable must be available. Ignoring it." );
85+ if (required ) {
86+ throw new IllegalStateException ("No container runtime was found. "
87+ + "Make sure you have either Docker or Podman installed in your environment." );
88+ } else {
89+ storeConfig (ContainerRuntime .UNAVAILABLE );
90+ return ContainerRuntime .UNAVAILABLE ;
91+ }
4792 }
4893 }
94+ }
4995
50- if (dockerAvailable ) {
51- // Check if "docker" is an alias to "podman"
52- if (dockerVersionOutput .equals (podmanVersionOutput )) {
53- return ContainerRuntime .PODMAN ;
96+ private static ContainerRuntime loadConfig () {
97+ try {
98+ if (Files .isReadable (CONTAINER_RUNTIME )) {
99+ final String runtime = Files .readString (CONTAINER_RUNTIME , StandardCharsets .UTF_8 );
100+ if (ContainerRuntime .DOCKER .name ().equalsIgnoreCase (runtime )) {
101+ return ContainerRuntime .DOCKER ;
102+ } else if (ContainerRuntime .PODMAN .name ().equalsIgnoreCase (runtime )) {
103+ return ContainerRuntime .PODMAN ;
104+ } else if (ContainerRuntime .UNAVAILABLE .name ().equalsIgnoreCase (runtime )) {
105+ return ContainerRuntime .UNAVAILABLE ;
106+ } else {
107+ log .warnf ("The file %s contains an unknown value %s. Ignoring it." ,
108+ CONTAINER_RUNTIME .toAbsolutePath (), runtime );
109+ return null ;
110+ }
111+ } else {
112+ return null ;
54113 }
55- return ContainerRuntime .DOCKER ;
56- } else if (podmanAvailable ) {
57- return ContainerRuntime .PODMAN ;
58- } else {
59- throw new IllegalStateException ("No container runtime was found to. "
60- + "Make sure you have Docker or Podman installed in your environment." );
114+ } catch (IOException e ) {
115+ log .warnf ("Error reading file %s. Ignoring it. See: %s" ,
116+ CONTAINER_RUNTIME .toAbsolutePath (), e );
117+ return null ;
118+ }
119+ }
120+
121+ private static void storeConfig (ContainerRuntime containerRuntime ) {
122+ try {
123+ Files .writeString (CONTAINER_RUNTIME , containerRuntime .name (), StandardCharsets .UTF_8 ,
124+ StandardOpenOption .CREATE , StandardOpenOption .TRUNCATE_EXISTING );
125+ CONTAINER_RUNTIME .toFile ().deleteOnExit ();
126+ } catch (IOException e ) {
127+ log .warnf ("Error writing to file %s. Ignoring it. See: %s" ,
128+ CONTAINER_RUNTIME .toAbsolutePath (), e );
61129 }
62130 }
63131
64132 private static String getVersionOutputFor (ContainerRuntime containerRuntime ) {
65133 Process versionProcess = null ;
66134 try {
67- ProcessBuilder pb = new ProcessBuilder (containerRuntime .getExecutableName (), "--version" )
135+ final ProcessBuilder pb = new ProcessBuilder (containerRuntime .getExecutableName (), "--version" )
68136 .redirectErrorStream (true );
69137 versionProcess = pb .start ();
70138 versionProcess .waitFor ();
@@ -100,7 +168,7 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) {
100168 bufferedReader .lines ().collect (Collectors .joining (System .lineSeparator ())));
101169 return false ;
102170 } else {
103- Predicate <String > stringPredicate ;
171+ final Predicate <String > stringPredicate ;
104172 // Docker includes just "rootless" under SecurityOptions, while podman includes "rootless: <boolean>"
105173 if (containerRuntime == ContainerRuntime .DOCKER ) {
106174 stringPredicate = line -> line .trim ().equals ("rootless" );
@@ -126,19 +194,26 @@ private static boolean getRootlessStateFor(ContainerRuntime containerRuntime) {
126194 */
127195 public enum ContainerRuntime {
128196 DOCKER ,
129- PODMAN ;
197+ PODMAN ,
198+ UNAVAILABLE ;
130199
131- private final boolean rootless ;
132-
133- ContainerRuntime () {
134- this .rootless = getRootlessStateFor (this );
135- }
200+ private Boolean rootless ;
136201
137202 public String getExecutableName () {
138203 return this .name ().toLowerCase ();
139204 }
140205
141206 public boolean isRootless () {
207+ if (rootless != null ) {
208+ return rootless ;
209+ } else {
210+ if (this != ContainerRuntime .UNAVAILABLE ) {
211+ rootless = getRootlessStateFor (this );
212+ } else {
213+ throw new IllegalStateException ("No container runtime was found. "
214+ + "Make sure you have either Docker or Podman installed in your environment." );
215+ }
216+ }
142217 return rootless ;
143218 }
144219 }
0 commit comments