package io.fathom.cloud.dns.backend.aws;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.route53.AmazonRoute53;
import com.amazonaws.services.route53.AmazonRoute53Client;
import com.amazonaws.services.route53.model.Change;
import com.amazonaws.services.route53.model.ChangeAction;
import com.amazonaws.services.route53.model.ChangeBatch;
import com.amazonaws.services.route53.model.ChangeResourceRecordSetsRequest;
import com.amazonaws.services.route53.model.ChangeResourceRecordSetsResult;
import com.amazonaws.services.route53.model.CreateHostedZoneRequest;
import com.amazonaws.services.route53.model.CreateHostedZoneResult;
import com.amazonaws.services.route53.model.HostedZone;
import com.amazonaws.services.route53.model.HostedZoneAlreadyExistsException;
import com.amazonaws.services.route53.model.ListHostedZonesRequest;
import com.amazonaws.services.route53.model.ListHostedZonesResult;
import com.amazonaws.services.route53.model.ListResourceRecordSetsRequest;
import com.amazonaws.services.route53.model.ListResourceRecordSetsResult;
import com.amazonaws.services.route53.model.ResourceRecordSet;
import com.fathomdb.Configuration;
import com.google.common.base.CharMatcher;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@Singleton
// If injected, a singleton
public class AwsRoute53Client {
private static final Logger log = LoggerFactory.getLogger(AwsRoute53Client.class);
final AmazonRoute53 restClient;
Map<String, HostedZone> hostedZones;
@Inject
public AwsRoute53Client(Configuration config) {
this(config.get("aws.route53.access"), config.get("aws.route53.secret"));
}
public AwsRoute53Client(String accessKey, String secretKey) {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
this.restClient = new AmazonRoute53Client(awsCredentials);
}
public List<ResourceRecordSet> getResourceRecords(String awsZoneId) {
List<ResourceRecordSet> resourceRecords = Lists.newArrayList();
ListResourceRecordSetsResult previous = null;
while (true) {
ListResourceRecordSetsRequest request = new ListResourceRecordSetsRequest(awsZoneId);
if (previous != null) {
request.setStartRecordIdentifier(previous.getNextRecordIdentifier());
request.setStartRecordName(previous.getNextRecordName());
request.setStartRecordType(previous.getNextRecordType());
}
ListResourceRecordSetsResult response = restClient.listResourceRecordSets(request);
for (ResourceRecordSet resourceRecordSet : response.getResourceRecordSets()) {
resourceRecords.add(resourceRecordSet);
}
if (!Objects.equal(response.isTruncated(), Boolean.TRUE)) {
break;
}
previous = response;
}
return resourceRecords;
}
public synchronized Map<String, HostedZone> getHostedZones() {
if (hostedZones == null) {
refreshHostedZones();
}
return hostedZones;
}
private synchronized void refreshHostedZones() {
hostedZones = getHostedZones0();
}
private synchronized Map<String, HostedZone> getHostedZones0() {
Map<String, HostedZone> hostedZones = Maps.newHashMap();
String marker = null;
while (true) {
ListHostedZonesRequest request = new ListHostedZonesRequest();
if (marker != null) {
request.withMarker(marker);
}
ListHostedZonesResult response = restClient.listHostedZones(request);
for (HostedZone hostedZone : response.getHostedZones()) {
String key = CharMatcher.is('.').trimTrailingFrom(hostedZone.getName());
hostedZones.put(key, hostedZone);
}
if (!Objects.equal(response.isTruncated(), Boolean.TRUE)) {
break;
}
marker = response.getMarker();
}
return hostedZones;
}
public HostedZone createHostedZone(String zoneName) {
HostedZone zone = getHostedZones().get(zoneName);
if (zone != null) {
return zone;
}
log.info("Creating Route 53 zone: {}", zoneName);
CreateHostedZoneRequest request = new CreateHostedZoneRequest();
request.setName(zoneName);
request.setCallerReference("CreateHostedZoneRequest:" + zoneName);
// HostedZoneConfig hostedZoneConfig = new HostedZoneConfig();
// hostedZoneConfig.setComment(comment);
// request.setHostedZoneConfig(hostedZoneConfig);
try {
CreateHostedZoneResult result = restClient.createHostedZone(request);
zone = result.getHostedZone();
} catch (HostedZoneAlreadyExistsException e) {
refreshHostedZones();
zone = hostedZones.get(zoneName);
}
if (zone == null) {
throw new IllegalStateException();
}
return zone;
}
public void changeRecords(String hostedZoneId, List<ResourceRecordSet> create, List<ResourceRecordSet> remove) {
List<Change> changes = Lists.newArrayList();
for (ResourceRecordSet r : remove) {
Change change = new Change(ChangeAction.DELETE, r);
changes.add(change);
}
for (ResourceRecordSet r : create) {
Change change = new Change(ChangeAction.CREATE, r);
changes.add(change);
}
if (changes.isEmpty()) {
log.info("No DNS record changes");
return;
}
ChangeBatch changeBatch = new ChangeBatch();
changeBatch.setChanges(changes);
ChangeResourceRecordSetsRequest request = new ChangeResourceRecordSetsRequest();
request.setHostedZoneId(hostedZoneId);
request.setChangeBatch(changeBatch);
log.info("Changing DNS records: {}", request);
ChangeResourceRecordSetsResult response = restClient.changeResourceRecordSets(request);
log.info("Changed records: {}", response);
}
public String getAwsZoneName(String domainName) {
domainName = CharMatcher.is('.').trimTrailingFrom(domainName);
int lastDot = domainName.lastIndexOf('.');
if (lastDot == -1) {
return domainName;
}
String suffix = domainName.substring(lastDot + 1);
// TODO: Check for compound suffixes?
String prefix = domainName.substring(0, lastDot);
int lastDotPrefix = prefix.lastIndexOf('.');
if (lastDotPrefix != -1) {
prefix = prefix.substring(lastDotPrefix + 1);
}
return prefix + "." + suffix;
}
}