package com.github.dockerjava.netty;
import com.github.dockerjava.api.command.AttachContainerCmd;
import com.github.dockerjava.api.command.AuthCmd;
import com.github.dockerjava.api.command.BuildImageCmd;
import com.github.dockerjava.api.command.CommitCmd;
import com.github.dockerjava.api.command.ConnectToNetworkCmd;
import com.github.dockerjava.api.command.ContainerDiffCmd;
import com.github.dockerjava.api.command.CopyArchiveFromContainerCmd;
import com.github.dockerjava.api.command.CopyArchiveToContainerCmd;
import com.github.dockerjava.api.command.CopyFileFromContainerCmd;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateImageCmd;
import com.github.dockerjava.api.command.CreateNetworkCmd;
import com.github.dockerjava.api.command.CreateVolumeCmd;
import com.github.dockerjava.api.command.DisconnectFromNetworkCmd;
import com.github.dockerjava.api.command.DockerCmdExecFactory;
import com.github.dockerjava.api.command.EventsCmd;
import com.github.dockerjava.api.command.ExecCreateCmd;
import com.github.dockerjava.api.command.ExecStartCmd;
import com.github.dockerjava.api.command.InfoCmd;
import com.github.dockerjava.api.command.InspectContainerCmd;
import com.github.dockerjava.api.command.InspectExecCmd;
import com.github.dockerjava.api.command.InspectImageCmd;
import com.github.dockerjava.api.command.InspectNetworkCmd;
import com.github.dockerjava.api.command.InspectVolumeCmd;
import com.github.dockerjava.api.command.KillContainerCmd;
import com.github.dockerjava.api.command.ListContainersCmd;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.command.ListNetworksCmd;
import com.github.dockerjava.api.command.ListVolumesCmd;
import com.github.dockerjava.api.command.LoadImageCmd;
import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.command.PauseContainerCmd;
import com.github.dockerjava.api.command.PingCmd;
import com.github.dockerjava.api.command.PullImageCmd;
import com.github.dockerjava.api.command.PushImageCmd;
import com.github.dockerjava.api.command.RemoveContainerCmd;
import com.github.dockerjava.api.command.RemoveImageCmd;
import com.github.dockerjava.api.command.RemoveNetworkCmd;
import com.github.dockerjava.api.command.RemoveVolumeCmd;
import com.github.dockerjava.api.command.RestartContainerCmd;
import com.github.dockerjava.api.command.SaveImageCmd;
import com.github.dockerjava.api.command.SearchImagesCmd;
import com.github.dockerjava.api.command.StartContainerCmd;
import com.github.dockerjava.api.command.StatsCmd;
import com.github.dockerjava.api.command.StopContainerCmd;
import com.github.dockerjava.api.command.TagImageCmd;
import com.github.dockerjava.api.command.TopContainerCmd;
import com.github.dockerjava.api.command.UnpauseContainerCmd;
import com.github.dockerjava.api.command.UpdateContainerCmd;
import com.github.dockerjava.api.command.VersionCmd;
import com.github.dockerjava.api.command.WaitContainerCmd;
import com.github.dockerjava.api.command.RenameContainerCmd;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.core.SSLConfig;
import com.github.dockerjava.netty.exec.AttachContainerCmdExec;
import com.github.dockerjava.netty.exec.AuthCmdExec;
import com.github.dockerjava.netty.exec.BuildImageCmdExec;
import com.github.dockerjava.netty.exec.CommitCmdExec;
import com.github.dockerjava.netty.exec.ConnectToNetworkCmdExec;
import com.github.dockerjava.netty.exec.ContainerDiffCmdExec;
import com.github.dockerjava.netty.exec.CopyArchiveFromContainerCmdExec;
import com.github.dockerjava.netty.exec.CopyArchiveToContainerCmdExec;
import com.github.dockerjava.netty.exec.CopyFileFromContainerCmdExec;
import com.github.dockerjava.netty.exec.CreateContainerCmdExec;
import com.github.dockerjava.netty.exec.CreateImageCmdExec;
import com.github.dockerjava.netty.exec.CreateNetworkCmdExec;
import com.github.dockerjava.netty.exec.CreateVolumeCmdExec;
import com.github.dockerjava.netty.exec.DisconnectFromNetworkCmdExec;
import com.github.dockerjava.netty.exec.EventsCmdExec;
import com.github.dockerjava.netty.exec.ExecCreateCmdExec;
import com.github.dockerjava.netty.exec.ExecStartCmdExec;
import com.github.dockerjava.netty.exec.InfoCmdExec;
import com.github.dockerjava.netty.exec.InspectContainerCmdExec;
import com.github.dockerjava.netty.exec.InspectExecCmdExec;
import com.github.dockerjava.netty.exec.InspectImageCmdExec;
import com.github.dockerjava.netty.exec.InspectNetworkCmdExec;
import com.github.dockerjava.netty.exec.InspectVolumeCmdExec;
import com.github.dockerjava.netty.exec.KillContainerCmdExec;
import com.github.dockerjava.netty.exec.ListContainersCmdExec;
import com.github.dockerjava.netty.exec.ListImagesCmdExec;
import com.github.dockerjava.netty.exec.ListNetworksCmdExec;
import com.github.dockerjava.netty.exec.ListVolumesCmdExec;
import com.github.dockerjava.netty.exec.LoadImageCmdExec;
import com.github.dockerjava.netty.exec.LogContainerCmdExec;
import com.github.dockerjava.netty.exec.PauseContainerCmdExec;
import com.github.dockerjava.netty.exec.PingCmdExec;
import com.github.dockerjava.netty.exec.PullImageCmdExec;
import com.github.dockerjava.netty.exec.PushImageCmdExec;
import com.github.dockerjava.netty.exec.RemoveContainerCmdExec;
import com.github.dockerjava.netty.exec.RemoveImageCmdExec;
import com.github.dockerjava.netty.exec.RemoveNetworkCmdExec;
import com.github.dockerjava.netty.exec.RemoveVolumeCmdExec;
import com.github.dockerjava.netty.exec.RestartContainerCmdExec;
import com.github.dockerjava.netty.exec.SaveImageCmdExec;
import com.github.dockerjava.netty.exec.SearchImagesCmdExec;
import com.github.dockerjava.netty.exec.StartContainerCmdExec;
import com.github.dockerjava.netty.exec.StatsCmdExec;
import com.github.dockerjava.netty.exec.StopContainerCmdExec;
import com.github.dockerjava.netty.exec.TagImageCmdExec;
import com.github.dockerjava.netty.exec.TopContainerCmdExec;
import com.github.dockerjava.netty.exec.UnpauseContainerCmdExec;
import com.github.dockerjava.netty.exec.UpdateContainerCmdExec;
import com.github.dockerjava.netty.exec.VersionCmdExec;
import com.github.dockerjava.netty.exec.WaitContainerCmdExec;
import com.github.dockerjava.netty.exec.RenameContainerCmdExec;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelConfig;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollDomainSocketChannel;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DuplexChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.channel.unix.UnixChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.security.Security;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Experimental implementation of {@link DockerCmdExecFactory} that supports http connection hijacking that is needed to pass STDIN to the
* container.
*
* To use it just pass an instance via {@link DockerClientImpl#withDockerCmdExecFactory(DockerCmdExecFactory)}
*
* @see https://docs.docker.com/engine/reference/api/docker_remote_api_v1.21/#attach-to-a-container
* @see https://docs.docker.com/engine/reference/api/docker_remote_api_v1.21/#exec-start
*
*
* @author Marcus Linke
*/
public class NettyDockerCmdExecFactory implements DockerCmdExecFactory {
private static String threadPrefix = "dockerjava-netty";
/*
* useful links:
*
* http://stackoverflow.com/questions/33296749/netty-connect-to-unix-domain-socket-failed
* http://netty.io/wiki/native-transports.html
* https://github.com/netty/netty/blob/master/example/src/main/java/io/netty/example/http/snoop/HttpSnoopClient.java
* https://github.com/slandelle/netty-request-chunking/blob/master/src/test/java/slandelle/ChunkingTest.java
*/
private DockerClientConfig dockerClientConfig;
private Bootstrap bootstrap;
private EventLoopGroup eventLoopGroup;
private NettyInitializer nettyInitializer;
private WebTarget baseResource;
private ChannelProvider channelProvider = new ChannelProvider() {
@Override
public DuplexChannel getChannel() {
DuplexChannel channel = connect();
channel.pipeline().addLast(new LoggingHandler(getClass()));
return channel;
}
};
private Integer connectTimeout = null;
@Override
public void init(DockerClientConfig dockerClientConfig) {
checkNotNull(dockerClientConfig, "config was not specified");
this.dockerClientConfig = dockerClientConfig;
bootstrap = new Bootstrap();
String scheme = dockerClientConfig.getDockerHost().getScheme();
if ("unix".equals(scheme)) {
nettyInitializer = new UnixDomainSocketInitializer();
} else if ("tcp".equals(scheme)) {
nettyInitializer = new InetSocketInitializer();
}
eventLoopGroup = nettyInitializer.init(bootstrap, dockerClientConfig);
baseResource = new WebTarget(channelProvider).path(dockerClientConfig.getApiVersion().asWebPathPart());
}
private DuplexChannel connect() {
try {
return connect(bootstrap);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private DuplexChannel connect(final Bootstrap bootstrap) throws InterruptedException {
return nettyInitializer.connect(bootstrap);
}
private interface NettyInitializer {
EventLoopGroup init(final Bootstrap bootstrap, DockerClientConfig dockerClientConfig);
DuplexChannel connect(final Bootstrap bootstrap) throws InterruptedException;
}
private class UnixDomainSocketInitializer implements NettyInitializer {
@Override
public EventLoopGroup init(Bootstrap bootstrap, DockerClientConfig dockerClientConfig) {
EventLoopGroup epollEventLoopGroup = new EpollEventLoopGroup(0, new DefaultThreadFactory(threadPrefix));
ChannelFactory<EpollDomainSocketChannel> factory = new ChannelFactory<EpollDomainSocketChannel>() {
@Override
public EpollDomainSocketChannel newChannel() {
return configure(new EpollDomainSocketChannel());
}
};
bootstrap.group(epollEventLoopGroup).channelFactory(factory)
.handler(new ChannelInitializer<UnixChannel>() {
@Override
protected void initChannel(final UnixChannel channel) throws Exception {
channel.pipeline().addLast(new HttpClientCodec());
}
});
return epollEventLoopGroup;
}
@Override
public DuplexChannel connect(Bootstrap bootstrap) throws InterruptedException {
return (DuplexChannel) bootstrap.connect(new DomainSocketAddress("/var/run/docker.sock")).sync().channel();
}
}
private class InetSocketInitializer implements NettyInitializer {
@Override
public EventLoopGroup init(Bootstrap bootstrap, final DockerClientConfig dockerClientConfig) {
EventLoopGroup nioEventLoopGroup = new NioEventLoopGroup(0, new DefaultThreadFactory(threadPrefix));
InetAddress addr = InetAddress.getLoopbackAddress();
final SocketAddress proxyAddress = new InetSocketAddress(addr, 8008);
Security.addProvider(new BouncyCastleProvider());
ChannelFactory<NioSocketChannel> factory = new ChannelFactory<NioSocketChannel>() {
@Override
public NioSocketChannel newChannel() {
return configure(new NioSocketChannel());
}
};
bootstrap.group(nioEventLoopGroup).channelFactory(factory)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(final SocketChannel channel) throws Exception {
// channel.pipeline().addLast(new
// HttpProxyHandler(proxyAddress));
channel.pipeline().addLast(new HttpClientCodec());
}
});
return nioEventLoopGroup;
}
@Override
public DuplexChannel connect(Bootstrap bootstrap) throws InterruptedException {
String host = dockerClientConfig.getDockerHost().getHost();
int port = dockerClientConfig.getDockerHost().getPort();
if (port == -1) {
throw new RuntimeException("no port configured for " + host);
}
DuplexChannel channel = (DuplexChannel) bootstrap.connect(host, port).sync().channel();
final SslHandler ssl = initSsl(dockerClientConfig);
if (ssl != null) {
channel.pipeline().addFirst(ssl);
}
return channel;
}
private SslHandler initSsl(DockerClientConfig dockerClientConfig) {
SslHandler ssl = null;
try {
String host = dockerClientConfig.getDockerHost().getHost();
int port = dockerClientConfig.getDockerHost().getPort();
final SSLConfig sslConfig = dockerClientConfig.getSSLConfig();
if (sslConfig != null && sslConfig.getSSLContext() != null) {
SSLEngine engine = sslConfig.getSSLContext().createSSLEngine(host, port);
engine.setUseClientMode(true);
engine.setSSLParameters(enableHostNameVerification(engine.getSSLParameters()));
// in the future we may use HostnameVerifier like here:
// https://github.com/AsyncHttpClient/async-http-client/blob/1.8.x/src/main/java/com/ning/http/client/providers/netty/NettyConnectListener.java#L76
ssl = new SslHandler(engine);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return ssl;
}
}
protected DockerClientConfig getDockerClientConfig() {
checkNotNull(dockerClientConfig,
"Factor not initialized, dockerClientConfig not set. You probably forgot to call init()!");
return dockerClientConfig;
}
public SSLParameters enableHostNameVerification(SSLParameters sslParameters) {
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
return sslParameters;
}
@Override
public CopyArchiveFromContainerCmd.Exec createCopyArchiveFromContainerCmdExec() {
return new CopyArchiveFromContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CopyArchiveToContainerCmd.Exec createCopyArchiveToContainerCmdExec() {
return new CopyArchiveToContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public AuthCmd.Exec createAuthCmdExec() {
return new AuthCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InfoCmd.Exec createInfoCmdExec() {
return new InfoCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public PingCmd.Exec createPingCmdExec() {
return new PingCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public VersionCmd.Exec createVersionCmdExec() {
return new VersionCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public PullImageCmd.Exec createPullImageCmdExec() {
return new PullImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public PushImageCmd.Exec createPushImageCmdExec() {
return new PushImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public SaveImageCmd.Exec createSaveImageCmdExec() {
return new SaveImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CreateImageCmd.Exec createCreateImageCmdExec() {
return new CreateImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public LoadImageCmd.Exec createLoadImageCmdExec() {
return new LoadImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public SearchImagesCmd.Exec createSearchImagesCmdExec() {
return new SearchImagesCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RemoveImageCmd.Exec createRemoveImageCmdExec() {
return new RemoveImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ListImagesCmd.Exec createListImagesCmdExec() {
return new ListImagesCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InspectImageCmd.Exec createInspectImageCmdExec() {
return new InspectImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ListContainersCmd.Exec createListContainersCmdExec() {
return new ListContainersCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CreateContainerCmd.Exec createCreateContainerCmdExec() {
return new CreateContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public StartContainerCmd.Exec createStartContainerCmdExec() {
return new StartContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InspectContainerCmd.Exec createInspectContainerCmdExec() {
return new InspectContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ExecCreateCmd.Exec createExecCmdExec() {
return new ExecCreateCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RemoveContainerCmd.Exec createRemoveContainerCmdExec() {
return new RemoveContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public WaitContainerCmd.Exec createWaitContainerCmdExec() {
return new WaitContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public AttachContainerCmd.Exec createAttachContainerCmdExec() {
return new AttachContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ExecStartCmd.Exec createExecStartCmdExec() {
return new ExecStartCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InspectExecCmd.Exec createInspectExecCmdExec() {
return new InspectExecCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public LogContainerCmd.Exec createLogContainerCmdExec() {
return new LogContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CopyFileFromContainerCmd.Exec createCopyFileFromContainerCmdExec() {
return new CopyFileFromContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public StopContainerCmd.Exec createStopContainerCmdExec() {
return new StopContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ContainerDiffCmd.Exec createContainerDiffCmdExec() {
return new ContainerDiffCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public KillContainerCmd.Exec createKillContainerCmdExec() {
return new KillContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public UpdateContainerCmd.Exec createUpdateContainerCmdExec() {
return new UpdateContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RenameContainerCmd.Exec createRenameContainerCmdExec() {
return new RenameContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RestartContainerCmd.Exec createRestartContainerCmdExec() {
return new RestartContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CommitCmd.Exec createCommitCmdExec() {
return new CommitCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public BuildImageCmd.Exec createBuildImageCmdExec() {
return new BuildImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public TopContainerCmd.Exec createTopContainerCmdExec() {
return new TopContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public TagImageCmd.Exec createTagImageCmdExec() {
return new TagImageCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public PauseContainerCmd.Exec createPauseContainerCmdExec() {
return new PauseContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public UnpauseContainerCmd.Exec createUnpauseContainerCmdExec() {
return new UnpauseContainerCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public EventsCmd.Exec createEventsCmdExec() {
return new EventsCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public StatsCmd.Exec createStatsCmdExec() {
return new StatsCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CreateVolumeCmd.Exec createCreateVolumeCmdExec() {
return new CreateVolumeCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InspectVolumeCmd.Exec createInspectVolumeCmdExec() {
return new InspectVolumeCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RemoveVolumeCmd.Exec createRemoveVolumeCmdExec() {
return new RemoveVolumeCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ListVolumesCmd.Exec createListVolumesCmdExec() {
return new ListVolumesCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ListNetworksCmd.Exec createListNetworksCmdExec() {
return new ListNetworksCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public InspectNetworkCmd.Exec createInspectNetworkCmdExec() {
return new InspectNetworkCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public CreateNetworkCmd.Exec createCreateNetworkCmdExec() {
return new CreateNetworkCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public RemoveNetworkCmd.Exec createRemoveNetworkCmdExec() {
return new RemoveNetworkCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public ConnectToNetworkCmd.Exec createConnectToNetworkCmdExec() {
return new ConnectToNetworkCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public DisconnectFromNetworkCmd.Exec createDisconnectFromNetworkCmdExec() {
return new DisconnectFromNetworkCmdExec(getBaseResource(), getDockerClientConfig());
}
@Override
public void close() throws IOException {
checkNotNull(eventLoopGroup, "Factory not initialized. You probably forgot to call init()!");
eventLoopGroup.shutdownGracefully();
}
/**
* Configure connection timeout in milliseconds
*/
public NettyDockerCmdExecFactory withConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}
private <T extends Channel> T configure(T channel) {
ChannelConfig channelConfig = channel.config();
if (connectTimeout != null) {
channelConfig.setConnectTimeoutMillis(connectTimeout);
}
return channel;
}
private WebTarget getBaseResource() {
checkNotNull(baseResource, "Factory not initialized, baseResource not set. You probably forgot to call init()!");
return baseResource;
}
}