/*
* Copyright 2016 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.dns;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.transform;
import com.google.api.client.util.Data;
import com.google.api.services.dns.model.DnsKeySpec;
import com.google.api.services.dns.model.ManagedZone;
import com.google.api.services.dns.model.ManagedZoneDnsSecConfig;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.threeten.bp.Instant;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.format.DateTimeFormatter;
/**
* A {@code Zone} represents a DNS zone hosted by the Google Cloud DNS service. A zone is a subtree
* of the DNS namespace under one administrative responsibility. See Google Cloud DNS documentation for
* more information.
*/
public class ZoneInfo implements Serializable {
private static final long serialVersionUID = -5313169712036079818L;
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneOffset.UTC);
private final String name;
private final String generatedId;
private final Long creationTimeMillis;
private final String dnsName;
private final String description;
private final String nameServerSet;
private final List nameServers;
private final DnsSecConfig dnsSecConfig;
private final Map labels;
/** This class represents the DNS key spec. */
public static class KeySpec {
private String algorithm;
private Long keyLength;
private String keyType;
public static class Builder {
private String algorithm;
private Long keyLength;
private String keyType;
private Builder() {}
private Builder(KeySpec keySpec) {
this.algorithm = keySpec.algorithm;
this.keyLength = keySpec.keyLength;
this.keyType = keySpec.getKeyType();
}
/** Specifies the DNSSEC algorithm of this key. */
public Builder setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
/** Specifies the length of the keys in bits. */
public Builder setKeyLength(Long keyLength) {
this.keyLength = keyLength;
return this;
}
/**
* Specifies the key type, Whether this key is a signing key (KSK) or a zone signing key
* (ZSK).
*/
public Builder setKeyType(String keyType) {
this.keyType = keyType;
return this;
}
/** Creates a {@code KeySpec} object. */
public KeySpec build() {
return new KeySpec(this);
}
}
/** Returns a builder for {@code KeySpec} objects. */
public static Builder newBuilder() {
return new Builder();
}
private KeySpec(Builder builder) {
algorithm = builder.algorithm;
keyLength = builder.keyLength;
keyType = builder.keyType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
KeySpec keySpec = (KeySpec) o;
return Objects.equals(algorithm, keySpec.algorithm)
&& Objects.equals(keyLength, keySpec.keyLength)
&& Objects.equals(keyType, keySpec.keyType);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("algorithm", getAlgorithm())
.add("keyLength", getKeyLength())
.add("keyType", getKeyType())
.toString();
}
@Override
public int hashCode() {
return Objects.hash(algorithm, keyLength, keyType);
}
DnsKeySpec toPb() {
DnsKeySpec dnsKeySpecPb = new DnsKeySpec();
dnsKeySpecPb.setAlgorithm(algorithm);
dnsKeySpecPb.setKeyLength(keyLength);
dnsKeySpecPb.setKeyType(keyType);
return dnsKeySpecPb;
}
static KeySpec fromPb(DnsKeySpec dnsKeySpec) {
Builder builder = newBuilder();
builder.setAlgorithm(dnsKeySpec.getAlgorithm() == null ? null : dnsKeySpec.getAlgorithm());
builder.setKeyLength(dnsKeySpec.getKeyLength() == null ? null : dnsKeySpec.getKeyLength());
builder.setKeyType(dnsKeySpec.getKeyType() == null ? null : dnsKeySpec.getKeyType());
return builder.build();
}
/** Returns the DNSSEC algorithm of this key. */
public String getAlgorithm() {
return algorithm;
}
/** Returns the key length. */
public Long getKeyLength() {
return keyLength;
}
/** Returns the key type. */
public String getKeyType() {
return keyType;
}
}
/** This class represents the DNSSEC configuration. */
public static class DnsSecConfig {
private static final Set VALID_STATE_VALUES = ImmutableSet.of("on", "off", "transfer");
private static final Set VALID_NONEXISTANCE_VALUES = ImmutableSet.of("nsec", "nsec3");
private List defaultKeySpecs;
private String nonExistence;
private String state;
public static class Builder {
private List defaultKeySpecs;
private String nonExistence;
private String state;
private Builder() {}
private Builder(DnsSecConfig dnsSecConfig) {
this.defaultKeySpecs = dnsSecConfig.defaultKeySpecs;
this.nonExistence = dnsSecConfig.nonExistence;
this.state = dnsSecConfig.state;
}
/**
* Specifies parameters for generating initial DnsKeys for this ManagedZone. This can be
* change while state is OFF.
*/
public Builder setDefaultKeySpecs(List defaultKeySpecs) {
this.defaultKeySpecs = defaultKeySpecs;
return this;
}
/**
* Specifies the mechanism for authenticated denial-of-existence responses. This can be change
* while state is OFF. Acceptable values are 'nsec' or 'nsec3'.
*
* @throws IllegalArgumentException if nonExistence value is not acceptable
*/
public Builder setNonExistence(String nonExistence) {
validateValue(nonExistence, VALID_NONEXISTANCE_VALUES);
this.nonExistence = nonExistence;
return this;
}
/**
* Specifies whether DNSSEC is enabled, and what mode it is in. Acceptable values are 'on',
* 'off' or 'transfer'.
*
* @throws IllegalArgumentException if state value is not acceptable
*/
public Builder setState(String state) {
validateValue(state, VALID_STATE_VALUES);
this.state = state;
return this;
}
/** Creates a {@code DnsSecConfig} object. */
public DnsSecConfig build() {
return new DnsSecConfig(this);
}
}
/** Returns a builder for the current blob. */
public Builder toBuilder() {
return new Builder(this);
}
/** Returns a builder for {@code DnsSecConfig} objects. */
public static Builder newBuilder() {
return new Builder();
}
private DnsSecConfig(Builder builder) {
defaultKeySpecs = builder.defaultKeySpecs;
nonExistence = builder.nonExistence;
state = builder.state;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DnsSecConfig that = (DnsSecConfig) o;
return Objects.equals(defaultKeySpecs, that.defaultKeySpecs)
&& Objects.equals(nonExistence, that.nonExistence)
&& Objects.equals(state, that.state);
}
@Override
public int hashCode() {
return Objects.hash(defaultKeySpecs, nonExistence, state);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("defaultKeySpecs", getDefaultKeySpecs())
.add("nonExistence", getNonExistence())
.add("state", getState())
.toString();
}
ManagedZoneDnsSecConfig toPb() {
ManagedZoneDnsSecConfig dnsSecConfigPb = new ManagedZoneDnsSecConfig();
if (defaultKeySpecs != null) {
dnsSecConfigPb.setDefaultKeySpecs(
transform(
defaultKeySpecs,
new Function() {
@Override
public DnsKeySpec apply(KeySpec keySpec) {
return keySpec.toPb();
}
}));
}
dnsSecConfigPb.setNonExistence(nonExistence);
dnsSecConfigPb.setState(state);
return dnsSecConfigPb;
}
static DnsSecConfig fromPb(ManagedZoneDnsSecConfig managedZoneDnsSecConfig) {
Builder builder = newBuilder();
if (managedZoneDnsSecConfig.getDefaultKeySpecs() != null) {
builder.setDefaultKeySpecs(
transform(
managedZoneDnsSecConfig.getDefaultKeySpecs(),
new Function() {
@Override
public KeySpec apply(DnsKeySpec dnsKeySpec) {
return KeySpec.fromPb(dnsKeySpec);
}
}));
}
builder.setNonExistence(managedZoneDnsSecConfig.getNonExistence());
builder.setState(managedZoneDnsSecConfig.getState());
return builder.build();
}
/** Returns the DefaultKeySpecs. */
public List getDefaultKeySpecs() {
return defaultKeySpecs;
}
/** Returns the authenticated denial-of-existence responses. */
public String getNonExistence() {
return nonExistence;
}
/** Returns the DNSSEC state. */
public String getState() {
return state;
}
private static void validateValue(String value, Set validValues) {
if (!validValues.contains(value)) {
throw new IllegalArgumentException(
"Invalid value, Use one of the value from acceptable values " + validValues);
}
}
}
/** Builder for {@code ZoneInfo}. */
public abstract static class Builder {
/** Sets a mandatory user-provided name for the zone. It must be unique within the project. */
public abstract Builder setName(String name);
/** Sets service-generated id for the zone. */
abstract Builder setGeneratedId(String generatedId);
/** Sets the time when this zone was created. */
abstract Builder setCreationTimeMillis(long creationTimeMillis);
/** Sets a mandatory DNS name of this zone, for instance "example.com.". */
public abstract Builder setDnsName(String dnsName);
/**
* Sets a mandatory description for this zone. The value is a string of at most 1024 characters
* which has no effect on the zone's function.
*/
public abstract Builder setDescription(String description);
/**
* Optionally specifies the NameServerSet for this zone. A NameServerSet is a set of DNS name
* servers that all host the same zones. Most users will not need to specify this value.
*/
abstract Builder setNameServerSet(String nameServerSet);
// this should not be included in tooling as per the service owners
/**
* Sets a list of servers that hold the information about the zone. This information is provided
* by Google Cloud DNS and is read only.
*/
abstract Builder setNameServers(List nameServers);
/** Sets the DNSSEC configuration. */
public Builder setDnsSecConfig(DnsSecConfig dnsSecConfig) {
return this;
}
/** Sets the label of this zone. */
public Builder setLabels(Map labels) {
return this;
}
/** Builds the instance of {@code ZoneInfo} based on the information set by this builder. */
public abstract ZoneInfo build();
}
static class BuilderImpl extends Builder {
private String name;
private String generatedId;
private Long creationTimeMillis;
private String dnsName;
private String description;
private String nameServerSet;
private List nameServers;
private DnsSecConfig dnsSecConfig;
private Map labels;
private BuilderImpl(String name) {
this.name = checkNotNull(name);
}
/** Creates a builder from an existing ZoneInfo object. */
BuilderImpl(ZoneInfo info) {
this.name = info.name;
this.generatedId = info.generatedId;
this.creationTimeMillis = info.creationTimeMillis;
this.dnsName = info.dnsName;
this.description = info.description;
this.nameServerSet = info.nameServerSet;
if (info.nameServers != null) {
this.nameServers = ImmutableList.copyOf(info.nameServers);
}
this.dnsSecConfig = info.dnsSecConfig;
this.labels = info.labels;
}
@Override
public Builder setName(String name) {
this.name = checkNotNull(name);
return this;
}
@Override
Builder setGeneratedId(String generatedId) {
this.generatedId = generatedId;
return this;
}
@Override
Builder setCreationTimeMillis(long creationTimeMillis) {
this.creationTimeMillis = creationTimeMillis;
return this;
}
@Override
public Builder setDnsName(String dnsName) {
this.dnsName = checkNotNull(dnsName);
return this;
}
@Override
public Builder setDescription(String description) {
this.description = checkNotNull(description);
return this;
}
@Override
Builder setNameServerSet(String nameServerSet) {
this.nameServerSet = checkNotNull(nameServerSet);
return this;
}
@Override
Builder setNameServers(List nameServers) {
checkNotNull(nameServers);
this.nameServers = Lists.newLinkedList(nameServers);
return this;
}
@Override
public Builder setDnsSecConfig(DnsSecConfig dnsSecConfig) {
this.dnsSecConfig = checkNotNull(dnsSecConfig);
return this;
}
@Override
public Builder setLabels(Map labels) {
if (labels != null) {
this.labels =
Maps.transformValues(
labels,
new Function() {
@Override
public String apply(String input) {
// replace null values with empty strings
return input == null ? Data.nullOf(String.class) : input;
}
});
}
return this;
}
@Override
public ZoneInfo build() {
return new ZoneInfo(this);
}
}
ZoneInfo(BuilderImpl builder) {
this.name = builder.name;
this.generatedId = builder.generatedId;
this.creationTimeMillis = builder.creationTimeMillis;
this.dnsName = builder.dnsName;
this.description = builder.description;
this.nameServerSet = builder.nameServerSet;
this.nameServers =
builder.nameServers == null ? null : ImmutableList.copyOf(builder.nameServers);
this.dnsSecConfig = builder.dnsSecConfig;
this.labels = builder.labels;
}
/**
* Returns a ZoneInfo object with assigned {@code name}, {@code dnsName} and {@code description}.
*/
public static ZoneInfo of(String name, String dnsName, String description) {
return new BuilderImpl(name).setDnsName(dnsName).setDescription(description).build();
}
/** Returns a {@code ZoneInfo} builder where the DNS name is set to the provided name. */
public static Builder newBuilder(String name) {
return new BuilderImpl(name);
}
/** Returns the user-defined name of the zone. */
public String getName() {
return name;
}
/** Returns the service-generated id for this zone. */
public String getGeneratedId() {
return generatedId;
}
/** Returns the time when this zone was created on the server. */
public Long getCreationTimeMillis() {
return creationTimeMillis;
}
/** Returns the DNS name of this zone, for instance "example.com.". */
public String getDnsName() {
return dnsName;
}
/** Returns the description of this zone. */
public String getDescription() {
return description;
}
/**
* Returns the optionally specified set of DNS name servers that all host this zone. This value is
* set only for specific use cases and is left empty for vast majority of users.
*/
public String getNameServerSet() {
return nameServerSet;
}
/** Returns the labels for this zone. */
public Map getLabels() {
return labels;
}
/**
* The nameservers that the zone should be delegated to. This is defined by the Google DNS cloud.
*/
public List getNameServers() {
return nameServers == null ? ImmutableList.of() : nameServers;
}
public DnsSecConfig getDnsSecConfig() {
return dnsSecConfig;
}
/** Returns a builder for {@code ZoneInfo} prepopulated with the metadata of this zone. */
public Builder toBuilder() {
return new BuilderImpl(this);
}
ManagedZone toPb() {
ManagedZone pb = new ManagedZone();
pb.setDescription(this.getDescription());
pb.setDnsName(this.getDnsName());
if (this.getGeneratedId() != null) {
pb.setId(new BigInteger(this.getGeneratedId()));
}
pb.setName(this.getName());
pb.setNameServers(this.nameServers); // do use real attribute value which may be null
pb.setNameServerSet(this.getNameServerSet());
if (this.getCreationTimeMillis() != null) {
pb.setCreationTime(
DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(this.getCreationTimeMillis())));
}
if (this.dnsSecConfig != null) {
pb.setDnssecConfig(this.dnsSecConfig.toPb());
}
if (this.getLabels() != null) {
pb.setLabels(labels);
}
return pb;
}
static ZoneInfo fromPb(ManagedZone pb) {
Builder builder = new BuilderImpl(pb.getName());
if (pb.getDescription() != null) {
builder.setDescription(pb.getDescription());
}
if (pb.getDnsName() != null) {
builder.setDnsName(pb.getDnsName());
}
if (pb.getId() != null) {
builder.setGeneratedId(pb.getId().toString());
}
if (pb.getNameServers() != null) {
builder.setNameServers(pb.getNameServers());
}
if (pb.getNameServerSet() != null) {
builder.setNameServerSet(pb.getNameServerSet());
}
if (pb.getCreationTime() != null) {
builder.setCreationTimeMillis(
DATE_TIME_FORMATTER.parse(pb.getCreationTime(), Instant.FROM).toEpochMilli());
}
if (pb.getDnssecConfig() != null) {
builder.setDnsSecConfig(DnsSecConfig.fromPb(pb.getDnssecConfig()));
}
if (pb.getLabels() != null) {
builder.setLabels(pb.getLabels());
}
return builder.build();
}
@Override
public boolean equals(Object obj) {
return obj == this
|| obj != null
&& obj.getClass().equals(ZoneInfo.class)
&& Objects.equals(toPb(), ((ZoneInfo) obj).toPb());
}
@Override
public int hashCode() {
return Objects.hash(
name,
generatedId,
creationTimeMillis,
dnsName,
description,
nameServerSet,
nameServers,
dnsSecConfig,
labels);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", getName())
.add("generatedId", getGeneratedId())
.add("description", getDescription())
.add("dnsName", getDnsName())
.add("nameServerSet", getNameServerSet())
.add("nameServers", getNameServers())
.add("creationTimeMillis", getCreationTimeMillis())
.add("dnsSecConfig", getDnsSecConfig())
.add("labels", getLabels())
.toString();
}
}