Add nullability annotations to buildpack/spring-boot-buildpack-platform

See gh-46587
This commit is contained in:
Moritz Halbritter
2025-08-08 09:20:13 +02:00
parent 0c572ed53d
commit 2449769573
80 changed files with 595 additions and 313 deletions
@@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.util.List;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
@@ -45,7 +47,7 @@ public abstract class AbstractBuildLog implements BuildLog {
}
@Override
public Consumer<TotalProgressEvent> pullingImage(ImageReference imageReference, ImagePlatform platform,
public Consumer<TotalProgressEvent> pullingImage(ImageReference imageReference, @Nullable ImagePlatform platform,
ImageType imageType) {
return (platform != null)
? getProgressConsumer(" > Pulling %s '%s' for platform '%s'".formatted(imageType.getDescription(),
@@ -109,7 +111,7 @@ public abstract class AbstractBuildLog implements BuildLog {
}
@Override
public void failedCleaningWorkDir(Cache cache, Exception exception) {
public void failedCleaningWorkDir(Cache cache, @Nullable Exception exception) {
StringBuilder message = new StringBuilder("Warning: Working location " + cache + " could not be cleaned");
if (exception != null) {
message.append(": ").append(exception.getMessage());
@@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.util.Arrays;
import java.util.stream.IntStream;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.ApiVersion;
import org.springframework.util.StringUtils;
@@ -62,7 +64,7 @@ final class ApiVersions {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.PrintStream;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
@@ -52,7 +54,7 @@ public interface BuildLog {
* @param imageType the image type
* @return a consumer for progress update events
*/
Consumer<TotalProgressEvent> pullingImage(ImageReference imageReference, ImagePlatform platform,
Consumer<TotalProgressEvent> pullingImage(ImageReference imageReference, @Nullable ImagePlatform platform,
ImageType imageType);
/**
@@ -124,7 +126,7 @@ public interface BuildLog {
* @param exception any exception that caused the failure
* @since 3.2.6
*/
void failedCleaningWorkDir(Cache cache, Exception exception);
void failedCleaningWorkDir(Cache cache, @Nullable Exception exception);
/**
* Log that a binding with a sensitive target has been detected.
@@ -26,6 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ImagePlatform;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
@@ -69,9 +71,9 @@ public class BuildRequest {
private final ImageReference builder;
private final Boolean trustBuilder;
private final @Nullable Boolean trustBuilder;
private final ImageReference runImage;
private final @Nullable ImageReference runImage;
private final Creator creator;
@@ -89,23 +91,23 @@ public class BuildRequest {
private final List<Binding> bindings;
private final String network;
private final @Nullable String network;
private final List<ImageReference> tags;
private final Cache buildWorkspace;
private final @Nullable Cache buildWorkspace;
private final Cache buildCache;
private final @Nullable Cache buildCache;
private final Cache launchCache;
private final @Nullable Cache launchCache;
private final Instant createdDate;
private final @Nullable Instant createdDate;
private final String applicationDirectory;
private final @Nullable String applicationDirectory;
private final List<String> securityOptions;
private final @Nullable List<String> securityOptions;
private final ImagePlatform platform;
private final @Nullable ImagePlatform platform;
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent) {
Assert.notNull(name, "'name' must not be null");
@@ -135,11 +137,12 @@ public class BuildRequest {
}
BuildRequest(ImageReference name, Function<Owner, TarArchive> applicationContent, ImageReference builder,
Boolean trustBuilder, ImageReference runImage, Creator creator, Map<String, String> env, boolean cleanCache,
boolean verboseLogging, PullPolicy pullPolicy, boolean publish, List<BuildpackReference> buildpacks,
List<Binding> bindings, String network, List<ImageReference> tags, Cache buildWorkspace, Cache buildCache,
Cache launchCache, Instant createdDate, String applicationDirectory, List<String> securityOptions,
ImagePlatform platform) {
@Nullable Boolean trustBuilder, @Nullable ImageReference runImage, Creator creator, Map<String, String> env,
boolean cleanCache, boolean verboseLogging, PullPolicy pullPolicy, boolean publish,
List<BuildpackReference> buildpacks, List<Binding> bindings, @Nullable String network,
List<ImageReference> tags, @Nullable Cache buildWorkspace, @Nullable Cache buildCache,
@Nullable Cache launchCache, @Nullable Instant createdDate, @Nullable String applicationDirectory,
@Nullable List<String> securityOptions, @Nullable ImagePlatform platform) {
this.name = name;
this.applicationContent = applicationContent;
this.builder = builder;
@@ -538,7 +541,7 @@ public class BuildRequest {
* Return the run image that should be used, if provided.
* @return the run image
*/
public ImageReference getRunImage() {
public @Nullable ImageReference getRunImage() {
return this.runImage;
}
@@ -612,7 +615,7 @@ public class BuildRequest {
* @return the network
* @since 2.6.0
*/
public String getNetwork() {
public @Nullable String getNetwork() {
return this.network;
}
@@ -629,7 +632,7 @@ public class BuildRequest {
* @return the build workspace or {@code null}
* @since 3.2.0
*/
public Cache getBuildWorkspace() {
public @Nullable Cache getBuildWorkspace() {
return this.buildWorkspace;
}
@@ -637,7 +640,7 @@ public class BuildRequest {
* Return the custom build cache that should be used by the lifecycle.
* @return the build cache
*/
public Cache getBuildCache() {
public @Nullable Cache getBuildCache() {
return this.buildCache;
}
@@ -645,7 +648,7 @@ public class BuildRequest {
* Return the custom launch cache that should be used by the lifecycle.
* @return the launch cache
*/
public Cache getLaunchCache() {
public @Nullable Cache getLaunchCache() {
return this.launchCache;
}
@@ -653,7 +656,7 @@ public class BuildRequest {
* Return the custom created date that should be used by the lifecycle.
* @return the created date
*/
public Instant getCreatedDate() {
public @Nullable Instant getCreatedDate() {
return this.createdDate;
}
@@ -661,7 +664,7 @@ public class BuildRequest {
* Return the application directory that should be used by the lifecycle.
* @return the application directory
*/
public String getApplicationDirectory() {
public @Nullable String getApplicationDirectory() {
return this.applicationDirectory;
}
@@ -670,7 +673,7 @@ public class BuildRequest {
* @return the security options or {@code null}
* @since 3.2.0
*/
public List<String> getSecurityOptions() {
public @Nullable List<String> getSecurityOptions() {
return this.securityOptions;
}
@@ -679,7 +682,7 @@ public class BuildRequest {
* @return the platform or {@code null}
* @since 3.4.0
*/
public ImagePlatform getImagePlatform() {
public @Nullable ImagePlatform getImagePlatform() {
return this.platform;
}
@@ -20,6 +20,8 @@ import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.DockerLog;
import org.springframework.boot.buildpack.platform.docker.TotalProgressEvent;
@@ -86,12 +88,12 @@ public class Builder {
* @param dockerConfiguration the docker configuration
* @since 3.5.0
*/
public Builder(BuildLog log, BuilderDockerConfiguration dockerConfiguration) {
public Builder(BuildLog log, @Nullable BuilderDockerConfiguration dockerConfiguration) {
this(log, new DockerApi((dockerConfiguration != null) ? dockerConfiguration.connection() : null,
BuildLogAdapter.get(log)), dockerConfiguration);
}
Builder(BuildLog log, DockerApi docker, BuilderDockerConfiguration dockerConfiguration) {
Builder(BuildLog log, DockerApi docker, @Nullable BuilderDockerConfiguration dockerConfiguration) {
Assert.notNull(log, "'log' must not be null");
this.log = log;
this.docker = docker;
@@ -109,6 +111,7 @@ public class Builder {
Image builderImage = imageFetcher.fetchImage(ImageType.BUILDER, request.getBuilder());
BuilderMetadata builderMetadata = BuilderMetadata.fromImage(builderImage);
request = withRunImageIfNeeded(request, builderMetadata);
Assert.state(request.getRunImage() != null, "'request.getRunImage()' must not be null");
Image runImage = imageFetcher.fetchImage(ImageType.RUNNER, request.getRunImage());
assertStackIdsMatch(runImage, builderImage);
BuildOwner buildOwner = BuildOwner.fromEnv(builderImage.getConfig().getEnv());
@@ -181,7 +184,7 @@ public class Builder {
}
}
private ResolvedDockerHost getDockerHost() {
private @Nullable ResolvedDockerHost getDockerHost() {
boolean bindToBuilder = this.dockerConfiguration.bindHostToBuilder();
return (bindToBuilder) ? ResolvedDockerHost.from(this.dockerConfiguration.connection()) : null;
}
@@ -208,7 +211,8 @@ public class Builder {
this.log.pushedImage(reference);
}
private static String authHeader(DockerRegistryAuthentication authentication, ImageReference reference) {
private static @Nullable String authHeader(@Nullable DockerRegistryAuthentication authentication,
ImageReference reference) {
return (authentication != null) ? authentication.getAuthHeader(reference) : null;
}
@@ -217,14 +221,14 @@ public class Builder {
*/
private class ImageFetcher {
private final DockerRegistryAuthentication registryAuthentication;
private final @Nullable DockerRegistryAuthentication registryAuthentication;
private final PullPolicy pullPolicy;
private ImagePlatform defaultPlatform;
private @Nullable ImagePlatform defaultPlatform;
ImageFetcher(DockerRegistryAuthentication registryAuthentication, PullPolicy pullPolicy,
ImagePlatform platform) {
ImageFetcher(@Nullable DockerRegistryAuthentication registryAuthentication, PullPolicy pullPolicy,
@Nullable ImagePlatform platform) {
this.registryAuthentication = registryAuthentication;
this.pullPolicy = pullPolicy;
this.defaultPlatform = platform;
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.IOException;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.util.Assert;
@@ -59,10 +61,10 @@ class BuilderBuildpack implements Buildpack {
* @param reference the buildpack reference
* @return the resolved {@link Buildpack} or {@code null}
*/
static Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
boolean unambiguous = reference.hasPrefix(PREFIX);
BuilderReference builderReference = BuilderReference
.of(unambiguous ? reference.getSubReference(PREFIX) : reference.toString());
.of(unambiguous ? getSubReference(reference) : reference.toString());
BuildpackMetadata buildpackMetadata = findBuildpackMetadata(context, builderReference);
if (unambiguous) {
Assert.state(buildpackMetadata != null, () -> "Buildpack '" + reference + "' not found in builder");
@@ -70,7 +72,13 @@ class BuilderBuildpack implements Buildpack {
return (buildpackMetadata != null) ? new BuilderBuildpack(buildpackMetadata) : null;
}
private static BuildpackMetadata findBuildpackMetadata(BuildpackResolverContext context,
private static String getSubReference(BuildpackReference reference) {
String result = reference.getSubReference(PREFIX);
Assert.state(result != null, "'result' must not be null");
return result;
}
private static @Nullable BuildpackMetadata findBuildpackMetadata(BuildpackResolverContext context,
BuilderReference builderReference) {
for (BuildpackMetadata candidate : context.getBuildpackMetadata()) {
if (builderReference.matches(candidate)) {
@@ -87,9 +95,9 @@ class BuilderBuildpack implements Buildpack {
private final String id;
private final String version;
private final @Nullable String version;
BuilderReference(String id, String version) {
BuilderReference(String id, @Nullable String version) {
this.id = id;
this.version = version;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.build;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
@@ -32,9 +34,9 @@ import org.springframework.boot.buildpack.platform.docker.configuration.DockerRe
* @author Scott Frederick
* @since 3.5.0
*/
public record BuilderDockerConfiguration(DockerConnectionConfiguration connection, boolean bindHostToBuilder,
DockerRegistryAuthentication builderRegistryAuthentication,
DockerRegistryAuthentication publishRegistryAuthentication) {
public record BuilderDockerConfiguration(@Nullable DockerConnectionConfiguration connection, boolean bindHostToBuilder,
@Nullable DockerRegistryAuthentication builderRegistryAuthentication,
@Nullable DockerRegistryAuthentication publishRegistryAuthentication) {
public BuilderDockerConfiguration() {
this(null, false, null, null);
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
@@ -59,13 +60,31 @@ class BuilderMetadata extends MappedObject {
BuilderMetadata(JsonNode node) {
super(node, MethodHandles.lookup());
this.stack = valueAt("/stack", Stack.class);
this.stack = extractStack();
this.runImages = childrenAt("/images", RunImage::new);
this.lifecycle = valueAt("/lifecycle", Lifecycle.class);
this.createdBy = valueAt("/createdBy", CreatedBy.class);
this.lifecycle = extractLifecycle();
this.createdBy = extractCreatedBy();
this.buildpacks = extractBuildpacks(getNode().at("/buildpacks"));
}
private CreatedBy extractCreatedBy() {
CreatedBy result = valueAt("/createdBy", CreatedBy.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
private Lifecycle extractLifecycle() {
Lifecycle result = valueAt("/lifecycle", Lifecycle.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
private Stack extractStack() {
Stack result = valueAt("/stack", Stack.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
private List<BuildpackMetadata> extractBuildpacks(JsonNode node) {
if (node.isEmpty()) {
return Collections.emptyList();
@@ -219,10 +238,16 @@ class BuilderMetadata extends MappedObject {
*/
RunImage(JsonNode node) {
super(node, MethodHandles.lookup());
this.image = valueAt("/image", String.class);
this.image = extractImage();
this.mirrors = childrenAt("/mirrors", JsonNode::asText);
}
private String extractImage() {
String result = valueAt("/image", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
String getImage() {
return this.image;
}
@@ -284,7 +309,7 @@ class BuilderMetadata extends MappedObject {
* Return the supported buildpack API versions.
* @return the buildpack versions
*/
default String[] getBuildpack() {
default String @Nullable [] getBuildpack() {
return valueAt(this, "/buildpack/supported", String[].class);
}
@@ -292,7 +317,7 @@ class BuilderMetadata extends MappedObject {
* Return the supported platform API versions.
* @return the platform versions
*/
default String[] getPlatform() {
default String @Nullable [] getPlatform() {
return valueAt(this, "/platform/supported", String[].class);
}
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import org.jspecify.annotations.Nullable;
import org.tomlj.Toml;
import org.tomlj.TomlParseResult;
@@ -39,9 +40,9 @@ final class BuildpackCoordinates {
private final String id;
private final String version;
private final @Nullable String version;
private BuildpackCoordinates(String id, String version) {
private BuildpackCoordinates(String id, @Nullable String version) {
Assert.hasText(id, "'id' must not be empty");
this.id = id;
this.version = version;
@@ -59,12 +60,12 @@ final class BuildpackCoordinates {
return this.id.replace("/", "_");
}
String getVersion() {
@Nullable String getVersion() {
return this.version;
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -22,6 +22,7 @@ import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
@@ -53,7 +54,7 @@ final class BuildpackLayersMetadata extends MappedObject {
* @return the buildpack details or {@code null} if a buildpack with the given ID and
* version does not exist in the metadata
*/
BuildpackLayerDetails getBuildpack(String id, String version) {
@Nullable BuildpackLayerDetails getBuildpack(String id, @Nullable String version) {
return this.buildpacks.getBuildpack(id, version);
}
@@ -105,7 +106,7 @@ final class BuildpackLayersMetadata extends MappedObject {
private final Map<String, BuildpackVersions> buildpacks = new HashMap<>();
private BuildpackLayerDetails getBuildpack(String id, String version) {
private @Nullable BuildpackLayerDetails getBuildpack(String id, @Nullable String version) {
if (this.buildpacks.containsKey(id)) {
return this.buildpacks.get(id).getBuildpack(version);
}
@@ -130,7 +131,7 @@ final class BuildpackLayersMetadata extends MappedObject {
private final Map<String, BuildpackLayerDetails> versions = new HashMap<>();
private BuildpackLayerDetails getBuildpack(String version) {
private @Nullable BuildpackLayerDetails getBuildpack(@Nullable String version) {
return this.versions.get(version);
}
@@ -150,11 +151,11 @@ final class BuildpackLayersMetadata extends MappedObject {
static final class BuildpackLayerDetails extends MappedObject {
private final String name;
private final @Nullable String name;
private final String homepage;
private final @Nullable String homepage;
private final String layerDiffId;
private final @Nullable String layerDiffId;
private BuildpackLayerDetails(JsonNode node) {
super(node, MethodHandles.lookup());
@@ -167,7 +168,7 @@ final class BuildpackLayersMetadata extends MappedObject {
* Return the buildpack name.
* @return the name
*/
String getName() {
@Nullable String getName() {
return this.name;
}
@@ -175,7 +176,7 @@ final class BuildpackLayersMetadata extends MappedObject {
* Return the buildpack homepage address.
* @return the homepage address
*/
String getHomepage() {
@Nullable String getHomepage() {
return this.homepage;
}
@@ -183,7 +184,7 @@ final class BuildpackLayersMetadata extends MappedObject {
* Return the buildpack layer {@code diffID}.
* @return the layer {@code diffID}
*/
String getLayerDiffId() {
@Nullable String getLayerDiffId() {
return this.layerDiffId;
}
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
@@ -39,17 +40,23 @@ final class BuildpackMetadata extends MappedObject {
private final String id;
private final String version;
private final @Nullable String version;
private final String homepage;
private final @Nullable String homepage;
private BuildpackMetadata(JsonNode node) {
super(node, MethodHandles.lookup());
this.id = valueAt("/id", String.class);
this.id = extractId();
this.version = valueAt("/version", String.class);
this.homepage = valueAt("/homepage", String.class);
}
private String extractId() {
String result = valueAt("/id", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
/**
* Return the buildpack ID.
* @return the ID
@@ -62,7 +69,7 @@ final class BuildpackMetadata extends MappedObject {
* Return the buildpack version.
* @return the version
*/
String getVersion() {
@Nullable String getVersion() {
return this.version;
}
@@ -70,7 +77,7 @@ final class BuildpackMetadata extends MappedObject {
* Return the buildpack homepage address.
* @return the homepage
*/
String getHomepage() {
@Nullable String getHomepage() {
return this.homepage;
}
@@ -22,6 +22,8 @@ import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -44,11 +46,11 @@ public final class BuildpackReference {
return this.value.startsWith(prefix);
}
String getSubReference(String prefix) {
@Nullable String getSubReference(String prefix) {
return this.value.startsWith(prefix) ? this.value.substring(prefix.length()) : null;
}
Path asPath() {
@Nullable Path asPath() {
try {
URL url = new URL(this.value);
if (url.getProtocol().equals("file")) {
@@ -68,7 +70,7 @@ public final class BuildpackReference {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.build;
import org.jspecify.annotations.Nullable;
/**
* Strategy interface used to resolve a {@link BuildpackReference} to a {@link Buildpack}.
*
@@ -31,6 +33,6 @@ interface BuildpackResolver {
* @param reference the reference to resolve
* @return a resolved {@link Buildpack} instance or {@code null}
*/
Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference);
@Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference);
}
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.VolumeName;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -67,7 +69,7 @@ public class Cache {
* Return the details of the cache if it is a volume cache.
* @return the cache, or {@code null} if it is not a volume cache
*/
public Volume getVolume() {
public @Nullable Volume getVolume() {
return (this.format.equals(Format.VOLUME)) ? (Volume) this : null;
}
@@ -75,7 +77,7 @@ public class Cache {
* Return the details of the cache if it is a bind cache.
* @return the cache, or {@code null} if it is not a bind cache
*/
public Bind getBind() {
public @Nullable Bind getBind() {
return (this.format.equals(Format.BIND)) ? (Bind) this : null;
}
@@ -110,7 +112,7 @@ public class Cache {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -147,7 +149,7 @@ public class Cache {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -192,7 +194,7 @@ public class Cache {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -25,6 +25,8 @@ import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.FilePermissions;
@@ -99,7 +101,7 @@ final class DirectoryBuildpack implements Buildpack {
* @param reference the buildpack reference
* @return the resolved {@link Buildpack} or {@code null}
*/
static Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
Path path = reference.asPath();
if (path != null && Files.exists(path) && Files.isDirectory(path)) {
return new DirectoryBuildpack(path);
@@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.io.IOException;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchive.Update;
@@ -63,7 +65,8 @@ class EphemeralBuilder {
* @param buildpacks an optional set of buildpacks to apply
*/
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, ImageReference targetImage,
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env, Buildpacks buildpacks) {
BuilderMetadata builderMetadata, Creator creator, Map<String, String> env,
@Nullable Buildpacks buildpacks) {
this.name = ImageReference.random("pack.local/builder/").inTaggedForm();
this.buildOwner = buildOwner;
this.creator = creator;
@@ -28,6 +28,7 @@ import java.util.List;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.BuildpackLayersMetadata.BuildpackLayerDetails;
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
@@ -37,6 +38,7 @@ import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.docker.type.LayerId;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
import org.springframework.boot.buildpack.platform.io.TarArchive;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
/**
@@ -54,7 +56,7 @@ final class ImageBuildpack implements Buildpack {
private final BuildpackCoordinates coordinates;
private final ExportedLayers exportedLayers;
private final @Nullable ExportedLayers exportedLayers;
private ImageBuildpack(BuildpackResolverContext context, ImageReference imageReference) {
ImageReference reference = imageReference.inTaggedOrDigestForm();
@@ -95,11 +97,18 @@ final class ImageBuildpack implements Buildpack {
* @param reference the buildpack reference
* @return the resolved {@link Buildpack} or {@code null}
*/
static Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
boolean unambiguous = reference.hasPrefix(PREFIX);
try {
ImageReference imageReference = ImageReference
.of((unambiguous) ? reference.getSubReference(PREFIX) : reference.toString());
ImageReference imageReference;
if (unambiguous) {
String subReference = reference.getSubReference(PREFIX);
Assert.state(subReference != null, "'subReference' must not be null");
imageReference = ImageReference.of(subReference);
}
else {
imageReference = ImageReference.of(reference.toString());
}
return new ImageBuildpack(context, imageReference);
}
catch (IllegalArgumentException ex) {
@@ -25,7 +25,9 @@ import java.util.List;
import java.util.function.Consumer;
import com.sun.jna.Platform;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.build.Cache.Bind;
import org.springframework.boot.buildpack.platform.docker.DockerApi;
import org.springframework.boot.buildpack.platform.docker.LogUpdateEvent;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
@@ -66,7 +68,7 @@ class Lifecycle implements Closeable {
private final DockerApi docker;
private final ResolvedDockerHost dockerHost;
private final @Nullable ResolvedDockerHost dockerHost;
private final BuildRequest request;
@@ -100,7 +102,7 @@ class Lifecycle implements Closeable {
* @param request the request to process
* @param builder the ephemeral builder used to run the phases
*/
Lifecycle(BuildLog log, DockerApi docker, ResolvedDockerHost dockerHost, BuildRequest request,
Lifecycle(BuildLog log, DockerApi docker, @Nullable ResolvedDockerHost dockerHost, BuildRequest request,
EphemeralBuilder builder) {
this.log = log;
this.docker = docker;
@@ -189,7 +191,9 @@ class Lifecycle implements Closeable {
phase.withApp(this.applicationDirectory,
Binding.from(getCacheBindingSource(this.application), this.applicationDirectory));
phase.withPlatform(Directory.PLATFORM);
phase.withRunImage(this.request.getRunImage());
ImageReference runImage = this.request.getRunImage();
Assert.state(runImage != null, "'runImage' must not be null");
phase.withRunImage(runImage);
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withBuildCache(Directory.CACHE, Binding.from(getCacheBindingSource(this.buildCache), Directory.CACHE));
phase.withLaunchCache(Directory.LAUNCH_CACHE,
@@ -214,7 +218,9 @@ class Lifecycle implements Closeable {
phase.withLaunchCache(Directory.LAUNCH_CACHE,
Binding.from(getCacheBindingSource(this.launchCache), Directory.LAUNCH_CACHE));
phase.withLayers(Directory.LAYERS, Binding.from(getCacheBindingSource(this.layers), Directory.LAYERS));
phase.withRunImage(this.request.getRunImage());
ImageReference runImage = this.request.getRunImage();
Assert.state(runImage != null, "'runImage' must not be null");
phase.withRunImage(runImage);
phase.withImageName(this.request.getName());
configureOptions(phase);
return phase;
@@ -282,12 +288,22 @@ class Lifecycle implements Closeable {
}
private Cache getBuildWorkspaceBindingSource(Cache buildWorkspace, String suffix) {
return (buildWorkspace.getVolume() != null) ? Cache.volume(buildWorkspace.getVolume().getName() + "-" + suffix)
: Cache.bind(buildWorkspace.getBind().getSource() + "-" + suffix);
if (buildWorkspace.getVolume() != null) {
return Cache.volume(buildWorkspace.getVolume().getName() + "-" + suffix);
}
Bind bind = buildWorkspace.getBind();
Assert.state(bind != null, "'bind' must not be null");
return Cache.bind(bind.getSource() + "-" + suffix);
}
private String getCacheBindingSource(Cache cache) {
return (cache.getVolume() != null) ? cache.getVolume().getName() : cache.getBind().getSource();
if (cache.getVolume() != null) {
return cache.getVolume().getName();
}
Bind bind = cache.getBind();
Assert.state(bind != null, "'bind' must not be null");
return bind.getSource();
}
private Cache createVolumeCache(String prefix) {
@@ -310,7 +326,9 @@ class Lifecycle implements Closeable {
phase.withEnv("DOCKER_HOST", this.dockerHost.getAddress());
if (this.dockerHost.isSecure()) {
phase.withEnv("DOCKER_TLS_VERIFY", "1");
phase.withEnv("DOCKER_CERT_PATH", this.dockerHost.getCertificatePath());
if (this.dockerHost.getCertificatePath() != null) {
phase.withEnv("DOCKER_CERT_PATH", this.dockerHost.getCertificatePath());
}
}
}
else {
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.build;
import java.util.Comparator;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -44,7 +46,7 @@ class LifecycleVersion implements Comparable<LifecycleVersion> {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -22,6 +22,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Binding;
import org.springframework.boot.buildpack.platform.docker.type.ContainerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
@@ -48,7 +50,7 @@ class Phase {
private final List<String> securityOptions = new ArrayList<>();
private String networkMode;
private @Nullable String networkMode;
private boolean requiresApp;
@@ -152,7 +154,7 @@ class Phase {
* Update this phase with the network the build container will connect to.
* @param networkMode the network
*/
void withNetworkMode(String networkMode) {
void withNetworkMode(@Nullable String networkMode) {
this.networkMode = networkMode;
}
@@ -16,6 +16,10 @@
package org.springframework.boot.buildpack.platform.build;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Image;
import org.springframework.boot.buildpack.platform.docker.type.ImageConfig;
import org.springframework.util.Assert;
@@ -30,21 +34,21 @@ class StackId {
private static final String LABEL_NAME = "io.buildpacks.stack.id";
private final String value;
private final @Nullable String value;
StackId(String value) {
StackId(@Nullable String value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.value.equals(((StackId) obj).value);
return Objects.equals(this.value, ((StackId) obj).value);
}
boolean hasId() {
@@ -53,12 +57,12 @@ class StackId {
@Override
public int hashCode() {
return this.value.hashCode();
return Objects.hashCode(this.value);
}
@Override
public String toString() {
return this.value;
return (this.value != null) ? this.value : "<null>";
}
/**
@@ -27,6 +27,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.Layer;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
@@ -118,7 +119,7 @@ final class TarGzipBuildpack implements Buildpack {
* @param reference the buildpack reference
* @return the resolved {@link Buildpack} or {@code null}
*/
static Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
static @Nullable Buildpack resolve(BuildpackResolverContext context, BuildpackReference reference) {
Path path = reference.asPath();
if (path != null && Files.exists(path) && Files.isRegularFile(path)) {
return new TarGzipBuildpack(path);
@@ -17,4 +17,7 @@
/**
* Central API for performing a buildpack build.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.build;
import org.jspecify.annotations.NullMarked;
@@ -27,7 +27,9 @@ import java.util.Objects;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.net.URIBuilder;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.PushImageUpdateEvent.ErrorDetail;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport;
import org.springframework.boot.buildpack.platform.docker.transport.HttpTransport.Response;
@@ -81,7 +83,7 @@ public class DockerApi {
private final SystemApi system;
private volatile ApiVersion apiVersion = null;
private volatile @Nullable ApiVersion apiVersion;
/**
* Create a new {@link DockerApi} instance.
@@ -96,7 +98,7 @@ public class DockerApi {
* @param log a logger used to record output
* @since 3.5.0
*/
public DockerApi(DockerConnectionConfiguration connectionConfiguration, DockerLog log) {
public DockerApi(@Nullable DockerConnectionConfiguration connectionConfiguration, DockerLog log) {
this(HttpTransport.create(connectionConfiguration), log);
}
@@ -125,7 +127,7 @@ public class DockerApi {
return this.jsonStream;
}
private URI buildUrl(String path, Collection<?> params) {
private URI buildUrl(String path, @Nullable Collection<?> params) {
return buildUrl(API_VERSION, path, (params != null) ? params.toArray() : null);
}
@@ -133,7 +135,7 @@ public class DockerApi {
return buildUrl(API_VERSION, path, params);
}
private URI buildUrl(ApiVersion apiVersion, String path, Object... params) {
private URI buildUrl(ApiVersion apiVersion, String path, Object @Nullable ... params) {
verifyApiVersion(apiVersion);
try {
URIBuilder builder = new URIBuilder("/v" + apiVersion + path);
@@ -158,12 +160,10 @@ public class DockerApi {
}
private ApiVersion getApiVersion() {
ApiVersion apiVersion = this.apiVersion;
if (this.apiVersion == null) {
apiVersion = this.system.getApiVersion();
this.apiVersion = apiVersion;
this.apiVersion = this.system.getApiVersion();
}
return apiVersion;
return this.apiVersion;
}
/**
@@ -220,8 +220,8 @@ public class DockerApi {
* @return the {@link ImageApi pulled image} instance
* @throws IOException on IO error
*/
public Image pull(ImageReference reference, ImagePlatform platform,
UpdateListener<PullImageUpdateEvent> listener, String registryAuth) throws IOException {
public Image pull(ImageReference reference, @Nullable ImagePlatform platform,
UpdateListener<PullImageUpdateEvent> listener, @Nullable String registryAuth) throws IOException {
Assert.notNull(reference, "'reference' must not be null");
Assert.notNull(listener, "'listener' must not be null");
URI createUri = (platform != null)
@@ -250,8 +250,8 @@ public class DockerApi {
* @param registryAuth registry authentication credentials
* @throws IOException on IO error
*/
public void push(ImageReference reference, UpdateListener<PushImageUpdateEvent> listener, String registryAuth)
throws IOException {
public void push(ImageReference reference, UpdateListener<PushImageUpdateEvent> listener,
@Nullable String registryAuth) throws IOException {
Assert.notNull(reference, "'reference' must not be null");
Assert.notNull(listener, "'listener' must not be null");
URI pushUri = buildUrl("/images/" + reference + "/push");
@@ -375,8 +375,8 @@ public class DockerApi {
* @return a {@link ContainerReference} for the newly created container
* @throws IOException on IO error
*/
public ContainerReference create(ContainerConfig config, ImagePlatform platform, ContainerContent... contents)
throws IOException {
public ContainerReference create(ContainerConfig config, @Nullable ImagePlatform platform,
ContainerContent... contents) throws IOException {
Assert.notNull(config, "'config' must not be null");
Assert.noNullElements(contents, "'contents' must not contain null elements");
ContainerReference containerReference = createContainer(config, platform);
@@ -386,7 +386,8 @@ public class DockerApi {
return containerReference;
}
private ContainerReference createContainer(ContainerConfig config, ImagePlatform platform) throws IOException {
private ContainerReference createContainer(ContainerConfig config, @Nullable ImagePlatform platform)
throws IOException {
URI createUri = (platform != null)
? buildUrl(PLATFORM_API_VERSION, "/containers/create", "platform", platform)
: buildUrl("/containers/create");
@@ -530,7 +531,7 @@ public class DockerApi {
private static final String PREFIX = "Digest:";
private String digest;
private @Nullable String digest;
@Override
public void onUpdate(ProgressUpdateEvent event) {
@@ -551,7 +552,7 @@ public class DockerApi {
private final ImageArchive archive;
private String stream;
private @Nullable String stream;
private LoadImageUpdateListener(ImageArchive archive) {
this.archive = archive;
@@ -584,8 +585,11 @@ public class DockerApi {
@Override
public void onUpdate(PushImageUpdateEvent event) {
Assert.state(event.getErrorDetail() == null,
() -> "Error response received when pushing image: " + event.getErrorDetail().getMessage());
ErrorDetail errorDetail = event.getErrorDetail();
if (errorDetail != null) {
throw new IllegalStateException(
"Error response received when pushing image: " + errorDetail.getMessage());
}
}
}
@@ -34,6 +34,7 @@ import java.util.stream.Stream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.BlobReference;
import org.springframework.boot.buildpack.platform.docker.type.ImageArchiveIndex;
@@ -101,7 +102,7 @@ class ExportedImageTar implements Closeable {
* @return a new {@link TarArchive} instance or {@code null} if this entry is not
* a layer.
*/
abstract TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry);
abstract @Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry);
/**
* Create a new {@link LayerArchiveFactory} for the given tar file using either
@@ -129,8 +130,11 @@ class ExportedImageTar implements Closeable {
Assert.state(index != null || manifest != null,
() -> "Exported image '%s' does not contain 'index.json' or 'manifest.json'"
.formatted(reference));
return (index != null) ? new IndexLayerArchiveFactory(tarFile, index)
: new ManifestLayerArchiveFactory(tarFile, manifest);
if (index != null) {
return new IndexLayerArchiveFactory(tarFile, index);
}
Assert.state(manifest != null, "'manifest' must not be null");
return new ManifestLayerArchiveFactory(tarFile, manifest);
}
}
@@ -239,7 +243,7 @@ class ExportedImageTar implements Closeable {
}
@Override
TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
@Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
String mediaType = this.layerMediaTypes.get(entry.getName());
if (mediaType == null) {
return null;
@@ -264,7 +268,7 @@ class ExportedImageTar implements Closeable {
*/
private static class ManifestLayerArchiveFactory extends LayerArchiveFactory {
private Set<String> layers;
private final Set<String> layers;
ManifestLayerArchiveFactory(Path tarFile, ImageArchiveManifest manifest) {
this.layers = manifest.getEntries()
@@ -274,7 +278,7 @@ class ExportedImageTar implements Closeable {
}
@Override
TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
@Nullable TarArchive getLayerArchive(TarArchiveInputStream tar, TarArchiveEntry entry) {
if (!this.layers.contains(entry.getName())) {
return null;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker;
import org.jspecify.annotations.Nullable;
/**
* A {@link ProgressUpdateEvent} fired for image events.
*
@@ -25,9 +27,10 @@ package org.springframework.boot.buildpack.platform.docker;
*/
public class ImageProgressUpdateEvent extends ProgressUpdateEvent {
private final String id;
private final @Nullable String id;
protected ImageProgressUpdateEvent(String id, String status, ProgressDetail progressDetail, String progress) {
protected ImageProgressUpdateEvent(@Nullable String id, String status, @Nullable ProgressDetail progressDetail,
@Nullable String progress) {
super(status, progressDetail, progress);
this.id = id;
}
@@ -36,7 +39,7 @@ public class ImageProgressUpdateEvent extends ProgressUpdateEvent {
* Returns the ID of the image layer being updated if available.
* @return the ID of the updated layer or {@code null}
*/
public String getId() {
public @Nullable String getId() {
return this.id;
}
@@ -18,6 +18,7 @@ package org.springframework.boot.buildpack.platform.docker;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jspecify.annotations.Nullable;
/**
* A {@link ProgressUpdateEvent} fired as an image is loaded.
@@ -27,13 +28,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*/
public class LoadImageUpdateEvent extends ProgressUpdateEvent {
private final String stream;
private final @Nullable String stream;
private final ErrorDetail errorDetail;
private final @Nullable ErrorDetail errorDetail;
@JsonCreator
public LoadImageUpdateEvent(String stream, String status, ProgressDetail progressDetail, String progress,
ErrorDetail errorDetail) {
public LoadImageUpdateEvent(@Nullable String stream, String status, ProgressDetail progressDetail, String progress,
@Nullable ErrorDetail errorDetail) {
super(status, progressDetail, progress);
this.stream = stream;
this.errorDetail = errorDetail;
@@ -43,7 +44,7 @@ public class LoadImageUpdateEvent extends ProgressUpdateEvent {
* Return the stream response or {@code null} if no response is available.
* @return the stream response.
*/
public String getStream() {
public @Nullable String getStream() {
return this.stream;
}
@@ -52,7 +53,7 @@ public class LoadImageUpdateEvent extends ProgressUpdateEvent {
* @return the error detail, if any
* @since 3.2.12
*/
public ErrorDetail getErrorDetail() {
public @Nullable ErrorDetail getErrorDetail() {
return this.errorDetail;
}
@@ -22,6 +22,8 @@ import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
@@ -80,7 +82,7 @@ public class LogUpdateEvent extends UpdateEvent {
}
}
catch (IllegalStateException ex) {
byte[] message = ex.getMessage().getBytes(StandardCharsets.UTF_8);
byte[] message = (ex.getMessage() == null) ? new byte[0] : ex.getMessage().getBytes(StandardCharsets.UTF_8);
consumer.accept(new LogUpdateEvent(StreamType.STD_ERR, message));
StreamUtils.drain(inputStream);
}
@@ -89,7 +91,7 @@ public class LogUpdateEvent extends UpdateEvent {
}
}
private static LogUpdateEvent read(InputStream inputStream) throws IOException {
private static @Nullable LogUpdateEvent read(InputStream inputStream) throws IOException {
byte[] header = read(inputStream, 8);
if (header == null) {
return null;
@@ -100,10 +102,11 @@ public class LogUpdateEvent extends UpdateEvent {
size = (size << 8) + (header[i + 4] & 0xff);
}
byte[] payload = read(inputStream, size);
Assert.state(payload != null, "'payload' must not be null");
return new LogUpdateEvent(streamType, payload);
}
private static byte[] read(InputStream inputStream, long size) throws IOException {
private static byte @Nullable [] read(InputStream inputStream, long size) throws IOException {
byte[] data = new byte[(int) size];
int offset = 0;
do {
@@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker;
import com.fasterxml.jackson.annotation.JsonCreator;
import org.jspecify.annotations.Nullable;
/**
* An {@link UpdateEvent} that includes progress information.
@@ -27,13 +28,14 @@ import com.fasterxml.jackson.annotation.JsonCreator;
*/
public abstract class ProgressUpdateEvent extends UpdateEvent {
private final String status;
private final @Nullable String status;
private final ProgressDetail progressDetail;
private final @Nullable ProgressDetail progressDetail;
private final String progress;
private final @Nullable String progress;
protected ProgressUpdateEvent(String status, ProgressDetail progressDetail, String progress) {
protected ProgressUpdateEvent(@Nullable String status, @Nullable ProgressDetail progressDetail,
@Nullable String progress) {
this.status = status;
this.progressDetail = (ProgressDetail.isEmpty(progressDetail)) ? null : progressDetail;
this.progress = progress;
@@ -43,7 +45,7 @@ public abstract class ProgressUpdateEvent extends UpdateEvent {
* Return the status for the update. For example, "Extracting" or "Downloading".
* @return the status of the update.
*/
public String getStatus() {
public @Nullable String getStatus() {
return this.status;
}
@@ -51,7 +53,7 @@ public abstract class ProgressUpdateEvent extends UpdateEvent {
* Return progress details if available.
* @return progress details or {@code null}
*/
public ProgressDetail getProgressDetail() {
public @Nullable ProgressDetail getProgressDetail() {
return this.progressDetail;
}
@@ -59,7 +61,7 @@ public abstract class ProgressUpdateEvent extends UpdateEvent {
* Return a text based progress bar if progress information is available.
* @return the progress bar or {@code null}
*/
public String getProgress() {
public @Nullable String getProgress() {
return this.progress;
}
@@ -68,12 +70,12 @@ public abstract class ProgressUpdateEvent extends UpdateEvent {
*/
public static class ProgressDetail {
private final Long current;
private final @Nullable Long current;
private final Long total;
private final @Nullable Long total;
@JsonCreator
public ProgressDetail(Long current, Long total) {
public ProgressDetail(@Nullable Long current, @Nullable Long total) {
this.current = current;
this.total = total;
}
@@ -84,11 +86,14 @@ public abstract class ProgressUpdateEvent extends UpdateEvent {
* @since 3.3.7
*/
public int asPercentage() {
if (this.total == null || this.current == null) {
return 0;
}
int percentage = (int) ((100.0 / this.total) * this.current);
return (percentage < 0) ? 0 : Math.min(percentage, 100);
}
private static boolean isEmpty(ProgressDetail progressDetail) {
private static boolean isEmpty(@Nullable ProgressDetail progressDetail) {
return progressDetail == null || progressDetail.current == null || progressDetail.total == null;
}
@@ -18,6 +18,7 @@ package org.springframework.boot.buildpack.platform.docker;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jspecify.annotations.Nullable;
/**
* A {@link ProgressUpdateEvent} fired as an image is pushed to a registry.
@@ -27,11 +28,11 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*/
public class PushImageUpdateEvent extends ImageProgressUpdateEvent {
private final ErrorDetail errorDetail;
private final @Nullable ErrorDetail errorDetail;
@JsonCreator
public PushImageUpdateEvent(String id, String status, ProgressDetail progressDetail, String progress,
ErrorDetail errorDetail) {
@Nullable ErrorDetail errorDetail) {
super(id, status, progressDetail, progress);
this.errorDetail = errorDetail;
}
@@ -40,7 +41,7 @@ public class PushImageUpdateEvent extends ImageProgressUpdateEvent {
* Returns the details of any error encountered during processing.
* @return the error
*/
public ErrorDetail getErrorDetail() {
public @Nullable ErrorDetail getErrorDetail() {
return this.errorDetail;
}
@@ -19,8 +19,10 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* A class that represents credentials for a server as returned from a
@@ -40,15 +42,27 @@ class Credential extends MappedObject {
private final String secret;
private final String serverUrl;
private final @Nullable String serverUrl;
Credential(JsonNode node) {
super(node, MethodHandles.lookup());
this.username = valueAt("/Username", String.class);
this.secret = valueAt("/Secret", String.class);
this.username = extractUsername();
this.secret = extractSecret();
this.serverUrl = valueAt("/ServerURL", String.class);
}
private String extractSecret() {
String result = valueAt("/Secret", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
private String extractUsername() {
String result = valueAt("/Username", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
String getUsername() {
return this.username;
}
@@ -57,7 +71,7 @@ class Credential extends MappedObject {
return this.secret;
}
String getServerUrl() {
@Nullable String getServerUrl() {
return this.serverUrl;
}
@@ -25,6 +25,7 @@ import java.util.List;
import java.util.Set;
import com.sun.jna.Platform;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@@ -48,7 +49,7 @@ class CredentialHelper {
this.executable = executable;
}
Credential get(String serverUrl) throws IOException {
@Nullable Credential get(String serverUrl) throws IOException {
ProcessBuilder processBuilder = processBuilder("get");
Process process = start(processBuilder);
try (OutputStream request = process.getOutputStream()) {
@@ -31,6 +31,7 @@ import java.util.function.Supplier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@@ -124,7 +125,7 @@ final class DockerConfigurationMetadata {
}
}
private static DockerContext createDockerContext(String configLocation, String currentContext) {
private static DockerContext createDockerContext(String configLocation, @Nullable String currentContext) {
if (currentContext == null || DEFAULT_CONTEXT.equals(currentContext)) {
return DockerContext.empty();
}
@@ -145,7 +146,7 @@ final class DockerConfigurationMetadata {
}
}
private static String asHash(String currentContext) {
private static @Nullable String asHash(String currentContext) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(currentContext.getBytes(StandardCharsets.UTF_8));
@@ -167,9 +168,9 @@ final class DockerConfigurationMetadata {
static final class DockerConfig extends MappedObject {
private final String currentContext;
private final @Nullable String currentContext;
private final String credsStore;
private final @Nullable String credsStore;
private final Map<String, String> credHelpers;
@@ -183,11 +184,11 @@ final class DockerConfigurationMetadata {
this.auths = mapAt("/auths", Auth::new);
}
String getCurrentContext() {
@Nullable String getCurrentContext() {
return this.currentContext;
}
String getCredsStore() {
@Nullable String getCredsStore() {
return this.credsStore;
}
@@ -211,11 +212,11 @@ final class DockerConfigurationMetadata {
static final class Auth extends MappedObject {
private final String username;
private final @Nullable String username;
private final String password;
private final @Nullable String password;
private final String email;
private final @Nullable String email;
Auth(JsonNode node) {
super(node, MethodHandles.lookup());
@@ -233,15 +234,15 @@ final class DockerConfigurationMetadata {
this.email = valueAt("/email", String.class);
}
String getUsername() {
@Nullable String getUsername() {
return this.username;
}
String getPassword() {
@Nullable String getPassword() {
return this.password;
}
String getEmail() {
@Nullable String getEmail() {
return this.email;
}
@@ -254,20 +255,20 @@ final class DockerConfigurationMetadata {
static final class DockerContext extends MappedObject {
private final String dockerHost;
private final @Nullable String dockerHost;
private final Boolean skipTlsVerify;
private final @Nullable Boolean skipTlsVerify;
private final String tlsPath;
private final @Nullable String tlsPath;
private DockerContext(JsonNode node, String tlsPath) {
private DockerContext(JsonNode node, @Nullable String tlsPath) {
super(node, MethodHandles.lookup());
this.dockerHost = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/Host", String.class);
this.skipTlsVerify = valueAt("/Endpoints/" + DOCKER_ENDPOINT + "/SkipTLSVerify", Boolean.class);
this.tlsPath = tlsPath;
}
String getDockerHost() {
@Nullable String getDockerHost() {
return this.dockerHost;
}
@@ -275,7 +276,7 @@ final class DockerConfigurationMetadata {
return this.skipTlsVerify != null && !this.skipTlsVerify;
}
String getTlsPath() {
@Nullable String getTlsPath() {
return this.tlsPath;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker.configuration;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -33,7 +35,8 @@ public sealed interface DockerConnectionConfiguration {
* @param secure if connection is secure
* @param certificatePath a path to the certificate used for secure connections
*/
record Host(String address, boolean secure, String certificatePath) implements DockerConnectionConfiguration {
record Host(String address, boolean secure,
@Nullable String certificatePath) implements DockerConnectionConfiguration {
public Host(String address) {
this(address, false, null);
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker.configuration;
import org.jspecify.annotations.Nullable;
/**
* Docker host connection options.
*
@@ -24,23 +26,23 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
*/
public class DockerHost {
private final String address;
private final @Nullable String address;
private final boolean secure;
private final String certificatePath;
private final @Nullable String certificatePath;
public DockerHost(String address) {
public DockerHost(@Nullable String address) {
this(address, false, null);
}
public DockerHost(String address, boolean secure, String certificatePath) {
public DockerHost(@Nullable String address, boolean secure, @Nullable String certificatePath) {
this.address = address;
this.secure = secure;
this.certificatePath = certificatePath;
}
public String getAddress() {
public @Nullable String getAddress() {
return this.address;
}
@@ -48,7 +50,7 @@ public class DockerHost {
return this.secure;
}
public String getCertificatePath() {
public @Nullable String getCertificatePath() {
return this.certificatePath;
}
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.configuration;
import java.util.function.BiConsumer;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.util.Assert;
@@ -43,7 +45,7 @@ public interface DockerRegistryAuthentication {
* @return the auth header
* @since 3.5.0
*/
default String getAuthHeader(ImageReference imageReference) {
default @Nullable String getAuthHeader(@Nullable ImageReference imageReference) {
return getAuthHeader();
}
@@ -51,7 +53,7 @@ public interface DockerRegistryAuthentication {
* Returns the auth header that should be used for docker authentication.
* @return the auth header
*/
String getAuthHeader();
@Nullable String getAuthHeader();
/**
* Factory method to that returns a new {@link DockerRegistryAuthentication} instance
@@ -76,7 +78,8 @@ public interface DockerRegistryAuthentication {
* @return a new {@link DockerRegistryAuthentication} instance
* @since 3.5.0
*/
static DockerRegistryAuthentication user(String username, String password, String serverAddress, String email) {
static DockerRegistryAuthentication user(String username, String password, @Nullable String serverAddress,
@Nullable String email) {
return new DockerRegistryUserAuthentication(username, password, serverAddress, email);
}
@@ -90,7 +93,7 @@ public interface DockerRegistryAuthentication {
* @since 3.5.0
* @see #configuration(DockerRegistryAuthentication, BiConsumer)
*/
static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback) {
static DockerRegistryAuthentication configuration(@Nullable DockerRegistryAuthentication fallback) {
return configuration(fallback, (message, ex) -> System.out.println(message));
}
@@ -106,7 +109,7 @@ public interface DockerRegistryAuthentication {
* @since 3.5.0
* @see #configuration(DockerRegistryAuthentication, BiConsumer)
*/
static DockerRegistryAuthentication configuration(DockerRegistryAuthentication fallback,
static DockerRegistryAuthentication configuration(@Nullable DockerRegistryAuthentication fallback,
BiConsumer<String, Exception> credentialHelperExceptionHandler) {
Assert.notNull(credentialHelperExceptionHandler, () -> "'credentialHelperExceptionHandler' must not be null");
return new DockerRegistryConfigAuthentication(fallback, credentialHelperExceptionHandler);
@@ -21,10 +21,13 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.Auth;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerConfig;
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
import org.springframework.boot.buildpack.platform.system.Environment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -42,7 +45,7 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
static Map<String, Credential> credentialFromHelperCache = new ConcurrentHashMap<>();
private final DockerRegistryAuthentication fallback;
private final @Nullable DockerRegistryAuthentication fallback;
private final BiConsumer<String, Exception> credentialHelperExceptionHandler;
@@ -50,13 +53,13 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
private final DockerConfig dockerConfig;
DockerRegistryConfigAuthentication(DockerRegistryAuthentication fallback,
DockerRegistryConfigAuthentication(@Nullable DockerRegistryAuthentication fallback,
BiConsumer<String, Exception> credentialHelperExceptionHandler) {
this(fallback, credentialHelperExceptionHandler, Environment.SYSTEM,
(helper) -> new CredentialHelper("docker-credential-" + helper));
}
DockerRegistryConfigAuthentication(DockerRegistryAuthentication fallback,
DockerRegistryConfigAuthentication(@Nullable DockerRegistryAuthentication fallback,
BiConsumer<String, Exception> credentialHelperExceptionHandler, Environment environment,
Function<String, CredentialHelper> credentialHelperFactory) {
this.fallback = fallback;
@@ -66,23 +69,23 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
}
@Override
public String getAuthHeader() {
public @Nullable String getAuthHeader() {
return getAuthHeader(null);
}
@Override
public String getAuthHeader(ImageReference imageReference) {
public @Nullable String getAuthHeader(@Nullable ImageReference imageReference) {
String serverUrl = getServerUrl(imageReference);
DockerRegistryAuthentication authentication = getAuthentication(serverUrl);
return (authentication != null) ? authentication.getAuthHeader(imageReference) : null;
}
private String getServerUrl(ImageReference imageReference) {
private @Nullable String getServerUrl(@Nullable ImageReference imageReference) {
String domain = (imageReference != null) ? imageReference.getDomain() : null;
return (!DEFAULT_DOMAIN.equals(domain)) ? domain : INDEX_URL;
}
private DockerRegistryAuthentication getAuthentication(String serverUrl) {
private @Nullable DockerRegistryAuthentication getAuthentication(@Nullable String serverUrl) {
Credential credentialsFromHelper = getCredentialsFromHelper(serverUrl);
Map.Entry<String, Auth> authConfigEntry = getAuthConfigEntry(serverUrl);
Auth authConfig = (authConfigEntry != null) ? authConfigEntry.getValue() : null;
@@ -90,14 +93,19 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
return getAuthentication(credentialsFromHelper, authConfig, serverUrl);
}
if (authConfig != null) {
return DockerRegistryAuthentication.user(authConfig.getUsername(), authConfig.getPassword(),
authConfigEntry.getKey(), authConfig.getEmail());
Assert.state(authConfigEntry != null, "'authConfigEntry' must not be null");
String username = authConfig.getUsername();
String password = authConfig.getPassword();
Assert.state(username != null, "'username' must not be null");
Assert.state(password != null, "'password' must not be null");
return DockerRegistryAuthentication.user(username, password, authConfigEntry.getKey(),
authConfig.getEmail());
}
return this.fallback;
}
private DockerRegistryAuthentication getAuthentication(Credential credentialsFromHelper, Auth authConfig,
String serverUrl) {
private DockerRegistryAuthentication getAuthentication(Credential credentialsFromHelper, @Nullable Auth authConfig,
@Nullable String serverUrl) {
if (credentialsFromHelper.isIdentityToken()) {
return DockerRegistryAuthentication.token(credentialsFromHelper.getSecret());
}
@@ -109,12 +117,12 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
return DockerRegistryAuthentication.user(username, password, serverAddress, email);
}
private Credential getCredentialsFromHelper(String serverUrl) {
private @Nullable Credential getCredentialsFromHelper(@Nullable String serverUrl) {
return StringUtils.hasLength(serverUrl)
? credentialFromHelperCache.computeIfAbsent(serverUrl, this::computeCredentialsFromHelper) : null;
}
private Credential computeCredentialsFromHelper(String serverUrl) {
private @Nullable Credential computeCredentialsFromHelper(String serverUrl) {
CredentialHelper credentialHelper = getCredentialHelper(serverUrl);
if (credentialHelper != null) {
try {
@@ -129,12 +137,15 @@ class DockerRegistryConfigAuthentication implements DockerRegistryAuthentication
return null;
}
private CredentialHelper getCredentialHelper(String serverUrl) {
private @Nullable CredentialHelper getCredentialHelper(String serverUrl) {
String name = this.dockerConfig.getCredHelpers().getOrDefault(serverUrl, this.dockerConfig.getCredsStore());
return (StringUtils.hasLength(name)) ? this.credentialHelperFactory.apply(name) : null;
}
private Map.Entry<String, Auth> getAuthConfigEntry(String serverUrl) {
private Map.@Nullable Entry<String, Auth> getAuthConfigEntry(@Nullable String serverUrl) {
if (serverUrl == null) {
return null;
}
for (Map.Entry<String, Auth> candidate : this.dockerConfig.getAuths().entrySet()) {
if (candidate.getKey().equals(serverUrl) || candidate.getKey().endsWith("://" + serverUrl)) {
return candidate;
@@ -17,6 +17,7 @@
package org.springframework.boot.buildpack.platform.docker.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jspecify.annotations.Nullable;
/**
* {@link DockerRegistryAuthentication} for
@@ -33,12 +34,12 @@ class DockerRegistryUserAuthentication extends JsonEncodedDockerRegistryAuthenti
private final String password;
@JsonProperty("serveraddress")
private final String url;
private final @Nullable String url;
@JsonProperty
private final String email;
private final @Nullable String email;
DockerRegistryUserAuthentication(String username, String password, String url, String email) {
DockerRegistryUserAuthentication(String username, String password, @Nullable String url, @Nullable String email) {
this.username = username;
this.password = password;
this.url = url;
@@ -54,11 +55,11 @@ class DockerRegistryUserAuthentication extends JsonEncodedDockerRegistryAuthenti
return this.password;
}
String getUrl() {
@Nullable String getUrl() {
return this.url;
}
String getEmail() {
@Nullable String getEmail() {
return this.email;
}
@@ -20,6 +20,7 @@ import java.util.Base64;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
@@ -32,10 +33,10 @@ import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
class JsonEncodedDockerRegistryAuthentication implements DockerRegistryAuthentication {
@JsonIgnore
private String authHeader;
private @Nullable String authHeader;
@Override
public String getAuthHeader() {
public @Nullable String getAuthHeader() {
return this.authHeader;
}
@@ -20,6 +20,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import com.sun.jna.Platform;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfigurationMetadata.DockerContext;
import org.springframework.boot.buildpack.platform.system.Environment;
@@ -46,11 +47,11 @@ public class ResolvedDockerHost extends DockerHost {
private static final String DOCKER_CONTEXT = "DOCKER_CONTEXT";
ResolvedDockerHost(String address) {
ResolvedDockerHost(@Nullable String address) {
super(address);
}
ResolvedDockerHost(String address, boolean secure, String certificatePath) {
ResolvedDockerHost(@Nullable String address, boolean secure, @Nullable String certificatePath) {
super(address, secure, certificatePath);
}
@@ -81,11 +82,12 @@ public class ResolvedDockerHost extends DockerHost {
* @param connectionConfiguration the host configuration or {@code null}
* @return the resolved docker host
*/
public static ResolvedDockerHost from(DockerConnectionConfiguration connectionConfiguration) {
public static ResolvedDockerHost from(@Nullable DockerConnectionConfiguration connectionConfiguration) {
return from(Environment.SYSTEM, connectionConfiguration);
}
static ResolvedDockerHost from(Environment environment, DockerConnectionConfiguration connectionConfiguration) {
static ResolvedDockerHost from(Environment environment,
@Nullable DockerConnectionConfiguration connectionConfiguration) {
DockerConfigurationMetadata environmentConfiguration = DockerConfigurationMetadata.from(environment);
if (environment.get(DOCKER_CONTEXT) != null) {
DockerContext context = environmentConfiguration.forContext(environment.get(DOCKER_CONTEXT));
@@ -114,7 +116,7 @@ public class ResolvedDockerHost extends DockerHost {
return Platform.isWindows() ? WINDOWS_NAMED_PIPE_PATH : DOMAIN_SOCKET_PATH;
}
private static boolean isTrue(String value) {
private static boolean isTrue(@Nullable String value) {
try {
return (value != null) && (Integer.parseInt(value) == 1);
}
@@ -17,4 +17,7 @@
/**
* Docker configuration options.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.docker.configuration;
import org.jspecify.annotations.NullMarked;
@@ -17,4 +17,7 @@
/**
* A limited Docker API providing the operations needed by pack.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.docker;
import org.jspecify.annotations.NullMarked;
@@ -28,6 +28,8 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import org.jspecify.annotations.Nullable;
/**
* Utility methods for creating Java trust material from key and certificate files.
*
@@ -74,7 +76,7 @@ final class KeyStoreFactory {
return keyStore;
}
private static PrivateKey getPrivateKey(Path path) throws IOException {
private static @Nullable PrivateKey getPrivateKey(@Nullable Path path) throws IOException {
if (path != null && Files.exists(path)) {
String text = Files.readString(path);
return PemPrivateKeyParser.parse(text);
@@ -82,8 +84,8 @@ final class KeyStoreFactory {
return null;
}
private static void addCertificates(KeyStore keyStore, X509Certificate[] certificates, PrivateKey privateKey,
String alias) throws KeyStoreException {
private static void addCertificates(KeyStore keyStore, X509Certificate[] certificates,
@Nullable PrivateKey privateKey, String alias) throws KeyStoreException {
if (privateKey != null) {
keyStore.setKeyEntry(alias, privateKey, NO_PASSWORD, certificates);
}
@@ -29,6 +29,7 @@ import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.springframework.lang.Contract;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -56,6 +57,7 @@ final class PemCertificateParser {
* @param text the text to parse
* @return the parsed certificates
*/
@Contract("!null -> !null")
static @Nullable List<X509Certificate> parse(@Nullable String text) {
if (text == null) {
return null;
@@ -17,4 +17,7 @@
/**
* Utilities and classes for managing SSL context and keys.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.docker.ssl;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker.transport;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -45,7 +47,7 @@ public class DockerConnectionException extends RuntimeException {
return message.toString();
}
private static String getCauseMessage(Exception cause) {
private static @Nullable String getCauseMessage(Exception cause) {
if (cause.getCause() != null && cause.getCause().getClass().getName().equals(JNA_EXCEPTION_CLASS_NAME)) {
return cause.getCause().getMessage();
}
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.transport;
import java.net.URI;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -34,12 +36,12 @@ public class DockerEngineException extends RuntimeException {
private final String reasonPhrase;
private final Errors errors;
private final @Nullable Errors errors;
private final Message responseMessage;
private final @Nullable Message responseMessage;
public DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
Message responseMessage) {
public DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, @Nullable Errors errors,
@Nullable Message responseMessage) {
super(buildMessage(host, uri, statusCode, reasonPhrase, errors, responseMessage));
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
@@ -68,7 +70,7 @@ public class DockerEngineException extends RuntimeException {
* errors JSON could not be read.
* @return the errors or {@code null}
*/
public Errors getErrors() {
public @Nullable Errors getErrors() {
return this.errors;
}
@@ -77,12 +79,12 @@ public class DockerEngineException extends RuntimeException {
* message JSON could not be read.
* @return the message or {@code null}
*/
public Message getResponseMessage() {
public @Nullable Message getResponseMessage() {
return this.responseMessage;
}
private static String buildMessage(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
Message responseMessage) {
private static String buildMessage(String host, URI uri, int statusCode, String reasonPhrase,
@Nullable Errors errors, @Nullable Message responseMessage) {
Assert.notNull(host, "'host' must not be null");
Assert.notNull(uri, "'uri' must not be null");
StringBuilder message = new StringBuilder(
@@ -23,6 +23,7 @@ import java.util.stream.Stream;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jspecify.annotations.Nullable;
/**
* Errors returned from the Docker API.
@@ -35,7 +36,7 @@ public class Errors implements Iterable<Errors.Error> {
private final List<Error> errors;
@JsonCreator
Errors(@JsonProperty("errors") List<Error> errors) {
Errors(@JsonProperty("errors") @Nullable List<Error> errors) {
this.errors = (errors != null) ? errors : Collections.emptyList();
}
@@ -37,6 +37,7 @@ import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
@@ -95,7 +96,7 @@ abstract class HttpClientTransport implements HttpTransport {
* @return the operation response
*/
@Override
public Response post(URI uri, String registryAuth) {
public Response post(URI uri, @Nullable String registryAuth) {
return execute(new HttpPost(uri), registryAuth);
}
@@ -148,7 +149,7 @@ abstract class HttpClientTransport implements HttpTransport {
return execute(request);
}
private Response execute(HttpUriRequestBase request, String registryAuth) {
private Response execute(HttpUriRequestBase request, @Nullable String registryAuth) {
if (StringUtils.hasText(registryAuth)) {
request.setHeader(REGISTRY_AUTH_HEADER, registryAuth);
}
@@ -178,7 +179,7 @@ abstract class HttpClientTransport implements HttpTransport {
protected void beforeExecute(HttpRequest request) {
}
private byte[] readContent(ClassicHttpResponse response) throws IOException {
private byte @Nullable [] readContent(ClassicHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
if (entity == null) {
return null;
@@ -188,7 +189,7 @@ abstract class HttpClientTransport implements HttpTransport {
}
}
private Errors deserializeErrors(byte[] content) {
private @Nullable Errors deserializeErrors(byte @Nullable [] content) {
if (content == null) {
return null;
}
@@ -200,7 +201,7 @@ abstract class HttpClientTransport implements HttpTransport {
}
}
private Message deserializeMessage(byte[] content) {
private @Nullable Message deserializeMessage(byte @Nullable [] content) {
if (content == null) {
return null;
}
@@ -23,6 +23,7 @@ import java.io.OutputStream;
import java.net.URI;
import org.apache.hc.core5.http.Header;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConnectionConfiguration;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost;
@@ -61,7 +62,7 @@ public interface HttpTransport {
* @return the operation response
* @throws IOException on IO error
*/
Response post(URI uri, String registryAuth) throws IOException;
Response post(URI uri, @Nullable String registryAuth) throws IOException;
/**
* Perform an HTTP POST operation.
@@ -104,7 +105,7 @@ public interface HttpTransport {
* @param connectionConfiguration the Docker host information
* @return a {@link HttpTransport} instance
*/
static HttpTransport create(DockerConnectionConfiguration connectionConfiguration) {
static HttpTransport create(@Nullable DockerConnectionConfiguration connectionConfiguration) {
ResolvedDockerHost host = ResolvedDockerHost.from(connectionConfiguration);
HttpTransport remote = RemoteHttpClientTransport.createIfPossible(host);
return (remote != null) ? remote : LocalHttpClientTransport.create(host);
@@ -122,7 +123,7 @@ public interface HttpTransport {
*/
InputStream getContent() throws IOException;
default Header getHeader(String name) {
default @Nullable Header getHeader(String name) {
throw new UnsupportedOperationException();
}
@@ -39,6 +39,7 @@ import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.config.Lookup;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.util.TimeValue;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
import org.springframework.boot.buildpack.platform.socket.NamedPipeSocket;
@@ -85,7 +86,7 @@ final class LocalHttpClientTransport extends HttpClientTransport {
.setValidateAfterInactivity(TimeValue.NEG_ONE_MILLISECOND)
.build();
private static final Lookup<TlsSocketStrategy> NO_TLS_SOCKET = (name) -> null;
private static final Lookup<@Nullable TlsSocketStrategy> NO_TLS_SOCKET = (name) -> null;
LocalConnectionManager(ResolvedDockerHost dockerHost) {
super(createhttpClientConnectionOperator(dockerHost), null);
@@ -18,6 +18,7 @@ package org.springframework.boot.buildpack.platform.docker.transport;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.jspecify.annotations.Nullable;
/**
* A message returned from the Docker API.
@@ -27,10 +28,10 @@ import com.fasterxml.jackson.annotation.JsonProperty;
*/
public class Message {
private final String message;
private final @Nullable String message;
@JsonCreator
Message(@JsonProperty("message") String message) {
Message(@JsonProperty("message") @Nullable String message) {
this.message = message;
}
@@ -38,13 +39,13 @@ public class Message {
* Return the message contained in the response.
* @return the message
*/
public String getMessage() {
public @Nullable String getMessage() {
return this.message;
}
@Override
public String toString() {
return this.message;
return (this.message == null) ? "<null>" : this.message;
}
}
@@ -30,6 +30,7 @@ import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.Timeout;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost;
import org.springframework.boot.buildpack.platform.docker.configuration.ResolvedDockerHost;
@@ -51,11 +52,11 @@ final class RemoteHttpClientTransport extends HttpClientTransport {
super(client, host);
}
static RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost) {
static @Nullable RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost) {
return createIfPossible(dockerHost, new SslContextFactory());
}
static RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost,
static @Nullable RemoteHttpClientTransport createIfPossible(ResolvedDockerHost dockerHost,
SslContextFactory sslContextFactory) {
if (!dockerHost.isRemote()) {
return null;
@@ -17,4 +17,7 @@
/**
* Docker transport classes providing HTTP operations on a local or remote engine.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.docker.transport;
import org.jspecify.annotations.NullMarked;
@@ -19,6 +19,8 @@ package org.springframework.boot.buildpack.platform.docker.type;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -90,7 +92,7 @@ public final class ApiVersion {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -21,6 +21,7 @@ import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* A reference to a blob by its digest.
@@ -36,8 +37,20 @@ public class BlobReference extends MappedObject {
BlobReference(JsonNode node) {
super(node, MethodHandles.lookup());
this.digest = valueAt("/digest", String.class);
this.mediaType = valueAt("/mediaType", String.class);
this.digest = extractDigest();
this.mediaType = extractMediaType();
}
private String extractMediaType() {
String result = valueAt("/mediaType", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
private String extractDigest() {
String result = valueAt("/digest", String.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
/**
@@ -29,6 +29,7 @@ import java.util.function.Consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.SharedObjectMapper;
import org.springframework.util.Assert;
@@ -48,9 +49,9 @@ public class ContainerConfig {
private final String json;
ContainerConfig(String user, ImageReference image, String command, List<String> args, Map<String, String> labels,
List<Binding> bindings, Map<String, String> env, String networkMode, List<String> securityOptions)
throws IOException {
ContainerConfig(@Nullable String user, ImageReference image, String command, List<String> args,
Map<String, String> labels, List<Binding> bindings, Map<String, String> env, @Nullable String networkMode,
List<String> securityOptions) throws IOException {
Assert.notNull(image, "'image' must not be null");
Assert.hasText(command, "'command' must not be empty");
ObjectMapper objectMapper = SharedObjectMapper.get();
@@ -112,9 +113,9 @@ public class ContainerConfig {
private final ImageReference image;
private String user;
private @Nullable String user;
private String command;
private @Nullable String command;
private final List<String> args = new ArrayList<>();
@@ -124,7 +125,7 @@ public class ContainerConfig {
private final Map<String, String> env = new LinkedHashMap<>();
private String networkMode;
private @Nullable String networkMode;
private final List<String> securityOptions = new ArrayList<>();
@@ -135,6 +136,7 @@ public class ContainerConfig {
private ContainerConfig run(Consumer<Update> update) {
update.accept(this);
try {
Assert.state(this.command != null, "'command' must not be null");
return new ContainerConfig(this.user, this.image, this.command, this.args, this.labels, this.bindings,
this.env, this.networkMode, this.securityOptions);
}
@@ -201,7 +203,7 @@ public class ContainerConfig {
* connect to.
* @param networkMode the network
*/
public void withNetworkMode(String networkMode) {
public void withNetworkMode(@Nullable String networkMode) {
this.networkMode = networkMode;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker.type;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -34,7 +36,7 @@ public final class ContainerReference {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -21,8 +21,11 @@ import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* Status details returned from {@code Docker container wait}.
@@ -34,20 +37,26 @@ public class ContainerStatus extends MappedObject {
private final int statusCode;
private final String waitingErrorMessage;
private final @Nullable String waitingErrorMessage;
ContainerStatus(int statusCode, String waitingErrorMessage) {
super(null, null);
ContainerStatus(int statusCode, @Nullable String waitingErrorMessage) {
super(NullNode.getInstance(), MethodHandles.lookup());
this.statusCode = statusCode;
this.waitingErrorMessage = waitingErrorMessage;
}
ContainerStatus(JsonNode node) {
super(node, MethodHandles.lookup());
this.statusCode = valueAt("/StatusCode", Integer.class);
this.statusCode = extractStatusCode();
this.waitingErrorMessage = valueAt("/Error/Message", String.class);
}
private Integer extractStatusCode() {
Integer result = valueAt("/StatusCode", Integer.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
/**
* Return the container exit status code.
* @return the exit status code
@@ -60,7 +69,7 @@ public class ContainerStatus extends MappedObject {
* Return a message indicating an error waiting for a container to stop.
* @return the waiting error message
*/
public String getWaitingErrorMessage() {
public @Nullable String getWaitingErrorMessage() {
return this.waitingErrorMessage;
}
@@ -81,7 +90,7 @@ public class ContainerStatus extends MappedObject {
* @param errorMessage the error message
* @return a new {@link ContainerStatus} instance
*/
public static ContainerStatus of(int statusCode, String errorMessage) {
public static ContainerStatus of(int statusCode, @Nullable String errorMessage) {
return new ContainerStatus(statusCode, errorMessage);
}
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.StringUtils;
@@ -43,13 +44,13 @@ public class Image extends MappedObject {
private final List<LayerId> layers;
private final String os;
private final @Nullable String os;
private final String architecture;
private final @Nullable String architecture;
private final String variant;
private final @Nullable String variant;
private final String created;
private final @Nullable String created;
Image(JsonNode node) {
super(node, MethodHandles.lookup());
@@ -62,7 +63,7 @@ public class Image extends MappedObject {
this.created = valueAt("/Created", String.class);
}
private List<LayerId> extractLayers(String[] layers) {
private List<LayerId> extractLayers(String @Nullable [] layers) {
if (layers == null) {
return Collections.emptyList();
}
@@ -105,7 +106,7 @@ public class Image extends MappedObject {
* Return the architecture of the image.
* @return the image architecture
*/
public String getArchitecture() {
public @Nullable String getArchitecture() {
return this.architecture;
}
@@ -113,7 +114,7 @@ public class Image extends MappedObject {
* Return the variant of the image.
* @return the image variant
*/
public String getVariant() {
public @Nullable String getVariant() {
return this.variant;
}
@@ -121,7 +122,7 @@ public class Image extends MappedObject {
* Return the created date of the image.
* @return the image created date
*/
public String getCreated() {
public @Nullable String getCreated() {
return this.created;
}
@@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.io.Content;
import org.springframework.boot.buildpack.platform.io.IOConsumer;
@@ -72,20 +73,21 @@ public class ImageArchive implements TarArchive {
private final Instant createDate;
private final ImageReference tag;
private final @Nullable ImageReference tag;
private final String os;
private final String architecture;
private final @Nullable String architecture;
private final String variant;
private final @Nullable String variant;
private final List<LayerId> existingLayers;
private final List<Layer> newLayers;
ImageArchive(ObjectMapper objectMapper, ImageConfig imageConfig, Instant createDate, ImageReference tag, String os,
String architecture, String variant, List<LayerId> existingLayers, List<Layer> newLayers) {
ImageArchive(ObjectMapper objectMapper, ImageConfig imageConfig, Instant createDate, @Nullable ImageReference tag,
String os, @Nullable String architecture, @Nullable String variant, List<LayerId> existingLayers,
List<Layer> newLayers) {
this.objectMapper = objectMapper;
this.imageConfig = imageConfig;
this.createDate = createDate;
@@ -117,7 +119,7 @@ public class ImageArchive implements TarArchive {
* Return the tag of the archive.
* @return the tag
*/
public ImageReference getTag() {
public @Nullable ImageReference getTag() {
return this.tag;
}
@@ -257,9 +259,9 @@ public class ImageArchive implements TarArchive {
private ImageConfig config;
private Instant createDate;
private @Nullable Instant createDate;
private ImageReference tag;
private @Nullable ImageReference tag;
private final List<Layer> newLayers = new ArrayList<>();
@@ -24,6 +24,7 @@ import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* Image archive index information as provided by {@code index.json}.
@@ -42,10 +43,16 @@ public class ImageArchiveIndex extends MappedObject {
protected ImageArchiveIndex(JsonNode node) {
super(node, MethodHandles.lookup());
this.schemaVersion = valueAt("/schemaVersion", Integer.class);
this.schemaVersion = extractSchemaVersion();
this.manifests = childrenAt("/manifests", BlobReference::new);
}
private Integer extractSchemaVersion() {
Integer result = valueAt("/schemaVersion", Integer.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
public Integer getSchemaVersion() {
return this.schemaVersion;
}
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.docker.type;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -41,7 +43,7 @@ public class ImageName {
private final String string;
ImageName(String domain, String path) {
ImageName(@Nullable String domain, String path) {
Assert.hasText(path, "'path' must not be empty");
this.domain = getDomainOrDefault(domain);
this.name = getNameWithDefaultPath(this.domain, path);
@@ -65,7 +67,7 @@ public class ImageName {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -100,7 +102,7 @@ public class ImageName {
return this.string;
}
private String getDomainOrDefault(String domain) {
private String getDomainOrDefault(@Nullable String domain) {
if (domain == null || LEGACY_DOMAIN.equals(domain)) {
return DEFAULT_DOMAIN;
}
@@ -135,7 +137,7 @@ public class ImageName {
return new ImageName(domain, path);
}
static String parseDomain(String value) {
static @Nullable String parseDomain(String value) {
int firstSlash = value.indexOf('/');
String candidate = (firstSlash != -1) ? value.substring(0, firstSlash) : null;
if (candidate != null && Regex.DOMAIN.matcher(candidate).matches()) {
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.type;
import java.util.Objects;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -30,11 +32,11 @@ public class ImagePlatform {
private final String os;
private final String architecture;
private final @Nullable String architecture;
private final String variant;
private final @Nullable String variant;
ImagePlatform(String os, String architecture, String variant) {
ImagePlatform(String os, @Nullable String architecture, @Nullable String variant) {
Assert.hasText(os, "'os' must not be empty");
this.os = os;
this.architecture = architecture;
@@ -42,7 +44,7 @@ public class ImagePlatform {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -21,6 +21,8 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -41,13 +43,13 @@ public final class ImageReference {
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 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");
this.name = name;
this.tag = tag;
@@ -77,7 +79,7 @@ public final class ImageReference {
* Return the tag from the reference or {@code null}.
* @return the referenced tag
*/
public String getTag() {
public @Nullable String getTag() {
return this.tag;
}
@@ -85,12 +87,12 @@ public final class ImageReference {
* Return the digest from the reference or {@code null}.
* @return the referenced digest
*/
public String getDigest() {
public @Nullable String getDigest() {
return this.digest;
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -124,7 +126,7 @@ public final class ImageReference {
return buildString(this.name.toLegacyString(), this.tag, this.digest);
}
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);
if (tag != null) {
string.append(":").append(tag);
@@ -18,6 +18,8 @@ package org.springframework.boot.buildpack.platform.docker.type;
import java.math.BigInteger;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -57,7 +59,7 @@ public final class LayerId {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (obj == this) {
return true;
}
@@ -22,8 +22,10 @@ import java.lang.invoke.MethodHandles;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* A manifest as defined in {@code application/vnd.docker.distribution.manifest} or
@@ -38,22 +40,28 @@ public class Manifest extends MappedObject {
private final Integer schemaVersion;
private final String mediaType;
private final @Nullable String mediaType;
private final List<BlobReference> layers;
protected Manifest(JsonNode node) {
super(node, MethodHandles.lookup());
this.schemaVersion = valueAt("/schemaVersion", Integer.class);
this.schemaVersion = extractSchemaVersion();
this.mediaType = valueAt("/mediaType", String.class);
this.layers = childrenAt("/layers", BlobReference::new);
}
private Integer extractSchemaVersion() {
Integer result = valueAt("/schemaVersion", Integer.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
public Integer getSchemaVersion() {
return this.schemaVersion;
}
public String getMediaType() {
public @Nullable String getMediaType() {
return this.mediaType;
}
@@ -23,8 +23,10 @@ import java.util.List;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.buildpack.platform.json.MappedObject;
import org.springframework.util.Assert;
/**
* A distribution manifest list as defined in
@@ -39,22 +41,28 @@ public class ManifestList extends MappedObject {
private final Integer schemaVersion;
private final String mediaType;
private final @Nullable String mediaType;
private final List<BlobReference> manifests;
protected ManifestList(JsonNode node) {
super(node, MethodHandles.lookup());
this.schemaVersion = valueAt("/schemaVersion", Integer.class);
this.schemaVersion = extractSchemaVersion();
this.mediaType = valueAt("/mediaType", String.class);
this.manifests = childrenAt("/manifests", BlobReference::new);
}
private Integer extractSchemaVersion() {
Integer result = valueAt("/schemaVersion", Integer.class);
Assert.state(result != null, "'result' must not be null");
return result;
}
public Integer getSchemaVersion() {
return this.schemaVersion;
}
public String getMediaType() {
public @Nullable String getMediaType() {
return this.mediaType;
}
@@ -22,6 +22,8 @@ import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
/**
@@ -39,7 +41,7 @@ public final class VolumeName {
}
@Override
public boolean equals(Object obj) {
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
@@ -17,4 +17,7 @@
/**
* Docker types.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.docker.type;
import org.jspecify.annotations.NullMarked;
@@ -24,6 +24,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
@@ -137,7 +139,7 @@ public class InspectedContent implements Content {
private OutputStream delegate;
private File tempFile;
private @Nullable File tempFile;
private final byte[] singleByteBuffer = new byte[0];
@@ -17,4 +17,7 @@
/**
* IO classes and utilities.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.io;
import org.jspecify.annotations.NullMarked;
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jspecify.annotations.Nullable;
/**
* Utility class that allows JSON to be parsed and processed as it's received.
@@ -78,7 +79,7 @@ public class JsonStream {
}
@SuppressWarnings("unchecked")
private <T> T read(JsonParser parser, Class<T> type) throws IOException {
private <T> @Nullable T read(JsonParser parser, Class<T> type) throws IOException {
if (ObjectNode.class.isAssignableFrom(type)) {
ObjectNode node = this.objectMapper.readTree(parser);
if (node == null || node.isMissingNode() || node.isEmpty()) {
@@ -32,6 +32,7 @@ import java.util.function.Function;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jspecify.annotations.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
@@ -74,7 +75,7 @@ public class MappedObject {
* @param type the desired type. May be a simple JSON type or an interface
* @return the value
*/
protected <T> T valueAt(String expression, Class<T> type) {
protected <T> @Nullable T valueAt(String expression, Class<T> type) {
return valueAt(this, this.node, this.lookup, expression, type);
}
@@ -104,7 +105,7 @@ public class MappedObject {
* @return a list of children
* @since 3.2.6
*/
protected <T> List<T> childrenAt(String expression, Function<JsonNode, T> factory) {
protected <T> List<T> childrenAt(@Nullable String expression, Function<JsonNode, T> factory) {
JsonNode node = (expression != null) ? this.node.at(expression) : this.node;
if (node.isEmpty()) {
return Collections.emptyList();
@@ -120,13 +121,14 @@ public class MappedObject {
return (T) handler.root;
}
protected static <T> T valueAt(Object proxy, String expression, Class<T> type) {
protected static <T> @Nullable T valueAt(Object proxy, String expression, Class<T> type) {
MappedInvocationHandler handler = (MappedInvocationHandler) Proxy.getInvocationHandler(proxy);
return valueAt(handler.root, handler.node, handler.lookup, expression, type);
}
@SuppressWarnings("unchecked")
private static <T> T valueAt(MappedObject root, JsonNode node, Lookup lookup, String expression, Class<T> type) {
private static <T> @Nullable T valueAt(MappedObject root, JsonNode node, Lookup lookup, String expression,
Class<T> type) {
JsonNode result = node.at(expression);
if (result.isMissingNode() && expression.startsWith("/") && expression.length() > 1
&& Character.isLowerCase(expression.charAt(1))) {
@@ -233,7 +235,7 @@ public class MappedObject {
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
public @Nullable Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> declaringClass = method.getDeclaringClass();
if (method.isDefault()) {
Lookup lookup = this.lookup.in(declaringClass);
@@ -262,7 +264,7 @@ public class MappedObject {
return result.toString();
}
private Object valueForProperty(String name, Class<?> type) {
private @Nullable Object valueForProperty(String name, Class<?> type) {
return valueAt(this.root, this.node, this.lookup, "/" + name, type);
}
@@ -17,4 +17,7 @@
/**
* Utilities and classes for JSON processing.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.json;
import org.jspecify.annotations.NullMarked;
@@ -21,6 +21,8 @@ import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import org.jspecify.annotations.Nullable;
/**
* Abstract base class for custom socket implementation.
*
@@ -57,22 +59,22 @@ class AbstractSocket extends Socket {
}
@Override
public InetAddress getInetAddress() {
public @Nullable InetAddress getInetAddress() {
return null;
}
@Override
public InetAddress getLocalAddress() {
public @Nullable InetAddress getLocalAddress() {
return null;
}
@Override
public SocketAddress getLocalSocketAddress() {
public @Nullable SocketAddress getLocalSocketAddress() {
return null;
}
@Override
public SocketAddress getRemoteSocketAddress() {
public @Nullable SocketAddress getRemoteSocketAddress() {
return null;
}
@@ -17,4 +17,7 @@
/**
* Low-level {@link java.net.Socket} implementations required for local Docker access.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.socket;
import org.jspecify.annotations.NullMarked;
@@ -16,6 +16,8 @@
package org.springframework.boot.buildpack.platform.system;
import org.jspecify.annotations.Nullable;
/**
* Provides access to environment variable values.
*
@@ -38,6 +40,6 @@ public interface Environment {
* @return the string value of the variable, or {@code null} if the variable is not
* defined in the environment
*/
String get(String name);
@Nullable String get(String name);
}
@@ -17,4 +17,7 @@
/**
* System abstractions.
*/
@NullMarked
package org.springframework.boot.buildpack.platform.system;
import org.jspecify.annotations.NullMarked;
@@ -92,4 +92,5 @@
<suppress files="LambdaSafe\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="ConditionMessage\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="EntityManagerFactoryBuilder\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
<suppress files="DockerApi\.java" checks="NoWhitespaceBefore" message="'...' is preceded with whitespace"/>
</suppressions>