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.:
+ *
+ *
+ * - tools.projectid.appspot.com --> tools-dot-projectid.appspot.com
+ *
- version.backend.projectid.appspot.com --> version-dot-backend-dot-projectid.appspot.com
+ *
+ *
+ * @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 extends ShardableTestCase {
@Rule public final MockitoJUnitRule mocks = MockitoJUnitRule.create();
- @Mock
AppEngineServiceUtils appEngineServiceUtils;
+ @Mock ModulesService modulesService;
+
@Before
public void setUp() {
taskQueue = LocalTaskQueueTestConfig.getLocalTaskQueue();
ApiProxyLocal proxy = (ApiProxyLocal) ApiProxy.getDelegate();
// Creating files is not allowed in some test execution environments, so don't.
proxy.setProperty(LocalBlobstoreService.NO_STORAGE_PROPERTY, "true");
- when(appEngineServiceUtils.getServiceHostname("backend")).thenReturn("backend.hostname.tld");
+ appEngineServiceUtils = new AppEngineServiceUtilsImpl(modulesService);
+ when(modulesService.getVersionHostname("backend", null))
+ .thenReturn("version.backend.projectid.appspot.com");
}
protected MapreduceRunner makeDefaultRunner() {
diff --git a/javatests/google/registry/tools/server/GenerateZoneFilesActionTest.java b/javatests/google/registry/tools/server/GenerateZoneFilesActionTest.java
index cce1b4687..2c7c8a04a 100644
--- a/javatests/google/registry/tools/server/GenerateZoneFilesActionTest.java
+++ b/javatests/google/registry/tools/server/GenerateZoneFilesActionTest.java
@@ -132,7 +132,8 @@ public class GenerateZoneFilesActionTest 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");
+ }
}