github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/helper/hashitools/installer_vagrant.go (about) 1 package hashitools 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "os/signal" 11 "path/filepath" 12 "runtime" 13 "strings" 14 15 "github.com/hashicorp/go-cleanhttp" 16 "github.com/hashicorp/go-version" 17 "github.com/hashicorp/otto/ui" 18 "github.com/mitchellh/ioprogress" 19 "gopkg.in/flosch/pongo2.v3" 20 ) 21 22 // VagrantInstaller is an Installer that knows how to install Vagrant, 23 // which uses its own system installer. 24 type VagrantInstaller struct { 25 // Ui is the Otto UI for asking the user for input and outputting 26 // the status of installation. 27 Ui ui.Ui 28 } 29 30 func (i *VagrantInstaller) InstallAsk(installed, required, latest *version.Version) (bool, error) { 31 input := &ui.InputOpts{ 32 Id: "vagrant_install", 33 Query: "Would you like Otto to install Vagrant?", 34 Default: "", 35 } 36 37 // Figure out the description text to use for input 38 var tplString string 39 if installed == nil { 40 tplString = vagrantInstallRequired 41 } else { 42 tplString = vagrantInstallRequiredUpdate 43 } 44 45 // Parse the template and render it 46 tpl, err := pongo2.FromString(strings.TrimSpace(tplString)) 47 if err != nil { 48 return false, err 49 } 50 input.Description, err = tpl.Execute(map[string]interface{}{ 51 "name": "vagrant", 52 "installed": installed, 53 "latest": latest, 54 "required": required, 55 }) 56 if err != nil { 57 return false, err 58 } 59 60 result, err := i.Ui.Input(input) 61 if err != nil { 62 return false, err 63 } 64 65 return strings.ToLower(result) == "yes", nil 66 } 67 68 func (i *VagrantInstaller) Install(vsn *version.Version) error { 69 // Determine the URL 70 var url string 71 switch runtime.GOOS { 72 case "darwin": 73 url = fmt.Sprintf( 74 "https://releases.hashicorp.com/vagrant/%s/vagrant_%s.dmg", 75 vsn, vsn) 76 default: 77 return fmt.Errorf( 78 "Otto doesn't yet support installing Vagrant automatically\n" + 79 "on your OS. Please install Vagrant manually.") 80 } 81 82 // Create the temporary directory where we'll store the data 83 td, err := ioutil.TempDir("", "otto") 84 if err != nil { 85 return err 86 } 87 defer os.RemoveAll(td) 88 89 // Create the file path 90 path := filepath.Join(td, "vagrant") 91 f, err := os.Create(path) 92 if err != nil { 93 return err 94 } 95 96 // Download the file to a temporary directory 97 i.Ui.Header(fmt.Sprintf("Downloading Vagrant v%s...", vsn)) 98 i.Ui.Message("URL: " + url) 99 i.Ui.Message("") 100 resp, err := cleanhttp.DefaultClient().Get(url) 101 if err != nil { 102 f.Close() 103 return err 104 } 105 if resp.StatusCode != 200 { 106 resp.Body.Close() 107 f.Close() 108 return fmt.Errorf("Error downloading, status code %d", resp.StatusCode) 109 } 110 111 // Build the progress bar for our download 112 progressR := &ioprogress.Reader{ 113 Reader: resp.Body, 114 Size: resp.ContentLength, 115 DrawFunc: ioprogress.DrawTerminalf(os.Stdout, i.progressFormat), 116 } 117 118 // Listen for interrupts so we can cancel the download 119 sigCh := make(chan os.Signal, 1) 120 signal.Notify(sigCh, os.Interrupt) 121 defer signal.Stop(sigCh) 122 123 // Copy the zip data 124 errCh := make(chan error, 1) 125 go func() { 126 _, err := io.Copy(f, progressR) 127 errCh <- err 128 }() 129 130 // Wait for an interrupt or finish 131 select { 132 case err = <-errCh: 133 case <-sigCh: 134 err = fmt.Errorf("interrupted") 135 } 136 137 // Finish up 138 resp.Body.Close() 139 f.Close() 140 if err != nil { 141 return err 142 } 143 144 // Run the proper installer 145 switch runtime.GOOS { 146 case "darwin": 147 return i.installDarwin(path) 148 } 149 150 panic("unknown OS") 151 } 152 153 func (i *VagrantInstaller) Path() string { 154 // Always return "" since "vagrant" is just expected to be on the PATH 155 return "" 156 } 157 158 func (i *VagrantInstaller) installDarwin(installerPath string) error { 159 // Grab the script from our assets 160 asset, err := Asset("data/vagrant-darwin/install.sh") 161 if err != nil { 162 return err 163 } 164 165 // Create a temporary directory for our script 166 td, err := ioutil.TempDir("", "otto") 167 if err != nil { 168 return err 169 } 170 defer os.RemoveAll(td) 171 172 // Write the script 173 path := filepath.Join(td, "install.sh") 174 f, err := os.Create(path) 175 if err != nil { 176 return err 177 } 178 _, err = io.Copy(f, bytes.NewReader(asset)) 179 f.Close() 180 if err != nil { 181 return err 182 } 183 184 // Execute the script 185 cmd := exec.Command("bash", path, installerPath) 186 cmd.Stdin = os.Stdin 187 cmd.Stdout = os.Stdout 188 cmd.Stderr = os.Stderr 189 if err := cmd.Run(); err != nil { 190 return err 191 } 192 193 i.Ui.Header("[green]Vagrant installed successfully!") 194 return nil 195 } 196 197 func (i *VagrantInstaller) progressFormat(progress, total int64) string { 198 return fmt.Sprintf(" %s", ioprogress.DrawTextFormatBytes(progress, total)) 199 } 200 201 const vagrantInstallRequired = ` 202 Otto requires {{name}} to be installed, but it couldn't be found on your 203 system. Otto can install the latest version of {{name}} for you. {{name}} 204 will be installed system-wide since it uses system-specific installers. 205 Would you like Otto to install {{name}} for you? Alternatively, you may install 206 this on your own. 207 208 If you answer yes, Otto will install {{name}} version {{latest}}. 209 210 Please enter 'yes' to continue. Any other value will exit. 211 ` 212 213 const vagrantInstallRequiredUpdate = ` 214 An older version of {{name}} was found installed ({{installed}}). Otto requires 215 version {{required}} or higher. Otto can install the latest version of {{name}} 216 for you ({{latest}}). Would you like Otto to update {{name}} for you? 217 218 Please enter 'yes' to continue. Any other value will exit. 219 `