Add nullability annotations to core/spring-boot-docker-compose
See gh-46587
This commit is contained in:
parent
b6e4533296
commit
cb2a26ceec
|
@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides access to the ports that can be used to connect to a {@link RunningService}.
|
* Provides access to the ports that can be used to connect to a {@link RunningService}.
|
||||||
*
|
*
|
||||||
|
@ -52,6 +54,6 @@ public interface ConnectionPorts {
|
||||||
* all host ports
|
* all host ports
|
||||||
* @return a list of all host ports using the given protocol
|
* @return a list of all host ports using the given protocol
|
||||||
*/
|
*/
|
||||||
List<Integer> getAll(String protocol);
|
List<Integer> getAll(@Nullable String protocol);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.Config;
|
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.Config;
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostConfig;
|
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostConfig;
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostPort;
|
import org.springframework.boot.docker.compose.core.DockerCliInspectResponse.HostPort;
|
||||||
|
@ -57,7 +59,7 @@ class DefaultConnectionPorts implements ConnectionPorts {
|
||||||
return (config != null) && "host".equals(config.networkMode());
|
return (config != null) && "host".equals(config.networkMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<ContainerPort, Integer> buildMappingsForNetworkSettings(NetworkSettings networkSettings) {
|
private Map<ContainerPort, Integer> buildMappingsForNetworkSettings(@Nullable NetworkSettings networkSettings) {
|
||||||
if (networkSettings == null || CollectionUtils.isEmpty(networkSettings.ports())) {
|
if (networkSettings == null || CollectionUtils.isEmpty(networkSettings.ports())) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +75,7 @@ class DefaultConnectionPorts implements ConnectionPorts {
|
||||||
return Collections.unmodifiableMap(mappings);
|
return Collections.unmodifiableMap(mappings);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isIpV4(HostPort hostPort) {
|
private boolean isIpV4(@Nullable HostPort hostPort) {
|
||||||
String ip = (hostPort != null) ? hostPort.hostIp() : null;
|
String ip = (hostPort != null) ? hostPort.hostIp() : null;
|
||||||
return !StringUtils.hasLength(ip) || ip.contains(".");
|
return !StringUtils.hasLength(ip) || ip.contains(".");
|
||||||
}
|
}
|
||||||
|
@ -108,7 +110,7 @@ class DefaultConnectionPorts implements ConnectionPorts {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Integer> getAll(String protocol) {
|
public List<Integer> getAll(@Nullable String protocol) {
|
||||||
List<Integer> hostPorts = new ArrayList<>();
|
List<Integer> hostPorts = new ArrayList<>();
|
||||||
this.mappings.forEach((containerPort, hostPort) -> {
|
this.mappings.forEach((containerPort, hostPort) -> {
|
||||||
if (protocol == null || protocol.equalsIgnoreCase(containerPort.protocol())) {
|
if (protocol == null || protocol.equalsIgnoreCase(containerPort.protocol())) {
|
||||||
|
|
|
@ -25,6 +25,8 @@ import java.util.Map.Entry;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.logging.LogLevel;
|
import org.springframework.boot.logging.LogLevel;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
@ -41,7 +43,7 @@ class DefaultDockerCompose implements DockerCompose {
|
||||||
|
|
||||||
private final DockerHost hostname;
|
private final DockerHost hostname;
|
||||||
|
|
||||||
DefaultDockerCompose(DockerCli cli, String host) {
|
DefaultDockerCompose(DockerCli cli, @Nullable String host) {
|
||||||
this.cli = cli;
|
this.cli = cli;
|
||||||
this.hostname = DockerHost.get(host, () -> cli.run(new DockerCliCommand.Context()));
|
this.hostname = DockerHost.get(host, () -> cli.run(new DockerCliCommand.Context()));
|
||||||
}
|
}
|
||||||
|
@ -114,7 +116,8 @@ class DefaultDockerCompose implements DockerCompose {
|
||||||
return inspectResponses.stream().collect(Collectors.toMap(DockerCliInspectResponse::id, Function.identity()));
|
return inspectResponses.stream().collect(Collectors.toMap(DockerCliInspectResponse::id, Function.identity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DockerCliInspectResponse inspectContainer(String id, Map<String, DockerCliInspectResponse> inspected) {
|
private @Nullable DockerCliInspectResponse inspectContainer(String id,
|
||||||
|
Map<String, DockerCliInspectResponse> inspected) {
|
||||||
DockerCliInspectResponse inspect = inspected.get(id);
|
DockerCliInspectResponse inspect = inspected.get(id);
|
||||||
if (inspect != null) {
|
if (inspect != null) {
|
||||||
return inspect;
|
return inspect;
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.springframework.boot.docker.compose.core;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.origin.Origin;
|
import org.springframework.boot.origin.Origin;
|
||||||
import org.springframework.boot.origin.OriginProvider;
|
import org.springframework.boot.origin.OriginProvider;
|
||||||
|
|
||||||
|
@ -45,10 +47,10 @@ class DefaultRunningService implements RunningService, OriginProvider {
|
||||||
|
|
||||||
private final DockerEnv env;
|
private final DockerEnv env;
|
||||||
|
|
||||||
private final DockerComposeFile composeFile;
|
private final @Nullable DockerComposeFile composeFile;
|
||||||
|
|
||||||
DefaultRunningService(DockerHost host, DockerComposeFile composeFile, DockerCliComposePsResponse composePsResponse,
|
DefaultRunningService(DockerHost host, @Nullable DockerComposeFile composeFile,
|
||||||
DockerCliInspectResponse inspectResponse) {
|
DockerCliComposePsResponse composePsResponse, DockerCliInspectResponse inspectResponse) {
|
||||||
this.origin = new DockerComposeOrigin(composeFile, composePsResponse.name());
|
this.origin = new DockerComposeOrigin(composeFile, composePsResponse.name());
|
||||||
this.name = composePsResponse.name();
|
this.name = composePsResponse.name();
|
||||||
this.image = ImageReference
|
this.image = ImageReference
|
||||||
|
@ -101,7 +103,7 @@ class DefaultRunningService implements RunningService, OriginProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DockerComposeFile composeFile() {
|
public @Nullable DockerComposeFile composeFile() {
|
||||||
return this.composeFile;
|
return this.composeFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
|
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.Type;
|
import org.springframework.boot.docker.compose.core.DockerCliCommand.Type;
|
||||||
|
@ -60,7 +61,7 @@ class DockerCli {
|
||||||
* @param workingDirectory the working directory or {@code null}
|
* @param workingDirectory the working directory or {@code null}
|
||||||
* @param dockerComposeOptions the Docker Compose options to use or {@code null}.
|
* @param dockerComposeOptions the Docker Compose options to use or {@code null}.
|
||||||
*/
|
*/
|
||||||
DockerCli(File workingDirectory, DockerComposeOptions dockerComposeOptions) {
|
DockerCli(@Nullable File workingDirectory, @Nullable DockerComposeOptions dockerComposeOptions) {
|
||||||
this.processRunner = new ProcessRunner(workingDirectory);
|
this.processRunner = new ProcessRunner(workingDirectory);
|
||||||
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
|
this.dockerCommands = dockerCommandsCache.computeIfAbsent(workingDirectory,
|
||||||
(key) -> new DockerCommands(this.processRunner));
|
(key) -> new DockerCommands(this.processRunner));
|
||||||
|
@ -82,7 +83,7 @@ class DockerCli {
|
||||||
return dockerCommand.deserialize(json);
|
return dockerCommand.deserialize(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumer<String> createOutputConsumer(LogLevel logLevel) {
|
private @Nullable Consumer<String> createOutputConsumer(@Nullable LogLevel logLevel) {
|
||||||
if (logLevel == null || logLevel == LogLevel.OFF) {
|
if (logLevel == null || logLevel == LogLevel.OFF) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -123,7 +124,7 @@ class DockerCli {
|
||||||
* Return the {@link DockerComposeFile} being used by this CLI instance.
|
* Return the {@link DockerComposeFile} being used by this CLI instance.
|
||||||
* @return the Docker Compose file
|
* @return the Docker Compose file
|
||||||
*/
|
*/
|
||||||
DockerComposeFile getDockerComposeFile() {
|
@Nullable DockerComposeFile getDockerComposeFile() {
|
||||||
return this.dockerComposeOptions.composeFile();
|
return this.dockerComposeOptions.composeFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,11 +206,14 @@ class DockerCli {
|
||||||
* @param activeProfiles the profiles to activate
|
* @param activeProfiles the profiles to activate
|
||||||
* @param arguments the arguments to pass to Docker Compose
|
* @param arguments the arguments to pass to Docker Compose
|
||||||
*/
|
*/
|
||||||
record DockerComposeOptions(DockerComposeFile composeFile, Set<String> activeProfiles, List<String> arguments) {
|
record DockerComposeOptions(@Nullable DockerComposeFile composeFile, Set<String> activeProfiles,
|
||||||
|
List<String> arguments) {
|
||||||
|
|
||||||
DockerComposeOptions {
|
DockerComposeOptions(@Nullable DockerComposeFile composeFile, @Nullable Set<String> activeProfiles,
|
||||||
activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
@Nullable List<String> arguments) {
|
||||||
arguments = (arguments != null) ? arguments : Collections.emptyList();
|
this.composeFile = composeFile;
|
||||||
|
this.activeProfiles = (activeProfiles != null) ? activeProfiles : Collections.emptySet();
|
||||||
|
this.arguments = (arguments != null) ? arguments : Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
static DockerComposeOptions none() {
|
static DockerComposeOptions none() {
|
||||||
|
|
|
@ -78,8 +78,8 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
R deserialize(String json) {
|
R deserialize(String json) {
|
||||||
if (this.responseType == Void.class) {
|
if (this.responseType == None.class) {
|
||||||
return null;
|
return (R) None.INSTANCE;
|
||||||
}
|
}
|
||||||
return (R) ((!this.listResponse) ? DockerJson.deserialize(json, this.responseType)
|
return (R) ((!this.listResponse) ? DockerJson.deserialize(json, this.responseType)
|
||||||
: DockerJson.deserializeToList(json, this.responseType));
|
: DockerJson.deserializeToList(json, this.responseType));
|
||||||
|
@ -175,10 +175,10 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
/**
|
/**
|
||||||
* The {@code docker compose up} command.
|
* The {@code docker compose up} command.
|
||||||
*/
|
*/
|
||||||
static final class ComposeUp extends DockerCliCommand<Void> {
|
static final class ComposeUp extends DockerCliCommand<None> {
|
||||||
|
|
||||||
ComposeUp(LogLevel logLevel, List<String> arguments) {
|
ComposeUp(LogLevel logLevel, List<String> arguments) {
|
||||||
super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments));
|
super(Type.DOCKER_COMPOSE, logLevel, None.class, false, getCommand(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getCommand(List<String> arguments) {
|
private static String[] getCommand(List<String> arguments) {
|
||||||
|
@ -196,10 +196,10 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
/**
|
/**
|
||||||
* The {@code docker compose down} command.
|
* The {@code docker compose down} command.
|
||||||
*/
|
*/
|
||||||
static final class ComposeDown extends DockerCliCommand<Void> {
|
static final class ComposeDown extends DockerCliCommand<None> {
|
||||||
|
|
||||||
ComposeDown(Duration timeout, List<String> arguments) {
|
ComposeDown(Duration timeout, List<String> arguments) {
|
||||||
super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments));
|
super(Type.DOCKER_COMPOSE, None.class, false, getCommand(timeout, arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getCommand(Duration timeout, List<String> arguments) {
|
private static String[] getCommand(Duration timeout, List<String> arguments) {
|
||||||
|
@ -216,10 +216,10 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
/**
|
/**
|
||||||
* The {@code docker compose start} command.
|
* The {@code docker compose start} command.
|
||||||
*/
|
*/
|
||||||
static final class ComposeStart extends DockerCliCommand<Void> {
|
static final class ComposeStart extends DockerCliCommand<None> {
|
||||||
|
|
||||||
ComposeStart(LogLevel logLevel, List<String> arguments) {
|
ComposeStart(LogLevel logLevel, List<String> arguments) {
|
||||||
super(Type.DOCKER_COMPOSE, logLevel, Void.class, false, getCommand(arguments));
|
super(Type.DOCKER_COMPOSE, logLevel, None.class, false, getCommand(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getCommand(List<String> arguments) {
|
private static String[] getCommand(List<String> arguments) {
|
||||||
|
@ -234,10 +234,10 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
/**
|
/**
|
||||||
* The {@code docker compose stop} command.
|
* The {@code docker compose stop} command.
|
||||||
*/
|
*/
|
||||||
static final class ComposeStop extends DockerCliCommand<Void> {
|
static final class ComposeStop extends DockerCliCommand<None> {
|
||||||
|
|
||||||
ComposeStop(Duration timeout, List<String> arguments) {
|
ComposeStop(Duration timeout, List<String> arguments) {
|
||||||
super(Type.DOCKER_COMPOSE, Void.class, false, getCommand(timeout, arguments));
|
super(Type.DOCKER_COMPOSE, None.class, false, getCommand(timeout, arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] getCommand(Duration timeout, List<String> arguments) {
|
private static String[] getCommand(Duration timeout, List<String> arguments) {
|
||||||
|
@ -268,6 +268,15 @@ abstract sealed class DockerCliCommand<R> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static final class None {
|
||||||
|
|
||||||
|
public static final None INSTANCE = new None();
|
||||||
|
|
||||||
|
private None() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Docker compose version.
|
* Docker compose version.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.docker.compose.core;
|
package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response from {@link DockerCliCommand.ComposePs docker compose ps}.
|
* Response from {@link DockerCliCommand.ComposePs docker compose ps}.
|
||||||
*
|
*
|
||||||
|
@ -27,6 +29,6 @@ package org.springframework.boot.docker.compose.core;
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
record DockerCliComposePsResponse(String id, String name, String image, String state) {
|
record DockerCliComposePsResponse(String id, String name, @Nullable String image, String state) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.springframework.boot.docker.compose.core;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response from {@link DockerCliCommand.Inspect docker inspect}.
|
* Response from {@link DockerCliCommand.Inspect docker inspect}.
|
||||||
*
|
*
|
||||||
|
@ -31,7 +33,8 @@ import java.util.Map;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
*/
|
*/
|
||||||
record DockerCliInspectResponse(String id, DockerCliInspectResponse.Config config,
|
record DockerCliInspectResponse(String id, DockerCliInspectResponse.Config config,
|
||||||
DockerCliInspectResponse.NetworkSettings networkSettings, DockerCliInspectResponse.HostConfig hostConfig) {
|
DockerCliInspectResponse.NetworkSettings networkSettings,
|
||||||
|
DockerCliInspectResponse.@Nullable HostConfig hostConfig) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for the container that is portable between hosts.
|
* Configuration for the container that is portable between hosts.
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions;
|
import org.springframework.boot.docker.compose.core.DockerCli.DockerComposeOptions;
|
||||||
import org.springframework.boot.logging.LogLevel;
|
import org.springframework.boot.logging.LogLevel;
|
||||||
|
|
||||||
|
@ -126,7 +128,7 @@ public interface DockerCompose {
|
||||||
* @param activeProfiles a set of the profiles that should be activated
|
* @param activeProfiles a set of the profiles that should be activated
|
||||||
* @return a {@link DockerCompose} instance
|
* @return a {@link DockerCompose} instance
|
||||||
*/
|
*/
|
||||||
static DockerCompose get(DockerComposeFile file, String hostname, Set<String> activeProfiles) {
|
static DockerCompose get(DockerComposeFile file, @Nullable String hostname, Set<String> activeProfiles) {
|
||||||
return get(file, hostname, activeProfiles, Collections.emptyList());
|
return get(file, hostname, activeProfiles, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ public interface DockerCompose {
|
||||||
* @return a {@link DockerCompose} instance
|
* @return a {@link DockerCompose} instance
|
||||||
* @since 3.4.0
|
* @since 3.4.0
|
||||||
*/
|
*/
|
||||||
static DockerCompose get(DockerComposeFile file, String hostname, Set<String> activeProfiles,
|
static DockerCompose get(DockerComposeFile file, @Nullable String hostname, Set<String> activeProfiles,
|
||||||
List<String> arguments) {
|
List<String> arguments) {
|
||||||
DockerCli cli = new DockerCli(null, new DockerComposeOptions(file, activeProfiles, arguments));
|
DockerCli cli = new DockerCli(null, new DockerComposeOptions(file, activeProfiles, arguments));
|
||||||
return new DefaultDockerCompose(cli, hostname);
|
return new DefaultDockerCompose(cli, hostname);
|
||||||
|
|
|
@ -26,6 +26,8 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +109,7 @@ public final class DockerComposeFile {
|
||||||
* current directory
|
* current directory
|
||||||
* @return the located file or {@code null} if no Docker Compose file can be found
|
* @return the located file or {@code null} if no Docker Compose file can be found
|
||||||
*/
|
*/
|
||||||
public static DockerComposeFile find(File workingDirectory) {
|
public static @Nullable DockerComposeFile find(@Nullable File workingDirectory) {
|
||||||
File base = (workingDirectory != null) ? workingDirectory : new File(".");
|
File base = (workingDirectory != null) ? workingDirectory : new File(".");
|
||||||
if (!base.exists()) {
|
if (!base.exists()) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.docker.compose.core;
|
package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.origin.Origin;
|
import org.springframework.boot.origin.Origin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +29,7 @@ import org.springframework.boot.origin.Origin;
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @since 3.1.0
|
* @since 3.1.0
|
||||||
*/
|
*/
|
||||||
public record DockerComposeOrigin(DockerComposeFile composeFile, String serviceName) implements Origin {
|
public record DockerComposeOrigin(@Nullable DockerComposeFile composeFile, String serviceName) implements Origin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +71,7 @@ class DockerEnv {
|
||||||
return this.map;
|
return this.map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Entry(String key, String value) {
|
private record Entry(String key, @Nullable String value) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +71,7 @@ final class DockerHost {
|
||||||
* {@link DockerCliContextResponse}
|
* {@link DockerCliContextResponse}
|
||||||
* @return a new docker host instance
|
* @return a new docker host instance
|
||||||
*/
|
*/
|
||||||
static DockerHost get(String host, Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
static DockerHost get(@Nullable String host, Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
||||||
return get(host, System::getenv, contextsSupplier);
|
return get(host, System::getenv, contextsSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +83,7 @@ final class DockerHost {
|
||||||
* {@link DockerCliContextResponse}
|
* {@link DockerCliContextResponse}
|
||||||
* @return a new docker host instance
|
* @return a new docker host instance
|
||||||
*/
|
*/
|
||||||
static DockerHost get(String host, Function<String, String> systemEnv,
|
static DockerHost get(@Nullable String host, Function<String, String> systemEnv,
|
||||||
Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
||||||
host = (StringUtils.hasText(host)) ? host : fromServicesHostEnv(systemEnv);
|
host = (StringUtils.hasText(host)) ? host : fromServicesHostEnv(systemEnv);
|
||||||
host = (StringUtils.hasText(host)) ? host : fromDockerHostEnv(systemEnv);
|
host = (StringUtils.hasText(host)) ? host : fromDockerHostEnv(systemEnv);
|
||||||
|
@ -94,24 +96,24 @@ final class DockerHost {
|
||||||
return systemEnv.apply("SERVICES_HOST");
|
return systemEnv.apply("SERVICES_HOST");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromDockerHostEnv(Function<String, String> systemEnv) {
|
private static @Nullable String fromDockerHostEnv(Function<String, String> systemEnv) {
|
||||||
return fromEndpoint(systemEnv.apply("DOCKER_HOST"));
|
return fromEndpoint(systemEnv.apply("DOCKER_HOST"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromCurrentContext(Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
private static @Nullable String fromCurrentContext(Supplier<List<DockerCliContextResponse>> contextsSupplier) {
|
||||||
DockerCliContextResponse current = getCurrentContext(contextsSupplier.get());
|
DockerCliContextResponse current = getCurrentContext(contextsSupplier.get());
|
||||||
return (current != null) ? fromEndpoint(current.dockerEndpoint()) : null;
|
return (current != null) ? fromEndpoint(current.dockerEndpoint()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DockerCliContextResponse getCurrentContext(List<DockerCliContextResponse> candidates) {
|
private static @Nullable DockerCliContextResponse getCurrentContext(List<DockerCliContextResponse> candidates) {
|
||||||
return candidates.stream().filter(DockerCliContextResponse::current).findFirst().orElse(null);
|
return candidates.stream().filter(DockerCliContextResponse::current).findFirst().orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromEndpoint(String endpoint) {
|
private static @Nullable String fromEndpoint(@Nullable String endpoint) {
|
||||||
return (StringUtils.hasLength(endpoint)) ? fromUri(URI.create(endpoint)) : null;
|
return (StringUtils.hasLength(endpoint)) ? fromUri(URI.create(endpoint)) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromUri(URI uri) {
|
private static @Nullable String fromUri(URI uri) {
|
||||||
try {
|
try {
|
||||||
return switch (uri.getScheme()) {
|
return switch (uri.getScheme()) {
|
||||||
case "http", "https", "tcp" -> uri.getHost();
|
case "http", "https", "tcp" -> uri.getHost();
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.docker.compose.core;
|
package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +40,7 @@ class ImageName {
|
||||||
|
|
||||||
private final String string;
|
private final String string;
|
||||||
|
|
||||||
ImageName(String domain, String path) {
|
ImageName(@Nullable String domain, String path) {
|
||||||
Assert.hasText(path, "'path' must not be empty");
|
Assert.hasText(path, "'path' must not be empty");
|
||||||
this.domain = getDomainOrDefault(domain);
|
this.domain = getDomainOrDefault(domain);
|
||||||
this.name = getNameWithDefaultPath(this.domain, path);
|
this.name = getNameWithDefaultPath(this.domain, path);
|
||||||
|
@ -90,7 +92,7 @@ class ImageName {
|
||||||
return this.string;
|
return this.string;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDomainOrDefault(String domain) {
|
private String getDomainOrDefault(@Nullable String domain) {
|
||||||
if (domain == null || LEGACY_DOMAIN.equals(domain)) {
|
if (domain == null || LEGACY_DOMAIN.equals(domain)) {
|
||||||
return DEFAULT_DOMAIN;
|
return DEFAULT_DOMAIN;
|
||||||
}
|
}
|
||||||
|
@ -104,7 +106,7 @@ class ImageName {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String parseDomain(String value) {
|
static @Nullable String parseDomain(String value) {
|
||||||
int firstSlash = value.indexOf('/');
|
int firstSlash = value.indexOf('/');
|
||||||
String candidate = (firstSlash != -1) ? value.substring(0, firstSlash) : null;
|
String candidate = (firstSlash != -1) ? value.substring(0, firstSlash) : null;
|
||||||
if (candidate != null && Regex.DOMAIN.matcher(candidate).matches()) {
|
if (candidate != null && Regex.DOMAIN.matcher(candidate).matches()) {
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
@ -32,13 +34,13 @@ public final class ImageReference {
|
||||||
|
|
||||||
private final ImageName name;
|
private final ImageName name;
|
||||||
|
|
||||||
private final String tag;
|
private final @Nullable String tag;
|
||||||
|
|
||||||
private final String digest;
|
private final @Nullable String digest;
|
||||||
|
|
||||||
private final String string;
|
private final String string;
|
||||||
|
|
||||||
private ImageReference(ImageName name, String tag, String digest) {
|
private ImageReference(ImageName name, @Nullable String tag, @Nullable String digest) {
|
||||||
Assert.notNull(name, "'name' must not be null");
|
Assert.notNull(name, "'name' must not be null");
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
|
@ -68,7 +70,7 @@ public final class ImageReference {
|
||||||
* Return the tag from the reference or {@code null}.
|
* Return the tag from the reference or {@code null}.
|
||||||
* @return the referenced tag
|
* @return the referenced tag
|
||||||
*/
|
*/
|
||||||
public String getTag() {
|
public @Nullable String getTag() {
|
||||||
return this.tag;
|
return this.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ public final class ImageReference {
|
||||||
* Return the digest from the reference or {@code null}.
|
* Return the digest from the reference or {@code null}.
|
||||||
* @return the referenced digest
|
* @return the referenced digest
|
||||||
*/
|
*/
|
||||||
public String getDigest() {
|
public @Nullable String getDigest() {
|
||||||
return this.digest;
|
return this.digest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +113,7 @@ public final class ImageReference {
|
||||||
return this.string;
|
return this.string;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildString(String name, String tag, String digest) {
|
private String buildString(String name, @Nullable String tag, @Nullable String digest) {
|
||||||
StringBuilder string = new StringBuilder(name);
|
StringBuilder string = new StringBuilder(name);
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
string.append(":").append(tag);
|
string.append(":").append(tag);
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.docker.compose.core;
|
package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown by {@link ProcessRunner} when the process exits with a non-zero code.
|
* Exception thrown by {@link ProcessRunner} when the process exits with a non-zero code.
|
||||||
*
|
*
|
||||||
|
@ -37,7 +39,7 @@ class ProcessExitException extends RuntimeException {
|
||||||
this(exitCode, command, stdOut, stdErr, null);
|
this(exitCode, command, stdOut, stdErr, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessExitException(int exitCode, String[] command, String stdOut, String stdErr, Throwable cause) {
|
ProcessExitException(int exitCode, String[] command, String stdOut, String stdErr, @Nullable Throwable cause) {
|
||||||
super(buildMessage(exitCode, command, stdOut, stdErr), cause);
|
super(buildMessage(exitCode, command, stdOut, stdErr), cause);
|
||||||
this.exitCode = exitCode;
|
this.exitCode = exitCode;
|
||||||
this.command = command;
|
this.command = command;
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.core.log.LogMessage;
|
import org.springframework.core.log.LogMessage;
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ class ProcessRunner {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(ProcessRunner.class);
|
private static final Log logger = LogFactory.getLog(ProcessRunner.class);
|
||||||
|
|
||||||
private final File workingDirectory;
|
private final @Nullable File workingDirectory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link ProcessRunner} instance.
|
* Create a new {@link ProcessRunner} instance.
|
||||||
|
@ -60,7 +61,7 @@ class ProcessRunner {
|
||||||
* Create a new {@link ProcessRunner} instance.
|
* Create a new {@link ProcessRunner} instance.
|
||||||
* @param workingDirectory the working directory for the process
|
* @param workingDirectory the working directory for the process
|
||||||
*/
|
*/
|
||||||
ProcessRunner(File workingDirectory) {
|
ProcessRunner(@Nullable File workingDirectory) {
|
||||||
this.workingDirectory = workingDirectory;
|
this.workingDirectory = workingDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ class ProcessRunner {
|
||||||
* @return the output of the command
|
* @return the output of the command
|
||||||
* @throws ProcessExitException if execution failed
|
* @throws ProcessExitException if execution failed
|
||||||
*/
|
*/
|
||||||
String run(Consumer<String> outputConsumer, String... command) {
|
String run(@Nullable Consumer<String> outputConsumer, String... command) {
|
||||||
logger.trace(LogMessage.of(() -> "Running '%s'".formatted(String.join(" ", command))));
|
logger.trace(LogMessage.of(() -> "Running '%s'".formatted(String.join(" ", command))));
|
||||||
Process process = startProcess(command);
|
Process process = startProcess(command);
|
||||||
ReaderThread stdOutReader = new ReaderThread(process.getInputStream(), "stdout", outputConsumer);
|
ReaderThread stdOutReader = new ReaderThread(process.getInputStream(), "stdout", outputConsumer);
|
||||||
|
@ -133,13 +134,13 @@ class ProcessRunner {
|
||||||
|
|
||||||
private final InputStream source;
|
private final InputStream source;
|
||||||
|
|
||||||
private final Consumer<String> outputConsumer;
|
private final @Nullable Consumer<String> outputConsumer;
|
||||||
|
|
||||||
private final StringBuilder output = new StringBuilder();
|
private final StringBuilder output = new StringBuilder();
|
||||||
|
|
||||||
private final CountDownLatch latch = new CountDownLatch(1);
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
ReaderThread(InputStream source, String name, Consumer<String> outputConsumer) {
|
ReaderThread(InputStream source, String name, @Nullable Consumer<String> outputConsumer) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.outputConsumer = outputConsumer;
|
this.outputConsumer = outputConsumer;
|
||||||
setName("OutputReader-" + name);
|
setName("OutputReader-" + name);
|
||||||
|
@ -174,7 +175,7 @@ class ProcessRunner {
|
||||||
return this.output.toString();
|
return this.output.toString();
|
||||||
}
|
}
|
||||||
catch (InterruptedException ex) {
|
catch (InterruptedException ex) {
|
||||||
return null;
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides details of a running Docker Compose service.
|
* Provides details of a running Docker Compose service.
|
||||||
*
|
*
|
||||||
|
@ -69,7 +71,7 @@ public interface RunningService {
|
||||||
* @return the Docker Compose file
|
* @return the Docker Compose file
|
||||||
* @since 3.5.0
|
* @since 3.5.0
|
||||||
*/
|
*/
|
||||||
default DockerComposeFile composeFile() {
|
default @Nullable DockerComposeFile composeFile() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
/**
|
/**
|
||||||
* Core interfaces and classes for working with Docker Compose.
|
* Core interfaces and classes for working with Docker Compose.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.boot.docker.compose.core;
|
package org.springframework.boot.docker.compose.core;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.Set;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.aot.AotDetector;
|
import org.springframework.aot.AotDetector;
|
||||||
import org.springframework.boot.SpringApplicationShutdownHandlers;
|
import org.springframework.boot.SpringApplicationShutdownHandlers;
|
||||||
|
@ -57,11 +58,11 @@ class DockerComposeLifecycleManager {
|
||||||
|
|
||||||
private static final String IGNORE_LABEL = "org.springframework.boot.ignore";
|
private static final String IGNORE_LABEL = "org.springframework.boot.ignore";
|
||||||
|
|
||||||
private final File workingDirectory;
|
private final @Nullable File workingDirectory;
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
private final ClassLoader classLoader;
|
private final @Nullable ClassLoader classLoader;
|
||||||
|
|
||||||
private final SpringApplicationShutdownHandlers shutdownHandlers;
|
private final SpringApplicationShutdownHandlers shutdownHandlers;
|
||||||
|
|
||||||
|
@ -80,10 +81,10 @@ class DockerComposeLifecycleManager {
|
||||||
new DockerComposeSkipCheck(), null);
|
new DockerComposeSkipCheck(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
DockerComposeLifecycleManager(File workingDirectory, ApplicationContext applicationContext, Binder binder,
|
DockerComposeLifecycleManager(@Nullable File workingDirectory, ApplicationContext applicationContext, Binder binder,
|
||||||
SpringApplicationShutdownHandlers shutdownHandlers, DockerComposeProperties properties,
|
SpringApplicationShutdownHandlers shutdownHandlers, DockerComposeProperties properties,
|
||||||
Set<ApplicationListener<?>> eventListeners, DockerComposeSkipCheck skipCheck,
|
Set<ApplicationListener<?>> eventListeners, DockerComposeSkipCheck skipCheck,
|
||||||
ServiceReadinessChecks serviceReadinessChecks) {
|
@Nullable ServiceReadinessChecks serviceReadinessChecks) {
|
||||||
this.workingDirectory = workingDirectory;
|
this.workingDirectory = workingDirectory;
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
this.classLoader = applicationContext.getClassLoader();
|
this.classLoader = applicationContext.getClassLoader();
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.bind.Binder;
|
import org.springframework.boot.context.properties.bind.Binder;
|
||||||
import org.springframework.boot.docker.compose.core.RunningService;
|
import org.springframework.boot.docker.compose.core.RunningService;
|
||||||
|
@ -64,7 +66,7 @@ public class DockerComposeProperties {
|
||||||
/**
|
/**
|
||||||
* Hostname or IP of the machine where the docker containers are started.
|
* Hostname or IP of the machine where the docker containers are started.
|
||||||
*/
|
*/
|
||||||
private String host;
|
private @Nullable String host;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start configuration.
|
* Start configuration.
|
||||||
|
@ -109,11 +111,11 @@ public class DockerComposeProperties {
|
||||||
this.lifecycleManagement = lifecycleManagement;
|
this.lifecycleManagement = lifecycleManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHost() {
|
public @Nullable String getHost() {
|
||||||
return this.host;
|
return this.host;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHost(String host) {
|
public void setHost(@Nullable String host) {
|
||||||
this.host = host;
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import java.util.Collections;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplicationAotProcessor;
|
import org.springframework.boot.SpringApplicationAotProcessor;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
@ -44,7 +46,7 @@ class DockerComposeSkipCheck {
|
||||||
SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped);
|
SKIPPED_STACK_ELEMENTS = Collections.unmodifiableSet(skipped);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean shouldSkip(ClassLoader classLoader, DockerComposeProperties.Skip properties) {
|
boolean shouldSkip(@Nullable ClassLoader classLoader, DockerComposeProperties.Skip properties) {
|
||||||
if (properties.isInTests() && hasAtLeastOneRequiredClass(classLoader)) {
|
if (properties.isInTests() && hasAtLeastOneRequiredClass(classLoader)) {
|
||||||
Thread thread = Thread.currentThread();
|
Thread thread = Thread.currentThread();
|
||||||
for (StackTraceElement element : thread.getStackTrace()) {
|
for (StackTraceElement element : thread.getStackTrace()) {
|
||||||
|
@ -56,7 +58,7 @@ class DockerComposeSkipCheck {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAtLeastOneRequiredClass(ClassLoader classLoader) {
|
private boolean hasAtLeastOneRequiredClass(@Nullable ClassLoader classLoader) {
|
||||||
for (String requiredClass : REQUIRED_CLASSES) {
|
for (String requiredClass : REQUIRED_CLASSES) {
|
||||||
if (ClassUtils.isPresent(requiredClass, classLoader)) {
|
if (ClassUtils.isPresent(requiredClass, classLoader)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.docker.compose.lifecycle;
|
package org.springframework.boot.docker.compose.lifecycle;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.docker.compose.core.RunningService;
|
import org.springframework.boot.docker.compose.core.RunningService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +35,7 @@ class ServiceNotReadyException extends RuntimeException {
|
||||||
this(service, message, null);
|
this(service, message, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceNotReadyException(RunningService service, String message, Throwable cause) {
|
ServiceNotReadyException(RunningService service, String message, @Nullable Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
/**
|
/**
|
||||||
* Lifecycle management for Docker Compose with the context of a Spring application.
|
* Lifecycle management for Docker Compose with the context of a Spring application.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.boot.docker.compose.lifecycle;
|
package org.springframework.boot.docker.compose.lifecycle;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.Arrays;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails;
|
||||||
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory;
|
import org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory;
|
||||||
import org.springframework.boot.docker.compose.core.DockerComposeFile;
|
import org.springframework.boot.docker.compose.core.DockerComposeFile;
|
||||||
|
@ -92,7 +94,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final D getConnectionDetails(DockerComposeConnectionSource source) {
|
public final @Nullable D getConnectionDetails(DockerComposeConnectionSource source) {
|
||||||
return (!accept(source)) ? null : getDockerComposeConnectionDetails(source);
|
return (!accept(source)) ? null : getDockerComposeConnectionDetails(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
* @param source the source
|
* @param source the source
|
||||||
* @return the service connection or {@code null}.
|
* @return the service connection or {@code null}.
|
||||||
*/
|
*/
|
||||||
protected abstract D getDockerComposeConnectionDetails(DockerComposeConnectionSource source);
|
protected abstract @Nullable D getDockerComposeConnectionDetails(DockerComposeConnectionSource source);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenient base class for {@link ConnectionDetails} results that are backed by a
|
* Convenient base class for {@link ConnectionDetails} results that are backed by a
|
||||||
|
@ -120,9 +122,9 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
*/
|
*/
|
||||||
protected static class DockerComposeConnectionDetails implements ConnectionDetails, OriginProvider {
|
protected static class DockerComposeConnectionDetails implements ConnectionDetails, OriginProvider {
|
||||||
|
|
||||||
private final Origin origin;
|
private final @Nullable Origin origin;
|
||||||
|
|
||||||
private volatile SslBundle sslBundle;
|
private volatile @Nullable SslBundle sslBundle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link DockerComposeConnectionDetails} instance.
|
* Create a new {@link DockerComposeConnectionDetails} instance.
|
||||||
|
@ -134,11 +136,11 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Origin getOrigin() {
|
public @Nullable Origin getOrigin() {
|
||||||
return this.origin;
|
return this.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SslBundle getSslBundle(RunningService service) {
|
protected @Nullable SslBundle getSslBundle(RunningService service) {
|
||||||
if (this.sslBundle != null) {
|
if (this.sslBundle != null) {
|
||||||
return this.sslBundle;
|
return this.sslBundle;
|
||||||
}
|
}
|
||||||
|
@ -155,7 +157,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
return sslBundle;
|
return sslBundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslBundle getJksSslBundle(RunningService service) {
|
private @Nullable SslBundle getJksSslBundle(RunningService service) {
|
||||||
JksSslStoreDetails keyStoreDetails = getJksSslStoreDetails(service, "keystore");
|
JksSslStoreDetails keyStoreDetails = getJksSslStoreDetails(service, "keystore");
|
||||||
JksSslStoreDetails trustStoreDetails = getJksSslStoreDetails(service, "truststore");
|
JksSslStoreDetails trustStoreDetails = getJksSslStoreDetails(service, "truststore");
|
||||||
if (keyStoreDetails == null && trustStoreDetails == null) {
|
if (keyStoreDetails == null && trustStoreDetails == null) {
|
||||||
|
@ -173,13 +175,13 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
options, protocol);
|
options, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResourceLoader getResourceLoader(Path workingDirectory) {
|
private ResourceLoader getResourceLoader(@Nullable Path workingDirectory) {
|
||||||
ClassLoader classLoader = ApplicationResourceLoader.get().getClassLoader();
|
ClassLoader classLoader = ApplicationResourceLoader.get().getClassLoader();
|
||||||
return ApplicationResourceLoader.get(classLoader,
|
return ApplicationResourceLoader.get(classLoader,
|
||||||
SpringFactoriesLoader.forDefaultResourceLocation(classLoader), workingDirectory);
|
SpringFactoriesLoader.forDefaultResourceLocation(classLoader), workingDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JksSslStoreDetails getJksSslStoreDetails(RunningService service, String storeType) {
|
private @Nullable JksSslStoreDetails getJksSslStoreDetails(RunningService service, String storeType) {
|
||||||
String type = service.labels().get("org.springframework.boot.sslbundle.jks.%s.type".formatted(storeType));
|
String type = service.labels().get("org.springframework.boot.sslbundle.jks.%s.type".formatted(storeType));
|
||||||
String provider = service.labels()
|
String provider = service.labels()
|
||||||
.get("org.springframework.boot.sslbundle.jks.%s.provider".formatted(storeType));
|
.get("org.springframework.boot.sslbundle.jks.%s.provider".formatted(storeType));
|
||||||
|
@ -193,7 +195,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
return new JksSslStoreDetails(type, provider, location, password);
|
return new JksSslStoreDetails(type, provider, location, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path getWorkingDirectory(RunningService runningService) {
|
private @Nullable Path getWorkingDirectory(RunningService runningService) {
|
||||||
DockerComposeFile composeFile = runningService.composeFile();
|
DockerComposeFile composeFile = runningService.composeFile();
|
||||||
if (composeFile == null || CollectionUtils.isEmpty(composeFile.getFiles())) {
|
if (composeFile == null || CollectionUtils.isEmpty(composeFile.getFiles())) {
|
||||||
return Path.of(".");
|
return Path.of(".");
|
||||||
|
@ -201,7 +203,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
return composeFile.getFiles().get(0).toPath().getParent();
|
return composeFile.getFiles().get(0).toPath().getParent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslOptions createSslOptions(String ciphers, String enabledProtocols) {
|
private SslOptions createSslOptions(@Nullable String ciphers, @Nullable String enabledProtocols) {
|
||||||
Set<String> ciphersSet = null;
|
Set<String> ciphersSet = null;
|
||||||
if (StringUtils.hasLength(ciphers)) {
|
if (StringUtils.hasLength(ciphers)) {
|
||||||
ciphersSet = StringUtils.commaDelimitedListToSet(ciphers);
|
ciphersSet = StringUtils.commaDelimitedListToSet(ciphers);
|
||||||
|
@ -213,7 +215,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
return SslOptions.of(ciphersSet, enabledProtocolsSet);
|
return SslOptions.of(ciphersSet, enabledProtocolsSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SslBundle getPemSslBundle(RunningService service) {
|
private @Nullable SslBundle getPemSslBundle(RunningService service) {
|
||||||
PemSslStoreDetails keyStoreDetails = getPemSslStoreDetails(service, "keystore");
|
PemSslStoreDetails keyStoreDetails = getPemSslStoreDetails(service, "keystore");
|
||||||
PemSslStoreDetails trustStoreDetails = getPemSslStoreDetails(service, "truststore");
|
PemSslStoreDetails trustStoreDetails = getPemSslStoreDetails(service, "truststore");
|
||||||
if (keyStoreDetails == null && trustStoreDetails == null) {
|
if (keyStoreDetails == null && trustStoreDetails == null) {
|
||||||
|
@ -231,7 +233,7 @@ public abstract class DockerComposeConnectionDetailsFactory<D extends Connection
|
||||||
PemSslStore.load(trustStoreDetails, resourceLoader)), key, options, protocol);
|
PemSslStore.load(trustStoreDetails, resourceLoader)), key, options, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PemSslStoreDetails getPemSslStoreDetails(RunningService service, String storeType) {
|
private @Nullable PemSslStoreDetails getPemSslStoreDetails(RunningService service, String storeType) {
|
||||||
String type = service.labels().get("org.springframework.boot.sslbundle.pem.%s.type".formatted(storeType));
|
String type = service.labels().get("org.springframework.boot.sslbundle.pem.%s.type".formatted(storeType));
|
||||||
String certificate = service.labels()
|
String certificate = service.labels()
|
||||||
.get("org.springframework.boot.sslbundle.pem.%s.certificate".formatted(storeType));
|
.get("org.springframework.boot.sslbundle.pem.%s.certificate".formatted(storeType));
|
||||||
|
|
|
@ -17,4 +17,7 @@
|
||||||
/**
|
/**
|
||||||
* Service connection support for Docker Compose.
|
* Service connection support for Docker Compose.
|
||||||
*/
|
*/
|
||||||
|
@NullMarked
|
||||||
package org.springframework.boot.docker.compose.service.connection;
|
package org.springframework.boot.docker.compose.service.connection;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
|
import org.springframework.boot.docker.compose.core.DockerCliCommand.ComposeVersion;
|
||||||
|
import org.springframework.boot.docker.compose.core.DockerCliCommand.None;
|
||||||
import org.springframework.boot.logging.LogLevel;
|
import org.springframework.boot.logging.LogLevel;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -85,7 +86,7 @@ class DockerCliCommandTests {
|
||||||
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
|
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
|
||||||
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("up", "--no-color", "--detach", "--wait",
|
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("up", "--no-color", "--detach", "--wait",
|
||||||
"--renew-anon-volumes");
|
"--renew-anon-volumes");
|
||||||
assertThat(command.deserialize("[]")).isNull();
|
assertThat(command.deserialize("[]")).isSameAs(None.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,7 +95,7 @@ class DockerCliCommandTests {
|
||||||
List.of("--remove-orphans"));
|
List.of("--remove-orphans"));
|
||||||
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
||||||
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("down", "--timeout", "1", "--remove-orphans");
|
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("down", "--timeout", "1", "--remove-orphans");
|
||||||
assertThat(command.deserialize("[]")).isNull();
|
assertThat(command.deserialize("[]")).isSameAs(None.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -103,7 +104,7 @@ class DockerCliCommandTests {
|
||||||
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
||||||
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
|
assertThat(command.getLogLevel()).isEqualTo(LogLevel.INFO);
|
||||||
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("start", "--dry-run");
|
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("start", "--dry-run");
|
||||||
assertThat(command.deserialize("[]")).isNull();
|
assertThat(command.deserialize("[]")).isSameAs(None.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -111,7 +112,7 @@ class DockerCliCommandTests {
|
||||||
DockerCliCommand<?> command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1), List.of("--dry-run"));
|
DockerCliCommand<?> command = new DockerCliCommand.ComposeStop(Duration.ofSeconds(1), List.of("--dry-run"));
|
||||||
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
assertThat(command.getType()).isEqualTo(DockerCliCommand.Type.DOCKER_COMPOSE);
|
||||||
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("stop", "--timeout", "1", "--dry-run");
|
assertThat(command.getCommand(COMPOSE_VERSION)).containsExactly("stop", "--timeout", "1", "--dry-run");
|
||||||
assertThat(command.deserialize("[]")).isNull();
|
assertThat(command.deserialize("[]")).isSameAs(None.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue