mirror of https://github.com/apache/jclouds.git
JCLOUDS-1014: Make the login port lookup function configurable
This commit is contained in:
parent
8ca3a326c8
commit
01d43f503b
|
@ -84,6 +84,10 @@
|
|||
<artifactId>jclouds-okhttp</artifactId>
|
||||
<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>
|
||||
|
|
|
@ -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,6 +32,9 @@ 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> {
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue