X Tutup
Skip to content

Commit d94b93f

Browse files
Merge pull request #95 from signalfx/master
Add support for .dockerignore files
2 parents a6ac91a + f98b904 commit d94b93f

File tree

20 files changed

+518
-5
lines changed

20 files changed

+518
-5
lines changed

src/main/java/com/github/dockerjava/core/CompressArchiveUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static File archiveTARFiles(File base, Iterable<File> files, String archi
3737
return tarFile;
3838
}
3939

40-
private static String relativize(File base, File absolute) {
40+
public static String relativize(File base, File absolute) {
4141
String relative = base.toURI().relativize(absolute.toURI()).getPath();
4242
return relative;
4343
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/**
2+
* Copyright (C) 2014 SignalFuse, Inc.
3+
*/
4+
package com.github.dockerjava.core;
5+
6+
import java.io.File;
7+
import java.util.List;
8+
9+
import org.apache.commons.lang.StringUtils;
10+
11+
/**
12+
* Implementation of golang's file.Match
13+
*
14+
* Match returns true if name matches the shell file name pattern. The pattern syntax is:
15+
*
16+
* <pre>
17+
* pattern:
18+
* { term }
19+
* term:
20+
* '*' matches any sequence of non-Separator characters
21+
* '?' matches any single non-Separator character
22+
* '[' [ '^' ] { character-range } ']'
23+
* character class (must be non-empty)
24+
* c matches character c (c != '*', '?', '\\', '[')
25+
* '\\' c matches character c
26+
*
27+
* character-range:
28+
* c matches character c (c != '\\', '-', ']')
29+
* '\\' c matches character c
30+
* lo '-' hi matches character c for lo <= c <= hi
31+
*
32+
* Match requires pattern to match all of name, not just a substring.
33+
* The only possible returned error is ErrBadPattern, when pattern
34+
* is malformed.
35+
*
36+
* On Windows, escaping is disabled. Instead, '\\' is treated as
37+
* path separator.
38+
* </pre>
39+
*
40+
* @author tedo
41+
*
42+
*/
43+
public class GoLangFileMatch {
44+
45+
public static final boolean IS_WINDOWS = File.separatorChar == '\\';
46+
47+
public static boolean match(List<String> patterns, File file) {
48+
return match(patterns, file.getPath());
49+
}
50+
51+
public static boolean match(String pattern, File file) {
52+
return match(pattern, file.getPath());
53+
}
54+
55+
public static boolean match(List<String> patterns, String name) {
56+
for (String pattern : patterns) {
57+
if (match(pattern, name)) {
58+
return true;
59+
}
60+
}
61+
return false;
62+
}
63+
64+
public static boolean match(String pattern, String name) {
65+
Pattern: while (!pattern.isEmpty()) {
66+
ScanResult scanResult = scanChunk(pattern);
67+
pattern = scanResult.pattern;
68+
if (scanResult.star && StringUtils.isEmpty(scanResult.chunk)) {
69+
// Trailing * matches rest of string unless it has a /.
70+
return name.indexOf(File.separatorChar) < 0;
71+
}
72+
// Look for match at current position.
73+
String matchResult = matchChunk(scanResult.chunk, name);
74+
75+
// if we're the last chunk, make sure we've exhausted the name
76+
// otherwise we'll give a false result even if we could still match
77+
// using the star
78+
if (matchResult != null && (matchResult.isEmpty() || !pattern.isEmpty())) {
79+
name = matchResult;
80+
continue;
81+
}
82+
if (scanResult.star) {
83+
for (int i = 0; i < name.length() && name.charAt(i) != File.separatorChar; i++) {
84+
matchResult = matchChunk(scanResult.chunk, name.substring(i + 1));
85+
if (matchResult != null) {
86+
// if we're the last chunk, make sure we exhausted the name
87+
if (pattern.isEmpty() && !matchResult.isEmpty()) {
88+
continue;
89+
}
90+
name = matchResult;
91+
continue Pattern;
92+
}
93+
}
94+
}
95+
return false;
96+
}
97+
return name.isEmpty();
98+
}
99+
100+
static ScanResult scanChunk(String pattern) {
101+
boolean star = false;
102+
if (!pattern.isEmpty() && pattern.charAt(0) == '*') {
103+
pattern = pattern.substring(1);
104+
star = true;
105+
}
106+
boolean inRange = false;
107+
int i;
108+
Scan: for (i = 0; i < pattern.length(); i++) {
109+
switch (pattern.charAt(i)) {
110+
case '\\': {
111+
if (!IS_WINDOWS) {
112+
// error check handled in matchChunk: bad pattern.
113+
if (i + 1 < pattern.length()) {
114+
i++;
115+
}
116+
}
117+
break;
118+
}
119+
case '[':
120+
inRange = true;
121+
break;
122+
case ']':
123+
inRange = false;
124+
break;
125+
case '*':
126+
if (!inRange) {
127+
break Scan;
128+
}
129+
}
130+
}
131+
return new ScanResult(star, pattern.substring(0, i), pattern.substring(i));
132+
}
133+
134+
static String matchChunk(String chunk, String s) {
135+
int chunkLength = chunk.length();
136+
int chunkOffset = 0;
137+
int sLength = s.length();
138+
int sOffset = 0;
139+
char r;
140+
while (chunkOffset < chunkLength) {
141+
if (sOffset == sLength) {
142+
return null;
143+
}
144+
switch (chunk.charAt(chunkOffset)) {
145+
case '[':
146+
r = s.charAt(sOffset);
147+
sOffset++;
148+
chunkOffset++;
149+
// We can't end right after '[', we're expecting at least
150+
// a closing bracket and possibly a caret.
151+
if (chunkOffset == chunkLength) {
152+
throw new GoLangFileMatchException();
153+
}
154+
// possibly negated
155+
boolean negated = chunk.charAt(chunkOffset) == '^';
156+
if (negated) {
157+
chunkOffset++;
158+
}
159+
// parse all ranges
160+
boolean match = false;
161+
int nrange = 0;
162+
while (true) {
163+
if (chunkOffset < chunkLength && chunk.charAt(chunkOffset) == ']' && nrange > 0) {
164+
chunkOffset++;
165+
break;
166+
}
167+
GetEscResult result = getEsc(chunk, chunkOffset, chunkLength);
168+
char lo = result.lo;
169+
char hi = lo;
170+
chunkOffset = result.chunkOffset;
171+
if (chunk.charAt(chunkOffset) == '-') {
172+
result = getEsc(chunk, ++chunkOffset, chunkLength);
173+
chunkOffset = result.chunkOffset;
174+
hi = result.lo;
175+
}
176+
if (lo <= r && r <= hi) {
177+
match = true;
178+
}
179+
nrange++;
180+
}
181+
if (match == negated) {
182+
return null;
183+
}
184+
break;
185+
186+
case '?':
187+
if (s.charAt(sOffset) == File.separatorChar) {
188+
return null;
189+
}
190+
sOffset++;
191+
chunkOffset++;
192+
break;
193+
case '\\':
194+
if (!IS_WINDOWS) {
195+
chunkOffset++;
196+
if (chunkOffset == chunkLength) {
197+
throw new GoLangFileMatchException();
198+
}
199+
}
200+
// fallthrough
201+
default:
202+
if (chunk.charAt(chunkOffset) != s.charAt(sOffset)) {
203+
return null;
204+
}
205+
sOffset++;
206+
chunkOffset++;
207+
}
208+
}
209+
return s.substring(sOffset);
210+
}
211+
212+
static GetEscResult getEsc(String chunk, int chunkOffset, int chunkLength) {
213+
if (chunkOffset == chunkLength) {
214+
throw new GoLangFileMatchException();
215+
}
216+
char r = chunk.charAt(chunkOffset);
217+
if (r == '-' || r == ']') {
218+
throw new GoLangFileMatchException();
219+
}
220+
if (r == '\\' && !IS_WINDOWS) {
221+
chunkOffset++;
222+
if (chunkOffset == chunkLength) {
223+
throw new GoLangFileMatchException();
224+
}
225+
226+
}
227+
r = chunk.charAt(chunkOffset);
228+
chunkOffset++;
229+
if (chunkOffset == chunkLength) {
230+
throw new GoLangFileMatchException();
231+
}
232+
return new GetEscResult(r, chunkOffset);
233+
}
234+
235+
private static final class ScanResult {
236+
public boolean star;
237+
public String chunk;
238+
public String pattern;
239+
240+
public ScanResult(boolean star, String chunk, String pattern) {
241+
this.star = star;
242+
this.chunk = chunk;
243+
this.pattern = pattern;
244+
}
245+
}
246+
247+
private static final class GetEscResult {
248+
public char lo;
249+
public int chunkOffset;
250+
251+
public GetEscResult(char lo, int chunkOffset) {
252+
this.lo = lo;
253+
this.chunkOffset = chunkOffset;
254+
}
255+
}
256+
257+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (C) 2014 SignalFuse, Inc.
3+
*/
4+
package com.github.dockerjava.core;
5+
6+
public class GoLangFileMatchException extends IllegalArgumentException {
7+
8+
private static final long serialVersionUID = -1204971075600864898L;
9+
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (C) 2014 SignalFuse, Inc.
3+
*/
4+
package com.github.dockerjava.core;
5+
6+
import java.io.File;
7+
import java.util.List;
8+
9+
import org.apache.commons.io.filefilter.AbstractFileFilter;
10+
11+
public class GoLangMatchFileFilter extends AbstractFileFilter {
12+
13+
private final List<String> patterns;
14+
15+
16+
public GoLangMatchFileFilter(List<String> patterns) {
17+
super();
18+
this.patterns = patterns;
19+
}
20+
21+
@Override
22+
public boolean accept(File file) {
23+
return !GoLangFileMatch.match(patterns, file);
24+
}
25+
26+
27+
}

src/main/java/com/github/dockerjava/core/command/BuildImageCmdImpl.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@
1414
import java.util.regex.Pattern;
1515

1616
import org.apache.commons.io.FileUtils;
17+
import org.apache.commons.io.FilenameUtils;
18+
import org.apache.commons.io.filefilter.TrueFileFilter;
1719

1820
import com.github.dockerjava.api.DockerClientException;
1921
import com.github.dockerjava.api.command.BuildImageCmd;
2022
import com.github.dockerjava.core.CompressArchiveUtil;
21-
23+
import com.github.dockerjava.core.GoLangFileMatch;
24+
import com.github.dockerjava.core.GoLangFileMatchException;
25+
import com.github.dockerjava.core.GoLangMatchFileFilter;
2226
import com.google.common.base.Preconditions;
2327

2428
/**
@@ -158,6 +162,30 @@ protected InputStream buildDockerFolderTar(File dockerFolder) {
158162
"Dockerfile %s is empty", dockerFile));
159163
}
160164

165+
List<String> ignores = new ArrayList<String>();
166+
File dockerIgnoreFile = new File(dockerFolder, ".dockerignore");
167+
if (dockerIgnoreFile.exists()) {
168+
int lineNumber = 0;
169+
List<String> dockerIgnoreFileContent = FileUtils.readLines(dockerIgnoreFile);
170+
for (String pattern: dockerIgnoreFileContent) {
171+
lineNumber++;
172+
pattern = pattern.trim();
173+
if (pattern.isEmpty()) {
174+
continue; // skip empty lines
175+
}
176+
pattern = FilenameUtils.normalize(pattern);
177+
try {
178+
// validate pattern and make sure we aren't excluding Dockerfile
179+
if (GoLangFileMatch.match(pattern, "Dockerfile")) {
180+
throw new DockerClientException(
181+
String.format("Dockerfile is excluded by pattern '%s' on line %s in .dockerignore file", pattern, lineNumber));
182+
}
183+
ignores.add(pattern);
184+
} catch (GoLangFileMatchException e) {
185+
throw new DockerClientException(String.format("Invalid pattern '%s' on line %s in .dockerignore file", pattern, lineNumber));
186+
}
187+
}
188+
}
161189
List<File> filesToAdd = new ArrayList<File>();
162190
filesToAdd.add(dockerFile);
163191

@@ -215,10 +243,13 @@ protected InputStream buildDockerFolderTar(File dockerFolder) {
215243
"Source file %s doesn't exist", src));
216244
}
217245
if (src.isDirectory()) {
218-
filesToAdd.addAll(FileUtils.listFiles(src, null,
219-
true));
220-
} else {
246+
filesToAdd.addAll(FileUtils.listFiles(src,
247+
new GoLangMatchFileFilter(ignores), TrueFileFilter.INSTANCE));
248+
} else if (!GoLangFileMatch.match(ignores, CompressArchiveUtil.relativize(dockerFolder, src))){
221249
filesToAdd.add(src);
250+
} else {
251+
throw new DockerClientException(String.format(
252+
"Source file %s is excluded by .dockerignore file", src));
222253
}
223254
}
224255
}

0 commit comments

Comments
 (0)
X Tutup