au.net.causal.maven.plugins.boxdb.AbstractDatabaseMojo Maven / Gradle / Ivy
package au.net.causal.maven.plugins.boxdb;
import au.net.causal.maven.plugins.boxdb.db.BoxConfiguration;
import au.net.causal.maven.plugins.boxdb.db.BoxContext;
import au.net.causal.maven.plugins.boxdb.db.BoxDatabase;
import au.net.causal.maven.plugins.boxdb.db.BoxDatabaseException;
import au.net.causal.maven.plugins.boxdb.db.BoxDatabaseFactory;
import au.net.causal.maven.plugins.boxdb.db.BoxLookup;
import au.net.causal.maven.plugins.boxdb.db.DatabaseLog;
import au.net.causal.maven.plugins.boxdb.db.DatabaseStage;
import au.net.causal.maven.plugins.boxdb.db.DockerService;
import au.net.causal.maven.plugins.boxdb.db.ExecutionMode;
import au.net.causal.maven.plugins.boxdb.db.ProjectConfiguration;
import au.net.causal.maven.plugins.boxdb.db.ScriptExecution;
import au.net.causal.maven.plugins.boxdb.db.ScriptSelection;
import io.fabric8.maven.docker.config.ImagePullPolicy;
import io.fabric8.maven.docker.service.ImagePullManager;
import io.fabric8.maven.docker.service.ImagePullManager.CacheStore;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.shared.filtering.MavenReaderFilter;
import org.apache.maven.shared.filtering.MavenResourcesFiltering;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.DependencyResolutionException;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public abstract class AbstractDatabaseMojo extends AbstractDockerDbMojo
{
/**
* Chooses the database type to use. For example: postgres. Use the versions
goal
* to list all available database types and versions.
*/
@Parameter(property = "db.type")
private String databaseType;
/**
* Chooses the version fo the database to use. If not specified, the default version for the given database
* type will be used. Use the versions
goal to list all available database types and versions.
*/
@Parameter(property = "db.version")
private String databaseVersion;
/**
* The name of the database to create. Some database implementations support hosting multiple databases in the
* one container. This controls the name of this database that will be generated. Might not be used on all
* database implementations.
*/
@Parameter(property = "db.name")
private String databaseName;
/**
* The name to give the database container. For Docker-based databases, this controls the Docker container name.
* For Vagrant-based databases, this controls the Vagrant box name. Not used for non-container-based
* databases. Defaults to a name derived from the database name.
*/
@Parameter(property = "db.containerName")
private String containerName;
/**
* For file-based databases, controls the name of the database file or directory.
*/
@Parameter(property = "db.file")
private File databaseFile;
/**
* If specified, this backup file will be used to restore data from the database. The backup file can be generated
* using the backup
goal, or using database-specific tools.
*/
@Parameter(property = "db.restoreFile")
private File databaseRestoreFile;
/**
* The name of the user that will own the target database. This user will typically only
* have access to the target database. This user is created when setting up the database.
* Defaults to a database-specific username.
*/
@Parameter(property = "db.user")
private String databaseUser;
/**
* The password to use for the user that will own the target database. This user will typically only
* have access to the target database.
*/
@Parameter(property = "db.password")
private String databasePassword;
/**
* The collation to use for creating the database. Default is dependent on database type.
*
* @since 2.0
*/
@Parameter(property = "db.collation")
private String databaseCollation;
/**
* The encoding to use for creating the database. Default is dependent on database type.
*
* @since 2.0
*/
@Parameter(property = "db.encoding")
private String databaseEncoding;
/**
* Controls whether the database image and binaries are checked for updates in remote repositories.
* Some database images, such as Docker images, may be updated remotely. When this option is turned on, the plugin
* will check for updates and download them if available. When turned off, images are only downloaded when it does
* not exist locally.
*
*
* This global setting overrides any configured setting in each database configuration.
*
* @since 3.0
*/
//Note: db.updateImage is referred to in prepareMojo() strings
@Parameter(property = "db.updateImage")
private Boolean updateImage;
/**
* The port to run the database on. Default is dependent on database type, and might not be configurable for all
* database types.
*
* @since 3.0
*/
@Parameter(property = "db.port")
private Integer databasePort;
/**
* Shortcut for setting additional box database configuration options globally. Available options are dependent on database type.
*/
@Parameter
private Map databaseConfiguration = new LinkedHashMap<>();
/**
* Only used to allow databaseConfiguration parameter to be set from the command line. In plugin
* configurations, only databaseConfiguration should be used. Each entry is in the form key=value.
*/
@Parameter(property = "db.configuration")
private List databaseConfigurationList = new ArrayList<>();
@Parameter(property = "db.initialize")
private Boolean databaseInitialize;
/**
* Maximum time to wait in seconds for database to start up.
*/
@Parameter(property = "boxdb.startupTimeout", defaultValue = "1800", required = true)
protected long startupTimeout;
/**
* Maximum time in seconds a SQL script may run before it is terminated. Timeouts may not have effect on all database types.
*/
@Parameter(property = "boxdb.scriptTimeout", defaultValue = "3600", required = true)
protected long scriptTimeout;
/**
* Maximum time in seconds a database backup or restore may run before it is terminated. Timeouts may not have effect
* on all database types.
*/
@Parameter(property = "boxdb.backupTimeout", defaultValue = "3600", required = true)
protected long backupTimeout;
/**
* Time to wait in milliseconds during graceful database stop before doing a forced kill. Timeouts may not have effect on all database types.
* Timeout of zero means wait indefinitely.
*/
@Parameter(property = "boxdb.killTimeout", defaultValue = "0", required = true)
protected long killTimeout;
/**
* Amount of time in milliseconds to wait between polling when checking database operations, scripts, etc.
* When polling is used depends on database type.
*/
@Parameter(property = "boxdb.pollTimeMillis", defaultValue = "300", required = true)
protected long pollTimeMillis;
/**
* If true, passwords for connections are displayed in console output. If false (the default), they are not.
*/
@Parameter(property = "boxdb.displayPasswords", defaultValue = "false")
protected boolean displayPasswords;
@Parameter(defaultValue = "${project.build.directory}/boxdb", required = true)
protected File workDirectory;
@Parameter(defaultValue = "${user.home}/.boxdb", required = true)
protected File globalConfigDirectory;
@Parameter
protected BoxConfiguration box = new BoxConfiguration();
@Parameter(defaultValue = ScriptExecution.DEFAULT_BASE_DIRECTORY)
private File defaultScriptDirectory;
@Parameter(property = "db.deleteOnStop")
protected boolean deleteOnStop;
/**
* Additional Maven artifacts to readTags for BoxDatabase
implementations.
* Each element in the list is a string in the form groupId:artifactId:version.
*
*
* This can be a handy alternative to specifying additional dependency
elements when configuring the
* plugin, especially when running this Maven plugin without a Maven project.
*/
@Parameter(property = "boxdb.artifacts")
private List additionalBoxArtifacts = new ArrayList<>();
/**
* If true, completely skip execution of BoxDB. Useful for skipping execution from command line.
*/
@Parameter(property = "boxdb.skip", defaultValue = "false")
private boolean skipExecution;
@Component
protected MavenResourcesFiltering resourcesFiltering;
@Component
protected MavenReaderFilter readerFilter;
@Component
protected RepositorySystem repositorySystem;
@Component
protected ArchiverManager archiverManager;
@Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
protected RepositorySystemSession repoSession;
@Parameter(defaultValue = "${project.remotePluginRepositories}", readonly = true)
protected List remoteRepos;
protected final List tempFilesCreated = new ArrayList<>();
/**
* Cache to create classloaders at static level to prevent as much as possible to create too many classloaders.
*/
protected static final ClassLoaderCache classLoaderCache = new ClassLoaderCache();
@Override
public void execute() throws MojoExecutionException, MojoFailureException
{
if (skipExecution)
{
getLog().info("Skipping execution");
return;
}
//If we don't have a real project then use tempdir as work directory
boolean usingTempDirectory = false;
if (project == null || project.getFile() == null)
{
//Can't do this - to map scripts to docker machine they need to be inside user dir on Mac
//workDirectory = new File(System.getProperty("java.io.tmpdir"));
workDirectory = new File(".").getAbsoluteFile();
usingTempDirectory = true;
}
if (databaseType != null)
box.setDatabaseType(databaseType);
if (databaseVersion != null)
box.setDatabaseVersion(databaseVersion);
if (containerName != null)
box.setContainerName(containerName);
if (databaseName != null)
box.setDatabaseName(databaseName);
if (databaseFile != null)
box.setDatabaseFile(databaseFile.toPath());
if (databaseRestoreFile != null)
box.setRestoreFile(databaseRestoreFile);
if (databaseUser != null)
box.setDatabaseUser(databaseUser);
if (databasePassword != null)
box.setDatabasePassword(databasePassword);
if (databaseCollation != null)
box.setDatabaseCollation(databaseCollation);
if (databaseEncoding != null)
box.setDatabaseEncoding(databaseEncoding);
if (databasePort != null)
box.setDatabasePort(databasePort);
if (!databaseConfigurationList.isEmpty())
fillInMapFromListParameter(databaseConfiguration, databaseConfigurationList);
if (!databaseConfiguration.isEmpty())
box.getConfiguration().putAll(databaseConfiguration);
if (databaseInitialize != null)
box.setInitializeDatabase(databaseInitialize);
if (updateImage != null)
box.setUpdateImage(updateImage);
//Fill in box default scripts if needed
if (box.getScriptExecutions().isEmpty())
box.setScriptExecutions(defaultScriptExecutions());
try
{
super.execute();
}
finally
{
//If we don't have a project we have used the user's temp directory
//so try and clean this up after execution
if (usingTempDirectory)
{
//Delete in reverse order, so we get directories last
Collections.reverse(tempFilesCreated);
for (Path tempFile : tempFilesCreated)
{
try
{
getLog().info("Delete temp file " + tempFile);
Files.delete(tempFile);
}
catch (IOException e)
{
getLog().warn("Error deleting temp file " + tempFile);
}
}
}
}
}
private void fillInMapFromListParameter(Map super String, ? super String> map, List list)
{
for (String item : list)
{
String[] kv = item.split(Pattern.quote("="), 2);
if (kv.length == 2)
map.put(kv[0], kv[1]);
}
}
private List defaultScriptExecutions()
{
ScriptExecution createExecution = new ScriptExecution();
createExecution.setDirectory(defaultScriptDirectory);
createExecution.setIgnoreMissing(true);
createExecution.setSelection(ScriptSelection.FIRST);
createExecution.setStage(DatabaseStage.CREATE);
createExecution.setMode(ExecutionMode.NATIVE);
createExecution.getScripts().add("create-${box.databaseType}-${box.databaseVersion}.sql");
createExecution.getScripts().add("create-${box.databaseType}-${box.databaseVersion.major}.sql");
createExecution.getScripts().add("create-${box.databaseType}.sql");
createExecution.getScripts().add("create.sql");
ScriptExecution createJdbcExecution = new ScriptExecution();
createJdbcExecution.setDirectory(defaultScriptDirectory);
createJdbcExecution.setIgnoreMissing(true);
createJdbcExecution.setSelection(ScriptSelection.FIRST);
createJdbcExecution.setStage(DatabaseStage.CREATE);
createJdbcExecution.setMode(ExecutionMode.JDBC);
createJdbcExecution.getScripts().add("create-jdbc-${box.databaseType}-${box.databaseVersion}.sql");
createJdbcExecution.getScripts().add("create-jdbc-${box.databaseType}-${box.databaseVersion.major}.sql");
createJdbcExecution.getScripts().add("create-jdbc-${box.databaseType}.sql");
createJdbcExecution.getScripts().add("create-jdbc.sql");
ScriptExecution setupExecution = new ScriptExecution();
setupExecution.setDirectory(defaultScriptDirectory);
setupExecution.setIgnoreMissing(true);
setupExecution.setSelection(ScriptSelection.FIRST);
setupExecution.setStage(DatabaseStage.SETUP);
setupExecution.setMode(ExecutionMode.NATIVE);
setupExecution.getScripts().add("setup-${box.databaseType}-${box.databaseVersion}.sql");
setupExecution.getScripts().add("setup-${box.databaseType}-${box.databaseVersion.major}.sql");
setupExecution.getScripts().add("setup-${box.databaseType}.sql");
setupExecution.getScripts().add("setup.sql");
ScriptExecution setupJdbcExecution = new ScriptExecution();
setupJdbcExecution.setDirectory(defaultScriptDirectory);
setupJdbcExecution.setIgnoreMissing(true);
setupJdbcExecution.setSelection(ScriptSelection.FIRST);
setupJdbcExecution.setStage(DatabaseStage.SETUP);
setupJdbcExecution.setMode(ExecutionMode.JDBC);
setupJdbcExecution.getScripts().add("setup-jdbc-${box.databaseType}-${box.databaseVersion}.sql");
setupJdbcExecution.getScripts().add("setup-jdbc-${box.databaseType}-${box.databaseVersion.major}.sql");
setupJdbcExecution.getScripts().add("setup-jdbc-${box.databaseType}.sql");
setupJdbcExecution.getScripts().add("setup-jdbc.sql");
ScriptExecution postRestoreExecution = new ScriptExecution();
postRestoreExecution.setDirectory(defaultScriptDirectory);
postRestoreExecution.setIgnoreMissing(true);
postRestoreExecution.setSelection(ScriptSelection.FIRST);
postRestoreExecution.setStage(DatabaseStage.POST_RESTORE);
postRestoreExecution.setMode(ExecutionMode.NATIVE);
postRestoreExecution.getScripts().add("postrestore-${box.databaseType}-${box.databaseVersion}.sql");
postRestoreExecution.getScripts().add("postrestore-${box.databaseType}-${box.databaseVersion.major}.sql");
postRestoreExecution.getScripts().add("postrestore-${box.databaseType}.sql");
postRestoreExecution.getScripts().add("postrestore.sql");
ScriptExecution postRestoreJdbcExecution = new ScriptExecution();
postRestoreJdbcExecution.setDirectory(defaultScriptDirectory);
postRestoreJdbcExecution.setIgnoreMissing(true);
postRestoreJdbcExecution.setSelection(ScriptSelection.FIRST);
postRestoreJdbcExecution.setStage(DatabaseStage.POST_RESTORE);
postRestoreJdbcExecution.setMode(ExecutionMode.JDBC);
postRestoreJdbcExecution.getScripts().add("postrestore-jdbc-${box.databaseType}-${box.databaseVersion}.sql");
postRestoreJdbcExecution.getScripts().add("postrestore-jdbc-${box.databaseType}-${box.databaseVersion.major}.sql");
postRestoreJdbcExecution.getScripts().add("postrestore-jdbc-${box.databaseType}.sql");
postRestoreJdbcExecution.getScripts().add("postrestore-jdbc.sql");
return Arrays.asList(createExecution, createJdbcExecution,
setupExecution, setupJdbcExecution,
postRestoreExecution, postRestoreJdbcExecution);
}
protected ClassLoader createBoxClassLoader()
throws MojoExecutionException
{
getLog().debug("Additional artifacts: " + additionalBoxArtifacts);
if (additionalBoxArtifacts == null || additionalBoxArtifacts.isEmpty())
return AbstractDatabaseMojo.class.getClassLoader();
try
{
List jarFiles = DependencyUtils.resolveDependenciesFromStrings(additionalBoxArtifacts, repositorySystem,
repoSession, remoteRepos);
List jarUrls = new ArrayList<>(jarFiles.size());
for (Path jarFile : jarFiles)
{
jarUrls.add(jarFile.toUri().toURL());
}
URL[] jarUrlArray = jarUrls.stream().toArray(URL[]::new);
return URLClassLoader.newInstance(jarUrlArray, AbstractDatabaseMojo.class.getClassLoader());
}
catch (DependencyResolutionException e)
{
throw new MojoExecutionException("Failed to resolve additional box artifacts: " + e.getMessage(), e);
}
catch (MalformedURLException e)
{
throw new MojoExecutionException("Failed to generate URLs from dependency JARs: " + e, e);
}
}
protected BoxDatabaseFactory databaseFactory(ExceptionalSupplier serviceHub)
throws MojoExecutionException, BoxDatabaseException
{
ClassLoader boxClassLoader = createBoxClassLoader();
String dbType = box.getDatabaseType();
BoxLookup lookup = new BoxLookup(getLog(), boxClassLoader);
BoxDatabaseFactory factory = lookup.findFactory(dbType);
if (factory == null)
{
throw new MojoExecutionException("Database type '" + dbType + "' not supported. Available database types: " +
lookup.getAvailableBoxFactoryNames());
}
return factory;
}
protected ProjectConfiguration projectConfiguration()
{
return new ProjectConfiguration(project, settings, Duration.ofSeconds(scriptTimeout),
Duration.ofSeconds(backupTimeout), Duration.ofMillis(pollTimeMillis),
killTimeout == 0 ? null : Duration.ofMillis(killTimeout));
}
protected BoxContext boxContext(ExceptionalSupplier serviceHub)
{
CacheStore sessionCacheStore = getSessionCacheStore();
ImagePullManager updatingImagePullManager = new ImagePullManager(sessionCacheStore,
ImagePullPolicy.Always.name(), null);
ImagePullManager nonUpdatingImagePullManager = new ImagePullManager(sessionCacheStore,
ImagePullPolicy.IfNotPresent.name(), null);
return new BoxContext(serviceHub, authConfigFactory, workDirectory.toPath(), globalConfigDirectory.toPath(),
getLog(), logSpecFactory, session, resourcesFiltering,
readerFilter, repositorySystem, repoSession, remoteRepos, archiverManager, classLoaderCache,
updatingImagePullManager, nonUpdatingImagePullManager, getAuthConfig(), isSkipExtendedAuth());
}
protected BoxDatabase database(ExceptionalSupplier serviceHub)
throws MojoExecutionException, BoxDatabaseException
{
BoxDatabaseFactory factory = databaseFactory(serviceHub);
return factory.create(box, projectConfiguration(), boxContext(serviceHub));
}
protected void stopDatabaseContainer(BoxDatabase boxDatabase)
throws MojoExecutionException, BoxDatabaseException
{
boolean exists = true;
if (!boxDatabase.exists())
{
getLog().warn("Database '" + boxDatabase.getName() + "' does not exist");
exists = false;
}
else if (!boxDatabase.isRunning())
getLog().info("Database '" + boxDatabase.getName() + "' is not running");
else
{
getLog().info("Stopping database '" + boxDatabase.getName() + "'");
boxDatabase.stop();
getLog().info("Database stopped.");
}
if (exists && deleteOnStop)
{
getLog().info("Deleting database '" + boxDatabase.getName() + "'");
boxDatabase.delete();
}
}
protected void dumpLog(DatabaseLog dbLog)
throws BoxDatabaseException, IOException
{
getLog().info(dbLog.getName() + ":");
try (StringWriter w = new StringWriter())
{
dbLog.save(w);
getLog().info(w.getBuffer());
}
}
protected void dumpLogToError(DatabaseLog dbLog)
throws BoxDatabaseException, IOException
{
getLog().error(dbLog.getName() + ":");
try (StringWriter w = new StringWriter())
{
dbLog.save(w);
getLog().error(w.getBuffer());
}
}
}