diff --git a/core/build.gradle b/core/build.gradle index 380322a3b..f129a09df 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -226,7 +226,7 @@ dependencies { testCompile deps['org.seleniumhq.selenium:selenium-chrome-driver'] testCompile deps['org.seleniumhq.selenium:selenium-java'] testCompile deps['org.seleniumhq.selenium:selenium-remote-driver'] - testCompile deps['org.testcontainers:postgresql'] + compile deps['org.testcontainers:postgresql'] testCompile deps['org.testcontainers:selenium'] compile deps['xerces:xmlParserAPIs'] compile deps['xpp3:xpp3'] diff --git a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java index 75cc029fa..2d4db1408 100644 --- a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java +++ b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java @@ -16,6 +16,7 @@ package google.registry.tools; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.google.common.annotations.VisibleForTesting; import google.registry.model.domain.DomainBase; import java.util.EnumSet; import java.util.HashMap; @@ -24,6 +25,7 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.tool.hbm2ddl.SchemaExport; import org.hibernate.tool.schema.TargetType; +import org.testcontainers.containers.PostgreSQLContainer; /** * Generates a schema for JPA annotated classes using hibernate. @@ -35,13 +37,27 @@ import org.hibernate.tool.schema.TargetType; @Parameters(separators = " =", commandDescription = "Generate postgresql schema.") public class GenerateSqlSchemaCommand implements Command { + @VisibleForTesting + public static final String DB_OPTIONS_CLASH = + "Database host and port may not be spcified along with the option to start a " + + "postgresql container."; + + @VisibleForTesting public static final int POSTGRESQL_PORT = 5432; + private PostgreSQLContainer postgresContainer = null; + @Parameter( names = {"-o", "--out-file"}, - description = "") + description = "Name of the output file.", + required = true) String outFile; + @Parameter( + names = {"-s", "--start-postgresql"}, + description = "If specified, start postgresql in a docker container.") + boolean startPostgresql = false; + @Parameter( names = {"-a", "--db-host"}, description = "Database host name.") @@ -50,30 +66,73 @@ public class GenerateSqlSchemaCommand implements Command { @Parameter( names = {"-p", "--db-port"}, description = "Database port number. This defaults to the postgresql default port.") - int databasePort = POSTGRESQL_PORT; + Integer databasePort; @Override public void run() { - // TODO(mmuller): Optionally (and perhaps by default) start a postgresql instance container - // rather than relying on the user to have one to connect to. - Map settings = new HashMap<>(); - settings.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect"); - settings.put( - "hibernate.connection.url", - "jdbc:postgresql://" + databaseHost + ":" + databasePort + "/postgres?useSSL=false"); - settings.put("hibernate.connection.username", "postgres"); - settings.put("hibernate.connection.password", "domain-registry"); - settings.put("hibernate.hbm2ddl.auto", "none"); - settings.put("show_sql", "true"); + // Start postgres if requested. + if (startPostgresql) { + // Complain if the user has also specified either --db-host or --db-port. + if (databaseHost != null || databasePort != null) { + System.err.println(DB_OPTIONS_CLASH); + // TODO: it would be nice to exit(1) here, but this breaks testability. + return; + } - MetadataSources metadata = - new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build()); - metadata.addAnnotatedClass(DomainBase.class); - SchemaExport schemaExport = new SchemaExport(); - schemaExport.setHaltOnError(true); - schemaExport.setFormat(true); - schemaExport.setDelimiter(";"); - schemaExport.setOutputFile(outFile); - schemaExport.createOnly(EnumSet.of(TargetType.SCRIPT), metadata.buildMetadata()); + // Start the container and store the address information. + postgresContainer = new PostgreSQLContainer() + .withDatabaseName("postgres") + .withUsername("postgres") + .withPassword("domain-registry"); + postgresContainer.start(); + databaseHost = postgresContainer.getContainerIpAddress(); + databasePort = postgresContainer.getMappedPort(POSTGRESQL_PORT); + } else if (databaseHost == null) { + System.err.println( + "You must specify either --start-postgresql to start a PostgreSQL database in a\n" + + "docker instance, or specify --db-host (and, optionally, --db-port) to identify\n" + + "the location of a running instance. To start a long-lived instance (suitable\n" + + "for running this command multiple times) run this:\n\n" + + " docker run --rm --name some-postgres -e POSTGRES_PASSWORD=domain-registry \\\n" + + " -d postgres:9.6.12\n\n" + + "Copy the container id output from the command, then run:\n\n" + + " docker inspect | grep IPAddress\n\n" + + "To obtain the value for --db-host.\n" + ); + // TODO: need exit(1), see above. + return; + } + + // use the default port if non has been defined. + if (databasePort == null) { + databasePort = POSTGRESQL_PORT; + } + + try { + // Configure hibernate settings. + Map settings = new HashMap<>(); + settings.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL9Dialect"); + settings.put( + "hibernate.connection.url", + "jdbc:postgresql://" + databaseHost + ":" + databasePort + "/postgres?useSSL=false"); + settings.put("hibernate.connection.username", "postgres"); + settings.put("hibernate.connection.password", "domain-registry"); + settings.put("hibernate.hbm2ddl.auto", "none"); + settings.put("show_sql", "true"); + + MetadataSources metadata = + new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build()); + metadata.addAnnotatedClass(DomainBase.class); + SchemaExport schemaExport = new SchemaExport(); + schemaExport.setHaltOnError(true); + schemaExport.setFormat(true); + schemaExport.setDelimiter(";"); + schemaExport.setOutputFile(outFile); + schemaExport.createOnly(EnumSet.of(TargetType.SCRIPT), metadata.buildMetadata()); + } finally { + if (postgresContainer != null) { + postgresContainer.stop(); + } + } } } diff --git a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java index db64b43bc..62c79e7ff 100644 --- a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java +++ b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import java.io.File; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -35,7 +36,7 @@ public class GenerateSqlSchemaCommandTest extends CommandTestCase