github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/vagrant/vagrant.go (about)

     1  package vagrant
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"sync"
     8  
     9  	"github.com/hashicorp/go-version"
    10  	"github.com/hashicorp/otto/context"
    11  	execHelper "github.com/hashicorp/otto/helper/exec"
    12  	"github.com/hashicorp/otto/helper/hashitools"
    13  	"github.com/hashicorp/otto/ui"
    14  )
    15  
    16  var (
    17  	vagrantMinVersion = version.Must(version.NewVersion("1.8.1"))
    18  )
    19  
    20  // Project returns the hashitools Project for this.
    21  func Project(ctx *context.Shared) *hashitools.Project {
    22  	return &hashitools.Project{
    23  		Name:       "vagrant",
    24  		MinVersion: vagrantMinVersion,
    25  		Installer: &hashitools.VagrantInstaller{
    26  			Ui: ctx.Ui,
    27  		},
    28  	}
    29  }
    30  
    31  // Vagrant wraps `vagrant` execution into an easy-to-use API.
    32  type Vagrant struct {
    33  	// Dir is the working directory where all Vagrant commands will
    34  	// be executed from.
    35  	Dir string
    36  
    37  	// DataDir is the directory where Vagrant commands should store data.
    38  	DataDir string
    39  
    40  	// Env is extra environment variables to set when executing Vagrant.
    41  	// This will be on top of the environment variables that are in this
    42  	// process.
    43  	Env map[string]string
    44  
    45  	// Ui, if given, will be used to stream output from the Vagrant
    46  	// commands. If this is nil, then the output will be logged but
    47  	// won't be visible to the user.
    48  	Ui ui.Ui
    49  
    50  	// Callbacks is a mapping of callbacks that will be called for certain
    51  	// event types within the output. These will always be serialized and
    52  	// will block on this callback returning so it is important to make
    53  	// this fast.
    54  	Callbacks map[string]OutputCallback
    55  
    56  	lock sync.Mutex
    57  }
    58  
    59  // A global mutex to prevent any Vagrant commands from running in parallel,
    60  // which is not a supported mode of operation for Vagrant.
    61  var vagrantMutex = &sync.Mutex{}
    62  
    63  const (
    64  	// The environment variable that Vagrant uses to configure its working dir
    65  	vagrantCwdEnvVar = "VAGRANT_CWD"
    66  
    67  	// The environment variable that Vagrant uses to configure its data dir.
    68  	vagrantDataDirEnvVar = "VAGRANT_DOTFILE_PATH"
    69  )
    70  
    71  // Execute executes a raw Vagrant command.
    72  func (v *Vagrant) Execute(commandRaw ...string) error {
    73  	vagrantMutex.Lock()
    74  	defer vagrantMutex.Unlock()
    75  
    76  	if v.Env == nil {
    77  		v.Env = make(map[string]string)
    78  	}
    79  
    80  	// Where to store data
    81  	v.Env[vagrantDataDirEnvVar] = v.DataDir
    82  
    83  	// Make sure we use our cwd properly
    84  	v.Env[vagrantCwdEnvVar] = v.Dir
    85  
    86  	// Build up the environment
    87  	env := os.Environ()
    88  	for k, v := range v.Env {
    89  		env = append(env, fmt.Sprintf("%s=%s", k, v))
    90  	}
    91  
    92  	// Build the args
    93  	command := make([]string, len(commandRaw)+1)
    94  	command[0] = "--machine-readable"
    95  	copy(command[1:], commandRaw)
    96  
    97  	// Build the command to execute
    98  	cmd := exec.Command("vagrant", command...)
    99  	cmd.Dir = v.Dir
   100  	cmd.Env = env
   101  
   102  	// Build our custom UI that we'll use that'll call the registered
   103  	// callbacks as well as streaming data to the UI.
   104  	callbacks := make(map[string]OutputCallback)
   105  	callbacks["invalid"] = v.uiCallback
   106  	callbacks["ui"] = v.uiCallback
   107  	for n, cb := range v.Callbacks {
   108  		callbacks[n] = cb
   109  	}
   110  	ui := &vagrantUi{Callbacks: callbacks}
   111  
   112  	// Run it with the execHelper
   113  	err := execHelper.Run(ui, cmd)
   114  	ui.Finish()
   115  	if err != nil {
   116  		return fmt.Errorf(
   117  			"Error executing Vagrant: %s\n\n"+
   118  				"The error messages from Vagrant are usually very informative.\n"+
   119  				"Please read it carefully and fix any issues it mentions. If\n"+
   120  				"the message isn't clear, please report this to the Otto project.",
   121  			err)
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  func (v *Vagrant) ExecuteSilent(command ...string) error {
   128  	v.lock.Lock()
   129  	defer v.lock.Unlock()
   130  
   131  	// Store the old UI and restore it before exit
   132  	old := v.Ui
   133  	defer func() { v.Ui = old }()
   134  
   135  	// Make the Ui silent
   136  	v.Ui = &ui.Logged{Ui: &ui.Null{}}
   137  	return v.Execute(command...)
   138  }
   139  
   140  func (v *Vagrant) uiCallback(o *Output) {
   141  	// If we don't have a UI return
   142  	if v.Ui == nil {
   143  		v.Ui = &ui.Logged{Ui: &ui.Null{}}
   144  	}
   145  
   146  	// The output is just the data itself unless it is longer. This means
   147  	// that there is a type as the first element and we want the second one
   148  	// for the output.
   149  	output := o.Data[0]
   150  	if len(o.Data) > 1 {
   151  		output = o.Data[1]
   152  	}
   153  
   154  	// Output the things to our own UI!
   155  	v.Ui.Raw(output + "\n")
   156  }