Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Gradle Property and Provider for creationTime and filesModificationTime #3709

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}