Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.

Commit af891e4

Browse files
committed
1 parent 6fac660 commit af891e4

34 files changed

Lines changed: 650 additions & 0 deletions

engine/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ dependencies {
7777
testImplementation("com.squareup.okhttp3:mockwebserver")
7878

7979
implementation("org.apache.commons:commons-compress:1.20")
80+
testImplementation("org.apache.commons:commons-lang3:3.12.0")
8081

8182
implementation("de.gesellix:docker-filesocket:2021-06-06T17-29-35")
8283
testImplementation("de.gesellix:testutil:2021-04-21T23-29-14")
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package de.gesellix.docker.builder;
2+
3+
import de.gesellix.util.IOUtils;
4+
import okio.Okio;
5+
import okio.Source;
6+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
7+
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
import java.io.File;
12+
import java.io.FileOutputStream;
13+
import java.io.IOException;
14+
import java.io.OutputStream;
15+
import java.nio.file.Files;
16+
import java.util.ArrayList;
17+
import java.util.Collections;
18+
import java.util.List;
19+
import java.util.stream.Collectors;
20+
import java.util.zip.GZIPOutputStream;
21+
22+
public class BuildContextBuilder {
23+
24+
private static final Logger log = LoggerFactory.getLogger(BuildContextBuilder.class);
25+
26+
public static void archiveTarFilesRecursively(File base, File targetFile) throws IOException {
27+
List<File> filenames = new DockerignoreFileFilter(base, new ArrayList<>(Collections.singletonList(targetFile.getAbsolutePath()))).collectFiles(base);
28+
log.debug("found {} files in buildContext.", filenames.size());
29+
archiveTarFiles(base, filenames.stream().map(File::getAbsolutePath).collect(Collectors.toList()), targetFile);
30+
}
31+
32+
public static void archiveTarFilesRecursively(File base, OutputStream target) throws IOException {
33+
List<File> filenames = new DockerignoreFileFilter(base, new ArrayList<>()).collectFiles(base);
34+
log.debug("found {} files in buildContext.", filenames.size());
35+
archiveTarFiles(base, filenames.stream().map(File::getAbsolutePath).collect(Collectors.toList()), target);
36+
}
37+
38+
public static void archiveTarFiles(File base, List<String> filenames, File targetFile) throws IOException {
39+
archiveTarFiles(base, filenames, new FileOutputStream(targetFile));
40+
}
41+
42+
public static void archiveTarFiles(File base, List<String> filenames, OutputStream target) throws IOException {
43+
try (TarArchiveOutputStream tos = new TarArchiveOutputStream(new GZIPOutputStream(target))) {
44+
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
45+
for (String filename : filenames) {
46+
String relativeFileName = relativize(base, new File(filename));
47+
log.debug("adding {} as {}", filename, relativeFileName);
48+
addAsTarEntry(new File(filename), relativeFileName, tos);
49+
}
50+
}
51+
}
52+
53+
public static void addAsTarEntry(File file, String relativeFileName, TarArchiveOutputStream tos) throws IOException {
54+
TarArchiveEntry tarEntry = new TarArchiveEntry(file);
55+
tarEntry.setName(relativeFileName);
56+
57+
if (!file.isDirectory()) {
58+
if (Files.isExecutable(file.toPath())) {
59+
tarEntry.setMode(tarEntry.getMode() | 0755);
60+
}
61+
}
62+
63+
tos.putArchiveEntry(tarEntry);
64+
65+
if (!file.isDirectory()) {
66+
copyFile(file, tos);
67+
}
68+
69+
tos.closeArchiveEntry();
70+
}
71+
72+
public static String relativize(File base, File absolute) {
73+
return base.toPath().relativize(absolute.toPath()).toString();
74+
}
75+
76+
public static long copyFile(File input, OutputStream output) throws IOException {
77+
Source source = null;
78+
try {
79+
source = Okio.source(input);
80+
return IOUtils.copy(source, Okio.sink(output));
81+
}
82+
finally {
83+
IOUtils.closeQuietly(source);
84+
}
85+
}
86+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package de.gesellix.docker.builder;
2+
3+
import de.gesellix.util.IOUtils;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.io.File;
8+
import java.io.FileInputStream;
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.ArrayList;
13+
import java.util.Arrays;
14+
import java.util.Collection;
15+
import java.util.Collections;
16+
import java.util.List;
17+
import java.util.Optional;
18+
import java.util.stream.Collectors;
19+
20+
public class DockerignoreFileFilter {
21+
22+
private static final Logger log = LoggerFactory.getLogger(DockerignoreFileFilter.class);
23+
24+
private final GlobsMatcher globsMatcher;
25+
26+
public DockerignoreFileFilter(File base) {
27+
this(base, Collections.emptyList());
28+
}
29+
30+
public DockerignoreFileFilter(File base, List<String> additionalExcludes) {
31+
List<String> dockerignore = getDockerignorePatterns(base);
32+
dockerignore.add(".dockerignore");
33+
dockerignore.addAll(additionalExcludes);
34+
try {
35+
dockerignore = relativize(dockerignore, base);
36+
}
37+
catch (IllegalArgumentException e) {
38+
log.error(String.format("base: %1$s, dockerignore: %2$s", base.getAbsolutePath(), dockerignore), e);
39+
throw e;
40+
}
41+
42+
log.debug("base: {}", base.getAbsolutePath());
43+
log.debug("dockerignore: {}", dockerignore);
44+
globsMatcher = new GlobsMatcher(base, dockerignore);
45+
}
46+
47+
public List<String> getDockerignorePatterns(final File base) {
48+
List<String> result = new ArrayList<>();
49+
File[] files = base.listFiles();
50+
if (files == null || files.length == 0) {
51+
return result;
52+
}
53+
Optional<File> dockerignoreFile = Arrays.stream(files).filter((file -> {
54+
String relativeFileName = relativize(base, file);
55+
return ".dockerignore".equals(relativeFileName);
56+
})).findFirst();
57+
if (!dockerignoreFile.isPresent()) {
58+
return result;
59+
}
60+
try {
61+
Collections.addAll(result, IOUtils.toString(new FileInputStream(dockerignoreFile.get())).split("[\r\n]+"));
62+
return result;
63+
}
64+
catch (IOException e) {
65+
log.error("Couldn't read {}", dockerignoreFile.get());
66+
throw new RuntimeException(e);
67+
}
68+
}
69+
70+
public List<String> relativize(Collection<String> dockerignores, final File base) {
71+
return dockerignores.stream()
72+
.map((String dockerignore) -> new File(dockerignore).isAbsolute() ? relativize(base, new File(dockerignore)) : dockerignore)
73+
.collect(Collectors.toList());
74+
}
75+
76+
public String relativize(File base, File absolute) {
77+
Path basePath = base.getAbsoluteFile().toPath();
78+
Path otherPath = absolute.getAbsoluteFile().toPath();
79+
if (!basePath.getRoot().equals(otherPath.getRoot())) {
80+
// Can occur on Windows, when
81+
// - java temp directory is under C:/
82+
// - project directory is under D:/
83+
return otherPath.toString();
84+
}
85+
86+
return basePath.relativize(otherPath).toString();
87+
}
88+
89+
public List<File> collectFiles(File base) throws IOException {
90+
final List<File> files = new ArrayList<>();
91+
Files.walk(base.toPath())
92+
.filter(p -> Files.isRegularFile(p) && !getGlobsMatcher().matches(p.toFile()))
93+
.forEach(p -> files.add(p.toFile()));
94+
log.debug("filtered list of files: {}", files);
95+
return files;
96+
}
97+
98+
public GlobsMatcher getGlobsMatcher() {
99+
return globsMatcher;
100+
}
101+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package de.gesellix.docker.builder;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.File;
7+
import java.nio.file.FileSystem;
8+
import java.nio.file.FileSystems;
9+
import java.nio.file.Path;
10+
import java.nio.file.PathMatcher;
11+
import java.util.ArrayDeque;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Optional;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
17+
18+
public class GlobsMatcher {
19+
20+
private static final Logger log = LoggerFactory.getLogger(GlobsMatcher.class);
21+
22+
private final File base;
23+
private final List<String> globs;
24+
private List<Matcher> matchers;
25+
26+
public GlobsMatcher(File base, List<String> globs) {
27+
this.base = base;
28+
this.globs = globs;
29+
}
30+
31+
public void initMatchers() {
32+
if (this.matchers == null) {
33+
final FileSystem fileSystem = FileSystems.getDefault();
34+
this.matchers = new ArrayList<>();
35+
globs.stream()
36+
.flatMap(glob -> {
37+
if (glob.endsWith("/")) {
38+
return Stream.of(
39+
new Matcher(fileSystem, glob.replaceAll("/$", "")),
40+
new Matcher(fileSystem, glob.replaceAll("/$", "/**")));
41+
}
42+
else {
43+
return Stream.of(new Matcher(fileSystem, glob));
44+
}
45+
})
46+
.collect(Collectors.toCollection(ArrayDeque::new))
47+
.descendingIterator() // reverse the stream
48+
.forEachRemaining(matchers::add);
49+
if (log.isDebugEnabled()) {
50+
matchers.forEach((m) -> log.debug("pattern: " + m.getPattern()));
51+
}
52+
}
53+
}
54+
55+
public boolean matches(File path) {
56+
initMatchers();
57+
58+
final Path relativePath = base.getAbsoluteFile().toPath().relativize(path.getAbsoluteFile().toPath());
59+
Optional<Matcher> matcher = matchers.stream().filter((m) -> m.matches(relativePath)).findFirst();
60+
if (!matcher.isPresent() && relativePath.getParent() != null) {
61+
matcher = matchers.stream().filter((m) -> m.matches(relativePath.getParent())).findFirst();
62+
}
63+
64+
return matcher.isPresent() && !matcher.get().getNegate();
65+
}
66+
67+
public List<Matcher> getMatchers() {
68+
return matchers;
69+
}
70+
71+
public static class Matcher implements PathMatcher {
72+
73+
private final String pattern;
74+
private final PathMatcher matcher;
75+
private final boolean negate;
76+
77+
public Matcher(FileSystem fileSystem, String pattern) {
78+
// According to https://docs.docker.com/engine/reference/builder/#dockerignore-file
79+
// and https://golang.org/pkg/path/filepath/#Clean we clean paths
80+
// by removing trailing slashes and also by replacing slashes with the path separator.
81+
String replacement = File.separatorChar == '\\' ? "\\\\" : File.separatorChar + "";
82+
log.info("pattern: '{}', separator: '{}', replacement: '{}'", pattern, File.separatorChar, replacement);
83+
this.pattern = String.join(replacement, pattern.replaceAll("/", replacement).split(replacement));
84+
85+
String negation = "!";
86+
this.negate = pattern.startsWith(negation);
87+
if (this.negate) {
88+
String invertedPattern = this.pattern.substring(negation.length());
89+
this.matcher = createGlob(fileSystem, invertedPattern);
90+
}
91+
else {
92+
this.matcher = createGlob(fileSystem, this.pattern);
93+
}
94+
}
95+
96+
public static PathMatcher createGlob(FileSystem fileSystem, final String glob) {
97+
return fileSystem.getPathMatcher("glob:" + glob);
98+
}
99+
100+
@Override
101+
public boolean matches(Path path) {
102+
return matcher.matches(path);
103+
}
104+
105+
public String getPattern() {
106+
return pattern;
107+
}
108+
109+
public boolean getNegate() {
110+
return negate;
111+
}
112+
113+
@Override
114+
public String toString() {
115+
return "matching " + (negate ? "!" : "") + pattern;
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)