au.net.causal.maven.plugins.boxdb.db.CockroachDbDatabase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of boxdb-maven-plugin Show documentation
Show all versions of boxdb-maven-plugin Show documentation
Maven plugin to start databases using Docker and VMs
package au.net.causal.maven.plugins.boxdb.db;
import au.net.causal.maven.plugins.boxdb.ImageCheckerUtils;
import au.net.causal.maven.plugins.boxdb.JdbcSqlRunner;
import au.net.causal.maven.plugins.boxdb.ScriptReaderRunner;
import com.google.common.collect.ImmutableList;
import io.fabric8.maven.docker.access.DockerAccess;
import io.fabric8.maven.docker.access.DockerAccessException;
import io.fabric8.maven.docker.access.PortMapping;
import io.fabric8.maven.docker.config.Arguments;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.config.LogConfiguration;
import io.fabric8.maven.docker.config.RunImageConfiguration;
import io.fabric8.maven.docker.config.RunVolumeConfiguration;
import io.fabric8.maven.docker.log.LogDispatcher;
import io.fabric8.maven.docker.log.LogOutputSpec;
import io.fabric8.maven.docker.model.Container;
import io.fabric8.maven.docker.service.RunService;
import io.fabric8.maven.docker.util.GavLabel;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Properties;
public class CockroachDbDatabase extends DockerDatabase
{
public CockroachDbDatabase(BoxConfiguration boxConfiguration,
ProjectConfiguration projectConfiguration,
BoxContext context, DockerRegistry dockerRegistry)
{
super(boxConfiguration, projectConfiguration, context, dockerRegistry);
}
@Override
protected String dockerImageName()
{
return CockroachDbFactory.COCKROACH_DB_DOCKER_REPOSITORY + ":" + "v" + getBoxConfiguration().getDatabaseVersion();
}
@Override
protected int containerDatabasePort()
{
return 26257;
}
@Override
public void configureNewDatabase()
throws IOException, SQLException, BoxDatabaseException
{
URL initScript = CockroachDbDatabase.class.getResource("cockroachdb-create-database.sql");
ScriptReaderRunner scriptRunner = getContext().createScriptReaderRunner(this, getBoxConfiguration(), getProjectConfiguration());
ScriptReaderExecution execution = new ScriptReaderExecution();
execution.setFiltering(true);
execution.setScripts(Arrays.asList(initScript));
try
{
scriptRunner.execute(execution, DatabaseTarget.ADMIN, getProjectConfiguration().getScriptTimeout());
}
catch (MojoExecutionException e)
{
throw new BoxDatabaseException(e);
}
}
@Override
protected void configureRunImage(RunImageConfiguration.Builder builder)
{
super.configureRunImage(builder);
builder.cmd(new Arguments(ImmutableList.of("start", "--insecure")));
}
@Override
public Collection extends ImageComponent> checkImage()
throws BoxDatabaseException
{
ImageComponent jdbcDriverComponent = ImageCheckerUtils.checkImageUsingMavenDependencies("JDBC driver",
getContext(),
jdbcDriverInfo().getDependencies());
ImageComponent dockerDatabaseComponent = checkDockerDatabaseImage(CockroachDbFactory.COCKROACH_DB_DOCKER_REPOSITORY);
return ImmutableList.of(jdbcDriverComponent, dockerDatabaseComponent);
}
@Override
protected ImageComponent checkDockerDatabaseImage(String remoteDockerRepositoryName)
throws BoxDatabaseException
{
return checkDockerDatabaseImage(remoteDockerRepositoryName, "v" + getBoxConfiguration().getDatabaseVersion());
}
/**
* Returns the URL that can be used as a parameter for CockroachDB's console tools to connect to the database.
*
* @param target whether to connect as the admin user or the database user.
*
* @return a string URL suitable for use with the {@code -url} command line option for CockroachDB command line tools.
*
* @throws BoxDatabaseException if an error occurs.
*/
protected String cockroachDbUrl(DatabaseTarget target)
throws BoxDatabaseException
{
String databaseName;
if (target == DatabaseTarget.ADMIN)
databaseName = "system";
else
databaseName = getBoxConfiguration().getDatabaseName();
//This is the host for a console command run with a container link with this hostname
String host = "cockroachdb";
//sslmode=disable is required for versions < 2.0
// //for >= 2.0 it's not required but still works if it is included
return "postgresql://" + target.user(getBoxConfiguration()) + "@" +
host + ":" + getBoxConfiguration().getDatabasePort() +
"/" + databaseName + "?sslmode=disable";
}
@Override
public JdbcConnectionInfo jdbcConnectionInfo(DatabaseTarget target)
throws BoxDatabaseException
{
String databaseName;
if (target == DatabaseTarget.ADMIN)
databaseName = "system";
else
databaseName = getBoxConfiguration().getDatabaseName();
String uri = "jdbc:postgresql://" +
getContext().getDockerHostAddress() +
":" + getBoxConfiguration().getDatabasePort() +
"/" + databaseName;
return new JdbcConnectionInfo(uri,
target.user(getBoxConfiguration()),
target.password(getBoxConfiguration()),
getContext().getDockerHostAddress(),
getBoxConfiguration().getDatabasePort());
}
@Override
public JdbcDriverInfo jdbcDriverInfo()
throws BoxDatabaseException
{
//CockroachDB uses Postgres drivers
return JdbcDrivers.postgresDriver();
}
protected DataSourceBuilder dataSourceBuilder(DatabaseTarget target)
throws BoxDatabaseException
{
//CockroachDB uses Postgres drivers
return JdbcDrivers.postgresDataSourceBuilder(jdbcConnectionInfo(target), jdbcDriverInfo(), getContext());
}
@Override
public Connection createJdbcConnection(DatabaseTarget targetDatabase)
throws SQLException, BoxDatabaseException, IOException
{
return dataSourceBuilder(targetDatabase).create().getConnection();
}
@Override
public void executeJdbcScript(Reader scriptReader, DatabaseTarget targetDatabase)
throws IOException, SQLException, BoxDatabaseException
{
try (Connection con = createJdbcConnection(targetDatabase);
JdbcSqlRunner sqlRunner = new JdbcSqlRunner(con, getContext().getLog()))
{
sqlRunner.executeSql(new BufferedReader(scriptReader));
}
}
@Override
protected void executeScriptFile(Path scriptFile, DatabaseTarget targetDatabase, Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
if (!Files.exists(scriptFile))
throw new NoSuchFileException(scriptFile.toString());
RunVolumeConfiguration volumes = new RunVolumeConfiguration.Builder()
.bind(Arrays.asList(scriptFile.getParent().toAbsolutePath().toString() + ":/data/scripts:ro"))
.build();
executeCockroachSql(volumes, "< /data/scripts/" + scriptFile.getFileName().toString(), targetDatabase, timeout);
}
@Override
public void executeSql(String sql, DatabaseTarget targetDatabase, Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
executeCockroachSql(new RunVolumeConfiguration.Builder().build(), "--execute " + ScriptUtils.shellEscape(sql), targetDatabase, timeout);
}
@Override
public void backup(Path backupFile, BackupFileTypeHint backupFileTypeHint)
throws BoxDatabaseException, IOException, SQLException
{
Path backupDirectory = backupFile.getParent();
if (!Files.exists(backupDirectory))
Files.createDirectories(backupDirectory);
//Mount backup file as volume to /data/backup
RunVolumeConfiguration volumes = new RunVolumeConfiguration.Builder()
.bind(Arrays.asList(backupDirectory.toAbsolutePath().toString() + ":/data/backup"))
.build();
//Run the dump
String preArgs = getBoxConfiguration().getDatabaseName();
String postArgs = " > /data/backup/" + backupFile.getFileName().toString();
executeCockroachCommand(volumes, DatabaseTarget.USER, "dump", preArgs, postArgs, getProjectConfiguration().getBackupTimeout());
}
@Override
public void restore(Path backupFile)
throws BoxDatabaseException, IOException, SQLException
{
executeScriptFile(backupFile, DatabaseTarget.USER, getProjectConfiguration().getBackupTimeout());
}
private void executeCockroachSql(RunVolumeConfiguration volumes, String cockroachSqlArgs, DatabaseTarget targetDatabase,
Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
executeCockroachCommand(volumes, targetDatabase, "sql", "", cockroachSqlArgs, timeout);
}
private void executeCockroachCommand(RunVolumeConfiguration volumes,
DatabaseTarget target,
String command,
String preArgs, String postArgs,
Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
RunService runService = getContext().getDockerServiceHub().getRunService();
DockerAccess docker = getContext().getDockerServiceHub().getDockerAccess();
Properties projectProperties = getProjectConfiguration().getProjectProperties();
GavLabel pomLabel = getProjectConfiguration().getPomLabel();
PortMapping mappedPorts = new PortMapping(Collections.emptyList(), projectProperties);
int cockroachSqlMaxExecutionTimeSeconds = Math.toIntExact(timeout.getSeconds());
LogConfiguration logConfig = new LogConfiguration.Builder()
.enabled(true)
.prefix("cockroach")
.build();
RunImageConfiguration.Builder cockroachSqlRunConfigBuilder = new RunImageConfiguration.Builder()
.links(Collections.singletonList(containerName() + ":cockroachdb"))
.containerNamePattern("%a")
.cmd("unused") //not used but must be non-null - is replaced later
//.env(Collections.singletonMap("PGPASSWORD", targetDatabase.password(getBoxConfiguration())))
.volumes(volumes)
.entrypoint(new Arguments(ImmutableList.of("")))
.log(logConfig);
RunImageConfiguration cockroachSqlRunConfig = cockroachSqlRunConfigBuilder.build();
cockroachSqlRunConfig.getCmd().setExec(
Arrays.asList("sh", "-c", "/cockroach/cockroach.sh " + command + " " + preArgs + " --insecure --url " + cockroachDbUrl(target) + " " + postArgs));
cockroachSqlRunConfig.getCmd().setShell(null);
ImageConfiguration psqlConfig = new ImageConfiguration.Builder()
.runConfig(cockroachSqlRunConfig)
.name(dockerImageName())
.alias(containerName() + "-cockroach")
.build();
LogDispatcher dispatcher = new LogDispatcher(getContext().getDockerServiceHub().getDockerAccess());
Date buildTimestamp = new Date();
String containerId = runService.createAndStartContainer(psqlConfig, mappedPorts, pomLabel, projectProperties, getProjectConfiguration().getBaseDirectory(), null, buildTimestamp);
Container container = null;
try
{
getContext().getLog().debug("Cockroach-sql running in container " + containerId);
dispatcher.trackContainerLog(containerId, new LogOutputSpec.Builder()
.logStdout(true)
.prefix("cockroach-sql> ")
.build());
}
finally
{
try
{
container = waitForContainerToFinish(containerId, cockroachSqlMaxExecutionTimeSeconds);
}
catch (DockerAccessException e)
{
getContext().getLog().warn("Error shutting down Cockroach-SQL container", e);
}
try
{
boolean removeVolumes = true;
docker.removeContainer(containerId, removeVolumes);
}
catch (DockerAccessException e)
{
getContext().getLog().warn("Error removing Cockroach-SQL container", e);
}
}
int exitCode = readExitCodeFromContainer(container);
if (exitCode != 0)
throw new SQLException("Cockroach-SQL exit code: " + exitCode);
getContext().getLog().debug("All done");
}
}