au.net.causal.maven.plugins.boxdb.vagrant.LocalVagrant 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.vagrant;
import com.google.common.annotations.VisibleForTesting;
import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.StreamConsumer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Vagrant implementation that runs a local Vagrant process.
*/
public class LocalVagrant implements Vagrant
{
private final Commandline baseCommandLine;
private final Log log;
/**
* @param baseCommandLine command line that has just the Vagrant executable, no parameters.
* @param log log that will receive output from the process.
*/
public LocalVagrant(Commandline baseCommandLine, Log log)
{
Objects.requireNonNull(baseCommandLine, "baseCommandLine == null");
Objects.requireNonNull(log, "log == null");
this.baseCommandLine = baseCommandLine;
this.log = log;
}
/**
* Convenience converter varargs to String array.
*/
private static String[] args(String... args)
{
return args;
}
private void configureBaseEnvironment(Commandline commandLine, BaseOptions options)
{
options.getEnvironment().forEach(commandLine::addEnvironment);
}
private void configureEnvironment(Commandline commandLine, InstanceOptions options)
{
commandLine.setWorkingDirectory(options.getBaseDirectory().toFile());
if (options.getVagrantFileName() != null)
commandLine.addEnvironment("VAGRANT_VAGRANTFILE", options.getVagrantFileName());
configureBaseEnvironment(commandLine, options);
}
private String executeCommand(Commandline commandLine, Duration timeout)
throws VagrantException
{
CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
try
{
int exitCode = CommandLineUtils.executeCommandLine(commandLine, out, err, Math.toIntExact(timeout.getSeconds()));
if (exitCode != 0)
throw new VagrantException("Vagrant error " + exitCode + ": " + err.getOutput());
}
catch (CommandLineException e)
{
throw new VagrantException("Error running Vagrant: " + e, e);
}
return out.getOutput();
}
private void executeCommandWithLogging(Commandline commandLine, Duration timeout)
throws VagrantException
{
StreamConsumer out = new LoggingConsumer(log, "vagrant> ");
StreamConsumer err = new LoggingConsumer(log, "vagrant> ");
try
{
int exitCode = CommandLineUtils.executeCommandLine(commandLine, out, err, Math.toIntExact(timeout.getSeconds()));
if (exitCode != 0)
throw new VagrantException("Vagrant error " + exitCode + ".");
}
catch (CommandLineException e)
{
throw new VagrantException("Error running Vagrant: " + e, e);
}
}
@Override
public void up(UpOptions options) throws VagrantException
{
Commandline statusCommand = (Commandline)baseCommandLine.clone();
statusCommand.addArguments(args("up", options.getBoxName()));
configureEnvironment(statusCommand, options);
executeCommandWithLogging(statusCommand, options.getTimeout());
}
@Override
public void destroy(DestroyOptions options) throws VagrantException
{
Commandline statusCommand = (Commandline)baseCommandLine.clone();
statusCommand.addArguments(args("destroy", "--force", options.getBoxName()));
configureEnvironment(statusCommand, options);
executeCommandWithLogging(statusCommand, options.getTimeout());
}
@Override
public void halt(HaltOptions options) throws VagrantException
{
Commandline statusCommand = (Commandline)baseCommandLine.clone();
statusCommand.addArguments(args("halt", options.getBoxName()));
configureEnvironment(statusCommand, options);
executeCommandWithLogging(statusCommand, options.getTimeout());
}
@Override
public void provision(ProvisionOptions options) throws VagrantException
{
Commandline statusCommand = (Commandline)baseCommandLine.clone();
statusCommand.addArguments(args("provision", options.getBoxName()));
if (!options.getProvisioners().isEmpty())
{
statusCommand.addArguments(args("--provision-with"));
statusCommand.addArguments(args(options.getProvisioners().stream().collect(Collectors.joining(","))));
}
configureEnvironment(statusCommand, options);
executeCommandWithLogging(statusCommand, options.getTimeout());
}
@Override
public BoxStatus status(StatusOptions options)
throws VagrantException
{
Commandline statusCommand = (Commandline)baseCommandLine.clone();
statusCommand.addArguments(args("status", options.getBoxName()));
configureEnvironment(statusCommand, options);
String output = executeCommand(statusCommand, options.getTimeout());
return parseStatusOutput(output, options.getBoxName());
}
@Override
public List extends BoxDefinition> boxList(BoxListOptions options) throws VagrantException
{
Commandline listCommand = (Commandline)baseCommandLine.clone();
listCommand.addArguments(args("box", "list"));
String output = executeCommand(listCommand, options.getTimeout());
return parseBoxListOutput(output);
}
@Override
public void boxRemove(BoxRemoveOptions options) throws VagrantException
{
Commandline removeCommand = (Commandline)baseCommandLine.clone();
configureBaseEnvironment(removeCommand, options);
removeCommand.addArguments(args("box", "remove", options.getBox().getName()));
removeCommand.addArguments(args("--provider", options.getBox().getProvider()));
removeCommand.addArguments(args("--box-version", options.getBox().getVersion()));
if (options.isForce())
removeCommand.addArguments(args("--force"));
executeCommandWithLogging(removeCommand, options.getTimeout());
}
@Override
public List extends Plugin> pluginList(PluginListOptions options) throws VagrantException
{
Commandline listCommand = (Commandline)baseCommandLine.clone();
listCommand.addArguments(args("plugin", "list"));
String output = executeCommand(listCommand, options.getTimeout());
return parsePluginListOutput(output);
}
@Override
public void pluginInstall(PluginInstallOptions options)
throws VagrantException
{
Commandline installCommand = (Commandline)baseCommandLine.clone();
installCommand.addArguments(args("plugin", "install", options.getPluginName()));
executeCommandWithLogging(installCommand, options.getTimeout());
}
@VisibleForTesting
List extends Plugin> parsePluginListOutput(String output)
throws VagrantException
{
//e.g.
/*
vagrant-exec (0.5.2)
vagrant-share (1.1.5, system)
vagrant-vbguest (0.12.0)
*/
List results = new ArrayList<>();
for (String line : output.split("[\\r\\n]+"))
{
line = line.trim();
if (!line.isEmpty())
results.add(parsePluginListLine(line));
}
return results;
}
@VisibleForTesting
Plugin parsePluginListLine(String line)
throws VagrantException
{
//e.g.
//vagrant-vbguest (0.12.0)
String[] tokens = line.split("([\\s\\(\\)])+");
if (tokens.length < 2)
throw new VagrantException("Failed to parse plugin list: " + line);
return new Plugin(tokens[0], joinRemainingTokens(tokens, 1, " "));
}
/**
* Join remaining elements in an array.
* e.g. joinRemainingTokens({"one", "two", "three"}, 1, " ")
-> "two three"
*/
private static String joinRemainingTokens(String[] elements, int fromIndex, String joiner)
{
StringBuilder buf = new StringBuilder();
for (int i = fromIndex; i < elements.length; i++)
{
buf.append(elements[i]);
if (i < (elements.length - 1))
buf.append(joiner);
}
return buf.toString();
}
@VisibleForTesting
List extends BoxDefinition> parseBoxListOutput(String output)
throws VagrantException
{
//e.g.
/*
debian/jessie64 (virtualbox, 8.3.0)
msabramo/mssqlserver2014express (virtualbox, 0.1)
*/
List results = new ArrayList<>();
for (String line : output.split("[\\r\\n]+"))
{
line = line.trim();
if (!line.isEmpty())
results.add(parseBoxListLine(line));
}
return results;
}
@VisibleForTesting
BoxDefinition parseBoxListLine(String line)
throws VagrantException
{
//e.g.
//debian/jessie64 (virtualbox, 8.3.0)
String[] tokens = line.split("([\\s\\(\\),])+");
if (tokens.length != 3)
throw new VagrantException("Failed to parse box list: " + line);
return new BoxDefinition(tokens[0], tokens[2], tokens[1]);
}
@VisibleForTesting
BoxStatus parseStatusOutput(String output, String boxName)
throws VagrantException
{
/*
Current machine states:
boxdb-sqlserver not created (virtualbox)
The environment has not yet been created. Run `vagrant up` to
...
*/
Pattern headerLinePattern = Pattern.compile(".*:$");
int blankLineCount = 0;
int headerLineCount = 0;
//Ignore first line with text
try (BufferedReader r = new BufferedReader(new StringReader(output)))
{
String line;
do
{
line = r.readLine();
if (line != null)
{
if (line.trim().isEmpty())
blankLineCount++;
else if (headerLinePattern.matcher(line).matches())
headerLineCount++;
else if (headerLineCount > 0 && blankLineCount > 0)
{
if (line.startsWith(boxName))
return parseStatusLine(line);
}
}
}
while (line != null);
}
catch (IOException e)
{
//Should not happen, all in memory
throw new VagrantException(e);
}
//If we get here we have no status
throw new VagrantException("Could not find status line for box '" + boxName + "' in output: " + output);
}
BoxStatus parseStatusLine(String line)
throws VagrantException
{
//box not created (virtualbox)
//box poweroff (virtualbox)
//box running (virtualbox)
Pattern statusLinePattern = Pattern.compile("\\S+\\s+([^\\(]+)");
Matcher matcher = statusLinePattern.matcher(line);
boolean matched = matcher.find();
if (!matched)
throw new VagrantException("Failed to parse status: " + line);
String statusText = matcher.group(1).trim();
switch (statusText)
{
case "not created":
return BoxStatus.NOT_CREATED;
case "poweroff":
return BoxStatus.STOPPED;
case "running":
return BoxStatus.RUNNING;
case "aborted":
return BoxStatus.ABORTED;
default:
throw new VagrantException("Unknown status: " + line);
}
}
/**
* Every line of output from the Vagrant process is sent to the log at INFO level.
*/
private static class LoggingConsumer implements StreamConsumer
{
private final Log log;
private final String prefix;
public LoggingConsumer(Log log, String prefix)
{
this.log = log;
this.prefix = prefix;
}
@Override
public void consumeLine(String line)
{
log.info(prefix + line);
}
}
}