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 }