package org.testcontainers.utility;
import com.github.dockerjava.api.command.
InspectContainerResponse;
import java.time.
Duration;
import java.time.
Instant;
import java.time.format.
DateTimeFormatter;
/**
* Utility functions for dealing with docker status based on the information available to us, and trying to be
* defensive.
* <p>
* <p>In docker-java version 2.2.0, which we're using, only these
* fields are available in the container state returned from Docker Inspect: "isRunning", "isPaused", "startedAt", and
* "finishedAt". There are states that can occur (including "created", "OOMkilled" and "dead") that aren't directly
* shown through this result.
* <p>
* <p>Docker also doesn't seem to use null values for timestamps; see DOCKER_TIMESTAMP_ZERO, below.
*/
public class
DockerStatus {
/**
* When the docker client has an "empty" timestamp, it returns this special value, rather than
* null or an empty string.
*/
static final
String DOCKER_TIMESTAMP_ZERO = "0001-01-01T00:00:00Z";
/**
* Based on this status, is this container running, and has it been doing so for the specified amount of time?
*
* @param state the state provided by InspectContainer
* @param minimumRunningDuration minimum duration to consider this as "solidly" running, or null
* @param now the time to consider as the current time
* @return true if we can conclude that the container is running, false otherwise
*/
public static boolean
isContainerRunning(
InspectContainerResponse.
ContainerState state,
Duration minimumRunningDuration,
Instant now) {
if (
state.
getRunning()) {
if (
minimumRunningDuration == null) {
return true;
}
Instant startedAt =
DateTimeFormatter.
ISO_INSTANT.
parse(
state.
getStartedAt(),
Instant::from);
if (
startedAt.
isBefore(
now.
minus(
minimumRunningDuration))) {
return true;
}
}
return false;
}
/**
* Based on this status, has the container halted?
*
* @param state the state provided by InspectContainer
* @return true if we can conclude that the container has started but is now stopped, false otherwise.
*/
public static boolean
isContainerStopped(
InspectContainerResponse.
ContainerState state) {
// get some preconditions out of the way
if (
state.
getRunning() ||
state.
getPaused()) {
return false;
}
// if the finished timestamp is non-empty, that means the container started and finished.
boolean
hasStarted =
isDockerTimestampNonEmpty(
state.
getStartedAt());
boolean
hasFinished =
isDockerTimestampNonEmpty(
state.
getFinishedAt());
return
hasStarted &&
hasFinished;
}
public static boolean
isDockerTimestampNonEmpty(
String dockerTimestamp) {
// This is a defensive approach. Current versions of Docker use the DOCKER_TIMESTAMP_ZERO value, but
// that could change.
return
dockerTimestamp != null
&& !
dockerTimestamp.
isEmpty()
&& !
dockerTimestamp.
equals(
DOCKER_TIMESTAMP_ZERO)
&&
DateTimeFormatter.
ISO_INSTANT.
parse(
dockerTimestamp,
Instant::from).
getEpochSecond() >= 0L;
}
public static boolean
isContainerExitCodeSuccess(
InspectContainerResponse.
ContainerState state) {
int
exitCode =
state.
getExitCode();
// 0 is the only exit code we can consider as success
return
exitCode == 0;
}
}