Skip to content

Commit 99beef2

Browse files
committed
Merge pull request #280 from bschelberg/master
Fix for #279 Handle multiple source files in ADD command
2 parents d13d364 + 4993c71 commit 99beef2

File tree

7 files changed

+144
-29
lines changed

7 files changed

+144
-29
lines changed

src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,9 @@ private void processAddStatement(DockerfileStatement.Add add) throws IOException
184184

185185
add = add.transform(environmentMap);
186186

187-
if (add.isFileResource()) {
187+
for (String resource : add.getFileResources()) {
188188

189189
File dockerFolder = getDockerFolder();
190-
String resource = add.source;
191190

192191
File src = new File(resource);
193192
if (!src.isAbsolute()) {

src/main/java/com/github/dockerjava/core/dockerfile/DockerfileStatement.java

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,19 @@
22

33
import java.net.URI;
44
import java.net.URISyntaxException;
5+
import java.util.ArrayList;
6+
import java.util.Collection;
57
import java.util.Map;
68
import java.util.regex.Matcher;
79
import java.util.regex.Pattern;
810

911
import com.github.dockerjava.api.DockerClientException;
12+
import com.google.common.base.Function;
1013
import com.google.common.base.Objects;
1114
import com.google.common.base.Optional;
15+
import com.google.common.base.Predicate;
16+
import com.google.common.collect.Collections2;
17+
import org.apache.commons.lang.StringUtils;
1218

1319
/**
1420
* A statement present in a dockerfile.
@@ -72,36 +78,42 @@ public String toString() {
7278
*/
7379
public static class Add extends DockerfileStatement<Add> {
7480

75-
private static final Pattern ADD_OR_COPY_PATTERN = Pattern.compile("^(ADD|COPY)\\s+(.*)\\s+(.*)$");
81+
private static final Pattern ARGUMENT_TOKENIZER = Pattern.compile("(?:\"[^\"]+\")|(\\S+)");
7682

77-
public final String source;
83+
public final Collection<String> sources;
7884

7985
public final String destination;
8086

81-
private Add(String source, String destination) {
82-
this.source = source;
87+
private Add(Collection<String> sources, String destination) {
88+
this.sources = sources;
8389
this.destination = destination;
8490
}
8591

86-
private Add(final Matcher matcher) {
87-
source = matcher.group(2);
88-
destination = matcher.group(3);
89-
}
90-
9192
@Override
92-
public Add transform(Map<String, String> env) {
93-
String resource = filterForEnvironmentVars(env, source).trim();
94-
return new Add(resource, destination);
95-
}
96-
97-
public boolean isFileResource() {
98-
URI uri;
99-
try {
100-
uri = new URI(source);
101-
} catch (URISyntaxException e) {
102-
return false;
103-
}
104-
return uri.getScheme() == null || "file".equals(uri.getScheme());
93+
public Add transform(final Map<String, String> env) {
94+
Collection<String> resources = Collections2.transform(sources, new Function<String, String>() {
95+
@Override
96+
public String apply(String source) {
97+
return filterForEnvironmentVars(env, source).trim();
98+
}
99+
});
100+
return new Add(resources, destination);
101+
}
102+
103+
public Iterable<String> getFileResources() {
104+
return Collections2.filter(sources, new Predicate<String>() {
105+
106+
@Override
107+
public boolean apply(String source) {
108+
URI uri;
109+
try {
110+
uri = new URI(source);
111+
} catch (URISyntaxException e) {
112+
return false;
113+
}
114+
return uri.getScheme() == null || "file".equals(uri.getScheme());
115+
}
116+
});
105117
}
106118

107119
/**
@@ -112,21 +124,37 @@ public boolean isFileResource() {
112124
* @return optional typed item.
113125
*/
114126
public static Optional<Add> create(String statement) {
115-
Matcher matcher = ADD_OR_COPY_PATTERN.matcher(statement.trim());
116-
if (!matcher.find()) {
127+
Matcher argumentMatcher = ARGUMENT_TOKENIZER.matcher(statement.trim());
128+
129+
if (!argumentMatcher.find()) {
117130
return Optional.absent();
118131
}
119132

120-
if (matcher.groupCount() != 3) {
133+
String commandName = argumentMatcher.group();
134+
if (!(StringUtils.equals(commandName, "ADD") || StringUtils.equals(commandName, "COPY"))) {
135+
return Optional.absent();
136+
}
137+
138+
String lastToken = null;
139+
Collection<String> sources = new ArrayList<>();
140+
141+
while (argumentMatcher.find()) {
142+
if (lastToken != null) {
143+
sources.add(lastToken);
144+
}
145+
lastToken = argumentMatcher.group().replaceAll("(^\")|(\"$)", "");
146+
}
147+
148+
if (sources.isEmpty()) {
121149
throw new DockerClientException("Wrong ADD or COPY format");
122150
}
123151

124-
return Optional.of(new Add(matcher));
152+
return Optional.of(new Add(sources, lastToken));
125153
}
126154

127155
@Override
128156
public String toString() {
129-
return Objects.toStringHelper(this).add("source", source).add("destination", destination).toString();
157+
return Objects.toStringHelper(this).add("sources", sources).add("destination", destination).toString();
130158
}
131159
}
132160

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.dockerjava.core.dockerfile;
2+
3+
import com.google.common.base.Function;
4+
import junit.framework.TestCase;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.testng.annotations.Test;
8+
9+
import java.io.File;
10+
import java.io.IOException;
11+
import java.util.Collection;
12+
13+
import static com.google.common.collect.Collections2.transform;
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.hamcrest.Matchers.containsInAnyOrder;
16+
17+
public class DockerfileAddMultipleFilesTest extends TestCase {
18+
19+
private static final Logger log = LoggerFactory.getLogger(DockerfileAddMultipleFilesTest.class);
20+
private static final Function<File, String> TO_FILE_NAMES = new Function<File, String>() {
21+
@Override
22+
public String apply(File file) {
23+
return file.getName();
24+
}
25+
};
26+
27+
@Test
28+
public void testAddMultipleFiles() throws IOException {
29+
File baseDir = new File(Thread.currentThread().getContextClassLoader().getResource("testAddMultipleFiles").getFile());
30+
Dockerfile dockerfile = new Dockerfile(new File(baseDir, "Dockerfile"));
31+
Dockerfile.ScannedResult result = dockerfile.parse();
32+
Collection<String> filesToAdd = transform(result.filesToAdd, TO_FILE_NAMES);
33+
34+
assertThat(filesToAdd, containsInAnyOrder("Dockerfile", "src1", "src2"));
35+
}
36+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.github.dockerjava.core.dockerfile;
2+
3+
import com.github.dockerjava.api.DockerClientException;
4+
import com.google.common.base.Optional;
5+
import junit.framework.TestCase;
6+
import org.hamcrest.Matcher;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import org.testng.annotations.DataProvider;
10+
import org.testng.annotations.Test;
11+
12+
import static org.hamcrest.MatcherAssert.assertThat;
13+
import static org.hamcrest.Matchers.contains;
14+
import static org.hamcrest.Matchers.containsInAnyOrder;
15+
import static org.hamcrest.Matchers.is;
16+
17+
public class DockerfileStatementAddTest extends TestCase {
18+
19+
private static final Logger log = LoggerFactory.getLogger(DockerfileStatementAddTest.class);
20+
21+
@DataProvider(name = "valid scenarios")
22+
public Object[][] validScenarios() {
23+
return new Object[][] {
24+
{"ADD src dest", contains("src"), "dest"},
25+
{"ADD \"src file\" \"dest\"", contains("src file"), "dest"},
26+
{"ADD src\"file dest", contains("src\"file"), "dest"},
27+
{"ADD src1 src2 dest", containsInAnyOrder("src1", "src2"), "dest"},
28+
{"COPY src dest", contains("src"), "dest"},
29+
{"COPY \"src file\" \"dest\"", contains("src file"), "dest"},
30+
{"COPY src\"file dest", contains("src\"file"), "dest"},
31+
{"COPY src1 src2 dest", containsInAnyOrder("src1", "src2"), "dest"}
32+
};
33+
}
34+
35+
@Test(dataProvider = "valid scenarios")
36+
public void testAddOrCopyPattern(String command, Matcher matchesExpectation, String expectedDest) {
37+
Optional<DockerfileStatement.Add> optionalAdd = DockerfileStatement.Add.create(command);
38+
assertThat(optionalAdd.isPresent(), is(true));
39+
assertThat(optionalAdd.get().sources, matchesExpectation);
40+
assertThat(optionalAdd.get().destination, is(expectedDest));
41+
}
42+
43+
@Test(expectedExceptions = { DockerClientException.class })
44+
public void shouldThrowExceptionIfDestNotSpecified() {
45+
DockerfileStatement.Add.create("ADD src");
46+
}
47+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM ubuntu:latest
2+
3+
# Copy multiple source files into the container
4+
5+
ADD src1 src2 /tmp/

src/test/resources/testAddMultipleFiles/src1

Whitespace-only changes.

src/test/resources/testAddMultipleFiles/src2

Whitespace-only changes.

0 commit comments

Comments
 (0)