Skip to content

Commit 8857f56

Browse files
fix(bug): ImageName should respect project.build.outputTimestamp when present (3780)
fix(bug): ImageName should respect project.build.outputTimestamp when present --- fix(bug): Add PR to changelog --- fix(bug): replace with parameterized test
1 parent f75b1e4 commit 8857f56

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Usage:
2424
* Fix #3750: Remove unneeded XMLUtil.createNewDocument method
2525
* Fix #3740: Add verbose extension prop as per the gradle plugin docs
2626
* Fix #3779: Compatibility with gradle 9
27+
* Fix #3780: ImageName should respect project.build.outputTimestamp when present
2728

2829
### 1.18.1 (2025-02-11)
2930
* Fix #3642: Config properties resolved for generated images

jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatter.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
package org.eclipse.jkube.kit.build.api.helper;
1515

1616
import java.text.SimpleDateFormat;
17+
import java.time.Instant;
18+
import java.time.format.DateTimeParseException;
1719
import java.util.Date;
1820
import java.util.HashMap;
1921
import java.util.Map;
22+
import java.util.TimeZone;
2023

2124
import static org.eclipse.jkube.kit.common.JKubeFileInterpolator.DEFAULT_FILTER;
2225
import static org.eclipse.jkube.kit.common.JKubeFileInterpolator.interpolate;
@@ -150,6 +153,12 @@ private static class DefaultTagLookup extends AbstractLookup {
150153
*/
151154
private static final String DOCKER_IMAGE_TAG = "jkube.image.tag";
152155

156+
/**
157+
* Maven property for reproducible builds timestamp.
158+
* See <a href="https://maven.apache.org/guides/mini/guide-reproducible-builds.html">...</a>
159+
*/
160+
private static final String OUTPUT_TIMESTAMP = "project.build.outputTimestamp";
161+
153162
// how to resolve the version
154163
private final Mode mode;
155164

@@ -207,14 +216,45 @@ private String generateTag(String plusSubstitute) {
207216

208217
switch (mode) {
209218
case SNAPSHOT_WITH_TIMESTAMP:
210-
return "snapshot-" + new SimpleDateFormat("yyMMdd-HHmmss-SSSS").format(now) + buildmetadata;
219+
Date timestamp = getTimestamp();
220+
SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd-HHmmss-SSSS");
221+
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
222+
return "snapshot-" + dateFormat.format(timestamp) + buildmetadata;
211223
case SNAPSHOT_LATEST:
212224
return "latest" + buildmetadata;
213225
default:
214226
throw new IllegalStateException("mode is '" + mode.name() + "', which is not implemented.");
215227
}
216228
}
217229

230+
/**
231+
* Gets the timestamp to use for image tagging.
232+
* First checks for project.build.outputTimestamp property (for reproducible builds),
233+
* then falls back to the current time.
234+
*
235+
* @return the timestamp to use for tagging
236+
*/
237+
private Date getTimestamp() {
238+
final String outputTimestamp = getProperty(OUTPUT_TIMESTAMP);
239+
if (StringUtils.isBlank(outputTimestamp)) {
240+
return now;
241+
}
242+
243+
try {
244+
// Try parsing as a number (seconds since epoch)
245+
long epochSeconds = Long.parseLong(outputTimestamp);
246+
return Date.from(Instant.ofEpochSecond(epochSeconds));
247+
} catch (NumberFormatException e) {
248+
// Not a number, try parsing as ISO 8601 timestamp
249+
try {
250+
return Date.from(Instant.parse(outputTimestamp));
251+
} catch (DateTimeParseException ex) {
252+
// Invalid format, fall back to now
253+
return now;
254+
}
255+
}
256+
}
257+
218258
private static String sanitizeTag(String tagName, String plusSubstitute) {
219259
StringBuilder ret = new StringBuilder(tagName.length());
220260

jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatterTest.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
* Red Hat, Inc. - initial API and implementation
1313
*/
1414
package org.eclipse.jkube.kit.build.api.helper;
15-
import org.eclipse.jkube.kit.build.api.helper.ImageNameFormatter;
1615
import org.eclipse.jkube.kit.common.JavaProject;
1716
import org.junit.jupiter.api.BeforeEach;
1817
import org.junit.jupiter.api.DisplayName;
@@ -179,4 +178,54 @@ void format_whenPropertyInImageName_thenResolveProperty() {
179178
// Then
180179
assertThat(result).isEqualTo("registry.gitlab.com/myproject/myrepo/mycontainer:der12");
181180
}
181+
182+
@ParameterizedTest(name = "version = {0}, outputTimestamp = {1} should generate {2}")
183+
@MethodSource("outputTimestampReproducibleData")
184+
void format_whenOutputTimestampSet_thenUsesReproducibleTimestamp(String version, String outputTimestamp, String expectedTag) {
185+
// Given
186+
project.setVersion(version);
187+
project.getProperties().put("project.build.outputTimestamp", outputTimestamp);
188+
formatter = new ImageNameFormatter(project, new Date());
189+
// When
190+
final String result = formatter.format("%g/%a:%t");
191+
// Then
192+
assertThat(result)
193+
.isEqualTo(expectedTag)
194+
.satisfies(i -> assertThat(new ImageName(i)).isNotNull());
195+
}
196+
197+
static Stream<Arguments> outputTimestampReproducibleData() {
198+
return Stream.of(
199+
arguments("1.2.3-SNAPSHOT", "1672531200", "jkube/kubernetes-maven-plugin:snapshot-230101-000000-0000"),
200+
arguments("1.2.3-SNAPSHOT", "2023-01-01T00:00:00Z", "jkube/kubernetes-maven-plugin:snapshot-230101-000000-0000"),
201+
arguments("1.2.3-SNAPSHOT+semver.build_meta-data", "1672531200", "jkube/kubernetes-maven-plugin:snapshot-230101-000000-0000-semver.build_meta-data")
202+
);
203+
}
204+
205+
@Test
206+
void format_whenOutputTimestampNotSet_thenUsesCurrentTime() {
207+
// Given
208+
project.setVersion("1.2.3-SNAPSHOT");
209+
formatter = new ImageNameFormatter(project, new Date());
210+
// When
211+
final String result = formatter.format("%g/%a:%t");
212+
// Then
213+
assertThat(result)
214+
.matches("^jkube/kubernetes-maven-plugin:snapshot-\\d{6}-\\d{6}-\\d{4}$")
215+
.satisfies(i -> assertThat(new ImageName(i)).isNotNull());
216+
}
217+
218+
@Test
219+
void format_whenOutputTimestampInvalid_thenFallsBackToCurrentTime() {
220+
// Given
221+
project.setVersion("1.2.3-SNAPSHOT");
222+
project.getProperties().put("project.build.outputTimestamp", "invalid-timestamp");
223+
formatter = new ImageNameFormatter(project, new Date());
224+
// When
225+
final String result = formatter.format("%g/%a:%t");
226+
// Then
227+
assertThat(result)
228+
.matches("^jkube/kubernetes-maven-plugin:snapshot-\\d{6}-\\d{6}-\\d{4}$")
229+
.satisfies(i -> assertThat(new ImageName(i)).isNotNull());
230+
}
182231
}

0 commit comments

Comments
 (0)