@@ -17,11 +17,14 @@ limitations under the License.
1717package differs
1818
1919import (
20+ "bufio"
2021 "bytes"
2122 "context"
23+ "errors"
2224 "fmt"
2325 "math/rand"
2426 "os"
27+ "os/exec"
2528 "path/filepath"
2629 "strconv"
2730 "strings"
@@ -40,6 +43,14 @@ import (
4043 "github.com/sirupsen/logrus"
4144)
4245
46+ //RPM macros file location
47+ const rpmMacros string = "/usr/lib/rpm/macros"
48+
49+ //RPM command to extract packages from the rpm database
50+ var rpmCmd = []string {
51+ "rpm" , "--nodigest" , "--nosignature" ,
52+ "-qa" , "--qf" , "%{NAME}\t %{VERSION}\t %{SIZE}\n " ,
53+ }
4354var letters = []rune ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" )
4455
4556// daemonMutex is required to protect against other go-routines, as
@@ -87,7 +98,68 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
8798 }
8899 }
89100
90- return rpmDataFromContainer (image )
101+ packages , err := rpmDataFromImageFS (image )
102+ if err != nil {
103+ logrus .Info ("Running RPM binary from image in a container" )
104+ return rpmDataFromContainer (image )
105+ }
106+ return packages , err
107+ }
108+
109+ // rpmDataFromImageFS runs a local rpm binary, if any, to query the image
110+ // rpmdb and returns a map of installed packages.
111+ func rpmDataFromImageFS (image pkgutil.Image ) (map [string ]util.PackageInfo , error ) {
112+ packages := make (map [string ]util.PackageInfo )
113+ // Check there is an executable rpm tool in host
114+ if err := exec .Command ("rpm" , "--version" ).Run (); err != nil {
115+ logrus .Warn ("No RPM binary in host" )
116+ return packages , err
117+ }
118+ dbPath , err := rpmDBPath (image .FSPath )
119+ if err != nil {
120+ logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
121+ return packages , err
122+ }
123+ cmdArgs := append ([]string {"--root" , image .FSPath , "--dbpath" , dbPath }, rpmCmd [1 :]... )
124+ out , err := exec .Command (rpmCmd [0 ], cmdArgs ... ).Output ()
125+ if err != nil {
126+ logrus .Warnf ("RPM call failed: %s" , err .Error ())
127+ return packages , err
128+ }
129+ output := strings .Split (string (out ), "\n " )
130+ return parsePackageData (output )
131+ }
132+
133+ // rpmDBPath tries to get the RPM database path from the /usr/lib/rpm/macros
134+ // file in the image rootfs.
135+ func rpmDBPath (rootFSPath string ) (string , error ) {
136+ imgMacrosFile , err := os .Open (filepath .Join (rootFSPath , rpmMacros ))
137+ if err != nil {
138+ return "" , err
139+ }
140+ defer imgMacrosFile .Close ()
141+
142+ scanner := bufio .NewScanner (imgMacrosFile )
143+ for scanner .Scan () {
144+ line := strings .TrimSpace (scanner .Text ())
145+
146+ // We are looking for a macro definition like (form openSUSE Leap):
147+ // %_dbpath %{_usr}/lib/sysimage/rpm
148+ if strings .HasPrefix (line , "%_dbpath" ) {
149+ fields := strings .Fields (line )
150+ if len (fields ) < 2 {
151+ break
152+ }
153+ out , err := exec .Command ("rpm" , "-E" , fields [1 ]).Output ()
154+ if err != nil {
155+ return "" , err
156+ }
157+ dbPath := strings .TrimRight (string (out ), "\r \n " )
158+ _ , err = os .Stat (filepath .Join (rootFSPath , dbPath ))
159+ return dbPath , err
160+ }
161+ }
162+ return "" , errors .New ("Failed parsing macros file" )
91163}
92164
93165// rpmDataFromContainer runs image in a container, queries the data of
@@ -114,7 +186,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
114186 defer logrus .Infof ("Removing image %s" , imageName )
115187
116188 contConf := godocker.Config {
117- Entrypoint : [] string { "rpm" , "--nodigest" , "--nosignature" , "-qa" , "--qf" , "%{NAME} \t %{VERSION} \t %{SIZE} \n " } ,
189+ Entrypoint : rpmCmd ,
118190 Image : imageName ,
119191 }
120192
0 commit comments