diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index d03efd7d9..d03b3f5f5 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -3,15 +3,15 @@ name-template: '$NEXT_PATCH_VERSION 🌈'
categories:
- title: '🚀 Features'
labels:
- - 'feature'
- - 'enhancement'
+ - 'type/feature'
+ - title: '📈 Enhancements'
+ labels:
+ - 'type/enhancement'
- title: '🐛 Bug Fixes'
labels:
- - 'fix'
- - 'bugfix'
- - 'bug'
+ - 'type/bug'
- title: '🧰 Maintenance'
- label: 'chore'
+ label: 'type/housekeeping'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
template: |
## Changes
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b9e2bc8b4..af27a5718 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,7 +3,8 @@ name: Release
on:
release:
types:
- - created
+ - prereleased
+ - released
jobs:
build:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..5072b0864
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,58 @@
+# Build with Maven
+
+#### Prerequisites:
+
+* Java min 1.8
+* Maven 3
+
+Build and run integration tests as follows:
+
+ $ mvn clean install
+
+If you do not have access to a Docker server or just want to execute the build quickly, you can run the build without the integration tests:
+
+ $ mvn clean install -DskipITs
+
+By default the docker engine is using local UNIX sockets for communication with the docker CLI so docker-java
+client also uses UNIX domain sockets to connect to the docker daemon by default. To make the docker daemon listening on a TCP (http/https) port you have to configure it by setting the DOCKER_OPTS environment variable to something like the following:
+
+ DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"
+
+More details about setting up Docker Engine can be found in the official documentation: https://docs.docker.com/engine/admin/
+
+To force docker-java to use TCP (http) configure the following (see [Configuration](https://github.com/docker-java/docker-java#configuration) for details):
+
+ DOCKER_HOST=tcp://127.0.0.1:2375
+
+For secure tls (https) communication:
+
+ DOCKER_HOST=tcp://127.0.0.1:2376
+ DOCKER_TLS_VERIFY=1
+ DOCKER_CERT_PATH=/Users/marcus/.docker/machine/machines/docker-1.11.2
+
+
+# Code Design
+ * Model is based on Objects and not primitives that allows nullify requests and have null values for data
+ that wasn't provided by docker daemon.
+ * For null safeness findbugs annotations are used.
+ ** Every method that may return `null` (and we are unsure in any fields as docker daemon may change something)
+ should be annotated with `@CheckForNull` return qualifier from `javax.annotation` package.
+ ** Methods that can't return `null` must be annotated with `@Nonnull`.
+ ** The same for Arguments.
+ ** `@Nullable` must be used only for changing inherited (other typed) qualifier.
+ * Setters in builder style must be prefixed with `withXX`.
+ * All classes should provide `toString()` `equals()` and `hashCode()` defined methods.
+ * Javadocs
+ ** Provide full information on field:
+ *** For models define API version with `@since {@link RemoteApiVersion#VERSION_1_X}`.
+ ** getters/setters should refernce to field `@see #$field`.
+ * If it is `Serializable` it shall have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field shall be `1L`.
+
+# Coding style
+ * Some initial styling already enforced with checkstyle. Please aim for consistency with the existing code.
+
+# Testing
+ * Unit tests for serder (serialization-deserialization).
+ * Integration tests for commands.
+ * If model object has builders, then fill it with data and compare by `equals()` with expected response
+ from docker daemon. If failed, then some fields mappings are wrong.
\ No newline at end of file
diff --git a/README.md b/README.md
index a67905b35..c60907548 100644
--- a/README.md
+++ b/README.md
@@ -1,143 +1,9 @@
[](https://gitter.im/docker-java/docker-java?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[](https://mvnrepository.com/artifact/com.github.docker-java/docker-java)
-[](https://bintray.com/kostyasha/maven/com.github.docker-java%3Adocker-java/_latestVersion)
-[](https://travis-ci.org/docker-java/docker-java)
-[](https://scan.coverity.com/projects/9177)
[](http://codecov.io/github/docker-java/docker-java?branch=master)
[](https://github.com/docker-java/docker-java/blob/master/LICENSE)
-
-
# docker-java
Java API client for [Docker](http://docs.docker.io/ "Docker")
-[Changelog](https://github.com/docker-java/docker-java/blob/master/CHANGELOG.md)
-[Wiki](https://github.com/docker-java/docker-java/wiki)
-
-## Build with Maven
-
-###### Prerequisites:
-
-* Java min 1.8
-* Maven 3
-
-Build and run integration tests as follows:
-
- $ mvn clean install
-
-If you do not have access to a Docker server or just want to execute the build quickly, you can run the build without the integration tests:
-
- $ mvn clean install -DskipITs
-
-By default the docker engine is using local UNIX sockets for communication with the docker CLI so docker-java
-client also uses UNIX domain sockets to connect to the docker daemon by default. To make the docker daemon listening on a TCP (http/https) port you have to configure it by setting the DOCKER_OPTS environment variable to something like the following:
-
- DOCKER_OPTS="-H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock"
-
-More details about setting up Docker Engine can be found in the official documentation: https://docs.docker.com/engine/admin/
-
-To force docker-java to use TCP (http) configure the following (see [Configuration](https://github.com/docker-java/docker-java#configuration) for details):
-
- DOCKER_HOST=tcp://127.0.0.1:2375
-
-For secure tls (https) communication:
-
- DOCKER_HOST=tcp://127.0.0.1:2376
- DOCKER_TLS_VERIFY=1
- DOCKER_CERT_PATH=/Users/marcus/.docker/machine/machines/docker-1.11.2
-
-### Latest release version
-[Maven repository modules](https://mvnrepository.com/artifact/com.github.docker-java)
-
-Since 3.2.0 project provides 3 transports:
-- `docker-java-transport-jersey` (doesn't support streams)
-- `docker-java-transport-netty`
-- `docker-java-transport-okhttp`
-
-For backward compatibility `docker-java` module keeping dependency only on jersey and netty transports.
-
-
- com.github.docker-java
- docker-java
-
- 3.X.Y
-
-
-### Latest development version
-May contain new features while they are not released.
-
-You can find the latest development version including javadoc and source files on [Sonatypes OSS repository](https://oss.sonatype.org/content/groups/public/com/github/docker-java/docker-java/).
-
-
- com.github.docker-java
- docker-java
- 3.X.Y-SNAPSHOT
-
-
-
-## Documentation
-
-For code examples, please look at the [Wiki](https://github.com/docker-java/docker-java/wiki) or [Test cases](https://github.com/docker-java/docker-java/tree/master/docker-java/src/test/java/com/github/dockerjava/core/command "Test cases")
-
-## Configuration
-
-There are a couple of configuration items, all of which have sensible defaults:
-
-* `DOCKER_HOST` The Docker Host URL, e.g. `tcp://localhost:2376` or `unix:///var/run/docker.sock`
-* `DOCKER_TLS_VERIFY` enable/disable TLS verification (switch between `http` and `https` protocol)
-* `DOCKER_CERT_PATH` Path to the certificates needed for TLS verification
-* `DOCKER_CONFIG` Path for additional docker configuration files (like `.dockercfg`)
-* `api.version` The API version, e.g. `1.23`.
-* `registry.url` Your registry's address.
-* `registry.username` Your registry username (required to push containers).
-* `registry.password` Your registry password.
-* `registry.email` Your registry email.
-
-There are three ways to configure, in descending order of precedence:
-
-#### Programmatic:
-In your application, e.g.
-
- DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder()
- .withDockerHost("tcp://my-docker-host.tld:2376")
- .withDockerTlsVerify(true)
- .withDockerCertPath("/home/user/.docker/certs")
- .withDockerConfig("/home/user/.docker")
- .withApiVersion("1.30") // optional
- .withRegistryUrl("https://index.docker.io/v1/")
- .withRegistryUsername("dockeruser")
- .withRegistryPassword("ilovedocker")
- .withRegistryEmail("dockeruser@github.com")
- .build();
- DockerClient docker = DockerClientBuilder.getInstance(config).build();
-
-#### Properties (docker-java.properties)
-
- DOCKER_HOST=tcp://localhost:2376
- DOCKER_TLS_VERIFY=1
- DOCKER_CERT_PATH=/home/user/.docker/certs
- DOCKER_CONFIG=/home/user/.docker
- api.version=1.23
- registry.url=https://index.docker.io/v1/
- registry.username=dockeruser
- registry.password=ilovedocker
- registry.email=dockeruser@github.com
-
-##### System Properties:
-
- java -DDOCKER_HOST=tcp://localhost:2375 -Dregistry.username=dockeruser pkg.Main
-
-##### System Environment
-
- export DOCKER_HOST=tcp://localhost:2376
- export DOCKER_TLS_VERIFY=1
- export DOCKER_CERT_PATH=/home/user/.docker/certs
- export DOCKER_CONFIG=/home/user/.docker
-
-##### File System
-
-In `$HOME/.docker-java.properties`
-
-##### Class Path
-
-In the class path at `/docker-java.properties`
+# [Read the documentation here](docs/README.md)
\ No newline at end of file
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/DockerClient.java b/docker-java-api/src/main/java/com/github/dockerjava/api/DockerClient.java
index 3857f2fa2..59c90aa0b 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/DockerClient.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/DockerClient.java
@@ -54,6 +54,8 @@
import com.github.dockerjava.api.command.RemoveServiceCmd;
import com.github.dockerjava.api.command.RemoveVolumeCmd;
import com.github.dockerjava.api.command.RenameContainerCmd;
+import com.github.dockerjava.api.command.ResizeContainerCmd;
+import com.github.dockerjava.api.command.ResizeExecCmd;
import com.github.dockerjava.api.command.RestartContainerCmd;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.command.SaveImagesCmd;
@@ -165,6 +167,8 @@ public interface DockerClient extends Closeable {
ExecCreateCmd execCreateCmd(@Nonnull String containerId);
+ ResizeExecCmd resizeExecCmd(@Nonnull String execId);
+
InspectContainerCmd inspectContainerCmd(@Nonnull String containerId);
RemoveContainerCmd removeContainerCmd(@Nonnull String containerId);
@@ -242,6 +246,8 @@ public interface DockerClient extends Closeable {
RestartContainerCmd restartContainerCmd(@Nonnull String containerId);
+ ResizeContainerCmd resizeContainerCmd(@Nonnull String containerId);
+
CommitCmd commitCmd(@Nonnull String containerId);
BuildImageCmd buildImageCmd();
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/command/DelegatingDockerCmdExecFactory.java b/docker-java-api/src/main/java/com/github/dockerjava/api/command/DelegatingDockerCmdExecFactory.java
index dbd691974..c414a6d21 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/command/DelegatingDockerCmdExecFactory.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/command/DelegatingDockerCmdExecFactory.java
@@ -25,11 +25,21 @@ public PingCmd.Exec createPingCmdExec() {
return getDockerCmdExecFactory().createPingCmdExec();
}
+ @Override
+ public ResizeContainerCmd.Exec createResizeContainerCmdExec() {
+ return getDockerCmdExecFactory().createResizeContainerCmdExec();
+ }
+
@Override
public ExecCreateCmd.Exec createExecCmdExec() {
return getDockerCmdExecFactory().createExecCmdExec();
}
+ @Override
+ public ResizeExecCmd.Exec createResizeExecCmdExec() {
+ return getDockerCmdExecFactory().createResizeExecCmdExec();
+ }
+
@Override
public VersionCmd.Exec createVersionCmdExec() {
return getDockerCmdExecFactory().createVersionCmdExec();
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java b/docker-java-api/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
index e01d9239f..27084bf76 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/command/DockerCmdExecFactory.java
@@ -49,8 +49,12 @@ public interface DockerCmdExecFactory extends Closeable {
AttachContainerCmd.Exec createAttachContainerCmdExec();
+ ResizeContainerCmd.Exec createResizeContainerCmdExec();
+
ExecStartCmd.Exec createExecStartCmdExec();
+ ResizeExecCmd.Exec createResizeExecCmdExec();
+
InspectExecCmd.Exec createInspectExecCmdExec();
LogContainerCmd.Exec createLogContainerCmdExec();
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/command/EventsCmd.java b/docker-java-api/src/main/java/com/github/dockerjava/api/command/EventsCmd.java
index c52706226..34a0c5ad5 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/command/EventsCmd.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/command/EventsCmd.java
@@ -2,10 +2,12 @@
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import com.github.dockerjava.api.model.Event;
+import com.github.dockerjava.api.model.EventType;
/**
* Get events
@@ -33,6 +35,24 @@ public interface EventsCmd extends AsyncDockerCmd {
*/
EventsCmd withEventFilter(String... event);
+ /**
+ * @param eventTypes event types to filter
+ */
+ EventsCmd withEventTypeFilter(String... eventTypes);
+
+ /**
+ * This provides a type safe version of {@link #withEventTypeFilter(String...)}.
+ *
+ * @param eventTypes event types to filter
+ */
+ default EventsCmd withEventTypeFilter(EventType... eventTypes) {
+ return withEventTypeFilter(
+ Stream.of(eventTypes)
+ .map(EventType::getValue)
+ .toArray(String[]::new)
+ );
+ }
+
/**
* @param image
* - image to filter
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeContainerCmd.java b/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeContainerCmd.java
new file mode 100644
index 000000000..fef0087ed
--- /dev/null
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeContainerCmd.java
@@ -0,0 +1,30 @@
+package com.github.dockerjava.api.command;
+
+import com.github.dockerjava.api.exception.NotFoundException;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+public interface ResizeContainerCmd extends SyncDockerCmd {
+
+ @CheckForNull
+ String getContainerId();
+
+ Integer getHeight();
+
+ Integer getWidth();
+
+ ResizeContainerCmd withContainerId(@Nonnull String execId);
+
+ ResizeContainerCmd withSize(int height, int width);
+
+ /**
+ * @throws NotFoundException no such container instance
+ */
+ @Override
+ Void exec() throws NotFoundException;
+
+ interface Exec extends DockerCmdSyncExec {
+ }
+
+}
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeExecCmd.java b/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeExecCmd.java
new file mode 100644
index 000000000..5910705e0
--- /dev/null
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/command/ResizeExecCmd.java
@@ -0,0 +1,29 @@
+package com.github.dockerjava.api.command;
+
+import com.github.dockerjava.api.exception.NotFoundException;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+public interface ResizeExecCmd extends SyncDockerCmd {
+ @CheckForNull
+ String getExecId();
+
+ Integer getHeight();
+
+ Integer getWidth();
+
+ ResizeExecCmd withExecId(@Nonnull String execId);
+
+ ResizeExecCmd withSize(int height, int width);
+
+ /**
+ * @throws NotFoundException no such exec instance
+ */
+ @Override
+ Void exec() throws NotFoundException;
+
+ interface Exec extends DockerCmdSyncExec {
+ }
+
+}
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPorts.java b/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPorts.java
index 445cba2de..9fb9910bd 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPorts.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/model/ExposedPorts.java
@@ -1,14 +1,14 @@
package com.github.dockerjava.api.model;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonValue;
-
public class ExposedPorts implements Serializable {
private static final long serialVersionUID = 1L;
@@ -36,8 +36,9 @@ public static ExposedPorts fromPrimitive(Map object) {
@JsonValue
public Map toPrimitive() {
return Stream.of(exposedPorts).collect(Collectors.toMap(
- ExposedPort::toString,
- __ -> new Object()
+ ExposedPort::toString,
+ __ -> new Object(),
+ (a, b) -> a
));
}
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/model/ServicePlacement.java b/docker-java-api/src/main/java/com/github/dockerjava/api/model/ServicePlacement.java
index 9c2f0223a..777be8668 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/model/ServicePlacement.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/model/ServicePlacement.java
@@ -28,6 +28,12 @@ public class ServicePlacement implements Serializable {
@JsonProperty("Platforms")
private List platforms;
+ /**
+ * @since 1.40
+ */
+ @JsonProperty("MaxReplicas")
+ private Integer maxReplicas;
+
/**
* @see #constraints
*/
@@ -54,4 +60,31 @@ public List getPlatforms() {
public void setPlatforms(List platforms) {
this.platforms = platforms;
}
+
+ /**
+ * Specifies the maximum amount of replicas / tasks that can run on one node.
+ * 0 means unlimited replicas per node.
+ *
+ * @param maxReplicas Max number of replicas
+ * @return This instance of ServicePlacement
+ * @throws IllegalArgumentException if maxReplicas is less than 0
+ */
+ public ServicePlacement withMaxReplicas(int maxReplicas) {
+ if (maxReplicas < 0) {
+ throw new IllegalArgumentException("The Value for MaxReplicas must be greater or equal to 0");
+ }
+
+ this.maxReplicas = maxReplicas;
+ return this;
+ }
+
+ /**
+ * Getter for maxReplicas
+ *
+ * @return The maximum amount of replicas / tasks that can run on one node.
+ */
+ public Integer getMaxReplicas() {
+ return this.maxReplicas;
+ }
+
}
diff --git a/docker-java-api/src/main/java/com/github/dockerjava/api/model/SwarmNodeStatus.java b/docker-java-api/src/main/java/com/github/dockerjava/api/model/SwarmNodeStatus.java
index ba99a194a..804efe2f1 100644
--- a/docker-java-api/src/main/java/com/github/dockerjava/api/model/SwarmNodeStatus.java
+++ b/docker-java-api/src/main/java/com/github/dockerjava/api/model/SwarmNodeStatus.java
@@ -22,6 +22,12 @@ public class SwarmNodeStatus implements Serializable {
@JsonProperty("State")
private SwarmNodeState state;
+ /**
+ * @since 1.25
+ */
+ @JsonProperty("Addr")
+ private String address;
+
/**
* @see #state
*/
@@ -37,4 +43,20 @@ public SwarmNodeStatus withState(SwarmNodeState state) {
this.state = state;
return this;
}
+
+ /**
+ * @see #address
+ */
+ @CheckForNull
+ public String getAddress() {
+ return address;
+ }
+
+ /**
+ * @see #address
+ */
+ public SwarmNodeStatus withAddress(String address) {
+ this.address = address;
+ return this;
+ }
}
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/AbstractDockerCmdExecFactory.java b/docker-java-core/src/main/java/com/github/dockerjava/core/AbstractDockerCmdExecFactory.java
index e93260b8a..e31431741 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/AbstractDockerCmdExecFactory.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/AbstractDockerCmdExecFactory.java
@@ -57,6 +57,8 @@
import com.github.dockerjava.api.command.RemoveSwarmNodeCmd;
import com.github.dockerjava.api.command.RemoveVolumeCmd;
import com.github.dockerjava.api.command.RenameContainerCmd;
+import com.github.dockerjava.api.command.ResizeContainerCmd;
+import com.github.dockerjava.api.command.ResizeExecCmd;
import com.github.dockerjava.api.command.RestartContainerCmd;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.command.SaveImagesCmd;
@@ -92,6 +94,8 @@
import com.github.dockerjava.core.exec.EventsCmdExec;
import com.github.dockerjava.core.exec.ExecCreateCmdExec;
import com.github.dockerjava.core.exec.ExecStartCmdExec;
+import com.github.dockerjava.core.exec.ResizeContainerCmdExec;
+import com.github.dockerjava.core.exec.ResizeExecCmdExec;
import com.github.dockerjava.core.exec.InfoCmdExec;
import com.github.dockerjava.core.exec.InitializeSwarmCmdExec;
import com.github.dockerjava.core.exec.InspectContainerCmdExec;
@@ -303,11 +307,21 @@ public AttachContainerCmd.Exec createAttachContainerCmdExec() {
return new AttachContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
+ @Override
+ public ResizeContainerCmd.Exec createResizeContainerCmdExec() {
+ return new ResizeContainerCmdExec(getBaseResource(), getDockerClientConfig());
+ }
+
@Override
public ExecStartCmd.Exec createExecStartCmdExec() {
return new ExecStartCmdExec(getBaseResource(), getDockerClientConfig());
}
+ @Override
+ public ResizeExecCmd.Exec createResizeExecCmdExec() {
+ return new ResizeExecCmdExec(getBaseResource(), getDockerClientConfig());
+ }
+
@Override
public InspectExecCmd.Exec createInspectExecCmdExec() {
return new InspectExecCmdExec(getBaseResource(), getDockerClientConfig());
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/DockerClientImpl.java b/docker-java-core/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
index 4caae102c..eddddc220 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/DockerClientImpl.java
@@ -56,6 +56,8 @@
import com.github.dockerjava.api.command.RemoveServiceCmd;
import com.github.dockerjava.api.command.RemoveVolumeCmd;
import com.github.dockerjava.api.command.RenameContainerCmd;
+import com.github.dockerjava.api.command.ResizeContainerCmd;
+import com.github.dockerjava.api.command.ResizeExecCmd;
import com.github.dockerjava.api.command.RestartContainerCmd;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.command.SaveImagesCmd;
@@ -132,6 +134,8 @@
import com.github.dockerjava.core.command.RemoveServiceCmdImpl;
import com.github.dockerjava.core.command.RemoveVolumeCmdImpl;
import com.github.dockerjava.core.command.RenameContainerCmdImpl;
+import com.github.dockerjava.core.command.ResizeContainerCmdImpl;
+import com.github.dockerjava.core.command.ResizeExecCmdImpl;
import com.github.dockerjava.core.command.RestartContainerCmdImpl;
import com.github.dockerjava.core.command.SaveImageCmdImpl;
import com.github.dockerjava.core.command.SaveImagesCmdImpl;
@@ -386,6 +390,11 @@ public ExecCreateCmd execCreateCmd(String containerId) {
return new ExecCreateCmdImpl(getDockerCmdExecFactory().createExecCmdExec(), containerId);
}
+ @Override
+ public ResizeExecCmd resizeExecCmd(@Nonnull String execId) {
+ return new ResizeExecCmdImpl(getDockerCmdExecFactory().createResizeExecCmdExec(), execId);
+ }
+
@Override
public RemoveContainerCmd removeContainerCmd(String containerId) {
return new RemoveContainerCmdImpl(getDockerCmdExecFactory().createRemoveContainerCmdExec(), containerId);
@@ -464,6 +473,11 @@ public RestartContainerCmd restartContainerCmd(String containerId) {
return new RestartContainerCmdImpl(getDockerCmdExecFactory().createRestartContainerCmdExec(), containerId);
}
+ @Override
+ public ResizeContainerCmd resizeContainerCmd(@Nonnull String containerId) {
+ return new ResizeContainerCmdImpl(getDockerCmdExecFactory().createResizeContainerCmdExec(), containerId);
+ }
+
@Override
public CommitCmd commitCmd(String containerId) {
return new CommitCmdImpl(getDockerCmdExecFactory().createCommitCmdExec(), containerId);
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/command/EventsCmdImpl.java b/docker-java-core/src/main/java/com/github/dockerjava/core/command/EventsCmdImpl.java
index 230f6dd5a..d6cca6899 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/command/EventsCmdImpl.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/command/EventsCmdImpl.java
@@ -57,6 +57,13 @@ public EventsCmd withEventFilter(String... event) {
return this;
}
+ @Override
+ public EventsCmd withEventTypeFilter(String... eventTypes) {
+ checkNotNull(eventTypes, "event types have not been specified");
+ this.filters.withEventTypes(eventTypes);
+ return this;
+ }
+
@Override
public EventsCmd withLabelFilter(String... label) {
checkNotNull(label, "label have not been specified");
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeContainerCmdImpl.java b/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeContainerCmdImpl.java
new file mode 100644
index 000000000..de1b6ac20
--- /dev/null
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeContainerCmdImpl.java
@@ -0,0 +1,57 @@
+package com.github.dockerjava.core.command;
+
+import com.github.dockerjava.api.command.ResizeContainerCmd;
+import com.github.dockerjava.api.exception.NotFoundException;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ResizeContainerCmdImpl extends AbstrDockerCmd implements ResizeContainerCmd {
+
+ private String containerId;
+
+ private Integer height;
+
+ private Integer width;
+
+ public ResizeContainerCmdImpl(ResizeContainerCmd.Exec exec, String execId) {
+ super(exec);
+ withContainerId(execId);
+ }
+
+ @Override
+ public String getContainerId() {
+ return containerId;
+ }
+
+ @Override
+ public Integer getHeight() {
+ return height;
+ }
+
+ @Override
+ public Integer getWidth() {
+ return width;
+ }
+
+ @Override
+ public ResizeContainerCmd withContainerId(String containerId) {
+ checkNotNull(containerId, "containerId was not specified");
+ this.containerId = containerId;
+ return this;
+ }
+
+ @Override
+ public ResizeContainerCmd withSize(int height, int width) {
+ this.height = height;
+ this.width = width;
+ return this;
+ }
+
+ /**
+ * @throws NotFoundException no such exec instance
+ */
+ @Override
+ public Void exec() throws NotFoundException {
+ return super.exec();
+ }
+}
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeExecCmdImpl.java b/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeExecCmdImpl.java
new file mode 100644
index 000000000..80b1fef88
--- /dev/null
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/command/ResizeExecCmdImpl.java
@@ -0,0 +1,58 @@
+package com.github.dockerjava.core.command;
+
+import com.github.dockerjava.api.command.ResizeExecCmd;
+import com.github.dockerjava.api.exception.NotFoundException;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+public class ResizeExecCmdImpl extends AbstrDockerCmd implements ResizeExecCmd {
+
+ private String execId;
+
+ private Integer height;
+
+ private Integer width;
+
+ public ResizeExecCmdImpl(ResizeExecCmd.Exec exec, String execId) {
+ super(exec);
+ withExecId(execId);
+ }
+
+ @Override
+ public String getExecId() {
+ return execId;
+ }
+
+ @Override
+ public Integer getHeight() {
+ return height;
+ }
+
+ @Override
+ public Integer getWidth() {
+ return width;
+ }
+
+ @Override
+ public ResizeExecCmd withExecId(String execId) {
+ checkNotNull(execId, "execId was not specified");
+ this.execId = execId;
+ return this;
+ }
+
+ @Override
+ public ResizeExecCmd withSize(int height, int width) {
+ this.height = height;
+ this.width = width;
+ return this;
+ }
+
+ /**
+ * @throws NotFoundException no such exec instance
+ */
+ @Override
+ public Void exec() throws NotFoundException {
+ return super.exec();
+ }
+
+}
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/exec/PushImageCmdExec.java b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/PushImageCmdExec.java
index 0f3705089..4f4540891 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/exec/PushImageCmdExec.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/PushImageCmdExec.java
@@ -6,7 +6,6 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.PushImageCmd;
-import com.github.dockerjava.api.model.AuthConfig;
import com.github.dockerjava.api.model.PushResponseItem;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.InvocationBuilder;
@@ -22,17 +21,11 @@ public PushImageCmdExec(WebTarget baseResource, DockerClientConfig dockerClientC
super(baseResource, dockerClientConfig);
}
- private String name(PushImageCmd command) {
- String name = command.getName();
- AuthConfig authConfig = command.getAuthConfig();
- return (name.contains("/") || authConfig == null) ? name : authConfig.getUsername();
- }
-
@Override
protected Void execute0(PushImageCmd command, ResultCallback resultCallback) {
-
- WebTarget webResource = getBaseResource().path("/images/" + name(command) + "/push").queryParam("tag",
- command.getTag());
+ WebTarget webResource = getBaseResource().path("/images/{imageName}/push")
+ .resolveTemplate("imageName", command.getName())
+ .queryParam("tag", command.getTag());
LOGGER.trace("POST: {}", webResource);
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeContainerCmdExec.java b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeContainerCmdExec.java
new file mode 100644
index 000000000..f13fc582a
--- /dev/null
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeContainerCmdExec.java
@@ -0,0 +1,30 @@
+package com.github.dockerjava.core.exec;
+
+import com.github.dockerjava.api.command.ResizeContainerCmd;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.MediaType;
+import com.github.dockerjava.core.WebTarget;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ResizeContainerCmdExec extends AbstrSyncDockerCmdExec implements ResizeContainerCmd.Exec {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ResizeContainerCmdExec.class);
+
+ public ResizeContainerCmdExec(WebTarget baseResource, DockerClientConfig dockerClientConfig) {
+ super(baseResource, dockerClientConfig);
+ }
+
+ @Override
+ protected Void execute(ResizeContainerCmd command) {
+ WebTarget webResource = getBaseResource().path("/containers/{id}/resize")
+ .resolveTemplate("id", command.getContainerId()).queryParam("h", command.getHeight())
+ .queryParam("w", command.getWidth());
+
+ LOGGER.trace("POST: {}", webResource);
+
+ webResource.request().accept(MediaType.APPLICATION_JSON).post(command);
+
+ return null;
+ }
+}
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeExecCmdExec.java b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeExecCmdExec.java
new file mode 100644
index 000000000..ba01c5ffe
--- /dev/null
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/exec/ResizeExecCmdExec.java
@@ -0,0 +1,31 @@
+package com.github.dockerjava.core.exec;
+
+import com.github.dockerjava.api.command.ResizeExecCmd;
+import com.github.dockerjava.core.DockerClientConfig;
+import com.github.dockerjava.core.MediaType;
+import com.github.dockerjava.core.WebTarget;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class ResizeExecCmdExec extends AbstrSyncDockerCmdExec implements ResizeExecCmd.Exec {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ResizeExecCmdExec.class);
+
+ public ResizeExecCmdExec(WebTarget baseResource, DockerClientConfig dockerClientConfig) {
+ super(baseResource, dockerClientConfig);
+ }
+
+ @Override
+ protected Void execute(ResizeExecCmd command) {
+ WebTarget webResource = getBaseResource().path("/exec/{id}/resize")
+ .resolveTemplate("id", command.getExecId()).queryParam("h", command.getHeight()).queryParam("w", command.getWidth());
+
+ LOGGER.trace("POST: {}", webResource);
+
+ webResource.request().accept(MediaType.APPLICATION_JSON).post(null);
+
+ return null;
+ }
+
+}
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/util/CertificateUtils.java b/docker-java-core/src/main/java/com/github/dockerjava/core/util/CertificateUtils.java
index 82563962a..7cb1a19d4 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/util/CertificateUtils.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/util/CertificateUtils.java
@@ -89,11 +89,13 @@ public static List loadCertificates(final Reader reader) throws IOE
JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME);
- Object certObj = pemParser.readObject();
+ Object certObj;
- if (certObj instanceof X509CertificateHolder) {
- X509CertificateHolder certificateHolder = (X509CertificateHolder) certObj;
- certificates.add(certificateConverter.getCertificate(certificateHolder));
+ while ((certObj = pemParser.readObject()) != null) {
+ if (certObj instanceof X509CertificateHolder) {
+ X509CertificateHolder certificateHolder = (X509CertificateHolder) certObj;
+ certificates.add(certificateConverter.getCertificate(certificateHolder));
+ }
}
return certificates;
diff --git a/docker-java-core/src/main/java/com/github/dockerjava/core/util/FiltersBuilder.java b/docker-java-core/src/main/java/com/github/dockerjava/core/util/FiltersBuilder.java
index 73b369ca7..0d6c1d268 100644
--- a/docker-java-core/src/main/java/com/github/dockerjava/core/util/FiltersBuilder.java
+++ b/docker-java-core/src/main/java/com/github/dockerjava/core/util/FiltersBuilder.java
@@ -10,6 +10,8 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Representation of Docker filters.
@@ -64,6 +66,16 @@ public List getContainer() {
return getFilter("container");
}
+ /**
+ * Filter by event types
+ *
+ * @param eventTypes an array of event types
+ */
+ public FiltersBuilder withEventTypes(String... eventTypes) {
+ withFilter("type", Stream.of(eventTypes).collect(Collectors.toList()));
+ return this;
+ }
+
/**
* Filter by labels
*
diff --git a/docker-java-transport-httpclient5/pom.xml b/docker-java-transport-httpclient5/pom.xml
index 41300602f..5f204724f 100644
--- a/docker-java-transport-httpclient5/pom.xml
+++ b/docker-java-transport-httpclient5/pom.xml
@@ -36,7 +36,7 @@
net.java.dev.jna
- jna-platform
+ jna
5.5.0
diff --git a/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClient.java b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClient.java
index 0a018cd06..cf2b7300d 100644
--- a/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClient.java
+++ b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClient.java
@@ -1,45 +1,11 @@
package com.github.dockerjava.httpclient5;
import com.github.dockerjava.transport.SSLConfig;
-import com.github.dockerjava.transport.DockerHttpClient;
-import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
-import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
-import org.apache.hc.client5.http.impl.classic.HttpClients;
-import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
-import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
-import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
-import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
-import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
-import org.apache.hc.core5.http.ConnectionClosedException;
-import org.apache.hc.core5.http.ContentLengthStrategy;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.NameValuePair;
-import org.apache.hc.core5.http.config.Registry;
-import org.apache.hc.core5.http.config.RegistryBuilder;
-import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
-import org.apache.hc.core5.http.impl.io.EmptyInputStream;
-import org.apache.hc.core5.http.io.entity.InputStreamEntity;
-import org.apache.hc.core5.http.protocol.BasicHttpContext;
-import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.net.URIAuthority;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import javax.net.ssl.SSLContext;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.Socket;
import java.net.URI;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-public final class ApacheDockerHttpClient implements DockerHttpClient {
+public final class ApacheDockerHttpClient extends ApacheDockerHttpClientImpl {
public static final class Builder {
@@ -63,183 +29,7 @@ public ApacheDockerHttpClient build() {
}
}
- private final CloseableHttpClient httpClient;
-
- private final HttpHost host;
-
- private ApacheDockerHttpClient(
- URI dockerHost,
- SSLConfig sslConfig
- ) {
- Registry socketFactoryRegistry = createConnectionSocketFactoryRegistry(sslConfig, dockerHost);
-
- switch (dockerHost.getScheme()) {
- case "unix":
- case "npipe":
- host = new HttpHost(dockerHost.getScheme(), "localhost", 2375);
- break;
- case "tcp":
- host = new HttpHost(
- socketFactoryRegistry.lookup("https") != null ? "https" : "http",
- dockerHost.getHost(),
- dockerHost.getPort()
- );
- break;
- default:
- host = HttpHost.create(dockerHost);
- }
-
- httpClient = HttpClients.custom()
- .setRequestExecutor(new HijackingHttpRequestExecutor(null))
- .setConnectionManager(new PoolingHttpClientConnectionManager(
- socketFactoryRegistry,
- new ManagedHttpClientConnectionFactory(
- null,
- null,
- null,
- null,
- message -> {
- Header transferEncodingHeader = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
- if (transferEncodingHeader != null) {
- if ("identity".equalsIgnoreCase(transferEncodingHeader.getValue())) {
- return ContentLengthStrategy.UNDEFINED;
- }
- }
- return DefaultContentLengthStrategy.INSTANCE.determineLength(message);
- },
- null
- )
- ))
- .build();
- }
-
- private Registry createConnectionSocketFactoryRegistry(
- SSLConfig sslConfig,
- URI dockerHost
- ) {
- RegistryBuilder socketFactoryRegistryBuilder = RegistryBuilder.create();
-
- if (sslConfig != null) {
- try {
- SSLContext sslContext = sslConfig.getSSLContext();
- if (sslContext != null) {
- socketFactoryRegistryBuilder.register("https", new SSLConnectionSocketFactory(sslContext));
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- return socketFactoryRegistryBuilder
- .register("tcp", PlainConnectionSocketFactory.INSTANCE)
- .register("http", PlainConnectionSocketFactory.INSTANCE)
- .register("unix", new PlainConnectionSocketFactory() {
- @Override
- public Socket createSocket(HttpContext context) throws IOException {
- return new UnixDomainSocket(dockerHost.getPath());
- }
- })
- .register("npipe", new PlainConnectionSocketFactory() {
- @Override
- public Socket createSocket(HttpContext context) {
- return new NamedPipeSocket(dockerHost.getPath());
- }
- })
- .build();
- }
-
- @Override
- public Response execute(Request request) {
- HttpContext context = new BasicHttpContext();
- HttpUriRequestBase httpUriRequest = new HttpUriRequestBase(request.method(), URI.create(request.path()));
- httpUriRequest.setScheme(host.getSchemeName());
- httpUriRequest.setAuthority(new URIAuthority(host.getHostName(), host.getPort()));
-
- request.headers().forEach(httpUriRequest::addHeader);
-
- InputStream body = request.body();
- if (body != null) {
- httpUriRequest.setEntity(new InputStreamEntity(body, null));
- }
-
- if (request.hijackedInput() != null) {
- context.setAttribute(HijackingHttpRequestExecutor.HIJACKED_INPUT_ATTRIBUTE, request.hijackedInput());
- httpUriRequest.setHeader("Upgrade", "tcp");
- httpUriRequest.setHeader("Connection", "Upgrade");
- }
-
- try {
- CloseableHttpResponse response = httpClient.execute(host, httpUriRequest, context);
-
- return new ApacheResponse(httpUriRequest, response);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void close() throws IOException {
- httpClient.close();
- }
-
- static class ApacheResponse implements Response {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ApacheResponse.class);
-
- private final HttpUriRequestBase request;
-
- private final CloseableHttpResponse response;
-
- ApacheResponse(HttpUriRequestBase httpUriRequest, CloseableHttpResponse response) {
- this.request = httpUriRequest;
- this.response = response;
- }
-
- @Override
- public int getStatusCode() {
- return response.getCode();
- }
-
- @Override
- public Map> getHeaders() {
- return Stream.of(response.getHeaders()).collect(Collectors.groupingBy(
- NameValuePair::getName,
- Collectors.mapping(NameValuePair::getValue, Collectors.toList())
- ));
- }
-
- @Override
- public String getHeader(String name) {
- Header firstHeader = response.getFirstHeader(name);
- return firstHeader != null ? firstHeader.getValue() : null;
- }
-
- @Override
- public InputStream getBody() {
- try {
- return response.getEntity() != null
- ? response.getEntity().getContent()
- : EmptyInputStream.INSTANCE;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void close() {
- try {
- request.abort();
- } catch (Exception e) {
- LOGGER.debug("Failed to abort the request", e);
- }
-
- try {
- response.close();
- } catch (ConnectionClosedException e) {
- LOGGER.trace("Failed to close the response", e);
- } catch (Exception e) {
- LOGGER.debug("Failed to close the response", e);
- }
- }
+ private ApacheDockerHttpClient(URI dockerHost, SSLConfig sslConfig) {
+ super(dockerHost, sslConfig);
}
}
diff --git a/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClientImpl.java b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClientImpl.java
new file mode 100644
index 000000000..d06bd81ab
--- /dev/null
+++ b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClientImpl.java
@@ -0,0 +1,222 @@
+package com.github.dockerjava.httpclient5;
+
+import com.github.dockerjava.transport.DockerHttpClient;
+import com.github.dockerjava.transport.SSLConfig;
+import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
+import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
+import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
+import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
+import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
+import org.apache.hc.core5.http.ConnectionClosedException;
+import org.apache.hc.core5.http.ContentLengthStrategy;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.http.config.Registry;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
+import org.apache.hc.core5.http.impl.io.EmptyInputStream;
+import org.apache.hc.core5.http.io.entity.InputStreamEntity;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.net.URIAuthority;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+class ApacheDockerHttpClientImpl implements DockerHttpClient {
+
+ private final CloseableHttpClient httpClient;
+
+ private final HttpHost host;
+
+ protected ApacheDockerHttpClientImpl(
+ URI dockerHost,
+ SSLConfig sslConfig
+ ) {
+ Registry socketFactoryRegistry = createConnectionSocketFactoryRegistry(sslConfig, dockerHost);
+
+ switch (dockerHost.getScheme()) {
+ case "unix":
+ case "npipe":
+ host = new HttpHost(dockerHost.getScheme(), "localhost", 2375);
+ break;
+ case "tcp":
+ host = new HttpHost(
+ socketFactoryRegistry.lookup("https") != null ? "https" : "http",
+ dockerHost.getHost(),
+ dockerHost.getPort()
+ );
+ break;
+ default:
+ host = HttpHost.create(dockerHost);
+ }
+
+ httpClient = HttpClients.custom()
+ .setRequestExecutor(new HijackingHttpRequestExecutor(null))
+ .setConnectionManager(new PoolingHttpClientConnectionManager(
+ socketFactoryRegistry,
+ new ManagedHttpClientConnectionFactory(
+ null,
+ null,
+ null,
+ null,
+ message -> {
+ Header transferEncodingHeader = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
+ if (transferEncodingHeader != null) {
+ if ("identity".equalsIgnoreCase(transferEncodingHeader.getValue())) {
+ return ContentLengthStrategy.UNDEFINED;
+ }
+ }
+ return DefaultContentLengthStrategy.INSTANCE.determineLength(message);
+ },
+ null
+ )
+ ))
+ .build();
+ }
+
+ private Registry createConnectionSocketFactoryRegistry(
+ SSLConfig sslConfig,
+ URI dockerHost
+ ) {
+ RegistryBuilder socketFactoryRegistryBuilder = RegistryBuilder.create();
+
+ if (sslConfig != null) {
+ try {
+ SSLContext sslContext = sslConfig.getSSLContext();
+ if (sslContext != null) {
+ socketFactoryRegistryBuilder.register("https", new SSLConnectionSocketFactory(sslContext));
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return socketFactoryRegistryBuilder
+ .register("tcp", PlainConnectionSocketFactory.INSTANCE)
+ .register("http", PlainConnectionSocketFactory.INSTANCE)
+ .register("unix", new PlainConnectionSocketFactory() {
+ @Override
+ public Socket createSocket(HttpContext context) throws IOException {
+ return new UnixDomainSocket(dockerHost.getPath());
+ }
+ })
+ .register("npipe", new PlainConnectionSocketFactory() {
+ @Override
+ public Socket createSocket(HttpContext context) {
+ return new NamedPipeSocket(dockerHost.getPath());
+ }
+ })
+ .build();
+ }
+
+ @Override
+ public Response execute(Request request) {
+ HttpContext context = new BasicHttpContext();
+ HttpUriRequestBase httpUriRequest = new HttpUriRequestBase(request.method(), URI.create(request.path()));
+ httpUriRequest.setScheme(host.getSchemeName());
+ httpUriRequest.setAuthority(new URIAuthority(host.getHostName(), host.getPort()));
+
+ request.headers().forEach(httpUriRequest::addHeader);
+
+ InputStream body = request.body();
+ if (body != null) {
+ httpUriRequest.setEntity(new InputStreamEntity(body, null));
+ }
+
+ if (request.hijackedInput() != null) {
+ context.setAttribute(HijackingHttpRequestExecutor.HIJACKED_INPUT_ATTRIBUTE, request.hijackedInput());
+ httpUriRequest.setHeader("Upgrade", "tcp");
+ httpUriRequest.setHeader("Connection", "Upgrade");
+ }
+
+ try {
+ CloseableHttpResponse response = httpClient.execute(host, httpUriRequest, context);
+
+ return new ApacheResponse(httpUriRequest, response);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ httpClient.close();
+ }
+
+ static class ApacheResponse implements Response {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApacheResponse.class);
+
+ private final HttpUriRequestBase request;
+
+ private final CloseableHttpResponse response;
+
+ ApacheResponse(HttpUriRequestBase httpUriRequest, CloseableHttpResponse response) {
+ this.request = httpUriRequest;
+ this.response = response;
+ }
+
+ @Override
+ public int getStatusCode() {
+ return response.getCode();
+ }
+
+ @Override
+ public Map> getHeaders() {
+ return Stream.of(response.getHeaders()).collect(Collectors.groupingBy(
+ NameValuePair::getName,
+ Collectors.mapping(NameValuePair::getValue, Collectors.toList())
+ ));
+ }
+
+ @Override
+ public String getHeader(String name) {
+ Header firstHeader = response.getFirstHeader(name);
+ return firstHeader != null ? firstHeader.getValue() : null;
+ }
+
+ @Override
+ public InputStream getBody() {
+ try {
+ return response.getEntity() != null
+ ? response.getEntity().getContent()
+ : EmptyInputStream.INSTANCE;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ request.abort();
+ } catch (Exception e) {
+ LOGGER.debug("Failed to abort the request", e);
+ }
+
+ try {
+ response.close();
+ } catch (ConnectionClosedException e) {
+ LOGGER.trace("Failed to close the response", e);
+ } catch (Exception e) {
+ LOGGER.debug("Failed to close the response", e);
+ }
+ }
+ }
+}
diff --git a/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/NamedPipeSocket.java b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/NamedPipeSocket.java
index b016c03d2..fd080d90f 100644
--- a/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/NamedPipeSocket.java
+++ b/docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/NamedPipeSocket.java
@@ -1,24 +1,31 @@
package com.github.dockerjava.httpclient5;
-import com.sun.jna.platform.win32.Kernel32;
+import com.sun.jna.Native;
+import com.sun.jna.win32.StdCallLibrary;
+import com.sun.jna.win32.W32APIOptions;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.RandomAccessFile;
import java.net.Socket;
import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.Channels;
+import java.nio.channels.CompletionHandler;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
class NamedPipeSocket extends Socket {
private final String socketFileName;
- private RandomAccessFile file;
-
- private InputStream is;
-
- private OutputStream os;
+ private AsynchronousFileByteChannel channel;
NamedPipeSocket(String socketFileName) {
this.socketFileName = socketFileName;
@@ -26,26 +33,31 @@ class NamedPipeSocket extends Socket {
@Override
public void close() throws IOException {
- if (file != null) {
- file.close();
- file = null;
+ if (channel != null) {
+ channel.close();
}
}
@Override
- public void connect(SocketAddress endpoint) {
+ public void connect(SocketAddress endpoint) throws IOException {
connect(endpoint, 0);
}
@Override
- public void connect(SocketAddress endpoint, int timeout) {
+ public void connect(SocketAddress endpoint, int timeout) throws IOException {
long startedAt = System.currentTimeMillis();
timeout = Math.max(timeout, 10_000);
while (true) {
try {
- file = new RandomAccessFile(socketFileName, "rw");
+ channel = new AsynchronousFileByteChannel(
+ AsynchronousFileChannel.open(
+ Paths.get(socketFileName),
+ StandardOpenOption.READ,
+ StandardOpenOption.WRITE
+ )
+ );
break;
- } catch (FileNotFoundException e) {
+ } catch (NoSuchFileException e) {
if (System.currentTimeMillis() - startedAt >= timeout) {
throw new RuntimeException(e);
} else {
@@ -53,49 +65,94 @@ public void connect(SocketAddress endpoint, int timeout) {
}
}
}
+ }
- is = new InputStream() {
- @Override
- public int read(byte[] bytes, int off, int len) throws IOException {
- return file.read(bytes, off, len);
- }
+ @Override
+ public InputStream getInputStream() {
+ return Channels.newInputStream(channel);
+ }
- @Override
- public int read() throws IOException {
- return file.read();
- }
+ @Override
+ public OutputStream getOutputStream() {
+ return Channels.newOutputStream(channel);
+ }
- @Override
- public int read(byte[] bytes) throws IOException {
- return file.read(bytes);
- }
- };
+ interface Kernel32 extends StdCallLibrary {
- os = new OutputStream() {
- @Override
- public void write(byte[] bytes, int off, int len) throws IOException {
- file.write(bytes, off, len);
- }
+ Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);
+
+ @SuppressWarnings("checkstyle:methodname")
+ boolean WaitNamedPipe(String lpNamedPipeName, int nTimeOut);
+ }
+
+ private static class AsynchronousFileByteChannel implements AsynchronousByteChannel {
+ private final AsynchronousFileChannel fileChannel;
+
+ AsynchronousFileByteChannel(AsynchronousFileChannel fileChannel) {
+ this.fileChannel = fileChannel;
+ }
+
+ @Override
+ public void read(ByteBuffer dst, A attachment, CompletionHandler handler) {
+ fileChannel.read(dst, 0, attachment, new CompletionHandler() {
+ @Override
+ public void completed(Integer read, A attachment) {
+ handler.completed(read > 0 ? read : -1, attachment);
+ }
+
+ @Override
+ public void failed(Throwable exc, A attachment) {
+ if (exc instanceof AsynchronousCloseException) {
+ handler.completed(-1, attachment);
+ return;
+ }
+ handler.failed(exc, attachment);
+ }
+ });
+ }
+
+ @Override
+ public Future read(ByteBuffer dst) {
+ CompletableFutureHandler future = new CompletableFutureHandler();
+ fileChannel.read(dst, 0, null, future);
+ return future;
+ }
+
+ @Override
+ public void write(ByteBuffer src, A attachment, CompletionHandler handler) {
+ fileChannel.write(src, 0, attachment, handler);
+ }
+
+ @Override
+ public Future write(ByteBuffer src) {
+ return fileChannel.write(src, 0);
+ }
+
+ @Override
+ public void close() throws IOException {
+ fileChannel.close();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return fileChannel.isOpen();
+ }
+
+ private static class CompletableFutureHandler extends CompletableFuture implements CompletionHandler {
@Override
- public void write(int value) throws IOException {
- file.write(value);
+ public void completed(Integer read, Object attachment) {
+ complete(read > 0 ? read : -1);
}
@Override
- public void write(byte[] bytes) throws IOException {
- file.write(bytes);
+ public void failed(Throwable exc, Object attachment) {
+ if (exc instanceof AsynchronousCloseException) {
+ complete(-1);
+ return;
+ }
+ completeExceptionally(exc);
}
- };
- }
-
- @Override
- public InputStream getInputStream() {
- return is;
- }
-
- @Override
- public OutputStream getOutputStream() {
- return os;
+ }
}
}
diff --git a/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocket.java b/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocket.java
new file mode 100644
index 000000000..f63e92019
--- /dev/null
+++ b/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocket.java
@@ -0,0 +1,159 @@
+package com.github.dockerjava.okhttp;
+
+import com.sun.jna.Native;
+import com.sun.jna.win32.StdCallLibrary;
+import com.sun.jna.win32.W32APIOptions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.Channels;
+import java.nio.channels.CompletionHandler;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+class NamedPipeSocket extends Socket {
+
+ private final String socketFileName;
+
+ private AsynchronousFileByteChannel channel;
+
+ NamedPipeSocket(String socketFileName) {
+ this.socketFileName = socketFileName;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (channel != null) {
+ channel.close();
+ }
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint) throws IOException {
+ connect(endpoint, 0);
+ }
+
+ @Override
+ public void connect(SocketAddress endpoint, int timeout) throws IOException {
+ long startedAt = System.currentTimeMillis();
+
+ timeout = Math.max(timeout, 10_000);
+ while (true) {
+ try {
+ channel = new AsynchronousFileByteChannel(
+ AsynchronousFileChannel.open(
+ Paths.get(socketFileName),
+ StandardOpenOption.READ,
+ StandardOpenOption.WRITE
+ )
+ );
+ break;
+ } catch (NoSuchFileException e) {
+ if (System.currentTimeMillis() - startedAt >= timeout) {
+ throw new RuntimeException(e);
+ } else {
+ Kernel32.INSTANCE.WaitNamedPipe(socketFileName, 100);
+ }
+ }
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return Channels.newInputStream(channel);
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return Channels.newOutputStream(channel);
+ }
+
+ interface Kernel32 extends StdCallLibrary {
+
+ Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);
+
+ @SuppressWarnings("checkstyle:methodname")
+ boolean WaitNamedPipe(String lpNamedPipeName, int nTimeOut);
+ }
+
+ private static class AsynchronousFileByteChannel implements AsynchronousByteChannel {
+ private final AsynchronousFileChannel fileChannel;
+
+ AsynchronousFileByteChannel(AsynchronousFileChannel fileChannel) {
+ this.fileChannel = fileChannel;
+ }
+
+ @Override
+ public void read(ByteBuffer dst, A attachment, CompletionHandler handler) {
+ fileChannel.read(dst, 0, attachment, new CompletionHandler() {
+ @Override
+ public void completed(Integer read, A attachment) {
+ handler.completed(read > 0 ? read : -1, attachment);
+ }
+
+ @Override
+ public void failed(Throwable exc, A attachment) {
+ if (exc instanceof AsynchronousCloseException) {
+ handler.completed(-1, attachment);
+ return;
+ }
+ handler.failed(exc, attachment);
+ }
+ });
+ }
+
+ @Override
+ public Future read(ByteBuffer dst) {
+ CompletableFutureHandler future = new CompletableFutureHandler();
+ fileChannel.read(dst, 0, null, future);
+ return future;
+ }
+
+ @Override
+ public void write(ByteBuffer src, A attachment, CompletionHandler handler) {
+ fileChannel.write(src, 0, attachment, handler);
+ }
+
+ @Override
+ public Future write(ByteBuffer src) {
+ return fileChannel.write(src, 0);
+ }
+
+ @Override
+ public void close() throws IOException {
+ fileChannel.close();
+ }
+
+ @Override
+ public boolean isOpen() {
+ return fileChannel.isOpen();
+ }
+
+ private static class CompletableFutureHandler extends CompletableFuture implements CompletionHandler {
+
+ @Override
+ public void completed(Integer read, Object attachment) {
+ complete(read > 0 ? read : -1);
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ if (exc instanceof AsynchronousCloseException) {
+ complete(-1);
+ return;
+ }
+ completeExceptionally(exc);
+ }
+ }
+ }
+}
diff --git a/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocketFactory.java b/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocketFactory.java
index d993a9c52..464910b04 100644
--- a/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocketFactory.java
+++ b/docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/NamedPipeSocketFactory.java
@@ -1,17 +1,8 @@
package com.github.dockerjava.okhttp;
-import com.github.dockerjava.okhttp.OkDockerHttpClient.OkResponse;
-import com.sun.jna.platform.win32.Kernel32;
-
import javax.net.SocketFactory;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.net.Socket;
-import java.net.SocketAddress;
class NamedPipeSocketFactory extends SocketFactory {
@@ -23,96 +14,7 @@ class NamedPipeSocketFactory extends SocketFactory {
@Override
public Socket createSocket() {
- return new Socket() {
-
- RandomAccessFile file;
- InputStream is;
- OutputStream os;
-
- @Override
- public void close() throws IOException {
- if (file != null) {
- file.close();
- file = null;
- }
- }
-
- @Override
- public void connect(SocketAddress endpoint) {
- connect(endpoint, 0);
- }
-
- @Override
- public void connect(SocketAddress endpoint, int timeout) {
- long startedAt = System.currentTimeMillis();
- timeout = Math.max(timeout, 10_000);
- while (true) {
- try {
- file = new RandomAccessFile(socketFileName, "rw");
- break;
- } catch (FileNotFoundException e) {
- if (System.currentTimeMillis() - startedAt >= timeout) {
- throw new RuntimeException(e);
- } else {
- Kernel32.INSTANCE.WaitNamedPipe(socketFileName, 100);
- }
- }
- }
-
- is = new InputStream() {
- @Override
- public int read(byte[] bytes, int off, int len) throws IOException {
- if (OkResponse.CLOSING.get()) {
- return 0;
- }
- return file.read(bytes, off, len);
- }
-
- @Override
- public int read() throws IOException {
- if (OkResponse.CLOSING.get()) {
- return 0;
- }
- return file.read();
- }
-
- @Override
- public int read(byte[] bytes) throws IOException {
- if (OkResponse.CLOSING.get()) {
- return 0;
- }
- return file.read(bytes);
- }
- };
-
- os = new OutputStream() {
- @Override
- public void write(byte[] bytes, int off, int len) throws IOException {
- file.write(bytes, off, len);
- }
-
- @Override
- public void write(int value) throws IOException {
- file.write(value);
- }
-
- @Override
- public void write(byte[] bytes) throws IOException {
- file.write(bytes);
- }
- };
- }
-
- @Override
- public InputStream getInputStream() {
- return is;
- }
-
- @Override
- public OutputStream getOutputStream() {
- return os;
- }
- };
+ return new NamedPipeSocket(socketFileName);
}
@Override
diff --git a/docker-java-transport-zerodep/pom.xml b/docker-java-transport-zerodep/pom.xml
new file mode 100644
index 000000000..cec907067
--- /dev/null
+++ b/docker-java-transport-zerodep/pom.xml
@@ -0,0 +1,104 @@
+
+ 4.0.0
+
+
+ com.github.docker-java
+ docker-java-parent
+ 3.2.2-SNAPSHOT
+ ../pom.xml
+
+
+ docker-java-transport-zerodep
+ jar
+
+ docker-java-transport-zerodep
+ https://github.com/docker-java/docker-java
+ Java API Client for Docker
+
+
+
+ ${project.groupId}
+ docker-java-transport-httpclient5
+ ${project.version}
+
+
+
+
+
+
+ com.github.siom79.japicmp
+ japicmp-maven-plugin
+
+
+ true
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+ true
+
+
+ com.github.dockerjava.zerodep.*
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+ true
+ true
+ true
+
+
+
+ com.github.docker-java:docker-java-transport
+ net.java.dev.jna:jna-platform
+ net.java.dev.jna:*
+ org.slf4j:slf4j-api
+
+
+
+
+ com.github.docker-java:docker-java-transport-httpclient5
+
+ com/github/dockerjava/httpclient5/ApacheDockerHttpClient.class
+ com/github/dockerjava/httpclient5/ApacheDockerHttpClient$*
+
+
+
+ org.apache.httpcomponents.client5:httpclient5
+
+ mozilla/*
+
+
+
+
+
+ org.apache
+ com.github.dockerjava.zerodep.shaded.org.apache
+
+
+ com.github.dockerjava.httpclient5
+ com.github.dockerjava.zerodep
+
+
+
+
+
+
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
diff --git a/docker-java-transport-zerodep/src/main/java/com/github/dockerjava/httpclient5/ZerodepDockerHttpClient.java b/docker-java-transport-zerodep/src/main/java/com/github/dockerjava/httpclient5/ZerodepDockerHttpClient.java
new file mode 100644
index 000000000..2298da816
--- /dev/null
+++ b/docker-java-transport-zerodep/src/main/java/com/github/dockerjava/httpclient5/ZerodepDockerHttpClient.java
@@ -0,0 +1,36 @@
+package com.github.dockerjava.httpclient5;
+
+import com.github.dockerjava.transport.SSLConfig;
+
+import java.net.URI;
+import java.util.Objects;
+
+@SuppressWarnings("unused")
+public final class ZerodepDockerHttpClient extends ApacheDockerHttpClientImpl {
+
+ public static final class Builder {
+
+ private URI dockerHost = null;
+
+ private SSLConfig sslConfig = null;
+
+ public Builder dockerHost(URI value) {
+ this.dockerHost = Objects.requireNonNull(value, "dockerHost");
+ return this;
+ }
+
+ public Builder sslConfig(SSLConfig value) {
+ this.sslConfig = value;
+ return this;
+ }
+
+ public ZerodepDockerHttpClient build() {
+ Objects.requireNonNull(dockerHost, "dockerHost");
+ return new ZerodepDockerHttpClient(dockerHost, sslConfig);
+ }
+ }
+
+ protected ZerodepDockerHttpClient(URI dockerHost, SSLConfig sslConfig) {
+ super(dockerHost, sslConfig);
+ }
+}
diff --git a/docker-java/src/test/java/com/github/dockerjava/api/model/ExposedPortsTest.java b/docker-java/src/test/java/com/github/dockerjava/api/model/ExposedPortsTest.java
index c64d5ace2..456e65ca4 100644
--- a/docker-java/src/test/java/com/github/dockerjava/api/model/ExposedPortsTest.java
+++ b/docker-java/src/test/java/com/github/dockerjava/api/model/ExposedPortsTest.java
@@ -6,10 +6,13 @@
import org.junit.Test;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
+import static org.hamcrest.Matchers.arrayWithSize;
import static org.hamcrest.Matchers.notNullValue;
public class ExposedPortsTest {
@@ -46,4 +49,19 @@ public void usesFromJson() throws Exception {
new ExposedPort(3868, InternetProtocol.SCTP)
));
}
+
+ @Test
+ public void usesFromJsonWithDuplicate() throws Exception {
+ ExposedPorts ports = new ExposedPorts(
+ new ExposedPort(80, InternetProtocol.UDP),
+ new ExposedPort(80),
+ new ExposedPort(80)
+ );
+
+ assertThat(ports, notNullValue());
+ assertThat(ports.getExposedPorts(), arrayWithSize(3));
+
+ Map map = ports.toPrimitive();
+ assertThat(map, aMapWithSize(2));
+ }
}
diff --git a/docker-java/src/test/java/com/github/dockerjava/cmd/EventsCmdIT.java b/docker-java/src/test/java/com/github/dockerjava/cmd/EventsCmdIT.java
index 897c6fd25..2a16e5474 100644
--- a/docker-java/src/test/java/com/github/dockerjava/cmd/EventsCmdIT.java
+++ b/docker-java/src/test/java/com/github/dockerjava/cmd/EventsCmdIT.java
@@ -3,6 +3,7 @@
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.model.Event;
+import com.github.dockerjava.api.model.EventType;
import com.github.dockerjava.utils.TestUtils;
import org.junit.Test;
import org.slf4j.Logger;
@@ -10,6 +11,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -113,6 +115,37 @@ public void testEventStreamingWithFilter() throws Exception {
}
}
+ @Test
+ public void testEventStreamingWithEventTypeFilter() throws Exception {
+ assumeNotSwarm("", dockerRule);
+
+ String startTime = getEpochTime();
+ generateEvents();
+ String endTime = getEpochTime();
+
+ for (EventType eventType : EventType.values()) {
+ List events = new CopyOnWriteArrayList<>();
+ try (
+ ResultCallback.Adapter> eventCallback = dockerRule.getClient().eventsCmd()
+ .withSince(startTime)
+ .withUntil(endTime)
+ .withEventTypeFilter(eventType)
+ .exec(new ResultCallback.Adapter() {
+ @Override
+ public void onNext(Event event) {
+ events.add(event);
+ }
+ })
+ ) {
+ eventCallback.awaitCompletion(30, TimeUnit.SECONDS);
+
+ for (Event event : events) {
+ assertThat("Received event: " + event, event.getType(), is(eventType));
+ }
+ }
+ }
+ }
+
/**
* This method generates some events and returns the number of events being generated
*/
diff --git a/docker-java/src/test/java/com/github/dockerjava/cmd/PushImageCmdIT.java b/docker-java/src/test/java/com/github/dockerjava/cmd/PushImageCmdIT.java
index e05ca151e..7f55d5f9e 100644
--- a/docker-java/src/test/java/com/github/dockerjava/cmd/PushImageCmdIT.java
+++ b/docker-java/src/test/java/com/github/dockerjava/cmd/PushImageCmdIT.java
@@ -79,7 +79,7 @@ public void pushNonExistentImage() throws Exception {
exception.expect(NotFoundException.class);
}
- dockerRule.getClient().pushImageCmd("local/" + UUID.randomUUID().toString().replace("-", ""))
+ dockerRule.getClient().pushImageCmd(UUID.randomUUID().toString().replace("-", ""))
.start()
.awaitCompletion(30, TimeUnit.SECONDS); // exclude infinite await sleep
diff --git a/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeContainerCmdIT.java b/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeContainerCmdIT.java
new file mode 100644
index 000000000..eeb79b2ff
--- /dev/null
+++ b/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeContainerCmdIT.java
@@ -0,0 +1,39 @@
+package com.github.dockerjava.cmd;
+
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.SecureRandom;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+public class ResizeContainerCmdIT extends CmdIT {
+ private static final Logger LOG = LoggerFactory.getLogger(ResizeContainerCmdIT.class);
+
+ private static final int TTY_HEIGHT = 30;
+ private static final int TTY_WIDTH = 120;
+
+ @Test
+ public void resizeContainerTtyTest() {
+ String containerName = "generated_" + new SecureRandom().nextInt();
+
+ // wait until tty size changed to target size
+ CreateContainerResponse container = dockerRule.getClient().createContainerCmd("busybox").withUser("root")
+ .withCmd("sh", "-c", String.format("until stty size | grep '%d %d'; do : ; done", TTY_HEIGHT, TTY_WIDTH))
+ .withName(containerName).withTty(true).withStdinOpen(true).exec();
+
+ dockerRule.getClient().startContainerCmd(container.getId()).exec();
+ dockerRule.getClient().resizeContainerCmd(container.getId()).withSize(TTY_HEIGHT, TTY_WIDTH).exec();
+
+ int exitCode = dockerRule.getClient().waitContainerCmd(container.getId()).start()
+ .awaitStatusCode(10, TimeUnit.SECONDS);
+
+ LOG.info("Container exit code: {}", exitCode);
+
+ assertThat(exitCode, equalTo(0));
+ }
+}
diff --git a/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeExecCmdIT.java b/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeExecCmdIT.java
new file mode 100644
index 000000000..9e5c9b65f
--- /dev/null
+++ b/docker-java/src/test/java/com/github/dockerjava/cmd/ResizeExecCmdIT.java
@@ -0,0 +1,48 @@
+package com.github.dockerjava.cmd;
+
+import com.github.dockerjava.api.command.CreateContainerResponse;
+import com.github.dockerjava.api.command.ExecCreateCmdResponse;
+import com.github.dockerjava.core.command.ExecStartResultCallback;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.SecureRandom;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+
+
+public class ResizeExecCmdIT extends CmdIT {
+ private static final Logger LOG = LoggerFactory.getLogger(ResizeExecCmdIT.class);
+
+ private static final int TTY_HEIGHT = 30;
+ private static final int TTY_WIDTH = 120;
+
+ @Test
+ public void resizeExecInstanceTtyTest() throws Exception {
+ String containerName = "generated_" + new SecureRandom().nextInt();
+
+ CreateContainerResponse container = dockerRule.getClient().createContainerCmd("busybox").withUser("root")
+ .withCmd("sleep", "9999").withName(containerName).exec();
+
+ dockerRule.getClient().startContainerCmd(container.getId()).exec();
+
+ // wait until tty size changed to target size
+ ExecCreateCmdResponse execCreateCmdResponse = dockerRule.getClient().execCreateCmd(container.getId()).withTty(true)
+ .withAttachStdout(true).withAttachStderr(true)
+ .withCmd("sh", "-c", String.format("until stty size | grep '%d %d'; do : ; done", TTY_HEIGHT, TTY_WIDTH)).exec();
+
+ final ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback(System.out, System.err);
+
+ dockerRule.getClient().execStartCmd(execCreateCmdResponse.getId()).exec(execStartResultCallback).awaitStarted();
+
+ dockerRule.getClient().resizeExecCmd(execCreateCmdResponse.getId()).withSize(TTY_HEIGHT, TTY_WIDTH).exec();
+
+ // time out, exec instance resize failed
+ boolean waitResult = execStartResultCallback.awaitCompletion(10, TimeUnit.SECONDS);
+
+ assertThat(waitResult, equalTo(true));
+ }
+}
diff --git a/docker-java/src/test/java/com/github/dockerjava/core/util/CertificateUtilsTest.java b/docker-java/src/test/java/com/github/dockerjava/core/util/CertificateUtilsTest.java
index 28818d24c..c29cedcf9 100644
--- a/docker-java/src/test/java/com/github/dockerjava/core/util/CertificateUtilsTest.java
+++ b/docker-java/src/test/java/com/github/dockerjava/core/util/CertificateUtilsTest.java
@@ -11,6 +11,8 @@
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.Security;
+import java.security.cert.Certificate;
+import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
@@ -86,6 +88,20 @@ public void readMultipleCaCerts() throws Exception {
assertThat(keyStore.isCertificateEntry("ca-2"), is(true));
}
+ @Test
+ public void readCert() throws Exception {
+ String certpem = readFileAsString("caTest/single_ca.pem");
+ List certs = CertificateUtils.loadCertificates(certpem);
+ assertThat(certs.size(), is(1));
+ }
+
+ @Test
+ public void readMultipleCerts() throws Exception {
+ String certpem = readFileAsString("caTest/multiple_ca.pem");
+ List certs = CertificateUtils.loadCertificates(certpem);
+ assertThat(certs.size(), is(2));
+ }
+
private String readFileAsString(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(new File(baseDir + path).getPath())));
}
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..4b8ef0798
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,2 @@
+* [Getting Started](./getting_started.md)
+* [Available transports](./transports.md)
diff --git a/docs/devel.adoc b/docs/devel.adoc
deleted file mode 100644
index 1b6295662..000000000
--- a/docs/devel.adoc
+++ /dev/null
@@ -1,34 +0,0 @@
-### Code Design
- * Model is based on Objects and not primitives that allows nullify requests and have null values for data
- that wasn't provided by docker daemon.
- * For null safeness findbugs annotations are used.
- ** Every method that may return `null` (and we are unsure in any fields as docker daemon may change something)
- should be annotated with `@CheckForNull` return qualifier from `javax.annotation` package.
- ** Methods that can't return `null` must be annotated with `@Nonnull`.
- ** The same for Arguments.
- ** `@Nullable` must be used only for changing inherited (other typed) qualifier.
- * Setters in builder style must be prefixed with `withXX`.
- * All classes should provide `toString()` `equals()` and `hashCode()` defined methods.
- * Javadocs
- ** Provide full information on field:
- *** For models define API version with `@since {@link RemoteApiVersion#VERSION_1_X}`.
- ** getters/setters should refernce to field `@see #$field`.
- * If it is `Serializable` it shall have a `serialVersionUID` field. Unless code has shipped to users, the initial value of the `serialVersionUID` field shall be `1L`.
-
-### Coding style
- * TBD, some initial styling already enforced with checkstyle.
- IDEA/checkstyle file analogues will be provided soon.
-
-### Testing
- * Unit tests for serder (serialization-deserialization).
- * Integration tests for commands.
- * If model object has builders, then fill it with data and compare by `equals()` with expected response
- from docker daemon. If failed, then some fields mappings are wrong.
-
-### Debug
- * When there are unreproducible Travis errors:
- ** Try locally run test 10-20 times in IDE against the same docker daemon version and same connection type (tcp or socket).
- ** Limit `.travis.yml` to single run (to not consume their resources with matrix run).
- ** Remove `travis-logback.xml` replacement (build can't output everything in every run because travis has log limitation).
- ** Set single test in `pom.xml` `for maven-failsafe-plugin`
- ** Make PR or if you are maintainer push to branch, catch log and fix.
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 000000000..ebc41d62e
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,121 @@
+# Getting Started
+
+## Dependencies
+
+To start using `docker-java` , you need to add at least two dependencies:
+1. `com.github.docker-java:docker-java-core` for the `DockerClient`
+1. one of `com.github.docker-java:docker-java-transport-*` to communicate with the Docker daemon. See [Available Transports](./transports.md) for more info.
+
+The latest available version:
+[](https://mvnrepository.com/artifact/com.github.docker-java/docker-java)
+
+
+## Instantiating a `DockerClientConfig`
+
+You will need an instance of `DockerClientConfig` to tell the library how to access Docker, which credentials to use to pull from Docker registries, etc etc.
+
+The builder is available and allows you to configure every property of the client:
+```java
+DockerClientConfig standard = DefaultDockerClientConfig.createDefaultConfigBuilder().build();
+```
+
+```java
+DockerClientConfig custom = DefaultDockerClientConfig.createDefaultConfigBuilder()
+ .withDockerHost("tcp://docker.somewhere.tld:2376")
+ .withDockerTlsVerify(true)
+ .withDockerCertPath("/home/user/.docker")
+ .withRegistryUsername(registryUser)
+ .withRegistryPassword(registryPass)
+ .withRegistryEmail(registryMail)
+ .withRegistryUrl(registryUrl)
+ .build();
+```
+
+Here you can tune registry auth, DOCKER_HOST and other options.
+
+There are a couple of configuration items, all of which have sensible defaults:
+
+* `DOCKER_HOST` The Docker Host URL, e.g. `tcp://localhost:2376` or `unix:///var/run/docker.sock`
+* `DOCKER_TLS_VERIFY` enable/disable TLS verification (switch between `http` and `https` protocol)
+* `DOCKER_CERT_PATH` Path to the certificates needed for TLS verification
+* `DOCKER_CONFIG` Path for additional docker configuration files (like `.dockercfg`)
+* `api.version` The API version, e.g. `1.23`.
+* `registry.url` Your registry's address.
+* `registry.username` Your registry username (required to push containers).
+* `registry.password` Your registry password.
+* `registry.email` Your registry email.
+
+There are three ways to configure, in descending order of precedence:
+
+##### Properties (docker-java.properties)
+
+ DOCKER_HOST=tcp://localhost:2376
+ DOCKER_TLS_VERIFY=1
+ DOCKER_CERT_PATH=/home/user/.docker/certs
+ DOCKER_CONFIG=/home/user/.docker
+ api.version=1.23
+ registry.url=https://index.docker.io/v1/
+ registry.username=dockeruser
+ registry.password=ilovedocker
+ registry.email=dockeruser@github.com
+
+##### System Properties:
+
+ java -DDOCKER_HOST=tcp://localhost:2375 -Dregistry.username=dockeruser pkg.Main
+
+##### System Environment
+
+ export DOCKER_HOST=tcp://localhost:2376
+ export DOCKER_TLS_VERIFY=1
+ export DOCKER_CERT_PATH=/home/user/.docker/certs
+ export DOCKER_CONFIG=/home/user/.docker
+
+##### File System
+
+In `$HOME/.docker-java.properties`
+
+##### Class Path
+
+In the class path at `/docker-java.properties`
+
+### Jackson
+
+Should you need to customize the Jackson's `ObjectMapper` used by `docker-java`, you can create your own `DockerClientConfig` and override `DockerClientConfig#getObjectMapper()`.
+
+## Instantiating a `DockerHttpClient`
+Once you decided which transport to use, you will need to instantiate an HTTP client:
+```java
+DockerClientConfig config = ...;
+
+DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
+ .dockerHost(config.getDockerHost())
+ .sslConfig(config.getSSLConfig())
+ .build();
+```
+
+Please refer to selected transport's builder for other available configuration options (like timeouts).
+
+Once you have an HTTP client, you can make raw requests to the Docker daemon directly:
+```java
+Request request = Request.builder()
+ .method(Request.Method.GET)
+ .path("/_ping")
+ .build();
+
+try (Response response = httpClient.execute(request)) {
+ assertThat(response.getStatusCode(), equalTo(200));
+ assertThat(IOUtils.toString(response.getBody()), equalTo("OK"));
+}
+```
+
+## Instantiating a `DockerClient`
+
+To get an instance of `DockerClient`, you need to pass both `DockerClientConfig` and `DockerHttpClient`:
+```java
+DockerClient dockerClient = DockerClientImpl.getInstance(config, httpClient);
+```
+
+Once you have it, you can start executing Docker commands:
+```java
+dockerClient.pingCmd().exec();
+```
\ No newline at end of file
diff --git a/docs/transports.md b/docs/transports.md
new file mode 100644
index 000000000..0daa429ab
--- /dev/null
+++ b/docs/transports.md
@@ -0,0 +1,74 @@
+# Available transports
+
+## Apache HttpClient 5
+| | |
+|---|---|
+| Maven coordinates | `com.github.docker-java:docker-java-transport-httpclient5` |
+| Stabilitty | 🙂|
+| Long term support plans | ✅ |
+| Unix sockets support | ✅ |
+| Windows Npipe support | ✅ |
+| Stdin attachment support | ✅ |
+
+This transport is based on Apache HttpClient library version 5, which has a great flexibility and allows us to implement all Docker-specific features and protocols required, without having to use internal APIs or anything.
+
+It has everything to become the default transport of docker-java in future releases.
+
+## "Zerodep"
+| | |
+|---|---|
+| Maven coordinates | `com.github.docker-java:docker-java-transport-zerodep` |
+| Stabilitty | 🙂|
+| Long term support plans | ✅ |
+| Unix sockets support | ✅ |
+| Windows Npipe support | ✅ |
+| Stdin attachment support | ✅ |
+
+The idea of this transport is to provide a transport that supports 100% of the features without having to sorry about transitive dependencies.
+
+Note: due to the implementation details, it cannot be true "0 dependencies" module, so it needs to depend on `slf4j-api` and JNA.
+
+## OkHttp
+| | |
+|---|---|
+| Maven coordinates | `com.github.docker-java:docker-java-transport-okhttp` |
+| Stabilitty | 🧐|
+| Long term support plans | ❓ |
+| Unix sockets support | ✅ |
+| Windows Npipe support | ✅ |
+| Stdin attachment support | ✅ |
+
+The OkHttp transport was first implemented in [the Testcontainers library](http://github.com/testcontainers/testcontainers-java) as a replacement for Netty. The main motivation for it was to not have heavy-weight Netty-specific native dependencies and the lack of Npipe support in the Netty one.
+
+OkHttp's migration to Kotlin and the need to use internal APIs for doing stdin hijacking makes us question the future of this transport (still under the consideration).
+
+## Netty
+| | |
+|---|---|
+| Maven coordinates | `com.github.docker-java:docker-java-transport-netty` |
+| Stabilitty | 🧐|
+| Long term support plans | ❌ |
+| Unix sockets support | ✅ |
+| Windows Npipe support | ❌ |
+| Stdin attachment support | ✅ |
+
+Netty was the first alternative transport introduced as an alternative to Jersey.
+
+Although it gives a very low level access to the protocol, the lack of Windows Npipe support and the native library dependency for Unix Sockets make it hard to maintain and there are no plans to continue including this transport option in future versions.
+
+The community may decide to pick it up and continue the development as a 3rd party transport based on the existing abstractions `docker-java` provides.
+
+## Jersey
+| | |
+|---|---|
+| Maven coordinates | `com.github.docker-java:docker-java-transport-jersey` |
+| Stabilitty | 🙃|
+| Long term support plans | ❌ |
+| Unix sockets support | ✅ |
+| Windows Npipe support | ❌ |
+| Stdin attachment support | ❌ |
+
+Jersey was the initial transport of the project. And, while working well, it was lacking support for connection hijacking (e.g. stdin attachment) or Windows Npipes.
+The big amount of dependencies was also causing issues.
+
+Since Apache HttpClient 5-based transport is available now, there is no reason to keep Jersey and it will eventually be removed.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4faa09eaf..57809069a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,7 @@
docker-java-transport-jersey
docker-java-transport-okhttp
docker-java-transport-httpclient5
+ docker-java-transport-zerodep
docker-java