JCLOUDS-1014: Make the login port lookup function configurable

Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/01d43f50
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/01d43f50
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/01d43f50

Branch: refs/heads/master
Commit: 01d43f503b96c5ab225410b4e4e5d3711d18ef4f
Parents: 8ca3a32
Author: Ignasi Barrera <[email protected]>
Authored: Sat Oct 10 00:10:50 2015 +0200
Committer: Ignasi Barrera <[email protected]>
Committed: Mon Oct 19 16:14:05 2015 +0200

----------------------------------------------------------------------
 apis/docker/pom.xml                             |   4 +
 .../DockerComputeServiceContextModule.java      |   9 +-
 .../compute/config/LoginPortLookupModule.java   |  40 +++++++
 .../functions/ContainerToNodeMetadata.java      |  44 +++-----
 .../functions/CustomLoginPortFromImage.java     |  84 +++++++++++++++
 .../functions/LoginPortForContainer.java        |  51 +++++++++
 .../functions/PublicPortForContainerPort.java   |  58 ++++++++++
 .../functions/ContainerToNodeMetadataTest.java  |   8 +-
 .../functions/CustomLoginPortFromImageTest.java | 106 +++++++++++++++++++
 9 files changed, 364 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/pom.xml
----------------------------------------------------------------------
diff --git a/apis/docker/pom.xml b/apis/docker/pom.xml
index d93d43b..a10b263 100644
--- a/apis/docker/pom.xml
+++ b/apis/docker/pom.xml
@@ -85,6 +85,10 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>com.google.inject.extensions</groupId>
+      <artifactId>guice-multibindings</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.jclouds</groupId>
       <artifactId>jclouds-core</artifactId>
       <version>${project.version}</version>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/config/DockerComputeServiceContextModule.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/config/DockerComputeServiceContextModule.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/config/DockerComputeServiceContextModule.java
index 063d6c9..f9e03f4 100644
--- 
a/apis/docker/src/main/java/org/jclouds/docker/compute/config/DockerComputeServiceContextModule.java
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/config/DockerComputeServiceContextModule.java
@@ -16,8 +16,6 @@
  */
 package org.jclouds.docker.compute.config;
 
-import com.google.common.base.Function;
-import com.google.inject.TypeLiteral;
 import org.jclouds.compute.ComputeServiceAdapter;
 import org.jclouds.compute.config.ComputeServiceAdapterContextModule;
 import org.jclouds.compute.domain.Hardware;
@@ -34,8 +32,11 @@ import org.jclouds.docker.domain.State;
 import org.jclouds.domain.Location;
 import org.jclouds.functions.IdentityFunction;
 
+import com.google.common.base.Function;
+import com.google.inject.TypeLiteral;
+
 public class DockerComputeServiceContextModule extends
-        ComputeServiceAdapterContextModule<Container, Hardware, Image, 
Location> {
+      ComputeServiceAdapterContextModule<Container, Hardware, Image, Location> 
{
 
    @SuppressWarnings("unchecked")
    @Override
@@ -54,6 +55,8 @@ public class DockerComputeServiceContextModule extends
       bind(new TypeLiteral<Function<State, NodeMetadata.Status>>() {
       }).to(StateToStatus.class);
       bind(TemplateOptions.class).to(DockerTemplateOptions.class);
+
+      install(new LoginPortLookupModule());
    }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/config/LoginPortLookupModule.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/config/LoginPortLookupModule.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/config/LoginPortLookupModule.java
new file mode 100644
index 0000000..2daf287
--- /dev/null
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/config/LoginPortLookupModule.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.docker.compute.config;
+
+import org.jclouds.docker.compute.functions.LoginPortForContainer;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.multibindings.MapBinder;
+
+public class LoginPortLookupModule extends AbstractModule {
+
+   @Override
+   protected void configure() {
+      // Declare it to initialize the binder allowing duplicates. Users may
+      // provide different functions for the same image in different modules, 
or
+      // we could provide predefined functions for known images. This allows
+      // users to set their own ones too.
+      loginPortLookupBinder(binder());
+   }
+
+   public static MapBinder<String, LoginPortForContainer> 
loginPortLookupBinder(Binder binder) {
+      return MapBinder.newMapBinder(binder, String.class, 
LoginPortForContainer.class).permitDuplicates();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadata.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadata.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadata.java
index 20df6a3..db75e50 100644
--- 
a/apis/docker/src/main/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadata.java
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadata.java
@@ -16,9 +16,6 @@
  */
 package org.jclouds.docker.compute.functions;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.collect.Iterables.getOnlyElement;
-
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
@@ -34,7 +31,6 @@ import org.jclouds.compute.domain.NodeMetadataBuilder;
 import org.jclouds.compute.domain.Processor;
 import org.jclouds.compute.functions.GroupNamingConvention;
 import org.jclouds.docker.domain.Container;
-import org.jclouds.docker.domain.Port;
 import org.jclouds.docker.domain.State;
 import org.jclouds.domain.Location;
 import org.jclouds.providers.ProviderMetadata;
@@ -54,24 +50,26 @@ public class ContainerToNodeMetadata implements 
Function<Container, NodeMetadata
     * started outside jclouds. Client code should check for this value
     * when accessing NodeMetadata from Docker.
     */
-   public static final Integer NO_LOGIN_PORT = Integer.valueOf(-1);
+   private static final Integer NO_LOGIN_PORT = Integer.valueOf(-1);
 
    private final ProviderMetadata providerMetadata;
    private final Function<State, NodeMetadata.Status> toPortableStatus;
    private final GroupNamingConvention nodeNamingConvention;
    private final Supplier<Map<String, ? extends Image>> images;
    private final Supplier<Set<? extends Location>> locations;
+   private final LoginPortForContainer loginPortForContainer;
 
    @Inject
-   public ContainerToNodeMetadata(ProviderMetadata providerMetadata, 
Function<State,
-           NodeMetadata.Status> toPortableStatus, 
GroupNamingConvention.Factory namingConvention,
-                                  Supplier<Map<String, ? extends Image>> 
images,
-                                  @Memoized Supplier<Set<? extends Location>> 
locations) {
-      this.providerMetadata = checkNotNull(providerMetadata, 
"providerMetadata");
-      this.toPortableStatus = checkNotNull(toPortableStatus, "toPortableStatus 
cannot be null");
-      this.nodeNamingConvention = checkNotNull(namingConvention, 
"namingConvention").createWithoutPrefix();
-      this.images = checkNotNull(images, "images cannot be null");
-      this.locations = checkNotNull(locations, "locations");
+   ContainerToNodeMetadata(ProviderMetadata providerMetadata,
+         Function<State, NodeMetadata.Status> toPortableStatus, 
GroupNamingConvention.Factory namingConvention,
+         Supplier<Map<String, ? extends Image>> images, @Memoized 
Supplier<Set<? extends Location>> locations,
+         LoginPortForContainer loginPortForContainer) {
+      this.providerMetadata = providerMetadata;
+      this.toPortableStatus = toPortableStatus;
+      this.nodeNamingConvention = namingConvention.createWithoutPrefix();
+      this.images = images;
+      this.locations = locations;
+      this.loginPortForContainer = loginPortForContainer;
    }
 
    @Override
@@ -90,7 +88,7 @@ public class ContainerToNodeMetadata implements 
Function<Container, NodeMetadata
                       .processor(new Processor(container.config().cpuShares(), 
container.config().cpuShares()))
                       .build());
       builder.status(toPortableStatus.apply(container.state()));
-      builder.loginPort(getLoginPort(container));
+      
builder.loginPort(loginPortForContainer.apply(container).or(NO_LOGIN_PORT));
       builder.publicAddresses(getPublicIpAddresses());
       builder.privateAddresses(getPrivateIpAddresses(container));
       builder.location(Iterables.getOnlyElement(locations.get()));
@@ -117,20 +115,4 @@ public class ContainerToNodeMetadata implements 
Function<Container, NodeMetadata
       return ImmutableList.of(dockerIpAddress);
    }
 
-   protected static int getLoginPort(Container container) {
-      if (container.networkSettings() != null) {
-          Map<String, List<Map<String, String>>> ports = 
container.networkSettings().ports();
-          if (ports != null && ports.containsKey("22/tcp")) {
-            return 
Integer.parseInt(getOnlyElement(ports.get("22/tcp")).get("HostPort"));
-          }
-      // this is needed in case the container list is coming from 
listContainers
-      } else if (container.ports() != null) {
-         for (Port port : container.ports()) {
-            if (port.privatePort() == 22) {
-               return port.publicPort();
-            }
-         }
-      }
-      return NO_LOGIN_PORT;
-   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImage.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImage.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImage.java
new file mode 100644
index 0000000..811cdd4
--- /dev/null
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImage.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.docker.compute.functions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+
+import org.jclouds.docker.domain.Container;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+@Beta
+public class CustomLoginPortFromImage implements LoginPortForContainer {
+
+   private final Map<String, Set<LoginPortForContainer>> imageToPortLookup;
+
+   @Inject
+   CustomLoginPortFromImage(Map<String, Set<LoginPortForContainer>> 
imageToPortLookup) {
+      this.imageToPortLookup = imageToPortLookup;
+   }
+
+   @Override
+   public Optional<Integer> apply(final Container container) {
+      Map<String, Set<LoginPortForContainer>> matchingFunctions = 
Maps.filterKeys(imageToPortLookup,
+            new Predicate<String>() {
+               @Override
+               public boolean apply(String input) {
+                  return container.config().image().matches(input);
+               }
+            });
+
+      // We allow to provide several forms in the image-to-function map:
+      // - redis
+      // - redis:12
+      // - owner/redis:12
+      // - registry:5000/owner/redis:12
+      // We consider the longest match first, as it is the more accurate one
+      List<String> sortedImages = new 
ArrayList<String>(matchingFunctions.keySet());
+      Collections.sort(sortedImages, LongestStringFirst);
+
+      for (String currentImage : sortedImages) {
+         Set<LoginPortForContainer> functions = 
matchingFunctions.get(currentImage);
+         for (LoginPortForContainer function : functions) {
+            Optional<Integer> port = function.apply(container);
+            if (port.isPresent()) {
+               return port;
+            }
+         }
+      }
+
+      return Optional.absent();
+   }
+
+   private static final Comparator<String> LongestStringFirst = new 
Comparator<String>() {
+      @Override
+      public int compare(String s1, String s2) {
+         return s2.length() - s1.length();
+      }
+   };
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/functions/LoginPortForContainer.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/functions/LoginPortForContainer.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/LoginPortForContainer.java
new file mode 100644
index 0000000..723f9c0
--- /dev/null
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/LoginPortForContainer.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.docker.compute.functions;
+
+import javax.inject.Inject;
+
+import 
org.jclouds.docker.compute.functions.LoginPortForContainer.LoginPortLookupChain;
+import org.jclouds.docker.domain.Container;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.ImplementedBy;
+
+@Beta
+@ImplementedBy(LoginPortLookupChain.class)
+public interface LoginPortForContainer extends Function<Container, 
Optional<Integer>> {
+
+   @Beta
+   static final class LoginPortLookupChain implements LoginPortForContainer {
+      private final PublicPortForContainerPort publicPortForContainerPort;
+      private final CustomLoginPortFromImage customLoginPortFromImage;
+
+      @Inject
+      LoginPortLookupChain(CustomLoginPortFromImage customLoginPortFromImage) {
+         this.publicPortForContainerPort = new PublicPortForContainerPort(22);
+         this.customLoginPortFromImage = customLoginPortFromImage;
+      }
+
+      @Override
+      public Optional<Integer> apply(Container input) {
+         Optional<Integer> loginPort = publicPortForContainerPort.apply(input);
+         return loginPort.isPresent() ? loginPort : 
customLoginPortFromImage.apply(input);
+      }
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/main/java/org/jclouds/docker/compute/functions/PublicPortForContainerPort.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/main/java/org/jclouds/docker/compute/functions/PublicPortForContainerPort.java
 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/PublicPortForContainerPort.java
new file mode 100644
index 0000000..3b11547
--- /dev/null
+++ 
b/apis/docker/src/main/java/org/jclouds/docker/compute/functions/PublicPortForContainerPort.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.docker.compute.functions;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jclouds.docker.domain.Container;
+import org.jclouds.docker.domain.Port;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Optional;
+
+@Beta
+public class PublicPortForContainerPort implements LoginPortForContainer {
+
+   private final int containerPort;
+
+   public PublicPortForContainerPort(int containerPort) {
+      this.containerPort = containerPort;
+   }
+
+   @Override
+   public Optional<Integer> apply(Container container) {
+      if (container.networkSettings() != null) {
+         Map<String, List<Map<String, String>>> ports = 
container.networkSettings().ports();
+         if (ports != null && ports.containsKey(containerPort + "/tcp")) {
+            return 
Optional.of(Integer.parseInt(getOnlyElement(ports.get(containerPort + 
"/tcp")).get("HostPort")));
+         }
+         // this is needed in case the container list is coming from
+         // listContainers
+      } else if (container.ports() != null) {
+         for (Port port : container.ports()) {
+            if (port.privatePort() == containerPort) {
+               return Optional.of(port.publicPort());
+            }
+         }
+      }
+      return Optional.absent();
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/test/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadataTest.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/test/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadataTest.java
 
b/apis/docker/src/test/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadataTest.java
index 850863c..0f81c94 100644
--- 
a/apis/docker/src/test/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadataTest.java
+++ 
b/apis/docker/src/test/java/org/jclouds/docker/compute/functions/ContainerToNodeMetadataTest.java
@@ -44,7 +44,6 @@ import org.jclouds.docker.domain.State;
 import org.jclouds.domain.Location;
 import org.jclouds.domain.LocationBuilder;
 import org.jclouds.domain.LocationScope;
-import org.jclouds.domain.LoginCredentials;
 import org.jclouds.providers.ProviderMetadata;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -62,8 +61,6 @@ import com.google.inject.Guice;
 @Test(groups = "unit", testName = "ContainerToNodeMetadataTest")
 public class ContainerToNodeMetadataTest {
 
-   private LoginCredentials credentials;
-
    private ContainerToNodeMetadata function;
 
    private Container container;
@@ -170,9 +167,8 @@ public class ContainerToNodeMetadataTest {
          }
       };
 
-      credentials = 
LoginCredentials.builder().user("foo").password("bar").build();
-
-      function = new ContainerToNodeMetadata(providerMetadata, 
toPortableStatus(), namingConvention, images, locations);
+      function = new ContainerToNodeMetadata(providerMetadata, 
toPortableStatus(), namingConvention, images, locations,
+            new LoginPortForContainer.LoginPortLookupChain(null));
    }
 
    private Function<State, NodeMetadata.Status> toPortableStatus() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/01d43f50/apis/docker/src/test/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImageTest.java
----------------------------------------------------------------------
diff --git 
a/apis/docker/src/test/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImageTest.java
 
b/apis/docker/src/test/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImageTest.java
new file mode 100644
index 0000000..3e747d3
--- /dev/null
+++ 
b/apis/docker/src/test/java/org/jclouds/docker/compute/functions/CustomLoginPortFromImageTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.docker.compute.functions;
+
+import static 
org.jclouds.docker.compute.config.LoginPortLookupModule.loginPortLookupBinder;
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.docker.compute.config.LoginPortLookupModule;
+import org.jclouds.docker.domain.Config;
+import org.jclouds.docker.domain.Container;
+import org.testng.annotations.BeforeClass;
+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.Iterables;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.multibindings.MapBinder;
+
+@Test(groups = "unit")
+public class CustomLoginPortFromImageTest {
+
+   private CustomLoginPortFromImage customLoginPortFromImage;
+
+   @BeforeClass
+   public void setup() {
+      Injector i = Guice.createInjector(new LoginPortLookupModule(), new 
AbstractModule() {
+         @Override
+         protected void configure() {
+            MapBinder<String, LoginPortForContainer> imageToFunction = 
loginPortLookupBinder(binder());
+            
imageToFunction.addBinding(".*alpine-ext.*").toInstance(LoginPortFromEnvVar);
+            imageToFunction.addBinding(".*ubuntu.*").toInstance(AlwaysPort22);
+            
imageToFunction.addBinding(".*ubuntu:12\\.04.*").toInstance(AlwaysPort8080);
+         }
+      });
+      customLoginPortFromImage = i.getInstance(CustomLoginPortFromImage.class);
+   }
+
+   public void testPortFromEnvironmentVariables() {
+      Config config = 
Config.builder().image("alpine-ext:3.2").env(ImmutableList.of("FOO=bar", 
"SSH_PORT=2345"))
+            .build();
+      Container container = 
Container.builder().id("id").config(config).build();
+
+      assertEquals(customLoginPortFromImage.apply(container).get().intValue(), 
2345);
+   }
+
+   public void testMostSpecificImageIsPicked() {
+      Config config = Config.builder().image("ubuntu:12.04").build();
+      Container container = 
Container.builder().id("id").config(config).build();
+
+      assertEquals(customLoginPortFromImage.apply(container).get().intValue(), 
8080);
+   }
+
+   public void testNoImageFoundInMap() {
+      Config config = Config.builder().image("unexisting").build();
+      Container container = 
Container.builder().id("id").config(config).build();
+
+      assertEquals(customLoginPortFromImage.apply(container), 
Optional.absent());
+   }
+
+   private static final LoginPortForContainer LoginPortFromEnvVar = new 
LoginPortForContainer() {
+      @Override
+      public Optional<Integer> apply(Container input) {
+         Optional<String> portVariable = 
Iterables.tryFind(input.config().env(), new Predicate<String>() {
+            @Override
+            public boolean apply(String input) {
+               String[] var = input.split("=");
+               return var[0].equals("SSH_PORT");
+            }
+         });
+         return portVariable.isPresent() ? 
Optional.of(Integer.valueOf(portVariable.get().split("=")[1])) : Optional
+               .<Integer> absent();
+      }
+   };
+
+   private static final LoginPortForContainer AlwaysPort22 = new 
LoginPortForContainer() {
+      @Override
+      public Optional<Integer> apply(Container input) {
+         return Optional.of(22);
+      }
+   };
+
+   private static final LoginPortForContainer AlwaysPort8080 = new 
LoginPortForContainer() {
+      @Override
+      public Optional<Integer> apply(Container input) {
+         return Optional.of(8080);
+      }
+   };
+}

Reply via email to