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  `