From 6ffe84e93d7b8da83e1d24c3dcda07e2eeaa5283 Mon Sep 17 00:00:00 2001 From: Ben McIlwain Date: Fri, 15 Oct 2021 12:28:18 -0400 Subject: [PATCH] Add a scrap command to hard-delete a host resource (#1391) --- .../google/registry/tools/RegistryTool.java | 2 + .../registry/tools/RegistryToolComponent.java | 3 + .../javascrap/HardDeleteHostCommand.java | 99 +++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 core/src/main/java/google/registry/tools/javascrap/HardDeleteHostCommand.java diff --git a/core/src/main/java/google/registry/tools/RegistryTool.java b/core/src/main/java/google/registry/tools/RegistryTool.java index f0d7d1227..b7bd5886f 100644 --- a/core/src/main/java/google/registry/tools/RegistryTool.java +++ b/core/src/main/java/google/registry/tools/RegistryTool.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMap; import google.registry.tools.javascrap.BackfillRegistryLocksCommand; import google.registry.tools.javascrap.BackfillSpec11ThreatMatchesCommand; import google.registry.tools.javascrap.DeleteContactByRoidCommand; +import google.registry.tools.javascrap.HardDeleteHostCommand; import google.registry.tools.javascrap.PopulateNullRegistrarFieldsCommand; import google.registry.tools.javascrap.RemoveIpAddressCommand; import google.registry.tools.javascrap.ResaveAllTldsCommand; @@ -86,6 +87,7 @@ public final class RegistryTool { .put("get_sql_credential", GetSqlCredentialCommand.class) .put("get_tld", GetTldCommand.class) .put("ghostryde", GhostrydeCommand.class) + .put("hard_delete_host", HardDeleteHostCommand.class) .put("hash_certificate", HashCertificateCommand.class) .put("import_datastore", ImportDatastoreCommand.class) .put("list_cursors", ListCursorsCommand.class) diff --git a/core/src/main/java/google/registry/tools/RegistryToolComponent.java b/core/src/main/java/google/registry/tools/RegistryToolComponent.java index 6600fcf76..eb8f46278 100644 --- a/core/src/main/java/google/registry/tools/RegistryToolComponent.java +++ b/core/src/main/java/google/registry/tools/RegistryToolComponent.java @@ -43,6 +43,7 @@ import google.registry.request.Modules.UserServiceModule; import google.registry.tools.AuthModule.LocalCredentialModule; import google.registry.tools.javascrap.BackfillRegistryLocksCommand; import google.registry.tools.javascrap.DeleteContactByRoidCommand; +import google.registry.tools.javascrap.HardDeleteHostCommand; import google.registry.util.UtilsModule; import google.registry.whois.NonCachingWhoisModule; import javax.annotation.Nullable; @@ -124,6 +125,8 @@ interface RegistryToolComponent { void inject(GhostrydeCommand command); + void inject(HardDeleteHostCommand command); + void inject(ImportDatastoreCommand command); void inject(ListCursorsCommand command); diff --git a/core/src/main/java/google/registry/tools/javascrap/HardDeleteHostCommand.java b/core/src/main/java/google/registry/tools/javascrap/HardDeleteHostCommand.java new file mode 100644 index 000000000..f7fc784ed --- /dev/null +++ b/core/src/main/java/google/registry/tools/javascrap/HardDeleteHostCommand.java @@ -0,0 +1,99 @@ +// Copyright 2021 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.javascrap; + +import static com.google.common.base.Verify.verify; +import static google.registry.model.ofy.ObjectifyService.auditedOfy; +import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.Key; +import google.registry.model.host.HostResource; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.tools.CommandWithRemoteApi; +import google.registry.tools.ConfirmingCommand; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Deletes a {@link HostResource} by its ROID. + * + *

This deletes the host itself, everything in the same entity group including all {@link + * google.registry.model.reporting.HistoryEntry}s and {@link + * google.registry.model.poll.PollMessage}s, the {@link EppResourceIndex}, and the {@link + * ForeignKeyIndex} (if it exists). + * + *

DO NOT use this to hard-delete a host that is still in use on a domain. Bad things will + * happen. + */ +@Parameters(separators = " =", commandDescription = "Delete a host by its ROID.") +public class HardDeleteHostCommand extends ConfirmingCommand implements CommandWithRemoteApi { + + @Parameter(names = "--roid", description = "The ROID of the host to be deleted.") + String roid; + + @Parameter(names = "--hostname", description = "The hostname, for verification.") + String hostname; + + private ImmutableList> toDelete; + + @Override + protected void init() { + ofyTm() + .transact( + () -> { + Key targetKey = Key.create(HostResource.class, roid); + HostResource host = auditedOfy().load().key(targetKey).now(); + verify(Objects.equals(host.getHostName(), hostname), "Hostname does not match"); + + List> objectsInEntityGroup = + auditedOfy().load().ancestor(host).keys().list(); + + Optional> fki = + Optional.ofNullable( + auditedOfy().load().key(ForeignKeyIndex.createKey(host)).now()); + if (!fki.isPresent()) { + System.out.println( + "No ForeignKeyIndex exists, likely because resource is soft-deleted." + + " Continuing."); + } + + EppResourceIndex eppResourceIndex = + auditedOfy().load().entity(EppResourceIndex.create(targetKey)).now(); + verify(eppResourceIndex.getKey().equals(targetKey), "Wrong EppResource Index loaded"); + + ImmutableList.Builder> toDeleteBuilder = + new ImmutableList.Builder>() + .addAll(objectsInEntityGroup) + .add(Key.create(eppResourceIndex)); + fki.ifPresent(f -> toDeleteBuilder.add(Key.create(f))); + toDelete = toDeleteBuilder.build(); + + System.out.printf("\n\nAbout to delete %d entities with keys:\n", toDelete.size()); + toDelete.forEach(System.out::println); + }); + } + + @Override + protected String execute() { + tm().transact(() -> auditedOfy().delete().keys(toDelete).now()); + return "Done."; + } +}