Skip to content

Commit 059f98c

Browse files
committed
* Bugfix: Image.getByJson() threw AssertionError if the image registry contained a port number.
* Bugfix: ImageBuilder was not including files recursively when parsing the ADD or COPY commands. * Bugfix: ImagePuller.pull() was always returning null. * Replaced Node.getByName() with Node.getById(). * Replaced Swarm.getNodeById() and Swarm.getNodeByName() with Node.getById(). * Renamed Image.getNameToTag() to Image.getNameToTags(). * Renamed Image.getNameToDigest() to Image.getNameToDigests(). * Removed DockerClient parameter from non-static method Image.pusher() since it already has an associated client. * Added Container.LogStreams.getExceptions(). * Added support for Dockerfile commands that span multiple lines, such as ENTRYPOINT []. * Replaced non-static method `Image.pusher(name, tag)` with `Image.pusher(name)` where `name` may include a tag.
1 parent 16915e9 commit 059f98c

File tree

21 files changed

+596
-4157
lines changed

21 files changed

+596
-4157
lines changed

LICENSE-3RD-PARTY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Third-party dependencies
22
* Apache License, Version 2.0:
3-
* [jcommander](https://jcommander.org)
43
* [Jackson-annotations](https://github.com/FasterXML/jackson)
54
* [Jackson-core](https://github.com/FasterXML/jackson-core)
65
* [jackson-databind](https://github.com/FasterXML/jackson)
@@ -15,6 +14,7 @@
1514
* [Apache Commons Compress](https://commons.apache.org/proper/commons-compress/)
1615
* [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
1716
* [Apache Mina SSHD :: OSGi](https://www.apache.org/sshd/sshd-osgi/)
17+
* [jcommander](https://jcommander.org)
1818
* [JCL 1.2 implemented over SLF4J](http://www.slf4j.org)
1919
* [testng](https://testng.org)
2020
* Apache Software License - Version 2.0:

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
# <img src="docs/logo.svg" width=64 height=64 alt="logo"> Docker Java Client
55

6-
[![API](https://img.shields.io/badge/api_docs-5B45D5.svg)](https://cowwoc.github.io/docker/0.7/)
6+
[![API](https://img.shields.io/badge/api_docs-5B45D5.svg)](https://cowwoc.github.io/docker/0.8/)
77
[![Changelog](https://img.shields.io/badge/changelog-A345D5.svg)](docs/changelog.md)
88

99
A Java client for [Docker](https://www.docker.com/).
@@ -14,7 +14,7 @@ To get started, add this Maven dependency:
1414
<dependency>
1515
<groupId>com.github.cowwoc.docker</groupId>
1616
<artifactId>docker</artifactId>
17-
<version>0.7</version>
17+
<version>0.8</version>
1818
</dependency>
1919
```
2020

@@ -51,7 +51,7 @@ class Example
5151

5252
## Getting Started
5353

54-
See the [API documentation](https://cowwoc.github.io/docker/0.7/) for more details.
54+
See the [API documentation](https://cowwoc.github.io/docker/0.8/) for more details.
5555

5656
## Licenses
5757

docs/changelog.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@ See https://github.com/cowwoc/docker/commits/main for a full list.
66

77
* Bugfix: `ImageBuilder` was not including files recursively when parsing the `ADD` or `COPY` commands.
88
* Bugfix: `ImagePuller.pull()` was always returning `null`.
9+
* Bugfix: `Container.getById()` was throwing an exception if no match was found instead of returning `null`.
910
* Replaced `Node.getByName()` with `Node.getById()`.
11+
* Replaced `Container.getByName()` with `Container.getById()`.
1012
* Replaced `Swarm.getNodeById()` and `Swarm.getNodeByName()` with `Node.getById()`.
13+
* Replaced `ContainerNotFoundException` and `ImageNotFoundException` with `ResourceNotFoundException`.
1114
* Renamed `Image.getNameToTag()` to `Image.getNameToTags()`.
1215
* Renamed `Image.getNameToDigest()` to `Image.getNameToDigests()`.
13-
* Removed `DockerClient` parameter from non-static method `Image.pusher()` since it already has an associated
14-
client.
16+
* Replaced non-static method `Image.pusher(client, name, tag)` with `Image.pusher(name)` where `name` may
17+
include a tag. The image already has an associated client, so there is no need to pass it a second time.
1518
* Added `Container.LogStreams.getExceptions()`.
19+
* Added `Network`.
20+
* Added support for Dockerfile commands that span multiple lines such as `ENTRYPOINT []`.
1621

1722
## Version 0.6 - 2025/01/17
1823

docs/logo.svg

Lines changed: 167 additions & 3940 deletions
Loading

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
5151
<jetty.version>12.0.16</jetty.version>
5252
<jackson.version>2.18.2</jackson.version>
53-
<pmd.version>7.9.0</pmd.version>
53+
<pmd.version>7.10.0</pmd.version>
5454
<maven.checkstyle.version>3.6.0</maven.checkstyle.version>
5555
<checkstyle.failOnViolation>true</checkstyle.failOnViolation>
56-
<requirements.version>10.12-SNAPSHOT</requirements.version>
56+
<requirements.version>10.12</requirements.version>
5757
</properties>
5858

5959
<dependencies>
@@ -111,7 +111,7 @@
111111
<dependency>
112112
<groupId>org.testng</groupId>
113113
<artifactId>testng</artifactId>
114-
<version>7.10.2</version>
114+
<version>7.11.0</version>
115115
<scope>test</scope>
116116
</dependency>
117117
</dependencies>

src/main/java/com/github/cowwoc/docker/exception/ImageNotFoundException.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/main/java/com/github/cowwoc/docker/exception/ContainerNotFoundException.java renamed to src/main/java/com/github/cowwoc/docker/exception/ResourceNotFoundException.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
import java.io.IOException;
44
import java.io.Serial;
55

6+
import static com.github.cowwoc.requirements10.java.DefaultJavaValidators.requireThat;
7+
68
/**
7-
* Thrown if a referenced container does not exist.
9+
* Thrown if a referenced resource does not exist.
810
*/
9-
public class ContainerNotFoundException extends IOException
11+
public class ResourceNotFoundException extends IOException
1012
{
1113
@Serial
1214
private static final long serialVersionUID = 0L;
@@ -16,7 +18,7 @@ public class ContainerNotFoundException extends IOException
1618
*
1719
* @param message an explanation of what went wrong
1820
*/
19-
public ContainerNotFoundException(String message)
21+
public ResourceNotFoundException(String message)
2022
{
2123
super(message);
2224
}
@@ -27,8 +29,9 @@ public ContainerNotFoundException(String message)
2729
* @param cause the underlying exception
2830
* @throws NullPointerException if {@code cause} is null
2931
*/
30-
public ContainerNotFoundException(Throwable cause)
32+
public ResourceNotFoundException(Throwable cause)
3133
{
3234
super(cause);
35+
requireThat(cause, "cause").isNotNull();
3336
}
3437
}

src/main/java/com/github/cowwoc/docker/internal/client/MainInternalClient.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ public ContentResponse send(Request request) throws IOException, TimeoutExceptio
141141
{
142142
Throwable cause = e.getCause();
143143
if (cause instanceof IOException ioe)
144-
throw ioe;
144+
{
145+
// Convert ExecutionException to IOException. We can't throw the cause directly because it does not
146+
// contain any stack traces from the current thread. Jetty executes requests in a separate thread.
147+
throw new IOException(ioe);
148+
}
145149
throw new AssertionError(toString(request), e);
146150
}
147151
}

src/main/java/com/github/cowwoc/docker/internal/util/AsyncResponseListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public abstract class AsyncResponseListener implements Response.Listener
3333
* A CountDownLatch that counts down to zero once the response is ready for processing.
3434
*/
3535
protected final CountDownLatch responseReady = new CountDownLatch(1);
36-
protected final Logger log = LoggerFactory.getLogger(getClass());
36+
protected final Logger log = LoggerFactory.getLogger(AsyncResponseListener.class);
3737

3838
/**
3939
* Creates a new instance.

src/main/java/com/github/cowwoc/docker/internal/util/DockerfileParser.java

Lines changed: 113 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.github.cowwoc.docker.internal.util;
22

3+
import java.io.BufferedReader;
34
import java.io.IOException;
45
import java.nio.file.Files;
56
import java.nio.file.Path;
6-
import java.util.List;
77
import java.util.Locale;
88
import java.util.Set;
99
import java.util.function.Predicate;
@@ -17,54 +17,140 @@
1717
public final class DockerfileParser extends GlobParser
1818
{
1919
/**
20-
* Creates a new instance.
20+
* The file being parsed.
2121
*/
22-
public DockerfileParser()
23-
{
24-
}
22+
private final Path dockerfile;
23+
/**
24+
* The directory that contains the Dockerfile.
25+
*/
26+
private final Path directoryOfDockerfile;
27+
/**
28+
* The build context.
29+
*/
30+
private final Path buildContext;
31+
/**
32+
* {@code true} if the previous line ended with a backslash line continuation.
33+
*/
34+
private boolean lineEndedWithContinuation;
35+
/**
36+
* The command being constructed by the parser.
37+
* <p>
38+
* For multi-line commands, this accumulates fragments until the entire command is complete and ready for
39+
* processing.
40+
*/
41+
private final StringBuilder command = new StringBuilder();
2542

2643
/**
44+
* Creates a new Dockerfile parser.
45+
*
2746
* @param dockerfile the path of a {@code Dockerfile}
2847
* @param buildContext the path of the build context
29-
* @return a {@code Predicate<Path>} that returns {@code true} if a path should be included from the build
30-
* context
3148
* @throws NullPointerException if any of the arguments are null
32-
* @throws IOException if an error occurs while reading the file
3349
*/
34-
public Predicate<Path> parse(Path dockerfile, Path buildContext) throws IOException
50+
public DockerfileParser(Path dockerfile, Path buildContext)
3551
{
3652
// Evaluate Dockerfile relative to the build context
3753
assert that(dockerfile, "dockerfile").isRelative().elseThrow();
3854
assert that(buildContext, "buildContext").isAbsolute().elseThrow();
55+
this.dockerfile = dockerfile;
3956

4057
Path directoryOfDockerfile = dockerfile.getParent();
4158
if (directoryOfDockerfile == null)
4259
directoryOfDockerfile = Path.of("");
60+
61+
this.directoryOfDockerfile = directoryOfDockerfile;
62+
this.buildContext = buildContext;
63+
}
64+
65+
/**
66+
* @return a {@code Predicate<Path>} that returns {@code true} if a path should be included from the build
67+
* context
68+
* @throws IOException if an error occurs while reading the file
69+
*/
70+
public Predicate<Path> parse() throws IOException
71+
{
4372
patterns.add(new PatternPredicate("", buildContext, false, "it is the build context"));
44-
addPathsLeadingToDockerfile(dockerfile, directoryOfDockerfile, buildContext);
73+
addPathsLeadingToDockerfile(dockerfile);
4574

46-
List<String> lines = Files.readAllLines(buildContext.resolve(dockerfile));
4775
int lineNumber = 0;
48-
for (String line : lines)
76+
try (BufferedReader reader = Files.newBufferedReader(buildContext.resolve(dockerfile)))
77+
{
78+
while (true)
79+
{
80+
++lineNumber;
81+
String line = reader.readLine();
82+
if (line == null)
83+
break;
84+
processLine(line, lineNumber);
85+
}
86+
}
87+
if (lineEndedWithContinuation)
88+
throw new IllegalArgumentException("Unexpected end of line: " + command);
89+
if (!command.isEmpty())
4990
{
50-
++lineNumber;
51-
// https://docs.docker.com/build/concepts/dockerfile/#dockerfile-syntax
52-
if (line.startsWith("#"))
53-
continue;
54-
processLine(line, lineNumber, directoryOfDockerfile, buildContext);
91+
// Run the last command
92+
processCommand(lineNumber);
5593
}
5694
return getPredicate(patterns);
5795
}
5896

5997
/**
60-
* Adds predicates for the Dockerfile and the directories leading to it.
98+
* Processes a single line of a Dockerfile command.
6199
*
62-
* @param dockerfile the path of the Dockerfile relative to the build context
63-
* @param directoryOfDockerfile the directory of the Dockerfile
64-
* @param buildContext the build context
100+
* @param line the line
101+
* @param lineNumber the line number
65102
* @throws NullPointerException if any of the arguments are null
66103
*/
67-
private void addPathsLeadingToDockerfile(Path dockerfile, Path directoryOfDockerfile, Path buildContext)
104+
private void processLine(String line, int lineNumber)
105+
{
106+
// https://docs.docker.com/build/concepts/dockerfile/#dockerfile-syntax
107+
if (line.startsWith("#"))
108+
return;
109+
lineEndedWithContinuation = line.endsWith("\\");
110+
if (lineEndedWithContinuation)
111+
{
112+
command.append(line, 0, line.length() - 1);
113+
return;
114+
}
115+
116+
String[] tokens = line.split("\\s+");
117+
// Commands that may span multiple lines without a backslash at the end of the line
118+
boolean commandMaySpanLines = switch (tokens[0])
119+
{
120+
case "LABEL", "ENV", "ARG", "VOLUME" -> true;
121+
default -> false;
122+
};
123+
if (commandMaySpanLines)
124+
{
125+
if (!command.isEmpty())
126+
{
127+
processCommand(lineNumber);
128+
command.delete(0, command.length());
129+
}
130+
command.append(line);
131+
return;
132+
}
133+
if (line.startsWith(" "))
134+
{
135+
if (command.isEmpty())
136+
throw new IllegalArgumentException("Invalid command on line " + line + ": " + line);
137+
// The command is spanning multiple lines
138+
command.append(line);
139+
return;
140+
}
141+
// We have detected the end of a command
142+
processCommand(lineNumber);
143+
command.delete(0, command.length());
144+
command.append(line);
145+
}
146+
147+
/**
148+
* Adds predicates for the Dockerfile and the directories leading to it.
149+
*
150+
* @param dockerfile the path of the Dockerfile relative to the build context
151+
* @throws NullPointerException if {@code dockerfile} is null
152+
*/
153+
private void addPathsLeadingToDockerfile(Path dockerfile)
68154
{
69155
// Include the Dockerfile and the directories leading to it
70156
patterns.add(new PatternPredicate(dockerfile, buildContext, false, "it is the Dockerfile"));
@@ -99,25 +185,17 @@ private Predicate<Path> getPredicate(Set<PatternPredicate> patterns)
99185
/**
100186
* Processes a single line.
101187
*
102-
* @param line the line
103-
* @param lineNumber the number of the line
104-
* @param directoryOfDockerfile the directory that contains the Dockerfile
105-
* @param buildContext the build context
188+
* @param lineNumber the number of the line
106189
* @throws NullPointerException if any of the values are null
107190
* @throws IllegalArgumentException if the Dockerfile references paths outside the build context
108191
*/
109-
private void processLine(String line, int lineNumber, Path directoryOfDockerfile,
110-
Path buildContext)
192+
private void processCommand(int lineNumber)
111193
{
112-
// Evaluate commands relative to the build context
113-
assert that(directoryOfDockerfile, "directoryOfDockerfile").isRelative().elseThrow();
114-
assert that(buildContext, "buildContext").isAbsolute().elseThrow();
115-
116-
line = line.strip();
117-
if (line.isEmpty())
194+
String command = this.command.toString().strip();
195+
if (command.isEmpty())
118196
return;
119197

120-
String[] tokens = line.split("\\s+");
198+
String[] tokens = command.split("\\s+");
121199
switch (tokens[0])
122200
{
123201
case "ADD", "COPY" ->

0 commit comments

Comments
 (0)