Skip to content

Commit

Permalink
Adds Zone.ttl
Browse files Browse the repository at this point in the history
Zone.ttl is the SOA ttl. This is not always the default for new records.

See #357
  • Loading branch information
Adrian Cole committed Mar 31, 2015
1 parent 31b1842 commit b599e10
Show file tree
Hide file tree
Showing 27 changed files with 258 additions and 104 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### Version 4.5
* Adds `Zone.email()` and displays it in CLI zone list output
* Adds `Zone.email()` and `Zone.ttl()` and displays them in CLI zone list output
* Adds `ZoneApi.iterateByName()` to support lookups
* Adds `-n` parameter to CLI zone list
* Deprecates `Zone.idOrName()` as `Zone.id()` cannot be null
Expand Down
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ If you just want to fool around, you can use the `mock` provider.
```bash
# first column is the zone id, which isn't always its name!
$ denominator -p mock zone list
denominator.io. denominator.io. admin.denominator.io.
denominator.io. denominator.io. admin.denominator.io. 86400
$ denominator -p mock -z denominator.io. record list
denominator.io. SOA 3600 ns1.denominator.io. admin.denominator.io. 1 3600 600 604800 60
denominator.io. NS 86400 ns1.denominator.io.
Expand Down
3 changes: 2 additions & 1 deletion cli/src/main/java/denominator/cli/Denominator.java
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ public Iterator<String> doRun(final DNSApiManager mgr) {
return Iterators.transform(zones, new Function<Zone, String>() {
@Override
public String apply(Zone input) {
return format("%-24s %-36s %s", input.id(), input.name(), input.email());
return format("%-24s %-36s %-36s %d", input.id(), input.name(), input.email(),
input.ttl());
}
});
}
Expand Down
4 changes: 2 additions & 2 deletions cli/src/test/java/denominator/cli/DenominatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void listsAllProvidersWithCredentials() {
@Test // denominator -p mock zone list
public void testZoneList() {
assertThat(new ZoneList().doRun(mgr)).containsExactly(
"denominator.io. denominator.io. admin.denominator.io."
"denominator.io. denominator.io. admin.denominator.io. 86400"
);
}

Expand Down Expand Up @@ -226,7 +226,7 @@ public void testResourceRecordSetList() {
assertThat(command.doRun(mgr)).containsExactly(
"a.denominator.io. A alazona null 192.0.2.1",
"denominator.io. NS 86400 ns1.denominator.io.",
"denominator.io. SOA 3600 ns1.denominator.io. admin.denominator.io. 1 3600 600 604800 86400",
"denominator.io. SOA 86400 ns1.denominator.io. admin.denominator.io. 1 3600 600 604800 86400",
"server1.denominator.io. CERT 3600 12345 1 1 B33F",
"server1.denominator.io. SRV 3600 0 1 80 www.denominator.io.",
"www.geo.denominator.io. CNAME alazona 86400 a.denominator.io.",
Expand Down
34 changes: 18 additions & 16 deletions clouddns/src/main/java/denominator/clouddns/CloudDNSFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
import denominator.clouddns.RackspaceApis.CloudDNS;
import denominator.clouddns.RackspaceApis.Job;
import denominator.clouddns.RackspaceApis.Record;
import denominator.model.rdata.AAAAData;
import denominator.model.rdata.AData;
import denominator.model.rdata.CNAMEData;
import denominator.common.Util;
import denominator.model.rdata.MXData;
import denominator.model.rdata.NSData;
import denominator.model.rdata.SOAData;
import denominator.model.rdata.SRVData;
import denominator.model.rdata.TXTData;
import feign.RetryableException;
Expand Down Expand Up @@ -39,28 +37,32 @@ static void awaitComplete(CloudDNS api, Job job) {
}
}

/**
* Special-cases priority field and the strange and incomplete SOA record.
*/
static Map<String, Object> toRDataMap(Record record) {
if ("A".equals(record.type)) {
return AData.create(record.data());
} else if ("AAAA".equals(record.type)) {
return AAAAData.create(record.data());
} else if ("CNAME".equals(record.type)) {
return CNAMEData.create(record.data());
} else if ("MX".equals(record.type)) {
if ("MX".equals(record.type)) {
return MXData.create(record.priority, record.data());
} else if ("NS".equals(record.type)) {
return NSData.create(record.data());
} else if ("TXT".equals(record.type)) {
return TXTData.create(record.data());
} else if ("SRV".equals(record.type)) {
List<String> rdata = split(' ', record.data());
return SRVData.builder()
.priority(record.priority)
.weight(Integer.valueOf(rdata.get(0)))
.port(Integer.valueOf(rdata.get(1)))
.target(rdata.get(2)).build();
} else if ("TXT".equals(record.type)) {
return TXTData.create(record.data());
} else if ("SOA".equals(record.type)) {
List<String> threeParts = split(' ', record.data());
return SOAData.builder()
.mname(threeParts.get(0))
.rname(threeParts.get(1))
.serial(Integer.valueOf(threeParts.get(2)))
.refresh(record.ttl)
.retry(record.ttl)
.expire(record.ttl).minimum(record.ttl).build();
} else {
throw new UnsupportedOperationException("record type not yet supported" + record);
return Util.toMap(record.type, record.data());
}
}

Expand Down
52 changes: 50 additions & 2 deletions clouddns/src/main/java/denominator/clouddns/CloudDNSZoneApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import javax.inject.Inject;

import denominator.clouddns.RackspaceApis.CloudDNS;
import denominator.clouddns.RackspaceApis.ListWithNext;
import denominator.clouddns.RackspaceApis.Record;
import denominator.model.Zone;

import static denominator.common.Util.singletonIterator;

class CloudDNSZoneApi implements denominator.ZoneApi {

private final CloudDNS api;
Expand All @@ -18,11 +22,55 @@ class CloudDNSZoneApi implements denominator.ZoneApi {

@Override
public Iterator<Zone> iterator() {
return api.domains().iterator();
return new ZipWithDomain(api.domains());
}

@Override
public Iterator<Zone> iterateByName(String name) {
return api.domainsByName(name).iterator();
ListWithNext<Zone> zones = api.domainsByName(name);
if (zones.isEmpty()) {
return singletonIterator(null);
}
return singletonIterator(zipWithSOA(zones.get(0)));
}

/**
* CloudDNS doesn't expose the domain's ttl in the list api.
*/
private Zone zipWithSOA(Zone next) {
Record soa = api.recordsByNameAndType(Integer.parseInt(next.id()), next.name(), "SOA").get(0);
return Zone.create(next.id(), next.name(), soa.ttl, next.email());
}

class ZipWithDomain implements Iterator<Zone> {

ListWithNext<Zone> list;
int i = 0;
int length;

ZipWithDomain(ListWithNext<Zone> list) {
this.list = list;
this.length = list.size();
}

@Override
public boolean hasNext() {
while (i == length && list.next != null) {
list = api.domains(list.next);
length = list.size();
i = 0;
}
return i < length;
}

@Override
public Zone next() {
return zipWithSOA(list.get(i++));
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,17 @@ protected Zone build(JsonReader reader) throws IOException {
String name = null, id = null, email = null;
while (reader.hasNext()) {
String nextName = reader.nextName();
if (nextName.equals("name")) {
name = reader.nextString();
} else if (nextName.equals("id")) {
if (nextName.equals("id")) {
id = reader.nextString();
} else if (nextName.equals("name")) {
name = reader.nextString();
} else if (nextName.equals("emailAddress")) {
email = reader.nextString();
} else {
reader.skipValue();
}
}
return Zone.create(name, id, email);
return Zone.create(id, name, /* CloudDNS doesn't return ttl in the list api. */ 0, email);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ interface CloudDNS {
@RequestLine("GET /domains?name={name}")
ListWithNext<Zone> domainsByName(@Param("name") String name);

@RequestLine("GET")
ListWithNext<Zone> domains(URI href);

@RequestLine("GET /domains")
ListWithNext<Zone> domains();

@RequestLine("GET /domains/{domainId}?showRecords=false&showSubdomains=false")
Zone domain(@Param("domainId") String id);

@RequestLine("GET")
ListWithNext<Record> records(URI href);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import denominator.ResourceRecordSetApi;
import denominator.model.ResourceRecordSet;
import denominator.model.rdata.AData;
import denominator.model.rdata.SOAData;

import static denominator.assertj.ModelAssertions.assertThat;
import static denominator.clouddns.RackspaceApisTest.domainId;
import static denominator.clouddns.RackspaceApisTest.soaResponse;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;

Expand Down Expand Up @@ -151,6 +153,30 @@ public void getByNameAndTypeWhenPresent() throws Exception {
.hasPath("/v1.0/123123/domains/1234/records?name=www.denominator.io&type=A");
}

@Test
public void getByNameAndType_SOA() throws Exception {
server.enqueueAuthResponse();
server.enqueue(new MockResponse().setBody(soaResponse));

ResourceRecordSetApi api = server.connect().api().basicRecordSetsInZone(domainId + "");

assertThat(api.getByNameAndType("denominator.io", "SOA"))
.hasName("denominator.io")
.hasType("SOA")
.hasTtl(3600)
.containsExactlyRecords(SOAData.builder()
.mname("ns.rackspace.com")
.rname("admin@denominator.io")
.serial(1427817447)
.refresh(3600).retry(3600)
.expire(3600).minimum(3600).build());

server.assertAuthRequest();
server.assertRequest()
.hasMethod("GET")
.hasPath("/v1.0/123123/domains/1234/records?name=denominator.io&type=SOA");
}

@Test
public void getByNameAndTypeWhenAbsent() throws Exception {
server.enqueueAuthResponse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import static denominator.assertj.ModelAssertions.assertThat;
import static denominator.clouddns.RackspaceApisTest.domainsResponse;
import static denominator.clouddns.RackspaceApisTest.soaResponse;

public class CloudDNSZoneApiMockTest {

Expand All @@ -20,15 +21,18 @@ public class CloudDNSZoneApiMockTest {
public void iteratorWhenPresent() throws Exception {
server.enqueueAuthResponse();
server.enqueue(new MockResponse().setBody(domainsResponse));
server.enqueue(new MockResponse().setBody(soaResponse));

ZoneApi api = server.connect().api().zones();

assertThat(api.iterator()).containsExactly(
Zone.create("denominator.io", "1234", "admin@denominator.io")
Zone.create("1234", "denominator.io", 3600, "admin@denominator.io")
);

server.assertAuthRequest();
server.assertRequest().hasPath("/v1.0/123123/domains");
server.assertRequest()
.hasPath("/v1.0/123123/domains/1234/records?name=denominator.io&type=SOA");
}

@Test
Expand All @@ -47,15 +51,18 @@ public void iteratorWhenAbsent() throws Exception {
public void iteratorByNameWhenPresent() throws Exception {
server.enqueueAuthResponse();
server.enqueue(new MockResponse().setBody(domainsResponse));
server.enqueue(new MockResponse().setBody(soaResponse));

ZoneApi api = server.connect().api().zones();

assertThat(api.iterateByName("denominator.io")).containsExactly(
Zone.create("denominator.io", "1234", "admin@denominator.io")
Zone.create("1234", "denominator.io", 3600, "admin@denominator.io")
);

server.assertAuthRequest();
server.assertRequest().hasPath("/v1.0/123123/domains?name=denominator.io");
server.assertRequest()
.hasPath("/v1.0/123123/domains/1234/records?name=denominator.io&type=SOA");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,17 @@ public void domainsPresent() throws Exception {
.hasPath("/v1.0/123123/domains");
}

/**
* Rackspace doesn't expose the ttl in domain list. A dummy TTL of zero is added as this result is
* never used directly.
*/
@Test
public void domainsByNamePresent() throws Exception {
server.enqueueAuthResponse();
server.enqueue(new MockResponse().setBody(domainsResponse));

assertThat(mockApi().domainsByName("denominator.io")).containsExactly(
Zone.create("denominator.io", "1234", "admin@denominator.io")
Zone.create("1234", "denominator.io", 0, "admin@denominator.io")
);

server.assertAuthRequest();
Expand Down Expand Up @@ -279,6 +283,9 @@ public Credentials get() {
static String
domainsResponse =
"{\"domains\":[{\"name\":\"denominator.io\",\"id\":1234,\"accountId\":123123,\"emailAddress\":\"admin@denominator.io\",\"updated\":\"2013-09-02T19:46:56.000+0000\",\"created\":\"2013-09-02T19:45:51.000+0000\"}],\"totalEntries\":1}";
static String
soaResponse =
"{\"records\":[{\"name\":\"denominator.io\",\"id\":\"SOA-4612221\",\"type\":\"SOA\",\"data\":\"ns.rackspace.com admin@denominator.io 1427817447\",\"ttl\":3600}]}";
// NOTE records are allowed to be out of order by type
static String
recordsResponse =
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/java/denominator/mock/MockZoneApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void create(String name) {
zone.add(ResourceRecordSet.builder()
.type("SOA")
.name(name)
.ttl(3600)
.ttl(86400)
.add(SOAData.builder().mname("ns1." + name).rname("admin." + name)
.serial(1).refresh(3600).retry(600).expire(604800).minimum(86400).build())
.build());
Expand All @@ -64,13 +64,13 @@ public boolean hasNext() {
public Zone next() {
Entry<String, Collection<ResourceRecordSet<?>>> next = delegate.next();
String name = next.getKey();
Iterator<ResourceRecordSet<?>> soa =
Iterator<ResourceRecordSet<?>> soas =
filter(next.getValue().iterator(), nameAndTypeEqualTo(name, "SOA"));

checkState(soa.hasNext(), "SOA record for zone %s was not present", name);

SOAData soaData = (SOAData) soa.next().records().get(0);
return Zone.create(name, name, soaData.rname());
checkState(soas.hasNext(), "SOA record for zone %s was not present", name);
ResourceRecordSet<SOAData> soa = (ResourceRecordSet<SOAData>) soas.next();
SOAData soaData = soa.records().get(0);
return Zone.create(name, name, soa.ttl(), soaData.rname());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public void testImplicitDynamicCredentialsUpdate() {
DNSApiManager mgr = create(new DynamicCredentialsProvider());
ZoneApi zones = mgr.api().zones();
assertThat(zones.iterator())
.containsExactly(Zone.create("acme", "wily", "coyote"));
.containsExactly(Zone.create("acme", "wily", 86400, "coyote"));
assertThat(zones.iterator())
.containsExactly(Zone.create("acme", "road", "runner"));
.containsExactly(Zone.create("acme", "road", 86400, "runner"));

// now, if the supplier doesn't supply a set of credentials, we should
// get a correct message
Expand Down Expand Up @@ -118,7 +118,7 @@ public Iterator<Zone> iterator() {
CustomerUsernamePassword cup = creds.get();
// normally, the credentials object would be used to invoke a remote
// command. in this case, we don't and say we did :)
return asList(Zone.create(cup.customer, cup.username, cup.password)).iterator();
return asList(Zone.create(cup.customer, cup.username, 86400, cup.password)).iterator();
}

@Override
Expand Down
Loading

0 comments on commit b599e10

Please sign in to comment.