diff --git a/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java b/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java index fe1a18f68..9513678d7 100644 --- a/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java +++ b/src/main/java/com/github/dockerjava/core/dockerfile/Dockerfile.java @@ -184,10 +184,9 @@ private void processAddStatement(DockerfileStatement.Add add) throws IOException add = add.transform(environmentMap); - if (add.isFileResource()) { + for (String resource : add.getFileResources()) { File dockerFolder = getDockerFolder(); - String resource = add.source; File src = new File(resource); if (!src.isAbsolute()) { diff --git a/src/main/java/com/github/dockerjava/core/dockerfile/DockerfileStatement.java b/src/main/java/com/github/dockerjava/core/dockerfile/DockerfileStatement.java index a3ceab2b9..b37f5b4f7 100644 --- a/src/main/java/com/github/dockerjava/core/dockerfile/DockerfileStatement.java +++ b/src/main/java/com/github/dockerjava/core/dockerfile/DockerfileStatement.java @@ -2,13 +2,19 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.github.dockerjava.api.DockerClientException; +import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import org.apache.commons.lang.StringUtils; /** * A statement present in a dockerfile. @@ -72,36 +78,42 @@ public String toString() { */ public static class Add extends DockerfileStatement { - private static final Pattern ADD_OR_COPY_PATTERN = Pattern.compile("^(ADD|COPY)\\s+(.*)\\s+(.*)$"); + private static final Pattern ARGUMENT_TOKENIZER = Pattern.compile("(?:\"[^\"]+\")|(\\S+)"); - public final String source; + public final Collection sources; public final String destination; - private Add(String source, String destination) { - this.source = source; + private Add(Collection sources, String destination) { + this.sources = sources; this.destination = destination; } - private Add(final Matcher matcher) { - source = matcher.group(2); - destination = matcher.group(3); - } - @Override - public Add transform(Map env) { - String resource = filterForEnvironmentVars(env, source).trim(); - return new Add(resource, destination); - } - - public boolean isFileResource() { - URI uri; - try { - uri = new URI(source); - } catch (URISyntaxException e) { - return false; - } - return uri.getScheme() == null || "file".equals(uri.getScheme()); + public Add transform(final Map env) { + Collection resources = Collections2.transform(sources, new Function() { + @Override + public String apply(String source) { + return filterForEnvironmentVars(env, source).trim(); + } + }); + return new Add(resources, destination); + } + + public Iterable getFileResources() { + return Collections2.filter(sources, new Predicate() { + + @Override + public boolean apply(String source) { + URI uri; + try { + uri = new URI(source); + } catch (URISyntaxException e) { + return false; + } + return uri.getScheme() == null || "file".equals(uri.getScheme()); + } + }); } /** @@ -112,21 +124,37 @@ public boolean isFileResource() { * @return optional typed item. */ public static Optional create(String statement) { - Matcher matcher = ADD_OR_COPY_PATTERN.matcher(statement.trim()); - if (!matcher.find()) { + Matcher argumentMatcher = ARGUMENT_TOKENIZER.matcher(statement.trim()); + + if (!argumentMatcher.find()) { return Optional.absent(); } - if (matcher.groupCount() != 3) { + String commandName = argumentMatcher.group(); + if (!(StringUtils.equals(commandName, "ADD") || StringUtils.equals(commandName, "COPY"))) { + return Optional.absent(); + } + + String lastToken = null; + Collection sources = new ArrayList<>(); + + while (argumentMatcher.find()) { + if (lastToken != null) { + sources.add(lastToken); + } + lastToken = argumentMatcher.group().replaceAll("(^\")|(\"$)", ""); + } + + if (sources.isEmpty()) { throw new DockerClientException("Wrong ADD or COPY format"); } - return Optional.of(new Add(matcher)); + return Optional.of(new Add(sources, lastToken)); } @Override public String toString() { - return Objects.toStringHelper(this).add("source", source).add("destination", destination).toString(); + return Objects.toStringHelper(this).add("sources", sources).add("destination", destination).toString(); } } diff --git a/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileAddMultipleFilesTest.java b/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileAddMultipleFilesTest.java new file mode 100644 index 000000000..65af93774 --- /dev/null +++ b/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileAddMultipleFilesTest.java @@ -0,0 +1,36 @@ +package com.github.dockerjava.core.dockerfile; + +import com.google.common.base.Function; +import junit.framework.TestCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import static com.google.common.collect.Collections2.transform; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +public class DockerfileAddMultipleFilesTest extends TestCase { + + private static final Logger log = LoggerFactory.getLogger(DockerfileAddMultipleFilesTest.class); + private static final Function TO_FILE_NAMES = new Function() { + @Override + public String apply(File file) { + return file.getName(); + } + }; + + @Test + public void testAddMultipleFiles() throws IOException { + File baseDir = new File(Thread.currentThread().getContextClassLoader().getResource("testAddMultipleFiles").getFile()); + Dockerfile dockerfile = new Dockerfile(new File(baseDir, "Dockerfile")); + Dockerfile.ScannedResult result = dockerfile.parse(); + Collection filesToAdd = transform(result.filesToAdd, TO_FILE_NAMES); + + assertThat(filesToAdd, containsInAnyOrder("Dockerfile", "src1", "src2")); + } +} diff --git a/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileStatementAddTest.java b/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileStatementAddTest.java new file mode 100644 index 000000000..691137afd --- /dev/null +++ b/src/test/java/com/github/dockerjava/core/dockerfile/DockerfileStatementAddTest.java @@ -0,0 +1,47 @@ +package com.github.dockerjava.core.dockerfile; + +import com.github.dockerjava.api.DockerClientException; +import com.google.common.base.Optional; +import junit.framework.TestCase; +import org.hamcrest.Matcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.is; + +public class DockerfileStatementAddTest extends TestCase { + + private static final Logger log = LoggerFactory.getLogger(DockerfileStatementAddTest.class); + + @DataProvider(name = "valid scenarios") + public Object[][] validScenarios() { + return new Object[][] { + {"ADD src dest", contains("src"), "dest"}, + {"ADD \"src file\" \"dest\"", contains("src file"), "dest"}, + {"ADD src\"file dest", contains("src\"file"), "dest"}, + {"ADD src1 src2 dest", containsInAnyOrder("src1", "src2"), "dest"}, + {"COPY src dest", contains("src"), "dest"}, + {"COPY \"src file\" \"dest\"", contains("src file"), "dest"}, + {"COPY src\"file dest", contains("src\"file"), "dest"}, + {"COPY src1 src2 dest", containsInAnyOrder("src1", "src2"), "dest"} + }; + } + + @Test(dataProvider = "valid scenarios") + public void testAddOrCopyPattern(String command, Matcher matchesExpectation, String expectedDest) { + Optional optionalAdd = DockerfileStatement.Add.create(command); + assertThat(optionalAdd.isPresent(), is(true)); + assertThat(optionalAdd.get().sources, matchesExpectation); + assertThat(optionalAdd.get().destination, is(expectedDest)); + } + + @Test(expectedExceptions = { DockerClientException.class }) + public void shouldThrowExceptionIfDestNotSpecified() { + DockerfileStatement.Add.create("ADD src"); + } +} diff --git a/src/test/resources/testAddMultipleFiles/Dockerfile b/src/test/resources/testAddMultipleFiles/Dockerfile new file mode 100644 index 000000000..52c5bfc6c --- /dev/null +++ b/src/test/resources/testAddMultipleFiles/Dockerfile @@ -0,0 +1,5 @@ +FROM ubuntu:latest + +# Copy multiple source files into the container + +ADD src1 src2 /tmp/ diff --git a/src/test/resources/testAddMultipleFiles/src1 b/src/test/resources/testAddMultipleFiles/src1 new file mode 100644 index 000000000..e69de29bb diff --git a/src/test/resources/testAddMultipleFiles/src2 b/src/test/resources/testAddMultipleFiles/src2 new file mode 100644 index 000000000..e69de29bb