Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
au.net.causal.maven.plugins.boxdb.db.DockerDatabase Maven / Gradle / Ivy
package au.net.causal.maven.plugins.boxdb.db;
import au.net.causal.maven.plugins.boxdb.DependencyUtils;
import au.net.causal.maven.plugins.boxdb.db.DockerHacks.ImageDetails;
import au.net.causal.maven.plugins.boxdb.db.DockerRegistry.ReadManifestResult;
import au.net.causal.maven.plugins.boxdb.db.DockerRegistry.ReadManifestResult.Type;
import au.net.causal.maven.plugins.boxdb.db.ImageComponent.ImageStatus;
import com.google.common.collect.ImmutableList;
import io.fabric8.maven.docker.access.AuthConfig;
import io.fabric8.maven.docker.access.DockerAccess;
import io.fabric8.maven.docker.access.DockerAccessException;
import io.fabric8.maven.docker.access.ExecException;
import io.fabric8.maven.docker.access.PortMapping;
import io.fabric8.maven.docker.access.log.LogCallback;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.config.RunImageConfiguration;
import io.fabric8.maven.docker.config.WaitConfiguration;
import io.fabric8.maven.docker.model.Container;
import io.fabric8.maven.docker.service.QueryService;
import io.fabric8.maven.docker.service.RunService;
import io.fabric8.maven.docker.util.ImageName;
import io.fabric8.maven.docker.util.GavLabel;
import io.fabric8.maven.docker.util.Timestamp;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.plexus.util.IOUtil;
import org.eclipse.aether.resolution.DependencyResolutionException;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
public abstract class DockerDatabase implements BoxDatabase
{
private final BoxContext context;
private final BoxConfiguration boxConfiguration;
private final ProjectConfiguration projectConfiguration;
private final DockerRegistry dockerRegistry;
public DockerDatabase(BoxConfiguration boxConfiguration, ProjectConfiguration projectConfiguration,
BoxContext context, DockerRegistry dockerRegistry)
{
this.boxConfiguration = boxConfiguration;
this.projectConfiguration = projectConfiguration;
this.context = context;
this.dockerRegistry = dockerRegistry;
}
protected BoxConfiguration getBoxConfiguration()
{
return boxConfiguration;
}
protected ProjectConfiguration getProjectConfiguration()
{
return projectConfiguration;
}
protected BoxContext getContext()
{
return context;
}
@Override
public void start() throws BoxDatabaseException
{
DockerAccess dockerAccess = context.getDockerServiceHub().getDockerAccess();
try
{
dockerAccess.startContainer(containerName());
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
@Override
public void stop() throws BoxDatabaseException
{
ImageConfiguration imageConfig = imageConfiguration();
try
{
//Resolve container name to ID first
//There's what looks like a bug in RunService.shutdown() that does a substring for a log message and assumes
//it's an ID instead of a name and has a particular length
Container container = context.getDockerServiceHub().getDockerAccess().getContainer(containerName());
//No need to stop a container that doesn't exist
if (container == null)
return;
String containerId = container.getId();
context.getDockerServiceHub().getRunService().stopContainer(containerId, imageConfig, true, false);
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
catch (ExecException e)
{
throw createBoxDatabaseException(e);
}
}
@Override
public void createAndStart() throws BoxDatabaseException
{
RunService runService = context.getDockerServiceHub().getRunService();
ImageConfiguration imageConfig = imageConfiguration();
Properties projProperties = projectConfiguration.getProjectProperties();
GavLabel pomLabel = projectConfiguration.getPomLabel();
PortMapping portMapping = runService.createPortMapping(imageConfig.getRunConfiguration(), projProperties);
try
{
String imageName = imageConfig.getName();
DockerPuller.pullImage(imageName, getBoxConfiguration(), getProjectConfiguration(), getContext());
Date buildTimestamp = new Date();
String containerId = runService.createAndStartContainer(imageConfig, portMapping, pomLabel, projProperties, getProjectConfiguration().getBaseDirectory(), null, buildTimestamp);
context.getLog().info("Created docker container: " + containerId);
}
catch (MojoExecutionException e)
{
throw new BoxDatabaseException(e);
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
protected Container findDockerContainer()
throws BoxDatabaseException
{
QueryService queryService = context.getDockerServiceHub().getQueryService();
try
{
return queryService.getContainer(containerName());
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
@Override
public boolean exists() throws BoxDatabaseException
{
return findDockerContainer() != null;
}
@Override
public boolean isRunning() throws BoxDatabaseException
{
Container container = findDockerContainer();
if (container == null)
return false;
return container.isRunning();
}
@Override
public void waitUntilStarted(Duration maxTimeToWait)
throws TimeoutException, BoxDatabaseException
{
DatabaseUtils.waitUntilTcpPortResponding(maxTimeToWait, this, context);
}
@Override
public void delete() throws BoxDatabaseException
{
DockerAccess dockerAccess = context.getDockerServiceHub().getDockerAccess();
try
{
boolean removeVolumes = true;
dockerAccess.removeContainer(containerName(), removeVolumes);
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
@Override
public void deleteImage() throws BoxDatabaseException
{
DockerAccess dockerAccess = context.getDockerServiceHub().getDockerAccess();
try
{
boolean force = false;
dockerAccess.removeImage(dockerImageName(), force);
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
/**
* @return the name of the docker container. Defaults to the name given in the configuration.
*/
protected String containerName()
{
return boxConfiguration.getContainerName();
}
/**
* @return the port the container runs the database on. This is not necessarily the port that is mapped on
* the host system.
*
* @see BoxConfiguration#getDatabasePort()
*/
protected abstract int containerDatabasePort();
/**
* @return the name of the docker image to build the container with. Defaults to
* databaseType :databaseVersion
*/
protected String dockerImageName()
{
return boxConfiguration.getDatabaseType() + ":" + boxConfiguration.getDatabaseVersion();
}
/**
* Customize the docker run image for the database.
* To augment default settings, call super.configureRunImage()
first.
*
* @param builder the builder to configure.
*/
protected void configureRunImage(RunImageConfiguration.Builder builder)
{
builder.containerNamePattern("%a")
.ports(ImmutableList.of(boxConfiguration.getDatabasePort() + ":" + containerDatabasePort()));
}
/**
* Customize the docker image configuration for the database.
* To augment default settings, call super.configureImage()
first.
*/
protected void configureImage(ImageConfiguration.Builder builder)
{
builder.name(dockerImageName())
.alias(containerName());
}
protected ImageConfiguration imageConfiguration()
{
RunImageConfiguration.Builder runBuilder = new RunImageConfiguration.Builder();
if (getProjectConfiguration().getKillTimeout() != null)
{
WaitConfiguration.Builder waitBuilder = new WaitConfiguration.Builder();
waitBuilder.kill(Math.toIntExact(getProjectConfiguration().getKillTimeout().toMillis()));
runBuilder.wait(waitBuilder.build());
}
configureRunImage(runBuilder);
RunImageConfiguration runConfig = runBuilder.build();
ImageConfiguration.Builder imageBuilder = new ImageConfiguration.Builder();
imageBuilder.runConfig(runConfig);
configureImage(imageBuilder);
return imageBuilder.build();
}
protected static BoxDatabaseException createBoxDatabaseException(DockerAccessException ex)
{
return new BoxDatabaseException(ex);
}
protected static BoxDatabaseException createBoxDatabaseException(ExecException ex)
{
return new BoxDatabaseException(ex);
}
@Override
public String getName()
{
return containerName();
}
protected AuthConfig prepareAuthConfig(ImageName image, String configuredRegistry, boolean isPush, boolean skipExtendedAuth) throws MojoExecutionException
{
String user = isPush?image.getUser():null;
String registry = image.getRegistry() != null?image.getRegistry():configuredRegistry;
Map authConfigMap = new HashMap<>();
return getContext().getAuthConfigFactory().createAuthConfig(isPush, skipExtendedAuth, authConfigMap, getProjectConfiguration().getSettings(), user, registry);
}
/**
* Attempt to read the exit code from the process. Not publicly accessible, but we can use reflection to get it
* from the JSON response.
*
* @param container the docker container information.
*
* @return the exit code of the process the container was initialized with, or -1 if not available.
*
* @throws BoxDatabaseException if in error occurs.
*/
protected int readExitCodeFromContainer(Container container)
throws BoxDatabaseException
{
Integer exitCode = container.getExitCode();
if (exitCode == null)
return -1;
else
return exitCode;
}
@Override
public void executeScript(Reader scriptReader, DatabaseTarget targetDatabase, Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
//Save script to file
Path tempDir = getContext().getTempDirectory();
Path file = Files.createTempFile(tempDir, getBoxConfiguration().getContainerName() + "-", ".sql");
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8))
{
IOUtil.copy(scriptReader, writer);
}
executeScriptFile(file, targetDatabase, timeout);
}
@Override
public void executeScript(URL script, DatabaseTarget targetDatabase, Duration timeout)
throws IOException, SQLException, BoxDatabaseException
{
//Save script to file if it's not already a file
Path file;
try
{
file = Paths.get(script.toURI());
}
catch (URISyntaxException e)
{
throw new IOException("Invalid URL: " + script.toExternalForm() + ": " + e, e);
}
catch (FileSystemNotFoundException e)
{
//If we get here, then we need to save to temporary file first
Path tempDir = getContext().getTempDirectory();
file = Files.createTempFile(tempDir, getBoxConfiguration().getContainerName() + "-", ".sql");
try (InputStream is = script.openStream())
{
Files.copy(is, file);
}
}
executeScriptFile(file, targetDatabase, timeout);
}
protected abstract void executeScriptFile(Path file, DatabaseTarget targetDatabase, Duration timeout)
throws IOException, SQLException, BoxDatabaseException;
@Override
public void restore(URL backupResource) throws BoxDatabaseException, IOException, SQLException
{
//Save backup to file if it's not already a file
Path file;
try
{
file = Paths.get(backupResource.toURI());
}
catch (URISyntaxException e)
{
throw new IOException("Invalid URL: " + backupResource.toExternalForm() + ": " + e, e);
}
catch (FileSystemNotFoundException e)
{
//If we get here, then we need to save to temporary file first
Path tempDir = getContext().getTempDirectory();
file = Files.createTempFile(tempDir, getBoxConfiguration().getContainerName() + "-", ".dbbackup");
try (InputStream is = backupResource.openStream())
{
Files.copy(is, file);
}
}
restore(file);
}
protected Container waitForContainerToFinish(String containerId, int timeoutInSeconds)
throws DockerAccessException, BoxDatabaseException
{
DockerAccess docker = getContext().getDockerServiceHub().getDockerAccess();
try
{
long startTime = System.currentTimeMillis();
long maxTime = startTime + timeoutInSeconds * 1000L;
Container container;
do
{
container = docker.getContainer(containerId);
if (container == null)
throw new BoxDatabaseException("Container " + containerId + " does not exist.");
Thread.sleep(getProjectConfiguration().getPollTime().toMillis());
if (System.currentTimeMillis() > maxTime)
{
docker.stopContainer(containerId, timeoutInSeconds);
throw new BoxDatabaseException("Timeout waiting for container to execute.");
}
}
while (container.isRunning());
return container;
}
catch (InterruptedException e)
{
docker.stopContainer(containerId, timeoutInSeconds);
throw new BoxDatabaseException("Interrupted waiting for container to finish");
}
}
@Override
public List extends DatabaseLog> logFiles() throws BoxDatabaseException, IOException
{
//If the container does not exist, there are no logs
if (findDockerContainer() == null)
return Collections.emptyList();
else
return Collections.singletonList(new MainDockerDatabaseLog());
}
/**
* Reads log files from the docker container.
*
* @param includeMain true to include the main docker container log as first item in the list, false to just
* use the files.
* @param logFileEncoding the encoding of the log files in the container.
* @param containerPath the path to read log files from in the container. Ends with '/' character.
* @param fileNames a list of file names relative to containerPath. Do not prefix with '/'.
*
* @return a list of log files.
*
* @throws BoxDatabaseException if an error occurs with Docker.
* @throws IOException if an I/O error occurs.
*/
protected List extends DatabaseLog> logFilesInContainer(boolean includeMain, Charset logFileEncoding,
String containerPath, String... fileNames)
throws BoxDatabaseException, IOException
{
List results = new ArrayList<>();
if (includeMain)
results.add(new MainDockerDatabaseLog());
DockerAccess docker = getContext().getDockerServiceHub().getDockerAccess();
Container container = findDockerContainer();
//Might not find container if it doesn't exist yet or has terminated unexpectedly
if (container == null)
return Collections.emptyList();
String containerId = container.getId();
//If the container is running, then we can use exec to display log
Path archive = Files.createTempFile(getContext().getTempDirectory(), "logarchive", ".tar");
DockerHacks dockerHacks = new DockerHacks();
dockerHacks.copyArchiveFromDocker(docker, containerId, archive, containerPath);
for (String fileName : fileNames)
{
results.add(new ContainerFileDatabaseLog(archive, fileName, logFileEncoding));
}
return results;
}
public class ContainerFileDatabaseLog implements DatabaseLog
{
private final Path tarFile;
private final String entryName;
private final Charset encoding;
public ContainerFileDatabaseLog(Path tarFile, String entryName, Charset encoding)
{
this.tarFile = tarFile;
this.entryName = entryName;
this.encoding = encoding;
}
@Override
public String getName() throws BoxDatabaseException
{
return entryName;
}
@Override
public void save(Writer w) throws BoxDatabaseException, IOException
{
try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(tarFile)))
{
TarArchiveEntry entry;
do
{
entry = tis.getNextTarEntry();
if (entry != null && entry.getName().endsWith("/" + entryName))
{
//Intentionally not closed because I don't want to close the tar input stream
InputStreamReader isr = new InputStreamReader(tis, encoding);
IOUtil.copy(isr, w);
}
}
while (entry != null);
}
}
}
public class MainDockerDatabaseLog implements DatabaseLog
{
@Override
public String getName() throws BoxDatabaseException
{
return "docker.log";
}
@Override
public void save(Writer w) throws BoxDatabaseException, IOException
{
DockerAccess docker = getContext().getDockerServiceHub().getDockerAccess();
Container container = findDockerContainer();
//Might not find container if it doesn't exist yet or has terminated unexpectedly
if (container == null)
return;
String containerId = container.getId();
final AtomicReference exceptionHolder = new AtomicReference<>();
docker.getLogSync(containerId, new LogCallback()
{
@Override
public void log(int type, Timestamp timestamp, String txt)
{
try
{
w.write(txt);
w.write('\n');
}
catch (IOException e)
{
exceptionHolder.set(e);
}
}
@Override
public void error(String s)
{
getContext().getLog().error(s);
}
@Override
public void open() throws FileNotFoundException
{
}
@Override
public void close()
{
}
});
if (exceptionHolder.get() != null)
throw exceptionHolder.get();
}
}
@Override
public void prepareImage()
throws BoxDatabaseException
{
//JDBC drivers
try
{
DependencyUtils.resolveDependencies(jdbcDriverInfo().getDependencies(),
getContext().getRepositorySystem(),
getContext().getRepositorySystemSession(),
getContext().getRemoteRepositories());
}
catch (DependencyResolutionException e)
{
throw new BoxDatabaseException("Failed to download dependencies for database: " + e.getMessage(), e);
}
//Docker image
try
{
DockerPuller.pullImage(dockerImageName(), getBoxConfiguration(), getProjectConfiguration(), getContext());
}
catch (MojoExecutionException e)
{
throw new BoxDatabaseException(e);
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
}
protected ImageComponent checkDockerDatabaseImage(String remoteDockerRepositoryName)
throws BoxDatabaseException
{
return checkDockerDatabaseImage(remoteDockerRepositoryName, getBoxConfiguration().getDatabaseVersion());
}
protected ImageComponent checkDockerDatabaseImage(String remoteDockerRepositoryName, String imageVersion)
throws BoxDatabaseException
{
String imageName = dockerImageName();
ImageStatus dockerImageStatus;
try
{
DockerHacks dockerHacks = new DockerHacks();
ImageDetails localImageDetails = dockerHacks.inspectImage(getContext().getDockerServiceHub().getDockerAccess(), imageName);
boolean hasLocalImage = (localImageDetails != null);
ReadManifestResult repoImageDetails = dockerRegistry.readManifest(remoteDockerRepositoryName, imageVersion);
boolean hasRemoteImage = (repoImageDetails.getType() == Type.FOUND || repoImageDetails.getType() == Type.OLD_MANIFEST);
if (!hasRemoteImage)
{
if (!hasLocalImage)
dockerImageStatus = ImageStatus.NOT_FOUND;
else
dockerImageStatus = ImageStatus.LOCAL_ONLY;
}
else
{
if (!hasLocalImage)
dockerImageStatus = ImageStatus.NOT_DOWNLOADED;
else
{
//If we get here, we have both local and remote images
//Check if remote/local are different, which would indicate the remote has been updated
//If the remote has an image ID, compare those
boolean remoteDifferentFromLocal;
if (repoImageDetails.getImageId() != null)
remoteDifferentFromLocal = !repoImageDetails.getImageId().equals(localImageDetails.getId());
else if (repoImageDetails.getDigest() != null)
{
//Might be an old manifest version where the remote doesn't have image ID, but we still have
//repo hash
remoteDifferentFromLocal = localImageDetails.getUnprefixedRepoDigests().contains(repoImageDetails.getDigest());
}
else //Remote has no image ID or digest, could be really old registry? Just assume no changes then.
remoteDifferentFromLocal = false;
if (remoteDifferentFromLocal)
dockerImageStatus = ImageStatus.REMOTE_UPDATE_AVAILABLE;
else
dockerImageStatus = ImageStatus.DOWNLOADED;
}
}
}
catch (DockerAccessException e)
{
throw createBoxDatabaseException(e);
}
catch (IOException e)
{
throw new BoxDatabaseException("Error reading manifest from Docker registry: " + e.getMessage(), e);
}
return new ImageComponent("Docker image", imageName, dockerImageStatus);
}
}