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  }