@@ -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