github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/helper/hashitools/project.go (about) 1 package hashitools 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "log" 8 "os/exec" 9 "path/filepath" 10 "regexp" 11 "strings" 12 13 "github.com/hashicorp/go-checkpoint" 14 "github.com/hashicorp/go-version" 15 ) 16 17 //go:generate go-bindata -pkg=hashitools -nomemcopy -nometadata ./data/... 18 19 var ( 20 versionRe = regexp.MustCompile(`v?(\d+\.\d+\.[^\s]+)`) 21 ) 22 23 // Project represents a HashiCorp Go project and provides various operations 24 // around that. 25 type Project struct { 26 // Name is the name of the project, all lowercase 27 Name string 28 29 // Installer is the installer for this project 30 Installer Installer 31 32 // MinVersion is the minimum version of this project that Otto 33 // can use to function. This will be used with `InstallIfNeeded` 34 // to prompt the user to install. 35 MinVersion *version.Version 36 } 37 38 // InstallIfNeeded will check if installation of this project is required 39 // and will invoke the installer if needed. 40 func (p *Project) InstallIfNeeded() error { 41 log.Printf("[DEBUG] installIfNeeded: %s", p.Name) 42 43 // Start grabbing the latest version as early as possible since 44 // this requires a network call. We might as well do it while we're 45 // doing a subprocess. 46 latestCh := make(chan *version.Version, 1) 47 errCh := make(chan error, 1) 48 go func() { 49 latest, err := p.LatestVersion() 50 if err != nil { 51 errCh <- err 52 } 53 latestCh <- latest 54 }() 55 56 // Grab the version we have installed 57 installed, err := p.Version() 58 if err != nil { 59 return err 60 } 61 if installed == nil { 62 log.Printf("[DEBUG] installIfNeeded: %s not installed", p.Name) 63 } else { 64 log.Printf("[DEBUG] installIfNeeded: %s installed: %s", p.Name, installed) 65 } 66 67 // Wait for the latest 68 var latest *version.Version 69 select { 70 case latest = <-latestCh: 71 case err := <-errCh: 72 log.Printf("[ERROR] error checking latest version: %s", err) 73 } 74 log.Printf("[DEBUG] installIfNeeded: %s latest: %s", p.Name, latest) 75 log.Printf("[DEBUG] installIfNeeded: %s min: %s", p.Name, p.MinVersion) 76 77 // Determine if we require an install 78 installRequired := installed == nil 79 if installed != nil { 80 if installed.LessThan(p.MinVersion) { 81 installRequired = true 82 } 83 84 // TODO: updates 85 } 86 87 // No install required? Exit out. 88 if !installRequired { 89 log.Printf("[DEBUG] installIfNeeded: %s no installation needed", p.Name) 90 return nil 91 } 92 93 // We need to install! Ask the installer to verify for us 94 ok, err := p.Installer.InstallAsk(installed, p.MinVersion, latest) 95 if err != nil { 96 return err 97 } 98 if !ok { 99 return fmt.Errorf("Installation cancelled") 100 } 101 102 // Install 103 return p.Installer.Install(latest) 104 } 105 106 // Latest version returns the latest version of this project. 107 func (p *Project) LatestVersion() (*version.Version, error) { 108 check, err := checkpoint.Check(&checkpoint.CheckParams{ 109 Product: p.Name, 110 Force: true, 111 }) 112 if err != nil { 113 return nil, err 114 } 115 116 return version.NewVersion(check.CurrentVersion) 117 } 118 119 // Version reads the version of this project. 120 func (p *Project) Version() (*version.Version, error) { 121 path := p.Path() 122 if !filepath.IsAbs(path) { 123 // Look for it on the path first if we don't have a full path to it 124 _, err := exec.LookPath(path) 125 if err != nil { 126 if execErr, ok := err.(*exec.Error); ok && execErr.Err == exec.ErrNotFound { 127 return nil, nil 128 } 129 130 return nil, err 131 } 132 } 133 134 // Grab the version 135 var stdout, buf bytes.Buffer 136 cmd := exec.Command(path, "--version") 137 cmd.Stdout = io.MultiWriter(&stdout, &buf) 138 cmd.Stderr = &buf 139 runErr := cmd.Run() 140 141 // Match the version out before we check for a run error, since some `project 142 // --version` commands can return a non-zero exit code. 143 matches := versionRe.FindStringSubmatch(stdout.String()) 144 if len(matches) == 0 { 145 if runErr != nil { 146 return nil, fmt.Errorf( 147 "Error checking %s version: %s\n\n%s", 148 p.Name, runErr, buf.String()) 149 } 150 return nil, fmt.Errorf( 151 "unable to find %s version in output: %q", p.Name, buf.String()) 152 } 153 154 // This is a weird quirk: our version lib follows semver strictly 155 // but Vagrant in particular uses ".dev" instead "-dev" to end the 156 // version for Rubygems. Fix that up with duct tape. 157 matches[1] = strings.Replace(matches[1], ".dev", "-dev", 1) 158 159 return version.NewVersion(matches[1]) 160 } 161 162 // Path returns the path to this project. This will check if the project 163 // binary is pre-installed in our installation directory and use that path. 164 // Otherwise, it will return the raw project name. 165 func (p *Project) Path() string { 166 if p := p.Installer.Path(); p != "" { 167 return p 168 } 169 170 return p.Name 171 }