diff --git a/java/src/org/openqa/selenium/docker/ContainerConfig.java b/java/src/org/openqa/selenium/docker/ContainerConfig.java index 27cc002ebe85b..519829762f6d9 100644 --- a/java/src/org/openqa/selenium/docker/ContainerConfig.java +++ b/java/src/org/openqa/selenium/docker/ContainerConfig.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -42,6 +43,7 @@ public class ContainerConfig { private final String networkName; private final boolean autoRemove; private final long shmSize; + private final Map hostConfig; public ContainerConfig( Image image, @@ -51,6 +53,26 @@ public ContainerConfig( List devices, String networkName, long shmSize) { + this( + image, + portBindings, + envVars, + volumeBinds, + devices, + networkName, + shmSize, + ImmutableMap.of()); + } + + public ContainerConfig( + Image image, + Multimap> portBindings, + Map envVars, + Map volumeBinds, + List devices, + String networkName, + long shmSize, + Map hostConfig) { this.image = image; this.portBindings = portBindings; this.envVars = envVars; @@ -59,6 +81,7 @@ public ContainerConfig( this.networkName = networkName; this.autoRemove = true; this.shmSize = shmSize; + this.hostConfig = hostConfig; } public static ContainerConfig image(Image image) { @@ -123,6 +146,17 @@ public ContainerConfig devices(List devices) { image, portBindings, envVars, volumeBinds, devices, networkName, shmSize); } + public ContainerConfig applyHostConfig(Map hostConfig, List configKeys) { + Map setHostConfig = + configKeys.stream() + .filter(hostConfig::containsKey) + .filter(key -> hostConfig.get(key) != null) + .collect(Collectors.toMap(key -> key, hostConfig::get)); + + return new ContainerConfig( + image, portBindings, envVars, volumeBinds, devices, networkName, shmSize, setHostConfig); + } + @Override public String toString() { return "ContainerConfig{" @@ -142,6 +176,8 @@ public String toString() { + autoRemove + ", shmSize=" + shmSize + + ", hostConfig=" + + hostConfig + '}'; } @@ -175,6 +211,12 @@ private Map toJson() { "Binds", volumeBinds, "Devices", devicesMapping); + if (!this.hostConfig.isEmpty()) { + Map copyMap = new HashMap<>(hostConfig); + copyMap.putAll(this.hostConfig); + hostConfig = ImmutableMap.copyOf(copyMap); + } + return ImmutableMap.of( "Image", image.getId(), "Env", envVars, diff --git a/java/src/org/openqa/selenium/docker/ContainerInfo.java b/java/src/org/openqa/selenium/docker/ContainerInfo.java index fb1d49dc3c700..50d9c8a8d887b 100644 --- a/java/src/org/openqa/selenium/docker/ContainerInfo.java +++ b/java/src/org/openqa/selenium/docker/ContainerInfo.java @@ -29,13 +29,19 @@ public class ContainerInfo { private final ContainerId id; private final List> mountedVolumes; private final String networkName; + private Map hostConfig; public ContainerInfo( - ContainerId id, String ip, List> mountedVolumes, String networkName) { + ContainerId id, + String ip, + List> mountedVolumes, + String networkName, + Map hostConfig) { this.ip = Require.nonNull("Container ip address", ip); this.id = Require.nonNull("Container id", id); this.mountedVolumes = Require.nonNull("Mounted volumes", mountedVolumes); this.networkName = Require.nonNull("Network name", networkName); + this.hostConfig = Require.nonNull("Host config", hostConfig); } public String getIp() { @@ -54,6 +60,10 @@ public String getNetworkName() { return networkName; } + public Map getHostConfig() { + return this.hostConfig; + } + @Override public String toString() { return "ContainerInfo{" diff --git a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java index dc88538755c3a..744b6196a10b9 100644 --- a/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java +++ b/java/src/org/openqa/selenium/docker/v1_41/InspectContainer.java @@ -23,6 +23,7 @@ import static org.openqa.selenium.remote.http.HttpMethod.GET; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -67,7 +68,9 @@ public ContainerInfo apply(ContainerId id) { ArrayList mounts = (ArrayList) rawInspectInfo.get("Mounts"); List> mountedVolumes = mounts.stream().map(mount -> (Map) mount).collect(Collectors.toList()); + Map hostConfig = + (Map) rawInspectInfo.getOrDefault("HostConfig", Collections.emptyMap()); - return new ContainerInfo(id, ip, mountedVolumes, networkName); + return new ContainerInfo(id, ip, mountedVolumes, networkName, hostConfig); } } diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java index 20e0995373389..02af7f7c2dd41 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerFlags.java @@ -69,6 +69,18 @@ public class DockerFlags implements HasRoles { "[\"selenium/standalone-firefox:latest\", \"{\\\"browserName\\\": \\\"firefox\\\"}\"]") private List images2Capabilities; + @Parameter( + names = {"--docker-host-config-keys"}, + description = + "Specify which docker host configuration keys should be passed to browser containers." + + " Keys name can be found in the Docker API documentation, or by running `docker" + + " inspect` the node-docker container.") + @ConfigValue( + section = DockerOptions.DOCKER_SECTION, + name = "host-config-keys", + example = "[\"Dns\", \"DnsOptions\", \"DnsSearch\", \"ExtraHosts\", \"Binds\"]") + private List hostConfigKeys; + @Parameter( names = {"--docker-devices"}, description = diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java index 68491ed2bd8c0..d5bff8e52725b 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerOptions.java @@ -129,6 +129,9 @@ public Map> getDockerSessionFactories( .getAll(DOCKER_SECTION, "configs") .orElseThrow(() -> new DockerException("Unable to find docker configs")); + List hostConfigKeys = + config.getAll(DOCKER_SECTION, "host-config-keys").orElseGet(Collections::emptyList); + Multimap kinds = HashMultimap.create(); for (int i = 0; i < allConfigs.size(); i++) { String imageName = allConfigs.get(i); @@ -152,6 +155,7 @@ public Map> getDockerSessionFactories( DockerAssetsPath assetsPath = getAssetsPath(info); String networkName = getDockerNetworkName(info); + Map hostConfig = getDockerHostConfig(info); loadImages(docker, kinds.keySet().toArray(new String[0])); Image videoImage = getVideoImage(docker); @@ -182,7 +186,9 @@ public Map> getDockerSessionFactories( assetsPath, networkName, info.isPresent(), - capabilities -> options.getSlotMatcher().matches(caps, capabilities))); + capabilities -> options.getSlotMatcher().matches(caps, capabilities), + hostConfig, + hostConfigKeys)); } LOG.info( String.format( @@ -229,6 +235,11 @@ private String getDockerNetworkName(Optional info) { return DEFAULT_DOCKER_NETWORK; } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private Map getDockerHostConfig(Optional info) { + return info.map(ContainerInfo::getHostConfig).orElse(Collections.emptyMap()); + } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") private DockerAssetsPath getAssetsPath(Optional info) { if (info.isPresent()) { diff --git a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java index ff1e3250b21c8..dd0ed03d6fde0 100644 --- a/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java +++ b/java/src/org/openqa/selenium/grid/node/docker/DockerSessionFactory.java @@ -101,6 +101,8 @@ public class DockerSessionFactory implements SessionFactory { private final String networkName; private final boolean runningInDocker; private final Predicate predicate; + private final Map hostConfig; + private final List hostConfigKeys; public DockerSessionFactory( Tracer tracer, @@ -115,7 +117,9 @@ public DockerSessionFactory( DockerAssetsPath assetsPath, String networkName, boolean runningInDocker, - Predicate predicate) { + Predicate predicate, + Map hostConfig, + List hostConfigKeys) { this.tracer = Require.nonNull("Tracer", tracer); this.clientFactory = Require.nonNull("HTTP client", clientFactory); this.sessionTimeout = Require.nonNull("Session timeout", sessionTimeout); @@ -129,6 +133,8 @@ public DockerSessionFactory( this.assetsPath = assetsPath; this.runningInDocker = runningInDocker; this.predicate = Require.nonNull("Accepted capabilities predicate", predicate); + this.hostConfig = Require.nonNull("Container host config", hostConfig); + this.hostConfigKeys = Require.nonNull("Browser container host config keys", hostConfigKeys); } @Override @@ -285,10 +291,12 @@ private Container createBrowserContainer(int port, Capabilities sessionCapabilit .env(browserContainerEnvVars) .shmMemorySize(browserContainerShmMemorySize) .network(networkName) - .devices(devices); + .devices(devices) + .applyHostConfig(hostConfig, hostConfigKeys); if (!runningInDocker) { containerConfig = containerConfig.map(Port.tcp(4444), Port.tcp(port)); } + LOG.fine("Container config: " + containerConfig); return docker.create(containerConfig); }