diff --git a/java/google/registry/mapreduce/MapreduceRunner.java b/java/google/registry/mapreduce/MapreduceRunner.java index 39c07db21..a0e16a5f2 100644 --- a/java/google/registry/mapreduce/MapreduceRunner.java +++ b/java/google/registry/mapreduce/MapreduceRunner.java @@ -284,7 +284,10 @@ public class MapreduceRunner { private String renderMapreduceConsoleLink(String jobId) { return String.format( - MAPREDUCE_CONSOLE_LINK_FORMAT, appEngineServiceUtils.getServiceHostname("backend"), jobId); + MAPREDUCE_CONSOLE_LINK_FORMAT, + appEngineServiceUtils.convertToSingleSubdomain( + appEngineServiceUtils.getServiceHostname("backend")), + jobId); } /** @@ -301,7 +304,7 @@ public class MapreduceRunner { } public void sendLinkToMapreduceConsole(Response response) { - response.setPayload(getLinkToMapreduceConsole()); + response.setPayload(getLinkToMapreduceConsole() + "\n"); } public String getLinkToMapreduceConsole() { diff --git a/java/google/registry/util/AppEngineServiceUtils.java b/java/google/registry/util/AppEngineServiceUtils.java index b4aefcd08..2790a747c 100644 --- a/java/google/registry/util/AppEngineServiceUtils.java +++ b/java/google/registry/util/AppEngineServiceUtils.java @@ -38,6 +38,25 @@ public interface AppEngineServiceUtils { /** Returns a host name to use for the given service and version. */ String getVersionHostname(String service, String version); + /** + * Converts a multi-level App Engine host name (not URL) to the -dot- single subdomain format. + * + *

This is needed because appspot.com only has a single wildcard SSL certificate, so the native + * App Engine URLs of the form service.projectid.appspot.com or + * version.service.projectid.appspot.com won't work over HTTPS when being fetched from outside of + * GCP. The work-around is to change all of the "." subdomain markers to "-dot-". E.g.: + * + *

+ * + * @see How + * App Engine requests are routed + */ + String convertToSingleSubdomain(String hostname); + /** Set the number of instances at runtime for a given service and version. */ void setNumInstances(String service, String version, long numInstances); } diff --git a/java/google/registry/util/AppEngineServiceUtilsImpl.java b/java/google/registry/util/AppEngineServiceUtilsImpl.java index 1d9c7eb4e..b395b05ff 100644 --- a/java/google/registry/util/AppEngineServiceUtilsImpl.java +++ b/java/google/registry/util/AppEngineServiceUtilsImpl.java @@ -18,11 +18,18 @@ import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.appengine.api.modules.ModulesService; +import com.google.common.flogger.FluentLogger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.inject.Inject; /** A wrapper for {@link ModulesService} that provides a saner API. */ public class AppEngineServiceUtilsImpl implements AppEngineServiceUtils { + private static final Pattern APPSPOT_HOSTNAME_PATTERN = + Pattern.compile("^(.*)\\.appspot\\.com$"); + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private final ModulesService modulesService; @Inject @@ -56,4 +63,14 @@ public class AppEngineServiceUtilsImpl implements AppEngineServiceUtils { checkArgument(numInstances > 0, "Number of instances must be greater than 0"); modulesService.setNumInstances(service, version, numInstances); } + + @Override + public String convertToSingleSubdomain(String hostname) { + Matcher matcher = APPSPOT_HOSTNAME_PATTERN.matcher(hostname); + if (!matcher.matches()) { + logger.atWarning().log("Skipping conversion because hostname can't be parsed: %s", hostname); + return hostname; + } + return matcher.group(1).replace(".", "-dot-") + ".appspot.com"; + } } diff --git a/javatests/google/registry/export/ExportDomainListsActionTest.java b/javatests/google/registry/export/ExportDomainListsActionTest.java index 36a528920..d89ef8960 100644 --- a/javatests/google/registry/export/ExportDomainListsActionTest.java +++ b/javatests/google/registry/export/ExportDomainListsActionTest.java @@ -94,7 +94,8 @@ public class ExportDomainListsActionTest extends MapreduceTestCase appEngineServiceUtils.setNumInstances("service", "version", -10L)); assertThat(thrown).hasMessageThat().isEqualTo("Number of instances must be greater than 0"); } + + @Test + public void test_convertToSingleSubdomain_doesNothingWithoutServiceOrHostname() { + assertThat(appEngineServiceUtils.convertToSingleSubdomain("projectid.appspot.com")) + .isEqualTo("projectid.appspot.com"); + } + + @Test + public void test_convertToSingleSubdomain_doesNothingWhenItCannotParseCorrectly() { + assertThat(appEngineServiceUtils.convertToSingleSubdomain("garbage.notrealhost.example")) + .isEqualTo("garbage.notrealhost.example"); + } + + @Test + public void test_convertToSingleSubdomain_convertsWithServiceName() { + assertThat(appEngineServiceUtils.convertToSingleSubdomain("service.projectid.appspot.com")) + .isEqualTo("service-dot-projectid.appspot.com"); + } + + @Test + public void test_convertToSingleSubdomain_convertsWithVersionAndServiceName() { + assertThat( + appEngineServiceUtils.convertToSingleSubdomain("version.service.projectid.appspot.com")) + .isEqualTo("version-dot-service-dot-projectid.appspot.com"); + } + + @Test + public void test_convertToSingleSubdomain_convertsWithInstanceAndVersionAndServiceName() { + assertThat( + appEngineServiceUtils.convertToSingleSubdomain( + "instanceid.version.service.projectid.appspot.com")) + .isEqualTo("instanceid-dot-version-dot-service-dot-projectid.appspot.com"); + } }