Skip to content

Commit dcc89ad

Browse files
authored
[MBUILDCACHE-86] bugfix / enhancements restoration of outputs on disk (#104)
* [MBUILDCACHE-86] bugfix/enhancement around restoration on disk - Bugfix / "todo" : files in a base directory containing an underscore were wrongly restored to disk (not at the same location). -> To do so, the path is not guessed anymore from the classifier. I introduced a "filePath" property in the "attachedArtifact" section of the buildinfo.xml file. -> Because the buildInfo structure change, I changed the cache implementation version from "v1" to "v1.1". I assume it was one of the purpose of this value : we don't have to deal with structure migration. Any previous cache entry is defacto invalidated. - Forbid the possibility to extract/restore data in a directory outside the project (like extracting ../../../.ssh for example) -> I guess the extraction part is not a vulnerability since someone with commit permissions can guess other ways to extract data. But the possibility of restoring at any place on the disk looks pretty dangerous to me if a remote cache server is compromised. - Gives the possibility to restore artefacts on disk, with a dedicated property : maven.build.cache.restoreOnDiskArtefacts (default to true, open for discussion) - Introduce "globs" to filter extra attached outputs by filenames. * [MBUILDCACHE-86] Adapt restoration logic to MBUILDCACHE-80 developments + TI Fix javadoc for jdk8
1 parent 8e3dad7 commit dcc89ad

File tree

17 files changed

+472
-126
lines changed

17 files changed

+472
-126
lines changed

src/main/java/org/apache/maven/buildcache/CacheControllerImpl.java

Lines changed: 132 additions & 89 deletions
Large diffs are not rendered by default.

src/main/java/org/apache/maven/buildcache/CacheUtils.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@
2121
import java.io.File;
2222
import java.io.IOException;
2323
import java.nio.charset.StandardCharsets;
24+
import java.nio.file.FileSystems;
2425
import java.nio.file.FileVisitResult;
2526
import java.nio.file.Files;
2627
import java.nio.file.Path;
28+
import java.nio.file.PathMatcher;
2729
import java.nio.file.SimpleFileVisitor;
30+
import java.nio.file.StandardCopyOption;
2831
import java.nio.file.attribute.BasicFileAttributes;
2932
import java.nio.file.attribute.FileTime;
3033
import java.util.Arrays;
@@ -37,6 +40,7 @@
3740
import java.util.zip.ZipOutputStream;
3841

3942
import org.apache.commons.lang3.StringUtils;
43+
import org.apache.commons.lang3.mutable.MutableBoolean;
4044
import org.apache.maven.artifact.Artifact;
4145
import org.apache.maven.artifact.handler.ArtifactHandler;
4246
import org.apache.maven.buildcache.xml.build.Scm;
@@ -160,21 +164,39 @@ public static boolean isArchive(File file) {
160164
return StringUtils.endsWithAny(fileName, ".jar", ".zip", ".war", ".ear");
161165
}
162166

163-
public static void zip(Path dir, Path zip) throws IOException {
167+
/**
168+
* Put every matching files of a directory in a zip.
169+
* @param dir directory to zip
170+
* @param zip zip to populate
171+
* @param glob glob to apply to filenames
172+
* @return true if at least one file has been included in the zip.
173+
* @throws IOException
174+
*/
175+
public static boolean zip(final Path dir, final Path zip, final String glob) throws IOException {
176+
final MutableBoolean hasFiles = new MutableBoolean();
164177
try (ZipOutputStream zipOutputStream = new ZipOutputStream(Files.newOutputStream(zip))) {
178+
179+
PathMatcher matcher =
180+
"*".equals(glob) ? null : FileSystems.getDefault().getPathMatcher("glob:" + glob);
165181
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
166182

167183
@Override
168184
public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes)
169185
throws IOException {
170-
final ZipEntry zipEntry = new ZipEntry(dir.relativize(path).toString());
171-
zipOutputStream.putNextEntry(zipEntry);
172-
Files.copy(path, zipOutputStream);
173-
zipOutputStream.closeEntry();
186+
187+
if (matcher == null || matcher.matches(path.getFileName())) {
188+
final ZipEntry zipEntry =
189+
new ZipEntry(dir.relativize(path).toString());
190+
zipOutputStream.putNextEntry(zipEntry);
191+
Files.copy(path, zipOutputStream);
192+
hasFiles.setTrue();
193+
zipOutputStream.closeEntry();
194+
}
174195
return FileVisitResult.CONTINUE;
175196
}
176197
});
177198
}
199+
return hasFiles.booleanValue();
178200
}
179201

180202
public static void unzip(Path zip, Path out) throws IOException {
@@ -190,7 +212,7 @@ public static void unzip(Path zip, Path out) throws IOException {
190212
} else {
191213
Path parent = file.getParent();
192214
Files.createDirectories(parent);
193-
Files.copy(zis, file);
215+
Files.copy(zis, file, StandardCopyOption.REPLACE_EXISTING);
194216
}
195217
Files.setLastModifiedTime(file, FileTime.fromMillis(entry.getTime()));
196218
entry = zis.getNextEntry();
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.buildcache.artifact;
20+
21+
public enum OutputType {
22+
// generated project artifact
23+
ARTIFACT(""),
24+
// generated source (regular and test)
25+
GENERATED_SOURCE("mvn-cache-ext-generated-source-"),
26+
// any unclassified output added by configuration
27+
EXTRA_OUTPUT("mvn-cache-ext-extra-output-");
28+
29+
private String classifierPrefix;
30+
31+
OutputType(String getClassifierPrefix) {
32+
this.classifierPrefix = getClassifierPrefix;
33+
}
34+
35+
public String getClassifierPrefix() {
36+
return classifierPrefix;
37+
}
38+
39+
public static OutputType fromClassifier(String classifier) {
40+
if (classifier != null) {
41+
if (classifier.startsWith(GENERATED_SOURCE.classifierPrefix)) {
42+
return GENERATED_SOURCE;
43+
} else if (classifier.startsWith(EXTRA_OUTPUT.classifierPrefix)) {
44+
return EXTRA_OUTPUT;
45+
}
46+
}
47+
return ARTIFACT;
48+
}
49+
}

src/main/java/org/apache/maven/buildcache/artifact/RestoredArtifact.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.concurrent.ExecutionException;
2424
import java.util.concurrent.Future;
2525
import java.util.concurrent.RunnableFuture;
26+
import java.util.function.Function;
2627

2728
import org.apache.maven.artifact.Artifact;
2829
import org.apache.maven.artifact.DefaultArtifact;
@@ -46,8 +47,15 @@ public class RestoredArtifact extends DefaultArtifact {
4647

4748
private volatile Future<File> fileFuture;
4849

50+
private Function<File, File> restoreToDiskConsumer;
51+
4952
public RestoredArtifact(
50-
Artifact parent, Future<File> fileFuture, String type, String classifier, ArtifactHandler handler) {
53+
Artifact parent,
54+
Future<File> fileFuture,
55+
String type,
56+
String classifier,
57+
ArtifactHandler handler,
58+
Function<File, File> restoreToDiskConsumer) {
5159
super(
5260
parent.getGroupId(),
5361
parent.getArtifactId(),
@@ -58,6 +66,7 @@ public RestoredArtifact(
5866
handler,
5967
parent.isOptional());
6068
this.fileFuture = requireNonNull(fileFuture, "fileFuture == null");
69+
this.restoreToDiskConsumer = restoreToDiskConsumer;
6170
}
6271

6372
/**
@@ -89,7 +98,8 @@ public File getFile() {
8998
}
9099

91100
try {
92-
return fileFuture.get();
101+
File file = fileFuture.get();
102+
return restoreToDiskConsumer.apply(file);
93103
} catch (InterruptedException e) {
94104
Thread.currentThread().interrupt();
95105
throw new InvalidArtifactRTException(

src/main/java/org/apache/maven/buildcache/checksum/MavenProjectInput.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,16 +93,17 @@
9393
import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_ENABLED_PROPERTY_NAME;
9494
import static org.apache.maven.buildcache.xml.CacheConfigImpl.CACHE_SKIP;
9595
import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_GENERATED_SOURCES_PROPERTY_NAME;
96+
import static org.apache.maven.buildcache.xml.CacheConfigImpl.RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME;
9697

9798
/**
9899
* MavenProjectInput
99100
*/
100101
public class MavenProjectInput {
101102

102103
/**
103-
* Version of hashing algorithm implementation. It is recommended to change to simplify remote cache maintenance
104+
* Version of cache implementation. It is recommended to change to simplify remote cache maintenance
104105
*/
105-
public static final String CACHE_IMPLEMENTATION_VERSION = "v1";
106+
public static final String CACHE_IMPLEMENTATION_VERSION = "v1.1";
106107

107108
/**
108109
* property name to pass glob value. The glob to be used to list directory files in plugins scanning
@@ -728,6 +729,18 @@ public static boolean isRestoreGeneratedSources(MavenProject project) {
728729
project.getProperties().getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, "true"));
729730
}
730731

732+
/**
733+
* Allow skipping artifacts restoration on a per-project level via a property (which defaults to true)
734+
* e.g. {@code <maven.build.cache.restoreOnDiskArtifacts>false<maven.build.cache.restoreOnDiskArtifacts/>}.
735+
*
736+
* @param project
737+
* @return
738+
*/
739+
public static boolean isRestoreOnDiskArtifacts(MavenProject project) {
740+
return Boolean.parseBoolean(
741+
project.getProperties().getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, "true"));
742+
}
743+
731744
/**
732745
* Allow disabling caching entirely on a per-project level via a property - both artifact lookup and upload
733746
* Defaults to false

src/main/java/org/apache/maven/buildcache/xml/CacheConfig.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.apache.maven.buildcache.PluginScanConfig;
2828
import org.apache.maven.buildcache.hash.HashFactory;
29+
import org.apache.maven.buildcache.xml.config.DirName;
2930
import org.apache.maven.buildcache.xml.config.Exclude;
3031
import org.apache.maven.buildcache.xml.config.Include;
3132
import org.apache.maven.buildcache.xml.config.MultiModule;
@@ -103,7 +104,7 @@ public interface CacheConfig {
103104

104105
String getLocalRepositoryLocation();
105106

106-
List<String> getAttachedOutputs();
107+
List<DirName> getAttachedOutputs();
107108

108109
boolean adjustMetaInfVersion();
109110

@@ -133,6 +134,11 @@ public interface CacheConfig {
133134
*/
134135
boolean isRestoreGeneratedSources();
135136

137+
/**
138+
* Flag to restore (default) or not generated artifacts
139+
*/
140+
boolean isRestoreOnDiskArtifacts();
141+
136142
String getAlwaysRunPlugins();
137143

138144
/**

src/main/java/org/apache/maven/buildcache/xml/CacheConfigImpl.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.apache.maven.buildcache.xml.config.CacheConfig;
4343
import org.apache.maven.buildcache.xml.config.Configuration;
4444
import org.apache.maven.buildcache.xml.config.CoordinatesBase;
45+
import org.apache.maven.buildcache.xml.config.DirName;
4546
import org.apache.maven.buildcache.xml.config.Exclude;
4647
import org.apache.maven.buildcache.xml.config.Executables;
4748
import org.apache.maven.buildcache.xml.config.ExecutionConfigurationScan;
@@ -90,6 +91,7 @@ public class CacheConfigImpl implements org.apache.maven.buildcache.xml.CacheCon
9091
public static final String FAIL_FAST_PROPERTY_NAME = "maven.build.cache.failFast";
9192
public static final String BASELINE_BUILD_URL_PROPERTY_NAME = "maven.build.cache.baselineUrl";
9293
public static final String LAZY_RESTORE_PROPERTY_NAME = "maven.build.cache.lazyRestore";
94+
public static final String RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME = "maven.build.cache.restoreOnDiskArtifacts";
9395
public static final String RESTORE_GENERATED_SOURCES_PROPERTY_NAME = "maven.build.cache.restoreGeneratedSources";
9496
public static final String ALWAYS_RUN_PLUGINS = "maven.build.cache.alwaysRunPlugins";
9597
public static final String MANDATORY_CLEAN = "maven.build.cache.mandatoryClean";
@@ -504,6 +506,11 @@ public boolean isRestoreGeneratedSources() {
504506
return getProperty(RESTORE_GENERATED_SOURCES_PROPERTY_NAME, true);
505507
}
506508

509+
@Override
510+
public boolean isRestoreOnDiskArtifacts() {
511+
return getProperty(RESTORE_ON_DISK_ARTIFACTS_PROPERTY_NAME, true);
512+
}
513+
507514
@Override
508515
public String getAlwaysRunPlugins() {
509516
return getProperty(ALWAYS_RUN_PLUGINS, null);
@@ -550,7 +557,7 @@ public String getLocalRepositoryLocation() {
550557
}
551558

552559
@Override
553-
public List<String> getAttachedOutputs() {
560+
public List<DirName> getAttachedOutputs() {
554561
checkInitializedState();
555562
final AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs();
556563
return attachedOutputs == null ? Collections.emptyList() : attachedOutputs.getDirNames();

src/main/mdo/build-cache-build.mdo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ under the License.
240240
<name>fileSize</name>
241241
<type>long</type>
242242
</field>
243+
<field>
244+
<name>filePath</name>
245+
<type>String</type>
246+
</field>
243247
<!--/xs:sequence-->
244248
</fields>
245249
<!--/xs:complexType-->

src/main/mdo/build-cache-config.mdo

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,14 +374,42 @@ under the License.
374374
-->
375375
<class>
376376
<name>AttachedOutputs</name>
377+
<description>Section relative to outputs which are not artifacts but need to be saved/restored.</description>
377378
<fields>
378379
<field>
379380
<name>dirNames</name>
380381
<association>
381-
<type>String</type>
382+
<type>DirName</type>
382383
<multiplicity>*</multiplicity>
383384
</association>
384-
<description>Directory name in build output directory to attach to cached artifacts</description>
385+
<description>Path to a directory containing files which needs to be saved/restored (relative to the build directory).</description>
386+
</field>
387+
</fields>
388+
</class>
389+
<class>
390+
<name>DirName</name>
391+
<description><![CDATA[Path to a directory containing files which needs to be saved/restored (relative to the build directory).
392+
<br>
393+
Examples :
394+
<ul>
395+
<li><code>&lt;dirName&gt;classes&lt;/dirName&gt;</code> : files in ${project.basedir}/target/classes</li>
396+
<li><code>&lt;dirName glob="jacoco.xml"&gt;&lt;/dirName&gt;</code> : jacoco report files in ${project.basedir}/target</li>
397+
<li><code>&lt;dirName&gt;../src/main/javagen&lt;/dirName&gt;</code> : files in ${project.basedir}/src/main/javagen (in this exemple, javagen is a folder saved in git but erased on clean) </li>
398+
</ul>
399+
<br><br>
400+
401+
]]></description>
402+
<fields>
403+
<field xml.content="true">
404+
<name>value</name>
405+
<type>String</type>
406+
<description>Directory name in build output directory to attach to cached artifacts.</description>
407+
</field>
408+
<field xml.attribute="true">
409+
<name>glob</name>
410+
<type>String</type>
411+
<defaultValue>*</defaultValue>
412+
<description>Entries are filtered by matching this glob.</description>
385413
</field>
386414
</fields>
387415
</class>

0 commit comments

Comments
 (0)