Skip to content

Commit

Permalink
Adds ZoneApi.put ZoneApi.delete used by CLI zone add, update and replace
Browse files Browse the repository at this point in the history
Adds Zone write support and backfills functionality for all supported
providers. Adds usage notes to README files.

closes #264
  • Loading branch information
Adrian Cole committed Apr 6, 2015
1 parent 08bb321 commit f7169d0
Show file tree
Hide file tree
Showing 58 changed files with 2,099 additions and 186 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
### Version 4.5
* Adds `ZoneApi.put()` and `ZoneApi.delete()`
* Adds zone add, update and delete to the CLI
* 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
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

# Portable control of DNS clouds

Denominator is a portable Java library for manipulating DNS clouds. Denominator has pluggable back-ends, including AWS Route53, Neustar Ultra, DynECT, Rackspace Cloud DNS, OpenStack Designate, and a mock for testing. We also ship a command line version so it's easy for anyone to try it out. Denominator currently supports basic zone and record features, as well GEO (aka Directional) profiles. See [Netflix Tech Blog](http://techblog.netflix.com/2013/03/denominator-multi-vendor-interface-for.html) for an introduction.
Denominator is a portable Java library for manipulating DNS clouds. Denominator has pluggable back-ends, including AWS Route53, Neustar Ultra, DynECT, Rackspace Cloud DNS, OpenStack Designate, and a mock for testing. We also ship a command line version so it's easy for anyone to try it out. Denominator currently supports basic zone and record features, as well GEO (aka Directional) profiles. See [Netflix Tech Blog](http://techblog.netflix.com/2013/03/denominator-multi-vendor-interface-for.html) for an introduction.

[![Build Status](https://netflixoss.ci.cloudbees.com/job/denominator-master/badge/icon)](https://netflixoss.ci.cloudbees.com/job/denominator-master/)

## Command line
For your convenience, the denominator cli is a [single executable file](http://skife.org/java/unix/2011/06/20/really_executable_jars.html). Under the hood, the cli uses [airline](https://github.com/airlift/airline) to look and feel like dig or git.
For your convenience, the denominator cli is a [single executable file](http://skife.org/java/unix/2011/06/20/really_executable_jars.html). Under the hood, the cli uses [airline](https://github.com/airlift/airline) to look and feel like dig or git.

## Android
There's no official android client, yet. However, we do have an [Android Example](https://github.com/Netflix/denominator/tree/master/example-android) you can try out.
There's no official android client, yet. However, we do have an [Android Example](https://github.com/Netflix/denominator/tree/master/example-android) you can try out.

### Binaries
If you are using OSX, [Homebrew](http://mxcl.github.io/homebrew/) has a built-in installer for denominator.
Expand All @@ -23,7 +23,7 @@ Here's how to get denominator-cli `4.4.2` from [bintray](https://bintray.com/pkg

### Getting Started

Advanced usage, including ec2 hooks are covered in the [readme](https://github.com/Netflix/denominator/tree/master/cli). Here's a quick start for the impatient.
Advanced usage, including ec2 hooks are covered in the [readme](https://github.com/Netflix/denominator/tree/master/cli). Here's a quick start for the impatient.

If you just want to fool around, you can use the `mock` provider.
```bash
Expand All @@ -35,7 +35,7 @@ denominator.io. SOA 3600 ns1.denominator.
denominator.io. NS 86400 ns1.denominator.io.
```

Different providers connect to different urls and need different credentials. First step is to run `./denominator providers` to see how many `-c` args you need and what values they should have:
Different providers connect to different urls and need different credentials. First step is to run `./denominator providers` to see how many `-c` args you need and what values they should have:

```bash
$ denominator providers
Expand Down Expand Up @@ -64,13 +64,13 @@ email.netflix.com. A 3600 192.0.2.1

## Code

Denominator exposes a portable [model](https://github.com/Netflix/denominator/wiki/Models) implemented by pluggable `Provider`s such as `route53`, `ultradns`, `dynect`, `clouddns`, or `mock`. Under the covers, providers are [Dagger](http://square.github.com/dagger/) modules. Except for the mock, all current providers bind to http requests via [feign](https://github.com/Netflix/feign), which keeps the distribution light.
Denominator exposes a portable [model](https://github.com/Netflix/denominator/wiki/Models) implemented by pluggable `Provider`s such as `route53`, `ultradns`, `dynect`, `clouddns`, or `mock`. Under the covers, providers are [Dagger](http://square.github.com/dagger/) modules. Except for the mock, all current providers bind to http requests via [feign](https://github.com/Netflix/feign), which keeps the distribution light.

### Binaries

The current version of denominator is `4.4.2`

Denominator can be resolved as maven dependencies, or equivalent in lein, gradle, etc. Here are the coordinates, noting you only need to list the providers you use.
Denominator can be resolved as maven dependencies, or equivalent in lein, gradle, etc. Here are the coordinates, noting you only need to list the providers you use.
```xml
<dependencies>
<dependency>
Expand Down Expand Up @@ -109,7 +109,7 @@ import static denominator.CredentialsConfiguration.credentials;
...
DNSApiManager manager = Denominator.create("ultradns", credentials(username, password)); // manager is closeable, so please close it!
```
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:
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.id())) {
Expand All @@ -123,16 +123,18 @@ If you are running unit tests, or don't have a cloud account yet, you can use mo
DNSApiManager manager = Denominator.create("mock");
```

The Denominator [model](https://github.com/Netflix/denominator/wiki/Model) is based on the `ResourceRecordSet` concept. A `ResourceRecordSet` is simply a group of records who share the same name and type. For example all address (`A`) records for the name `www.netflix.com.` are aggregated into the same `ResourceRecordSet`. The values of each record in a set are type-specific. These data types are implemented as map-backed interfaces. This affords both the strong typing of java and extensibility and versatility of maps.
The Denominator [model](https://github.com/Netflix/denominator/wiki/Model) is based on the `ResourceRecordSet` concept. A `ResourceRecordSet` is simply a group of records who share the same name and type. For example all address (`A`) records for the name `www.netflix.com.` are aggregated into the same `ResourceRecordSet`. The values of each record in a set are type-specific. These data types are implemented as map-backed interfaces. This affords both the strong typing of java and extensibility and versatility of maps.

For example, the following are identical:
```java
mxData.getPreference();
mxData.get("preference");
```

Resource record sets live in a Zone, which is roughly analogous to a domain. Zones can be created on-demand and deleted.

### Use via Dagger
Some users may wish to use Denominator as a Dagger library. Here's one way to achieve that:
Some users may wish to use Denominator as a Dagger library. Here's one way to achieve that:
```java
import static denominator.CredentialsConfiguration.credentials;
import static denominator.Dagger.provider;
Expand Down
41 changes: 15 additions & 26 deletions cli/src/main/java/denominator/cli/Denominator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Ordering;
import com.google.common.io.Files;
import com.google.gson.Gson;
Expand Down Expand Up @@ -60,6 +58,10 @@
import denominator.cli.ResourceRecordSetCommands.ResourceRecordSetList;
import denominator.cli.ResourceRecordSetCommands.ResourceRecordSetRemove;
import denominator.cli.ResourceRecordSetCommands.ResourceRecordSetReplace;
import denominator.cli.ZoneCommands.ZoneAdd;
import denominator.cli.ZoneCommands.ZoneDelete;
import denominator.cli.ZoneCommands.ZoneList;
import denominator.cli.ZoneCommands.ZoneUpdate;
import denominator.dynect.DynECTProvider;
import denominator.model.Zone;
import denominator.ultradns.UltraDNSProvider;
Expand Down Expand Up @@ -120,7 +122,10 @@ public static void main(String[] args) {
builder.withGroup("zone")
.withDescription("manage zones")
.withDefaultCommand(ZoneList.class)
.withCommand(ZoneList.class);
.withCommand(ZoneList.class)
.withCommand(ZoneAdd.class)
.withCommand(ZoneUpdate.class)
.withCommand(ZoneDelete.class);

builder.withGroup("record")
.withDescription("manage resource record sets in a zone")
Expand Down Expand Up @@ -176,16 +181,20 @@ static String id(DNSApiManager mgr, String zoneIdOrName) {
if (zoneIdOrName.indexOf('.') == -1) { // Assume that ids don't have dots in them!
return zoneIdOrName;
}
// Special-case providers known to use zone names as ids, as this usually saves 1-200ms of
// lookups. We can later introduce a flag or other means to help third-party providers.
if (mgr.provider() instanceof UltraDNSProvider || mgr.provider() instanceof DynECTProvider) {
if (zoneNameIsId(mgr.provider())) {
return zoneIdOrName;
}
Iterator<Zone> result = mgr.api().zones().iterateByName(zoneIdOrName);
checkArgument(result.hasNext(), "zone %s not found", zoneIdOrName);
return result.next().id();
}

// Special-case providers known to use zone names as ids, as this usually saves 1-200ms of
// lookups. We can later introduce a flag or other means to help third-party providers.
static boolean zoneNameIsId(Provider provider) {
return provider instanceof UltraDNSProvider || provider instanceof DynECTProvider;
}

@Command(name = "version", description = "output the version of denominator and java runtime in use")
public static class PrintVersion implements Runnable {

Expand Down Expand Up @@ -372,26 +381,6 @@ void overrideFromEnv(Map<String, String> env) {
protected abstract Iterator<String> doRun(DNSApiManager mgr);
}

@Command(name = "list", description = "Lists the zones present in this provider. The zone id is the first column.")
public static class ZoneList extends DenominatorCommand {

@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) {
return format("%-24s %-36s %-36s %d", input.id(), input.name(), input.email(),
input.ttl());
}
});
}
}

@dagger.Module(overrides = true, library = true)
static class LogModule {

Expand Down
193 changes: 193 additions & 0 deletions cli/src/main/java/denominator/cli/ZoneCommands.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package denominator.cli;

import com.google.common.base.Function;
import com.google.common.collect.Iterators;

import java.util.Iterator;

import denominator.DNSApiManager;
import denominator.cli.Denominator.DenominatorCommand;
import denominator.model.Zone;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import io.airlift.airline.OptionType;

import static com.google.common.collect.Iterators.concat;
import static com.google.common.collect.Iterators.forArray;
import static java.lang.String.format;

class ZoneCommands {

@Command(name = "list", description = "Lists the zones present in this provider. The zone id is the first column.")
public static class ZoneList extends DenominatorCommand {

@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) {
return format("%-24s %-36s %-36s %d", input.id(), input.name(), input.email(),
input.ttl());
}
});
}
}

@Command(name = "add", description =
"Adds a zone or updates the first existing zone with the same name, outputs the result's id.\n"
+ "Note: This may create a duplicate zone if the provider supports it.")
public static class ZoneAdd extends DenominatorCommand {

@Option(type = OptionType.COMMAND, required = true, name = {"-n",
"--name"}, description = "name of the zone. ex. denominator.io.")
public String name;

@Option(type = OptionType.COMMAND, name = {"-t",
"--ttl"}, description = "time to live of the zone (SOA). defaults to 86400")
public int ttl = 86400;

@Option(type = OptionType.COMMAND, required = true, name = {"-e",
"--email"}, description = "Email contact for the zone. ex. admin@denominator.io")
public String email;

public Iterator<String> doRun(final DNSApiManager mgr) {
final Zone zone = Zone.create(null, name, ttl, email);
return new Iterator<String>() {
boolean printed = false;
boolean replaced = false;
boolean done = false;

@Override
public boolean hasNext() {
return !done;
}

@Override
public String next() {
if (!printed) {
printed = true;
String context = zone.name() + (zone.id() == null ? "" : " [" + zone.id() + "]");
return format(";; adding zone %s with ttl %d and email %s", context, ttl, email);
} else if (!replaced) {
replaced = true;
return mgr.api().zones().put(zone);
} else {
done = true;
return ";; ok";
}
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

@Command(name = "update", description = "Updates an existing zone with the specified ttl and/or email.")
public static class ZoneUpdate extends DenominatorCommand {

@Option(type = OptionType.COMMAND, required = true, name = {"-i",
"--id"}, description = "id of the zone.")
public String id;

@Option(type = OptionType.COMMAND, name = {"-t",
"--ttl"}, description = "time to live of the zone (SOA)")
public Integer ttl;

@Option(type = OptionType.COMMAND, name = {"-e",
"--email"}, description = "Email contact for the zone. ex. nil@denominator.io")
public String email;

public Iterator<String> doRun(final DNSApiManager mgr) {
final Zone existing = getZone(mgr, id);
final Zone update = Zone.create(id, existing.name(), ttl != null ? ttl : existing.ttl(),
email != null ? email : existing.email());
if (existing.equals(update)) {
return forArray(";; ok");
}
return new Iterator<String>() {
boolean printed = false;
boolean done = false;

@Override
public boolean hasNext() {
return !done;
}

@Override
public String next() {
if (!printed) {
printed = true;
StringBuilder result = new StringBuilder();
result.append(";; updating zone ").append(existing.id()).append(" with ");
if (ttl != null && email != null) {
result.append("ttl ").append(ttl).append(" and ").append("email ").append(email);
} else if (ttl != null) {
result.append("ttl ").append(ttl);
} else {
result.append("email ").append(email);
}
return result.toString();
} else {
done = true;
mgr.api().zones().put(update);
return ";; ok";
}
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}

static Zone getZone(DNSApiManager mgr, String id) {
for (Zone zone : mgr.api().zones()) {
if (zone.id().equals(id)) {
return zone; // TODO: consider getById
}
}
throw new IllegalArgumentException("zone " + id + " not found");
}

@Command(name = "delete", description = "deletes a zone by id")
public static class ZoneDelete extends DenominatorCommand {

@Option(type = OptionType.COMMAND, required = true, name = {"-i",
"--id"}, description = "id of the zone.")
public String id;

public Iterator<String> doRun(final DNSApiManager mgr) {
String cmd = format(";; deleting zone %s", id);
return concat(forArray(cmd), new Iterator<String>() {
boolean done = false;

@Override
public boolean hasNext() {
return !done;
}

@Override
public String next() {
mgr.api().zones().delete(id);
done = true;
return ";; ok";
}

@Override
public void remove() {
throw new UnsupportedOperationException();
}
});
}
}
}
Loading

0 comments on commit f7169d0

Please sign in to comment.