mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 20:17:51 +02:00
419 lines
16 KiB
Java
419 lines
16 KiB
Java
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
//
|
|
// 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 google.registry.tools;
|
|
|
|
import static com.google.common.base.Preconditions.checkArgument;
|
|
import static google.registry.util.CollectionUtils.findDuplicates;
|
|
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
|
|
|
|
import com.beust.jcommander.Parameter;
|
|
import com.google.common.base.CharMatcher;
|
|
import com.google.common.base.Joiner;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.ImmutableSortedMap;
|
|
import com.google.common.collect.ImmutableSortedSet;
|
|
import com.google.common.collect.Sets;
|
|
import google.registry.model.pricing.StaticPremiumListPricingEngine;
|
|
import google.registry.model.registry.Registries;
|
|
import google.registry.model.registry.Registry;
|
|
import google.registry.model.registry.Registry.TldState;
|
|
import google.registry.model.registry.Registry.TldType;
|
|
import google.registry.model.registry.label.PremiumList;
|
|
import google.registry.tools.params.OptionalIntervalParameter;
|
|
import google.registry.tools.params.OptionalStringParameter;
|
|
import google.registry.tools.params.TransitionListParameter.BillingCostTransitions;
|
|
import google.registry.tools.params.TransitionListParameter.TldStateTransitions;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import javax.annotation.Nullable;
|
|
import javax.inject.Inject;
|
|
import javax.inject.Named;
|
|
import org.joda.money.Money;
|
|
import org.joda.time.DateTime;
|
|
import org.joda.time.Duration;
|
|
import org.joda.time.Interval;
|
|
|
|
/** Shared base class for commands to create or update a TLD. */
|
|
abstract class CreateOrUpdateTldCommand extends MutatingCommand {
|
|
|
|
@Inject
|
|
@Named("dnsWriterNames")
|
|
Set<String> validDnsWriterNames;
|
|
|
|
@Parameter(description = "Names of the TLDs", required = true)
|
|
List<String> mainParameters;
|
|
|
|
@Parameter(
|
|
names = "--escrow",
|
|
description = "Whether to enable nightly RDE escrow deposits",
|
|
arity = 1)
|
|
private Boolean escrow;
|
|
|
|
@Parameter(
|
|
names = "--dns",
|
|
description = "Set to false to pause writing to the DNS queue",
|
|
arity = 1)
|
|
private Boolean dns;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--add_grace_period",
|
|
description = "Length of the add grace period (in ISO 8601 duration format)")
|
|
Duration addGracePeriod;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--sunrush_add_grace_period",
|
|
description = "Length of the add grace period during sunrush (in ISO 8601 duration format)")
|
|
Duration sunrushAddGracePeriod;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--redemption_grace_period",
|
|
description = "Length of the redemption grace period (in ISO 8601 duration format)")
|
|
Duration redemptionGracePeriod;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--pending_delete_length",
|
|
description = "Length of the pending delete period (in ISO 8601 duration format)")
|
|
Duration pendingDeleteLength;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--automatic_transfer_length",
|
|
description = "Length of the automatic transfer period (in ISO 8601 duration format)")
|
|
private Duration automaticTransferLength;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--restore_billing_cost",
|
|
description = "One-time billing cost for restoring a domain")
|
|
private Money restoreBillingCost;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--roid_suffix",
|
|
description = "The suffix to be used for ROIDs, e.g. COM for .com domains (which then "
|
|
+ "creates roids looking like 123ABC-COM)")
|
|
String roidSuffix;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--server_status_change_cost",
|
|
description = "One-time billing cost for a server status change")
|
|
private Money serverStatusChangeCost;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--tld_type",
|
|
description = "Tld type (REAL or TEST)")
|
|
private TldType tldType;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--premium_price_ack_required",
|
|
description = "Whether operations on premium domains require explicit ack of prices",
|
|
arity = 1)
|
|
private Boolean premiumPriceAckRequired;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--create_billing_cost",
|
|
description = "Per-year billing cost for creating a domain")
|
|
Money createBillingCost;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--drive_folder_id",
|
|
description = "Id of the folder in drive used to publish information for this TLD",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
Optional<String> driveFolderId;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--lordn_username",
|
|
description = "Username for LORDN uploads",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
Optional<String> lordnUsername;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--premium_list",
|
|
description = "The name of the premium list to apply to the TLD",
|
|
converter = OptionalStringParameter.class,
|
|
validateWith = OptionalStringParameter.class)
|
|
Optional<String> premiumListName;
|
|
|
|
@Parameter(
|
|
names = "--tld_state_transitions",
|
|
converter = TldStateTransitions.class,
|
|
validateWith = TldStateTransitions.class,
|
|
description = "Comma-delimited list of TLD state transitions, of the form "
|
|
+ "<time>=<tld-state>[,<time>=<tld-state>]*")
|
|
ImmutableSortedMap<DateTime, TldState> tldStateTransitions = ImmutableSortedMap.of();
|
|
|
|
@Parameter(
|
|
names = "--renew_billing_cost_transitions",
|
|
converter = BillingCostTransitions.class,
|
|
validateWith = BillingCostTransitions.class,
|
|
description = "Comma-delimited list of renew billing cost transitions, of the form "
|
|
+ "<time>=<money-amount>[,<time>=<money-amount>]* where each amount "
|
|
+ "represents the per-year billing cost for renewing a domain")
|
|
ImmutableSortedMap<DateTime, Money> renewBillingCostTransitions =
|
|
ImmutableSortedMap.of();
|
|
|
|
@Parameter(
|
|
names = "--eap_fee_schedule",
|
|
converter = BillingCostTransitions.class,
|
|
validateWith = BillingCostTransitions.class,
|
|
description = "Comma-delimited list of EAP fees effective on specific dates, of the form "
|
|
+ "<time>=<money-amount>[,<time>=<money-amount>]* where each amount represents the "
|
|
+ "EAP fee for creating a new domain under the TLD.")
|
|
ImmutableSortedMap<DateTime, Money> eapFeeSchedule = ImmutableSortedMap.of();
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--reserved_lists",
|
|
description = "A comma-separated list of reserved list names to be applied to the TLD")
|
|
List<String> reservedListNames;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--allowed_registrants",
|
|
description = "A comma-separated list of allowed registrants for the TLD")
|
|
List<String> allowedRegistrants;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--allowed_nameservers",
|
|
description = "A comma-separated list of allowed nameservers for the TLD")
|
|
List<String> allowedNameservers;
|
|
|
|
@Parameter(
|
|
names = {"-o", "--override_reserved_list_rules"},
|
|
description = "Override restrictions on reserved list naming")
|
|
boolean overrideReservedListRules;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = {"--domain_create_restricted"},
|
|
description = "If only domains with nameserver restricted reservation can be created",
|
|
arity = 1
|
|
)
|
|
Boolean domainCreateRestricted;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--claims_period_end",
|
|
description = "The end of the claims period")
|
|
DateTime claimsPeriodEnd;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--dns_writers",
|
|
description = "A comma-separated list of DnsWriter implementations to use")
|
|
List<String> dnsWriters;
|
|
|
|
@Nullable
|
|
@Parameter(
|
|
names = "--lrp_period",
|
|
description =
|
|
"LRP period (in ISO-8601 format, e.g. 2004-06-09T12:30:00Z/2004-07-10T13:30:00Z",
|
|
converter = OptionalIntervalParameter.class,
|
|
validateWith = OptionalIntervalParameter.class)
|
|
private Optional<Interval> lrpPeriod;
|
|
|
|
/** Returns the existing registry (for update) or null (for creates). */
|
|
@Nullable
|
|
abstract Registry getOldRegistry(String tld);
|
|
|
|
abstract ImmutableSet<String> getAllowedRegistrants(Registry oldRegistry);
|
|
|
|
abstract ImmutableSet<String> getAllowedNameservers(Registry oldRegistry);
|
|
|
|
abstract ImmutableSet<String> getReservedLists(Registry oldRegistry);
|
|
|
|
abstract Optional<Map.Entry<DateTime, TldState>> getTldStateTransitionToAdd();
|
|
|
|
/** Subclasses can override this to set their own properties. */
|
|
void setCommandSpecificProperties(@SuppressWarnings("unused") Registry.Builder builder) {}
|
|
|
|
/** Subclasses can override this to assert that the command can be run in this environment. */
|
|
void assertAllowedEnvironment() {}
|
|
|
|
protected abstract void initTldCommand() throws Exception;
|
|
|
|
@Override
|
|
protected final void init() throws Exception {
|
|
assertAllowedEnvironment();
|
|
initTldCommand();
|
|
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
|
|
checkArgument(duplicates.isEmpty(), "Duplicate arguments found: '%s'", duplicates);
|
|
Set<String> tlds = ImmutableSet.copyOf(mainParameters);
|
|
checkArgument(roidSuffix == null || tlds.size() == 1,
|
|
"Can't update roid suffixes on multiple TLDs simultaneously");
|
|
for (String tld : tlds) {
|
|
checkArgument(
|
|
tld.equals(canonicalizeDomainName(tld)),
|
|
"TLD '%s' should be given in the canonical form '%s'",
|
|
tld,
|
|
canonicalizeDomainName(tld));
|
|
checkArgument(
|
|
!CharMatcher.javaDigit().matches(tld.charAt(0)),
|
|
"TLDs cannot begin with a number");
|
|
Registry oldRegistry = getOldRegistry(tld);
|
|
// TODO(b/26901539): Add a flag to set the pricing engine once we have more than one option.
|
|
Registry.Builder builder =
|
|
oldRegistry == null
|
|
? new Registry.Builder()
|
|
.setTldStr(tld)
|
|
.setPremiumPricingEngine(StaticPremiumListPricingEngine.NAME)
|
|
: oldRegistry.asBuilder();
|
|
|
|
if (escrow != null) {
|
|
builder.setEscrowEnabled(escrow);
|
|
}
|
|
|
|
if (dns != null) {
|
|
builder.setDnsPaused(!dns);
|
|
}
|
|
|
|
Optional<Map.Entry<DateTime, TldState>> tldStateTransitionToAdd =
|
|
getTldStateTransitionToAdd();
|
|
if (!tldStateTransitions.isEmpty()) {
|
|
builder.setTldStateTransitions(tldStateTransitions);
|
|
} else if (tldStateTransitionToAdd.isPresent()) {
|
|
ImmutableSortedMap.Builder<DateTime, TldState> newTldStateTransitions =
|
|
ImmutableSortedMap.naturalOrder();
|
|
if (oldRegistry != null) {
|
|
checkArgument(
|
|
oldRegistry.getTldStateTransitions().lastKey().isBefore(
|
|
tldStateTransitionToAdd.get().getKey()),
|
|
"Cannot add %s at %s when there is a later transition already scheduled",
|
|
tldStateTransitionToAdd.get().getValue(),
|
|
tldStateTransitionToAdd.get().getKey());
|
|
newTldStateTransitions.putAll(oldRegistry.getTldStateTransitions());
|
|
}
|
|
builder.setTldStateTransitions(
|
|
newTldStateTransitions.put(getTldStateTransitionToAdd().get()).build());
|
|
}
|
|
|
|
if (!renewBillingCostTransitions.isEmpty()) {
|
|
// TODO(b/20764952): need invoicing support for multiple renew billing costs.
|
|
if (renewBillingCostTransitions.size() > 1) {
|
|
System.err.println(
|
|
"----------------------\n"
|
|
+ "WARNING: Do not set multiple renew cost transitions until b/20764952 is fixed.\n"
|
|
+ "----------------------\n");
|
|
}
|
|
builder.setRenewBillingCostTransitions(renewBillingCostTransitions);
|
|
}
|
|
|
|
if (!eapFeeSchedule.isEmpty()) {
|
|
builder.setEapFeeSchedule(eapFeeSchedule);
|
|
}
|
|
|
|
Optional.ofNullable(addGracePeriod).ifPresent(builder::setAddGracePeriodLength);
|
|
Optional.ofNullable(sunrushAddGracePeriod).ifPresent(builder::setSunrushAddGracePeriodLength);
|
|
Optional.ofNullable(redemptionGracePeriod).ifPresent(builder::setRedemptionGracePeriodLength);
|
|
Optional.ofNullable(pendingDeleteLength).ifPresent(builder::setPendingDeleteLength);
|
|
Optional.ofNullable(automaticTransferLength).ifPresent(builder::setAutomaticTransferLength);
|
|
Optional.ofNullable(driveFolderId).ifPresent(id -> builder.setDriveFolderId(id.orElse(null)));
|
|
Optional.ofNullable(createBillingCost).ifPresent(builder::setCreateBillingCost);
|
|
Optional.ofNullable(restoreBillingCost).ifPresent(builder::setRestoreBillingCost);
|
|
Optional.ofNullable(roidSuffix).ifPresent(builder::setRoidSuffix);
|
|
Optional.ofNullable(serverStatusChangeCost)
|
|
.ifPresent(builder::setServerStatusChangeBillingCost);
|
|
Optional.ofNullable(tldType).ifPresent(builder::setTldType);
|
|
Optional.ofNullable(premiumPriceAckRequired).ifPresent(builder::setPremiumPriceAckRequired);
|
|
Optional.ofNullable(lordnUsername).ifPresent(u -> builder.setLordnUsername(u.orElse(null)));
|
|
Optional.ofNullable(claimsPeriodEnd).ifPresent(builder::setClaimsPeriodEnd);
|
|
Optional.ofNullable(domainCreateRestricted).ifPresent(builder::setDomainCreateRestricted);
|
|
Optional.ofNullable(lrpPeriod).ifPresent(p -> builder.setLrpPeriod(p.orElse(null)));
|
|
|
|
if (premiumListName != null) {
|
|
if (premiumListName.isPresent()) {
|
|
Optional<PremiumList> premiumList = PremiumList.get(premiumListName.get());
|
|
checkArgument(premiumList.isPresent(),
|
|
String.format("The premium list '%s' doesn't exist", premiumListName.get()));
|
|
builder.setPremiumList(premiumList.get());
|
|
} else {
|
|
builder.setPremiumList(null);
|
|
}
|
|
}
|
|
|
|
if (dnsWriters != null) {
|
|
ImmutableSet<String> dnsWritersSet = ImmutableSet.copyOf(dnsWriters);
|
|
ImmutableSortedSet<String> invalidDnsWriters =
|
|
ImmutableSortedSet.copyOf(Sets.difference(dnsWritersSet, validDnsWriterNames));
|
|
checkArgument(
|
|
invalidDnsWriters.isEmpty(),
|
|
"Invalid DNS writer name(s) specified: %s",
|
|
invalidDnsWriters);
|
|
builder.setDnsWriters(dnsWritersSet);
|
|
}
|
|
|
|
ImmutableSet<String> newReservedListNames = getReservedLists(oldRegistry);
|
|
checkReservedListValidityForTld(tld, newReservedListNames);
|
|
builder.setReservedListsByName(newReservedListNames);
|
|
|
|
builder.setAllowedRegistrantContactIds(getAllowedRegistrants(oldRegistry));
|
|
|
|
builder.setAllowedFullyQualifiedHostNames(getAllowedNameservers(oldRegistry));
|
|
|
|
// Update the Registry object.
|
|
setCommandSpecificProperties(builder);
|
|
stageEntityChange(oldRegistry, builder.build());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String execute() throws Exception {
|
|
try {
|
|
return super.execute();
|
|
} finally {
|
|
// Manually reset the cache here so that subsequent commands (e.g. in SetupOteCommand) see
|
|
// the latest version of the data.
|
|
// TODO(b/24903801): change all those places to use uncached code paths to get Registries.
|
|
Registries.resetCache();
|
|
}
|
|
}
|
|
|
|
private void checkReservedListValidityForTld(String tld, Set<String> reservedListNames) {
|
|
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
|
|
for (String reservedListName : reservedListNames) {
|
|
if (!reservedListName.startsWith("common_") && !reservedListName.startsWith(tld + "_")) {
|
|
builder.add(reservedListName);
|
|
}
|
|
}
|
|
ImmutableList<String> invalidNames = builder.build();
|
|
if (!invalidNames.isEmpty()) {
|
|
String errMsg = String.format("The reserved list(s) %s cannot be applied to the tld %s",
|
|
Joiner.on(", ").join(invalidNames),
|
|
tld);
|
|
if (overrideReservedListRules) {
|
|
System.err.println("Error overriden: " + errMsg);
|
|
} else {
|
|
throw new IllegalArgumentException(errMsg);
|
|
}
|
|
}
|
|
}
|
|
}
|