package org.arquillian.cube.docker.impl.docker; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.BuildImageCmd; import com.github.dockerjava.api.command.CreateContainerCmd; import com.github.dockerjava.api.command.CreateNetworkCmd; import com.github.dockerjava.api.command.CreateNetworkResponse; import com.github.dockerjava.api.command.ExecCreateCmdResponse; import com.github.dockerjava.api.command.InspectContainerResponse; import com.github.dockerjava.api.command.InspectExecResponse; import com.github.dockerjava.api.command.LogContainerCmd; import com.github.dockerjava.api.command.PingCmd; import com.github.dockerjava.api.command.PullImageCmd; import com.github.dockerjava.api.command.StartContainerCmd; import com.github.dockerjava.api.command.StatsCmd; import com.github.dockerjava.api.command.TopContainerResponse; import com.github.dockerjava.api.exception.ConflictException; import com.github.dockerjava.api.exception.NotFoundException; import com.github.dockerjava.api.exception.NotModifiedException; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Capability; import com.github.dockerjava.api.model.ChangeLog; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.Device; import com.github.dockerjava.api.model.ExposedPort; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.api.model.InternetProtocol; import com.github.dockerjava.api.model.Link; import com.github.dockerjava.api.model.Ports; import com.github.dockerjava.api.model.Ports.Binding; import com.github.dockerjava.api.model.RestartPolicy; import com.github.dockerjava.api.model.Statistics; import com.github.dockerjava.api.model.Version; import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.api.model.VolumesFrom; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.async.ResultCallbackTemplate; import com.github.dockerjava.core.command.BuildImageResultCallback; import com.github.dockerjava.core.command.ExecStartResultCallback; import com.github.dockerjava.core.command.LogContainerResultCallback; import com.github.dockerjava.core.command.PullImageResultCallback; import com.github.dockerjava.core.command.WaitContainerResultCallback; import org.apache.http.conn.UnsupportedSchemeException; import org.arquillian.cube.TopContainer; import org.arquillian.cube.docker.impl.await.StatsLogsResultCallback; import org.arquillian.cube.docker.impl.client.CubeDockerConfiguration; import org.arquillian.cube.docker.impl.client.config.BuildImage; import org.arquillian.cube.docker.impl.client.config.CubeContainer; import org.arquillian.cube.docker.impl.client.config.IPAMConfig; import org.arquillian.cube.docker.impl.client.config.Image; import org.arquillian.cube.docker.impl.client.config.Network; import org.arquillian.cube.docker.impl.client.config.PortBinding; import org.arquillian.cube.docker.impl.util.BindingUtil; import org.arquillian.cube.docker.impl.util.HomeResolverUtil; import org.arquillian.cube.spi.CubeOutput; import javax.ws.rs.ProcessingException; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; public class DockerClientExecutor { public static final String PATH_IN_CONTAINER = "pathInContainer"; public static final String PATH_ON_HOST = "pathOnHost"; public static final String C_GROUP_PERMISSIONS = "cGroupPermissions"; public static final String PORTS_SEPARATOR = BindingUtil.PORTS_SEPARATOR; public static final String TAG_SEPARATOR = ":"; public static final String RESTART_POLICY = "restartPolicy"; public static final String CAP_DROP = "capDrop"; public static final String CAP_ADD = "capAdd"; public static final String DEVICES = "devices"; public static final String DNS_SEARCH = "dnsSearch"; public static final String NETWORK_MODE = "networkMode"; public static final String PUBLISH_ALL_PORTS = "publishAllPorts"; public static final String PRIVILEGED = "privileged"; public static final String PORT_BINDINGS = "portBindings"; public static final String LINKS = "links"; public static final String BINDS = "binds"; public static final String VOLUMES_FROM = "volumesFrom"; public static final String VOLUMES = "volumes"; public static final String DNS = "dns"; public static final String CMD = "cmd"; public static final String ENV = "env"; public static final String EXPOSED_PORTS = "exposedPorts"; public static final String ATTACH_STDERR = "attachStderr"; public static final String ATTACH_STDIN = "attachStdin"; public static final String CPU_SHARES = "cpuShares"; public static final String MEMORY_SWAP = "memorySwap"; public static final String MEMORY_LIMIT = "memoryLimit"; public static final String STDIN_ONCE = "stdinOnce"; public static final String STDIN_OPEN = "stdinOpen"; public static final String TTY = "tty"; public static final String USER = "user"; public static final String PORT_SPECS = "portSpecs"; public static final String HOST_NAME = "hostName"; public static final String DISABLE_NETWORK = "disableNetwork"; public static final String WORKING_DIR = "workingDir"; public static final String IMAGE = "image"; public static final String BUILD_IMAGE = "buildImage"; public static final String DOCKERFILE_LOCATION = "dockerfileLocation"; public static final String NO_CACHE = "noCache"; public static final String REMOVE = "remove"; public static final String ALWAYS_PULL = "alwaysPull"; public static final String ENTRYPOINT = "entryPoint"; public static final String CPU_SET = "cpuSet"; public static final String DOCKERFILE_NAME = "dockerfileName"; public static final String EXTRA_HOSTS = "extraHosts"; public static final String READ_ONLY_ROOT_FS = "ReadonlyRootfs"; public static final String LABELS = "labels"; public static final String DOMAINNAME = "domainName"; private static final String DEFAULT_C_GROUPS_PERMISSION = "rwm"; private static final Logger log = Logger.getLogger(DockerClientExecutor.class.getName()); private static final Pattern IMAGEID_PATTERN = Pattern.compile(".*Successfully built\\s(\\p{XDigit}+)"); private final URI dockerUri; private final String dockerServerIp; private DockerClient dockerClient; private CubeDockerConfiguration cubeConfiguration; private DockerClientConfig dockerClientConfig; //this should be removed in the future it is only a hack to avoid some errors with Hijack is incompatible with use of CloseNotifier. // It seems to be a problem with go and should be fixed in go 1.6 (and maybe in Docker 1.11.0). #320 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public DockerClientExecutor(CubeDockerConfiguration cubeConfiguration) { final DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig .createDefaultConfigBuilder(); String dockerServerUri = cubeConfiguration.getDockerServerUri(); dockerUri = URI.create(dockerServerUri); dockerServerIp = cubeConfiguration.getDockerServerIp(); configBuilder.withApiVersion(cubeConfiguration.getDockerServerVersion()) .withDockerHost(dockerUri.toString()); if (cubeConfiguration.getUsername() != null) { configBuilder.withRegistryUsername(cubeConfiguration.getUsername()); } if (cubeConfiguration.getPassword() != null) { configBuilder.withRegistryPassword(cubeConfiguration.getPassword()); } if (cubeConfiguration.getEmail() != null) { configBuilder.withRegistryEmail(cubeConfiguration.getEmail()); } if (cubeConfiguration.getDockerRegistry() != null) { configBuilder.withRegistryUrl(cubeConfiguration.getDockerRegistry()); } if (cubeConfiguration.getCertPath() != null) { configBuilder.withDockerCertPath(HomeResolverUtil.resolveHomeDirectoryChar(cubeConfiguration.getCertPath())); } configBuilder.withDockerTlsVerify(cubeConfiguration.getTlsVerify()); this.dockerClientConfig = configBuilder.build(); this.cubeConfiguration = cubeConfiguration; this.dockerClient = buildDockerClient(); } public static String getImageId(String fullLog) { Matcher m = IMAGEID_PATTERN.matcher(fullLog); String imageId = null; if (m.find()) { imageId = m.group(1); } return imageId; } private static final Device[] toDevices(Collection<org.arquillian.cube.docker.impl.client.config.Device> deviceList) { Device[] devices = new Device[deviceList.size()]; int i = 0; for (org.arquillian.cube.docker.impl.client.config.Device device : deviceList) { if (device.getPathOnHost() != null && device.getPathInContainer() != null) { String cGroupPermissions; if (device.getcGroupPermissions() != null) { cGroupPermissions = device.getcGroupPermissions(); } else { cGroupPermissions = DEFAULT_C_GROUPS_PERMISSION; } String pathOnHost = device.getPathOnHost(); String pathInContainer = device.getPathInContainer(); devices[i] = new Device(cGroupPermissions, pathInContainer, pathOnHost); i++; } } return devices; } private static final RestartPolicy toRestartPolicy( org.arquillian.cube.docker.impl.client.config.RestartPolicy restart) { if (restart.getName() != null) { String name = restart.getName(); if ("failure".equals(name)) { return RestartPolicy.onFailureRestart(restart.getMaximumRetryCount()); } else { if ("restart".equals(name)) { return RestartPolicy.alwaysRestart(); } else { return RestartPolicy.noRestart(); } } } else { return RestartPolicy.noRestart(); } } private static final Link[] toLinks(Collection<org.arquillian.cube.docker.impl.client.config.Link> linkList) { Link[] links = new Link[linkList.size()]; int i = 0; for (org.arquillian.cube.docker.impl.client.config.Link link : linkList) { links[i] = new Link(link.getName(), link.getAlias()); i++; } return links; } private static final Capability[] toCapability(Collection<String> configuredCapabilities) { List<Capability> capabilities = new ArrayList<Capability>(); for (String capability : configuredCapabilities) { capabilities.add(Capability.valueOf(capability)); } return capabilities.toArray(new Capability[capabilities.size()]); } private static final Bind[] toBinds(Collection<String> bindsList) { Bind[] binds = new Bind[bindsList.size()]; int i = 0; for (String bind : bindsList) { binds[i] = Bind.parse(bind); i++; } return binds; } private static final Volume[] toVolumes(Collection<String> volumesList) { Volume[] volumes = new Volume[volumesList.size()]; int i = 0; for (String volume : volumesList) { String[] volumeSection = volume.split(":"); if (volumeSection.length == 2 || volumeSection.length == 3) { volumes[i] = new Volume(volumeSection[1]); } else { volumes[i] = new Volume(volumeSection[0]); } i++; } return volumes; } private static final VolumesFrom[] toVolumesFrom(Collection<String> volumesFromList) { VolumesFrom[] volumesFrom = new VolumesFrom[volumesFromList.size()]; int i = 0; for (String volumesFromm : volumesFromList) { volumesFrom[i] = VolumesFrom.parse(volumesFromm); i++; } return volumesFrom; } public DockerClient buildDockerClient() { return DockerClientBuilder.getInstance(dockerClientConfig).build(); } public List<Container> listRunningContainers() { this.readWriteLock.readLock().lock(); try { return this.dockerClient.listContainersCmd().exec(); } finally { this.readWriteLock.readLock().unlock(); } } public String createContainer(String name, CubeContainer containerConfiguration) { String image = getImageName(containerConfiguration, name); try { this.readWriteLock.readLock().lock(); CreateContainerCmd createContainerCmd = this.dockerClient.createContainerCmd(image); createContainerCmd.withName(name); Set<ExposedPort> allExposedPorts = resolveExposedPorts(containerConfiguration, createContainerCmd); if (!allExposedPorts.isEmpty()) { int numberOfExposedPorts = allExposedPorts.size(); createContainerCmd.withExposedPorts(allExposedPorts.toArray(new ExposedPort[numberOfExposedPorts])); } if (containerConfiguration.getReadonlyRootfs() != null) { createContainerCmd.withReadonlyRootfs(containerConfiguration.getReadonlyRootfs()); } if (containerConfiguration.getLabels() != null) { createContainerCmd.withLabels(containerConfiguration.getLabels()); } if (containerConfiguration.getWorkingDir() != null) { createContainerCmd.withWorkingDir(containerConfiguration.getWorkingDir()); } if (containerConfiguration.getDisableNetwork() != null) { createContainerCmd.withNetworkDisabled(containerConfiguration.getDisableNetwork()); } if (containerConfiguration.getHostName() != null) { createContainerCmd.withHostName(containerConfiguration.getHostName()); } if (containerConfiguration.getPortSpecs() != null) { createContainerCmd.withPortSpecs(containerConfiguration.getPortSpecs().toArray(new String[0])); } if (containerConfiguration.getUser() != null) { createContainerCmd.withUser(containerConfiguration.getUser()); } if (containerConfiguration.getTty() != null) { createContainerCmd.withTty(containerConfiguration.getTty()); } if (containerConfiguration.getStdinOpen() != null) { createContainerCmd.withStdinOpen(containerConfiguration.getStdinOpen()); } if (containerConfiguration.getStdinOnce() != null) { createContainerCmd.withStdInOnce(containerConfiguration.getStdinOnce()); } if (containerConfiguration.getMemoryLimit() != null) { createContainerCmd.withMemory(containerConfiguration.getMemoryLimit()); } if (containerConfiguration.getMemorySwap() != null) { createContainerCmd.withMemorySwap(containerConfiguration.getMemorySwap()); } if (containerConfiguration.getShmSize() != null) { createContainerCmd.getHostConfig().withShmSize(containerConfiguration.getShmSize()); } if (containerConfiguration.getCpuShares() != null) { createContainerCmd.withCpuShares(containerConfiguration.getCpuShares()); } if (containerConfiguration.getCpuSet() != null) { createContainerCmd.withCpusetCpus(containerConfiguration.getCpuSet()); } if (containerConfiguration.getCpuQuota() != null) { createContainerCmd.getHostConfig().withCpuQuota(containerConfiguration.getCpuQuota()); } if (containerConfiguration.getAttachStdin() != null) { createContainerCmd.withAttachStdin(containerConfiguration.getAttachStdin()); } if (containerConfiguration.getAttachSterr() != null) { createContainerCmd.withAttachStderr(containerConfiguration.getAttachSterr()); } if (containerConfiguration.getEnv() != null) { createContainerCmd.withEnv( resolveDockerServerIpInList(containerConfiguration.getEnv()).toArray(new String[0])); } if (containerConfiguration.getCmd() != null) { createContainerCmd.withCmd(containerConfiguration.getCmd().toArray(new String[0])); } if (containerConfiguration.getDns() != null) { createContainerCmd.withDns(containerConfiguration.getDns().toArray(new String[0])); } if (containerConfiguration.getVolumes() != null) { createContainerCmd.withVolumes(toVolumes(containerConfiguration.getVolumes())); } if (containerConfiguration.getVolumesFrom() != null) { createContainerCmd.withVolumesFrom(toVolumesFrom(containerConfiguration.getVolumesFrom())); } if (containerConfiguration.getBinds() != null) { createContainerCmd.withBinds(toBinds(containerConfiguration.getBinds())); } // Dependencies is precedence over links if (containerConfiguration.getLinks() != null && containerConfiguration.getDependsOn() == null) { createContainerCmd.withLinks(toLinks(containerConfiguration.getLinks())); } if (containerConfiguration.getPortBindings() != null) { createContainerCmd.withPortBindings(toPortBindings(containerConfiguration.getPortBindings())); } if (containerConfiguration.getPrivileged() != null) { createContainerCmd.withPrivileged(containerConfiguration.getPrivileged()); } if (containerConfiguration.getPublishAllPorts() != null) { createContainerCmd.withPublishAllPorts(containerConfiguration.getPublishAllPorts()); } if (containerConfiguration.getNetworkMode() != null) { createContainerCmd.withNetworkMode(containerConfiguration.getNetworkMode()); } if (containerConfiguration.getDnsSearch() != null) { createContainerCmd.withDnsSearch(containerConfiguration.getDnsSearch().toArray(new String[0])); } if (containerConfiguration.getDevices() != null) { createContainerCmd.withDevices(toDevices(containerConfiguration.getDevices())); } if (containerConfiguration.getRestartPolicy() != null) { createContainerCmd.withRestartPolicy(toRestartPolicy(containerConfiguration.getRestartPolicy())); } if (containerConfiguration.getCapAdd() != null) { createContainerCmd.withCapAdd(toCapability(containerConfiguration.getCapAdd())); } if (containerConfiguration.getCapDrop() != null) { createContainerCmd.withCapDrop(toCapability(containerConfiguration.getCapDrop())); } if (containerConfiguration.getExtraHosts() != null) { createContainerCmd.withExtraHosts(containerConfiguration.getExtraHosts().toArray(new String[0])); } if (containerConfiguration.getEntryPoint() != null) { createContainerCmd.withEntrypoint(containerConfiguration.getEntryPoint().toArray(new String[0])); } if (containerConfiguration.getDomainName() != null) { createContainerCmd.withDomainName(containerConfiguration.getDomainName()); } if (containerConfiguration.getIpv4Address() != null) { createContainerCmd.withIpv4Address(containerConfiguration.getIpv4Address()); } if (containerConfiguration.getIpv6Address() != null) { createContainerCmd.withIpv6Address(containerConfiguration.getIpv6Address()); } boolean alwaysPull = false; if (containerConfiguration.getAlwaysPull() != null) { alwaysPull = containerConfiguration.getAlwaysPull(); } if (alwaysPull) { log.info(String.format( "Pulling latest Docker Image %s.", image)); this.pullImage(image); } try { return createContainerCmd.exec().getId(); } catch (NotFoundException e) { if (!alwaysPull) { log.warning(String.format( "Docker Image %s is not on DockerHost and it is going to be automatically pulled.", image)); this.pullImage(image); return createContainerCmd.exec().getId(); } else { throw e; } } catch (ConflictException e) { if (cubeConfiguration.isClean()) { log.warning(String.format("Container name %s is already use. Since clean mode is enabled, " + "container is going to be self removed.", name)); try { this.stopContainer(name); } catch (NotModifiedException e1) { // Container was already stopped } this.removeContainer(name, containerConfiguration.getRemoveVolumes()); return createContainerCmd.exec().getId(); } else { throw e; } } catch (ProcessingException e) { if (e.getCause() instanceof UnsupportedSchemeException) { if (e.getCause().getMessage().contains("https")) { throw new IllegalStateException("You have configured serverUri with https protocol but " + "certPath property is missing or points out to an invalid certificate to handle the SSL.", e.getCause()); } else { throw e; } } else { throw e; } } } finally { this.readWriteLock.readLock().unlock(); } } private List<String> resolveDockerServerIpInList(Collection<String> envs) { List<String> resolvedEnv = new ArrayList<String>(); for (String env : envs) { if (env.contains(CubeDockerConfiguration.DOCKER_SERVER_IP)) { resolvedEnv.add( env.replaceAll(CubeDockerConfiguration.DOCKER_SERVER_IP, cubeConfiguration.getDockerServerIp())); } else { resolvedEnv.add(env); } } return resolvedEnv; } private Set<ExposedPort> resolveExposedPorts(CubeContainer containerConfiguration, CreateContainerCmd createContainerCmd) { Set<ExposedPort> allExposedPorts = new HashSet<>(); if (containerConfiguration.getPortBindings() != null) { for (PortBinding binding : containerConfiguration.getPortBindings()) { allExposedPorts.add(new ExposedPort(binding.getExposedPort().getExposed(), InternetProtocol.parse(binding.getExposedPort().getType()))); } } if (containerConfiguration.getExposedPorts() != null) { for (org.arquillian.cube.docker.impl.client.config.ExposedPort port : containerConfiguration.getExposedPorts()) { allExposedPorts.add(new ExposedPort(port.getExposed(), InternetProtocol.parse(port.getType()))); } } return allExposedPorts; } private String getImageName(CubeContainer containerConfiguration, String name) { String image; if (containerConfiguration.getImage() != null) { image = containerConfiguration.getImage().toImageRef(); } else { if (containerConfiguration.getBuildImage() != null) { BuildImage buildImage = containerConfiguration.getBuildImage(); if (buildImage.getDockerfileLocation() != null) { Map<String, Object> params = new HashMap<String, Object>(); //(containerConfiguration, BUILD_IMAGE); params.put("noCache", buildImage.isNoCache()); params.put("remove", buildImage.isRemove()); params.put("dockerFileLocation", buildImage.getDockerfileLocation()); params.put("dockerFileName", buildImage.getDockerfileName()); image = this.buildImage(buildImage.getDockerfileLocation(), name, params); } else { throw new IllegalArgumentException( "A tar file with Dockerfile on root or a directory with a Dockerfile should be provided."); } } else { throw new IllegalArgumentException( String.format( "Current configuration file does not contain %s nor %s parameter and one of both should be provided.", IMAGE, BUILD_IMAGE)); } } return image; } public void startContainer(String id, CubeContainer containerConfiguration) { this.readWriteLock.readLock().lock(); try { StartContainerCmd startContainerCmd = this.dockerClient.startContainerCmd(id); startContainerCmd.exec(); } finally { this.readWriteLock.readLock().unlock(); } } public Statistics statsContainer(String id) throws IOException { this.readWriteLock.readLock().lock(); try { StatsCmd statsCmd = this.dockerClient.statsCmd(id); CountDownLatch countDownLatch = new CountDownLatch(1); StatsLogsResultCallback statslogs = new StatsLogsResultCallback(countDownLatch); try { StatsLogsResultCallback statscallback = statsCmd.exec(statslogs); countDownLatch.await(5, TimeUnit.SECONDS); statscallback.close(); } catch (InterruptedException e) { throw new IOException(e); } return statslogs.getStatistics(); } finally { this.readWriteLock.readLock().unlock(); } } private Ports toPortBindings(Collection<PortBinding> portBindings) { Ports ports = new Ports(); for (PortBinding portBinding : portBindings) { ports.bind( new ExposedPort( portBinding.getExposedPort().getExposed(), InternetProtocol.parse(portBinding.getExposedPort().getType())), new Binding(portBinding.getHost(), Integer.toString(portBinding.getBound()))); } return ports; } public void killContainer(String containerId) { this.readWriteLock.readLock().lock(); try { this.dockerClient.killContainerCmd(containerId).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void stopContainer(String containerId) { this.readWriteLock.readLock().lock(); try { this.dockerClient.stopContainerCmd(containerId).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void removeContainer(String containerId, boolean removeVolumes) { this.readWriteLock.readLock().lock(); try { this.dockerClient.removeContainerCmd(containerId).withRemoveVolumes(removeVolumes).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public InspectContainerResponse inspectContainer(String containerId) { this.readWriteLock.readLock().lock(); try { return this.dockerClient.inspectContainerCmd(containerId).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public int waitContainer(String containerId) { this.readWriteLock.readLock().lock(); try { return this.dockerClient.waitContainerCmd(containerId) .exec(new WaitContainerResultCallback()) .awaitStatusCode(); } finally { this.readWriteLock.readLock().unlock(); } } public Version dockerHostVersion() { this.readWriteLock.readLock().lock(); try { return this.dockerClient.versionCmd().exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void pingDockerServer() { this.readWriteLock.readLock().lock(); try { try { PingCmd pingCmd = this.dockerClient.pingCmd(); pingCmd.exec(); } catch (ProcessingException e) { if (e.getCause() instanceof ConnectException) { throw new IllegalStateException( String.format( "Docker server is not running in %s host or it does not accept connections in tcp protocol, read https://github.com/arquillian/arquillian-cube#preliminaries to learn how to enable it.", this.cubeConfiguration.getDockerServerUri()), e); } } } finally { this.readWriteLock.readLock().unlock(); } } private String buildImage(String location, String name, Map<String, Object> params) { this.readWriteLock.writeLock().lock(); try { BuildImageCmd buildImageCmd = createBuildCommand(location); configureBuildCommand(params, buildImageCmd); if (name != null) { buildImageCmd.withTag(name); } String imageId = buildImageCmd.exec(new BuildImageResultCallback()).awaitImageId(); if (imageId == null) { throw new IllegalStateException( String.format( "Docker server has not provided an imageId for image build from %s.", location)); } // TODO this should be removed in the future it is only a hack to avoid some errors with Hijack is incompatible with use of CloseNotifier. // It seems to be a problem with go and should be fixed in go 1.6 (and maybe in Docker 1.11.0). // To test in future versions we only need to comment the close + recreation. // following lines fixes #310 by closing and rebuilding dockerClient // https://github.com/arquillian/arquillian-cube/issues/322 try { this.dockerClient.close(); } catch (IOException ioe) { throw new RuntimeException(ioe); } this.dockerClient = buildDockerClient(); return imageId.trim(); } finally { this.readWriteLock.writeLock().unlock(); } } public void removeImage(String contaierID, Boolean force) { this.readWriteLock.readLock().lock(); try { this.dockerClient.removeImageCmd(contaierID).withForce(force).exec(); } finally { this.readWriteLock.readLock().unlock(); } } private void configureBuildCommand(Map<String, Object> params, BuildImageCmd buildImageCmd) { if (params.containsKey(NO_CACHE)) { buildImageCmd.withNoCache((boolean) params.get(NO_CACHE)); } if (params.containsKey(REMOVE)) { buildImageCmd.withRemove((boolean) params.get(REMOVE)); } if (params.containsKey(DOCKERFILE_NAME)) { buildImageCmd.withDockerfile(new File((String) params.get(DOCKERFILE_NAME))); } } private BuildImageCmd createBuildCommand(String location) { BuildImageCmd buildImageCmd = null; try { URL url = new URL(location); buildImageCmd = this.dockerClient.buildImageCmd(url.openStream()); } catch (MalformedURLException e) { // Means that it is not a URL so it can be a File or Directory File file = new File(location); if (file.exists()) { if (file.isDirectory()) { buildImageCmd = this.dockerClient.buildImageCmd(file); } else { try { buildImageCmd = this.dockerClient.buildImageCmd(new FileInputStream(file)); } catch (FileNotFoundException notFoundFile) { throw new IllegalArgumentException(notFoundFile); } } } } catch (IOException e) { throw new IllegalArgumentException(e); } return buildImageCmd; } public String containerLog(String containerId) { this.readWriteLock.readLock().lock(); try { return dockerClient.logContainerCmd(containerId) .withStdErr(true) .withStdOut(true) .exec(new LogContainerTestCallback()).awaitCompletion().toString(); } catch (InterruptedException e) { log.log(Level.SEVERE, e.getMessage()); return ""; } finally { this.readWriteLock.readLock().unlock(); } } public static class LogContainerTestCallback extends LogContainerResultCallback { protected final StringBuilder log = new StringBuilder(); List<Frame> collectedFrames = new ArrayList<>(); boolean collectFrames = false; public LogContainerTestCallback() { this(false); } public LogContainerTestCallback(boolean collectFrames) { this.collectFrames = collectFrames; } @Override public void onNext(Frame frame) { if(collectFrames) collectedFrames.add(frame); log.append(new String(frame.getPayload())); } @Override public String toString() { return log.toString(); } public List<Frame> getCollectedFrames() { return collectedFrames; } } public void pullImage(String imageName) { this.readWriteLock.readLock().lock(); try { final Image image = Image.valueOf(imageName); PullImageCmd pullImageCmd = this.dockerClient.pullImageCmd(image.getName()); String tag = image.getTag(); if (tag != null && !"".equals(tag)) { pullImageCmd.withTag(tag); } else { pullImageCmd.withTag("latest"); } pullImageCmd.exec(new PullImageResultCallback()).awaitSuccess(); } finally { this.readWriteLock.readLock().unlock(); } } public CubeOutput execStart(String containerId, String... commands) { this.readWriteLock.readLock().lock(); try { String id = execCreate(containerId, commands); return execStartOutput(id); } finally { this.readWriteLock.readLock().unlock(); } } public void execStartDetached(String containerId, String... commands) { this.readWriteLock.readLock().lock(); try { String id = execCreate(containerId, commands); this.dockerClient.execStartCmd(id).withDetach(true).exec(new ExecStartResultCallback()); } finally { this.readWriteLock.readLock().unlock(); } } /** * EXecutes command to given container returning the inspection object as well. This method does 3 calls to * dockerhost. Create, Start and Inspect. * * @param containerId * to execute command. */ public ExecInspection execStartVerbose(String containerId, String... commands) { this.readWriteLock.readLock().lock(); try { String id = execCreate(containerId, commands); CubeOutput output = execStartOutput(id); return new ExecInspection(output, inspectExec(id)); } finally { this.readWriteLock.readLock().unlock(); } } private InspectExecResponse inspectExec(String id) { final InspectExecResponse exec = this.dockerClient.inspectExecCmd(id).exec(); return exec; } private String execCreate(String containerId, String... commands) { ExecCreateCmdResponse execCreateCmdResponse = this.dockerClient.execCreateCmd(containerId) .withAttachStdout(true).withAttachStdin(true).withAttachStderr(true).withTty(false).withCmd(commands) .exec(); return execCreateCmdResponse.getId(); } private CubeOutput execStartOutput(String id) { OutputStream outputStream = new ByteArrayOutputStream(); OutputStream errorStream = new ByteArrayOutputStream(); try { dockerClient.execStartCmd(id).withDetach(false) .exec(new ExecStartResultCallback(outputStream, errorStream)).awaitCompletion(); } catch (InterruptedException e) { return new CubeOutput("", ""); } return new CubeOutput(outputStream.toString(), errorStream.toString()); } public List<org.arquillian.cube.ChangeLog> inspectChangesOnContainerFilesystem(String containerId) { this.readWriteLock.readLock().lock(); try { List<ChangeLog> changeLogs = dockerClient.containerDiffCmd(containerId).exec(); List<org.arquillian.cube.ChangeLog> changes = new ArrayList<>(); for (ChangeLog changeLog : changeLogs) { changes.add(new org.arquillian.cube.ChangeLog(changeLog.getPath(), changeLog.getKind())); } return changes; } finally { this.readWriteLock.readLock().unlock(); } } public TopContainer top(String containerId) { this.readWriteLock.readLock().lock(); try { TopContainerResponse topContainer = dockerClient.topContainerCmd(containerId).exec(); return new TopContainer(topContainer.getTitles(), topContainer.getProcesses()); } finally { this.readWriteLock.readLock().unlock(); } } public InputStream getFileOrDirectoryFromContainerAsTar(String containerId, String from) { this.readWriteLock.readLock().lock(); try { InputStream response = dockerClient.copyFileFromContainerCmd(containerId, from).exec(); return response; } finally { this.readWriteLock.readLock().unlock(); } } public void copyStreamToContainer(String containerId, File from) { this.readWriteLock.readLock().lock(); try { dockerClient.copyArchiveToContainerCmd(containerId).withHostResource(from.getAbsolutePath()).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void copyStreamToContainer(String containerId, File from, File to) { this.readWriteLock.readLock().lock(); try { dockerClient.copyArchiveToContainerCmd(containerId) .withRemotePath(to.getAbsolutePath()) .withHostResource(from.getAbsolutePath()).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void connectToNetwork(String networkId, String containerID) { this.readWriteLock.readLock().lock(); try { this.dockerClient.connectToNetworkCmd().withNetworkId(networkId).withContainerId(containerID).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public void copyLog(String containerId, boolean follow, boolean stdout, boolean stderr, boolean timestamps, int tail, OutputStream outputStream) throws IOException { this.readWriteLock.readLock().lock(); try { LogContainerCmd logContainerCmd = dockerClient.logContainerCmd(containerId).withStdErr(false).withStdOut(false); logContainerCmd.withFollowStream(follow); logContainerCmd.withStdOut(stdout); logContainerCmd.withStdErr(stderr); logContainerCmd.withTimestamps(timestamps); if (tail < 0) { logContainerCmd.withTailAll(); } else { logContainerCmd.withTail(tail); } OutputStreamLogsResultCallback outputStreamLogsResultCallback = new OutputStreamLogsResultCallback(outputStream); logContainerCmd.exec(outputStreamLogsResultCallback); try { outputStreamLogsResultCallback.awaitCompletion(); } catch (InterruptedException e) { throw new IOException(e); } } finally { this.readWriteLock.readLock().unlock(); } } private void readDockerRawStream(InputStream rawSteram, OutputStream outputStream) throws IOException { byte[] header = new byte[8]; while (rawSteram.read(header) > 0) { ByteBuffer headerBuffer = ByteBuffer.wrap(header); // Stream type byte type = headerBuffer.get(); // SKip 3 bytes headerBuffer.get(); headerBuffer.get(); headerBuffer.get(); // Payload frame size int size = headerBuffer.getInt(); byte[] streamOutputBuffer = new byte[size]; rawSteram.read(streamOutputBuffer); outputStream.write(streamOutputBuffer); } } private String readDockerRawStreamToString(InputStream rawStream) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); readDockerRawStream(rawStream, output); return new String(output.toByteArray()); } public String createNetwork(String id, Network network) { this.readWriteLock.readLock().lock(); try { final CreateNetworkCmd createNetworkCmd = this.dockerClient.createNetworkCmd().withName(id); if (network.getDriver() != null) { createNetworkCmd.withDriver(network.getDriver()); } if (network.getIpam() != null) { createNetworkCmd.withIpam(new com.github.dockerjava.api.model.Network.Ipam().withConfig( createIpamConfig(network))); } if (network.getOptions() != null && !network.getOptions().isEmpty()) { createNetworkCmd.withOptions(network.getOptions()); } final CreateNetworkResponse exec = createNetworkCmd.exec(); return exec.getId(); } finally { this.readWriteLock.readLock().unlock(); } } public void removeNetwork(String id) { this.readWriteLock.readLock().lock(); try { this.dockerClient.removeNetworkCmd(id).exec(); } finally { this.readWriteLock.readLock().unlock(); } } public List<com.github.dockerjava.api.model.Network> getNetworks() { this.readWriteLock.readLock().lock(); try { return this.dockerClient.listNetworksCmd().exec(); } finally { this.readWriteLock.readLock().unlock(); } } private List<com.github.dockerjava.api.model.Network.Ipam.Config> createIpamConfig(Network network) { List<com.github.dockerjava.api.model.Network.Ipam.Config> ipamConfigs = new ArrayList<>(); List<IPAMConfig> IPAMConfigs = network.getIpam().getIpamConfigs(); if (IPAMConfigs != null) { for (IPAMConfig IpamConfig : IPAMConfigs) { com.github.dockerjava.api.model.Network.Ipam.Config config = new com.github.dockerjava.api.model.Network.Ipam.Config(); if (IpamConfig.getGateway() != null) { config.withGateway(IpamConfig.getGateway()); } if (IpamConfig.getIpRange() != null) { config.withIpRange(IpamConfig.getIpRange()); } if (IpamConfig.getSubnet() != null) { config.withSubnet(IpamConfig.getSubnet()); } ipamConfigs.add(config); } } return ipamConfigs; } /** * Get the URI of the docker host */ public URI getDockerUri() { return dockerUri; } public DockerClient getDockerClient() { return this.dockerClient; } public String getDockerServerIp() { return dockerServerIp; } public static class ExecInspection { private CubeOutput output; private InspectExecResponse inspectExecResponse; public ExecInspection(CubeOutput output, InspectExecResponse inspectExecResponse) { this.output = output; this.inspectExecResponse = inspectExecResponse; } public CubeOutput getOutput() { return output; } public InspectExecResponse getInspectExecResponse() { return inspectExecResponse; } } private static class OutputStreamLogsResultCallback extends ResultCallbackTemplate<LogContainerResultCallback, Frame> { private OutputStream outputStream; public OutputStreamLogsResultCallback(OutputStream outputStream) { this.outputStream = outputStream; } @Override public void onNext(Frame object) { try { this.outputStream.write(object.getPayload()); this.outputStream.flush(); } catch (IOException e) { throw new RuntimeException(e); } } } }