github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/inspector/check/package_manager.go (about)

     1  package check
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"os/exec"
     8  	"strings"
     9  )
    10  
    11  // PackageManager runs queries against the underlying operating system's
    12  // package manager
    13  type PackageManager interface {
    14  	IsAvailable(PackageQuery) (bool, error)
    15  	IsInstalled(PackageQuery) (bool, error)
    16  }
    17  
    18  // NewPackageManager returns a package manager for the given distribution
    19  func NewPackageManager(distro Distro) (PackageManager, error) {
    20  	run := func(name string, arg ...string) ([]byte, error) {
    21  		r, err := exec.Command(name, arg...).CombinedOutput()
    22  		return r, err
    23  	}
    24  	switch distro {
    25  	case RHEL, CentOS:
    26  		return &rpmManager{
    27  			run: run,
    28  		}, nil
    29  	case Ubuntu:
    30  		return &debManager{
    31  			run: run,
    32  		}, nil
    33  	case Darwin:
    34  		return noopManager{}, nil
    35  	default:
    36  		return nil, fmt.Errorf("%s is not supported", distro)
    37  	}
    38  }
    39  
    40  type noopManager struct{}
    41  
    42  func (noopManager) IsAvailable(PackageQuery) (bool, error) {
    43  	return false, fmt.Errorf("unable to determine if package is available using noop pkg manager")
    44  }
    45  func (noopManager) IsInstalled(PackageQuery) (bool, error) {
    46  	return false, fmt.Errorf("unable to determine if package is installed using noop pkg manager")
    47  }
    48  func (noopManager) Enforced() bool {
    49  	return false
    50  }
    51  
    52  // package manager for EL-based distributions
    53  type rpmManager struct {
    54  	run func(string, ...string) ([]byte, error)
    55  }
    56  
    57  func (m rpmManager) IsAvailable(p PackageQuery) (bool, error) {
    58  	out, err := m.run("yum", "list", "--showduplicates", "available", "-q", p.Name)
    59  	if err != nil && strings.Contains(string(out), "No matching Packages to list") {
    60  		return false, nil
    61  	}
    62  	if err != nil {
    63  		return false, fmt.Errorf("unable to determine if %s is available: %v", packageName(p, " "), err)
    64  	}
    65  	return m.isPackageListed(p, out), nil
    66  }
    67  
    68  func (m rpmManager) IsInstalled(p PackageQuery) (bool, error) {
    69  	out, err := m.run("yum", "list", "installed", "-q", p.Name)
    70  	if err != nil && strings.Contains(string(out), "No matching Packages to list") {
    71  		return false, nil
    72  	}
    73  	if err != nil {
    74  		return false, fmt.Errorf("unable to determine if %s is installed: %v", packageName(p, " "), err)
    75  	}
    76  	return m.isPackageListed(p, out), nil
    77  }
    78  
    79  func (m rpmManager) isPackageListed(p PackageQuery, list []byte) bool {
    80  	s := bufio.NewScanner(bytes.NewReader(list))
    81  
    82  	for s.Scan() {
    83  		line := s.Text()
    84  		f := strings.Fields(line)
    85  		if len(f) != 3 {
    86  			// Ignore lines that don't match the expected format
    87  			continue
    88  		}
    89  		maybeName := strings.Split(f[0], ".")[0]
    90  		maybeVersion := f[1]
    91  		if p.Name == maybeName && (p.Version == "" || p.Version == maybeVersion) {
    92  			return true
    93  		}
    94  	}
    95  	return false
    96  }
    97  
    98  // package manager for debian-based distributions
    99  type debManager struct {
   100  	run func(string, ...string) ([]byte, error)
   101  }
   102  
   103  func (m debManager) IsInstalled(p PackageQuery) (bool, error) {
   104  	// First check if the package is installed
   105  	installed, err := m.isPackageListed(p)
   106  	if err != nil {
   107  		return false, err
   108  	}
   109  	return installed, nil
   110  }
   111  
   112  func (m debManager) IsAvailable(p PackageQuery) (bool, error) {
   113  	// If it's not installed, ensure that it is available via the
   114  	// package manager. We attempt to install using --dry-run. If exit status is zero, we
   115  	// know the package is available for download
   116  	out, err := m.run("apt-get", "install", "-q", "--dry-run", packageName(p, "="))
   117  	if err != nil && strings.Contains(string(out), "Unable to locate package") {
   118  		return false, nil
   119  	}
   120  	if err != nil {
   121  		return false, err
   122  	}
   123  	return true, nil
   124  }
   125  
   126  func (m debManager) isPackageListed(p PackageQuery) (bool, error) {
   127  	out, err := m.run("dpkg", "-l", p.Name)
   128  	if err != nil && strings.Contains(string(out), "no packages found matching") {
   129  		return false, nil
   130  	}
   131  	if err != nil {
   132  		return false, fmt.Errorf("unable to determine if %s is installed: %v", packageName(p, " "), err)
   133  	}
   134  	s := bufio.NewScanner(bytes.NewReader(out))
   135  	for s.Scan() {
   136  		line := s.Text()
   137  		f := strings.Fields(line)
   138  		if len(f) < 5 {
   139  			// Ignore lines with unexpected format
   140  			continue
   141  		}
   142  		if f[0] == "un" {
   143  			// skip if we see "un"
   144  			// The "u" means that the "Desired Action" for the package is "Unknown".
   145  			// The "n" means that the "Status" of the package is "Not installed"
   146  			continue
   147  		}
   148  		maybeName := strings.Split(f[1], ".")[0]
   149  		maybeVersion := f[2]
   150  		if p.Name == maybeName && (p.Version == "" || p.Version == maybeVersion) {
   151  			return true, nil
   152  		}
   153  	}
   154  	return false, nil
   155  }
   156  
   157  func packageName(p PackageQuery, delimeter string) string {
   158  	if p.Version == "" {
   159  		return p.Name
   160  	}
   161  
   162  	return fmt.Sprintf("%s%s%s", p.Name, delimeter, p.Version)
   163  }