Skip to content

Commit

Permalink
Merge pull request #350 from Netflix/adrian.zone-qualifiers
Browse files Browse the repository at this point in the history
Refines zone identifiers and handling of zones with the same name
  • Loading branch information
adriancole committed Mar 24, 2015
2 parents 51a5e31 + 99ed22d commit d435178
Show file tree
Hide file tree
Showing 80 changed files with 927 additions and 267 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
### Version 4.5
* Adds `ZoneApi.iterateByName()` to support lookups
* Adds `-n` parameter to CLI zone list
* Refines zone identifiers and handling of zones with the same name
* Adds `Zone.qualifier()` in support duplicate zones
* Deprecates `supportsDuplicateZoneNames()` in favor of `Provider.zoneIdentification()` in `NAME`, `OPAQUE` and `QUALIFIED`
* Deprecates `Zone.idOrName()` as `Zone.id()` cannot be null
* Documents third-party provider process
* Publishes model and core test jars
* Adds example server
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ DNSApiManager manager = Denominator.create("ultradns", credentials(username, pas
The credentials are variable length, as certain providers require more that 2 parts. The above returns an instance of `DNSApiManager` where apis such as `ZoneApis` are found. Here's how to list zones:
```java
for (Zone zone : manager.api().zones()) {
for (ResourceRecordSet<?> rrs : manager.api().recordSetsInZone(zone.idOrName())) {
for (ResourceRecordSet<?> rrs : manager.api().recordSetsInZone(zone.id())) {
processRRS(rrs);
}
}
Expand Down
43 changes: 32 additions & 11 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,21 @@ Different providers connect to different urls and need different credentials. F

```bash
$ denominator providers
provider url duplicateZones credentialType credentialArgs
mock mem:mock false
clouddns https://identity.api.rackspacecloud.com/v2.0/ true apiKey username apiKey
designate http://localhost:5000/v2.0 true password tenantId username password
dynect https://api2.dynect.net/REST false password customer username password
route53 https://route53.amazonaws.com true accessKey accessKey secretKey
route53 https://route53.amazonaws.com true session accessKey secretKey sessionToken
ultradns https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01 false password username password
provider url zoneIds credentialType credentialArgs
clouddns https://identity.api.rackspacecloud.com/v2.0 opaque password username password
clouddns https://identity.api.rackspacecloud.com/v2.0 opaque apiKey username apiKey
designate http://localhost:5000/v2.0 opaque password tenantId username password
dynect https://api2.dynect.net/REST name password customer username password
mock mem:mock name
route53 https://route53.amazonaws.com qualified accessKey accessKey secretKey
route53 https://route53.amazonaws.com qualified session accessKey secretKey sessionToken
ultradns https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01 name password username password
```

The first field says the type, if any. If there's no type listed, it needs no credentials. If there is a type listed, the following fields are credential args. Say for example, you were using `ultradns`.
If the credentialType column is blank it needs no credentials. Otherwise, credentialArgs describes what you need to pass. Say for example, you were using `ultradns`.

```
ultradns https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01 password username password
ultradns https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01 name password username password
```
This says the provider `ultradns` connects by default to `https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01` and supports `password` authentication, which needs two `-c` parameters: `username` and `password`. To put it together, you'd specify the following to do a zone list:
```bash
Expand All @@ -108,8 +109,11 @@ If you need to connect to an alternate url, pass the `-u` parameter:
./denominator -p ultradns -u https://ultra-api.ultradns.com:8443/UltraDNS_WS/v01-BETA -c myusername -c mypassword zone list
```

The `zoneIds` column indicates how the provider addresses zones. For example, `name` means you simply
pass the zone name (like `denominator.io.`) to commands that need it. This topic is further described in the `Zone Identifiers` section to follow.

### Zone
`zone list` returns the zones names in your account. Ex.
`zone list` returns the zones in your account. Ex.
```bash
$ denominator -p ultradns -c my_user -c my_password zone list
--snip--
Expand All @@ -126,6 +130,23 @@ email.netflix.com. A 3600 69.53.237.168
--snip--
```

### Zone Identifiers
When the provider doesn't use name-based zone identification, the last column zone list is the `id`.
This can be used to disambiguate zones with the same name, or to improve performance by eliminating a network call.
If you wish to use a zone's identifier, simply pass it instead of the zone name in record commands.

```bash
$ denominator -p route53 -c my_access_key -c my_secret_key zone list -n denominator.io.
[Route53#listHostedZonesByName] ---> GET https://route53.amazonaws.com/2013-04-01/hostedzonesbyname?dnsname=denominator.io. HTTP/1.1
[Route53#listHostedZonesByName] <--- HTTP/1.1 200 OK (678ms)
denominator.io. 63CEB242-9E3E-327D-9351-2EFD02493E18 Z2ZEEJCUZCVG56
denominator.io. 022DFF2F-2E0B-F0A2-A581-19339A7BA1F1 Z3OQLQGABCU3T
$ denominator -p route53 -c my_access_key -c my_secret_key record -z Z3OQLQGABCU3T list
--snip--
denominator.io. NS 172800 ns-1312.awsdns-36.org.
--snip--
```

### Geo
#### Show Supported Geo Regions
`denominator geo regions` returns all supported regions for the zone specified.
Expand Down
56 changes: 31 additions & 25 deletions cli/src/main/java/denominator/cli/Denominator.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
Expand Down Expand Up @@ -63,6 +62,7 @@
import denominator.cli.ResourceRecordSetCommands.ResourceRecordSetRemove;
import denominator.cli.ResourceRecordSetCommands.ResourceRecordSetReplace;
import denominator.model.Zone;
import denominator.model.Zone.Identification;
import feign.Logger;
import feign.Logger.Level;
import io.airlift.airline.Cli;
Expand Down Expand Up @@ -172,20 +172,13 @@ static Object logModule(boolean quiet, boolean verbose) {
return new LogModule(logLevel);
}

static String idOrName(DNSApiManager mgr, String zoneIdOrName) {
if (!InternetDomainName.isValid(zoneIdOrName) || !InternetDomainName.from(zoneIdOrName)
.hasParent()) {
static String id(DNSApiManager mgr, String zoneIdOrName) {
if (mgr.provider().zoneIdentification() == Identification.NAME) {
return zoneIdOrName;
} else if (InternetDomainName.isValid(zoneIdOrName) && mgr.provider()
.supportsDuplicateZoneNames()) {
List<Zone> currentZones = new ArrayList<Zone>();
for (Zone zone : mgr.api().zones()) {
if (zoneIdOrName.equals(zone.name())) {
return zone.id();
}
currentZones.add(zone);
}
checkArgument(false, "zone %s not found in %s", zoneIdOrName, currentZones);
} else if (zoneIdOrName.indexOf('.') != -1 && InternetDomainName.isValid(zoneIdOrName)) {
Iterator<Zone> result = mgr.api().zones().iterateByName(zoneIdOrName);
checkArgument(result.hasNext(), "zone %s not found", zoneIdOrName);
return result.next().id();
}
return zoneIdOrName;
}
Expand All @@ -202,24 +195,25 @@ public void run() {
@Command(name = "providers", description = "List the providers and their metadata ")
public static class ListProviders implements Runnable {

final static String table = "%-10s %-51s %-14s %-14s %s%n";
final static String table = "%-10s %-51s %-9s %-14s %s%n";

public static String providerAndCredentialsTable() {
StringBuilder builder = new StringBuilder();

builder.append(
format(table, "provider", "url", "duplicateZones", "credentialType", "credentialArgs"));
format(table, "provider", "url", "zoneIds", "credentialType", "credentialArgs"));
for (Provider provider : ImmutableSortedSet
.copyOf(Ordering.usingToString(), Providers.list())) {
if (provider.credentialTypeToParameterNames().isEmpty()) {
builder.append(format("%-10s %-51s %-14s %n", provider.name(), provider.url(),
provider.supportsDuplicateZoneNames()));
provider.zoneIdentification().toString().toLowerCase()));
}
for (Entry<String, Collection<String>> entry : provider.credentialTypeToParameterNames()
.entrySet()) {
String parameters = Joiner.on(' ').join(entry.getValue());
builder.append(format(table, provider.name(), provider.url(),
provider.supportsDuplicateZoneNames(), entry.getKey(), parameters));
provider.zoneIdentification().toString().toLowerCase(),
entry.getKey(), parameters));
}
}
return builder.toString();
Expand Down Expand Up @@ -378,20 +372,32 @@ void overrideFromEnv(Map<String, String> env) {
protected abstract Iterator<String> doRun(DNSApiManager mgr);
}

@Command(name = "list", description = "Lists the zones present in this provider. If the second column is present, it is the zone id.")
@Command(name = "list", description = "Lists the zones present in this provider. If more than one column is present, the last is the zone id.")
public static class ZoneList extends DenominatorCommand {

public Iterator<String> doRun(DNSApiManager mgr) {
return Iterators.transform(mgr.api().zones().iterator(), new Function<Zone, String>() {
@Option(type = OptionType.COMMAND, name = {"-n",
"--name"}, description = "name of the zone. ex. denominator.io.")
public String name;

public Iterator<String> doRun(final DNSApiManager mgr) {
Iterator<Zone> zones =
name == null ? mgr.api().zones().iterator() : mgr.api().zones().iterateByName(name);
return Iterators.transform(zones, new Function<Zone, String>() {

@Override
public String apply(Zone input) {
if (input.id() != null) {
return input.name() + " " + input.id();
Identification zoneId = mgr.provider().zoneIdentification();
switch (zoneId) {
case NAME:
return input.name();
case OPAQUE:
return format("%-36s %s", input.name(), input.id());
case QUALIFIED:
return format("%-36s %-19s %s", input.name(), input.qualifier(), input.id());
default:
throw new UnsupportedOperationException("unsupported zone identification: " + zoneId);
}
return input.name();
}

});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import static com.google.common.collect.Iterators.forArray;
import static com.google.common.collect.Iterators.singletonIterator;
import static com.google.common.collect.Iterators.transform;
import static denominator.cli.Denominator.idOrName;
import static denominator.cli.Denominator.id;
import static denominator.cli.Denominator.json;
import static denominator.common.Util.join;
import static denominator.model.profile.Geos.withAdditionalRegions;
Expand Down Expand Up @@ -99,7 +99,7 @@ public static class GeoRegionList extends GeoResourceRecordSetCommand {
@Override
protected Iterator<String> doRun(DNSApiManager mgr) {
return FluentIterable
.from(mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName)).supportedRegions()
.from(mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName)).supportedRegions()
.entrySet())
.transform(new Function<Map.Entry<String, Collection<String>>, String>() {
@Override
Expand All @@ -125,13 +125,13 @@ public Iterator<String> doRun(DNSApiManager mgr) {
Iterator<ResourceRecordSet<?>> iterator;
if (name != null && type != null) {
iterator =
mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName))
mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName))
.iterateByNameAndType(name, type);
}
if (name != null) {
iterator = mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName)).iterateByName(name);
iterator = mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName)).iterateByName(name);
} else {
iterator = mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName)).iterator();
iterator = mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName)).iterator();
}
return transform(iterator, GeoResourceRecordSetToString.INSTANCE);
}
Expand All @@ -153,7 +153,7 @@ public static class GeoResourceRecordSetGet extends GeoResourceRecordSetCommand
public String group;

public Iterator<String> doRun(DNSApiManager mgr) {
GeoResourceRecordSetApi api = mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName));
GeoResourceRecordSetApi api = mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName));
ResourceRecordSet<?> rrs = api.getByNameTypeAndQualifier(name, type, group);
return rrs != null ? singletonIterator(GeoResourceRecordSetToString.INSTANCE.apply(rrs))
: singletonIterator("");
Expand Down Expand Up @@ -193,7 +193,7 @@ public boolean hasNext() {

@Override
public String next() {
GeoResourceRecordSetApi api = mgr.api().geoRecordSetsInZone(idOrName(mgr, zoneIdOrName));
GeoResourceRecordSetApi api = mgr.api().geoRecordSetsInZone(id(mgr, zoneIdOrName));
ResourceRecordSet<?> rrs = api.getByNameTypeAndQualifier(name, type, group);
if (rrs != null &&
(rrs.ttl() == null || (rrs.ttl() != null && rrs.ttl().intValue() != ttl))) {
Expand Down Expand Up @@ -248,7 +248,7 @@ public static class GeoResourceRecordAddRegions extends GeoResourceRecordSetComm
public Iterator<String> doRun(final DNSApiManager mgr) {
checkArgument(!regions.isEmpty(), "specify regions to apply");
// resolve into a concrete zone id or name.
zoneIdOrName = idOrName(mgr, zoneIdOrName);
zoneIdOrName = id(mgr, zoneIdOrName);
final GeoResourceRecordSetApi api = mgr.api().geoRecordSetsInZone(zoneIdOrName);
checkArgument(api != null, "geo api not available on provider %s, zone %s", mgr.provider(),
zoneIdOrName);
Expand Down
16 changes: 8 additions & 8 deletions cli/src/main/java/denominator/cli/ResourceRecordSetCommands.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import static com.google.common.collect.Iterators.forArray;
import static com.google.common.collect.Iterators.singletonIterator;
import static com.google.common.collect.Iterators.transform;
import static denominator.cli.Denominator.idOrName;
import static denominator.cli.Denominator.id;
import static denominator.common.Util.join;
import static java.lang.String.format;

Expand Down Expand Up @@ -74,7 +74,7 @@ public static class ResourceRecordSetList extends ResourceRecordSetCommand {

public Iterator<String> doRun(DNSApiManager mgr) {
Iterator<ResourceRecordSet<?>> iterator;
String zoneId = idOrName(mgr, zoneIdOrName);
String zoneId = id(mgr, zoneIdOrName);
if (type != null) {
iterator = mgr.api().recordSetsInZone(zoneId).iterateByNameAndType(name, type);
} else if (name != null) {
Expand Down Expand Up @@ -103,7 +103,7 @@ public static class ResourceRecordSetGet extends ResourceRecordSetCommand {

public Iterator<String> doRun(DNSApiManager mgr) {
ResourceRecordSet<?> rrs;
String zoneId = idOrName(mgr, zoneIdOrName);
String zoneId = id(mgr, zoneIdOrName);
if (qualifier != null) {
rrs = mgr.api().recordSetsInZone(zoneId).getByNameTypeAndQualifier(name, type, qualifier);
} else {
Expand Down Expand Up @@ -142,7 +142,7 @@ public boolean hasNext() {

@Override
public String next() {
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(idOrName(mgr, zoneIdOrName));
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(id(mgr, zoneIdOrName));
ResourceRecordSet<?> rrs = api.getByNameAndType(name, type);
if (rrs != null && rrs.ttl() != null && rrs.ttl().intValue() != ttl) {
api.put(ResourceRecordSet.builder()
Expand Down Expand Up @@ -295,7 +295,7 @@ public boolean hasNext() {

@Override
public String next() {
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(idOrName(mgr, zoneIdOrName));
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(id(mgr, zoneIdOrName));
ResourceRecordSet<?> rrset = api.getByNameAndType(name, type);
if (rrset != null) {
Set<Map<String, Object>> data = new LinkedHashSet<Map<String, Object>>(rrset.records());
Expand Down Expand Up @@ -350,7 +350,7 @@ public boolean hasNext() {

@Override
public String next() {
mgr.api().basicRecordSetsInZone(idOrName(mgr, zoneIdOrName)).put(toAdd);
mgr.api().basicRecordSetsInZone(id(mgr, zoneIdOrName)).put(toAdd);
done = true;
return ";; ok";
}
Expand Down Expand Up @@ -382,7 +382,7 @@ public boolean hasNext() {

@Override
public String next() {
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(idOrName(mgr, zoneIdOrName));
ResourceRecordSetApi api = mgr.api().basicRecordSetsInZone(id(mgr, zoneIdOrName));
ResourceRecordSet<?> rrset = api.getByNameAndType(name, type);
if (rrset != null) {
Set<Map<String, Object>> data = new LinkedHashSet<Map<String, Object>>(rrset.records());
Expand Down Expand Up @@ -433,7 +433,7 @@ public boolean hasNext() {

@Override
public String next() {
mgr.api().basicRecordSetsInZone(idOrName(mgr, zoneIdOrName))
mgr.api().basicRecordSetsInZone(id(mgr, zoneIdOrName))
.deleteByNameAndType(name, type);
done = true;
return ";; ok";
Expand Down
Loading

0 comments on commit d435178

Please sign in to comment.