From 28643e4d2d014e1387bff713e3d3bc4722e09242 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 17 Jun 2020 12:21:54 -0500 Subject: [PATCH] Validate image references before passing to CNB builder Prior to this commit, an image name or run image name derived from the project name or provided by the user would be passed to the CNB builder without validation by the Maven plugin build-image goal or Gradle plugin bootBuildImage task. This could lead to error messages from the plugins that are difficult to understand and diagnose. This commit makes parsing of the image names more strict, based on the grammar implemented by the Docker go library. This provides validation of the image names before passing them to the builder, with a more descriptive error message when parsing and validation fails. Fixes gh-21495 --- .../platform/docker/type/ImageName.java | 44 +++-- .../platform/docker/type/ImageReference.java | 28 +-- .../docker/type/ImageReferenceParser.java | 159 ++++++++++++++++++ .../platform/build/BuildRequestTests.java | 8 +- .../platform/build/BuilderTests.java | 2 +- .../platform/docker/type/ImageNameTests.java | 13 +- .../type/ImageReferenceParserTests.java | 158 +++++++++++++++++ .../build/image-with-run-image-digest.json | 2 +- .../BootBuildImageIntegrationTests.java | 10 ++ 9 files changed, 367 insertions(+), 57 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/main/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParser.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-buildpack-platform/src/test/java/org/springframework/boot/buildpack/platform/docker/type/ImageReferenceParserTests.java 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();