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 }