io.vertx.servicediscovery.docker.DockerLinksServiceImporter Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2016 The original author or authors
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.servicediscovery.docker;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.servicediscovery.Record;
import io.vertx.servicediscovery.spi.ServiceImporter;
import io.vertx.servicediscovery.spi.ServicePublisher;
import io.vertx.servicediscovery.spi.ServiceType;
import io.vertx.servicediscovery.types.HttpEndpoint;
import io.vertx.servicediscovery.types.HttpLocation;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* A discovery bridge analyzing environment variables to detect Docker links. It imports found links as services in
* the discovery infrastructure. As they are mapped from environment variable the set of imported service does not
* change after the initialization.
*
* @author Clement Escoffier
*/
public class DockerLinksServiceImporter implements ServiceImporter {
/**
* The service discovery instance. Immutable once set.
*/
private ServicePublisher publisher;
/**
* The list of records that are imported. Content staled once initialized.
*/
private List records = new ArrayList<>();
private final static Logger LOGGER = LoggerFactory.getLogger(DockerLinksServiceImporter.class);
@Override
public void start(Vertx vertx, ServicePublisher publisher, JsonObject configuration,
Promise completion) {
this.publisher = publisher;
synchronized (this) {
lookup(completion);
}
}
private void lookup(Promise completion) {
Map variables = getVariables();
// Find names
List links = variables.keySet().stream()
.filter(key -> key.endsWith("_NAME"))
.map(key -> extractLinkName(key, variables))
.filter(key -> key != null)
.collect(Collectors.toList());
LOGGER.info("Docker links: " + links);
for (String link : links) {
try {
Record record = createRecord(link,
variables.entrySet().stream()
.filter(entry -> entry.getKey().startsWith(link + "_"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
LOGGER.info("Record created from link " + link + " : " + record);
publisher.publish(record, ar -> {
if (ar.succeeded()) {
records.add(ar.result());
LOGGER.info("Service imported from Docker link : " + link + " with endpoint set to "
+ ar.result().getLocation().getString(Record.ENDPOINT));
} else {
LOGGER.error("Publication of the docker link " + link + " as a service failed", ar.cause());
}
});
} catch (URISyntaxException e) {
if (completion != null) {
completion.fail(e);
} else {
throw new IllegalStateException("Cannot extract service record from variables for " + link, e);
}
}
}
if (completion != null) {
completion.complete();
}
}
private String extractLinkName(String key, Map variables) {
// We know it ends with _NAME;
String name = key.substring(0, key.length() - "_NAME".length());
if (name.isEmpty()) {
// Weird case the key is just _NAME
return null;
} else {
String port = name + "_PORT";
if (variables.containsKey(port)) {
return name;
} else {
// Not a valid link
return null;
}
}
}
private Map getVariables() {
LinkedHashMap vars = new LinkedHashMap<>();
vars.putAll(System.getenv());
System.getProperties().entrySet().forEach(entry ->
vars.put(entry.getKey().toString(), entry.getValue().toString()));
return vars;
}
@Override
public void close(Handler completionHandler) {
List list = new ArrayList<>();
for (Record record : records) {
publisher.unpublish(record.getRegistration(),
v -> list.add(v.succeeded() ? Future.succeededFuture() : Future.failedFuture(v.cause())));
}
CompositeFuture.all(list).onComplete(ar -> {
if (ar.succeeded()) {
LOGGER.info("Successfully closed the service importer " + this);
} else {
LOGGER.error("A failure has been caught while stopping " + this, ar.cause());
}
if (completionHandler != null) {
completionHandler.handle(null);
}
}
);
}
private Record createRecord(String name, Map variables) throws URISyntaxException {
Record record = new Record()
.setName(name);
// Add as metadata all entries
variables.entrySet().forEach(entry -> {
if (entry.getKey().startsWith(name + "_")) {
String label = entry.getKey().substring((name + "_").length());
record.getMetadata().put(label, entry.getValue());
}
});
String type = variables.get(name + "_ENV_SERVICE_TYPE");
if (type == null) {
type = ServiceType.UNKNOWN;
} else {
LOGGER.info("Service type for " + name + " : " + type);
}
URI url = new URI(variables.get(name + "_PORT"));
switch (type) {
case "http-endpoint":
HttpLocation http = new HttpLocation();
http.setHost(url.getHost());
http.setPort(url.getPort());
if (isTrue(variables, name + "_ENV_SSL")) {
http.setSsl(true);
}
record.setType(HttpEndpoint.TYPE);
record.setLocation(http.toJson());
break;
default:
JsonObject location = new JsonObject();
location
.put("endpoint", url.toString())
.put("port", url.getPort())
.put("host", url.getHost())
.put("proto", url.getScheme());
record.setType(type);
record.setLocation(location);
}
return record;
}
private static boolean isTrue(Map labels, String key) {
return labels != null && "true".equalsIgnoreCase(labels.get(key));
}
}