All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.net.causal.maven.plugins.boxdb.vagrant.LocalVagrant Maven / Gradle / Ivy

There is a newer version: 3.3
Show newest version
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 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 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 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 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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy