Home / Class/ DockerCli Class — spring-boot Architecture

DockerCli Class — spring-boot Architecture

Architecture documentation for the DockerCli class in DockerCli.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

core/spring-boot-docker-compose/src/main/java/org/springframework/boot/docker/compose/core/DockerCli.java lines 45–225

class DockerCli {

	private static final Map<@Nullable File, DockerCommands> dockerCommandsCache = new HashMap<>();

	private static final Log logger = LogFactory.getLog(DockerCli.class);

	private final ProcessRunner processRunner;

	private final DockerCommands dockerCommands;

	private final DockerComposeOptions dockerComposeOptions;

	private final ComposeVersion composeVersion;

	/**
	 * Create a new {@link DockerCli} instance.
	 * @param workingDirectory the working directory or {@code null}
	 * @param dockerComposeOptions the Docker Compose options to use or {@code null}.
	 */
	DockerCli(@Nullable File workingDirectory, @Nullable DockerComposeOptions dockerComposeOptions) {
		this.processRunner = new ProcessRunner(workingDirectory);
		this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
				(key) -> new DockerCommands(this.processRunner));
		this.dockerComposeOptions = (dockerComposeOptions != null) ? dockerComposeOptions : DockerComposeOptions.none();
		this.composeVersion = ComposeVersion.of(this.dockerCommands.get(Type.DOCKER_COMPOSE).version());
	}

	/**
	 * Run the given {@link DockerCli} command and return the response.
	 * @param <R> the response type
	 * @param dockerCommand the command to run
	 * @return the response
	 */
	<R> R run(DockerCliCommand<R> dockerCommand) {
		List<String> command = createCommand(dockerCommand.getType());
		command.addAll(dockerCommand.getCommand(this.composeVersion));
		Consumer<String> outputConsumer = createOutputConsumer(dockerCommand.getLogLevel());
		String json = this.processRunner.run(outputConsumer, command.toArray(new String[0]));
		return dockerCommand.deserialize(json);
	}

	private @Nullable Consumer<String> createOutputConsumer(@Nullable LogLevel logLevel) {
		if (logLevel == null || logLevel == LogLevel.OFF) {
			return null;
		}
		return (line) -> logLevel.log(logger, line);
	}

	private List<String> createCommand(Type type) {
		return switch (type) {
			case DOCKER -> new ArrayList<>(this.dockerCommands.get(type).command());
			case DOCKER_COMPOSE -> {
				List<String> result = new ArrayList<>(this.dockerCommands.get(type).command());
				DockerComposeFile composeFile = this.dockerComposeOptions.composeFile();
				if (composeFile != null) {
					for (File file : composeFile.getFiles()) {
						result.add("--file");
						result.add(file.getPath());
					}
				}
				result.add("--ansi");
				result.add("never");
				Set<String> activeProfiles = this.dockerComposeOptions.activeProfiles();
				if (!CollectionUtils.isEmpty(activeProfiles)) {
					for (String profile : activeProfiles) {
						result.add("--profile");
						result.add(profile);
					}
				}
				List<String> arguments = this.dockerComposeOptions.arguments();
				if (!CollectionUtils.isEmpty(arguments)) {
					result.addAll(arguments);
				}
				yield result;
			}
		};
	}

	/**
	 * Return the {@link DockerComposeFile} being used by this CLI instance.
	 * @return the Docker Compose file
	 */
	@Nullable DockerComposeFile getDockerComposeFile() {
		return this.dockerComposeOptions.composeFile();
	}

	/**
	 * Holds details of the actual CLI commands to invoke.
	 */
	private static class DockerCommands {

		private final DockerCommand dockerCommand;

		private final DockerCommand dockerComposeCommand;

		DockerCommands(ProcessRunner processRunner) {
			this.dockerCommand = getDockerCommand(processRunner);
			this.dockerComposeCommand = getDockerComposeCommand(processRunner);
		}

		private DockerCommand getDockerCommand(ProcessRunner processRunner) {
			try {
				String version = processRunner.run("docker", "version", "--format", "{{.Client.Version}}");
				logger.trace(LogMessage.format("Using docker %s", version));
				return new DockerCommand(version, List.of("docker"));
			}
			catch (ProcessStartException ex) {
				throw new DockerProcessStartException("Unable to start docker process. Is docker correctly installed?",
						ex);
			}
			catch (ProcessExitException ex) {
				if (ex.getStdErr().contains("docker daemon is not running")
						|| ex.getStdErr().contains("Cannot connect to the Docker daemon")) {
					throw new DockerNotRunningException(ex.getStdErr(), ex);
				}
				throw ex;
			}
		}

		private DockerCommand getDockerComposeCommand(ProcessRunner processRunner) {
			try {
				DockerCliComposeVersionResponse response = DockerJson.deserialize(
						processRunner.run("docker", "compose", "version", "--format", "json"),
						DockerCliComposeVersionResponse.class);
				logger.trace(LogMessage.format("Using Docker Compose %s", response.version()));
				return new DockerCommand(response.version(), List.of("docker", "compose"));
			}
			catch (ProcessExitException ex) {
				// Ignore and try docker-compose
			}
			try {
				DockerCliComposeVersionResponse response = DockerJson.deserialize(
						processRunner.run("docker-compose", "version", "--format", "json"),
						DockerCliComposeVersionResponse.class);
				logger.trace(LogMessage.format("Using docker-compose %s", response.version()));
				return new DockerCommand(response.version(), List.of("docker-compose"));
			}
			catch (ProcessStartException ex) {
				throw new DockerProcessStartException(
						"Unable to start 'docker-compose' process or use 'docker compose'. Is docker correctly installed?",
						ex);
			}
		}

		DockerCommand get(Type type) {
			return switch (type) {
				case DOCKER -> this.dockerCommand;
				case DOCKER_COMPOSE -> this.dockerComposeCommand;
			};
		}

	}

	private record DockerCommand(String version, List<String> command) {

	}

	/**
	 * Options for Docker Compose.
	 *
	 * @param composeFile the Docker Compose file to use
	 * @param activeProfiles the profiles to activate
	 * @param arguments the arguments to pass to Docker Compose
	 */
	record DockerComposeOptions(@Nullable DockerComposeFile composeFile, Set<String> activeProfiles,
			List<String> arguments) {

		DockerComposeOptions(@Nullable DockerComposeFile composeFile, @Nullable Set<String> activeProfiles,
				@Nullable List<String> arguments) {
			this.composeFile = composeFile;
			this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
			this.arguments = (arguments != null) ? arguments : Collections.emptyList();
		}

		static DockerComposeOptions none() {
			return new DockerComposeOptions(null, null, null);
		}

	}

}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free