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
|
|
}
|