diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageName.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageName.java
index 0d30468466..f11439d992 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageName.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageName.java
@@ -22,8 +22,10 @@ import org.springframework.util.Assert;
* A Docker image name of the form {@literal "docker.io/library/ubuntu"}.
*
* @author Phillip Webb
+ * @author Scott Frederick
* @since 2.3.0
* @see ImageReference
+ * @see ImageReferenceParser
* @see #of(String)
*/
public class ImageName {
@@ -41,11 +43,10 @@ public class ImageName {
private final String string;
ImageName(String domain, String name) {
- Assert.hasText(domain, "Domain must not be empty");
Assert.hasText(name, "Name must not be empty");
- this.domain = domain;
- this.name = name;
- this.string = domain + "/" + name;
+ this.domain = getDomainOrDefault(domain);
+ this.name = getNameWithDefaultPath(this.domain, name);
+ this.string = this.domain + "/" + this.name;
}
/**
@@ -100,6 +101,20 @@ public class ImageName {
return this.string;
}
+ private String getDomainOrDefault(String domain) {
+ if (domain == null || LEGACY_DOMAIN.equals(domain)) {
+ return DEFAULT_DOMAIN;
+ }
+ return domain;
+ }
+
+ private String getNameWithDefaultPath(String domain, String name) {
+ if (DEFAULT_DOMAIN.equals(domain) && !name.contains("/")) {
+ return OFFICIAL_REPOSITORY_NAME + "/" + name;
+ }
+ return name;
+ }
+
/**
* Create a new {@link ImageName} from the given value. The following value forms can
* be used:
@@ -112,26 +127,9 @@ public class ImageName {
* @return an {@link ImageName} instance
*/
public static ImageName of(String value) {
- String[] split = split(value);
- return new ImageName(split[0], split[1]);
- }
-
- static String[] split(String value) {
Assert.hasText(value, "Value must not be empty");
- String domain = DEFAULT_DOMAIN;
- int firstSlash = value.indexOf('/');
- if (firstSlash != -1) {
- String firstSegment = value.substring(0, firstSlash);
- if (firstSegment.contains(".") || firstSegment.contains(":") || "localhost".equals(firstSegment)) {
- domain = LEGACY_DOMAIN.equals(firstSegment) ? DEFAULT_DOMAIN : firstSegment;
- value = value.substring(firstSlash + 1);
- }
- }
- if (DEFAULT_DOMAIN.equals(domain) && !value.contains("/")) {
- value = OFFICIAL_REPOSITORY_NAME + "/" + value;
- }
- return new String[] { domain, value };
-
+ ImageReferenceParser parser = ImageReferenceParser.of(value);
+ return new ImageName(parser.getDomain(), parser.getName());
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java
index 1549274137..093b510b13 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReference.java
@@ -30,9 +30,7 @@ import org.springframework.util.ObjectUtils;
* @author Scott Frederick
* @since 2.3.0
* @see ImageName
- * @see How
- * are Docker image names parsed?
+ * @see ImageReferenceParser
*/
public final class ImageReference {
@@ -180,7 +178,7 @@ public final class ImageReference {
filename = filename.substring(0, filename.length() - 4);
int firstDot = filename.indexOf('.');
if (firstDot == -1) {
- return ImageReference.of(filename);
+ return of(filename);
}
String name = filename.substring(0, firstDot);
String version = filename.substring(firstDot + 1);
@@ -226,8 +224,9 @@ public final class ImageReference {
*/
public static ImageReference of(String value) {
Assert.hasText(value, "Value must not be null");
- String[] domainAndValue = ImageName.split(value);
- return of(domainAndValue[0], domainAndValue[1]);
+ ImageReferenceParser parser = ImageReferenceParser.of(value);
+ ImageName name = new ImageName(parser.getDomain(), parser.getName());
+ return new ImageReference(name, parser.getTag(), parser.getDigest());
}
/**
@@ -261,21 +260,4 @@ public final class ImageReference {
return new ImageReference(name, tag, digest);
}
- private static ImageReference of(String domain, String value) {
- String digest = null;
- int lastAt = value.indexOf('@');
- if (lastAt != -1) {
- digest = value.substring(lastAt + 1);
- value = value.substring(0, lastAt);
- }
- String tag = null;
- int firstColon = value.indexOf(':');
- if (firstColon != -1) {
- tag = value.substring(firstColon + 1);
- value = value.substring(0, firstColon);
- }
- ImageName name = new ImageName(domain, value);
- return new ImageReference(name, tag, digest);
- }
-
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParser.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParser.java
new file mode 100644
index 0000000000..b20cc1dc2d
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParser.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.buildpack.platform.docker.type;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A parser for Docker image references in the form
+ * {@code [domainHost:port/][path/]name[:tag][@digest]}.
+ *
+ * @author Scott Frederick
+ * @see Docker
+ * grammar reference
+ * @see Docker grammar
+ * implementation
+ * @see How
+ * are Docker image names parsed?
+ */
+final class ImageReferenceParser {
+
+ private static final String DOMAIN_SEGMENT_REGEX = "(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])";
+
+ private static final String DOMAIN_PORT_REGEX = "[0-9]+";
+
+ private static final String DOMAIN_REGEX = oneOf(
+ groupOf(DOMAIN_SEGMENT_REGEX, repeating("[.]", DOMAIN_SEGMENT_REGEX)),
+ groupOf(DOMAIN_SEGMENT_REGEX, "[:]", DOMAIN_PORT_REGEX),
+ groupOf(DOMAIN_SEGMENT_REGEX, repeating("[.]", DOMAIN_SEGMENT_REGEX), "[:]", DOMAIN_PORT_REGEX),
+ "localhost");
+
+ private static final String NAME_CHARS_REGEX = "[a-z0-9]+";
+
+ private static final String NAME_SEPARATOR_REGEX = "(?:[._]|__|[-]*)";
+
+ private static final String NAME_SEGMENT_REGEX = groupOf(NAME_CHARS_REGEX,
+ optional(repeating(NAME_SEPARATOR_REGEX, NAME_CHARS_REGEX)));
+
+ private static final String NAME_PATH_REGEX = groupOf(NAME_SEGMENT_REGEX,
+ optional(repeating("[/]", NAME_SEGMENT_REGEX)));
+
+ private static final String DIGEST_ALGORITHM_SEGMENT_REGEX = "[A-Za-z][A-Za-z0-9]*";
+
+ private static final String DIGEST_ALGORITHM_SEPARATOR_REGEX = "[-_+.]";
+
+ private static final String DIGEST_ALGORITHM_REGEX = groupOf(DIGEST_ALGORITHM_SEGMENT_REGEX,
+ optional(repeating(DIGEST_ALGORITHM_SEPARATOR_REGEX, DIGEST_ALGORITHM_SEGMENT_REGEX)));
+
+ private static final String DIGEST_VALUE_REGEX = "[0-9A-Fa-f]{32,}";
+
+ private static final String DIGEST_REGEX = groupOf(DIGEST_ALGORITHM_REGEX, "[:]", DIGEST_VALUE_REGEX);
+
+ private static final String TAG_REGEX = "[\\w][\\w.-]{0,127}";
+
+ private static final String DOMAIN_CAPTURE_GROUP = "domain";
+
+ private static final String NAME_CAPTURE_GROUP = "name";
+
+ private static final String TAG_CAPTURE_GROUP = "tag";
+
+ private static final String DIGEST_CAPTURE_GROUP = "digest";
+
+ private static final Pattern REFERENCE_REGEX_PATTERN = patternOf(anchored(
+ optional(captureOf(DOMAIN_CAPTURE_GROUP, DOMAIN_REGEX), "[/]"),
+ captureOf(NAME_CAPTURE_GROUP, NAME_PATH_REGEX), optional("[:]", captureOf(TAG_CAPTURE_GROUP, TAG_REGEX)),
+ optional("[@]", captureOf(DIGEST_CAPTURE_GROUP, DIGEST_REGEX))));
+
+ private final String domain;
+
+ private final String name;
+
+ private final String tag;
+
+ private final String digest;
+
+ private ImageReferenceParser(String domain, String name, String tag, String digest) {
+ this.domain = domain;
+ this.name = name;
+ this.tag = tag;
+ this.digest = digest;
+ }
+
+ String getDomain() {
+ return this.domain;
+ }
+
+ String getName() {
+ return this.name;
+ }
+
+ String getTag() {
+ return this.tag;
+ }
+
+ String getDigest() {
+ return this.digest;
+ }
+
+ static ImageReferenceParser of(String reference) {
+ Matcher matcher = REFERENCE_REGEX_PATTERN.matcher(reference);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Unable to parse image reference \"" + reference + "\". "
+ + "Image reference must be in the form \"[domainHost:port/][path/]name[:tag][@digest]\", "
+ + "with \"path\" and \"name\" containing only [a-z0-9][.][_][-]");
+ }
+ return new ImageReferenceParser(matcher.group(DOMAIN_CAPTURE_GROUP), matcher.group(NAME_CAPTURE_GROUP),
+ matcher.group(TAG_CAPTURE_GROUP), matcher.group(DIGEST_CAPTURE_GROUP));
+ }
+
+ private static Pattern patternOf(String... expressions) {
+ return Pattern.compile(join(expressions));
+ }
+
+ private static String groupOf(String... expressions) {
+ return "(?:" + join(expressions) + ')';
+ }
+
+ private static String captureOf(String groupName, String... expressions) {
+ return "(?<" + groupName + ">" + join(expressions) + ')';
+ }
+
+ private static String oneOf(String... expressions) {
+ return groupOf(String.join("|", expressions));
+ }
+
+ private static String optional(String... expressions) {
+ return groupOf(join(expressions)) + '?';
+ }
+
+ private static String repeating(String... expressions) {
+ return groupOf(join(expressions)) + '+';
+ }
+
+ private static String anchored(String... expressions) {
+ return '^' + join(expressions) + '$';
+ }
+
+ private static String join(String... expressions) {
+ return String.join("", expressions);
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java
index 266fd7d1b9..ccd42bc897 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuildRequestTests.java
@@ -100,9 +100,9 @@ public class BuildRequestTests {
@Test
void withBuilderWhenHasDigestUpdatesBuilder() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withBuilder(ImageReference
- .of("spring/builder:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
+ .of("spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
assertThat(request.getBuilder().toString()).isEqualTo(
- "docker.io/spring/builder:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
+ "docker.io/spring/builder@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
}
@Test
@@ -115,9 +115,9 @@ public class BuildRequestTests {
@Test
void withRunImageWhenHasDigestUpdatesRunImage() throws IOException {
BuildRequest request = BuildRequest.forJarFile(writeTestJarFile("my-app-0.0.1.jar")).withRunImage(ImageReference
- .of("example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
+ .of("example.com/custom/run-image@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d"));
assertThat(request.getRunImage().toString()).isEqualTo(
- "example.com/custom/run-image:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
+ "example.com/custom/run-image@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d");
}
@Test
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java
index e46a0a4e6c..99789bb4ce 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/build/BuilderTests.java
@@ -115,7 +115,7 @@ class BuilderTests {
given(docker.image().pull(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_NAME)), any()))
.willAnswer(withPulledImage(builderImage));
given(docker.image().pull(eq(ImageReference.of(
- "docker.io/cloudfoundry/run:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")),
+ "docker.io/cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d")),
any())).willAnswer(withPulledImage(runImage));
Builder builder = new Builder(BuildLog.to(out), docker);
BuildRequest request = getTestRequest();
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageNameTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageNameTests.java
index c964c21d22..f994e40047 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageNameTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageNameTests.java
@@ -25,6 +25,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
* Tests for {@link ImageName}.
*
* @author Phillip Webb
+ * @author Scott Frederick
*/
class ImageNameTests {
@@ -99,11 +100,13 @@ class ImageNameTests {
void hashCodeAndEquals() {
ImageName n1 = ImageName.of("ubuntu");
ImageName n2 = ImageName.of("library/ubuntu");
- ImageName n3 = ImageName.of("docker.io/library/ubuntu");
- ImageName n4 = ImageName.of("index.docker.io/library/ubuntu");
- ImageName n5 = ImageName.of("alpine");
- assertThat(n1.hashCode()).isEqualTo(n2.hashCode()).isEqualTo(n3.hashCode()).isEqualTo(n4.hashCode());
- assertThat(n1).isEqualTo(n1).isEqualTo(n2).isEqualTo(n3).isEqualTo(n4).isNotEqualTo(n5);
+ ImageName n3 = ImageName.of("docker.io/ubuntu");
+ ImageName n4 = ImageName.of("docker.io/library/ubuntu");
+ ImageName n5 = ImageName.of("index.docker.io/library/ubuntu");
+ ImageName n6 = ImageName.of("alpine");
+ assertThat(n1.hashCode()).isEqualTo(n2.hashCode()).isEqualTo(n3.hashCode()).isEqualTo(n4.hashCode())
+ .isEqualTo(n5.hashCode());
+ assertThat(n1).isEqualTo(n1).isEqualTo(n2).isEqualTo(n3).isEqualTo(n4).isNotEqualTo(n6);
}
}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParserTests.java b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParserTests.java
new file mode 100644
index 0000000000..f7abe4898d
--- /dev/null
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParserTests.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2012-2020 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.buildpack.platform.docker.type;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link ImageReferenceParser}.
+ *
+ * @author Scott Frederick
+ */
+class ImageReferenceParserTests {
+
+ @Test
+ void unableToParseWithUppercaseInName() {
+ assertThatIllegalArgumentException().isThrownBy(() -> ImageReferenceParser.of("Test"))
+ .withMessageContaining("Test");
+ }
+
+ @Test
+ void parsesName() {
+ ImageReferenceParser parser = ImageReferenceParser.of("ubuntu");
+ assertThat(parser.getDomain()).isNull();
+ assertThat(parser.getName()).isEqualTo("ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesNameWithPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("library/ubuntu");
+ assertThat(parser.getDomain()).isNull();
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesNameWithLongPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("path1/path2/path3/ubuntu");
+ assertThat(parser.getDomain()).isNull();
+ assertThat(parser.getName()).isEqualTo("path1/path2/path3/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesDomainAndNameWithPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("repo.example.com/library/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("repo.example.com");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesDomainWithPortAndNameWithPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("repo.example.com:8080/library/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("repo.example.com:8080");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesSimpleDomainWithPortAndName() {
+ ImageReferenceParser parser = ImageReferenceParser.of("repo:8080/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("repo:8080");
+ assertThat(parser.getName()).isEqualTo("ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesSimpleDomainWithPortAndNameWithPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("repo:8080/library/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("repo:8080");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesLocalhostDomainAndName() {
+ ImageReferenceParser parser = ImageReferenceParser.of("localhost/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("localhost");
+ assertThat(parser.getName()).isEqualTo("ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesLocalhostDomainAndNameWithPath() {
+ ImageReferenceParser parser = ImageReferenceParser.of("localhost/library/ubuntu");
+ assertThat(parser.getDomain()).isEqualTo("localhost");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesNameAndTag() {
+ ImageReferenceParser parser = ImageReferenceParser.of("ubuntu:v1");
+ assertThat(parser.getDomain()).isNull();
+ assertThat(parser.getName()).isEqualTo("ubuntu");
+ assertThat(parser.getTag()).isEqualTo("v1");
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesNameAndDigest() {
+ ImageReferenceParser parser = ImageReferenceParser
+ .of("ubuntu@sha256:47bfdb88c3ae13e488167607973b7688f69d9e8c142c2045af343ec199649c09");
+ assertThat(parser.getDomain()).isNull();
+ assertThat(parser.getName()).isEqualTo("ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest())
+ .isEqualTo("sha256:47bfdb88c3ae13e488167607973b7688f69d9e8c142c2045af343ec199649c09");
+ }
+
+ @Test
+ void parsesReferenceWithTag() {
+ ImageReferenceParser parser = ImageReferenceParser.of("repo.example.com:8080/library/ubuntu:v1");
+ assertThat(parser.getDomain()).isEqualTo("repo.example.com:8080");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isEqualTo("v1");
+ assertThat(parser.getDigest()).isNull();
+ }
+
+ @Test
+ void parsesReferenceWithDigest() {
+ ImageReferenceParser parser = ImageReferenceParser.of(
+ "repo.example.com:8080/library/ubuntu@sha256:47bfdb88c3ae13e488167607973b7688f69d9e8c142c2045af343ec199649c09");
+ assertThat(parser.getDomain()).isEqualTo("repo.example.com:8080");
+ assertThat(parser.getName()).isEqualTo("library/ubuntu");
+ assertThat(parser.getTag()).isNull();
+ assertThat(parser.getDigest())
+ .isEqualTo("sha256:47bfdb88c3ae13e488167607973b7688f69d9e8c142c2045af343ec199649c09");
+ }
+
+}
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json
index c2a3d83152..d31e02e3d9 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json
+++ b/spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/resources/org/springframework/boot/buildpack/platform/build/image-with-run-image-digest.json
@@ -58,7 +58,7 @@
"Entrypoint": null,
"OnBuild": null,
"Labels": {
- "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run:@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}",
+ "io.buildpacks.builder.metadata": "{\"description\":\"Ubuntu bionic base image with buildpacks for Java, NodeJS and Golang\",\"buildpacks\":[{\"id\":\"org.cloudfoundry.googlestackdriver\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.springboot\",\"version\":\"v1.2.13\"},{\"id\":\"org.cloudfoundry.debug\",\"version\":\"v1.2.11\"},{\"id\":\"org.cloudfoundry.tomcat\",\"version\":\"v1.3.18\"},{\"id\":\"org.cloudfoundry.go\",\"version\":\"v0.0.4\"},{\"id\":\"org.cloudfoundry.openjdk\",\"version\":\"v1.2.14\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"version\":\"v1.2.15\"},{\"id\":\"org.cloudfoundry.jvmapplication\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"version\":\"v1.1.11\"},{\"id\":\"org.cloudfoundry.archiveexpanding\",\"version\":\"v1.0.102\"},{\"id\":\"org.cloudfoundry.jmx\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.nodejs\",\"version\":\"v2.0.8\"},{\"id\":\"org.cloudfoundry.jdbc\",\"version\":\"v1.1.14\"},{\"id\":\"org.cloudfoundry.procfile\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dotnet-core\",\"version\":\"v0.0.6\"},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.distzip\",\"version\":\"v1.1.12\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"},{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\"},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\"},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\"},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\"},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\"},{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\"}],\"stack\":{\"runImage\":{\"image\":\"cloudfoundry/run@sha256:6e9f67fa63b0323e9a1e587fd71c561ba48a034504fb804fd26fd8800039835d\",\"mirrors\":null}},\"lifecycle\":{\"version\":\"0.7.2\",\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}},\"createdBy\":{\"name\":\"Pack CLI\",\"version\":\"v0.9.0 (git sha: d42c384a39f367588f2653f2a99702db910e5ad7)\"}}",
"io.buildpacks.buildpack.layers": "{\"org.cloudfoundry.archiveexpanding\":{\"v1.0.102\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:73b1a8ac1f7fca3d545766ce7fd3c56b40a63724ab78e464d71a29da0c6ac31c\"}},\"org.cloudfoundry.azureapplicationinsights\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0a2f7c467efbb8b1ac222f09013b88b68f3c117ec6b6e9dc95564be50f271ab\"}},\"org.cloudfoundry.buildsystem\":{\"v1.2.15\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:3f50d3a0e1a969a9606b59e5295842d731e425108cb349ce6c69a5b30ea1bab9\"}},\"org.cloudfoundry.debug\":{\"v1.2.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:04559213a01cfac69a8d6a6facb58b8681666525c74f605207c40a61a0f4c9b7\"}},\"org.cloudfoundry.dep\":{\"0.0.101\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:6aae3a2d671d369eec34dc9146ef267d06c87461f271fbfbe9136775ecf5dfb8\"}},\"org.cloudfoundry.distzip\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a0715e661e13d7d3ded5bdc068edd01e5b3aa0e2805152f4c8a1428b4e0673df\"}},\"org.cloudfoundry.dotnet-core\":{\"v0.0.6\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.158\",\"optional\":true},{\"id\":\"org.cloudfoundry.icu\",\"version\":\"0.0.43\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-runtime\",\"version\":\"0.0.127\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-aspnet\",\"version\":\"0.0.118\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-sdk\",\"version\":\"0.0.122\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-build\",\"version\":\"0.0.68\",\"optional\":true},{\"id\":\"org.cloudfoundry.dotnet-core-conf\",\"version\":\"0.0.115\"}]}],\"layerDiffID\":\"sha256:aa0effdf787ecfe74d60d6771006717fd1a9ce1ce0a8161624baa61b68120357\"}},\"org.cloudfoundry.dotnet-core-aspnet\":{\"0.0.118\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:a06615b5adc1a3afb7abd524e82f6900a28910927fcf0d4e9b85fd1fcbeb53ad\"}},\"org.cloudfoundry.dotnet-core-build\":{\"0.0.68\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:26d6f1e76275d17860005f7ab9b74fdd2283fcf84e0446bd88d49a6b4e9609f9\"}},\"org.cloudfoundry.dotnet-core-conf\":{\"0.0.115\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:55f7c052cf70c8ca01b8e241c0c5c8a9675599d4904c69bfb961a472e246238d\"}},\"org.cloudfoundry.dotnet-core-runtime\":{\"0.0.127\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:d9958b816a9ad179fca8c18d17c07e9814b152d461c685e1443bec6f990ab990\"}},\"org.cloudfoundry.dotnet-core-sdk\":{\"0.0.122\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:52142799a4b687fe6e5cf397c41064499ea6cc554b94904d46c1acade998e11f\"}},\"org.cloudfoundry.go\":{\"v0.0.4\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.go-mod\",\"version\":\"0.0.89\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go-compiler\",\"version\":\"0.0.105\"},{\"id\":\"org.cloudfoundry.dep\",\"version\":\"0.0.101\"}]}],\"layerDiffID\":\"sha256:352a299d6af4773322ed3643d8f98b01aad6f15d838d1852e52a0a3ca56c6efb\"}},\"org.cloudfoundry.go-compiler\":{\"0.0.105\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:cb21f14e306d94e437c5418d275bcc6efcea6bc9b3d26a400bdf54fa62242c24\"}},\"org.cloudfoundry.go-mod\":{\"0.0.89\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.tiny\"}],\"layerDiffID\":\"sha256:c9da8171f5ca048109ffba5e940e3a7d2db567eda281f92b0eb483173df06add\"}},\"org.cloudfoundry.googlestackdriver\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:ff29efc56c31eeccc79a33c6e4abd7b1ab3547d95e1cf83974af65a493576c41\"}},\"org.cloudfoundry.icu\":{\"0.0.43\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:48063dcdd043f9c88604d10fe9542569be8f8111d46806c96b08d77763ffa347\"}},\"org.cloudfoundry.jdbc\":{\"v1.1.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:a9c9bbbd69c212b7ab3c1a7f03011ccc4d99a6fce1bf1c785325c7bcad789e62\"}},\"org.cloudfoundry.jmx\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:da62dec6eb4ed884952a1b867fd89e3bfe3c510e5c849cc0ac7050ff867a2469\"}},\"org.cloudfoundry.jvmapplication\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c10732392b97c121a78a5f20201c2a5e834a2b8677196cdd49260a489a54fd22\"}},\"org.cloudfoundry.node-engine\":{\"0.0.158\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:70cf83155575fdb607f23ace41e31b1d5cb1c24dbbbf56f71c383b583724d339\"},\"0.0.163\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:11486cb955594f9d43909b60f94209bb6854f502a5a093207b657afbaa38a777\"}},\"org.cloudfoundry.nodejs\":{\"v2.0.8\":{\"api\":\"0.2\",\"order\":[{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.yarn-install\",\"version\":\"0.1.10\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.node-engine\",\"version\":\"0.0.163\"},{\"id\":\"org.cloudfoundry.npm\",\"version\":\"0.1.3\"}]}],\"layerDiffID\":\"sha256:76fe727e4aafc7f56f01282296ab736521c38b9d19c1ae5ebb193f9cd55fa109\"}},\"org.cloudfoundry.npm\":{\"0.1.3\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:243bbd007cb0ee99b704bfe0cf62e1301baa4095ab4c39b01293787a0e4234f1\"}},\"org.cloudfoundry.openjdk\":{\"v1.2.14\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:486b2abf434bb90cf04bab74f2f8bd2eb488ff90632b56eac4bddcbbf02e8151\"}},\"org.cloudfoundry.procfile\":{\"v1.1.12\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b7b78159dfdaa0dd484c58652e02fa6b755abfd0adb88f106d16178144e46f33\"}},\"org.cloudfoundry.springautoreconfiguration\":{\"v1.1.11\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:c185540c10fea822c6db1b987fcfe22b55a4662648124b98475db4c9dcddb2ab\"}},\"org.cloudfoundry.springboot\":{\"v1.2.13\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:b87e68574cc7dccbe974fa760702ef650711036bf144fd9da1f3a2d8f6ac335f\"}},\"org.cloudfoundry.tomcat\":{\"v1.3.18\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"io.buildpacks.stacks.bionic\"},{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"}],\"layerDiffID\":\"sha256:467c0082c57b80b48487a9b8429887c0744ddc5b066b3f7678866bde89b78ab2\"}},\"org.cloudfoundry.yarn-install\":{\"0.1.10\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"org.cloudfoundry.stacks.cflinuxfs3\"},{\"id\":\"io.buildpacks.stacks.bionic\"}],\"layerDiffID\":\"sha256:6aefa0ba7ce01584b4a531b18e36470298cee3b30ecae0e0c64b532a5cebd6e7\"}}}",
"io.buildpacks.buildpack.order": "[{\"group\":[{\"id\":\"org.cloudfoundry.openjdk\"},{\"id\":\"org.cloudfoundry.buildsystem\",\"optional\":true},{\"id\":\"org.cloudfoundry.jvmapplication\"},{\"id\":\"org.cloudfoundry.tomcat\",\"optional\":true},{\"id\":\"org.cloudfoundry.springboot\",\"optional\":true},{\"id\":\"org.cloudfoundry.distzip\",\"optional\":true},{\"id\":\"org.cloudfoundry.procfile\",\"optional\":true},{\"id\":\"org.cloudfoundry.azureapplicationinsights\",\"optional\":true},{\"id\":\"org.cloudfoundry.debug\",\"optional\":true},{\"id\":\"org.cloudfoundry.googlestackdriver\",\"optional\":true},{\"id\":\"org.cloudfoundry.jdbc\",\"optional\":true},{\"id\":\"org.cloudfoundry.jmx\",\"optional\":true},{\"id\":\"org.cloudfoundry.springautoreconfiguration\",\"optional\":true}]},{\"group\":[{\"id\":\"org.cloudfoundry.nodejs\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.go\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.dotnet-core\"}]},{\"group\":[{\"id\":\"org.cloudfoundry.procfile\"}]}]",
"io.buildpacks.stack.id": "io.buildpacks.stacks.bionic",
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java
index 396ea98a8a..e3486bab96 100644
--- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java
+++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java
@@ -135,6 +135,16 @@ class BootBuildImageIntegrationTests {
assertThat(result.getOutput()).containsPattern("Builder lifecycle '.*' failed with status code");
}
+ @TestTemplate
+ void failsWithInvalidImageName() {
+ writeMainClass();
+ writeLongNameResource();
+ BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--imageName=example/Invalid-Image-Name");
+ assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
+ assertThat(result.getOutput()).containsPattern("Unable to parse image reference")
+ .containsPattern("example/Invalid-Image-Name");
+ }
+
private void writeMainClass() {
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
examplePackage.mkdirs();