github.com/cilium/cilium@v1.16.2/tools/dev-doctor/binarycheck.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package main 5 6 import ( 7 "errors" 8 "fmt" 9 "os/exec" 10 "regexp" 11 12 "github.com/blang/semver/v4" 13 ) 14 15 // A binaryCheck checks that a binary called name is installed and optionally at 16 // least version minVersion (inclusive), and less than maxVersion (exclusive) 17 type binaryCheck struct { 18 name string 19 command string 20 alternateNames []string 21 ifNotFound checkResult 22 versionArgs []string 23 versionRegexp *regexp.Regexp 24 minVersion *semver.Version // inclusive 25 maxVersion *semver.Version // exclusive 26 hint string 27 } 28 29 func (c *binaryCheck) Name() string { 30 return c.name 31 } 32 33 func (c *binaryCheck) Run() (checkResult, string) { 34 var path string 35 command := c.command 36 if command == "" { 37 command = c.name 38 } 39 for _, name := range append([]string{command}, c.alternateNames...) { 40 var err error 41 path, err = exec.LookPath(name) 42 switch { 43 case errors.Is(err, exec.ErrNotFound): 44 continue 45 case err != nil: 46 return checkFailed, err.Error() 47 } 48 } 49 if path == "" { 50 return c.ifNotFound, fmt.Sprintf("%s not found in $PATH", c.name) 51 } 52 53 if c.versionArgs == nil { 54 return checkOK, fmt.Sprintf("found %s", path) 55 } 56 57 output, err := exec.Command(path, c.versionArgs...).CombinedOutput() 58 if err != nil { 59 return checkFailed, fmt.Sprintf("failed to run %s: %s\n%s", path, err, string(output)) 60 } 61 62 version := output 63 if c.versionRegexp != nil { 64 match := c.versionRegexp.FindSubmatch(version) 65 if len(match) < 2 { 66 return checkFailed, fmt.Sprintf("found %s, could not parse version from %s", path, version) 67 } 68 version = match[1] 69 } 70 71 if c.minVersion != nil || c.maxVersion != nil { 72 v, err := semver.ParseTolerant(string(version)) 73 if err != nil { 74 return checkFailed, err.Error() 75 } 76 77 // only compare the major, minor, and patch versions. this is because 78 // github.com/blang/semver treats any extra text is a pre-release 79 // version, meaning that, e.g. clang version "10.0.0-4ubuntu1" compares 80 // less than "10.0.0" 81 effectiveVersion := semver.Version{ 82 Major: v.Major, 83 Minor: v.Minor, 84 Patch: v.Patch, 85 } 86 87 if c.minVersion != nil && effectiveVersion.LT(*c.minVersion) { 88 return checkError, fmt.Sprintf("found %s, version %s, need at least %s", path, version, c.minVersion) 89 } 90 91 if c.maxVersion != nil && effectiveVersion.GTE(*c.maxVersion) { 92 return checkError, fmt.Sprintf("found %s, version %s, need less than %s", path, version, c.maxVersion) 93 } 94 } 95 96 return checkOK, fmt.Sprintf("found %s, version %s", path, version) 97 } 98 99 func (c *binaryCheck) Hint() string { 100 return c.hint 101 }