Skip to content

Commit

Permalink
Use Gradle Property and Provider for creationTime and filesModificati…
Browse files Browse the repository at this point in the history
…onTime (#3709)

Enables lazy evaluation for creationTime and filesModificationTime, e.g. to set times to the latest commit time provided by gradle-git-properties
  • Loading branch information
creckord committed Sep 26, 2022
1 parent 2221462 commit 3696e6c
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 26 deletions.
20 changes: 18 additions & 2 deletions jib-gradle-plugin/README.md
Expand Up @@ -36,6 +36,7 @@ For information about the project, see the [Jib project README](../README.md).
* [Using Docker Credential Helpers](#using-docker-credential-helpers)
* [Using Specific Credentials](#using-specific-credentials)
* [Custom Container Entrypoint](#custom-container-entrypoint)
* [Reproducible Build Timestamps](#reproducible-build-timestamps)
* [Jib Extensions](#jib-extensions)
* [WAR Projects](#war-projects)
* [Skaffold Integration](#skaffold-integration)
Expand Down Expand Up @@ -247,12 +248,12 @@ Property | Type | Default | Description
--- | --- | --- | ---
`appRoot` | `String` | `/app` | The root directory on the container where the app's contents are placed. Particularly useful for WAR-packaging projects to work with different Servlet engine base images by designating where to put exploded WAR contents; see [WAR usage](#war-projects) as an example.
`args` | `List<String>` | *None* | Additional program arguments appended to the command to start the container (similar to Docker's [CMD](https://docs.docker.com/engine/reference/builder/#cmd) instruction in relation with [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint)). In the default case where you do not set a custom `entrypoint`, this parameter is effectively the arguments to the main method of your Java application.
`creationTime` | `String` | `EPOCH` | Sets the container creation time. (Note that this property does not affect the file modification times, which are configured using `jib.container.filesModificationTime`.) The value can be `EPOCH` to set the timestamps to Epoch (default behavior), `USE_CURRENT_TIMESTAMP` to forgo reproducibility and use the real creation time, or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`.
`creationTime` | `String` | `EPOCH` | Sets the container creation time. (Note that this property does not affect the file modification times, which are configured using `jib.container.filesModificationTime`.) The value can be `EPOCH` to set the timestamps to Epoch (default behavior), `USE_CURRENT_TIMESTAMP` to forgo reproducibility and use the real creation time, or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.
`entrypoint` | `List<String>` | *None* | The command to start the container with (similar to Docker's [ENTRYPOINT](https://docs.docker.com/engine/reference/builder/#entrypoint) instruction). If set, then `jvmFlags`, `mainClass`, `extraClasspath`, and `expandClasspathDependencies` are ignored. You may also set `jib.container.entrypoint = 'INHERIT'` to indicate that the `entrypoint` and `args` should be inherited from the base image.\*
`environment` | `Map<String, String>` | *None* | Key-value pairs for setting environment variables on the container (similar to Docker's [ENV](https://docs.docker.com/engine/reference/builder/#env) instruction).
`extraClasspath` | `List<String>` | *None* | Additional paths in the container to prepend to the computed Java classpath.
`expandClasspathDependencies` | `boolean` | `false` | <ul><li>Java 8 *or* Jib < 3.1: When set to true, does not use a wildcard (for example, `/app/lib/*`) for dependency JARs in the default Java runtime classpath but instead enumerates the JARs. Has the effect of preserving the classpath loading order as defined by the Gradle project.</li><li>Java >= 9 *and* Jib >= 3.1: The option has no effect. Jib *always* enumerates the dependency JARs. This is achieved by [creating and using an argument file](#custom-container-entrypoint) for the `--class-path` JVM argument.</li></ul>
`filesModificationTime` | `String` | `EPOCH_PLUS_SECOND` | Sets the modification time (last modified time) of files in the image put by Jib. (Note that this does not set the image creation time, which can be set using `jib.container.creationTime`.) The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`.
`filesModificationTime` | `String` | `EPOCH_PLUS_SECOND` | Sets the modification time (last modified time) of files in the image put by Jib. (Note that this does not set the image creation time, which can be set using `jib.container.creationTime`.) The value should either be `EPOCH_PLUS_SECOND` to set the timestamps to Epoch + 1 second (default behavior), or an ISO 8601 date-time parsable with [`DateTimeFormatter.ISO_DATE_TIME`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html#ISO_DATE_TIME) such as `2019-07-15T10:15:30+09:00` or `2011-12-03T22:42:05Z`. The value can also be initialized [lazily](https://docs.gradle.org/current/userguide/lazy_configuration.html) with a provider.
`format` | `String` | `Docker` | Use `OCI` to build an [OCI container image](https://www.opencontainers.org/).
`jvmFlags` | `List<String>` | *None* | Additional flags to pass into the JVM when running your application.
`labels` | `Map<String, String>` | *None* | Key-value pairs for applying image metadata (similar to Docker's [LABEL](https://docs.docker.com/engine/reference/builder/#label) instruction).
Expand Down Expand Up @@ -572,6 +573,21 @@ Therefore, *for example*, the following commands will be able to launch your app
- (Java 9+) `java -cp @/app/jib-classpath-file @/app/jib-main-class-file`
- (with shell) `java -cp $( cat /app/jib-classpath-file ) $( cat /app/jib-main-class-file )`

### Reproducible Build Timestamps

To ensure that a Jib build is reproducible, Jib sets the image creation time to the Unix epoch (00:00:00, January 1st, 1970 in UTC) and all file modification times to one second past the epoch by default. See the [Jib FAQ](https://github.com/GoogleContainerTools/jib/blob/master/docs/faq.md#why-is-my-image-created-48-years-ago) for more details on reproducible builds.

Another, more complex way to achieve reproducible builds with stable creation times is to leverage commit timestamps from the project's SCM. For example, the [gradle-git-properties](https://plugins.gradle.org/plugin/com.gorylenko.gradle-git-properties) plugin can be used to inject Git commit information into the current build. These can then be used to configure `jib.container.creationTime`. Since the actual Git information is not yet available at the time the build is configured, it needs to be set through [lazy configuration in Gradle](https://docs.gradle.org/current/userguide/lazy_configuration.html), using a provider in `build.gradle`:

```groovy
jib {
container {
creationTime = project.provider { project.ext.git['git.commit.time'] }
}
}
```

This would build an image with the creation time set to the time of the latest commit from `project.ext.git['git.commit.time']`.

### Jib Extensions

Expand Down
Expand Up @@ -27,6 +27,7 @@
import javax.inject.Inject;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;

Expand All @@ -50,12 +51,14 @@ public class ContainerParameters {
private String appRoot = "";
@Nullable private String user;
@Nullable private String workingDirectory;
private String filesModificationTime = "EPOCH_PLUS_SECOND";
private String creationTime = "EPOCH";
private final Property<String> filesModificationTime;
private final Property<String> creationTime;

@Inject
public ContainerParameters(ObjectFactory objectFactory) {
labels = objectFactory.mapProperty(String.class, String.class).empty();
filesModificationTime = objectFactory.property(String.class).convention("EPOCH_PLUS_SECOND");
creationTime = objectFactory.property(String.class).convention("EPOCH");
}

@Input
Expand Down Expand Up @@ -262,27 +265,21 @@ public void setWorkingDirectory(String workingDirectory) {

@Input
@Optional
public String getFilesModificationTime() {
if (System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME) != null) {
return System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME);
public Property<String> getFilesModificationTime() {
String property = System.getProperty(PropertyNames.CONTAINER_FILES_MODIFICATION_TIME);
if (property != null && !property.equals(filesModificationTime.get())) {
filesModificationTime.set(property);
}
return filesModificationTime;
}

public void setFilesModificationTime(String filesModificationTime) {
this.filesModificationTime = filesModificationTime;
}

@Input
@Optional
public String getCreationTime() {
if (System.getProperty(PropertyNames.CONTAINER_CREATION_TIME) != null) {
return System.getProperty(PropertyNames.CONTAINER_CREATION_TIME);
public Property<String> getCreationTime() {
String property = System.getProperty(PropertyNames.CONTAINER_CREATION_TIME);
if (property != null && !property.equals(creationTime.get())) {
creationTime.set(property);
}
return creationTime;
}

public void setCreationTime(String creationTime) {
this.creationTime = creationTime;
}
}
Expand Up @@ -153,12 +153,12 @@ public Optional<String> getProperty(String propertyName) {

@Override
public String getFilesModificationTime() {
return jibExtension.getContainer().getFilesModificationTime();
return jibExtension.getContainer().getFilesModificationTime().get();
}

@Override
public String getCreationTime() {
return jibExtension.getContainer().getCreationTime();
return jibExtension.getContainer().getCreationTime().get();
}

@Override
Expand Down
Expand Up @@ -26,6 +26,7 @@
import java.util.HashSet;
import java.util.Optional;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -51,6 +52,8 @@ public void testGetters() {
OutputPathsParameters outputPathsParameters = Mockito.mock(OutputPathsParameters.class);
CredHelperParameters fromCredHelperParameters = Mockito.mock(CredHelperParameters.class);
CredHelperParameters toCredHelperParameters = Mockito.mock(CredHelperParameters.class);
Property<String> filesModificationTime = Mockito.mock(Property.class);
Property<String> creationTime = Mockito.mock(Property.class);

Mockito.when(authParameters.getUsername()).thenReturn("user");
Mockito.when(authParameters.getPassword()).thenReturn("password");
Expand Down Expand Up @@ -90,7 +93,10 @@ public void testGetters() {
Mockito.when(containerParameters.getMainClass()).thenReturn("com.example.Main");
Mockito.when(containerParameters.getPorts()).thenReturn(Arrays.asList("80/tcp", "0"));
Mockito.when(containerParameters.getUser()).thenReturn("admin:wheel");
Mockito.when(containerParameters.getFilesModificationTime()).thenReturn("2011-12-03T22:42:05Z");
Mockito.when(containerParameters.getFilesModificationTime()).thenReturn(filesModificationTime);
Mockito.when(filesModificationTime.get()).thenReturn("2011-12-03T22:42:05Z");
Mockito.when(containerParameters.getCreationTime()).thenReturn(creationTime);
Mockito.when(creationTime.get()).thenReturn("2011-12-03T11:42:05Z");

Mockito.when(dockerClientParameters.getExecutablePath()).thenReturn(Paths.get("test"));
Mockito.when(dockerClientParameters.getEnvironment())
Expand Down Expand Up @@ -135,6 +141,7 @@ public void testGetters() {
rawConfiguration.getToCredHelper().getEnvironment());
Assert.assertEquals("admin:wheel", rawConfiguration.getUser().get());
Assert.assertEquals("2011-12-03T22:42:05Z", rawConfiguration.getFilesModificationTime());
Assert.assertEquals("2011-12-03T11:42:05Z", rawConfiguration.getCreationTime());
Assert.assertEquals(Paths.get("test"), rawConfiguration.getDockerExecutable().get());
Assert.assertEquals(
new HashMap<>(ImmutableMap.of("docker", "client")),
Expand Down
Expand Up @@ -33,6 +33,7 @@
import java.util.List;
import java.util.Properties;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.testfixtures.ProjectBuilder;
Expand Down Expand Up @@ -184,9 +185,9 @@ public void testContainer() {
assertThat(testJibExtension.getContainer().getPorts()).isEmpty();
assertThat(testJibExtension.getContainer().getLabels().get()).isEmpty();
assertThat(testJibExtension.getContainer().getAppRoot()).isEmpty();
assertThat(testJibExtension.getContainer().getFilesModificationTime())
assertThat(testJibExtension.getContainer().getFilesModificationTime().get())
.isEqualTo("EPOCH_PLUS_SECOND");
assertThat(testJibExtension.getContainer().getCreationTime()).isEqualTo("EPOCH");
assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo("EPOCH");

testJibExtension.container(
container -> {
Expand All @@ -200,7 +201,8 @@ public void testContainer() {
container.setPorts(Arrays.asList("1000", "2000-2010", "3000"));
container.setFormat(ImageFormat.OCI);
container.setAppRoot("some invalid appRoot value");
container.setFilesModificationTime("some invalid time value");
container.getFilesModificationTime().set("some invalid time value");
container.getCreationTime().set("some other invalid time value");
});
ContainerParameters container = testJibExtension.getContainer();
assertThat(container.getEntrypoint()).containsExactly("foo", "bar", "baz").inOrder();
Expand All @@ -215,7 +217,16 @@ public void testContainer() {
assertThat(container.getPorts()).containsExactly("1000", "2000-2010", "3000").inOrder();
assertThat(container.getFormat()).isSameInstanceAs(ImageFormat.OCI);
assertThat(container.getAppRoot()).isEqualTo("some invalid appRoot value");
assertThat(container.getFilesModificationTime()).isEqualTo("some invalid time value");
assertThat(container.getFilesModificationTime().get()).isEqualTo("some invalid time value");
assertThat(container.getCreationTime().get()).isEqualTo("some other invalid time value");
testJibExtension.container(
extensionContainer -> {
extensionContainer.getFilesModificationTime().set((String) null);
extensionContainer.getCreationTime().set((String) null);
});
container = testJibExtension.getContainer();
assertThat(container.getFilesModificationTime().get()).isEqualTo("EPOCH_PLUS_SECOND");
assertThat(container.getCreationTime().get()).isEqualTo("EPOCH");
}

@Test
Expand Down Expand Up @@ -482,8 +493,16 @@ public void testProperties() {
System.setProperty("jib.container.user", "myUser");
assertThat(testJibExtension.getContainer().getUser()).isEqualTo("myUser");
System.setProperty("jib.container.filesModificationTime", "2011-12-03T22:42:05Z");
assertThat(testJibExtension.getContainer().getFilesModificationTime())
testJibExtension
.getContainer()
.getFilesModificationTime()
.set("property should override value");
assertThat(testJibExtension.getContainer().getFilesModificationTime().get())
.isEqualTo("2011-12-03T22:42:05Z");
System.setProperty("jib.container.creationTime", "2011-12-03T11:42:05Z");
testJibExtension.getContainer().getCreationTime().set("property should override value");
assertThat(testJibExtension.getContainer().getCreationTime().get())
.isEqualTo("2011-12-03T11:42:05Z");
System.setProperty("jib.containerizingMode", "packaged");
assertThat(testJibExtension.getContainerizingMode()).isEqualTo("packaged");

Expand All @@ -507,6 +526,22 @@ public void testProperties() {
.inOrder();
}

@Test
public void testLazyPropertiesFinalization() {
Property<String> filesModificationTime =
testJibExtension.getContainer().getFilesModificationTime();
filesModificationTime.set((String) null);
filesModificationTime.finalizeValue();
System.setProperty("jib.container.filesModificationTime", "EPOCH_PLUS_SECOND");
assertThat(testJibExtension.getContainer().getFilesModificationTime().get())
.isEqualTo("EPOCH_PLUS_SECOND");
Property<String> creationTime = testJibExtension.getContainer().getCreationTime();
creationTime.set((String) null);
creationTime.finalizeValue();
System.setProperty("jib.container.creationTime", "EPOCH");
assertThat(testJibExtension.getContainer().getCreationTime().get()).isEqualTo("EPOCH");
}

@Test
public void testSystemPropertiesWithInvalidPlatform() {
System.setProperty("jib.from.platforms", "linux /amd64");
Expand Down
Expand Up @@ -399,6 +399,14 @@ public void testLazyEvalForExtraDirectories_individualPaths() throws IOException
.contains("extraDirectories (excludes): [[exclude.txt]]");
}

@Test
public void testLazyEvalForContainerCreationAndFileModificationTimes() {
BuildResult showTimes = testProject.build("showtimes", "-Djib.console=plain");
String output = showTimes.getOutput();
assertThat(output).contains("creationTime=2022-07-19T10:23:42Z");
assertThat(output).contains("filesModificationTime=2022-07-19T11:23:42Z");
}

private Project createProject(String... plugins) {
Project project =
ProjectBuilder.builder().withProjectDir(testProjectRoot.getRoot()).withName("root").build();
Expand Down
Expand Up @@ -15,10 +15,14 @@ dependencies {
}

project.ext.value = 'original'
project.ext.jibCreationTime = '1970-01-23T00:23:42Z'
project.ext.jibFilesModificationTime = '1970-01-23T01:23:42Z'

project.afterEvaluate {
project.ext.value = 'updated'
project.ext.getCustomPermissions = { -> return ['/updated': '755'] }
project.ext.jibCreationTime = '2022-07-19T10:23:42Z'
project.ext.jibFilesModificationTime = '2022-07-19T11:23:42Z'
}

jib {
Expand All @@ -33,6 +37,8 @@ jib {
secondKey: project.ext.value + '-second-label'
]
}
creationTime = project.provider { project.ext.jibCreationTime }
filesModificationTime = project.provider { project.ext.jibFilesModificationTime }
}
extraDirectories {
paths = project.provider { ['src/main/' + project.ext.value + '-custom-extra-dir'] }
Expand All @@ -51,3 +57,10 @@ tasks.register('check-extra-directories') {
println('extraDirectories paths: ' + paths)
println('extraDirectories permissions: ' + permissions)
}

tasks.register('showtimes') {
String prop = project.extensions.jib.container.creationTime.get()
println('creationTime=' + prop)
prop = project.extensions.jib.container.filesModificationTime.get()
println('filesModificationTime=' + prop)
}

0 comments on commit 3696e6c

Please sign in to comment.