@@ -17,11 +17,14 @@ limitations under the License.
17
17
package differs
18
18
19
19
import (
20
+ "bufio"
20
21
"bytes"
21
22
"context"
23
+ "errors"
22
24
"fmt"
23
25
"math/rand"
24
26
"os"
27
+ "os/exec"
25
28
"path/filepath"
26
29
"strconv"
27
30
"strings"
@@ -40,6 +43,14 @@ import (
40
43
"github.com/sirupsen/logrus"
41
44
)
42
45
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
+ }
43
54
var letters = []rune ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" )
44
55
45
56
// 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
87
98
}
88
99
}
89
100
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" )
91
163
}
92
164
93
165
// rpmDataFromContainer runs image in a container, queries the data of
@@ -114,7 +186,7 @@ func rpmDataFromContainer(image pkgutil.Image) (map[string]util.PackageInfo, err
114
186
defer logrus .Infof ("Removing image %s" , imageName )
115
187
116
188
contConf := godocker.Config {
117
- Entrypoint : [] string { "rpm" , "--nodigest" , "--nosignature" , "-qa" , "--qf" , "%{NAME} \t %{VERSION} \t %{SIZE} \n " } ,
189
+ Entrypoint : rpmCmd ,
118
190
Image : imageName ,
119
191
}
120
192
0 commit comments