From 9b194da35120ec94014f8ce65cc81ab96c38b266 Mon Sep 17 00:00:00 2001 From: Marc Bruggmann Date: Tue, 21 Jan 2020 16:58:08 +0100 Subject: [PATCH] Write out the image metadata to a file after build (#2227) --- .../cloud/tools/jib/api/ImageReference.java | 20 ++++ .../cloud/tools/jib/api/JibContainer.java | 48 ++++++++- .../tools/jib/api/JibContainerBuilder.java | 4 +- .../tools/jib/api/ImageReferenceTest.java | 36 +++++++ .../cloud/tools/jib/api/JibContainerTest.java | 62 ++++++++++-- .../jib/gradle/GradleRawConfiguration.java | 5 + .../jib/gradle/OutputPathsParameters.java | 16 +++ .../gradle/GradleRawConfigurationTest.java | 2 + jib-maven-plugin/README.md | 1 + .../jib/maven/JibPluginConfiguration.java | 10 ++ .../jib/maven/MavenRawConfiguration.java | 5 + .../jib/maven/JibPluginConfigurationTest.java | 6 ++ .../jib/maven/MavenRawConfigurationTest.java | 3 + .../plugins/common/ImageMetadataOutput.java | 97 +++++++++++++++++++ .../jib/plugins/common/JibBuildRunner.java | 18 ++++ .../common/PluginConfigurationProcessor.java | 9 +- .../jib/plugins/common/PropertyNames.java | 1 + .../jib/plugins/common/RawConfiguration.java | 2 + .../common/ImageMetadataOutputTest.java | 55 +++++++++++ .../plugins/common/JibBuildRunnerTest.java | 35 +++++++ 20 files changed, 417 insertions(+), 18 deletions(-) create mode 100644 jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java create mode 100644 jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageReference.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageReference.java index 46c23629e6..2f2c010085 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageReference.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/ImageReference.java @@ -19,6 +19,7 @@ import com.google.cloud.tools.jib.registry.RegistryAliasGroup; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -347,4 +348,23 @@ public String toString() { public String toStringWithTag() { return toString() + (usesDefaultTag() ? ":" + DEFAULT_TAG : ""); } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof ImageReference)) { + return false; + } + ImageReference otherImageReference = (ImageReference) other; + return registry.equals(otherImageReference.registry) + && repository.equals(otherImageReference.repository) + && tag.equals(otherImageReference.tag); + } + + @Override + public int hashCode() { + return Objects.hash(registry, repository, tag); + } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java index 5818663a7f..bf41b2eb7e 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainer.java @@ -16,17 +16,47 @@ package com.google.cloud.tools.jib.api; +import com.google.cloud.tools.jib.builder.steps.BuildResult; +import com.google.cloud.tools.jib.configuration.BuildContext; +import com.google.common.annotations.VisibleForTesting; import java.util.Objects; +import java.util.Set; /** The container built by Jib. */ public class JibContainer { + private final ImageReference targetImage; private final DescriptorDigest imageDigest; private final DescriptorDigest imageId; + private final Set tags; - JibContainer(DescriptorDigest imageDigest, DescriptorDigest imageId) { + @VisibleForTesting + JibContainer( + ImageReference targetImage, + DescriptorDigest imageDigest, + DescriptorDigest imageId, + Set tags) { + this.targetImage = targetImage; this.imageDigest = imageDigest; this.imageId = imageId; + this.tags = tags; + } + + static JibContainer from(BuildContext buildContext, BuildResult buildResult) { + ImageReference targetImage = buildContext.getTargetImageConfiguration().getImage(); + DescriptorDigest imageDigest = buildResult.getImageDigest(); + DescriptorDigest imageId = buildResult.getImageId(); + Set tags = buildContext.getAllTargetImageTags(); + return new JibContainer(targetImage, imageDigest, imageId, tags); + } + + /** + * Get the target image that was built. + * + * @return the target image reference. + */ + public ImageReference getTargetImage() { + return targetImage; } /** @@ -48,9 +78,18 @@ public DescriptorDigest getImageId() { return imageId; } + /** + * Get the tags applied to the container. + * + * @return the set of all tags + */ + public Set getTags() { + return tags; + } + @Override public int hashCode() { - return Objects.hash(imageDigest, imageId); + return Objects.hash(targetImage, imageDigest, imageId, tags); } @Override @@ -62,6 +101,9 @@ public boolean equals(Object other) { return false; } JibContainer otherContainer = (JibContainer) other; - return imageDigest.equals(otherContainer.imageDigest) && imageId.equals(otherContainer.imageId); + return targetImage.equals(otherContainer.targetImage) + && imageDigest.equals(otherContainer.imageDigest) + && imageId.equals(otherContainer.imageId) + && tags.equals(otherContainer.tags); } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java index af058fe109..ad0182cdf3 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/JibContainerBuilder.java @@ -476,8 +476,8 @@ public JibContainer containerize(Containerizer containerizer) buildContext.getEventHandlers(), containerizer.getDescription())) { logSources(buildContext.getEventHandlers()); - BuildResult result = containerizer.run(buildContext); - return new JibContainer(result.getImageDigest(), result.getImageId()); + BuildResult buildResult = containerizer.run(buildContext); + return JibContainer.from(buildContext, buildResult); } catch (ExecutionException ex) { // If an ExecutionException occurs, re-throw the cause to be more easily handled by the user diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/ImageReferenceTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/ImageReferenceTest.java index ef90c027a1..34247c8b39 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/api/ImageReferenceTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/ImageReferenceTest.java @@ -202,6 +202,42 @@ public void testGetRegistry() { Assert.assertEquals("gcr.io", ImageReference.of("gcr.io", "someimage", null).getRegistry()); } + @Test + public void testEquality() throws InvalidImageReferenceException { + ImageReference image1 = ImageReference.parse("gcr.io/project/image:tag"); + ImageReference image2 = ImageReference.parse("gcr.io/project/image:tag"); + + Assert.assertEquals(image1, image2); + Assert.assertEquals(image1.hashCode(), image2.hashCode()); + } + + @Test + public void testEquality_differentRegistry() throws InvalidImageReferenceException { + ImageReference image1 = ImageReference.parse("gcr.io/project/image:tag"); + ImageReference image2 = ImageReference.parse("registry-1.docker.io/project/image:tag"); + + Assert.assertNotEquals(image1, image2); + Assert.assertNotEquals(image1.hashCode(), image2.hashCode()); + } + + @Test + public void testEquality_differentRepository() throws InvalidImageReferenceException { + ImageReference image1 = ImageReference.parse("gcr.io/project/image:tag"); + ImageReference image2 = ImageReference.parse("gcr.io/project2/image:tag"); + + Assert.assertNotEquals(image1, image2); + Assert.assertNotEquals(image1.hashCode(), image2.hashCode()); + } + + @Test + public void testEquality_differentTag() throws InvalidImageReferenceException { + ImageReference image1 = ImageReference.parse("gcr.io/project/image:tag1"); + ImageReference image2 = ImageReference.parse("gcr.io/project/image:tag2"); + + Assert.assertNotEquals(image1, image2); + Assert.assertNotEquals(image1.hashCode(), image2.hashCode()); + } + private void verifyParse(String registry, String repository, String tagSeparator, String tag) throws InvalidImageReferenceException { // Gets the expected parsed components. diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java index 52393ab304..607a3e9e95 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/api/JibContainerTest.java @@ -16,7 +16,9 @@ package com.google.cloud.tools.jib.api; +import com.google.common.collect.ImmutableSet; import java.security.DigestException; +import java.util.Set; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -28,39 +30,79 @@ public class JibContainerTest { @Rule public TemporaryFolder temporaryDirectory = new TemporaryFolder(); + private ImageReference targetImage1; + private ImageReference targetImage2; private DescriptorDigest digest1; private DescriptorDigest digest2; - private DescriptorDigest digest3; + private Set tags1; + private Set tags2; @Before - public void setUp() throws DigestException { + public void setUp() throws DigestException, InvalidImageReferenceException { + targetImage1 = ImageReference.parse("gcr.io/project/image:tag"); + targetImage2 = ImageReference.parse("gcr.io/project/image:tag2"); digest1 = DescriptorDigest.fromDigest( "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"); digest2 = DescriptorDigest.fromDigest( "sha256:9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"); - digest3 = - DescriptorDigest.fromDigest( - "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"); + tags1 = ImmutableSet.of("latest", "custom-tag"); + tags2 = ImmutableSet.of("latest"); } @Test public void testCreation() { - JibContainer container = new JibContainer(digest1, digest2); + JibContainer container = new JibContainer(targetImage1, digest1, digest2, tags1); + Assert.assertEquals(targetImage1, container.getTargetImage()); Assert.assertEquals(digest1, container.getDigest()); Assert.assertEquals(digest2, container.getImageId()); + Assert.assertEquals(tags1, container.getTags()); } @Test public void testEquality() { - JibContainer container1 = new JibContainer(digest1, digest2); - JibContainer container2 = new JibContainer(digest1, digest2); - JibContainer container3 = new JibContainer(digest2, digest3); + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1); Assert.assertEquals(container1, container2); Assert.assertEquals(container1.hashCode(), container2.hashCode()); - Assert.assertNotEquals(container1, container3); + } + + @Test + public void testEquality_differentTargetImage() { + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container2 = new JibContainer(targetImage2, digest1, digest2, tags1); + + Assert.assertNotEquals(container1, container2); + Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); + } + + @Test + public void testEquality_differentImageDigest() { + JibContainer container1 = new JibContainer(targetImage1, digest1, digest2, tags1); + JibContainer container2 = new JibContainer(targetImage1, digest2, digest2, tags1); + + Assert.assertNotEquals(container1, container2); + Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); + } + + @Test + public void testEquality_differentImageId() { + JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest2, tags1); + + Assert.assertNotEquals(container1, container2); + Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); + } + + @Test + public void testEquality_differentTags() { + JibContainer container1 = new JibContainer(targetImage1, digest1, digest1, tags1); + JibContainer container2 = new JibContainer(targetImage1, digest1, digest1, tags2); + + Assert.assertNotEquals(container1, container2); + Assert.assertNotEquals(container1.hashCode(), container2.hashCode()); } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java index ad1aa28fdf..6dc3a59b7f 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/GradleRawConfiguration.java @@ -195,4 +195,9 @@ public Path getDigestOutputPath() { public Path getImageIdOutputPath() { return jibExtension.getOutputPaths().getImageIdPath(); } + + @Override + public Path getImageJsonOutputPath() { + return jibExtension.getOutputPaths().getImageJsonPath(); + } } diff --git a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/OutputPathsParameters.java b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/OutputPathsParameters.java index 34f862ca5d..46b4b6e608 100644 --- a/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/OutputPathsParameters.java +++ b/jib-gradle-plugin/src/main/java/com/google/cloud/tools/jib/gradle/OutputPathsParameters.java @@ -32,12 +32,14 @@ public class OutputPathsParameters { private Path digest; private Path tar; private Path imageId; + private Path imageJson; @Inject public OutputPathsParameters(Project project) { this.project = project; digest = project.getBuildDir().toPath().resolve("jib-image.digest"); imageId = project.getBuildDir().toPath().resolve("jib-image.id"); + imageJson = project.getBuildDir().toPath().resolve("jib-image.json"); tar = project.getBuildDir().toPath().resolve("jib-image.tar"); } @@ -69,6 +71,20 @@ public void setImageId(String id) { this.imageId = Paths.get(id); } + @Input + public String getImageJson() { + return getRelativeToProjectRoot(imageJson, PropertyNames.OUTPUT_PATHS_IMAGE_JSON).toString(); + } + + @Internal + Path getImageJsonPath() { + return getRelativeToProjectRoot(imageJson, PropertyNames.OUTPUT_PATHS_IMAGE_JSON); + } + + public void setImageJson(String imageJson) { + this.imageJson = Paths.get(imageJson); + } + @Input public String getTar() { return getRelativeToProjectRoot(tar, PropertyNames.OUTPUT_PATHS_TAR).toString(); diff --git a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java index eeb01f18f4..001b6f848d 100644 --- a/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java +++ b/jib-gradle-plugin/src/test/java/com/google/cloud/tools/jib/gradle/GradleRawConfigurationTest.java @@ -80,6 +80,7 @@ public void testGetters() { Mockito.when(outputPathsParameters.getDigestPath()).thenReturn(Paths.get("digest/path")); Mockito.when(outputPathsParameters.getImageIdPath()).thenReturn(Paths.get("id/path")); + Mockito.when(outputPathsParameters.getImageJsonPath()).thenReturn(Paths.get("json/path")); Mockito.when(outputPathsParameters.getTarPath()).thenReturn(Paths.get("tar/path")); GradleRawConfiguration rawConfiguration = new GradleRawConfiguration(jibExtension); @@ -115,6 +116,7 @@ public void testGetters() { rawConfiguration.getDockerEnvironment()); Assert.assertEquals(Paths.get("digest/path"), rawConfiguration.getDigestOutputPath()); Assert.assertEquals(Paths.get("id/path"), rawConfiguration.getImageIdOutputPath()); + Assert.assertEquals(Paths.get("json/path"), rawConfiguration.getImageJsonOutputPath()); Assert.assertEquals(Paths.get("tar/path"), rawConfiguration.getTarOutputPath()); } } diff --git a/jib-maven-plugin/README.md b/jib-maven-plugin/README.md index 6875b7b5b7..db5032fa6f 100644 --- a/jib-maven-plugin/README.md +++ b/jib-maven-plugin/README.md @@ -288,6 +288,7 @@ Property | Type | Default | Description `tar` | string | `(project-dir)/target/jib-image.tar` | The path of the tarball generated by `jib:buildTar`. Relative paths are resolved relative to the project root. `digest` | string | `(project-dir)/target/jib-image.digest` | The path of the image digest written out during the build. Relative paths are resolved relative to the project root. `imageId` | string | `(project-dir)/target/jib-image.id` | The path of the image ID written out during the build. Relative paths are resolved relative to the project root. +`imageJson` | string | `(project-dir)/target/jib-image.json` | The path of the image metadata json file written out during the build. Relative paths are resolved relative to the project root. `dockerClient` is an object used to configure Docker when building to/from the Docker daemon. It has the following properties: diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java index ee1e1fcf84..8928c5fa74 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/JibPluginConfiguration.java @@ -215,6 +215,8 @@ public static class OutputPathsParameters { @Nullable @Parameter private File digest; @Nullable @Parameter private File imageId; + + @Nullable @Parameter private File imageJson; } @Nullable @@ -625,6 +627,14 @@ Path getImageIdOutputPath() { return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_IMAGE_ID); } + Path getImageJsonOutputPath() { + Path configuredPath = + outputPaths.imageJson == null + ? Paths.get(getProject().getBuild().getDirectory()).resolve("jib-image.json") + : outputPaths.imageJson.toPath(); + return getRelativeToProjectRoot(configuredPath, PropertyNames.OUTPUT_PATHS_IMAGE_JSON); + } + private Path getRelativeToProjectRoot(Path configuration, String propertyName) { String property = getProperty(propertyName); Path path = property != null ? Paths.get(property) : configuration; diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java index 292f51f318..269ff6ac37 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/MavenRawConfiguration.java @@ -200,4 +200,9 @@ public Path getDigestOutputPath() { public Path getImageIdOutputPath() { return jibPluginConfiguration.getImageIdOutputPath(); } + + @Override + public Path getImageJsonOutputPath() { + return jibPluginConfiguration.getImageJsonOutputPath(); + } } diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java index 8e676aa9dc..b0d7850e96 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/JibPluginConfigurationTest.java @@ -169,6 +169,10 @@ public void testSystemProperties() { sessionProperties.put("jib.outputPaths.imageId", "id/path"); Assert.assertEquals( Paths.get("/repository/project/id/path"), testPluginConfiguration.getImageIdOutputPath()); + sessionProperties.put("jib.outputPaths.imageJson", "json/path"); + Assert.assertEquals( + Paths.get("/repository/project/json/path"), + testPluginConfiguration.getImageJsonOutputPath()); sessionProperties.put("jib.outputPaths.tar", "tar/path"); Assert.assertEquals( Paths.get("/repository/project/tar/path"), testPluginConfiguration.getTarOutputPath()); @@ -258,6 +262,8 @@ public void testPomProperties() { Assert.assertEquals(Paths.get("/digest/path"), testPluginConfiguration.getDigestOutputPath()); project.getProperties().setProperty("jib.outputPaths.imageId", "/id/path"); Assert.assertEquals(Paths.get("/id/path"), testPluginConfiguration.getImageIdOutputPath()); + project.getProperties().setProperty("jib.outputPaths.imageJson", "/json/path"); + Assert.assertEquals(Paths.get("/json/path"), testPluginConfiguration.getImageJsonOutputPath()); project.getProperties().setProperty("jib.outputPaths.tar", "tar/path"); Assert.assertEquals( Paths.get("/repository/project/tar/path"), testPluginConfiguration.getTarOutputPath()); diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java index a1e1d4ee8e..89f791b1d4 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/MavenRawConfigurationTest.java @@ -83,6 +83,8 @@ public void testGetters() { .thenReturn(new HashMap<>(ImmutableMap.of("docker", "client"))); Mockito.when(jibPluginConfiguration.getDigestOutputPath()).thenReturn(Paths.get("digest/path")); Mockito.when(jibPluginConfiguration.getImageIdOutputPath()).thenReturn(Paths.get("id/path")); + Mockito.when(jibPluginConfiguration.getImageJsonOutputPath()) + .thenReturn(Paths.get("json/path")); Mockito.when(jibPluginConfiguration.getTarOutputPath()).thenReturn(Paths.get("tar/path")); MavenRawConfiguration rawConfiguration = new MavenRawConfiguration(jibPluginConfiguration); @@ -118,6 +120,7 @@ public void testGetters() { rawConfiguration.getDockerEnvironment()); Assert.assertEquals(Paths.get("digest/path"), jibPluginConfiguration.getDigestOutputPath()); Assert.assertEquals(Paths.get("id/path"), jibPluginConfiguration.getImageIdOutputPath()); + Assert.assertEquals(Paths.get("json/path"), jibPluginConfiguration.getImageJsonOutputPath()); Assert.assertEquals(Paths.get("tar/path"), jibPluginConfiguration.getTarOutputPath()); Mockito.verifyNoMoreInteractions(eventHandlers); diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java new file mode 100644 index 0000000000..786362e4a8 --- /dev/null +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutput.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC. + * + * 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 + * + * http://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 com.google.cloud.tools.jib.plugins.common; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.cloud.tools.jib.api.JibContainer; +import com.google.cloud.tools.jib.json.JsonTemplate; +import com.google.cloud.tools.jib.json.JsonTemplateMapper; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.List; + +/** + * Builds a JSON string containing metadata about a {@link JibContainer} from a build. + * + *

Example: + * + *

{@code
+ * {
+ *   "image": "gcr.io/project/image:tag",
+ *   "imageId": "sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9",
+ *   "imageDigest": "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6f",
+ *   "tags": ["latest", "tag"]
+ * }
+ * }
+ */ +public class ImageMetadataOutput implements JsonTemplate { + + private final String image; + private final String imageId; + private final String imageDigest; + private final List tags; + + @JsonCreator + ImageMetadataOutput( + @JsonProperty(value = "image", required = true) String image, + @JsonProperty(value = "imageId", required = true) String imageId, + @JsonProperty(value = "imageDigest", required = true) String imageDigest, + @JsonProperty(value = "tags", required = true) List tags) { + this.image = image; + this.imageId = imageId; + this.imageDigest = imageDigest; + this.tags = tags; + } + + @VisibleForTesting + static ImageMetadataOutput fromJson(String json) throws IOException { + return JsonTemplateMapper.readJson(json, ImageMetadataOutput.class); + } + + public static ImageMetadataOutput fromJibContainer(JibContainer jibContainer) { + String image = jibContainer.getTargetImage().toString(); + String imageId = jibContainer.getImageId().toString(); + String imageDigest = jibContainer.getDigest().toString(); + + // Make sure tags always appear in a predictable way, by sorting them into a list + List tags = ImmutableList.sortedCopyOf(jibContainer.getTags()); + + return new ImageMetadataOutput(image, imageId, imageDigest, tags); + } + + public String getImage() { + return image; + } + + public String getImageId() { + return imageId; + } + + public String getImageDigest() { + return imageDigest; + } + + public List getTags() { + return tags; + } + + public String toJson() throws IOException { + return JsonTemplateMapper.toUtf8String(this); + } +} diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java index 0ca73689ad..427ecc1991 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java @@ -192,6 +192,7 @@ private static void handleRegistryUnauthorizedException( private final HelpfulSuggestions helpfulSuggestions; @Nullable private Path imageDigestOutputPath; @Nullable private Path imageIdOutputPath; + @Nullable private Path imageJsonOutputPath; @VisibleForTesting JibBuildRunner( @@ -237,6 +238,11 @@ public JibContainer runBuild() String imageId = jibContainer.getImageId().toString(); Files.write(imageIdOutputPath, imageId.getBytes(StandardCharsets.UTF_8)); } + if (imageJsonOutputPath != null) { + ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJibContainer(jibContainer); + String imageJson = metadataOutput.toJson(); + Files.write(imageJsonOutputPath, imageJson.getBytes(StandardCharsets.UTF_8)); + } return jibContainer; @@ -304,4 +310,16 @@ public JibBuildRunner writeImageId(@Nullable Path imageIdOutputPath) { this.imageIdOutputPath = imageIdOutputPath; return this; } + + /** + * Set the location where the image metadata json will be saved. If {@code null} then the metadata + * is not saved. + * + * @param imageJsonOutputPath the location to write the image metadata, or {@code null} to skip + * @return this + */ + public JibBuildRunner writeImageJson(@Nullable Path imageJsonOutputPath) { + this.imageJsonOutputPath = imageJsonOutputPath; + return this; + } } diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java index 778a839fdd..a68460e700 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PluginConfigurationProcessor.java @@ -103,7 +103,8 @@ public static JibBuildRunner createJibBuildRunnerForDockerDaemonImage( targetImageReference, rawConfiguration.getToTags()) .writeImageDigest(rawConfiguration.getDigestOutputPath()) - .writeImageId(rawConfiguration.getImageIdOutputPath()); + .writeImageId(rawConfiguration.getImageIdOutputPath()) + .writeImageJson(rawConfiguration.getImageJsonOutputPath()); } public static JibBuildRunner createJibBuildRunnerForTarImage( @@ -136,7 +137,8 @@ public static JibBuildRunner createJibBuildRunnerForTarImage( helpfulSuggestions, rawConfiguration.getTarOutputPath()) .writeImageDigest(rawConfiguration.getDigestOutputPath()) - .writeImageId(rawConfiguration.getImageIdOutputPath()); + .writeImageId(rawConfiguration.getImageIdOutputPath()) + .writeImageJson(rawConfiguration.getImageJsonOutputPath()); } public static JibBuildRunner createJibBuildRunnerForRegistryImage( @@ -188,7 +190,8 @@ public static JibBuildRunner createJibBuildRunnerForRegistryImage( targetImageReference, rawConfiguration.getToTags()) .writeImageDigest(rawConfiguration.getDigestOutputPath()) - .writeImageId(rawConfiguration.getImageIdOutputPath()); + .writeImageId(rawConfiguration.getImageIdOutputPath()) + .writeImageJson(rawConfiguration.getImageJsonOutputPath()); } public static String getSkaffoldSyncMap( diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java index fd15d8f30e..b68a777e02 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/PropertyNames.java @@ -55,6 +55,7 @@ public class PropertyNames { public static final String DOCKER_CLIENT_ENVIRONMENT = "jib.dockerClient.environment"; public static final String OUTPUT_PATHS_DIGEST = "jib.outputPaths.digest"; public static final String OUTPUT_PATHS_IMAGE_ID = "jib.outputPaths.imageId"; + public static final String OUTPUT_PATHS_IMAGE_JSON = "jib.outputPaths.imageJson"; public static final String OUTPUT_PATHS_TAR = "jib.outputPaths.tar"; public static final String CONTAINERIZING_MODE = "jib.containerizingMode"; public static final String SKIP = "jib.skip"; diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java index 01a198f135..1ab721a5b3 100644 --- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java +++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/RawConfiguration.java @@ -94,4 +94,6 @@ public interface RawConfiguration { Path getDigestOutputPath(); Path getImageIdOutputPath(); + + Path getImageJsonOutputPath(); } diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java new file mode 100644 index 0000000000..156c4dd638 --- /dev/null +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/ImageMetadataOutputTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Google LLC. + * + * 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 + * + * http://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 com.google.cloud.tools.jib.plugins.common; + +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; + +public class ImageMetadataOutputTest { + + private static final String TEST_JSON = + "{\"image\":" + + "\"gcr.io/project/image:tag\"," + + "\"imageId\":" + + "\"sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9\"," + + "\"imageDigest\":" + + "\"sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc\"," + + "\"tags\":[\"latest\",\"tag\"]" + + "}"; + + @Test + public void testFromJson() throws IOException { + ImageMetadataOutput output = ImageMetadataOutput.fromJson(TEST_JSON); + Assert.assertEquals("gcr.io/project/image:tag", output.getImage()); + Assert.assertEquals( + "sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9", + output.getImageId()); + Assert.assertEquals( + "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc", + output.getImageDigest()); + + Assert.assertEquals(ImmutableList.of("latest", "tag"), output.getTags()); + } + + @Test + public void testToJson() throws IOException { + ImageMetadataOutput output = ImageMetadataOutput.fromJson(TEST_JSON); + Assert.assertEquals(TEST_JSON, output.toJson()); + } +} diff --git a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java index aa8e4c16ec..447e004d7d 100644 --- a/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java +++ b/jib-plugins-common/src/test/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunnerTest.java @@ -20,13 +20,21 @@ import com.google.api.client.http.HttpStatusCodes; import com.google.cloud.tools.jib.api.CacheDirectoryCreationException; import com.google.cloud.tools.jib.api.Containerizer; +import com.google.cloud.tools.jib.api.DescriptorDigest; +import com.google.cloud.tools.jib.api.ImageReference; import com.google.cloud.tools.jib.api.InsecureRegistryException; +import com.google.cloud.tools.jib.api.JibContainer; import com.google.cloud.tools.jib.api.JibContainerBuilder; import com.google.cloud.tools.jib.api.RegistryException; import com.google.cloud.tools.jib.api.RegistryUnauthorizedException; import com.google.cloud.tools.jib.registry.RegistryCredentialsNotSentException; +import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; import java.util.concurrent.ExecutionException; import org.apache.http.conn.HttpHostConnectException; import org.junit.Assert; @@ -50,6 +58,7 @@ public class JibBuildRunnerTest { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Mock private JibContainerBuilder mockJibContainerBuilder; + @Mock private JibContainer mockJibContainer; @Mock private Containerizer mockContainerizer; @Mock private RegistryUnauthorizedException mockRegistryUnauthorizedException; @Mock private RegistryCredentialsNotSentException mockRegistryCredentialsNotSentException; @@ -215,4 +224,30 @@ public void testBuildImage_other() Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.none(), ex.getMessage()); } } + + @Test + public void testBuildImage_writesImageJson() throws Exception { + final ImageReference targetImageReference = ImageReference.parse("gcr.io/distroless/java:11"); + final String imageId = + "sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9"; + final String digest = "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc"; + final Set tags = ImmutableSet.of("latest", "0.1.41-69d10e-20200116T101403"); + + final Path outputPath = temporaryFolder.newFile("jib-image.json").toPath(); + + Mockito.when(mockJibContainer.getTargetImage()).thenReturn(targetImageReference); + Mockito.when(mockJibContainer.getImageId()).thenReturn(DescriptorDigest.fromDigest(imageId)); + Mockito.when(mockJibContainer.getDigest()).thenReturn(DescriptorDigest.fromDigest(digest)); + Mockito.when(mockJibContainer.getTags()).thenReturn(tags); + Mockito.when(mockJibContainerBuilder.containerize(mockContainerizer)) + .thenReturn(mockJibContainer); + testJibBuildRunner.writeImageJson(outputPath).runBuild(); + + final String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8); + final ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJson(outputJson); + Assert.assertEquals(targetImageReference.toString(), metadataOutput.getImage()); + Assert.assertEquals(imageId, metadataOutput.getImageId()); + Assert.assertEquals(digest, metadataOutput.getImageDigest()); + Assert.assertEquals(tags, ImmutableSet.copyOf(metadataOutput.getTags())); + } }