diff --git a/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java b/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java index 4088a547c9..4239661e8d 100644 --- a/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java +++ b/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java @@ -22,8 +22,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.List; import java.util.Map; -import org.jclouds.compute.ComputeService; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.docker.domain.Config; import org.jclouds.docker.internal.NullSafeCopies; import org.jclouds.domain.LoginCredentials; import org.jclouds.javax.annotation.Nullable; @@ -34,23 +34,55 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; /** - * Contains options supported by the {@link ComputeService#createNodesInGroup(String, int, TemplateOptions) createNodes} - * operation on the docker provider. - * + * Contains options supported by the + * {@link org.jclouds.compute.ComputeService#createNodesInGroup(String, int, TemplateOptions) + * createNodes} operation on the docker provider. + * *

Usage

* - * The recommended way to instantiate a - * DockerTemplateOptions object is to statically import {@code DockerTemplateOptions.Builder.*} - * and invoke one of the static creation methods, followed by an instance mutator if needed. + * The recommended way to instantiate a DockerTemplateOptions object is to + * statically import {@code DockerTemplateOptions.Builder.*} and invoke one of + * the static creation methods, followed by an instance mutator if needed. * - *
{@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*;
+ * 
+ * {@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*;
  *
  * ComputeService api = // get connection
  * templateBuilder.options(inboundPorts(22, 80, 8080, 443));
- * Set set = api.createNodesInGroup(tag, 2, templateBuilder.build());}
+ * Set set = api.createNodesInGroup(tag, 2, templateBuilder.build());} + *
+ * + *

Advanced Usage

+ *

+ * In addition to basic configuration through its methods, this class also + * provides possibility to work directly with Docker API configuration object ( + * {@link Config.Builder}). When the + * {@link #configBuilder(org.jclouds.docker.domain.Config.Builder)} is used to + * configure not-null configBuilder, then this configuration object + * takes precedence over the other configuration in this class (i.e. the other + * config entries are not used) + *

+ *

+ * Note: The {@code image} property in the provided {@link Config.Builder} is rewritten by a placeholder value. + * The real value is configured by ComputeServiceAdapter. + *

+ * + *
+ * {@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*;
+ *
+ * ComputeService api = // get connection
+ * DockerTemplateOptions options = DockerTemplateOptions.Builder
+ *       .configBuilder(
+ *                Config.builder().env(ImmutableList. of("SSH_PORT=8822"))
+ *                      .hostConfig(HostConfig.builder().networkMode("host").build()));
+ * templateBuilder.options(options);
+ * Set set = api.createNodesInGroup("sample-group", 1, templateBuilder.build());}
+ * 
*/ public class DockerTemplateOptions extends TemplateOptions implements Cloneable { + private static final String NO_IMAGE = "jclouds-placeholder-for-image"; + protected List dns = ImmutableList.of(); protected String hostname; protected Integer memory; @@ -63,6 +95,8 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable protected String networkMode; protected Map extraHosts = ImmutableMap.of(); + protected Config.Builder configBuilder; + @Override public DockerTemplateOptions clone() { DockerTemplateOptions options = new DockerTemplateOptions(); @@ -94,6 +128,7 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable if (!extraHosts.isEmpty()) { eTo.extraHosts(extraHosts); } + eTo.configBuilder(configBuilder); } } @@ -113,12 +148,22 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable equal(this.cpuShares, that.cpuShares) && equal(this.env, that.env) && equal(this.portBindings, that.portBindings) && - equal(this.extraHosts, that.extraHosts); + equal(this.extraHosts, that.extraHosts) && + buildersEqual(this.configBuilder, that.configBuilder); } + + /** + * Compares two Config.Builder instances. + */ + private boolean buildersEqual(Config.Builder b1, Config.Builder b2) { + return b1 == b2 || (b1 != null && b2 != null && b1.build().equals(b2.build())); + } + @Override public int hashCode() { - return Objects.hashCode(super.hashCode(), volumes, hostname, dns, memory, entrypoint, commands, cpuShares, env, portBindings, extraHosts); + return Objects.hashCode(super.hashCode(), volumes, hostname, dns, memory, entrypoint, commands, cpuShares, env, + portBindings, extraHosts, configBuilder); } @Override @@ -134,6 +179,7 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable .add("env", env) .add("portBindings", portBindings) .add("extraHosts", extraHosts) + .add("configBuilder", configBuilder) .toString(); } @@ -235,6 +281,23 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable return this; } + /** + * This method sets Config.Builder configuration object, which can be used as + * a replacement for all the other settings from this class. Some values in + * the provided Config.Builder instance (the image name for instance) can be + * ignored or their value can be changed. + * + * @param configBuilder + * Config.Builder instance. This instance can be changed in this + * method! + */ + public DockerTemplateOptions configBuilder(Config.Builder configBuilder) { + this.configBuilder = configBuilder != null + ? Config.builder().fromConfig(configBuilder.image(NO_IMAGE).build()) + : null; + return this; + } + public Map getVolumes() { return volumes; } public List getDns() { return dns; } @@ -257,6 +320,8 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable public Map getExtraHosts() { return extraHosts; } + public Config.Builder getConfigBuilder() { return configBuilder; } + public static class Builder { /** @@ -379,6 +444,11 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable return options.extraHosts(extraHosts); } + public static DockerTemplateOptions configBuilder(Config.Builder configBuilder) { + DockerTemplateOptions options = new DockerTemplateOptions(); + return options.configBuilder(configBuilder); + } + /** * @see TemplateOptions#inboundPorts(int...) */ diff --git a/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java b/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java index d800b7e24c..64e9e679ea 100644 --- a/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java +++ b/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java @@ -32,6 +32,7 @@ import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.HardwareBuilder; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.Template; +import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.docker.DockerApi; import org.jclouds.docker.compute.options.DockerTemplateOptions; @@ -74,83 +75,97 @@ public class DockerComputeServiceAdapter implements this.api = checkNotNull(api, "api"); } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public NodeAndInitialCredentials createNodeWithGroupEncodedIntoName(String group, String name, Template template) { checkNotNull(template, "template was null"); - checkNotNull(template.getOptions(), "template options was null"); + TemplateOptions options = template.getOptions(); + checkNotNull(options, "template options was null"); String imageId = checkNotNull(template.getImage().getId(), "template image id must not be null"); String loginUser = template.getImage().getDefaultCredentials().getUser(); String loginUserPassword = template.getImage().getDefaultCredentials().getOptionalPassword().or("password"); - DockerTemplateOptions templateOptions = DockerTemplateOptions.class.cast(template.getOptions()); - int[] inboundPorts = templateOptions.getInboundPorts(); + DockerTemplateOptions templateOptions = DockerTemplateOptions.class.cast(options); + Config.Builder containerConfigBuilder = templateOptions.getConfigBuilder(); + if (containerConfigBuilder == null) { + containerConfigBuilder = Config.builder(); - Map exposedPorts = Maps.newHashMap(); - for (int inboundPort : inboundPorts) { - exposedPorts.put(inboundPort + "/tcp", Maps.newHashMap()); - } + containerConfigBuilder.entrypoint(templateOptions.getEntrypoint()); + containerConfigBuilder.cmd(templateOptions.getCommands()); + containerConfigBuilder.memory(templateOptions.getMemory()); + containerConfigBuilder.hostname(templateOptions.getHostname()); + containerConfigBuilder.cpuShares(templateOptions.getCpuShares()); + containerConfigBuilder.env(templateOptions.getEnv()); - Config.Builder containerConfigBuilder = Config.builder() - .image(imageId) - .exposedPorts(exposedPorts); - - containerConfigBuilder.entrypoint(templateOptions.getEntrypoint()); - containerConfigBuilder.cmd(templateOptions.getCommands()); - containerConfigBuilder.memory(templateOptions.getMemory()); - containerConfigBuilder.hostname(templateOptions.getHostname()); - containerConfigBuilder.cpuShares(templateOptions.getCpuShares()); - containerConfigBuilder.env(templateOptions.getEnv()); - - if (!templateOptions.getVolumes().isEmpty()) { - Map volumes = Maps.newLinkedHashMap(); - for (String containerDir : templateOptions.getVolumes().values()) { - volumes.put(containerDir, Maps.newHashMap()); + if (!templateOptions.getVolumes().isEmpty()) { + Map volumes = Maps.newLinkedHashMap(); + for (String containerDir : templateOptions.getVolumes().values()) { + volumes.put(containerDir, Maps.newHashMap()); + } + containerConfigBuilder.volumes(volumes); } - containerConfigBuilder.volumes(volumes); + + HostConfig.Builder hostConfigBuilder = HostConfig.builder() + .publishAllPorts(true) + .privileged(true); + + if (!templateOptions.getPortBindings().isEmpty()) { + Map>> portBindings = Maps.newHashMap(); + for (Map.Entry entry : templateOptions.getPortBindings().entrySet()) { + portBindings.put(entry.getValue() + "/tcp", + Lists.>newArrayList(ImmutableMap.of("HostPort", Integer.toString(entry.getKey())))); + } + hostConfigBuilder.portBindings(portBindings); + } + + if (!templateOptions.getDns().isEmpty()) { + hostConfigBuilder.dns(templateOptions.getDns()); + } + + if (!templateOptions.getExtraHosts().isEmpty()) { + List extraHosts = Lists.newArrayList(); + for (Map.Entry entry : templateOptions.getExtraHosts().entrySet()) { + extraHosts.add(entry.getKey() + ":" + entry.getValue()); + } + hostConfigBuilder.extraHosts(extraHosts); + } + + if (!templateOptions.getVolumes().isEmpty()) { + for (Map.Entry entry : templateOptions.getVolumes().entrySet()) { + hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue())); + } + } + + hostConfigBuilder.networkMode(templateOptions.getNetworkMode()); + containerConfigBuilder.hostConfig(hostConfigBuilder.build()); } - Config containerConfig = containerConfigBuilder.build(); + containerConfigBuilder.image(imageId); + // add the inbound ports into exposed ports map + Config containerConfig = containerConfigBuilder.build(); + Map exposedPorts = Maps.newHashMap(); + if (containerConfig.exposedPorts() == null) { + exposedPorts.putAll(containerConfig.exposedPorts()); + } + for (int inboundPort : templateOptions.getInboundPorts()) { + String portKey = inboundPort + "/tcp"; + if (!exposedPorts.containsKey(portKey)) { + exposedPorts.put(portKey, Maps.newHashMap()); + } + } + containerConfigBuilder.exposedPorts(exposedPorts); + + // build once more after setting inboundPorts + containerConfig = containerConfigBuilder.build(); + logger.debug(">> creating new container with containerConfig(%s)", containerConfig); Container container = api.getContainerApi().createContainer(name, containerConfig); logger.trace("<< container(%s)", container.id()); - HostConfig.Builder hostConfigBuilder = HostConfig.builder() - .publishAllPorts(true) - .privileged(true); - - if (!templateOptions.getPortBindings().isEmpty()) { - Map>> portBindings = Maps.newHashMap(); - for (Map.Entry entry : templateOptions.getPortBindings().entrySet()) { - portBindings.put(entry.getValue() + "/tcp", - Lists.>newArrayList(ImmutableMap.of("HostPort", Integer.toString(entry.getKey())))); - } - hostConfigBuilder.portBindings(portBindings); - } - - if (!templateOptions.getDns().isEmpty()) { - hostConfigBuilder.dns(templateOptions.getDns()); - } - - if (!templateOptions.getExtraHosts().isEmpty()) { - List extraHosts = Lists.newArrayList(); - for (Map.Entry entry : templateOptions.getExtraHosts().entrySet()) { - extraHosts.add(entry.getKey() + ":" + entry.getValue()); - } - hostConfigBuilder.extraHosts(extraHosts); - } - - if (!templateOptions.getVolumes().isEmpty()) { - for (Map.Entry entry : templateOptions.getVolumes().entrySet()) { - hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue())); - } - } - - hostConfigBuilder.networkMode(templateOptions.getNetworkMode()); - - HostConfig hostConfig = hostConfigBuilder.build(); + HostConfig hostConfig = containerConfig.hostConfig(); api.getContainerApi().startContainer(container.id(), hostConfig); container = api.getContainerApi().inspectContainer(container.id()); diff --git a/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java b/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java index cb35173c87..ba11feb089 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java @@ -35,7 +35,9 @@ import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest; import org.jclouds.docker.DockerApi; import org.jclouds.docker.compute.functions.LoginPortForContainer; import org.jclouds.docker.compute.options.DockerTemplateOptions; +import org.jclouds.docker.domain.Config; import org.jclouds.docker.domain.Container; +import org.jclouds.docker.domain.HostConfig; import org.jclouds.docker.domain.Image; import org.jclouds.docker.domain.ImageSummary; import org.jclouds.docker.options.BuildOptions; @@ -46,6 +48,7 @@ import org.testng.annotations.Test; import com.google.common.base.Optional; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; @@ -124,6 +127,33 @@ public class SshToCustomPortLiveTest extends BaseComputeServiceContextLiveTest { } } + + @Test(dependsOnMethods = "testImageCreated") + public void testAdvancedConfig() throws RunNodesException { + final DockerTemplateOptions options = DockerTemplateOptions.Builder + .configBuilder( + Config.builder().env(ImmutableList. of("SSH_PORT=" + SSH_PORT, "ROOT_PASSWORD=jcloudsRulez")) + .hostConfig(HostConfig.builder().networkMode("host").build()) + .image("test-if-this-value-is-correctly-overriden")) + .overrideLoginUser("root").overrideLoginPassword("jcloudsRulez").blockOnPort(SSH_PORT, 30); + + final Template template = view.getComputeService().templateBuilder().imageId(image.id()).options(options).build(); + + String nodeId = null; + try { + NodeMetadata node = Iterables + .getOnlyElement(view.getComputeService().createNodesInGroup("ssh-test-advanced", 1, template)); + + nodeId = node.getId(); + ExecResponse response = view.getComputeService().runScriptOnNode(nodeId, "sh -c 'true'", + wrapInInitScript(false)); + assertEquals(response.getExitStatus(), 0); + } finally { + if (nodeId != null) + view.getComputeService().destroyNode(nodeId); + } + } + /** * Build a new image with 2 tags on it in the test preparation phase. * diff --git a/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java b/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java index a1145f5a8d..a1bb321816 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java @@ -17,8 +17,11 @@ package org.jclouds.docker.compute.options; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.docker.domain.Config; +import org.jclouds.docker.domain.Config.Builder; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; @@ -91,6 +94,21 @@ public class DockerTemplateOptionsTest { assertEquals(options.as(DockerTemplateOptions.class).getNetworkMode(), "host"); } + @Test + public void testConfigBuilder() { + Builder builder = Config.builder().memory(1024) + .cpuShares(100).cmd(ImmutableList. of("/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0")) + .env(ImmutableList. of("JAVA_HOME=/opt/jdk-1.8", "MGMT_USER=admin", + "MGMT_PASSWORD=Schroedinger's Cat")); + TemplateOptions options = DockerTemplateOptions.Builder.configBuilder(builder); + Builder builderInOpts = options.as(DockerTemplateOptions.class).getConfigBuilder(); + assertNotNull(builderInOpts); + Config configFromOptions = builderInOpts.build(); + assertEquals(configFromOptions, builder.build()); + assertEquals(configFromOptions.env(), ImmutableList. of("JAVA_HOME=/opt/jdk-1.8", "MGMT_USER=admin", + "MGMT_PASSWORD=Schroedinger's Cat")); + } + @Test public void testNonDockerOptions() { TemplateOptions options = DockerTemplateOptions.Builder.userMetadata(ImmutableMap.of("key", "value")).cpuShares(1);