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); + } + }; +}
