@@ -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,11 @@ import (
4043 "github.com/sirupsen/logrus"
4144)
4245
46+ //RPM command to extract packages from the rpm database
47+ var rpmCmd = []string {
48+ "rpm" , "--nodigest" , "--nosignature" ,
49+ "-qa" , "--qf" , "%{NAME}\t %{VERSION}\t %{SIZE}\n " ,
50+ }
4351var letters = []rune ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" )
4452
4553// daemonMutex is required to protect against other go-routines, as
@@ -87,7 +95,65 @@ func (a RPMAnalyzer) getPackages(image pkgutil.Image) (map[string]util.PackageIn
8795 }
8896 }
8997
90- return rpmDataFromContainer (image )
98+ packages , err := rpmDataFromImageFS (image )
99+ if err != nil {
100+ logrus .Warn ("Trying to run the RPM binary of the image in a container" )
101+ return rpmDataFromContainer (image )
102+ }
103+ return packages , err
104+ }
105+
106+ // rpmDataFromImageFS runs a local rpm binary, if any, to query the image
107+ // rpmdb and returns a map of installed packages.
108+ func rpmDataFromImageFS (image pkgutil.Image ) (map [string ]util.PackageInfo , error ) {
109+ packages := make (map [string ]util.PackageInfo )
110+ // Check there is an executable rpm tool in host
111+ if err := exec .Command ("rpm" , "--version" ).Run (); err != nil {
112+ logrus .Warn ("No RPM binary in host" )
113+ return packages , err
114+ }
115+ dbPath , err := rpmDBPath (image .FSPath )
116+ if err != nil {
117+ logrus .Warnf ("Couldn't find RPM database: %s" , err .Error ())
118+ return packages , err
119+ }
120+ cmdArgs := append ([]string {"--root" , image .FSPath , "--dbpath" , dbPath }, rpmCmd [1 :]... )
121+ out , err := exec .Command (rpmCmd [0 ], cmdArgs ... ).Output ()
122+ if err != nil {
123+ logrus .Warnf ("RPM call failed: %s" , err .Error ())
124+ return packages , err
125+ }
126+ output := strings .Split (string (out ), "\n " )
127+ return parsePackageData (output )
128+ }
129+
130+ // rpmDBPath tries to get the RPM database path from the /usr/lib/rpm/macros
131+ // file in the image rootfs.
132+ func rpmDBPath (rootFSPath string ) (string , error ) {
133+ rpmMacros , err := os .Open (filepath .Join (rootFSPath , "usr/lib/rpm/macros" ))
134+ if err != nil {
135+ return "" , err
136+ }
137+ defer rpmMacros .Close ()
138+
139+ scanner := bufio .NewScanner (rpmMacros )
140+ for scanner .Scan () {
141+ line := strings .TrimSpace (scanner .Text ())
142+ if strings .HasPrefix (line , "%_dbpath" ) {
143+ fields := strings .Fields (line )
144+ if len (fields ) < 2 {
145+ break
146+ }
147+ out , err := exec .Command ("rpm" , "-E" , fields [1 ]).Output ()
148+ if err != nil {
149+ return "" , err
150+ }
151+ dbPath := strings .TrimRight (string (out ), "\r \n " )
152+ _ , err = os .Stat (filepath .Join (rootFSPath , dbPath ))
153+ return dbPath , err
154+ }
155+ }
156+ return "" , errors .New ("Failed parsing macros file" )
91157}
92158
93159// rpmDataFromContainer runs image in a container, queries the data of
@@ -114,7 +180,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
114180 defer logrus .Infof ("Removing image %s" , imageName )
115181
116182 contConf := godocker.Config {
117- Entrypoint : [] string { "rpm" , "--nodigest" , "--nosignature" , "-qa" , "--qf" , "%{NAME} \t %{VERSION} \t %{SIZE} \n " } ,
183+ Entrypoint : rpmCmd ,
118184 Image : imageName ,
119185 }
120186
0 commit comments