8248f52ff7
Update error detection to return an error if the process fails instead of testing for content in stderr.
277 lines
7.2 KiB
Go
277 lines
7.2 KiB
Go
package vagrant
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-version"
|
|
)
|
|
|
|
const VAGRANT_MIN_VERSION = ">= 2.0.2"
|
|
|
|
type Vagrant_2_2_Driver struct {
|
|
vagrantBinary string
|
|
VagrantCWD string
|
|
}
|
|
|
|
// Calls "vagrant init"
|
|
func (d *Vagrant_2_2_Driver) Init(args []string) error {
|
|
_, _, err := d.vagrantCmd(append([]string{"init"}, args...)...)
|
|
return err
|
|
}
|
|
|
|
// Calls "vagrant add"
|
|
func (d *Vagrant_2_2_Driver) Add(args []string) error {
|
|
// vagrant box add partyvm ubuntu-14.04.vmware.box
|
|
_, _, err := d.vagrantCmd(append([]string{"box", "add"}, args...)...)
|
|
return err
|
|
}
|
|
|
|
// Calls "vagrant up"
|
|
func (d *Vagrant_2_2_Driver) Up(args []string) (string, string, error) {
|
|
stdout, stderr, err := d.vagrantCmd(append([]string{"up"}, args...)...)
|
|
return stdout, stderr, err
|
|
}
|
|
|
|
// Calls "vagrant halt"
|
|
func (d *Vagrant_2_2_Driver) Halt(id string) error {
|
|
args := []string{"halt"}
|
|
if id != "" {
|
|
args = append(args, id)
|
|
}
|
|
_, _, err := d.vagrantCmd(args...)
|
|
return err
|
|
}
|
|
|
|
// Calls "vagrant suspend"
|
|
func (d *Vagrant_2_2_Driver) Suspend(id string) error {
|
|
args := []string{"suspend"}
|
|
if id != "" {
|
|
args = append(args, id)
|
|
}
|
|
_, _, err := d.vagrantCmd(args...)
|
|
return err
|
|
}
|
|
|
|
// Calls "vagrant destroy"
|
|
func (d *Vagrant_2_2_Driver) Destroy(id string) error {
|
|
args := []string{"destroy", "-f"}
|
|
if id != "" {
|
|
args = append(args, id)
|
|
}
|
|
_, _, err := d.vagrantCmd(args...)
|
|
return err
|
|
}
|
|
|
|
// Calls "vagrant package"
|
|
func (d *Vagrant_2_2_Driver) Package(args []string) error {
|
|
// Ideally we'd pass vagrantCWD into the package command but
|
|
// we have to change directory into the vagrant cwd instead in order to
|
|
// work around an upstream bug with the vagrant-libvirt plugin.
|
|
// We can stop doing this when
|
|
// https://github.com/vagrant-libvirt/vagrant-libvirt/issues/765
|
|
// is fixed.
|
|
oldDir, _ := os.Getwd()
|
|
os.Chdir(d.VagrantCWD)
|
|
defer os.Chdir(oldDir)
|
|
args = append(args, "--output", "package.box")
|
|
_, _, err := d.vagrantCmd(append([]string{"package"}, args...)...)
|
|
return err
|
|
}
|
|
|
|
// Verify makes sure that Vagrant exists at the given path
|
|
func (d *Vagrant_2_2_Driver) Verify() error {
|
|
vagrantPath, err := exec.LookPath(d.vagrantBinary)
|
|
if err != nil {
|
|
return fmt.Errorf("Can't find Vagrant binary!")
|
|
}
|
|
_, err = os.Stat(vagrantPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Can't find Vagrant binary.")
|
|
}
|
|
|
|
constraints, err := version.NewConstraint(VAGRANT_MIN_VERSION)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing vagrant minimum version: %v", err)
|
|
}
|
|
vers, err := d.Version()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting virtualbox version: %v", err)
|
|
}
|
|
v, err := version.NewVersion(vers)
|
|
if err != nil {
|
|
return fmt.Errorf("Error figuring out Vagrant version.")
|
|
}
|
|
|
|
if !constraints.Check(v) {
|
|
return fmt.Errorf("installed Vagrant version must be >=2.0.2")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type VagrantSSHConfig struct {
|
|
Hostname string
|
|
User string
|
|
Port string
|
|
UserKnownHostsFile string
|
|
StrictHostKeyChecking bool
|
|
PasswordAuthentication bool
|
|
IdentityFile string
|
|
IdentitiesOnly bool
|
|
LogLevel string
|
|
}
|
|
|
|
func parseSSHConfig(lines []string, value string) string {
|
|
out := ""
|
|
for _, line := range lines {
|
|
if index := strings.Index(line, value); index != -1 {
|
|
out = line[index+len(value):]
|
|
}
|
|
}
|
|
return strings.Trim(out, "\r\n")
|
|
}
|
|
|
|
func yesno(yn string) bool {
|
|
if yn == "no" {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (d *Vagrant_2_2_Driver) SSHConfig(id string) (*VagrantSSHConfig, error) {
|
|
// vagrant ssh-config --host 8df7860
|
|
args := []string{"ssh-config"}
|
|
if id != "" {
|
|
args = append(args, id)
|
|
}
|
|
sshConf := &VagrantSSHConfig{}
|
|
|
|
stdout, stderr, err := d.vagrantCmd(args...)
|
|
if err != nil {
|
|
if stderr != "" {
|
|
err = fmt.Errorf("ssh-config command returned errors: %s", stderr)
|
|
}
|
|
return sshConf, err
|
|
}
|
|
lines := strings.Split(stdout, "\n")
|
|
sshConf.Hostname = parseSSHConfig(lines, "HostName ")
|
|
sshConf.User = parseSSHConfig(lines, "User ")
|
|
sshConf.Port = parseSSHConfig(lines, "Port ")
|
|
if sshConf.Port == "" {
|
|
err := fmt.Errorf("error: SSH Port was not properly retrieved from SSHConfig.")
|
|
return sshConf, err
|
|
}
|
|
sshConf.UserKnownHostsFile = parseSSHConfig(lines, "UserKnownHostsFile ")
|
|
sshConf.IdentityFile = parseSSHConfig(lines, "IdentityFile ")
|
|
sshConf.LogLevel = parseSSHConfig(lines, "LogLevel ")
|
|
|
|
// handle the booleans
|
|
sshConf.StrictHostKeyChecking = yesno(parseSSHConfig(lines, "StrictHostKeyChecking "))
|
|
sshConf.PasswordAuthentication = yesno(parseSSHConfig(lines, "PasswordAuthentication "))
|
|
sshConf.IdentitiesOnly = yesno((parseSSHConfig(lines, "IdentitiesOnly ")))
|
|
|
|
return sshConf, err
|
|
}
|
|
|
|
// Version reads the version of VirtualBox that is installed.
|
|
func (d *Vagrant_2_2_Driver) Version() (string, error) {
|
|
stdoutString, _, err := d.vagrantCmd([]string{"--version"}...)
|
|
// Example stdout:
|
|
|
|
// Installed Version: 2.2.3
|
|
//
|
|
// Vagrant was unable to check for the latest version of Vagrant.
|
|
// Please check manually at https://www.vagrantup.com
|
|
|
|
// Use regex to find version
|
|
reg := regexp.MustCompile(`(\d+\.)?(\d+\.)?(\*|\d+)`)
|
|
version := reg.FindString(stdoutString)
|
|
if version == "" {
|
|
return "", err
|
|
}
|
|
|
|
return version, nil
|
|
}
|
|
|
|
// Copied and modified from Bufio; this will return data that contains a
|
|
// carriage return, not just data that contains a newline.
|
|
// This allows us to stream progress output from vagrant that would otherwise
|
|
// be smothered. It is a bit noisy, but probably prefereable to suppressing
|
|
// the output in a way that looks like Packer has hung.
|
|
func ScanLinesInclCR(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
if atEOF && len(data) == 0 {
|
|
return 0, nil, nil
|
|
}
|
|
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
|
// We have a full newline-terminated line.
|
|
return i + 1, data[0:i], nil
|
|
}
|
|
if i := bytes.IndexByte(data, '\r'); i >= 0 {
|
|
// We have a CR-terminated line.
|
|
return i + 1, data[0:i], nil
|
|
}
|
|
// If we're at EOF, we have a final, non-terminated line. Return it.
|
|
if atEOF {
|
|
return len(data), data, nil
|
|
}
|
|
// Request more data.
|
|
return 0, nil, nil
|
|
}
|
|
|
|
func (d *Vagrant_2_2_Driver) vagrantCmd(args ...string) (string, string, error) {
|
|
log.Printf("Calling Vagrant CLI: %#v", args)
|
|
cmd := exec.Command(d.vagrantBinary, args...)
|
|
cmd.Env = append(os.Environ(), fmt.Sprintf("VAGRANT_CWD=%s", d.VagrantCWD))
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
if err != nil {
|
|
log.Printf("error getting err pipe")
|
|
}
|
|
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
log.Printf("error getting out pipe")
|
|
}
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("Error starting vagrant command with args: %q",
|
|
strings.Join(args, " "))
|
|
}
|
|
|
|
stdoutString := ""
|
|
stderrString := ""
|
|
|
|
scanOut := bufio.NewScanner(stdout)
|
|
scanOut.Split(ScanLinesInclCR)
|
|
scanErr := bufio.NewScanner(stderr)
|
|
scanErr.Split(ScanLinesInclCR)
|
|
go func() {
|
|
for scanErr.Scan() {
|
|
line := scanErr.Text()
|
|
log.Printf("[vagrant driver] stderr: %s", line)
|
|
stderrString += line + "\n"
|
|
}
|
|
}()
|
|
|
|
for scanOut.Scan() {
|
|
line := scanOut.Text()
|
|
log.Printf("[vagrant driver] stdout: %s", line)
|
|
stdoutString += line + "\n"
|
|
}
|
|
cmd.Wait()
|
|
|
|
if _, ok := err.(*exec.ExitError); ok {
|
|
err = fmt.Errorf("Vagrant error: %s", stderrString)
|
|
}
|
|
|
|
return stdoutString, stderrString, err
|
|
}
|