Set ephemeral builder container creation to a fixed date

This commit fixes the `Created` date and time of the ephemeral builder
container image at the Windows epoch plus one second
(1980-01-01T00:00:01Z). This date matches the created date of the builder
image and influences the created date of the resulting image. Using
a fixed date for images ensures that the digest is consistent for all
images with the same version.

Fixes gh-20126
This commit is contained in:
Scott Frederick 2020-02-12 13:55:54 -06:00
parent 191dce3f5e
commit e294d26458
6 changed files with 22 additions and 35 deletions

View File

@ -17,8 +17,6 @@
package org.springframework.boot.buildpack.platform.build;
import java.io.IOException;
import java.time.Clock;
import java.time.Instant;
import java.util.Map;
import org.springframework.boot.buildpack.platform.docker.type.Image;
@ -55,21 +53,6 @@ class EphemeralBuilder {
*/
EphemeralBuilder(BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata, Creator creator,
Map<String, String> env) throws IOException {
this(Clock.systemUTC(), buildOwner, builderImage, builderMetadata, creator, env);
}
/**
* Create a new {@link EphemeralBuilder} instance with a specific clock.
* @param clock the clock used for the current time
* @param buildOwner the build owner
* @param builderImage the image
* @param builderMetadata the builder metadata
* @param creator the builder creator
* @param env the builder env
* @throws IOException on IO error
*/
EphemeralBuilder(Clock clock, BuildOwner buildOwner, Image builderImage, BuilderMetadata builderMetadata,
Creator creator, Map<String, String> env) throws IOException {
ImageReference name = ImageReference.random("pack.local/builder/").inTaggedForm();
this.buildOwner = buildOwner;
this.creator = creator;
@ -77,7 +60,6 @@ class EphemeralBuilder {
this.archive = ImageArchive.from(builderImage, (update) -> {
update.withUpdatedConfig(this.builderMetadata::attachTo);
update.withTag(name);
update.withCreateDate(Instant.now(clock));
if (env != null && !env.isEmpty()) {
update.withNewLayer(getEnvLayer(env));
}

View File

@ -21,6 +21,7 @@ import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@ -46,6 +47,7 @@ import org.springframework.util.Assert;
* An image archive that can be loaded into Docker.
*
* @author Phillip Webb
* @author Scott Frederick
* @since 2.3.0
* @see #from(Image, IOConsumer)
* @see <a href="https://github.com/moby/moby/blob/master/image/spec/v1.2.md">Docker Image
@ -53,6 +55,9 @@ import org.springframework.util.Assert;
*/
public class ImageArchive implements TarArchive {
private static final Instant WINDOWS_EPOCH_PLUS_SECOND = OffsetDateTime.of(1980, 1, 1, 0, 0, 1, 0, ZoneOffset.UTC)
.toInstant();
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ISO_ZONED_DATE_TIME
.withZone(ZoneOffset.UTC);
@ -248,7 +253,7 @@ public class ImageArchive implements TarArchive {
private ImageArchive applyTo(IOConsumer<Update> update) throws IOException {
update.accept(this);
Instant createDate = (this.createDate != null) ? this.createDate : Instant.now();
Instant createDate = (this.createDate != null) ? this.createDate : WINDOWS_EPOCH_PLUS_SECOND;
return new ImageArchive(SharedObjectMapper.get(), this.config, createDate, this.tag, this.image.getOs(),
this.image.getLayers(), Collections.unmodifiableList(this.newLayers));
}

View File

@ -22,9 +22,9 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Map;
@ -96,11 +96,16 @@ class EphemeralBuilderTests extends AbstractJsonTests {
}
@Test
void getArchiveHasCreateDate() throws Exception {
Clock clock = Clock.fixed(Instant.now(), ZoneOffset.UTC);
EphemeralBuilder builder = new EphemeralBuilder(clock, this.owner, this.image, this.metadata, this.creator,
this.env);
assertThat(builder.getArchive().getCreateDate()).isEqualTo(Instant.now(clock));
void getArchiveHasFixedCreateDate() throws Exception {
EphemeralBuilder builder = new EphemeralBuilder(this.owner, this.image, this.metadata, this.creator, this.env);
Instant createInstant = builder.getArchive().getCreateDate();
OffsetDateTime createDateTime = OffsetDateTime.ofInstant(createInstant, ZoneId.of("UTC"));
assertThat(createDateTime.getYear()).isEqualTo(1980);
assertThat(createDateTime.getMonthValue()).isEqualTo(1);
assertThat(createDateTime.getDayOfMonth()).isEqualTo(1);
assertThat(createDateTime.getHour()).isEqualTo(0);
assertThat(createDateTime.getMinute()).isEqualTo(0);
assertThat(createDateTime.getSecond()).isEqualTo(1);
}
@Test

View File

@ -20,9 +20,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
@ -39,17 +36,15 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link ImageArchive}.
*
* @author Phillip Webb
* @author Scott Frederick
*/
class ImageArchiveTests extends AbstractJsonTests {
static final Instant CREATE_DATE = OffsetDateTime.of(1906, 12, 9, 11, 30, 0, 0, ZoneOffset.UTC).toInstant();
@Test
void fromImageWritesToValidArchiveTar() throws Exception {
Image image = Image.of(getContent("image.json"));
ImageArchive archive = ImageArchive.from(image, (update) -> {
update.withNewLayer(Layer.of((layout) -> layout.folder("/spring", Owner.ROOT)));
update.withCreateDate(CREATE_DATE);
update.withTag(ImageReference.of("pack.local/builder/6b7874626575656b6162"));
});
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
@ -78,7 +73,7 @@ class ImageArchiveTests extends AbstractJsonTests {
}
private void assertExpectedConfig(TarArchiveEntry entry, byte[] content) throws Exception {
assertThat(entry.getName()).isEqualTo("/d1872169d781cff5e1aa22d111f636bef0c57e1c358ca3861e3d33a5bdb1b4a5.json");
assertThat(entry.getName()).isEqualTo("/682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json");
String actualJson = new String(content, StandardCharsets.UTF_8);
String expectedJson = StreamUtils.copyToString(getContent("image-archive-config.json"), StandardCharsets.UTF_8);
JSONAssert.assertEquals(expectedJson, actualJson, false);

View File

@ -25,7 +25,7 @@
"io.buildpacks.stack.id": "org.cloudfoundry.stacks.cflinuxfs3"
}
},
"created": "1906-12-09T11:30:00Z",
"created": "1980-01-01T00:00:01Z",
"history": [
{

View File

@ -1,6 +1,6 @@
[
{
"Config": "/d1872169d781cff5e1aa22d111f636bef0c57e1c358ca3861e3d33a5bdb1b4a5.json",
"Config": "/682f8d24b9d9c313d1190a0e955dcb5e65ec9beea40420999839c6f0cbb38382.json",
"Layers": [
"",
"",