Skip to content

Commit 5ded61b

Browse files
conga-aem-maven-plugin: Make use of ${project.build.outputTimestamp} for reproducible builds for dispatcher configuration and processed packages in "all" package. (#15)
1 parent eda94cd commit 5ded61b

6 files changed

Lines changed: 193 additions & 20 deletions

File tree

changes.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@
2323
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 https://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
2424
<body>
2525

26+
<release version="2.18.6" date="not released">
27+
<action type="update" dev="sseifert">
28+
conga-aem-maven-plugin: Make use of ${project.build.outputTimestamp} for reproducible builds for dispatcher configuration and processed packages in "all" package.
29+
</action>
30+
</release>
31+
2632
<release version="2.18.4" date="2022-08-15">
2733
<action type="update" dev="sseifert">
2834
conga-aem-maven-plugin: Check bundle status before package manager install status as the latter may report errors if certain bundles are restarted.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* #%L
3+
* wcm.io
4+
* %%
5+
* Copyright (C) 2022 wcm.io
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package io.wcm.devops.conga.plugins.aem.maven;
21+
22+
import java.nio.file.attribute.FileTime;
23+
import java.util.Date;
24+
25+
import org.apache.maven.archiver.MavenArchiver;
26+
import org.jetbrains.annotations.Nullable;
27+
28+
/**
29+
* Parse/convert ${project.build.outputTimestamp}.
30+
*/
31+
public class BuildOutputTimestamp {
32+
33+
private final Date date;
34+
35+
/**
36+
* @param outputTimestamp Configured output timestamp
37+
*/
38+
public BuildOutputTimestamp(@Nullable String outputTimestamp) {
39+
MavenArchiver mavenArchiver = new MavenArchiver();
40+
this.date = mavenArchiver.parseOutputTimestamp(outputTimestamp);
41+
}
42+
43+
/**
44+
* @return true if a valid timestamp is configured
45+
*/
46+
public boolean isValid() {
47+
return date != null;
48+
}
49+
50+
/**
51+
* @return Date or null if not a valid date
52+
*/
53+
@Nullable
54+
public Date toDate() {
55+
return date;
56+
}
57+
58+
/**
59+
* @return FileTime or null if not a valid date
60+
*/
61+
@Nullable
62+
public FileTime toFileTime() {
63+
if (date != null) {
64+
return FileTime.fromMillis(date.toInstant().getEpochSecond());
65+
}
66+
return null;
67+
}
68+
69+
}

tooling/conga-aem-maven-plugin/src/main/java/io/wcm/devops/conga/plugins/aem/maven/CloudManagerAllPackageMojo.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ public final class CloudManagerAllPackageMojo extends AbstractCloudManagerMojo {
158158
@Parameter(property = "conga.cloudManager.allPackage.skip", defaultValue = "false")
159159
private boolean skip;
160160

161+
@Parameter(defaultValue = "${project.build.outputTimestamp}")
162+
private String outputTimestamp;
163+
161164
@Parameter(readonly = true, defaultValue = "${project}")
162165
private MavenProject project;
163166
@Component
@@ -264,7 +267,8 @@ private AllPackageBuilder createBuilder(String packageName) {
264267
.autoDependenciesMode(this.autoDependenciesMode)
265268
.runModeOptimization(this.runModeOptimization)
266269
.packageTypeValidation(this.packageTypeValidation)
267-
.logger(getLog());
270+
.logger(getLog())
271+
.buildOutputTimestamp(new BuildOutputTimestamp(outputTimestamp));
268272
}
269273

270274
private void buildAllPackage(AllPackageBuilder builder) throws MojoExecutionException {

tooling/conga-aem-maven-plugin/src/main/java/io/wcm/devops/conga/plugins/aem/maven/CloudManagerDispatcherConfigMojo.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public final class CloudManagerDispatcherConfigMojo extends AbstractCloudManager
5959
@Component(role = Archiver.class, hint = "zip")
6060
private ZipArchiver zipArchiver;
6161

62+
@Parameter(defaultValue = "${project.build.outputTimestamp}")
63+
private String outputTimestamp;
64+
6265
@Override
6366
public void execute() throws MojoExecutionException, MojoFailureException {
6467
if (skip) {
@@ -92,6 +95,11 @@ private void buildDispatcherConfig(File environmentDir, File nodeDir) throws Moj
9295
addZipDirectory(basePath, nodeDir, Collections.singleton(ModelParser.MODEL_FILE));
9396
zipArchiver.setDestFile(targetFile);
9497

98+
BuildOutputTimestamp buildOutputTimestamp = new BuildOutputTimestamp(outputTimestamp);
99+
if (buildOutputTimestamp.isValid()) {
100+
zipArchiver.configureReproducible(buildOutputTimestamp.toDate());
101+
}
102+
95103
zipArchiver.createArchive();
96104
}
97105
catch (ArchiverException | IOException ex) {

tooling/conga-aem-maven-plugin/src/main/java/io/wcm/devops/conga/plugins/aem/maven/allpackage/AllPackageBuilder.java

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@
4545
import java.util.Properties;
4646
import java.util.Set;
4747
import java.util.stream.Collectors;
48-
import java.util.zip.ZipEntry;
49-
import java.util.zip.ZipFile;
50-
import java.util.zip.ZipOutputStream;
5148

49+
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
50+
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
51+
import org.apache.commons.compress.archivers.zip.ZipFile;
5252
import org.apache.commons.io.FileUtils;
5353
import org.apache.commons.io.FilenameUtils;
5454
import org.apache.commons.io.IOUtils;
@@ -65,6 +65,7 @@
6565
import com.google.common.collect.ImmutableSet;
6666

6767
import io.wcm.devops.conga.plugins.aem.maven.AutoDependenciesMode;
68+
import io.wcm.devops.conga.plugins.aem.maven.BuildOutputTimestamp;
6869
import io.wcm.devops.conga.plugins.aem.maven.PackageTypeValidation;
6970
import io.wcm.devops.conga.plugins.aem.maven.RunModeOptimization;
7071
import io.wcm.devops.conga.plugins.aem.maven.model.BundleFile;
@@ -105,6 +106,7 @@ public final class AllPackageBuilder {
105106
private RunModeOptimization runModeOptimization = RunModeOptimization.OFF;
106107
private PackageTypeValidation packageTypeValidation = PackageTypeValidation.STRICT;
107108
private Log log;
109+
private BuildOutputTimestamp buildOutputTimestamp;
108110

109111
private static final String RUNMODE_DEFAULT = "$default$";
110112
private static final Set<String> ALLOWED_PACKAGE_TYPES = ImmutableSet.of(
@@ -172,6 +174,15 @@ public AllPackageBuilder version(String value) {
172174
return this;
173175
}
174176

177+
/**
178+
* @param value Build output timestamp
179+
* @return this
180+
*/
181+
public AllPackageBuilder buildOutputTimestamp(BuildOutputTimestamp value) {
182+
this.buildOutputTimestamp = value;
183+
return this;
184+
}
185+
175186
private Log getLog() {
176187
if (this.log == null) {
177188
this.log = new SystemStreamLog();
@@ -530,18 +541,18 @@ private List<TemporaryContentPackageFile> processContentPackage(ContentPackageFi
530541

531542
// iterate through entries and write them to the temp. zip file
532543
try (FileOutputStream fos = new FileOutputStream(tempFile);
533-
ZipOutputStream zipOut = new ZipOutputStream(fos)) {
534-
Enumeration<? extends ZipEntry> zipInEntries = zipFileIn.entries();
544+
ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(fos)) {
545+
Enumeration<? extends ZipArchiveEntry> zipInEntries = zipFileIn.getEntries();
535546
while (zipInEntries.hasMoreElements()) {
536-
ZipEntry zipInEntry = zipInEntries.nextElement();
547+
ZipArchiveEntry zipInEntry = zipInEntries.nextElement();
537548
if (!zipInEntry.isDirectory()) {
538549
try (InputStream is = zipFileIn.getInputStream(zipInEntry)) {
539550
boolean processedEntry = false;
540551

541552
// if entry is properties.xml, update dependency information
542553
if (StringUtils.equals(zipInEntry.getName(), "META-INF/vault/properties.xml")) {
543-
Properties props = new Properties();
544-
props.loadFromXML(is);
554+
FileVaultProperties fileVaultProps = new FileVaultProperties(is);
555+
Properties props = fileVaultProps.getProperties();
545556
addSuffixToPackageName(props, pkg, environmentRunMode);
546557

547558
// update package dependencies
@@ -557,9 +568,10 @@ private List<TemporaryContentPackageFile> processContentPackage(ContentPackageFi
557568
props.put(NAME_PACKAGE_TYPE, packageType);
558569
}
559570

560-
ZipEntry zipOutEntry = newZipEntry(zipInEntry);
561-
zipOut.putNextEntry(zipOutEntry);
562-
props.storeToXML(zipOut, null);
571+
ZipArchiveEntry zipOutEntry = newZipEntry(zipInEntry);
572+
zipOut.putArchiveEntry(zipOutEntry);
573+
fileVaultProps.storeToXml(zipOut);
574+
zipOut.closeArchiveEntry();
563575
processedEntry = true;
564576
}
565577

@@ -589,13 +601,13 @@ else if (StringUtils.equals(FilenameUtils.getExtension(zipInEntry.getName()), "z
589601

590602
// otherwise transfer the binary data 1:1
591603
if (!processedEntry) {
592-
ZipEntry zipOutEntry = newZipEntry(zipInEntry);
593-
zipOut.putNextEntry(zipOutEntry);
604+
ZipArchiveEntry zipOutEntry = newZipEntry(zipInEntry);
605+
zipOut.putArchiveEntry(zipOutEntry);
594606
IOUtils.copy(is, zipOut);
607+
zipOut.closeArchiveEntry();
595608
}
596609
}
597610

598-
zipOut.closeEntry();
599611
}
600612
}
601613
}
@@ -615,12 +627,12 @@ else if (StringUtils.equals(FilenameUtils.getExtension(zipInEntry.getName()), "z
615627
return result;
616628
}
617629

618-
private static ZipEntry newZipEntry(ZipEntry in) {
619-
ZipEntry out = new ZipEntry(in.getName());
620-
if (in.getCreationTime() != null) {
621-
out.setCreationTime(in.getCreationTime());
630+
private ZipArchiveEntry newZipEntry(ZipArchiveEntry in) {
631+
ZipArchiveEntry out = new ZipArchiveEntry(in.getName());
632+
if (buildOutputTimestamp != null && buildOutputTimestamp.isValid()) {
633+
out.setLastModifiedTime(buildOutputTimestamp.toFileTime());
622634
}
623-
if (in.getLastModifiedTime() != null) {
635+
else if (in.getLastModifiedTime() != null) {
624636
out.setLastModifiedTime(in.getLastModifiedTime());
625637
}
626638
return out;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* #%L
3+
* wcm.io
4+
* %%
5+
* Copyright (C) 2022 wcm.io
6+
* %%
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* #L%
19+
*/
20+
package io.wcm.devops.conga.plugins.aem.maven.allpackage;
21+
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.IOException;
24+
import java.io.InputStream;
25+
import java.io.OutputStream;
26+
import java.io.OutputStreamWriter;
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.Properties;
29+
30+
import org.apache.commons.lang3.StringUtils;
31+
32+
/**
33+
* Read and write properties.xml for FileVault package.
34+
*/
35+
class FileVaultProperties {
36+
37+
private final Properties props;
38+
39+
/**
40+
* Read properties from input stream.
41+
* @param is Input stream
42+
* @throws IOException I/O exception
43+
*/
44+
FileVaultProperties(InputStream is) throws IOException {
45+
props = new Properties();
46+
props.loadFromXML(is);
47+
}
48+
49+
public Properties getProperties() {
50+
return this.props;
51+
}
52+
53+
/**
54+
* Store properties content to output stream.
55+
* Ensures consistent line endings are used on all operating systems.
56+
* @param os Output stream
57+
* @throws IOException I/O exception
58+
*/
59+
public void storeToXml(OutputStream os) throws IOException {
60+
// write properties XML to string
61+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
62+
props.storeToXML(bos, null);
63+
String xmlOutput = bos.toString(StandardCharsets.UTF_8.name());
64+
65+
// normalize line endings with unix line ending
66+
xmlOutput = StringUtils.replace(xmlOutput, System.lineSeparator(), "\n");
67+
68+
// output normalized XML
69+
OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
70+
writer.write(xmlOutput);
71+
writer.flush();
72+
}
73+
74+
}

0 commit comments

Comments
 (0)