Merge pull request #672 from KeyboardNerd/source_package/feature_type

Implement Feature types
master
Sida Chen 5 years ago committed by GitHub
commit 73bc2bc36b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -31,11 +31,6 @@ notifications:
matrix: matrix:
include: include:
- addons:
apt:
packages:
- rpm
postgresql: 9.4
- addons: - addons:
apt: apt:
packages: packages:

@ -232,6 +232,9 @@ type Feature struct {
Detector *Detector `protobuf:"bytes,5,opt,name=detector" json:"detector,omitempty"` Detector *Detector `protobuf:"bytes,5,opt,name=detector" json:"detector,omitempty"`
// The list of vulnerabilities that affect the feature. // The list of vulnerabilities that affect the feature.
Vulnerabilities []*Vulnerability `protobuf:"bytes,6,rep,name=vulnerabilities" json:"vulnerabilities,omitempty"` Vulnerabilities []*Vulnerability `protobuf:"bytes,6,rep,name=vulnerabilities" json:"vulnerabilities,omitempty"`
// The feature type indicates if the feature represents a source package or
// binary package.
FeatureType string `protobuf:"bytes,7,opt,name=feature_type,json=featureType" json:"feature_type,omitempty"`
} }
func (m *Feature) Reset() { *m = Feature{} } func (m *Feature) Reset() { *m = Feature{} }
@ -281,6 +284,13 @@ func (m *Feature) GetVulnerabilities() []*Vulnerability {
return nil return nil
} }
func (m *Feature) GetFeatureType() string {
if m != nil {
return m.FeatureType
}
return ""
}
type Layer struct { type Layer struct {
// The sha256 tarsum for the layer. // The sha256 tarsum for the layer.
Hash string `protobuf:"bytes,1,opt,name=hash" json:"hash,omitempty"` Hash string `protobuf:"bytes,1,opt,name=hash" json:"hash,omitempty"`
@ -1091,89 +1101,90 @@ var _StatusService_serviceDesc = grpc.ServiceDesc{
func init() { proto.RegisterFile("api/v3/clairpb/clair.proto", fileDescriptor0) } func init() { proto.RegisterFile("api/v3/clairpb/clair.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 1336 bytes of a gzipped FileDescriptorProto // 1350 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0x4b, 0x6f, 0x1b, 0x55, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0x4b, 0x6f, 0x1b, 0x55,
0x14, 0x66, 0x9c, 0x3a, 0xb6, 0x8f, 0xed, 0xc4, 0xbd, 0x49, 0x13, 0x67, 0xd2, 0x47, 0x32, 0x50, 0x14, 0x66, 0x9c, 0x3a, 0xb6, 0x8f, 0xed, 0xc4, 0xbd, 0x49, 0x13, 0x67, 0xd2, 0x47, 0x32, 0x50,
0x51, 0x0a, 0xb2, 0x85, 0x5b, 0xa4, 0xb6, 0x2c, 0x90, 0x9b, 0x38, 0x21, 0x52, 0x1b, 0xa2, 0x49, 0x51, 0x0a, 0xb2, 0x85, 0x5b, 0xa4, 0xb6, 0x2c, 0x90, 0x9b, 0x38, 0x21, 0x52, 0x1b, 0xa2, 0x49,
0x1a, 0x09, 0x10, 0x32, 0x37, 0x9e, 0xe3, 0x64, 0x94, 0xf1, 0xcc, 0x30, 0x73, 0x9d, 0xd4, 0xaa, 0x1a, 0x09, 0x10, 0x1a, 0x6e, 0x3c, 0xc7, 0xc9, 0x28, 0xe3, 0x19, 0x33, 0x73, 0x9d, 0xd4, 0xaa,
0xca, 0x82, 0x1d, 0x3b, 0x04, 0x0b, 0x56, 0xfc, 0x00, 0x36, 0x88, 0xff, 0xc0, 0x9e, 0x05, 0x6c, 0xca, 0x82, 0x1d, 0x5b, 0x58, 0xb0, 0xe2, 0x07, 0xb0, 0x41, 0x48, 0xfc, 0x04, 0xf6, 0x2c, 0x60,
0x61, 0xc7, 0x82, 0x3f, 0xc0, 0x1e, 0xdd, 0xc7, 0x4c, 0x66, 0x92, 0x49, 0xe2, 0x76, 0xe5, 0x7b, 0x0b, 0x3b, 0x16, 0xfc, 0x01, 0xf6, 0xe8, 0x3e, 0x66, 0x32, 0x93, 0x4c, 0x12, 0xb7, 0x2b, 0xdf,
0xde, 0x8f, 0xfb, 0xdd, 0x73, 0xc6, 0xa0, 0x53, 0xdf, 0x6e, 0x1e, 0xdd, 0x6b, 0xf6, 0x1c, 0x6a, 0x7b, 0xde, 0x8f, 0xef, 0x9e, 0x33, 0x06, 0x9d, 0x0e, 0x9c, 0xe6, 0xd1, 0xbd, 0x66, 0xd7, 0xa5,
0x07, 0xfe, 0x9e, 0xfc, 0x6d, 0xf8, 0x81, 0xc7, 0x3c, 0x52, 0xe9, 0x79, 0x01, 0x7a, 0x61, 0x43, 0x4e, 0x30, 0xd8, 0x93, 0xbf, 0x8d, 0x41, 0xe0, 0x33, 0x9f, 0x54, 0xba, 0x7e, 0x80, 0x7e, 0xd8,
0xf0, 0xf4, 0x5b, 0xfb, 0x9e, 0xb7, 0xef, 0x60, 0x53, 0xc8, 0xf6, 0x86, 0xfd, 0x26, 0xb3, 0x07, 0x10, 0x34, 0xfd, 0xd6, 0xbe, 0xef, 0xef, 0xbb, 0xd8, 0x14, 0xbc, 0xbd, 0x61, 0xaf, 0xc9, 0x9c,
0x18, 0x32, 0x3a, 0xf0, 0xa5, 0xba, 0x7e, 0x5d, 0x29, 0x70, 0x8f, 0xd4, 0x75, 0x3d, 0x46, 0x99, 0x3e, 0x86, 0x8c, 0xf6, 0x07, 0x52, 0x5c, 0xbf, 0xae, 0x04, 0xb8, 0x45, 0xea, 0x79, 0x3e, 0xa3,
0xed, 0xb9, 0xa1, 0x94, 0x1a, 0x3f, 0xe6, 0xa0, 0xba, 0x3b, 0x74, 0x5c, 0x0c, 0xe8, 0x9e, 0xed, 0xcc, 0xf1, 0xbd, 0x50, 0x72, 0x8d, 0x1f, 0x72, 0x50, 0xdd, 0x1d, 0xba, 0x1e, 0x06, 0x74, 0xcf,
0xd8, 0x6c, 0x44, 0x08, 0x5c, 0x71, 0xe9, 0x00, 0xeb, 0xda, 0x92, 0x76, 0xa7, 0x64, 0x8a, 0x33, 0x71, 0x1d, 0x36, 0x22, 0x04, 0xae, 0x78, 0xb4, 0x8f, 0x75, 0x6d, 0x49, 0xbb, 0x53, 0x32, 0xc5,
0xb9, 0x0d, 0x53, 0xfc, 0x37, 0xf4, 0x69, 0x0f, 0xbb, 0x42, 0x9a, 0x13, 0xd2, 0x6a, 0xcc, 0xdd, 0x99, 0xdc, 0x86, 0x29, 0xfe, 0x1b, 0x0e, 0x68, 0x17, 0x2d, 0xc1, 0xcd, 0x09, 0x6e, 0x35, 0xa6,
0xe4, 0x6a, 0x4b, 0x50, 0xb6, 0x30, 0xec, 0x05, 0xb6, 0xcf, 0x43, 0xd4, 0x27, 0x84, 0x4e, 0x92, 0x6e, 0x72, 0xb1, 0x25, 0x28, 0xdb, 0x18, 0x76, 0x03, 0x67, 0xc0, 0x5d, 0xd4, 0x27, 0x84, 0x4c,
0xc5, 0x9d, 0x3b, 0xb6, 0x7b, 0x58, 0xbf, 0x22, 0x9d, 0xf3, 0x33, 0xd1, 0xa1, 0x18, 0xe2, 0x11, 0x92, 0xc4, 0x8d, 0xbb, 0x8e, 0x77, 0x58, 0xbf, 0x22, 0x8d, 0xf3, 0x33, 0xd1, 0xa1, 0x18, 0xe2,
0x06, 0x36, 0x1b, 0xd5, 0xf3, 0x82, 0x1f, 0xd3, 0x5c, 0x36, 0x40, 0x46, 0x2d, 0xca, 0x68, 0x7d, 0x11, 0x06, 0x0e, 0x1b, 0xd5, 0xf3, 0x82, 0x1e, 0xdf, 0x39, 0xaf, 0x8f, 0x8c, 0xda, 0x94, 0xd1,
0x52, 0xca, 0x22, 0x9a, 0x2c, 0x40, 0xb1, 0x6f, 0x3f, 0x47, 0xab, 0xbb, 0x37, 0xaa, 0x17, 0x84, 0xfa, 0xa4, 0xe4, 0x45, 0x77, 0xb2, 0x00, 0xc5, 0x9e, 0xf3, 0x1c, 0x6d, 0x6b, 0x6f, 0x54, 0x2f,
0xac, 0x20, 0xe8, 0xc7, 0x23, 0xf2, 0x18, 0xae, 0xd2, 0x7e, 0x1f, 0x7b, 0x0c, 0xad, 0xee, 0x11, 0x08, 0x5e, 0x41, 0xdc, 0x1f, 0x8f, 0xc8, 0x63, 0xb8, 0x4a, 0x7b, 0x3d, 0xec, 0x32, 0xb4, 0xad,
0x06, 0x21, 0x2f, 0xb8, 0x5e, 0x5c, 0x9a, 0xb8, 0x53, 0x6e, 0x5d, 0x6b, 0x24, 0xdb, 0xd7, 0x58, 0x23, 0x0c, 0x42, 0x9e, 0x70, 0xbd, 0xb8, 0x34, 0x71, 0xa7, 0xdc, 0xba, 0xd6, 0x48, 0x96, 0xaf,
0x43, 0xca, 0x86, 0x01, 0x9a, 0xb5, 0x48, 0x7f, 0x57, 0xa9, 0x1b, 0xbf, 0x6b, 0x50, 0x5c, 0x45, 0xb1, 0x86, 0x94, 0x0d, 0x03, 0x34, 0x6b, 0x91, 0xfc, 0xae, 0x12, 0x37, 0x7e, 0xd7, 0xa0, 0xb8,
0x86, 0x3d, 0xe6, 0x05, 0x99, 0x4d, 0xa9, 0x43, 0x41, 0xf9, 0x56, 0xdd, 0x88, 0x48, 0xd2, 0x82, 0x8a, 0x0c, 0xbb, 0xcc, 0x0f, 0x32, 0x8b, 0x52, 0x87, 0x82, 0xb2, 0xad, 0xaa, 0x11, 0x5d, 0x49,
0xbc, 0xc5, 0x46, 0x3e, 0x8a, 0x0e, 0x4c, 0xb5, 0xae, 0xa7, 0x43, 0x46, 0x4e, 0x1b, 0xab, 0x3b, 0x0b, 0xf2, 0x36, 0x1b, 0x0d, 0x50, 0x54, 0x60, 0xaa, 0x75, 0x3d, 0xed, 0x32, 0x32, 0xda, 0x58,
0x23, 0x1f, 0x4d, 0xa9, 0x6a, 0x7c, 0x09, 0x79, 0x41, 0x93, 0x45, 0x98, 0x5f, 0xed, 0xec, 0x74, 0xdd, 0x19, 0x0d, 0xd0, 0x94, 0xa2, 0xc6, 0x97, 0x90, 0x17, 0x77, 0xb2, 0x08, 0xf3, 0xab, 0x9d,
0x56, 0x76, 0x3e, 0x31, 0xbb, 0xab, 0xdd, 0x9d, 0x4f, 0xb7, 0x3a, 0xdd, 0x8d, 0xcd, 0xdd, 0xf6, 0x9d, 0xce, 0xca, 0xce, 0x27, 0xa6, 0xb5, 0x6a, 0xed, 0x7c, 0xba, 0xd5, 0xb1, 0x36, 0x36, 0x77,
0x93, 0x8d, 0xd5, 0xda, 0x1b, 0xe4, 0x06, 0x2c, 0x9c, 0x16, 0x6e, 0xb6, 0x9f, 0x76, 0xb6, 0xb7, 0xdb, 0x4f, 0x36, 0x56, 0x6b, 0x6f, 0x90, 0x1b, 0xb0, 0x70, 0x9a, 0xb9, 0xd9, 0x7e, 0xda, 0xd9,
0xda, 0x2b, 0x9d, 0x9a, 0x96, 0x65, 0xbb, 0xd6, 0x69, 0xef, 0x3c, 0x33, 0x3b, 0xb5, 0x9c, 0xb1, 0xde, 0x6a, 0xaf, 0x74, 0x6a, 0x5a, 0x96, 0xee, 0x5a, 0xa7, 0xbd, 0xf3, 0xcc, 0xec, 0xd4, 0x72,
0x0d, 0xa5, 0xcd, 0xe8, 0xba, 0x32, 0x0b, 0x6a, 0x41, 0xd1, 0x52, 0xb9, 0x89, 0x8a, 0xca, 0xad, 0xc6, 0x36, 0x94, 0x36, 0xa3, 0x76, 0x65, 0x26, 0xd4, 0x82, 0xa2, 0xad, 0x62, 0x13, 0x19, 0x95,
0xb9, 0xec, 0xcc, 0xcd, 0x58, 0xcf, 0xf8, 0x2e, 0x07, 0x05, 0xd5, 0xc3, 0x4c, 0x9f, 0x1f, 0x40, 0x5b, 0x73, 0xd9, 0x91, 0x9b, 0xb1, 0x9c, 0xf1, 0x6b, 0x0e, 0x0a, 0xaa, 0x86, 0x99, 0x36, 0x3f,
0x29, 0xc6, 0x88, 0x72, 0x3a, 0x9f, 0x76, 0x1a, 0xe7, 0x64, 0x9e, 0x68, 0x26, 0x7b, 0x3b, 0x91, 0x80, 0x52, 0x8c, 0x11, 0x65, 0x74, 0x3e, 0x6d, 0x34, 0x8e, 0xc9, 0x3c, 0x91, 0x4c, 0xd6, 0x76,
0xee, 0xed, 0x6d, 0x98, 0x52, 0xc7, 0x6e, 0xdf, 0x0b, 0x06, 0x94, 0x29, 0x2c, 0x55, 0x15, 0x77, 0x22, 0x5d, 0xdb, 0xdb, 0x30, 0xa5, 0x8e, 0x56, 0xcf, 0x0f, 0xfa, 0x94, 0x29, 0x2c, 0x55, 0x15,
0x4d, 0x30, 0x53, 0xb5, 0xe4, 0xc7, 0xab, 0x85, 0x74, 0x60, 0xfa, 0x28, 0xf1, 0x14, 0x6c, 0x0c, 0x75, 0x4d, 0x10, 0x53, 0xb9, 0xe4, 0xc7, 0xcb, 0x85, 0x74, 0x60, 0xfa, 0x28, 0xf1, 0x14, 0x1c,
0xeb, 0x93, 0x02, 0x33, 0x8b, 0x69, 0xd3, 0xd4, 0x7b, 0x31, 0x4f, 0xdb, 0x18, 0x8b, 0x90, 0x7f, 0x0c, 0xeb, 0x93, 0x02, 0x33, 0x8b, 0x69, 0xd5, 0xd4, 0x7b, 0x31, 0x4f, 0xeb, 0x90, 0x65, 0xa8,
0x42, 0x47, 0x28, 0x40, 0x73, 0x40, 0xc3, 0x83, 0xa8, 0x1f, 0xfc, 0x6c, 0x7c, 0xab, 0x41, 0x79, 0xf4, 0x64, 0x45, 0x2c, 0x01, 0x02, 0x89, 0xcd, 0xb2, 0xa2, 0xf1, 0x1e, 0x1b, 0x8b, 0x90, 0x7f,
0x85, 0x7b, 0xd9, 0x66, 0x94, 0x0d, 0x43, 0x72, 0x1f, 0x4a, 0x51, 0xfc, 0xb0, 0xae, 0x89, 0x68, 0x42, 0x47, 0x28, 0x70, 0x75, 0x40, 0xc3, 0x83, 0xa8, 0x64, 0xfc, 0x6c, 0x7c, 0xab, 0x41, 0x79,
0xe7, 0x25, 0x7a, 0xa2, 0x48, 0x56, 0xa1, 0xe6, 0xd0, 0x90, 0x75, 0x87, 0xbe, 0x45, 0x19, 0x76, 0x85, 0x3b, 0xda, 0x66, 0x94, 0x0d, 0x43, 0x72, 0x1f, 0x4a, 0x51, 0x88, 0x61, 0x5d, 0x13, 0x01,
0xf9, 0x93, 0x57, 0xcd, 0xd5, 0x1b, 0xf2, 0xb9, 0x37, 0xa2, 0x79, 0xd0, 0xd8, 0x89, 0xe6, 0x81, 0x9d, 0x97, 0xcb, 0x89, 0x20, 0x59, 0x85, 0x9a, 0x4b, 0x43, 0x66, 0x0d, 0x07, 0x36, 0x65, 0x68,
0x39, 0xc5, 0x6d, 0x9e, 0x09, 0x13, 0xce, 0x34, 0x1e, 0x02, 0x59, 0x47, 0xd6, 0x76, 0x7b, 0x18, 0xf1, 0xa9, 0xa0, 0xea, 0xaf, 0x37, 0xe4, 0x44, 0x68, 0x44, 0x23, 0xa3, 0xb1, 0x13, 0x8d, 0x0c,
0xb2, 0x60, 0x64, 0xe2, 0x57, 0x43, 0x0c, 0x19, 0x79, 0x13, 0xaa, 0x54, 0xb1, 0xba, 0x89, 0xeb, 0x73, 0x8a, 0xeb, 0x3c, 0x13, 0x2a, 0x9c, 0x68, 0x3c, 0x04, 0xb2, 0x8e, 0xac, 0xed, 0x75, 0x31,
0xac, 0x44, 0x4c, 0x7e, 0x5f, 0xc6, 0xaf, 0x13, 0x30, 0x93, 0xb2, 0x0d, 0x7d, 0xcf, 0x0d, 0x91, 0x64, 0xc1, 0xc8, 0xc4, 0xaf, 0x86, 0x18, 0x32, 0xf2, 0x26, 0x54, 0xa9, 0x22, 0x59, 0x89, 0x8e,
0xac, 0x41, 0x31, 0xd2, 0x13, 0x76, 0xe5, 0xd6, 0xdd, 0x74, 0x35, 0x19, 0x46, 0x8d, 0x98, 0x11, 0x57, 0x22, 0x22, 0x6f, 0xa9, 0xf1, 0xcb, 0x04, 0xcc, 0xa4, 0x74, 0xc3, 0x81, 0xef, 0x85, 0x48,
0xdb, 0x92, 0xf7, 0x61, 0x32, 0x14, 0x0d, 0x52, 0x65, 0x2d, 0xa4, 0xbd, 0x24, 0x3a, 0x68, 0x2a, 0xd6, 0xa0, 0x18, 0xc9, 0x09, 0xbd, 0x72, 0xeb, 0x6e, 0x3a, 0x9b, 0x0c, 0xa5, 0x46, 0x4c, 0x88,
0x45, 0xfd, 0x6b, 0xa8, 0x46, 0x8e, 0x64, 0xfb, 0xdf, 0x81, 0xbc, 0xc3, 0x0f, 0x2a, 0x91, 0x99, 0x75, 0xc9, 0xfb, 0x30, 0x19, 0x8a, 0x02, 0xa9, 0xb4, 0x16, 0xd2, 0x56, 0x12, 0x15, 0x34, 0x95,
0xb4, 0x0b, 0xa1, 0x63, 0x4a, 0x0d, 0x3e, 0x2f, 0x64, 0x73, 0xd1, 0xea, 0xf6, 0x25, 0x9a, 0x79, 0xa0, 0xfe, 0x35, 0x54, 0x23, 0x43, 0xb2, 0xfc, 0xef, 0x40, 0xde, 0xe5, 0x07, 0x15, 0xc8, 0x4c,
0xe4, 0x8b, 0xe6, 0x45, 0xa4, 0xaf, 0x18, 0xa1, 0xfe, 0x93, 0x06, 0xc5, 0x28, 0x81, 0xcc, 0xa7, 0xda, 0x84, 0x90, 0x31, 0xa5, 0x04, 0x1f, 0x29, 0xb2, 0xb8, 0x68, 0x5b, 0xaa, 0x95, 0xdc, 0xf3,
0x90, 0xba, 0xea, 0xdc, 0xb8, 0x57, 0xbd, 0x0e, 0x93, 0x22, 0xc7, 0xb0, 0x3e, 0x21, 0x4c, 0x9a, 0x45, 0x23, 0x25, 0x92, 0x57, 0x84, 0x50, 0xff, 0x51, 0x83, 0x62, 0x14, 0x40, 0xe6, 0x6b, 0x49,
0xe3, 0xf7, 0x53, 0x96, 0xa8, 0xcc, 0x8d, 0xbf, 0x73, 0x30, 0xb3, 0xe5, 0x85, 0xaf, 0x75, 0xdf, 0xb5, 0x3a, 0x37, 0x6e, 0xab, 0xd7, 0x61, 0x52, 0xc4, 0x18, 0xd6, 0x27, 0x84, 0x4a, 0x73, 0xfc,
0x64, 0x0e, 0x26, 0xd5, 0x6b, 0x93, 0xa3, 0x4e, 0x51, 0x64, 0xe5, 0x54, 0x76, 0xef, 0xa6, 0xb3, 0x7a, 0xca, 0x14, 0x95, 0xba, 0xf1, 0x77, 0x0e, 0x66, 0xb6, 0xfc, 0xf0, 0xb5, 0xfa, 0x4d, 0xe6,
0xcb, 0x88, 0x27, 0x78, 0xa9, 0xcc, 0xf4, 0xdf, 0x34, 0x28, 0xc5, 0xdc, 0xac, 0x57, 0xc3, 0x79, 0x60, 0x52, 0x3d, 0x48, 0x39, 0x0d, 0xd5, 0x8d, 0xac, 0x9c, 0x8a, 0xee, 0xdd, 0x74, 0x74, 0x19,
0x3e, 0x65, 0x07, 0x2a, 0xb8, 0x38, 0x13, 0x13, 0x0a, 0x07, 0x48, 0xad, 0x93, 0xd8, 0x0f, 0x5e, 0xfe, 0x04, 0x2d, 0x15, 0x99, 0xfe, 0x9b, 0x06, 0xa5, 0x98, 0x9a, 0xf5, 0x6a, 0x38, 0x6d, 0x40,
0x21, 0x76, 0xe3, 0x63, 0x69, 0xda, 0x71, 0xb9, 0x34, 0x72, 0xa4, 0x3f, 0x82, 0x4a, 0x52, 0x40, 0xd9, 0x81, 0x72, 0x2e, 0xce, 0xc4, 0x84, 0xc2, 0x01, 0x52, 0xfb, 0xc4, 0xf7, 0x83, 0x57, 0xf0,
0x6a, 0x30, 0x71, 0x88, 0x23, 0x95, 0x0a, 0x3f, 0x92, 0x59, 0xc8, 0x1f, 0x51, 0x67, 0x18, 0x2d, 0xdd, 0xf8, 0x58, 0xaa, 0x76, 0x3c, 0xce, 0x8d, 0x0c, 0xe9, 0x8f, 0xa0, 0x92, 0x64, 0x90, 0x1a,
0x40, 0x49, 0x3c, 0xca, 0x3d, 0xd0, 0x8c, 0x0d, 0x98, 0x4d, 0x87, 0x54, 0x4f, 0xe2, 0x04, 0xca, 0x4c, 0x1c, 0xe2, 0x48, 0x85, 0xc2, 0x8f, 0x64, 0x16, 0xf2, 0x47, 0xd4, 0x1d, 0x46, 0x3b, 0x52,
0xda, 0x98, 0x50, 0x36, 0x7e, 0xd1, 0x60, 0x6e, 0x1d, 0xd9, 0xa6, 0xc7, 0xec, 0xbe, 0xdd, 0x13, 0x5e, 0x1e, 0xe5, 0x1e, 0x68, 0xc6, 0x06, 0xcc, 0xa6, 0x5d, 0xaa, 0x27, 0x71, 0x02, 0x65, 0x6d,
0xfb, 0x3a, 0xba, 0xad, 0xfb, 0x30, 0xe7, 0x39, 0x56, 0x37, 0x39, 0x73, 0x46, 0x5d, 0x9f, 0xee, 0x4c, 0x28, 0x1b, 0x3f, 0x6b, 0x30, 0xb7, 0x8e, 0x6c, 0xd3, 0x67, 0x4e, 0xcf, 0xe9, 0x8a, 0x95,
0x47, 0xd7, 0x36, 0xeb, 0x39, 0x56, 0x6a, 0x3e, 0x6d, 0xd1, 0x7d, 0x0e, 0xbd, 0x39, 0x17, 0x8f, 0x1e, 0x75, 0xeb, 0x3e, 0xcc, 0xf9, 0xae, 0x6d, 0x25, 0xc7, 0xd2, 0xc8, 0x1a, 0xd0, 0xfd, 0xa8,
0xb3, 0xac, 0x64, 0x19, 0xb3, 0x2e, 0x1e, 0x9f, 0xb5, 0x9a, 0x85, 0xbc, 0x63, 0x0f, 0x6c, 0x26, 0x6d, 0xb3, 0xbe, 0x6b, 0xa7, 0x46, 0xd8, 0x16, 0xdd, 0xe7, 0xd0, 0x9b, 0xf3, 0xf0, 0x38, 0x4b,
0x46, 0x70, 0xde, 0x94, 0x44, 0x0c, 0xed, 0x2b, 0x27, 0xd0, 0x36, 0xfe, 0xca, 0xc1, 0xfc, 0x99, 0x4b, 0xa6, 0x31, 0xeb, 0xe1, 0xf1, 0x59, 0xad, 0x59, 0xc8, 0xbb, 0x4e, 0xdf, 0x61, 0x62, 0x4a,
0x84, 0x55, 0xfd, 0xbb, 0x50, 0x71, 0x13, 0x7c, 0xd5, 0x85, 0xd6, 0x19, 0x18, 0x67, 0x19, 0x37, 0xe7, 0x4d, 0x79, 0x89, 0xa1, 0x7d, 0xe5, 0x04, 0xda, 0xc6, 0x5f, 0x39, 0x98, 0x3f, 0x13, 0xb0,
0x52, 0xcc, 0x94, 0x1f, 0xfd, 0x5f, 0x0d, 0x2a, 0x49, 0xf1, 0x79, 0x3b, 0xba, 0x17, 0x20, 0x65, 0xca, 0x7f, 0x17, 0x2a, 0x5e, 0x82, 0xae, 0xaa, 0xd0, 0x3a, 0x03, 0xe3, 0x2c, 0xe5, 0x46, 0x8a,
0x68, 0x45, 0x3b, 0x5a, 0x91, 0xfc, 0xcb, 0x42, 0xba, 0x43, 0x4b, 0xad, 0x98, 0x98, 0xe6, 0x56, 0x98, 0xb2, 0xa3, 0xff, 0xab, 0x41, 0x25, 0xc9, 0x3e, 0x6f, 0x8d, 0x77, 0x03, 0xa4, 0x0c, 0xed,
0x16, 0x3a, 0xc8, 0xad, 0x64, 0x95, 0x11, 0x49, 0x1e, 0xc2, 0x84, 0xe7, 0x58, 0x6a, 0xa3, 0xbc, 0x68, 0x8d, 0xab, 0x2b, 0xff, 0xf8, 0x90, 0xe6, 0xd0, 0x56, 0x5b, 0x28, 0xbe, 0x73, 0x2d, 0x1b,
0x7d, 0x0a, 0x70, 0x74, 0x1f, 0xe3, 0xde, 0x3b, 0xa8, 0x80, 0x60, 0x63, 0x68, 0x72, 0x1b, 0x6e, 0x5d, 0xe4, 0x5a, 0x32, 0xcb, 0xe8, 0x4a, 0x1e, 0xc2, 0x84, 0xef, 0xda, 0x6a, 0xe9, 0xbc, 0x7d,
0xea, 0xe2, 0xb1, 0xf8, 0x8a, 0x79, 0x15, 0x53, 0x17, 0x8f, 0x8d, 0x3f, 0x72, 0xb0, 0x70, 0xae, 0x0a, 0x70, 0x74, 0x1f, 0xe3, 0xda, 0xbb, 0xa8, 0x80, 0xe0, 0x60, 0x68, 0x72, 0x1d, 0xae, 0xea,
0x0a, 0x59, 0x86, 0x4a, 0x6f, 0x18, 0x04, 0xe8, 0xb2, 0x24, 0x10, 0xca, 0x8a, 0x27, 0x6e, 0x72, 0xe1, 0xb1, 0xf8, 0xd0, 0x79, 0x15, 0x55, 0x0f, 0x8f, 0x8d, 0x3f, 0x72, 0xb0, 0x70, 0xae, 0x08,
0x11, 0x4a, 0x2e, 0x3e, 0x67, 0xc9, 0x2b, 0x2f, 0x72, 0xc6, 0x05, 0xd7, 0xdc, 0x86, 0x6a, 0x0a, 0x5f, 0x49, 0xdd, 0x61, 0x10, 0xa0, 0xc7, 0x92, 0x40, 0x28, 0x2b, 0x9a, 0xe8, 0xe4, 0x22, 0x94,
0x2e, 0xa2, 0x13, 0x97, 0xac, 0xc2, 0xb4, 0x05, 0xf9, 0x1c, 0x80, 0xc6, 0x69, 0xd6, 0xf3, 0xe2, 0x3c, 0x7c, 0xce, 0x92, 0x2d, 0x2f, 0x72, 0xc2, 0x05, 0x6d, 0x6e, 0x43, 0x35, 0x05, 0x17, 0x51,
0x91, 0x7e, 0x38, 0x66, 0xe1, 0x8d, 0x0d, 0xd7, 0xc2, 0xe7, 0x68, 0xb5, 0x13, 0x53, 0xc8, 0x4c, 0x89, 0x4b, 0xb6, 0x65, 0x5a, 0x83, 0x7c, 0x0e, 0x40, 0xe3, 0x30, 0xeb, 0x79, 0xf1, 0x48, 0x3f,
0xb8, 0xd3, 0x3f, 0x82, 0x99, 0x0c, 0x15, 0x5e, 0x8c, 0xcd, 0xd9, 0xa2, 0x0b, 0x79, 0x53, 0x12, 0x1c, 0x33, 0xf1, 0xc6, 0x86, 0x67, 0xe3, 0x73, 0xb4, 0xdb, 0x89, 0x29, 0x64, 0x26, 0xcc, 0xe9,
0x31, 0x34, 0x72, 0x09, 0xcc, 0xde, 0x83, 0x1b, 0x4f, 0x69, 0x70, 0x98, 0x84, 0x50, 0x3b, 0x34, 0x1f, 0xc1, 0x4c, 0x86, 0x08, 0x4f, 0xc6, 0xe1, 0x64, 0x51, 0x85, 0xbc, 0x29, 0x2f, 0x31, 0x34,
0x91, 0x5a, 0xd1, 0x53, 0xcb, 0xc0, 0x93, 0xb1, 0x04, 0x37, 0xcf, 0x33, 0x92, 0x88, 0x35, 0x08, 0x72, 0x09, 0xcc, 0xde, 0x83, 0x1b, 0x4f, 0x69, 0x70, 0x98, 0x84, 0x50, 0x3b, 0x34, 0x91, 0xda,
0xd4, 0xd6, 0x91, 0xa9, 0x07, 0x2d, 0x3d, 0x19, 0x6b, 0x70, 0x35, 0xc1, 0x7b, 0xed, 0xb9, 0xd0, 0xd1, 0x53, 0xcb, 0xc0, 0x93, 0xb1, 0x04, 0x37, 0xcf, 0x53, 0x92, 0x88, 0x35, 0x08, 0xd4, 0xd6,
0xfa, 0x4f, 0x83, 0xe9, 0xa8, 0xda, 0x6d, 0x0c, 0x8e, 0xec, 0x1e, 0x92, 0x21, 0x94, 0x13, 0x3b, 0x91, 0xa9, 0x07, 0x2d, 0x2d, 0x19, 0x6b, 0x70, 0x35, 0x41, 0x7b, 0xed, 0xb9, 0xd0, 0xfa, 0x4f,
0x80, 0x2c, 0x5d, 0xb0, 0x1e, 0x44, 0x32, 0xfa, 0xf2, 0xa5, 0x0b, 0xc4, 0x58, 0xfe, 0xe6, 0xcf, 0x83, 0xe9, 0x28, 0xdb, 0x6d, 0x0c, 0x8e, 0x9c, 0x2e, 0x92, 0x21, 0x94, 0x13, 0x3b, 0x80, 0x2c,
0x7f, 0x7e, 0xc8, 0x2d, 0x92, 0x85, 0x66, 0xb4, 0x04, 0x9a, 0x2f, 0x52, 0x3b, 0xe2, 0x25, 0x39, 0x5d, 0xb0, 0x1e, 0x44, 0x30, 0xfa, 0xf2, 0xa5, 0x0b, 0xc4, 0x58, 0xfe, 0xe6, 0xcf, 0x7f, 0xbe,
0x84, 0x4a, 0x72, 0xda, 0x91, 0xe5, 0x4b, 0x87, 0xaf, 0x6e, 0x5c, 0xa4, 0xa2, 0x22, 0xcf, 0x8a, 0xcf, 0x2d, 0x92, 0x85, 0x66, 0xb4, 0x04, 0x9a, 0x2f, 0x52, 0x3b, 0xe2, 0x25, 0x39, 0x84, 0x4a,
0xc8, 0x53, 0x46, 0x29, 0x8e, 0xfc, 0x48, 0xbb, 0xdb, 0xfa, 0x39, 0x07, 0x33, 0xc9, 0x96, 0x47, 0x72, 0xda, 0x91, 0xe5, 0x4b, 0x87, 0xaf, 0x6e, 0x5c, 0x24, 0xa2, 0x3c, 0xcf, 0x0a, 0xcf, 0x53,
0xb5, 0xbf, 0x84, 0xe9, 0x53, 0x83, 0x83, 0xbc, 0x75, 0xc9, 0x5c, 0x91, 0xa9, 0xdc, 0x1e, 0x6b, 0x46, 0x29, 0xf6, 0xfc, 0x48, 0xbb, 0xdb, 0xfa, 0x29, 0x07, 0x33, 0xc9, 0x92, 0x47, 0xb9, 0xbf,
0xfa, 0x18, 0x37, 0x44, 0x36, 0xf3, 0xe4, 0x5a, 0x33, 0x39, 0x79, 0xc2, 0xe6, 0x0b, 0xd9, 0x83, 0x84, 0xe9, 0x53, 0x83, 0x83, 0xbc, 0x75, 0xc9, 0x5c, 0x91, 0xa1, 0xdc, 0x1e, 0x6b, 0xfa, 0x18,
0xef, 0x35, 0x98, 0xcb, 0x46, 0x03, 0x39, 0xb5, 0x07, 0x2f, 0x04, 0x9a, 0xfe, 0xde, 0x78, 0xca, 0x37, 0x44, 0x34, 0xf3, 0xe4, 0x5a, 0x33, 0x39, 0x79, 0xc2, 0xe6, 0x0b, 0x59, 0x83, 0xef, 0x34,
0xe9, 0xa4, 0xee, 0x66, 0x27, 0xd5, 0x72, 0xa1, 0x2a, 0x51, 0x13, 0x35, 0xe9, 0x0b, 0x28, 0xc5, 0x98, 0xcb, 0x46, 0x03, 0x39, 0xb5, 0x07, 0x2f, 0x04, 0x9a, 0xfe, 0xde, 0x78, 0xc2, 0xe9, 0xa0,
0xe0, 0x23, 0x37, 0xcf, 0x14, 0x9e, 0x42, 0xaa, 0x7e, 0xeb, 0x5c, 0xb9, 0x8a, 0x3e, 0x2d, 0xa2, 0xee, 0x66, 0x07, 0xd5, 0xf2, 0xa0, 0x2a, 0x51, 0x13, 0x15, 0xe9, 0x0b, 0x28, 0xc5, 0xe0, 0x23,
0x97, 0x48, 0xa1, 0x29, 0x31, 0xf9, 0xf8, 0x26, 0xcc, 0xf4, 0xbc, 0x41, 0xda, 0xcc, 0xdf, 0xfb, 0x37, 0xcf, 0x24, 0x9e, 0x42, 0xaa, 0x7e, 0xeb, 0x5c, 0xbe, 0xf2, 0x3e, 0x2d, 0xbc, 0x97, 0x48,
0xac, 0xa0, 0xfe, 0xb9, 0xee, 0x4d, 0x8a, 0x0f, 0xd1, 0x7b, 0xff, 0x07, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x29, 0x31, 0xf9, 0xf8, 0x26, 0xcc, 0x74, 0xfd, 0x7e, 0x5a, 0x6d, 0xb0, 0xf7, 0x59, 0x41,
0xcb, 0x5c, 0xce, 0x34, 0xd2, 0x0e, 0x00, 0x00, 0xfd, 0xb9, 0xdd, 0x9b, 0x14, 0x1f, 0xa2, 0xf7, 0xfe, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x1f, 0x9e,
0x9e, 0x7b, 0xf5, 0x0e, 0x00, 0x00,
} }

@ -80,6 +80,9 @@ message Feature {
Detector detector = 5; Detector detector = 5;
// The list of vulnerabilities that affect the feature. // The list of vulnerabilities that affect the feature.
repeated Vulnerability vulnerabilities = 6; repeated Vulnerability vulnerabilities = 6;
// The feature type indicates if the feature represents a source package or
// binary package.
string feature_type = 7;
} }
message Layer { message Layer {

@ -330,6 +330,10 @@
"$ref": "#/definitions/clairVulnerability" "$ref": "#/definitions/clairVulnerability"
}, },
"description": "The list of vulnerabilities that affect the feature." "description": "The list of vulnerabilities that affect the feature."
},
"feature_type": {
"type": "string",
"description": "The feature type indicates if the feature represents a source package or\nbinary package."
} }
} }
}, },

@ -99,6 +99,7 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica
return &noti, nil return &noti, nil
} }
// VulnerabilityFromDatabaseModel converts database Vulnerability to api Vulnerability.
func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerability, error) { func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerability, error) {
metaString := "" metaString := ""
if dbVuln.Metadata != nil { if dbVuln.Metadata != nil {
@ -119,6 +120,7 @@ func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability) (*Vulnerabili
}, nil }, nil
} }
// VulnerabilityWithFixedInFromDatabaseModel converts database VulnerabilityWithFixedIn to api Vulnerability.
func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWithFixedIn) (*Vulnerability, error) { func VulnerabilityWithFixedInFromDatabaseModel(dbVuln database.VulnerabilityWithFixedIn) (*Vulnerability, error) {
vuln, err := VulnerabilityFromDatabaseModel(dbVuln.Vulnerability) vuln, err := VulnerabilityFromDatabaseModel(dbVuln.Vulnerability)
if err != nil { if err != nil {
@ -145,9 +147,11 @@ func NamespacedFeatureFromDatabaseModel(feature database.AncestryFeature) *Featu
VersionFormat: feature.Namespace.VersionFormat, VersionFormat: feature.Namespace.VersionFormat,
Version: version, Version: version,
Detector: DetectorFromDatabaseModel(feature.FeatureBy), Detector: DetectorFromDatabaseModel(feature.FeatureBy),
FeatureType: string(feature.Type),
} }
} }
// DetectorFromDatabaseModel converts database detector to api detector.
func DetectorFromDatabaseModel(detector database.Detector) *Detector { func DetectorFromDatabaseModel(detector database.Detector) *Detector {
return &Detector{ return &Detector{
Name: detector.Name, Name: detector.Name,
@ -156,6 +160,7 @@ func DetectorFromDatabaseModel(detector database.Detector) *Detector {
} }
} }
// DetectorsFromDatabaseModel converts database detectors to api detectors.
func DetectorsFromDatabaseModel(dbDetectors []database.Detector) []*Detector { func DetectorsFromDatabaseModel(dbDetectors []database.Detector) []*Detector {
detectors := make([]*Detector, 0, len(dbDetectors)) detectors := make([]*Detector, 0, len(dbDetectors))
for _, d := range dbDetectors { for _, d := range dbDetectors {

@ -17,7 +17,6 @@
package database package database
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
@ -27,20 +26,20 @@ import (
var ( var (
// ErrBackendException is an error that occurs when the database backend // ErrBackendException is an error that occurs when the database backend
// does not work properly (ie. unreachable). // does not work properly (ie. unreachable).
ErrBackendException = errors.New("database: an error occurred when querying the backend") ErrBackendException = NewStorageError("an error occurred when querying the backend")
// ErrInconsistent is an error that occurs when a database consistency check // ErrInconsistent is an error that occurs when a database consistency check
// fails (i.e. when an entity which is supposed to be unique is detected // fails (i.e. when an entity which is supposed to be unique is detected
// twice) // twice)
ErrInconsistent = errors.New("database: inconsistent database") ErrInconsistent = NewStorageError("inconsistent database")
// ErrInvalidParameters is an error that occurs when the parameters are not valid. // ErrInvalidParameters is an error that occurs when the parameters are not valid.
ErrInvalidParameters = errors.New("database: parameters are not valid") ErrInvalidParameters = NewStorageError("parameters are not valid")
// ErrMissingEntities is an error that occurs when an associated immutable // ErrMissingEntities is an error that occurs when an associated immutable
// entity doesn't exist in the database. This error can indicate a wrong // entity doesn't exist in the database. This error can indicate a wrong
// implementation or corrupted database. // implementation or corrupted database.
ErrMissingEntities = errors.New("database: associated immutable entities are missing in the database") ErrMissingEntities = NewStorageError("associated immutable entities are missing in the database")
) )
// RegistrableComponentConfig is a configuration block that can be used to // RegistrableComponentConfig is a configuration block that can be used to

@ -1,4 +1,4 @@
// Copyright 2018 clair authors // Copyright 2019 clair authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -14,13 +14,22 @@
package database package database
// AffectedFeatureType indicates the type of feature that a vulnerability // StorageError is database error
// affects. type StorageError struct {
type AffectedFeatureType string reason string
original error
}
const ( func (e *StorageError) Error() string {
// AffectSourcePackage indicates the vulnerability affects a source package. return e.reason
AffectSourcePackage AffectedFeatureType = "source" }
// AffectBinaryPackage indicates the vulnerability affects a binary package.
AffectBinaryPackage AffectedFeatureType = "binary" // NewStorageErrorWithInternalError creates a new database error
) func NewStorageErrorWithInternalError(reason string, originalError error) *StorageError {
return &StorageError{reason, originalError}
}
// NewStorageError creates a new database error
func NewStorageError(reason string) *StorageError {
return &StorageError{reason, nil}
}

@ -0,0 +1,52 @@
// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package database
import (
"database/sql/driver"
"fmt"
)
// FeatureType indicates the type of feature that a vulnerability
// affects.
type FeatureType string
const (
SourcePackage FeatureType = "source"
BinaryPackage FeatureType = "binary"
)
var featureTypes = []FeatureType{
SourcePackage,
BinaryPackage,
}
// Scan implements the database/sql.Scanner interface.
func (t *FeatureType) Scan(value interface{}) error {
val := value.(string)
for _, ft := range featureTypes {
if string(ft) == val {
*t = ft
return nil
}
}
panic(fmt.Sprintf("invalid feature type received from database: '%s'", val))
}
// Value implements the database/sql/driver.Valuer interface.
func (t *FeatureType) Value() (driver.Value, error) {
return string(*t), nil
}

@ -155,18 +155,33 @@ type Namespace struct {
VersionFormat string VersionFormat string
} }
func NewNamespace(name string, versionFormat string) *Namespace {
return &Namespace{name, versionFormat}
}
// Feature represents a package detected in a layer but the namespace is not // Feature represents a package detected in a layer but the namespace is not
// determined. // determined.
// //
// e.g. Name: Libssl1.0, Version: 1.0, Name: Openssl, Version: 1.0, VersionFormat: dpkg. // e.g. Name: Libssl1.0, Version: 1.0, VersionFormat: dpkg, Type: binary
// dpkg is the version format of the installer package manager, which in this // dpkg is the version format of the installer package manager, which in this
// case could be dpkg or apk. // case could be dpkg or apk.
type Feature struct { type Feature struct {
Name string Name string
Version string Version string
SourceName string
SourceVersion string
VersionFormat string VersionFormat string
Type FeatureType
}
func NewFeature(name string, version string, versionFormat string, featureType FeatureType) *Feature {
return &Feature{name, version, versionFormat, featureType}
}
func NewBinaryPackage(name string, version string, versionFormat string) *Feature {
return &Feature{name, version, versionFormat, BinaryPackage}
}
func NewSourcePackage(name string, version string, versionFormat string) *Feature {
return &Feature{name, version, versionFormat, SourcePackage}
} }
// NamespacedFeature is a feature with determined namespace and can be affected // NamespacedFeature is a feature with determined namespace and can be affected
@ -179,6 +194,11 @@ type NamespacedFeature struct {
Namespace Namespace Namespace Namespace
} }
func NewNamespacedFeature(namespace *Namespace, feature *Feature) *NamespacedFeature {
// TODO: namespaced feature should use pointer values
return &NamespacedFeature{*feature, *namespace}
}
// AffectedNamespacedFeature is a namespaced feature affected by the // AffectedNamespacedFeature is a namespaced feature affected by the
// vulnerabilities with fixed-in versions for this feature. // vulnerabilities with fixed-in versions for this feature.
type AffectedNamespacedFeature struct { type AffectedNamespacedFeature struct {
@ -199,10 +219,10 @@ type VulnerabilityWithFixedIn struct {
// by a Vulnerability. Namespace and Feature Name is unique. Affected Feature is // by a Vulnerability. Namespace and Feature Name is unique. Affected Feature is
// bound to vulnerability. // bound to vulnerability.
type AffectedFeature struct { type AffectedFeature struct {
// AffectedType determines which type of package it affects. // FeatureType determines which type of package it affects.
AffectedType AffectedFeatureType FeatureType FeatureType
Namespace Namespace Namespace Namespace
FeatureName string FeatureName string
// FixedInVersion is known next feature version that's not affected by the // FixedInVersion is known next feature version that's not affected by the
// vulnerability. Empty FixedInVersion means the unaffected version is // vulnerability. Empty FixedInVersion means the unaffected version is
// unknown. // unknown.

@ -23,10 +23,11 @@ const (
findAncestryFeatures = ` findAncestryFeatures = `
SELECT namespace.name, namespace.version_format, feature.name, SELECT namespace.name, namespace.version_format, feature.name,
feature.version, feature.version_format, ancestry_layer.ancestry_index, feature.version, feature.version_format, feature_type.name, ancestry_layer.ancestry_index,
ancestry_feature.feature_detector_id, ancestry_feature.namespace_detector_id ancestry_feature.feature_detector_id, ancestry_feature.namespace_detector_id
FROM namespace, feature, namespaced_feature, ancestry_layer, ancestry_feature FROM namespace, feature, feature_type, namespaced_feature, ancestry_layer, ancestry_feature
WHERE ancestry_layer.ancestry_id = $1 WHERE ancestry_layer.ancestry_id = $1
AND feature_type.id = feature.type
AND ancestry_feature.ancestry_layer_id = ancestry_layer.id AND ancestry_feature.ancestry_layer_id = ancestry_layer.id
AND ancestry_feature.namespaced_feature_id = namespaced_feature.id AND ancestry_feature.namespaced_feature_id = namespaced_feature.id
AND namespaced_feature.feature_id = feature.id AND namespaced_feature.feature_id = feature.id
@ -256,6 +257,7 @@ func (tx *pgSession) findAncestryFeatures(ancestryID int64, detectors detectorMa
&feature.Feature.Name, &feature.Feature.Name,
&feature.Feature.Version, &feature.Feature.Version,
&feature.Feature.VersionFormat, &feature.Feature.VersionFormat,
&feature.Feature.Type,
&index, &index,
&featureDetectorID, &featureDetectorID,
&namespaceDetectorID, &namespaceDetectorID,

@ -25,6 +25,7 @@ import (
"github.com/pborman/uuid" "github.com/pborman/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt" "github.com/coreos/clair/ext/versionfmt"
@ -65,19 +66,13 @@ func testGenRandomVulnerabilityAndNamespacedFeature(t *testing.T, store database
for i := 0; i < numFeatures; i++ { for i := 0; i < numFeatures; i++ {
version := rand.Intn(numFeatures) version := rand.Intn(numFeatures)
features[i] = database.Feature{ features[i] = *database.NewSourcePackage(featureName, strconv.Itoa(version), featureVersionFormat)
Name: featureName,
VersionFormat: featureVersionFormat,
Version: strconv.Itoa(version),
}
nsFeatures[i] = database.NamespacedFeature{ nsFeatures[i] = database.NamespacedFeature{
Namespace: namespace, Namespace: namespace,
Feature: features[i], Feature: features[i],
} }
} }
// insert features
if !assert.Nil(t, tx.PersistFeatures(features)) { if !assert.Nil(t, tx.PersistFeatures(features)) {
t.FailNow() t.FailNow()
} }
@ -98,6 +93,7 @@ func testGenRandomVulnerabilityAndNamespacedFeature(t *testing.T, store database
{ {
Namespace: namespace, Namespace: namespace,
FeatureName: featureName, FeatureName: featureName,
FeatureType: database.SourcePackage,
AffectedVersion: strconv.Itoa(version), AffectedVersion: strconv.Itoa(version),
FixedInVersion: strconv.Itoa(version), FixedInVersion: strconv.Itoa(version),
}, },
@ -117,7 +113,6 @@ func TestConcurrency(t *testing.T) {
t.FailNow() t.FailNow()
} }
defer store.Close() defer store.Close()
start := time.Now() start := time.Now()
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(100) wg.Add(100)
@ -137,74 +132,33 @@ func TestConcurrency(t *testing.T) {
fmt.Println("total", time.Since(start)) fmt.Println("total", time.Since(start))
} }
func genRandomNamespaces(t *testing.T, count int) []database.Namespace {
r := make([]database.Namespace, count)
for i := 0; i < count; i++ {
r[i] = database.Namespace{
Name: uuid.New(),
VersionFormat: "dpkg",
}
}
return r
}
func TestCaching(t *testing.T) { func TestCaching(t *testing.T) {
store, err := openDatabaseForTest("Caching", false) store, err := openDatabaseForTest("Caching", false)
if !assert.Nil(t, err) { require.Nil(t, err)
t.FailNow()
}
defer store.Close() defer store.Close()
nsFeatures, vulnerabilities := testGenRandomVulnerabilityAndNamespacedFeature(t, store) nsFeatures, vulnerabilities := testGenRandomVulnerabilityAndNamespacedFeature(t, store)
tx, err := store.Begin()
require.Nil(t, err)
fmt.Printf("%d features, %d vulnerabilities are generated", len(nsFeatures), len(vulnerabilities)) require.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
require.Nil(t, tx.Commit())
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
tx, err := store.Begin()
if !assert.Nil(t, err) {
t.FailNow()
}
assert.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
fmt.Println("finished to insert namespaced features")
tx.Commit()
}()
go func() {
defer wg.Done()
tx, err := store.Begin()
if !assert.Nil(t, err) {
t.FailNow()
}
assert.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
fmt.Println("finished to insert vulnerabilities")
tx.Commit()
}() tx, err = store.Begin()
require.Nil(t, tx.Commit())
wg.Wait() require.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
require.Nil(t, tx.Commit())
tx, err := store.Begin() tx, err = store.Begin()
if !assert.Nil(t, err) { require.Nil(t, err)
t.FailNow()
}
defer tx.Rollback() defer tx.Rollback()
// Verify consistency now.
affected, err := tx.FindAffectedNamespacedFeatures(nsFeatures) affected, err := tx.FindAffectedNamespacedFeatures(nsFeatures)
if !assert.Nil(t, err) { require.Nil(t, err)
t.FailNow()
}
for _, ansf := range affected { for _, ansf := range affected {
if !assert.True(t, ansf.Valid) { require.True(t, ansf.Valid)
t.FailNow()
}
expectedAffectedNames := []string{} expectedAffectedNames := []string{}
for _, vuln := range vulnerabilities { for _, vuln := range vulnerabilities {
@ -220,7 +174,7 @@ func TestCaching(t *testing.T) {
actualAffectedNames = append(actualAffectedNames, s.Name) actualAffectedNames = append(actualAffectedNames, s.Name)
} }
assert.Len(t, strutil.Difference(expectedAffectedNames, actualAffectedNames), 0) require.Len(t, strutil.Difference(expectedAffectedNames, actualAffectedNames), 0, "\nvulns: %#v\nfeature:%#v\nexpected:%#v\nactual:%#v", vulnerabilities, ansf.NamespacedFeature, expectedAffectedNames, actualAffectedNames)
assert.Len(t, strutil.Difference(actualAffectedNames, expectedAffectedNames), 0) require.Len(t, strutil.Difference(actualAffectedNames, expectedAffectedNames), 0)
} }
} }

@ -46,6 +46,7 @@ const (
AND nf.feature_id = f.id AND nf.feature_id = f.id
AND nf.namespace_id = v.namespace_id AND nf.namespace_id = v.namespace_id
AND vaf.feature_name = f.name AND vaf.feature_name = f.name
AND vaf.feature_type = f.type
AND vaf.vulnerability_id = v.id AND vaf.vulnerability_id = v.id
AND v.deleted_at IS NULL` AND v.deleted_at IS NULL`
@ -68,6 +69,11 @@ func (tx *pgSession) PersistFeatures(features []database.Feature) error {
return nil return nil
} }
types, err := tx.getFeatureTypeMap()
if err != nil {
return err
}
// Sorting is needed before inserting into database to prevent deadlock. // Sorting is needed before inserting into database to prevent deadlock.
sort.Slice(features, func(i, j int) bool { sort.Slice(features, func(i, j int) bool {
return features[i].Name < features[j].Name || return features[i].Name < features[j].Name ||
@ -78,13 +84,13 @@ func (tx *pgSession) PersistFeatures(features []database.Feature) error {
// TODO(Sida): A better interface for bulk insertion is needed. // TODO(Sida): A better interface for bulk insertion is needed.
keys := make([]interface{}, 0, len(features)*3) keys := make([]interface{}, 0, len(features)*3)
for _, f := range features { for _, f := range features {
keys = append(keys, f.Name, f.Version, f.VersionFormat) keys = append(keys, f.Name, f.Version, f.VersionFormat, types.byName[f.Type])
if f.Name == "" || f.Version == "" || f.VersionFormat == "" { if f.Name == "" || f.Version == "" || f.VersionFormat == "" {
return commonerr.NewBadRequestError("Empty feature name, version or version format is not allowed") return commonerr.NewBadRequestError("Empty feature name, version or version format is not allowed")
} }
} }
_, err := tx.Exec(queryPersistFeature(len(features)), keys...) _, err = tx.Exec(queryPersistFeature(len(features)), keys...)
return handleError("queryPersistFeature", err) return handleError("queryPersistFeature", err)
} }
@ -240,52 +246,27 @@ func (tx *pgSession) PersistNamespacedFeatures(features []database.NamespacedFea
return nil return nil
} }
// FindAffectedNamespacedFeatures looks up cache table and retrieves all // FindAffectedNamespacedFeatures retrieves vulnerabilities associated with the
// vulnerabilities associated with the features. // feature.
func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.NamespacedFeature) ([]database.NullableAffectedNamespacedFeature, error) { func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.NamespacedFeature) ([]database.NullableAffectedNamespacedFeature, error) {
if len(features) == 0 { if len(features) == 0 {
return nil, nil return nil, nil
} }
returnFeatures := make([]database.NullableAffectedNamespacedFeature, len(features)) vulnerableFeatures := make([]database.NullableAffectedNamespacedFeature, len(features))
featureIDs, err := tx.findNamespacedFeatureIDs(features)
// featureMap is used to keep track of duplicated features.
featureMap := map[database.NamespacedFeature][]*database.NullableAffectedNamespacedFeature{}
// initialize return value and generate unique feature request queries.
for i, f := range features {
returnFeatures[i] = database.NullableAffectedNamespacedFeature{
AffectedNamespacedFeature: database.AffectedNamespacedFeature{
NamespacedFeature: f,
},
}
featureMap[f] = append(featureMap[f], &returnFeatures[i])
}
// query unique namespaced features
distinctFeatures := []database.NamespacedFeature{}
for f := range featureMap {
distinctFeatures = append(distinctFeatures, f)
}
nsFeatureIDs, err := tx.findNamespacedFeatureIDs(distinctFeatures)
if err != nil { if err != nil {
return nil, err return nil, err
} }
toQuery := []int64{} for i, id := range featureIDs {
featureIDMap := map[int64][]*database.NullableAffectedNamespacedFeature{}
for i, id := range nsFeatureIDs {
if id.Valid { if id.Valid {
toQuery = append(toQuery, id.Int64) vulnerableFeatures[i].Valid = true
for _, f := range featureMap[distinctFeatures[i]] { vulnerableFeatures[i].NamespacedFeature = features[i]
f.Valid = id.Valid
featureIDMap[id.Int64] = append(featureIDMap[id.Int64], f)
}
} }
} }
rows, err := tx.Query(searchNamespacedFeaturesVulnerabilities, pq.Array(toQuery)) rows, err := tx.Query(searchNamespacedFeaturesVulnerabilities, pq.Array(featureIDs))
if err != nil { if err != nil {
return nil, handleError("searchNamespacedFeaturesVulnerabilities", err) return nil, handleError("searchNamespacedFeaturesVulnerabilities", err)
} }
@ -296,6 +277,7 @@ func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.Namespac
featureID int64 featureID int64
vuln database.VulnerabilityWithFixedIn vuln database.VulnerabilityWithFixedIn
) )
err := rows.Scan(&featureID, err := rows.Scan(&featureID,
&vuln.Name, &vuln.Name,
&vuln.Description, &vuln.Description,
@ -306,16 +288,19 @@ func (tx *pgSession) FindAffectedNamespacedFeatures(features []database.Namespac
&vuln.Namespace.Name, &vuln.Namespace.Name,
&vuln.Namespace.VersionFormat, &vuln.Namespace.VersionFormat,
) )
if err != nil { if err != nil {
return nil, handleError("searchNamespacedFeaturesVulnerabilities", err) return nil, handleError("searchNamespacedFeaturesVulnerabilities", err)
} }
for _, f := range featureIDMap[featureID] { for i, id := range featureIDs {
f.AffectedBy = append(f.AffectedBy, vuln) if id.Valid && id.Int64 == featureID {
vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy = append(vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy, vuln)
}
} }
} }
return returnFeatures, nil return vulnerableFeatures, nil
} }
func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature) ([]sql.NullInt64, error) { func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature) ([]sql.NullInt64, error) {
@ -323,11 +308,10 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
return nil, nil return nil, nil
} }
nfsMap := map[database.NamespacedFeature]sql.NullInt64{} nfsMap := map[database.NamespacedFeature]int64{}
keys := make([]interface{}, 0, len(nfs)*4) keys := make([]interface{}, 0, len(nfs)*5)
for _, nf := range nfs { for _, nf := range nfs {
keys = append(keys, nf.Name, nf.Version, nf.VersionFormat, nf.Namespace.Name) keys = append(keys, nf.Name, nf.Version, nf.VersionFormat, nf.Type, nf.Namespace.Name)
nfsMap[nf] = sql.NullInt64{}
} }
rows, err := tx.Query(querySearchNamespacedFeature(len(nfs)), keys...) rows, err := tx.Query(querySearchNamespacedFeature(len(nfs)), keys...)
@ -337,12 +321,12 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
defer rows.Close() defer rows.Close()
var ( var (
id sql.NullInt64 id int64
nf database.NamespacedFeature nf database.NamespacedFeature
) )
for rows.Next() { for rows.Next() {
err := rows.Scan(&id, &nf.Name, &nf.Version, &nf.VersionFormat, &nf.Namespace.Name) err := rows.Scan(&id, &nf.Name, &nf.Version, &nf.VersionFormat, &nf.Type, &nf.Namespace.Name)
nf.Namespace.VersionFormat = nf.VersionFormat nf.Namespace.VersionFormat = nf.VersionFormat
if err != nil { if err != nil {
return nil, handleError("searchNamespacedFeature", err) return nil, handleError("searchNamespacedFeature", err)
@ -352,7 +336,11 @@ func (tx *pgSession) findNamespacedFeatureIDs(nfs []database.NamespacedFeature)
ids := make([]sql.NullInt64, len(nfs)) ids := make([]sql.NullInt64, len(nfs))
for i, nf := range nfs { for i, nf := range nfs {
ids[i] = nfsMap[nf] if id, ok := nfsMap[nf]; ok {
ids[i] = sql.NullInt64{id, true}
} else {
ids[i] = sql.NullInt64{}
}
} }
return ids, nil return ids, nil
@ -363,11 +351,17 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err
return nil, nil return nil, nil
} }
types, err := tx.getFeatureTypeMap()
if err != nil {
return nil, err
}
fMap := map[database.Feature]sql.NullInt64{} fMap := map[database.Feature]sql.NullInt64{}
keys := make([]interface{}, 0, len(fs)*3) keys := make([]interface{}, 0, len(fs)*4)
for _, f := range fs { for _, f := range fs {
keys = append(keys, f.Name, f.Version, f.VersionFormat) typeID := types.byName[f.Type]
keys = append(keys, f.Name, f.Version, f.VersionFormat, typeID)
fMap[f] = sql.NullInt64{} fMap[f] = sql.NullInt64{}
} }
@ -382,10 +376,13 @@ func (tx *pgSession) findFeatureIDs(fs []database.Feature) ([]sql.NullInt64, err
f database.Feature f database.Feature
) )
for rows.Next() { for rows.Next() {
err := rows.Scan(&id, &f.Name, &f.Version, &f.VersionFormat) var typeID int
err := rows.Scan(&id, &f.Name, &f.Version, &f.VersionFormat, &typeID)
if err != nil { if err != nil {
return nil, handleError("querySearchFeatureID", err) return nil, handleError("querySearchFeatureID", err)
} }
f.Type = types.byID[typeID]
fMap[f] = id fMap[f] = id
} }

@ -18,134 +18,53 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
// register dpkg feature lister for testing
_ "github.com/coreos/clair/ext/featurefmt/dpkg"
) )
func TestPersistFeatures(t *testing.T) { func TestPersistFeatures(t *testing.T) {
datastore, tx := openSessionForTest(t, "PersistFeatures", false) tx, cleanup := createTestPgSession(t, "TestPersistFeatures")
defer closeTest(t, datastore, tx) defer cleanup()
f1 := database.Feature{} invalid := database.Feature{}
f2 := database.Feature{Name: "n", Version: "v", VersionFormat: "vf"} valid := *database.NewBinaryPackage("mount", "2.31.1-0.4ubuntu3.1", "dpkg")
// empty
assert.Nil(t, tx.PersistFeatures([]database.Feature{}))
// invalid // invalid
assert.NotNil(t, tx.PersistFeatures([]database.Feature{f1})) require.NotNil(t, tx.PersistFeatures([]database.Feature{invalid}))
// duplicated
assert.Nil(t, tx.PersistFeatures([]database.Feature{f2, f2}))
// existing // existing
assert.Nil(t, tx.PersistFeatures([]database.Feature{f2})) require.Nil(t, tx.PersistFeatures([]database.Feature{valid}))
require.Nil(t, tx.PersistFeatures([]database.Feature{valid}))
fs := listFeatures(t, tx) features := selectAllFeatures(t, tx)
assert.Len(t, fs, 1) assert.Equal(t, []database.Feature{valid}, features)
assert.Equal(t, f2, fs[0])
} }
func TestPersistNamespacedFeatures(t *testing.T) { func TestPersistNamespacedFeatures(t *testing.T) {
datastore, tx := openSessionForTest(t, "PersistNamespacedFeatures", true) tx, cleanup := createTestPgSessionWithFixtures(t, "TestPersistNamespacedFeatures")
defer closeTest(t, datastore, tx) defer cleanup()
// existing features // existing features
f1 := database.Feature{ f1 := database.NewSourcePackage("ourchat", "0.5", "dpkg")
Name: "ourchat",
Version: "0.5",
VersionFormat: "dpkg",
}
// non-existing features // non-existing features
f2 := database.Feature{ f2 := database.NewSourcePackage("fake!", "", "")
Name: "fake!",
}
f3 := database.Feature{
Name: "openssl",
Version: "2.0",
VersionFormat: "dpkg",
}
// exising namespace // exising namespace
n1 := database.Namespace{ n1 := database.NewNamespace("debian:7", "dpkg")
Name: "debian:7",
VersionFormat: "dpkg",
}
n3 := database.Namespace{
Name: "debian:8",
VersionFormat: "dpkg",
}
// non-existing namespace // non-existing namespace
n2 := database.Namespace{ n2 := database.NewNamespace("debian:non", "dpkg")
Name: "debian:non",
VersionFormat: "dpkg",
}
// existing namespaced feature // existing namespaced feature
nf1 := database.NamespacedFeature{ nf1 := database.NewNamespacedFeature(n1, f1)
Namespace: n1,
Feature: f1,
}
// invalid namespaced feature // invalid namespaced feature
nf2 := database.NamespacedFeature{ nf2 := database.NewNamespacedFeature(n2, f2)
Namespace: n2,
Feature: f2,
}
// new namespaced feature affected by vulnerability
nf3 := database.NamespacedFeature{
Namespace: n3,
Feature: f3,
}
// namespaced features with namespaces or features not in the database will // namespaced features with namespaces or features not in the database will
// generate error. // generate error.
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{})) assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{}))
assert.NotNil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{*nf1, *nf2}))
assert.NotNil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1, nf2}))
// valid case: insert nf3 // valid case: insert nf3
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1, nf3})) assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{*nf1}))
all := listNamespacedFeatures(t, tx) all := listNamespacedFeatures(t, tx)
assert.Contains(t, all, nf1) assert.Contains(t, all, *nf1)
assert.Contains(t, all, nf3)
}
func TestVulnerableFeature(t *testing.T) {
datastore, tx := openSessionForTest(t, "VulnerableFeature", true)
defer closeTest(t, datastore, tx)
f1 := database.Feature{
Name: "openssl",
Version: "1.3",
VersionFormat: "dpkg",
}
n1 := database.Namespace{
Name: "debian:7",
VersionFormat: "dpkg",
}
nf1 := database.NamespacedFeature{
Namespace: n1,
Feature: f1,
}
assert.Nil(t, tx.PersistFeatures([]database.Feature{f1}))
assert.Nil(t, tx.PersistNamespacedFeatures([]database.NamespacedFeature{nf1}))
assert.Nil(t, tx.CacheAffectedNamespacedFeatures([]database.NamespacedFeature{nf1}))
// ensure the namespaced feature is affected correctly
anf, err := tx.FindAffectedNamespacedFeatures([]database.NamespacedFeature{nf1})
if assert.Nil(t, err) &&
assert.Len(t, anf, 1) &&
assert.True(t, anf[0].Valid) &&
assert.Len(t, anf[0].AffectedBy, 1) {
assert.Equal(t, "CVE-OPENSSL-1-DEB7", anf[0].AffectedBy[0].Name)
}
} }
func TestFindAffectedNamespacedFeatures(t *testing.T) { func TestFindAffectedNamespacedFeatures(t *testing.T) {
@ -156,6 +75,7 @@ func TestFindAffectedNamespacedFeatures(t *testing.T) {
Name: "openssl", Name: "openssl",
Version: "1.0", Version: "1.0",
VersionFormat: "dpkg", VersionFormat: "dpkg",
Type: database.SourcePackage,
}, },
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:7", Name: "debian:7",
@ -173,30 +93,41 @@ func TestFindAffectedNamespacedFeatures(t *testing.T) {
} }
func listNamespacedFeatures(t *testing.T, tx *pgSession) []database.NamespacedFeature { func listNamespacedFeatures(t *testing.T, tx *pgSession) []database.NamespacedFeature {
rows, err := tx.Query(`SELECT f.name, f.version, f.version_format, n.name, n.version_format types, err := tx.getFeatureTypeMap()
if err != nil {
panic(err)
}
rows, err := tx.Query(`SELECT f.name, f.version, f.version_format, f.type, n.name, n.version_format
FROM feature AS f, namespace AS n, namespaced_feature AS nf FROM feature AS f, namespace AS n, namespaced_feature AS nf
WHERE nf.feature_id = f.id AND nf.namespace_id = n.id`) WHERE nf.feature_id = f.id AND nf.namespace_id = n.id`)
if err != nil { if err != nil {
t.Error(err) panic(err)
t.FailNow()
} }
nf := []database.NamespacedFeature{} nf := []database.NamespacedFeature{}
for rows.Next() { for rows.Next() {
f := database.NamespacedFeature{} f := database.NamespacedFeature{}
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &f.Namespace.Name, &f.Namespace.VersionFormat) var typeID int
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &typeID, &f.Namespace.Name, &f.Namespace.VersionFormat)
if err != nil { if err != nil {
t.Error(err) panic(err)
t.FailNow()
} }
f.Type = types.byID[typeID]
nf = append(nf, f) nf = append(nf, f)
} }
return nf return nf
} }
func listFeatures(t *testing.T, tx *pgSession) []database.Feature { func selectAllFeatures(t *testing.T, tx *pgSession) []database.Feature {
rows, err := tx.Query("SELECT name, version, version_format FROM feature") types, err := tx.getFeatureTypeMap()
if err != nil {
panic(err)
}
rows, err := tx.Query("SELECT name, version, version_format, type FROM feature")
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
@ -204,7 +135,9 @@ func listFeatures(t *testing.T, tx *pgSession) []database.Feature {
fs := []database.Feature{} fs := []database.Feature{}
for rows.Next() { for rows.Next() {
f := database.Feature{} f := database.Feature{}
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat) var typeID int
err := rows.Scan(&f.Name, &f.Version, &f.VersionFormat, &typeID)
f.Type = types.byID[typeID]
if err != nil { if err != nil {
t.FailNow() t.FailNow()
} }
@ -233,3 +166,33 @@ func assertNamespacedFeatureEqual(t *testing.T, expected []database.NamespacedFe
} }
return false return false
} }
func TestFindNamespacedFeatureIDs(t *testing.T) {
tx, cleanup := createTestPgSessionWithFixtures(t, "TestFindNamespacedFeatureIDs")
defer cleanup()
features := []database.NamespacedFeature{}
expectedIDs := []int{}
for id, feature := range realNamespacedFeatures {
features = append(features, feature)
expectedIDs = append(expectedIDs, id)
}
features = append(features, realNamespacedFeatures[1]) // test duplicated
expectedIDs = append(expectedIDs, 1)
namespace := realNamespaces[1]
features = append(features, *database.NewNamespacedFeature(&namespace, database.NewBinaryPackage("not-found", "1.0", "dpkg"))) // test not found feature
ids, err := tx.findNamespacedFeatureIDs(features)
require.Nil(t, err)
require.Len(t, ids, len(expectedIDs)+1)
for i, id := range ids {
if i == len(ids)-1 {
require.False(t, id.Valid)
} else {
require.True(t, id.Valid)
require.Equal(t, expectedIDs[i], int(id.Int64))
}
}
}

@ -0,0 +1,53 @@
// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pgsql
import "github.com/coreos/clair/database"
const (
selectAllFeatureTypes = `SELECT id, name FROM feature_type`
)
type featureTypes struct {
byID map[int]database.FeatureType
byName map[database.FeatureType]int
}
func newFeatureTypes() *featureTypes {
return &featureTypes{make(map[int]database.FeatureType), make(map[database.FeatureType]int)}
}
func (tx *pgSession) getFeatureTypeMap() (*featureTypes, error) {
rows, err := tx.Query(selectAllFeatureTypes)
if err != nil {
return nil, err
}
types := newFeatureTypes()
for rows.Next() {
var (
id int
name database.FeatureType
)
if err := rows.Scan(&id, &name); err != nil {
return nil, err
}
types.byID[id] = name
types.byName[name] = id
}
return types, nil
}

@ -0,0 +1,38 @@
// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pgsql
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database"
)
func TestGetFeatureTypeMap(t *testing.T) {
tx, cleanup := createTestPgSession(t, "TestGetFeatureTypeMap")
defer cleanup()
types, err := tx.getFeatureTypeMap()
if err != nil {
require.Nil(t, err, err.Error())
}
require.Equal(t, database.SourcePackage, types.byID[1])
require.Equal(t, database.BinaryPackage, types.byID[2])
require.Equal(t, 1, types.byName[database.SourcePackage])
require.Equal(t, 2, types.byName[database.BinaryPackage])
}

@ -37,9 +37,10 @@ const (
SELECT id FROM layer WHERE hash = $1` SELECT id FROM layer WHERE hash = $1`
findLayerFeatures = ` findLayerFeatures = `
SELECT f.name, f.version, f.version_format, lf.detector_id SELECT f.name, f.version, f.version_format, t.name, lf.detector_id
FROM layer_feature AS lf, feature AS f FROM layer_feature AS lf, feature AS f, feature_type AS t
WHERE lf.feature_id = f.id WHERE lf.feature_id = f.id
AND t.id = f.type
AND lf.layer_id = $1` AND lf.layer_id = $1`
findLayerNamespaces = ` findLayerNamespaces = `
@ -307,7 +308,7 @@ func (tx *pgSession) findLayerFeatures(layerID int64, detectors detectorMap) ([]
detectorID int64 detectorID int64
feature database.LayerFeature feature database.LayerFeature
) )
if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &detectorID); err != nil { if err := rows.Scan(&feature.Name, &feature.Version, &feature.VersionFormat, &feature.Type, &detectorID); err != nil {
return nil, handleError("findLayerFeatures", err) return nil, handleError("findLayerFeatures", err)
} }

@ -43,12 +43,12 @@ var persistLayerTests = []struct {
features: []database.LayerFeature{ features: []database.LayerFeature{
{realFeatures[1], realDetectors[1]}, {realFeatures[1], realDetectors[1]},
}, },
err: "database: parameters are not valid", err: "parameters are not valid",
}, },
{ {
title: "layer with non-existing feature", title: "layer with non-existing feature",
name: "random-forest", name: "random-forest",
err: "database: associated immutable entities are missing in the database", err: "associated immutable entities are missing in the database",
by: []database.Detector{realDetectors[2]}, by: []database.Detector{realDetectors[2]},
features: []database.LayerFeature{ features: []database.LayerFeature{
{fakeFeatures[1], realDetectors[2]}, {fakeFeatures[1], realDetectors[2]},
@ -57,7 +57,7 @@ var persistLayerTests = []struct {
{ {
title: "layer with non-existing namespace", title: "layer with non-existing namespace",
name: "random-forest2", name: "random-forest2",
err: "database: associated immutable entities are missing in the database", err: "associated immutable entities are missing in the database",
by: []database.Detector{realDetectors[1]}, by: []database.Detector{realDetectors[1]},
namespaces: []database.LayerNamespace{ namespaces: []database.LayerNamespace{
{fakeNamespaces[1], realDetectors[1]}, {fakeNamespaces[1], realDetectors[1]},
@ -66,7 +66,7 @@ var persistLayerTests = []struct {
{ {
title: "layer with non-existing detector", title: "layer with non-existing detector",
name: "random-forest3", name: "random-forest3",
err: "database: associated immutable entities are missing in the database", err: "associated immutable entities are missing in the database",
by: []database.Detector{fakeDetector[1]}, by: []database.Detector{fakeDetector[1]},
}, },
{ {

@ -19,7 +19,12 @@ var (
// the ancestry. // the ancestry.
entities = MigrationQuery{ entities = MigrationQuery{
Up: []string{ Up: []string{
// namespaces `CREATE TABLE IF NOT EXISTS feature_type (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE);`,
`INSERT INTO feature_type(name) VALUES ('source'), ('binary')`,
`CREATE TABLE IF NOT EXISTS namespace ( `CREATE TABLE IF NOT EXISTS namespace (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT NULL, name TEXT NULL,
@ -27,13 +32,13 @@ var (
UNIQUE (name, version_format));`, UNIQUE (name, version_format));`,
`CREATE INDEX ON namespace(name);`, `CREATE INDEX ON namespace(name);`,
// features
`CREATE TABLE IF NOT EXISTS feature ( `CREATE TABLE IF NOT EXISTS feature (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
version_format TEXT NOT NULL, version_format TEXT NOT NULL,
UNIQUE (name, version, version_format));`, type INT REFERENCES feature_type ON DELETE CASCADE,
UNIQUE (name, version, version_format, type));`,
`CREATE INDEX ON feature(name);`, `CREATE INDEX ON feature(name);`,
`CREATE TABLE IF NOT EXISTS namespaced_feature ( `CREATE TABLE IF NOT EXISTS namespaced_feature (
@ -43,17 +48,15 @@ var (
UNIQUE (namespace_id, feature_id));`, UNIQUE (namespace_id, feature_id));`,
}, },
Down: []string{ Down: []string{
`DROP TABLE IF EXISTS namespace, feature, namespaced_feature CASCADE;`, `DROP TABLE IF EXISTS namespace, feature, namespaced_feature, feature_type CASCADE;`,
}, },
} }
// detector is analysis extensions used by the worker. // detector is analysis extensions used by the worker.
detector = MigrationQuery{ detector = MigrationQuery{
Up: []string{ Up: []string{
// Detector Type
`CREATE TYPE detector_type AS ENUM ('namespace', 'feature');`, `CREATE TYPE detector_type AS ENUM ('namespace', 'feature');`,
// Detector
`CREATE TABLE IF NOT EXISTS detector ( `CREATE TABLE IF NOT EXISTS detector (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
@ -70,7 +73,6 @@ var (
// layer contains all metadata and scanned features and namespaces. // layer contains all metadata and scanned features and namespaces.
layer = MigrationQuery{ layer = MigrationQuery{
Up: []string{ Up: []string{
// layers
`CREATE TABLE IF NOT EXISTS layer( `CREATE TABLE IF NOT EXISTS layer(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
hash TEXT NOT NULL UNIQUE);`, hash TEXT NOT NULL UNIQUE);`,
@ -107,7 +109,6 @@ var (
// layers. // layers.
ancestry = MigrationQuery{ ancestry = MigrationQuery{
Up: []string{ Up: []string{
// ancestry
`CREATE TABLE IF NOT EXISTS ancestry ( `CREATE TABLE IF NOT EXISTS ancestry (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE);`, name TEXT NOT NULL UNIQUE);`,
@ -145,7 +146,6 @@ var (
Up: []string{ Up: []string{
`CREATE TYPE severity AS ENUM ('Unknown', 'Negligible', 'Low', 'Medium', 'High', 'Critical', 'Defcon1');`, `CREATE TYPE severity AS ENUM ('Unknown', 'Negligible', 'Low', 'Medium', 'High', 'Critical', 'Defcon1');`,
// vulnerability
`CREATE TABLE IF NOT EXISTS vulnerability ( `CREATE TABLE IF NOT EXISTS vulnerability (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
namespace_id INT REFERENCES Namespace, namespace_id INT REFERENCES Namespace,
@ -159,13 +159,18 @@ var (
`CREATE INDEX ON vulnerability(namespace_id, name);`, `CREATE INDEX ON vulnerability(namespace_id, name);`,
`CREATE INDEX ON vulnerability(namespace_id);`, `CREATE INDEX ON vulnerability(namespace_id);`,
// vulnerability_affected_feature is a de-normalized table to store
// the affected features in a independent place other than the
// feature table to reduce table lock issue, and makes it easier for
// decoupling updater and the Clair main logic.
`CREATE TABLE IF NOT EXISTS vulnerability_affected_feature ( `CREATE TABLE IF NOT EXISTS vulnerability_affected_feature (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
vulnerability_id INT REFERENCES vulnerability ON DELETE CASCADE, vulnerability_id INT REFERENCES vulnerability ON DELETE CASCADE,
feature_name TEXT NOT NULL, feature_name TEXT NOT NULL,
feature_type INT NOT NULL REFERENCES feature_type ON DELETE CASCADE,
affected_version TEXT, affected_version TEXT,
fixedin TEXT);`, fixedin TEXT);`,
`CREATE INDEX ON vulnerability_affected_feature(vulnerability_id, feature_name);`, `CREATE INDEX ON vulnerability_affected_feature(vulnerability_id, feature_name, feature_type);`,
`CREATE TABLE IF NOT EXISTS vulnerability_affected_namespaced_feature( `CREATE TABLE IF NOT EXISTS vulnerability_affected_namespaced_feature(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
@ -176,8 +181,8 @@ var (
`CREATE INDEX ON vulnerability_affected_namespaced_feature(namespaced_feature_id);`, `CREATE INDEX ON vulnerability_affected_namespaced_feature(namespaced_feature_id);`,
}, },
Down: []string{ Down: []string{
`DROP TYPE IF EXISTS severity;`,
`DROP TABLE IF EXISTS vulnerability, vulnerability_affected_feature, vulnerability_affected_namespaced_feature CASCADE;`, `DROP TABLE IF EXISTS vulnerability, vulnerability_affected_feature, vulnerability_affected_namespaced_feature CASCADE;`,
`DROP TYPE IF EXISTS severity;`,
}, },
} }

@ -0,0 +1,60 @@
// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pgsql
import (
"testing"
_ "github.com/lib/pq"
"github.com/remind101/migrate"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database/pgsql/migrations"
)
var userTableCount = `SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname='public'`
func TestMigration(t *testing.T) {
db, cleanup := createAndConnectTestDB(t, "TestMigration")
defer cleanup()
err := migrate.NewPostgresMigrator(db).Exec(migrate.Up, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
err = migrate.NewPostgresMigrator(db).Exec(migrate.Down, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
rows, err := db.Query(userTableCount)
if err != nil {
panic(err)
}
var (
tables []string
table string
)
for rows.Next() {
if err = rows.Scan(&table); err != nil {
panic(err)
}
tables = append(tables, table)
}
require.True(t, len(tables) == 1 && tables[0] == "schema_migrations", "Only `schema_migrations` should be left")
}

@ -211,8 +211,8 @@ func TestInsertVulnerabilityNotifications(t *testing.T) {
} }
func TestFindNewNotification(t *testing.T) { func TestFindNewNotification(t *testing.T) {
datastore, tx := openSessionForTest(t, "FindNewNotification", true) tx, cleanup := createTestPgSessionWithFixtures(t, "TestFindNewNotification")
defer closeTest(t, datastore, tx) defer cleanup()
noti, ok, err := tx.FindNewNotification(time.Now()) noti, ok, err := tx.FindNewNotification(time.Now())
if assert.Nil(t, err) && assert.True(t, ok) { if assert.Nil(t, err) && assert.True(t, ok) {
@ -229,7 +229,7 @@ func TestFindNewNotification(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, ok) assert.False(t, ok)
// can find the notified after a period of time // can find the notified after a period of time
noti, ok, err = tx.FindNewNotification(time.Now().Add(time.Duration(1000))) noti, ok, err = tx.FindNewNotification(time.Now().Add(time.Duration(10 * time.Second)))
if assert.Nil(t, err) && assert.True(t, ok) { if assert.Nil(t, err) && assert.True(t, ok) {
assert.Equal(t, "test", noti.Name) assert.Equal(t, "test", noti.Name)
assert.NotEqual(t, time.Time{}, noti.Notified) assert.NotEqual(t, time.Time{}, noti.Notified)

@ -109,6 +109,7 @@ func dropTemplateDatabase(url string, name string) {
} }
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
fURL, fName := genTemplateDatabase("fixture", true) fURL, fName := genTemplateDatabase("fixture", true)
nfURL, nfName := genTemplateDatabase("nonfixture", false) nfURL, nfName := genTemplateDatabase("nonfixture", false)

@ -52,21 +52,22 @@ func querySearchNotDeletedVulnerabilityID(count int) string {
func querySearchFeatureID(featureCount int) string { func querySearchFeatureID(featureCount int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
SELECT id, name, version, version_format SELECT id, name, version, version_format, type
FROM Feature WHERE (name, version, version_format) IN (%s)`, FROM Feature WHERE (name, version, version_format, type) IN (%s)`,
queryString(3, featureCount), queryString(4, featureCount),
) )
} }
func querySearchNamespacedFeature(nsfCount int) string { func querySearchNamespacedFeature(nsfCount int) string {
return fmt.Sprintf(` return fmt.Sprintf(`
SELECT nf.id, f.name, f.version, f.version_format, n.name SELECT nf.id, f.name, f.version, f.version_format, t.name, n.name
FROM namespaced_feature AS nf, feature AS f, namespace AS n FROM namespaced_feature AS nf, feature AS f, namespace AS n, feature_type AS t
WHERE nf.feature_id = f.id WHERE nf.feature_id = f.id
AND nf.namespace_id = n.id AND nf.namespace_id = n.id
AND n.version_format = f.version_format AND n.version_format = f.version_format
AND (f.name, f.version, f.version_format, n.name) IN (%s)`, AND f.type = t.id
queryString(4, nsfCount), AND (f.name, f.version, f.version_format, t.name, n.name) IN (%s)`,
queryString(5, nsfCount),
) )
} }
@ -110,10 +111,11 @@ func queryInsertNotifications(count int) string {
func queryPersistFeature(count int) string { func queryPersistFeature(count int) string {
return queryPersist(count, return queryPersist(count,
"feature", "feature",
"feature_name_version_version_format_key", "feature_name_version_version_format_type_key",
"name", "name",
"version", "version",
"version_format") "version_format",
"type")
} }
func queryPersistLayerFeature(count int) string { func queryPersistLayerFeature(count int) string {

@ -4,11 +4,12 @@ INSERT INTO namespace (id, name, version_format) VALUES
(2, 'debian:8', 'dpkg'), (2, 'debian:8', 'dpkg'),
(3, 'fake:1.0', 'rpm'); (3, 'fake:1.0', 'rpm');
INSERT INTO feature (id, name, version, version_format) VALUES INSERT INTO feature (id, name, version, version_format, type) VALUES
(1, 'ourchat', '0.5', 'dpkg'), (1, 'ourchat', '0.5', 'dpkg', 1),
(2, 'openssl', '1.0', 'dpkg'), (2, 'openssl', '1.0', 'dpkg', 1),
(3, 'openssl', '2.0', 'dpkg'), (3, 'openssl', '2.0', 'dpkg', 1),
(4, 'fake', '2.0', 'rpm'); (4, 'fake', '2.0', 'rpm', 1),
(5, 'mount', '2.31.1-0.4ubuntu3.1', 'dpkg', 2);
INSERT INTO namespaced_feature(id, feature_id, namespace_id) VALUES INSERT INTO namespaced_feature(id, feature_id, namespace_id) VALUES
(1, 1, 1), -- ourchat 0.5, debian:7 (1, 1, 1), -- ourchat 0.5, debian:7
@ -112,9 +113,9 @@ INSERT INTO vulnerability (id, namespace_id, name, description, link, severity)
INSERT INTO vulnerability (id, namespace_id, name, description, link, severity, deleted_at) VALUES INSERT INTO vulnerability (id, namespace_id, name, description, link, severity, deleted_at) VALUES
(3, 1, 'CVE-DELETED', '', '', 'Unknown', '2017-08-08 17:49:31.668483'); (3, 1, 'CVE-DELETED', '', '', 'Unknown', '2017-08-08 17:49:31.668483');
INSERT INTO vulnerability_affected_feature(id, vulnerability_id, feature_name, affected_version, fixedin) VALUES INSERT INTO vulnerability_affected_feature(id, vulnerability_id, feature_name, affected_version, fixedin, feature_type) VALUES
(1, 1, 'openssl', '2.0', '2.0'), (1, 1, 'openssl', '2.0', '2.0', 1),
(2, 1, 'libssl', '1.9-abc', '1.9-abc'); (2, 1, 'libssl', '1.9-abc', '1.9-abc', 1);
INSERT INTO vulnerability_affected_namespaced_feature(id, vulnerability_id, namespaced_feature_id, added_by) VALUES INSERT INTO vulnerability_affected_namespaced_feature(id, vulnerability_id, namespaced_feature_id, added_by) VALUES
(1, 1, 2, 1); (1, 1, 2, 1);

@ -15,21 +15,34 @@
package pgsql package pgsql
import ( import (
"database/sql"
"fmt"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"runtime"
"strings"
"testing" "testing"
"time"
"github.com/remind101/migrate"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coreos/clair/database" "github.com/coreos/clair/database"
"github.com/coreos/clair/database/pgsql/migrations"
"github.com/coreos/clair/pkg/pagination" "github.com/coreos/clair/pkg/pagination"
) )
// int keys must be the consistent with the database ID. // int keys must be the consistent with the database ID.
var ( var (
realFeatures = map[int]database.Feature{ realFeatures = map[int]database.Feature{
1: {"ourchat", "0.5", "ourchat", "0.5", "dpkg"}, 1: {"ourchat", "0.5", "dpkg", "source"},
2: {"openssl", "1.0", "openssl", "1.0", "dpkg"}, 2: {"openssl", "1.0", "dpkg", "source"},
3: {"openssl", "2.0", "openssl", "2.0", "dpkg"}, 3: {"openssl", "2.0", "dpkg", "source"},
4: {"fake", "2.0", "fake", "2.0", "rpm"}, 4: {"fake", "2.0", "rpm", "source"},
5: {"mount", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
} }
realNamespaces = map[int]database.Namespace{ realNamespaces = map[int]database.Namespace{
@ -146,6 +159,7 @@ var (
Name: "ourchat", Name: "ourchat",
Version: "0.6", Version: "0.6",
VersionFormat: "dpkg", VersionFormat: "dpkg",
Type: "source",
}, },
} }
@ -260,3 +274,150 @@ func mustMarshalToken(key pagination.Key, v interface{}) pagination.Token {
return token return token
} }
var userDBCount = `SELECT count(datname) FROM pg_database WHERE datistemplate = FALSE AND datname != 'postgres';`
func createAndConnectTestDB(t *testing.T, testName string) (*sql.DB, func()) {
uri := "postgres@127.0.0.1:5432"
connectionTemplate := "postgresql://%s?sslmode=disable"
if envURI := os.Getenv("CLAIR_TEST_PGSQL"); envURI != "" {
uri = envURI
}
db, err := sql.Open("postgres", fmt.Sprintf(connectionTemplate, uri))
if err != nil {
panic(err)
}
testName = strings.ToLower(testName)
dbName := fmt.Sprintf("test_%s_%s", testName, time.Now().UTC().Format("2006_01_02_15_04_05"))
t.Logf("creating temporary database name = %s", dbName)
_, err = db.Exec("CREATE DATABASE " + dbName)
if err != nil {
panic(err)
}
testDB, err := sql.Open("postgres", fmt.Sprintf(connectionTemplate, uri+"/"+dbName))
if err != nil {
panic(err)
}
return testDB, func() {
t.Logf("cleaning up temporary database %s", dbName)
defer db.Close()
if err := testDB.Close(); err != nil {
panic(err)
}
if _, err := db.Exec(`DROP DATABASE ` + dbName); err != nil {
panic(err)
}
// ensure the database is cleaned up
var count int
if err := db.QueryRow(userDBCount).Scan(&count); err != nil {
panic(err)
}
}
}
func createTestPgSQL(t *testing.T, testName string) (*pgSQL, func()) {
connection, cleanup := createAndConnectTestDB(t, testName)
err := migrate.NewPostgresMigrator(connection).Exec(migrate.Up, migrations.Migrations...)
if err != nil {
require.Nil(t, err, err.Error())
}
return &pgSQL{connection, nil, Config{PaginationKey: pagination.Must(pagination.NewKey()).String()}}, cleanup
}
func createTestPgSQLWithFixtures(t *testing.T, testName string) (*pgSQL, func()) {
connection, cleanup := createTestPgSQL(t, testName)
session, err := connection.Begin()
if err != nil {
panic(err)
}
defer session.Rollback()
loadFixtures(session.(*pgSession))
if err = session.Commit(); err != nil {
panic(err)
}
return connection, cleanup
}
func createTestPgSession(t *testing.T, testName string) (*pgSession, func()) {
connection, cleanup := createTestPgSQL(t, testName)
session, err := connection.Begin()
if err != nil {
panic(err)
}
return session.(*pgSession), func() {
session.Rollback()
cleanup()
}
}
func createTestPgSessionWithFixtures(t *testing.T, testName string) (*pgSession, func()) {
tx, cleanup := createTestPgSession(t, testName)
defer func() {
// ensure to cleanup when loadFixtures failed
if r := recover(); r != nil {
cleanup()
}
}()
loadFixtures(tx)
return tx, cleanup
}
func loadFixtures(tx *pgSession) {
_, filename, _, _ := runtime.Caller(0)
fixturePath := filepath.Join(filepath.Dir(filename)) + "/testdata/data.sql"
d, err := ioutil.ReadFile(fixturePath)
if err != nil {
panic(err)
}
_, err = tx.Exec(string(d))
if err != nil {
panic(err)
}
}
func assertVulnerabilityWithAffectedEqual(t *testing.T, expected database.VulnerabilityWithAffected, actual database.VulnerabilityWithAffected) bool {
return assert.Equal(t, expected.Vulnerability, actual.Vulnerability) && assertAffectedFeaturesEqual(t, expected.Affected, actual.Affected)
}
func assertAffectedFeaturesEqual(t *testing.T, expected []database.AffectedFeature, actual []database.AffectedFeature) bool {
if assert.Len(t, actual, len(expected)) {
has := map[database.AffectedFeature]bool{}
for _, i := range expected {
has[i] = false
}
for _, i := range actual {
if visited, ok := has[i]; !ok {
return false
} else if visited {
return false
}
has[i] = true
}
return true
}
return false
}
func genRandomNamespaces(t *testing.T, count int) []database.Namespace {
r := make([]database.Namespace, count)
for i := 0; i < count; i++ {
r[i] = database.Namespace{
Name: fmt.Sprint(rand.Int()),
VersionFormat: "dpkg",
}
}
return r
}

@ -39,15 +39,15 @@ const (
` `
insertVulnerabilityAffected = ` insertVulnerabilityAffected = `
INSERT INTO vulnerability_affected_feature(vulnerability_id, feature_name, affected_version, fixedin) INSERT INTO vulnerability_affected_feature(vulnerability_id, feature_name, affected_version, feature_type, fixedin)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4, $5)
RETURNING ID RETURNING ID
` `
searchVulnerabilityAffected = ` searchVulnerabilityAffected = `
SELECT vulnerability_id, feature_name, affected_version, fixedin SELECT vulnerability_id, feature_name, affected_version, t.name, fixedin
FROM vulnerability_affected_feature FROM vulnerability_affected_feature AS vaf, feature_type AS t
WHERE vulnerability_id = ANY($1) WHERE t.id = vaf.feature_type AND vulnerability_id = ANY($1)
` `
searchVulnerabilityByID = ` searchVulnerabilityByID = `
@ -58,7 +58,7 @@ const (
searchVulnerabilityPotentialAffected = ` searchVulnerabilityPotentialAffected = `
WITH req AS ( WITH req AS (
SELECT vaf.id AS vaf_id, n.id AS n_id, vaf.feature_name AS name, v.id AS vulnerability_id SELECT vaf.id AS vaf_id, n.id AS n_id, vaf.feature_name AS name, vaf.feature_type AS type, v.id AS vulnerability_id
FROM vulnerability_affected_feature AS vaf, FROM vulnerability_affected_feature AS vaf,
vulnerability AS v, vulnerability AS v,
namespace AS n namespace AS n
@ -69,6 +69,7 @@ const (
SELECT req.vulnerability_id, nf.id, f.version, req.vaf_id AS added_by SELECT req.vulnerability_id, nf.id, f.version, req.vaf_id AS added_by
FROM feature AS f, namespaced_feature AS nf, req FROM feature AS f, namespaced_feature AS nf, req
WHERE f.name = req.name WHERE f.name = req.name
AND f.type = req.type
AND nf.namespace_id = req.n_id AND nf.namespace_id = req.n_id
AND nf.feature_id = f.id` AND nf.feature_id = f.id`
@ -180,7 +181,7 @@ func (tx *pgSession) FindVulnerabilities(vulnerabilities []database.Vulnerabilit
f database.AffectedFeature f database.AffectedFeature
) )
err := rows.Scan(&id, &f.FeatureName, &f.AffectedVersion, &f.FixedInVersion) err := rows.Scan(&id, &f.FeatureName, &f.AffectedVersion, &f.FeatureType, &f.FixedInVersion)
if err != nil { if err != nil {
return nil, handleError("searchVulnerabilityAffected", err) return nil, handleError("searchVulnerabilityAffected", err)
} }
@ -220,6 +221,11 @@ func (tx *pgSession) insertVulnerabilityAffected(vulnerabilityIDs []int64, vulne
affectedID int64 affectedID int64
) )
types, err := tx.getFeatureTypeMap()
if err != nil {
return nil, err
}
//TODO(Sida): Change to bulk insert. //TODO(Sida): Change to bulk insert.
stmt, err := tx.Prepare(insertVulnerabilityAffected) stmt, err := tx.Prepare(insertVulnerabilityAffected)
if err != nil { if err != nil {
@ -231,7 +237,7 @@ func (tx *pgSession) insertVulnerabilityAffected(vulnerabilityIDs []int64, vulne
// affected feature row ID -> affected feature // affected feature row ID -> affected feature
affectedFeatures := map[int64]database.AffectedFeature{} affectedFeatures := map[int64]database.AffectedFeature{}
for _, f := range vuln.Affected { for _, f := range vuln.Affected {
err := stmt.QueryRow(vulnerabilityIDs[i], f.FeatureName, f.AffectedVersion, f.FixedInVersion).Scan(&affectedID) err := stmt.QueryRow(vulnerabilityIDs[i], f.FeatureName, f.AffectedVersion, types.byName[f.FeatureType], f.FixedInVersion).Scan(&affectedID)
if err != nil { if err != nil {
return nil, handleError("insertVulnerabilityAffected", err) return nil, handleError("insertVulnerabilityAffected", err)
} }

@ -106,6 +106,7 @@ func TestCachingVulnerable(t *testing.T) {
Name: "openssl", Name: "openssl",
Version: "1.0", Version: "1.0",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
Type: database.SourcePackage,
}, },
Namespace: ns, Namespace: ns,
} }
@ -120,6 +121,7 @@ func TestCachingVulnerable(t *testing.T) {
{ {
Namespace: ns, Namespace: ns,
FeatureName: "openssl", FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.0", AffectedVersion: "2.0",
FixedInVersion: "2.1", FixedInVersion: "2.1",
}, },
@ -136,6 +138,7 @@ func TestCachingVulnerable(t *testing.T) {
{ {
Namespace: ns, Namespace: ns,
FeatureName: "openssl", FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.1", AffectedVersion: "2.1",
FixedInVersion: "2.2", FixedInVersion: "2.2",
}, },
@ -209,12 +212,14 @@ func TestFindVulnerabilities(t *testing.T) {
Affected: []database.AffectedFeature{ Affected: []database.AffectedFeature{
{ {
FeatureName: "openssl", FeatureName: "openssl",
FeatureType: database.SourcePackage,
AffectedVersion: "2.0", AffectedVersion: "2.0",
FixedInVersion: "2.0", FixedInVersion: "2.0",
Namespace: ns, Namespace: ns,
}, },
{ {
FeatureName: "libssl", FeatureName: "libssl",
FeatureType: database.SourcePackage,
AffectedVersion: "1.9-abc", AffectedVersion: "1.9-abc",
FixedInVersion: "1.9-abc", FixedInVersion: "1.9-abc",
Namespace: ns, Namespace: ns,
@ -318,26 +323,3 @@ func TestFindVulnerabilityIDs(t *testing.T) {
} }
} }
} }
func assertVulnerabilityWithAffectedEqual(t *testing.T, expected database.VulnerabilityWithAffected, actual database.VulnerabilityWithAffected) bool {
return assert.Equal(t, expected.Vulnerability, actual.Vulnerability) && assertAffectedFeaturesEqual(t, expected.Affected, actual.Affected)
}
func assertAffectedFeaturesEqual(t *testing.T, expected []database.AffectedFeature, actual []database.AffectedFeature) bool {
if assert.Len(t, actual, len(expected)) {
has := map[database.AffectedFeature]bool{}
for _, i := range expected {
has[i] = false
}
for _, i := range actual {
if visited, ok := has[i]; !ok {
return false
} else if visited {
return false
}
has[i] = true
}
return true
}
return false
}

@ -55,6 +55,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
line := scanner.Text() line := scanner.Text()
if len(line) < 2 { if len(line) < 2 {
if valid(&pkg) { if valid(&pkg) {
pkg.Type = database.BinaryPackage
packages.Add(pkg) packages.Add(pkg)
pkg = database.Feature{VersionFormat: dpkg.ParserName} pkg = database.Feature{VersionFormat: dpkg.ParserName}
} }
@ -81,6 +82,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// in case of no terminal line // in case of no terminal line
if valid(&pkg) { if valid(&pkg) {
pkg.Type = database.BinaryPackage
packages.Add(pkg) packages.Add(pkg)
} }

@ -28,17 +28,17 @@ func TestAPKFeatureDetection(t *testing.T) {
"valid case", "valid case",
map[string]string{"lib/apk/db/installed": "apk/testdata/valid"}, map[string]string{"lib/apk/db/installed": "apk/testdata/valid"},
[]database.Feature{ []database.Feature{
{"musl", "1.1.14-r10", "", "", dpkg.ParserName}, {"apk-tools", "2.6.7-r0", "dpkg", "binary"},
{"busybox", "1.24.2-r9", "", "", dpkg.ParserName}, {"musl", "1.1.14-r10", "dpkg", "binary"},
{"alpine-baselayout", "3.0.3-r0", "", "", dpkg.ParserName}, {"libssl1.0", "1.0.2h-r1", "dpkg", "binary"},
{"alpine-keys", "1.1-r0", "", "", dpkg.ParserName}, {"libc-utils", "0.7-r0", "dpkg", "binary"},
{"zlib", "1.2.8-r2", "", "", dpkg.ParserName}, {"busybox", "1.24.2-r9", "dpkg", "binary"},
{"libcrypto1.0", "1.0.2h-r1", "", "", dpkg.ParserName}, {"scanelf", "1.1.6-r0", "dpkg", "binary"},
{"libssl1.0", "1.0.2h-r1", "", "", dpkg.ParserName}, {"alpine-keys", "1.1-r0", "dpkg", "binary"},
{"apk-tools", "2.6.7-r0", "", "", dpkg.ParserName}, {"libcrypto1.0", "1.0.2h-r1", "dpkg", "binary"},
{"scanelf", "1.1.6-r0", "", "", dpkg.ParserName}, {"zlib", "1.2.8-r2", "dpkg", "binary"},
{"musl-utils", "1.1.14-r10", "", "", dpkg.ParserName}, {"musl-utils", "1.1.14-r10", "dpkg", "binary"},
{"libc-utils", "0.7-r0", "", "", dpkg.ParserName}, {"alpine-baselayout", "3.0.3-r0", "dpkg", "binary"},
}, },
}, },
} { } {

@ -37,22 +37,12 @@ var (
type lister struct{} type lister struct{}
func init() { func (l lister) RequiredFilenames() []string {
featurefmt.RegisterLister("dpkg", "1.0", &lister{}) return []string{"var/lib/dpkg/status"}
}
func valid(pkg *database.Feature) bool {
return pkg.Name != "" && pkg.Version != ""
} }
func addSourcePackage(pkg *database.Feature) { func init() {
if pkg.SourceName == "" { featurefmt.RegisterLister("dpkg", "1.0", &lister{})
pkg.SourceName = pkg.Name
}
if pkg.SourceVersion == "" {
pkg.SourceVersion = pkg.Version
}
} }
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) { func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) {
@ -61,21 +51,45 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
return []database.Feature{}, nil return []database.Feature{}, nil
} }
packages := mapset.NewSet()
scanner := bufio.NewScanner(strings.NewReader(string(f)))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
binary, source := parseDpkgDB(scanner)
if binary != nil {
packages.Add(*binary)
}
if source != nil {
packages.Add(*source)
}
}
return database.ConvertFeatureSetToFeatures(packages), nil
}
// parseDpkgDB consumes the status file scanner exactly one package info, until
// EOF or empty space, and generate the parsed packages from it.
func parseDpkgDB(scanner *bufio.Scanner) (binaryPackage *database.Feature, sourcePackage *database.Feature) {
var ( var (
pkg = database.Feature{VersionFormat: dpkg.ParserName} name string
pkgs = mapset.NewSet() version string
err error sourceName string
sourceVersion string
) )
scanner := bufio.NewScanner(strings.NewReader(string(f))) for {
for scanner.Scan() { line := strings.TrimSpace(scanner.Text())
line := scanner.Text() if line == "" {
if strings.HasPrefix(line, "Package: ") { break
// Package line }
// Defines the name of the package
pkg.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: ")) if strings.HasPrefix(line, "Package: ") {
pkg.Version = "" name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
} else if strings.HasPrefix(line, "Source: ") { } else if strings.HasPrefix(line, "Source: ") {
// Source line (Optional) // Source line (Optional)
// Gives the name of the source package // Gives the name of the source package
@ -87,14 +101,9 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
md[dpkgSrcCaptureRegexpNames[i]] = strings.TrimSpace(n) md[dpkgSrcCaptureRegexpNames[i]] = strings.TrimSpace(n)
} }
pkg.SourceName = md["name"] sourceName = md["name"]
if md["version"] != "" { if md["version"] != "" {
version := md["version"] sourceVersion = md["version"]
if err = versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithField("version", string(line[1])).Warning("could not parse package version. skipping")
} else {
pkg.SourceVersion = version
}
} }
} else if strings.HasPrefix(line, "Version: ") { } else if strings.HasPrefix(line, "Version: ") {
// Version line // Version line
@ -102,25 +111,43 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// This version is less important than a version retrieved from a Source line // This version is less important than a version retrieved from a Source line
// because the Debian vulnerabilities often skips the epoch from the Version field // because the Debian vulnerabilities often skips the epoch from the Version field
// which is not present in the Source version, and because +bX revisions don't matter // which is not present in the Source version, and because +bX revisions don't matter
version := strings.TrimPrefix(line, "Version: ") version = strings.TrimPrefix(line, "Version: ")
if err = versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithField("version", string(line[1])).Warning("could not parse package version. skipping")
} else {
pkg.Version = version
}
} else if line == "" {
pkg = database.Feature{VersionFormat: dpkg.ParserName}
} }
if valid(&pkg) { if !scanner.Scan() {
addSourcePackage(&pkg) break
pkgs.Add(pkg)
} }
} }
return database.ConvertFeatureSetToFeatures(pkgs), nil if name != "" && version != "" {
} if err := versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
} else {
binaryPackage = &database.Feature{name, version, dpkg.ParserName, database.BinaryPackage}
}
}
func (l lister) RequiredFilenames() []string { // Source version and names are computed from binary package names and versions
return []string{"var/lib/dpkg/status"} // in dpkg.
// Source package name:
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n338
// Source package version:
// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n355
if sourceName == "" {
sourceName = name
}
if sourceVersion == "" {
sourceVersion = version
}
if sourceName != "" && sourceVersion != "" {
if err := versionfmt.Valid(dpkg.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
} else {
sourcePackage = &database.Feature{sourceName, sourceVersion, dpkg.ParserName, database.SourcePackage}
}
}
return
} }

@ -28,105 +28,168 @@ func TestListFeatures(t *testing.T) {
"valid status file", "valid status file",
map[string]string{"var/lib/dpkg/status": "dpkg/testdata/valid"}, map[string]string{"var/lib/dpkg/status": "dpkg/testdata/valid"},
[]database.Feature{ []database.Feature{
{"adduser", "3.116ubuntu1", "adduser", "3.116ubuntu1", dpkg.ParserName}, {"libapt-pkg5.0", "1.6.3ubuntu0.1", "dpkg", "binary"},
{"apt", "1.6.3ubuntu0.1", "apt", "1.6.3ubuntu0.1", dpkg.ParserName}, {"perl-base", "5.26.1-6ubuntu0.2", "dpkg", "binary"},
{"base-files", "10.1ubuntu2.2", "base-files", "10.1ubuntu2.2", dpkg.ParserName}, {"libmount1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"base-passwd", "3.5.44", "base-passwd", "3.5.44", dpkg.ParserName}, {"perl", "5.26.1-6ubuntu0.2", "dpkg", "source"},
{"bash", "4.4.18-2ubuntu1", "bash", "4.4.18-2ubuntu1", dpkg.ParserName}, {"libgnutls30", "3.5.18-1ubuntu1", "dpkg", "binary"},
{"bsdutils", "1:2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"liblzma5", "5.2.2-1.3", "dpkg", "binary"},
{"bzip2", "1.0.6-8.1", "bzip2", "1.0.6-8.1", dpkg.ParserName}, {"ncurses-bin", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"coreutils", "8.28-1ubuntu1", "coreutils", "8.28-1ubuntu1", dpkg.ParserName}, {"lsb", "9.20170808ubuntu1", "dpkg", "source"},
{"dash", "0.5.8-2.10", "dash", "0.5.8-2.10", dpkg.ParserName}, {"sed", "4.4-2", "dpkg", "source"},
{"debconf", "1.5.66", "debconf", "1.5.66", dpkg.ParserName}, {"libsystemd0", "237-3ubuntu10.3", "dpkg", "binary"},
{"debianutils", "4.8.4", "debianutils", "4.8.4", dpkg.ParserName}, {"procps", "2:3.3.12-3ubuntu1.1", "dpkg", "source"},
{"diffutils", "1:3.6-1", "diffutils", "1:3.6-1", dpkg.ParserName}, {"login", "1:4.5-1ubuntu1", "dpkg", "binary"},
{"dpkg", "1.19.0.5ubuntu2", "dpkg", "1.19.0.5ubuntu2", dpkg.ParserName}, {"libunistring2", "0.9.9-0ubuntu1", "dpkg", "binary"},
{"e2fsprogs", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName}, {"sed", "4.4-2", "dpkg", "binary"},
{"fdisk", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"libselinux", "2.7-2build2", "dpkg", "source"},
{"findutils", "4.6.0+git+20170828-2", "findutils", "4.6.0+git+20170828-2", dpkg.ParserName}, {"libseccomp", "2.3.1-2.1ubuntu4", "dpkg", "source"},
{"gcc-8-base", "8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName}, {"libss2", "1.44.1-1", "dpkg", "binary"},
{"gpgv", "2.2.4-1ubuntu1.1", "gnupg2", "2.2.4-1ubuntu1.1", dpkg.ParserName}, {"liblz4-1", "0.0~r131-2ubuntu3", "dpkg", "binary"},
{"grep", "3.1-2", "grep", "3.1-2", dpkg.ParserName}, {"libsemanage1", "2.7-2build2", "dpkg", "binary"},
{"gzip", "1.6-5ubuntu1", "gzip", "1.6-5ubuntu1", dpkg.ParserName}, {"libtasn1-6", "4.13-2", "dpkg", "source"},
{"hostname", "3.20", "hostname", "3.20", dpkg.ParserName}, {"libzstd1", "1.3.3+dfsg-2ubuntu1", "dpkg", "binary"},
{"init-system-helpers", "1.51", "init-system-helpers", "1.51", dpkg.ParserName}, {"fdisk", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libacl1", "2.2.52-3build1", "acl", "2.2.52-3build1", dpkg.ParserName}, {"xz-utils", "5.2.2-1.3", "dpkg", "source"},
{"libapt-pkg5.0", "1.6.3ubuntu0.1", "apt", "1.6.3ubuntu0.1", dpkg.ParserName}, {"lsb-base", "9.20170808ubuntu1", "dpkg", "binary"},
{"libattr1", "1:2.4.47-2build1", "attr", "1:2.4.47-2build1", dpkg.ParserName}, {"libpam-modules-bin", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libaudit-common", "1:2.8.2-1ubuntu1", "audit", "1:2.8.2-1ubuntu1", dpkg.ParserName}, {"dash", "0.5.8-2.10", "dpkg", "binary"},
{"libaudit1", "1:2.8.2-1ubuntu1", "audit", "1:2.8.2-1ubuntu1", dpkg.ParserName}, {"gnupg2", "2.2.4-1ubuntu1.1", "dpkg", "source"},
{"libblkid1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"libfdisk1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libbz2-1.0", "1.0.6-8.1", "bzip2", "1.0.6-8.1", dpkg.ParserName}, {"lz4", "0.0~r131-2ubuntu3", "dpkg", "source"},
{"libc-bin", "2.27-3ubuntu1", "glibc", "2.27-3ubuntu1", dpkg.ParserName}, {"libpam0g", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libc6", "2.27-3ubuntu1", "glibc", "2.27-3ubuntu1", dpkg.ParserName}, {"libc-bin", "2.27-3ubuntu1", "dpkg", "binary"},
{"libcap-ng0", "0.7.7-3.1", "libcap-ng", "0.7.7-3.1", dpkg.ParserName}, {"libcap-ng", "0.7.7-3.1", "dpkg", "source"},
{"libcom-err2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName}, {"libcom-err2", "1.44.1-1", "dpkg", "binary"},
{"libdb5.3", "5.3.28-13.1ubuntu1", "db5.3", "5.3.28-13.1ubuntu1", dpkg.ParserName}, {"libudev1", "237-3ubuntu10.3", "dpkg", "binary"},
{"libdebconfclient0", "0.213ubuntu1", "cdebconf", "0.213ubuntu1", dpkg.ParserName}, {"debconf", "1.5.66", "dpkg", "binary"},
{"libext2fs2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName}, {"tar", "1.29b-2", "dpkg", "binary"},
{"libfdisk1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"diffutils", "1:3.6-1", "dpkg", "source"},
{"libffi6", "3.2.1-8", "libffi", "3.2.1-8", dpkg.ParserName}, {"gcc-8", "8-20180414-1ubuntu2", "dpkg", "source"},
{"libgcc1", "1:8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName}, {"e2fsprogs", "1.44.1-1", "dpkg", "source"},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "libgcrypt20", "1.8.1-4ubuntu1.1", dpkg.ParserName}, {"bzip2", "1.0.6-8.1", "dpkg", "source"},
{"libgmp10", "2:6.1.2+dfsg-2", "gmp", "2:6.1.2+dfsg-2", dpkg.ParserName}, {"diffutils", "1:3.6-1", "dpkg", "binary"},
{"libgnutls30", "3.5.18-1ubuntu1", "gnutls28", "3.5.18-1ubuntu1", dpkg.ParserName}, {"grep", "3.1-2", "dpkg", "binary"},
{"libgpg-error0", "1.27-6", "libgpg-error", "1.27-6", dpkg.ParserName}, {"libgcc1", "1:8-20180414-1ubuntu2", "dpkg", "binary"},
{"libhogweed4", "3.4-1", "nettle", "3.4-1", dpkg.ParserName}, {"bash", "4.4.18-2ubuntu1", "dpkg", "source"},
{"libidn2-0", "2.0.4-1.1build2", "libidn2", "2.0.4-1.1build2", dpkg.ParserName}, {"libtinfo5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"liblz4-1", "0.0~r131-2ubuntu3", "lz4", "0.0~r131-2ubuntu3", dpkg.ParserName}, {"procps", "2:3.3.12-3ubuntu1.1", "dpkg", "binary"},
{"liblzma5", "5.2.2-1.3", "xz-utils", "5.2.2-1.3", dpkg.ParserName}, {"bzip2", "1.0.6-8.1", "dpkg", "binary"},
{"libmount1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"init-system-helpers", "1.51", "dpkg", "binary"},
{"libncurses5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName}, {"libncursesw5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"libncursesw5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName}, {"init-system-helpers", "1.51", "dpkg", "source"},
{"libnettle6", "3.4-1", "nettle", "3.4-1", dpkg.ParserName}, {"libpam-modules", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libp11-kit0", "0.23.9-2", "p11-kit", "0.23.9-2", dpkg.ParserName}, {"libext2fs2", "1.44.1-1", "dpkg", "binary"},
{"libpam-modules", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName}, {"libacl1", "2.2.52-3build1", "dpkg", "binary"},
{"libpam-modules-bin", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName}, {"hostname", "3.20", "dpkg", "binary"},
{"libpam-runtime", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName}, {"libgpg-error", "1.27-6", "dpkg", "source"},
{"libpam0g", "1.1.8-3.6ubuntu2", "pam", "1.1.8-3.6ubuntu2", dpkg.ParserName}, {"acl", "2.2.52-3build1", "dpkg", "source"},
{"libpcre3", "2:8.39-9", "pcre3", "2:8.39-9", dpkg.ParserName}, {"apt", "1.6.3ubuntu0.1", "dpkg", "binary"},
{"libprocps6", "2:3.3.12-3ubuntu1.1", "procps", "2:3.3.12-3ubuntu1.1", dpkg.ParserName}, {"base-files", "10.1ubuntu2.2", "dpkg", "source"},
{"libseccomp2", "2.3.1-2.1ubuntu4", "libseccomp", "2.3.1-2.1ubuntu4", dpkg.ParserName}, {"libgpg-error0", "1.27-6", "dpkg", "binary"},
{"libselinux1", "2.7-2build2", "libselinux", "2.7-2build2", dpkg.ParserName}, {"audit", "1:2.8.2-1ubuntu1", "dpkg", "source"},
{"libsemanage-common", "2.7-2build2", "libsemanage", "2.7-2build2", dpkg.ParserName}, {"hostname", "3.20", "dpkg", "source"},
{"libsemanage1", "2.7-2build2", "libsemanage", "2.7-2build2", dpkg.ParserName}, {"gzip", "1.6-5ubuntu1", "dpkg", "binary"},
{"libsepol1", "2.7-1", "libsepol", "2.7-1", dpkg.ParserName}, {"libc6", "2.27-3ubuntu1", "dpkg", "binary"},
{"libsmartcols1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"libnettle6", "3.4-1", "dpkg", "binary"},
{"libss2", "1.44.1-1", "e2fsprogs", "1.44.1-1", dpkg.ParserName}, {"sysvinit-utils", "2.88dsf-59.10ubuntu1", "dpkg", "binary"},
{"libstdc++6", "8-20180414-1ubuntu2", "gcc-8", "8-20180414-1ubuntu2", dpkg.ParserName}, {"debianutils", "4.8.4", "dpkg", "source"},
{"libsystemd0", "237-3ubuntu10.3", "systemd", "237-3ubuntu10.3", dpkg.ParserName}, {"libstdc++6", "8-20180414-1ubuntu2", "dpkg", "binary"},
{"libtasn1-6", "4.13-2", "libtasn1-6", "4.13-2", dpkg.ParserName}, {"libsepol", "2.7-1", "dpkg", "source"},
{"libtinfo5", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName}, {"libpcre3", "2:8.39-9", "dpkg", "binary"},
{"libudev1", "237-3ubuntu10.3", "systemd", "237-3ubuntu10.3", dpkg.ParserName}, {"libuuid1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libunistring2", "0.9.9-0ubuntu1", "libunistring", "0.9.9-0ubuntu1", dpkg.ParserName}, {"systemd", "237-3ubuntu10.3", "dpkg", "source"},
{"libuuid1", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"tar", "1.29b-2", "dpkg", "source"},
{"libzstd1", "1.3.3+dfsg-2ubuntu1", "libzstd", "1.3.3+dfsg-2ubuntu1", dpkg.ParserName}, {"ubuntu-keyring", "2018.02.28", "dpkg", "source"},
{"login", "1:4.5-1ubuntu1", "shadow", "1:4.5-1ubuntu1", dpkg.ParserName}, {"passwd", "1:4.5-1ubuntu1", "dpkg", "binary"},
{"lsb-base", "9.20170808ubuntu1", "lsb", "9.20170808ubuntu1", dpkg.ParserName}, {"sysvinit", "2.88dsf-59.10ubuntu1", "dpkg", "source"},
{"mawk", "1.3.3-17ubuntu3", "mawk", "1.3.3-17ubuntu3", dpkg.ParserName}, {"libidn2-0", "2.0.4-1.1build2", "dpkg", "binary"},
{"mount", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"libhogweed4", "3.4-1", "dpkg", "binary"},
{"ncurses-base", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName}, {"db5.3", "5.3.28-13.1ubuntu1", "dpkg", "source"},
{"ncurses-bin", "6.1-1ubuntu1.18.04", "ncurses", "6.1-1ubuntu1.18.04", dpkg.ParserName}, {"sensible-utils", "0.0.12", "dpkg", "source"},
{"passwd", "1:4.5-1ubuntu1", "shadow", "1:4.5-1ubuntu1", dpkg.ParserName}, {"dpkg", "1.19.0.5ubuntu2", "dpkg", "source"},
{"perl-base", "5.26.1-6ubuntu0.2", "perl", "5.26.1-6ubuntu0.2", dpkg.ParserName}, {"libp11-kit0", "0.23.9-2", "dpkg", "binary"},
{"procps", "2:3.3.12-3ubuntu1.1", "procps", "2:3.3.12-3ubuntu1.1", dpkg.ParserName}, {"glibc", "2.27-3ubuntu1", "dpkg", "source"},
{"sed", "4.4-2", "sed", "4.4-2", dpkg.ParserName}, {"mount", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"sensible-utils", "0.0.12", "sensible-utils", "0.0.12", dpkg.ParserName}, {"libsemanage-common", "2.7-2build2", "dpkg", "binary"},
{"sysvinit-utils", "2.88dsf-59.10ubuntu1", "sysvinit", "2.88dsf-59.10ubuntu1", dpkg.ParserName}, {"libblkid1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"tar", "1.29b-2", "tar", "1.29b-2", dpkg.ParserName}, {"libdebconfclient0", "0.213ubuntu1", "dpkg", "binary"},
{"ubuntu-keyring", "2018.02.28", "ubuntu-keyring", "2018.02.28", dpkg.ParserName}, {"libffi", "3.2.1-8", "dpkg", "source"},
{"util-linux", "2.31.1-0.4ubuntu3.1", "util-linux", "2.31.1-0.4ubuntu3.1", dpkg.ParserName}, {"pam", "1.1.8-3.6ubuntu2", "dpkg", "source"},
{"zlib1g", "1:1.2.11.dfsg-0ubuntu2", "zlib", "1:1.2.11.dfsg-0ubuntu2", dpkg.ParserName}, {"bsdutils", "1:2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libtasn1-6", "4.13-2", "dpkg", "binary"},
{"libaudit-common", "1:2.8.2-1ubuntu1", "dpkg", "binary"},
{"gpgv", "2.2.4-1ubuntu1.1", "dpkg", "binary"},
{"libzstd", "1.3.3+dfsg-2ubuntu1", "dpkg", "source"},
{"base-passwd", "3.5.44", "dpkg", "source"},
{"adduser", "3.116ubuntu1", "dpkg", "binary"},
{"libattr1", "1:2.4.47-2build1", "dpkg", "binary"},
{"libncurses5", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"coreutils", "8.28-1ubuntu1", "dpkg", "binary"},
{"base-passwd", "3.5.44", "dpkg", "binary"},
{"ubuntu-keyring", "2018.02.28", "dpkg", "binary"},
{"adduser", "3.116ubuntu1", "dpkg", "source"},
{"libsmartcols1", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libunistring", "0.9.9-0ubuntu1", "dpkg", "source"},
{"mawk", "1.3.3-17ubuntu3", "dpkg", "source"},
{"coreutils", "8.28-1ubuntu1", "dpkg", "source"},
{"attr", "1:2.4.47-2build1", "dpkg", "source"},
{"gmp", "2:6.1.2+dfsg-2", "dpkg", "source"},
{"libsemanage", "2.7-2build2", "dpkg", "source"},
{"libselinux1", "2.7-2build2", "dpkg", "binary"},
{"libseccomp2", "2.3.1-2.1ubuntu4", "dpkg", "binary"},
{"zlib1g", "1:1.2.11.dfsg-0ubuntu2", "dpkg", "binary"},
{"dash", "0.5.8-2.10", "dpkg", "source"},
{"gnutls28", "3.5.18-1ubuntu1", "dpkg", "source"},
{"libpam-runtime", "1.1.8-3.6ubuntu2", "dpkg", "binary"},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "dpkg", "source"},
{"sensible-utils", "0.0.12", "dpkg", "binary"},
{"p11-kit", "0.23.9-2", "dpkg", "source"},
{"ncurses-base", "6.1-1ubuntu1.18.04", "dpkg", "binary"},
{"e2fsprogs", "1.44.1-1", "dpkg", "binary"},
{"libgcrypt20", "1.8.1-4ubuntu1.1", "dpkg", "binary"},
{"libprocps6", "2:3.3.12-3ubuntu1.1", "dpkg", "binary"},
{"debconf", "1.5.66", "dpkg", "source"},
{"gcc-8-base", "8-20180414-1ubuntu2", "dpkg", "binary"},
{"base-files", "10.1ubuntu2.2", "dpkg", "binary"},
{"libbz2-1.0", "1.0.6-8.1", "dpkg", "binary"},
{"grep", "3.1-2", "dpkg", "source"},
{"bash", "4.4.18-2ubuntu1", "dpkg", "binary"},
{"libgmp10", "2:6.1.2+dfsg-2", "dpkg", "binary"},
{"shadow", "1:4.5-1ubuntu1", "dpkg", "source"},
{"libidn2", "2.0.4-1.1build2", "dpkg", "source"},
{"gzip", "1.6-5ubuntu1", "dpkg", "source"},
{"util-linux", "2.31.1-0.4ubuntu3.1", "dpkg", "binary"},
{"libaudit1", "1:2.8.2-1ubuntu1", "dpkg", "binary"},
{"libsepol1", "2.7-1", "dpkg", "binary"},
{"pcre3", "2:8.39-9", "dpkg", "source"},
{"apt", "1.6.3ubuntu0.1", "dpkg", "source"},
{"nettle", "3.4-1", "dpkg", "source"},
{"util-linux", "2.31.1-0.4ubuntu3.1", "dpkg", "source"},
{"libcap-ng0", "0.7.7-3.1", "dpkg", "binary"},
{"debianutils", "4.8.4", "dpkg", "binary"},
{"ncurses", "6.1-1ubuntu1.18.04", "dpkg", "source"},
{"libffi6", "3.2.1-8", "dpkg", "binary"},
{"cdebconf", "0.213ubuntu1", "dpkg", "source"},
{"findutils", "4.6.0+git+20170828-2", "dpkg", "source"},
{"libdb5.3", "5.3.28-13.1ubuntu1", "dpkg", "binary"},
{"zlib", "1:1.2.11.dfsg-0ubuntu2", "dpkg", "source"},
{"findutils", "4.6.0+git+20170828-2", "dpkg", "binary"},
{"dpkg", "1.19.0.5ubuntu2", "dpkg", "binary"},
{"mawk", "1.3.3-17ubuntu3", "dpkg", "binary"},
}, },
}, },
{ {
"corrupted status file", "corrupted status file",
map[string]string{"var/lib/dpkg/status": "dpkg/testdata/corrupted"}, map[string]string{"var/lib/dpkg/status": "dpkg/testdata/corrupted"},
[]database.Feature{ []database.Feature{
{"libpam-runtime", "1.1.8-3.1ubuntu3", "pam", "1.1.8-3.1ubuntu3", dpkg.ParserName}, {"libpam-modules-bin", "1.1.8-3.1ubuntu3", "dpkg", "binary"},
{"libpam-modules-bin", "1.1.8-3.1ubuntu3", "pam", "1.1.8-3.1ubuntu3", dpkg.ParserName}, {"gcc-5", "5.1.1-12ubuntu1", "dpkg", "source"},
{"makedev", "2.3.1-93ubuntu1", "makedev", "2.3.1-93ubuntu1", dpkg.ParserName}, {"makedev", "2.3.1-93ubuntu1", "dpkg", "binary"},
{"libgcc1", "1:5.1.1-12ubuntu1", "gcc-5", "5.1.1-12ubuntu1", dpkg.ParserName}, {"libgcc1", "1:5.1.1-12ubuntu1", "dpkg", "binary"},
{"pam", "1.1.8-3.1ubuntu3", "dpkg", "source"},
{"makedev", "2.3.1-93ubuntu1", "dpkg", "source"},
{"libpam-runtime", "1.1.8-3.1ubuntu3", "dpkg", "binary"},
}, },
}, },
} { } {

@ -45,6 +45,10 @@ func init() {
featurefmt.RegisterLister("rpm", "1.0", &lister{}) featurefmt.RegisterLister("rpm", "1.0", &lister{})
} }
func (l lister) RequiredFilenames() []string {
return []string{"var/lib/rpm/Packages"}
}
func isIgnored(packageName string) bool { func isIgnored(packageName string) bool {
for _, pkg := range ignoredPackages { for _, pkg := range ignoredPackages {
if pkg == packageName { if pkg == packageName {
@ -55,12 +59,6 @@ func isIgnored(packageName string) bool {
return false return false
} }
func valid(pkg *database.Feature) bool {
return pkg.Name != "" && pkg.Version != "" &&
((pkg.SourceName == "" && pkg.SourceVersion != "") ||
(pkg.SourceName != "" && pkg.SourceVersion != ""))
}
func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) { func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error) {
f, hasFile := files["var/lib/rpm/Packages"] f, hasFile := files["var/lib/rpm/Packages"]
if !hasFile { if !hasFile {
@ -84,7 +82,7 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
// Extract binary package names because RHSA refers to binary package names. // Extract binary package names because RHSA refers to binary package names.
out, err := exec.Command("rpm", "--dbpath", tmpDir, "-qa", "--qf", "%{NAME} %{EPOCH}:%{VERSION}-%{RELEASE} %{SOURCERPM}\n").CombinedOutput() out, err := exec.Command("rpm", "--dbpath", tmpDir, "-qa", "--qf", "%{NAME} %{EPOCH}:%{VERSION}-%{RELEASE} %{SOURCERPM}\n").CombinedOutput()
if err != nil { if err != nil {
log.WithError(err).WithField("output", string(out)).Error("could not query RPM") log.WithError(err).WithField("output", string(out)).Error("failed to query RPM")
// Do not bubble up because we probably won't be able to fix it, // Do not bubble up because we probably won't be able to fix it,
// the database must be corrupted // the database must be corrupted
return []database.Feature{}, nil return []database.Feature{}, nil
@ -93,39 +91,51 @@ func (l lister) ListFeatures(files tarutil.FilesMap) ([]database.Feature, error)
packages := mapset.NewSet() packages := mapset.NewSet()
scanner := bufio.NewScanner(strings.NewReader(string(out))) scanner := bufio.NewScanner(strings.NewReader(string(out)))
for scanner.Scan() { for scanner.Scan() {
line := strings.Split(scanner.Text(), " ") rpmPackage, srpmPackage := parseRPMOutput(scanner.Text())
if len(line) != 3 { if rpmPackage != nil {
// We may see warnings on some RPM versions: packages.Add(*rpmPackage)
// "warning: Generating 12 missing index(es), please wait..."
continue
} }
if isIgnored(line[0]) { if srpmPackage != nil {
continue packages.Add(*srpmPackage)
} }
}
pkg := database.Feature{Name: line[0], VersionFormat: rpm.ParserName} return database.ConvertFeatureSetToFeatures(packages), nil
pkg.Version = strings.Replace(line[1], "(none):", "", -1) }
if err := versionfmt.Valid(rpm.ParserName, pkg.Version); err != nil {
log.WithError(err).WithField("version", line[1]).Warning("skipped unparseable package")
continue
}
if err := parseSourceRPM(line[2], &pkg); err != nil { func parseRPMOutput(raw string) (rpmPackage *database.Feature, srpmPackage *database.Feature) {
log.WithError(err).WithField("sourcerpm", line[2]).Warning("skipped unparseable package") line := strings.Split(raw, " ")
continue if len(line) != 3 {
} // We may see warnings on some RPM versions:
// "warning: Generating 12 missing index(es), please wait..."
return
}
if valid(&pkg) { if isIgnored(line[0]) {
packages.Add(pkg) return
}
} }
return database.ConvertFeatureSetToFeatures(packages), nil name, version, srpm := line[0], strings.Replace(line[1], "(none):", "", -1), line[2]
} if err := versionfmt.Valid(rpm.ParserName, version); err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "version": version}).Warning("skipped unparseable package")
return
}
func (l lister) RequiredFilenames() []string { rpmPackage = &database.Feature{name, version, rpm.ParserName, database.BinaryPackage}
return []string{"var/lib/rpm/Packages"} srpmName, srpmVersion, srpmRelease, _, err := parseSourceRPM(srpm)
if err != nil {
log.WithError(err).WithFields(log.Fields{"name": name, "sourcerpm": srpm}).Warning("skipped unparseable package")
return
}
srpmVersion = srpmVersion + "-" + srpmRelease
if err = versionfmt.Valid(rpm.ParserName, srpmVersion); err != nil {
return
}
srpmPackage = &database.Feature{srpmName, srpmVersion, rpm.ParserName, database.SourcePackage}
return
} }
type rpmParserState string type rpmParserState string
@ -140,11 +150,9 @@ const (
// parseSourceRPM parses the source rpm package representation string // parseSourceRPM parses the source rpm package representation string
// http://ftp.rpm.org/max-rpm/ch-rpm-file-format.html // http://ftp.rpm.org/max-rpm/ch-rpm-file-format.html
func parseSourceRPM(sourceRPM string, pkg *database.Feature) error { func parseSourceRPM(sourceRPM string) (name string, version string, release string, architecture string, err error) {
state := parseRPM state := parseRPM
previousCheckPoint := len(sourceRPM) previousCheckPoint := len(sourceRPM)
release := ""
version := ""
for i := len(sourceRPM) - 1; i >= 0; i-- { for i := len(sourceRPM) - 1; i >= 0; i-- {
switch state { switch state {
case parseRPM: case parseRPM:
@ -153,16 +161,18 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
packageType := strutil.Substring(sourceRPM, i+1, len(sourceRPM)) packageType := strutil.Substring(sourceRPM, i+1, len(sourceRPM))
previousCheckPoint = i previousCheckPoint = i
if packageType != "rpm" { if packageType != "rpm" {
return fmt.Errorf("unexpected package type, expect: 'rpm', got: '%s'", packageType) err = fmt.Errorf("unexpected package type, expect: 'rpm', got: '%s'", packageType)
return
} }
} }
case parseArchitecture: case parseArchitecture:
if string(sourceRPM[i]) == "." { if string(sourceRPM[i]) == "." {
state = parseRelease state = parseRelease
architecture := strutil.Substring(sourceRPM, i+1, previousCheckPoint) architecture = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i previousCheckPoint = i
if architecture != "src" && architecture != "nosrc" { if architecture != "src" && architecture != "nosrc" {
return fmt.Errorf("unexpected package architecture, expect: 'src' or 'nosrc', got: '%s'", architecture) err = fmt.Errorf("unexpected package architecture, expect: 'src' or 'nosrc', got: '%s'", architecture)
return
} }
} }
case parseRelease: case parseRelease:
@ -171,7 +181,8 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
release = strutil.Substring(sourceRPM, i+1, previousCheckPoint) release = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i previousCheckPoint = i
if release == "" { if release == "" {
return fmt.Errorf("unexpected package release, expect: not empty") err = fmt.Errorf("unexpected package release, expect: not empty")
return
} }
} }
case parseVersion: case parseVersion:
@ -181,7 +192,8 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
version = strutil.Substring(sourceRPM, i+1, previousCheckPoint) version = strutil.Substring(sourceRPM, i+1, previousCheckPoint)
previousCheckPoint = i previousCheckPoint = i
if version == "" { if version == "" {
return fmt.Errorf("unexpected package version, expect: not empty") err = fmt.Errorf("unexpected package version, expect: not empty")
return
} }
break break
} }
@ -189,20 +201,15 @@ func parseSourceRPM(sourceRPM string, pkg *database.Feature) error {
} }
if state != terminate { if state != terminate {
return fmt.Errorf("unexpected termination while parsing '%s'", state) err = fmt.Errorf("unexpected termination while parsing '%s'", state)
} return
concatVersion := version + "-" + release
if err := versionfmt.Valid(rpm.ParserName, concatVersion); err != nil {
return err
} }
name := strutil.Substring(sourceRPM, 0, previousCheckPoint) name = strutil.Substring(sourceRPM, 0, previousCheckPoint)
if name == "" { if name == "" {
return fmt.Errorf("unexpected package name, expect: not empty") err = fmt.Errorf("unexpected package name, expect: not empty")
return
} }
pkg.SourceName = name return
pkg.SourceVersion = concatVersion
return nil
} }

@ -25,179 +25,307 @@ import (
) )
var expectedBigCaseInfo = []database.Feature{ var expectedBigCaseInfo = []database.Feature{
{"publicsuffix-list-dafsa", "20180514-1.fc28", "publicsuffix-list", "20180514-1.fc28", rpm.ParserName}, {"libmount", "2.32.1-1.fc28", "rpm", "binary"},
{"libreport-filesystem", "2.9.5-1.fc28", "libreport", "2.9.5-1.fc28", rpm.ParserName}, {"libffi", "3.1-16.fc28", "rpm", "binary"},
{"fedora-gpg-keys", "28-5", "fedora-repos", "28-5", rpm.ParserName}, {"libunistring", "0.9.10-1.fc28", "rpm", "binary"},
{"fedora-release", "28-2", "fedora-release", "28-2", rpm.ParserName}, {"fedora-repos", "28-5", "rpm", "binary"},
{"filesystem", "3.8-2.fc28", "filesystem", "3.8-2.fc28", rpm.ParserName}, {"libarchive", "3.3.1-4.fc28", "rpm", "source"},
{"tzdata", "2018e-1.fc28", "tzdata", "2018e-1.fc28", rpm.ParserName}, {"langpacks", "1.0-12.fc28", "rpm", "source"},
{"pcre2", "10.31-10.fc28", "pcre2", "10.31-10.fc28", rpm.ParserName}, {"readline", "7.0-11.fc28", "rpm", "source"},
{"glibc-minimal-langpack", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName}, {"gzip", "1.9-3.fc28", "rpm", "source"},
{"glibc-common", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName}, {"libverto", "0.3.0-5.fc28", "rpm", "source"},
{"bash", "4.4.23-1.fc28", "bash", "4.4.23-1.fc28", rpm.ParserName}, {"ncurses-base", "6.1-5.20180224.fc28", "rpm", "binary"},
{"zlib", "1.2.11-8.fc28", "zlib", "1.2.11-8.fc28", rpm.ParserName}, {"libfdisk", "2.32.1-1.fc28", "rpm", "binary"},
{"bzip2-libs", "1.0.6-26.fc28", "bzip2", "1.0.6-26.fc28", rpm.ParserName}, {"libselinux", "2.8-1.fc28", "rpm", "source"},
{"libcap", "2.25-9.fc28", "libcap", "2.25-9.fc28", rpm.ParserName}, {"nss-util", "3.38.0-1.0.fc28", "rpm", "source"},
{"libgpg-error", "1.31-1.fc28", "libgpg-error", "1.31-1.fc28", rpm.ParserName}, {"mpfr", "3.1.6-1.fc28", "rpm", "source"},
{"libzstd", "1.3.5-1.fc28", "zstd", "1.3.5-1.fc28", rpm.ParserName}, {"libunistring", "0.9.10-1.fc28", "rpm", "source"},
{"expat", "2.2.5-3.fc28", "expat", "2.2.5-3.fc28", rpm.ParserName}, {"libpcap", "14:1.9.0-1.fc28", "rpm", "binary"},
{"nss-util", "3.38.0-1.0.fc28", "nss-util", "3.38.0-1.0.fc28", rpm.ParserName}, {"libarchive", "3.3.1-4.fc28", "rpm", "binary"},
{"libcom_err", "1.44.2-0.fc28", "e2fsprogs", "1.44.2-0.fc28", rpm.ParserName}, {"gmp", "1:6.1.2-7.fc28", "rpm", "binary"},
{"libffi", "3.1-16.fc28", "libffi", "3.1-16.fc28", rpm.ParserName}, {"crypto-policies", "20180425-5.git6ad4018.fc28", "rpm", "source"},
{"libgcrypt", "1.8.3-1.fc28", "libgcrypt", "1.8.3-1.fc28", rpm.ParserName}, {"gzip", "1.9-3.fc28", "rpm", "binary"},
{"libxml2", "2.9.8-4.fc28", "libxml2", "2.9.8-4.fc28", rpm.ParserName}, {"fedora-release", "28-2", "rpm", "source"},
{"libacl", "2.2.53-1.fc28", "acl", "2.2.53-1.fc28", rpm.ParserName}, {"zlib", "1.2.11-8.fc28", "rpm", "binary"},
{"sed", "4.5-1.fc28", "sed", "4.5-1.fc28", rpm.ParserName}, {"crypto-policies", "20180425-5.git6ad4018.fc28", "rpm", "binary"},
{"libmount", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"lz4", "1.8.1.2-4.fc28", "rpm", "source"},
{"p11-kit", "0.23.12-1.fc28", "p11-kit", "0.23.12-1.fc28", rpm.ParserName}, {"keyutils", "1.5.10-6.fc28", "rpm", "source"},
{"libidn2", "2.0.5-1.fc28", "libidn2", "2.0.5-1.fc28", rpm.ParserName}, {"gpgme", "1.10.0-4.fc28", "rpm", "binary"},
{"libcap-ng", "0.7.9-4.fc28", "libcap-ng", "0.7.9-4.fc28", rpm.ParserName}, {"libgpg-error", "1.31-1.fc28", "rpm", "binary"},
{"lz4-libs", "1.8.1.2-4.fc28", "lz4", "1.8.1.2-4.fc28", rpm.ParserName}, {"gnutls", "3.6.3-4.fc28", "rpm", "source"},
{"libassuan", "2.5.1-3.fc28", "libassuan", "2.5.1-3.fc28", rpm.ParserName}, {"coreutils", "8.29-7.fc28", "rpm", "source"},
{"keyutils-libs", "1.5.10-6.fc28", "keyutils", "1.5.10-6.fc28", rpm.ParserName}, {"libsepol", "2.8-1.fc28", "rpm", "source"},
{"glib2", "2.56.1-4.fc28", "glib2", "2.56.1-4.fc28", rpm.ParserName}, {"libssh", "0.8.2-1.fc28", "rpm", "binary"},
{"systemd-libs", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName}, {"libpwquality", "1.4.0-7.fc28", "rpm", "binary"},
{"dbus-libs", "1:1.12.10-1.fc28", "dbus", "1.12.10-1.fc28", rpm.ParserName}, {"dnf-conf", "2.7.5-12.fc28", "rpm", "binary"},
{"libtasn1", "4.13-2.fc28", "libtasn1", "4.13-2.fc28", rpm.ParserName}, {"basesystem", "11-5.fc28", "rpm", "source"},
{"ca-certificates", "2018.2.24-1.0.fc28", "ca-certificates", "2018.2.24-1.0.fc28", rpm.ParserName}, {"setup", "2.11.4-1.fc28", "rpm", "binary"},
{"libarchive", "3.3.1-4.fc28", "libarchive", "3.3.1-4.fc28", rpm.ParserName}, {"libmetalink", "0.1.3-6.fc28", "rpm", "source"},
{"openssl", "1:1.1.0h-3.fc28", "openssl", "1.1.0h-3.fc28", rpm.ParserName}, {"texinfo", "6.5-4.fc28", "rpm", "source"},
{"libusbx", "1.0.22-1.fc28", "libusbx", "1.0.22-1.fc28", rpm.ParserName}, {"expat", "2.2.5-3.fc28", "rpm", "source"},
{"libsemanage", "2.8-2.fc28", "libsemanage", "2.8-2.fc28", rpm.ParserName}, {"ncurses", "6.1-5.20180224.fc28", "rpm", "source"},
{"libutempter", "1.1.6-14.fc28", "libutempter", "1.1.6-14.fc28", rpm.ParserName}, {"libpwquality", "1.4.0-7.fc28", "rpm", "source"},
{"mpfr", "3.1.6-1.fc28", "mpfr", "3.1.6-1.fc28", rpm.ParserName}, {"pcre", "8.42-3.fc28", "rpm", "binary"},
{"gnutls", "3.6.3-4.fc28", "gnutls", "3.6.3-4.fc28", rpm.ParserName}, {"sssd", "1.16.3-2.fc28", "rpm", "source"},
{"gzip", "1.9-3.fc28", "gzip", "1.9-3.fc28", rpm.ParserName}, {"basesystem", "11-5.fc28", "rpm", "binary"},
{"acl", "2.2.53-1.fc28", "acl", "2.2.53-1.fc28", rpm.ParserName}, {"systemd-pam", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"nss-softokn-freebl", "3.38.0-1.0.fc28", "nss-softokn", "3.38.0-1.0.fc28", rpm.ParserName}, {"python3-six", "1.11.0-3.fc28", "rpm", "binary"},
{"nss", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName}, {"libcurl", "7.59.0-6.fc28", "rpm", "binary"},
{"libmetalink", "0.1.3-6.fc28", "libmetalink", "0.1.3-6.fc28", rpm.ParserName}, {"qrencode", "3.4.4-5.fc28", "rpm", "source"},
{"libdb-utils", "5.3.28-30.fc28", "libdb", "5.3.28-30.fc28", rpm.ParserName}, {"xz", "5.2.4-2.fc28", "rpm", "source"},
{"file-libs", "5.33-7.fc28", "file", "5.33-7.fc28", rpm.ParserName}, {"libpkgconf", "1.4.2-1.fc28", "rpm", "binary"},
{"libsss_idmap", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName}, {"libzstd", "1.3.5-1.fc28", "rpm", "binary"},
{"libsigsegv", "2.11-5.fc28", "libsigsegv", "2.11-5.fc28", rpm.ParserName}, {"bash", "4.4.23-1.fc28", "rpm", "binary"},
{"krb5-libs", "1.16.1-13.fc28", "krb5", "1.16.1-13.fc28", rpm.ParserName}, {"cyrus-sasl", "2.1.27-0.2rc7.fc28", "rpm", "source"},
{"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "libnsl2", "1.2.0-2.20180605git4a062cf.fc28", rpm.ParserName}, {"ncurses-libs", "6.1-5.20180224.fc28", "rpm", "binary"},
{"python3-pip", "9.0.3-2.fc28", "python-pip", "9.0.3-2.fc28", rpm.ParserName}, {"xz-libs", "5.2.4-2.fc28", "rpm", "binary"},
{"python3", "3.6.6-1.fc28", "python3", "3.6.6-1.fc28", rpm.ParserName}, {"dbus", "1.12.10-1.fc28", "rpm", "source"},
{"pam", "1.3.1-1.fc28", "pam", "1.3.1-1.fc28", rpm.ParserName}, {"grep", "3.1-5.fc28", "rpm", "binary"},
{"python3-gobject-base", "3.28.3-1.fc28", "pygobject3", "3.28.3-1.fc28", rpm.ParserName}, {"libusbx", "1.0.22-1.fc28", "rpm", "binary"},
{"python3-smartcols", "0.3.0-2.fc28", "python-smartcols", "0.3.0-2.fc28", rpm.ParserName}, {"audit", "2.8.4-2.fc28", "rpm", "source"},
{"python3-iniparse", "0.4-30.fc28", "python-iniparse", "0.4-30.fc28", rpm.ParserName}, {"sed", "4.5-1.fc28", "rpm", "binary"},
{"openldap", "2.4.46-3.fc28", "openldap", "2.4.46-3.fc28", rpm.ParserName}, {"sqlite", "3.22.0-4.fc28", "rpm", "source"},
{"libseccomp", "2.3.3-2.fc28", "libseccomp", "2.3.3-2.fc28", rpm.ParserName}, {"openldap", "2.4.46-3.fc28", "rpm", "binary"},
{"npth", "1.5-4.fc28", "npth", "1.5-4.fc28", rpm.ParserName}, {"gawk", "4.2.1-1.fc28", "rpm", "binary"},
{"gpgme", "1.10.0-4.fc28", "gpgme", "1.10.0-4.fc28", rpm.ParserName}, {"gpgme", "1.10.0-4.fc28", "rpm", "source"},
{"json-c", "0.13.1-2.fc28", "json-c", "0.13.1-2.fc28", rpm.ParserName}, {"lvm2", "2.02.177-5.fc28", "rpm", "source"},
{"libyaml", "0.1.7-5.fc28", "libyaml", "0.1.7-5.fc28", rpm.ParserName}, {"nspr", "4.19.0-1.fc28", "rpm", "source"},
{"libpkgconf", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName}, {"libsolv", "0.6.35-1.fc28", "rpm", "source"},
{"pkgconf-pkg-config", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName}, {"info", "6.5-4.fc28", "rpm", "binary"},
{"iptables-libs", "1.6.2-3.fc28", "iptables", "1.6.2-3.fc28", rpm.ParserName}, {"openssl-libs", "1:1.1.0h-3.fc28", "rpm", "binary"},
{"device-mapper-libs", "1.02.146-5.fc28", "lvm2", "2.02.177-5.fc28", rpm.ParserName}, {"libxcrypt", "4.1.2-1.fc28", "rpm", "binary"},
{"systemd-pam", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName}, {"libselinux", "2.8-1.fc28", "rpm", "binary"},
{"systemd", "238-9.git0e0aa59.fc28", "systemd", "238-9.git0e0aa59.fc28", rpm.ParserName}, {"libgcc", "8.1.1-5.fc28", "rpm", "binary"},
{"elfutils-default-yama-scope", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName}, {"cracklib", "2.9.6-13.fc28", "rpm", "binary"},
{"libcurl", "7.59.0-6.fc28", "curl", "7.59.0-6.fc28", rpm.ParserName}, {"python3-libs", "3.6.6-1.fc28", "rpm", "binary"},
{"python3-librepo", "1.8.1-7.fc28", "librepo", "1.8.1-7.fc28", rpm.ParserName}, {"glibc-langpack-en", "2.27-32.fc28", "rpm", "binary"},
{"rpm-plugin-selinux", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"json-c", "0.13.1-2.fc28", "rpm", "binary"},
{"rpm", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"gnupg2", "2.2.8-1.fc28", "rpm", "source"},
{"libdnf", "0.11.1-3.fc28", "libdnf", "0.11.1-3.fc28", rpm.ParserName}, {"openssl", "1:1.1.0h-3.fc28", "rpm", "binary"},
{"rpm-build-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"glibc-common", "2.27-32.fc28", "rpm", "binary"},
{"python3-rpm", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"p11-kit-trust", "0.23.12-1.fc28", "rpm", "binary"},
{"dnf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName}, {"zstd", "1.3.5-1.fc28", "rpm", "source"},
{"deltarpm", "3.6-25.fc28", "deltarpm", "3.6-25.fc28", rpm.ParserName}, {"libxml2", "2.9.8-4.fc28", "rpm", "source"},
{"sssd-client", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName}, {"dbus", "1:1.12.10-1.fc28", "rpm", "binary"},
{"cracklib-dicts", "2.9.6-13.fc28", "cracklib", "2.9.6-13.fc28", rpm.ParserName}, {"ca-certificates", "2018.2.24-1.0.fc28", "rpm", "binary"},
{"tar", "2:1.30-3.fc28", "tar", "1.30-3.fc28", rpm.ParserName}, {"libcomps", "0.1.8-11.fc28", "rpm", "binary"},
{"diffutils", "3.6-4.fc28", "diffutils", "3.6-4.fc28", rpm.ParserName}, {"nss", "3.38.0-1.0.fc28", "rpm", "binary"},
{"langpacks-en", "1.0-12.fc28", "langpacks", "1.0-12.fc28", rpm.ParserName}, {"libcom_err", "1.44.2-0.fc28", "rpm", "binary"},
{"libgcc", "8.1.1-5.fc28", "gcc", "8.1.1-5.fc28", rpm.ParserName}, {"keyutils-libs", "1.5.10-6.fc28", "rpm", "binary"},
{"pkgconf-m4", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName}, {"libseccomp", "2.3.3-2.fc28", "rpm", "binary"},
{"dnf-conf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName}, {"elfutils-libs", "0.173-1.fc28", "rpm", "binary"},
{"fedora-repos", "28-5", "fedora-repos", "28-5", rpm.ParserName}, {"libuuid", "2.32.1-1.fc28", "rpm", "binary"},
{"setup", "2.11.4-1.fc28", "setup", "2.11.4-1.fc28", rpm.ParserName}, {"pkgconf", "1.4.2-1.fc28", "rpm", "source"},
{"basesystem", "11-5.fc28", "basesystem", "11-5.fc28", rpm.ParserName}, {"grep", "3.1-5.fc28", "rpm", "source"},
{"ncurses-base", "6.1-5.20180224.fc28", "ncurses", "6.1-5.20180224.fc28", rpm.ParserName}, {"libpcap", "1.9.0-1.fc28", "rpm", "source"},
{"libselinux", "2.8-1.fc28", "libselinux", "2.8-1.fc28", rpm.ParserName}, {"deltarpm", "3.6-25.fc28", "rpm", "binary"},
{"ncurses-libs", "6.1-5.20180224.fc28", "ncurses", "6.1-5.20180224.fc28", rpm.ParserName}, {"krb5-libs", "1.16.1-13.fc28", "rpm", "binary"},
{"glibc", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName}, {"glibc", "2.27-32.fc28", "rpm", "binary"},
{"libsepol", "2.8-1.fc28", "libsepol", "2.8-1.fc28", rpm.ParserName}, {"libseccomp", "2.3.3-2.fc28", "rpm", "source"},
{"xz-libs", "5.2.4-2.fc28", "xz", "5.2.4-2.fc28", rpm.ParserName}, {"libsemanage", "2.8-2.fc28", "rpm", "binary"},
{"info", "6.5-4.fc28", "texinfo", "6.5-4.fc28", rpm.ParserName}, {"openssl-pkcs11", "0.4.8-1.fc28", "rpm", "binary"},
{"libdb", "5.3.28-30.fc28", "libdb", "5.3.28-30.fc28", rpm.ParserName}, {"libxml2", "2.9.8-4.fc28", "rpm", "binary"},
{"elfutils-libelf", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName}, {"e2fsprogs", "1.44.2-0.fc28", "rpm", "source"},
{"popt", "1.16-14.fc28", "popt", "1.16-14.fc28", rpm.ParserName}, {"file-libs", "5.33-7.fc28", "rpm", "binary"},
{"nspr", "4.19.0-1.fc28", "nspr", "4.19.0-1.fc28", rpm.ParserName}, {"elfutils-default-yama-scope", "0.173-1.fc28", "rpm", "binary"},
{"libxcrypt", "4.1.2-1.fc28", "libxcrypt", "4.1.2-1.fc28", rpm.ParserName}, {"glibc", "2.27-32.fc28", "rpm", "source"},
{"lua-libs", "5.3.4-10.fc28", "lua", "5.3.4-10.fc28", rpm.ParserName}, {"publicsuffix-list-dafsa", "20180514-1.fc28", "rpm", "binary"},
{"libuuid", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"popt", "1.16-14.fc28", "rpm", "binary"},
{"readline", "7.0-11.fc28", "readline", "7.0-11.fc28", rpm.ParserName}, {"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "rpm", "binary"},
{"libattr", "2.4.48-3.fc28", "attr", "2.4.48-3.fc28", rpm.ParserName}, {"lua-libs", "5.3.4-10.fc28", "rpm", "binary"},
{"coreutils-single", "8.29-7.fc28", "coreutils", "8.29-7.fc28", rpm.ParserName}, {"libsemanage", "2.8-2.fc28", "rpm", "source"},
{"libblkid", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"glibc-minimal-langpack", "2.27-32.fc28", "rpm", "binary"},
{"gmp", "1:6.1.2-7.fc28", "gmp", "6.1.2-7.fc28", rpm.ParserName}, {"attr", "2.4.48-3.fc28", "rpm", "source"},
{"libunistring", "0.9.10-1.fc28", "libunistring", "0.9.10-1.fc28", rpm.ParserName}, {"gdbm", "1.14.1-4.fc28", "rpm", "source"},
{"sqlite-libs", "3.22.0-4.fc28", "sqlite", "3.22.0-4.fc28", rpm.ParserName}, {"pkgconf", "1.4.2-1.fc28", "rpm", "binary"},
{"audit-libs", "2.8.4-2.fc28", "audit", "2.8.4-2.fc28", rpm.ParserName}, {"acl", "2.2.53-1.fc28", "rpm", "source"},
{"chkconfig", "1.10-4.fc28", "chkconfig", "1.10-4.fc28", rpm.ParserName}, {"gnutls", "3.6.3-4.fc28", "rpm", "binary"},
{"libsmartcols", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"fedora-repos", "28-5", "rpm", "source"},
{"pcre", "8.42-3.fc28", "pcre", "8.42-3.fc28", rpm.ParserName}, {"python3-pip", "9.0.3-2.fc28", "rpm", "binary"},
{"grep", "3.1-5.fc28", "grep", "3.1-5.fc28", rpm.ParserName}, {"libnsl2", "1.2.0-2.20180605git4a062cf.fc28", "rpm", "source"},
{"crypto-policies", "20180425-5.git6ad4018.fc28", "crypto-policies", "20180425-5.git6ad4018.fc28", rpm.ParserName}, {"rpm", "4.14.1-9.fc28", "rpm", "binary"},
{"gdbm-libs", "1:1.14.1-4.fc28", "gdbm", "1.14.1-4.fc28", rpm.ParserName}, {"libutempter", "1.1.6-14.fc28", "rpm", "source"},
{"p11-kit-trust", "0.23.12-1.fc28", "p11-kit", "0.23.12-1.fc28", rpm.ParserName}, {"libdnf", "0.11.1-3.fc28", "rpm", "source"},
{"openssl-libs", "1:1.1.0h-3.fc28", "openssl", "1.1.0h-3.fc28", rpm.ParserName}, {"vim-minimal", "2:8.1.328-1.fc28", "rpm", "binary"},
{"ima-evm-utils", "1.1-2.fc28", "ima-evm-utils", "1.1-2.fc28", rpm.ParserName}, {"tzdata", "2018e-1.fc28", "rpm", "binary"},
{"gdbm", "1:1.14.1-4.fc28", "gdbm", "1.14.1-4.fc28", rpm.ParserName}, {"nettle", "3.4-2.fc28", "rpm", "binary"},
{"gobject-introspection", "1.56.1-1.fc28", "gobject-introspection", "1.56.1-1.fc28", rpm.ParserName}, {"python-pip", "9.0.3-2.fc28", "rpm", "source"},
{"shadow-utils", "2:4.6-1.fc28", "shadow-utils", "4.6-1.fc28", rpm.ParserName}, {"python-six", "1.11.0-3.fc28", "rpm", "source"},
{"libpsl", "0.20.2-2.fc28", "libpsl", "0.20.2-2.fc28", rpm.ParserName}, {"diffutils", "3.6-4.fc28", "rpm", "binary"},
{"nettle", "3.4-2.fc28", "nettle", "3.4-2.fc28", rpm.ParserName}, {"rpm-plugin-selinux", "4.14.1-9.fc28", "rpm", "binary"},
{"libfdisk", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"shadow-utils", "2:4.6-1.fc28", "rpm", "binary"},
{"cracklib", "2.9.6-13.fc28", "cracklib", "2.9.6-13.fc28", rpm.ParserName}, {"pkgconf-pkg-config", "1.4.2-1.fc28", "rpm", "binary"},
{"libcomps", "0.1.8-11.fc28", "libcomps", "0.1.8-11.fc28", rpm.ParserName}, {"cracklib-dicts", "2.9.6-13.fc28", "rpm", "binary"},
{"nss-softokn", "3.38.0-1.0.fc28", "nss-softokn", "3.38.0-1.0.fc28", rpm.ParserName}, {"libblkid", "2.32.1-1.fc28", "rpm", "binary"},
{"nss-sysinit", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName}, {"python-setuptools", "39.2.0-6.fc28", "rpm", "source"},
{"libksba", "1.3.5-7.fc28", "libksba", "1.3.5-7.fc28", rpm.ParserName}, {"libsss_idmap", "1.16.3-2.fc28", "rpm", "binary"},
{"kmod-libs", "25-2.fc28", "kmod", "25-2.fc28", rpm.ParserName}, {"libksba", "1.3.5-7.fc28", "rpm", "source"},
{"libsss_nss_idmap", "1.16.3-2.fc28", "sssd", "1.16.3-2.fc28", rpm.ParserName}, {"sssd-client", "1.16.3-2.fc28", "rpm", "binary"},
{"libverto", "0.3.0-5.fc28", "libverto", "0.3.0-5.fc28", rpm.ParserName}, {"curl", "7.59.0-6.fc28", "rpm", "binary"},
{"gawk", "4.2.1-1.fc28", "gawk", "4.2.1-1.fc28", rpm.ParserName}, {"pam", "1.3.1-1.fc28", "rpm", "binary"},
{"libtirpc", "1.0.3-3.rc2.fc28", "libtirpc", "1.0.3-3.rc2.fc28", rpm.ParserName}, {"libsigsegv", "2.11-5.fc28", "rpm", "binary"},
{"python3-libs", "3.6.6-1.fc28", "python3", "3.6.6-1.fc28", rpm.ParserName}, {"langpacks-en", "1.0-12.fc28", "rpm", "binary"},
{"python3-setuptools", "39.2.0-6.fc28", "python-setuptools", "39.2.0-6.fc28", rpm.ParserName}, {"nss-softokn-freebl", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libpwquality", "1.4.0-7.fc28", "libpwquality", "1.4.0-7.fc28", rpm.ParserName}, {"glib2", "2.56.1-4.fc28", "rpm", "binary"},
{"util-linux", "2.32.1-1.fc28", "util-linux", "2.32.1-1.fc28", rpm.ParserName}, {"python3-gobject-base", "3.28.3-1.fc28", "rpm", "binary"},
{"python3-libcomps", "0.1.8-11.fc28", "libcomps", "0.1.8-11.fc28", rpm.ParserName}, {"libffi", "3.1-16.fc28", "rpm", "source"},
{"python3-six", "1.11.0-3.fc28", "python-six", "1.11.0-3.fc28", rpm.ParserName}, {"libmodulemd", "1.6.2-2.fc28", "rpm", "source"},
{"cyrus-sasl-lib", "2.1.27-0.2rc7.fc28", "cyrus-sasl", "2.1.27-0.2rc7.fc28", rpm.ParserName}, {"openssl", "1.1.0h-3.fc28", "rpm", "source"},
{"libssh", "0.8.2-1.fc28", "libssh", "0.8.2-1.fc28", rpm.ParserName}, {"libyaml", "0.1.7-5.fc28", "rpm", "source"},
{"qrencode-libs", "3.4.4-5.fc28", "qrencode", "3.4.4-5.fc28", rpm.ParserName}, {"pam", "1.3.1-1.fc28", "rpm", "source"},
{"gnupg2", "2.2.8-1.fc28", "gnupg2", "2.2.8-1.fc28", rpm.ParserName}, {"iptables", "1.6.2-3.fc28", "rpm", "source"},
{"python3-gpg", "1.10.0-4.fc28", "gpgme", "1.10.0-4.fc28", rpm.ParserName}, {"util-linux", "2.32.1-1.fc28", "rpm", "source"},
{"libargon2", "20161029-5.fc28", "argon2", "20161029-5.fc28", rpm.ParserName}, {"libsmartcols", "2.32.1-1.fc28", "rpm", "binary"},
{"libmodulemd", "1.6.2-2.fc28", "libmodulemd", "1.6.2-2.fc28", rpm.ParserName}, {"dnf", "2.7.5-12.fc28", "rpm", "binary"},
{"pkgconf", "1.4.2-1.fc28", "pkgconf", "1.4.2-1.fc28", rpm.ParserName}, {"glib2", "2.56.1-4.fc28", "rpm", "source"},
{"libpcap", "14:1.9.0-1.fc28", "libpcap", "1.9.0-1.fc28", rpm.ParserName}, {"lua", "5.3.4-10.fc28", "rpm", "source"},
{"device-mapper", "1.02.146-5.fc28", "lvm2", "2.02.177-5.fc28", rpm.ParserName}, {"nss-softokn", "3.38.0-1.0.fc28", "rpm", "source"},
{"cryptsetup-libs", "2.0.4-1.fc28", "cryptsetup", "2.0.4-1.fc28", rpm.ParserName}, {"python3-dnf", "2.7.5-12.fc28", "rpm", "binary"},
{"elfutils-libs", "0.173-1.fc28", "elfutils", "0.173-1.fc28", rpm.ParserName}, {"filesystem", "3.8-2.fc28", "rpm", "binary"},
{"dbus", "1:1.12.10-1.fc28", "dbus", "1.12.10-1.fc28", rpm.ParserName}, {"libsss_nss_idmap", "1.16.3-2.fc28", "rpm", "binary"},
{"libnghttp2", "1.32.1-1.fc28", "nghttp2", "1.32.1-1.fc28", rpm.ParserName}, {"pcre2", "10.31-10.fc28", "rpm", "source"},
{"librepo", "1.8.1-7.fc28", "librepo", "1.8.1-7.fc28", rpm.ParserName}, {"libyaml", "0.1.7-5.fc28", "rpm", "binary"},
{"curl", "7.59.0-6.fc28", "curl", "7.59.0-6.fc28", rpm.ParserName}, {"python3-rpm", "4.14.1-9.fc28", "rpm", "binary"},
{"rpm-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"zlib", "1.2.11-8.fc28", "rpm", "source"},
{"libsolv", "0.6.35-1.fc28", "libsolv", "0.6.35-1.fc28", rpm.ParserName}, {"libutempter", "1.1.6-14.fc28", "rpm", "binary"},
{"python3-hawkey", "0.11.1-3.fc28", "libdnf", "0.11.1-3.fc28", rpm.ParserName}, {"pcre2", "10.31-10.fc28", "rpm", "binary"},
{"rpm-sign-libs", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"libtirpc", "1.0.3-3.rc2.fc28", "rpm", "source"},
{"python3-dnf", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName}, {"pkgconf-m4", "1.4.2-1.fc28", "rpm", "binary"},
{"dnf-yum", "2.7.5-12.fc28", "dnf", "2.7.5-12.fc28", rpm.ParserName}, {"libreport", "2.9.5-1.fc28", "rpm", "source"},
{"rpm-plugin-systemd-inhibit", "4.14.1-9.fc28", "rpm", "4.14.1-9.fc28", rpm.ParserName}, {"vim", "8.1.328-1.fc28", "rpm", "source"},
{"nss-tools", "3.38.0-1.0.fc28", "nss", "3.38.0-1.0.fc28", rpm.ParserName}, {"file", "5.33-7.fc28", "rpm", "source"},
{"openssl-pkcs11", "0.4.8-1.fc28", "openssl-pkcs11", "0.4.8-1.fc28", rpm.ParserName}, {"shadow-utils", "4.6-1.fc28", "rpm", "source"},
{"vim-minimal", "2:8.1.328-1.fc28", "vim", "8.1.328-1.fc28", rpm.ParserName}, {"sqlite-libs", "3.22.0-4.fc28", "rpm", "binary"},
{"glibc-langpack-en", "2.27-32.fc28", "glibc", "2.27-32.fc28", rpm.ParserName}, {"setup", "2.11.4-1.fc28", "rpm", "source"},
{"rootfiles", "8.1-22.fc28", "rootfiles", "8.1-22.fc28", rpm.ParserName}, {"gcc", "8.1.1-5.fc28", "rpm", "source"},
{"mpfr", "3.1.6-1.fc28", "rpm", "binary"},
{"device-mapper", "1.02.146-5.fc28", "rpm", "binary"},
{"p11-kit", "0.23.12-1.fc28", "rpm", "source"},
{"fedora-release", "28-2", "rpm", "binary"},
{"libnghttp2", "1.32.1-1.fc28", "rpm", "binary"},
{"libcap-ng", "0.7.9-4.fc28", "rpm", "source"},
{"iptables-libs", "1.6.2-3.fc28", "rpm", "binary"},
{"audit-libs", "2.8.4-2.fc28", "rpm", "binary"},
{"libsigsegv", "2.11-5.fc28", "rpm", "source"},
{"rootfiles", "8.1-22.fc28", "rpm", "source"},
{"kmod-libs", "25-2.fc28", "rpm", "binary"},
{"lz4-libs", "1.8.1.2-4.fc28", "rpm", "binary"},
{"libassuan", "2.5.1-3.fc28", "rpm", "source"},
{"p11-kit", "0.23.12-1.fc28", "rpm", "binary"},
{"nss-sysinit", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libcap-ng", "0.7.9-4.fc28", "rpm", "binary"},
{"bash", "4.4.23-1.fc28", "rpm", "source"},
{"pygobject3", "3.28.3-1.fc28", "rpm", "source"},
{"dnf-yum", "2.7.5-12.fc28", "rpm", "binary"},
{"nss-softokn", "3.38.0-1.0.fc28", "rpm", "binary"},
{"expat", "2.2.5-3.fc28", "rpm", "binary"},
{"libassuan", "2.5.1-3.fc28", "rpm", "binary"},
{"libdb", "5.3.28-30.fc28", "rpm", "binary"},
{"tar", "2:1.30-3.fc28", "rpm", "binary"},
{"sed", "4.5-1.fc28", "rpm", "source"},
{"libmetalink", "0.1.3-6.fc28", "rpm", "binary"},
{"python-smartcols", "0.3.0-2.fc28", "rpm", "source"},
{"systemd", "238-9.git0e0aa59.fc28", "rpm", "source"},
{"python-iniparse", "0.4-30.fc28", "rpm", "source"},
{"libsepol", "2.8-1.fc28", "rpm", "binary"},
{"libattr", "2.4.48-3.fc28", "rpm", "binary"},
{"python3-smartcols", "0.3.0-2.fc28", "rpm", "binary"},
{"libdb", "5.3.28-30.fc28", "rpm", "source"},
{"libmodulemd", "1.6.2-2.fc28", "rpm", "binary"},
{"python3-hawkey", "0.11.1-3.fc28", "rpm", "binary"},
{"dbus-libs", "1:1.12.10-1.fc28", "rpm", "binary"},
{"chkconfig", "1.10-4.fc28", "rpm", "source"},
{"libargon2", "20161029-5.fc28", "rpm", "binary"},
{"openssl-pkcs11", "0.4.8-1.fc28", "rpm", "source"},
{"libusbx", "1.0.22-1.fc28", "rpm", "source"},
{"python3-setuptools", "39.2.0-6.fc28", "rpm", "binary"},
{"chkconfig", "1.10-4.fc28", "rpm", "binary"},
{"openldap", "2.4.46-3.fc28", "rpm", "source"},
{"bzip2", "1.0.6-26.fc28", "rpm", "source"},
{"npth", "1.5-4.fc28", "rpm", "source"},
{"libtirpc", "1.0.3-3.rc2.fc28", "rpm", "binary"},
{"util-linux", "2.32.1-1.fc28", "rpm", "binary"},
{"nss", "3.38.0-1.0.fc28", "rpm", "source"},
{"elfutils", "0.173-1.fc28", "rpm", "source"},
{"libcomps", "0.1.8-11.fc28", "rpm", "source"},
{"libxcrypt", "4.1.2-1.fc28", "rpm", "source"},
{"gnupg2", "2.2.8-1.fc28", "rpm", "binary"},
{"libdnf", "0.11.1-3.fc28", "rpm", "binary"},
{"cracklib", "2.9.6-13.fc28", "rpm", "source"},
{"libidn2", "2.0.5-1.fc28", "rpm", "source"},
{"bzip2-libs", "1.0.6-26.fc28", "rpm", "binary"},
{"json-c", "0.13.1-2.fc28", "rpm", "source"},
{"gdbm", "1:1.14.1-4.fc28", "rpm", "binary"},
{"pcre", "8.42-3.fc28", "rpm", "source"},
{"systemd", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"cryptsetup-libs", "2.0.4-1.fc28", "rpm", "binary"},
{"dnf", "2.7.5-12.fc28", "rpm", "source"},
{"ca-certificates", "2018.2.24-1.0.fc28", "rpm", "source"},
{"libidn2", "2.0.5-1.fc28", "rpm", "binary"},
{"libpsl", "0.20.2-2.fc28", "rpm", "binary"},
{"gdbm-libs", "1:1.14.1-4.fc28", "rpm", "binary"},
{"kmod", "25-2.fc28", "rpm", "source"},
{"libreport-filesystem", "2.9.5-1.fc28", "rpm", "binary"},
{"ima-evm-utils", "1.1-2.fc28", "rpm", "source"},
{"nghttp2", "1.32.1-1.fc28", "rpm", "source"},
{"cyrus-sasl-lib", "2.1.27-0.2rc7.fc28", "rpm", "binary"},
{"libsolv", "0.6.35-1.fc28", "rpm", "binary"},
{"cryptsetup", "2.0.4-1.fc28", "rpm", "source"},
{"filesystem", "3.8-2.fc28", "rpm", "source"},
{"libcap", "2.25-9.fc28", "rpm", "source"},
{"libpsl", "0.20.2-2.fc28", "rpm", "source"},
{"deltarpm", "3.6-25.fc28", "rpm", "source"},
{"fedora-gpg-keys", "28-5", "rpm", "binary"},
{"ima-evm-utils", "1.1-2.fc28", "rpm", "binary"},
{"nss-tools", "3.38.0-1.0.fc28", "rpm", "binary"},
{"libtasn1", "4.13-2.fc28", "rpm", "source"},
{"elfutils-libelf", "0.173-1.fc28", "rpm", "binary"},
{"device-mapper-libs", "1.02.146-5.fc28", "rpm", "binary"},
{"gobject-introspection", "1.56.1-1.fc28", "rpm", "source"},
{"publicsuffix-list", "20180514-1.fc28", "rpm", "source"},
{"libcap", "2.25-9.fc28", "rpm", "binary"},
{"librepo", "1.8.1-7.fc28", "rpm", "binary"},
{"rpm-sign-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"coreutils-single", "8.29-7.fc28", "rpm", "binary"},
{"libacl", "2.2.53-1.fc28", "rpm", "binary"},
{"popt", "1.16-14.fc28", "rpm", "source"},
{"libtasn1", "4.13-2.fc28", "rpm", "binary"},
{"gawk", "4.2.1-1.fc28", "rpm", "source"},
{"diffutils", "3.6-4.fc28", "rpm", "source"},
{"libgpg-error", "1.31-1.fc28", "rpm", "source"},
{"libdb-utils", "5.3.28-30.fc28", "rpm", "binary"},
{"python3-iniparse", "0.4-30.fc28", "rpm", "binary"},
{"acl", "2.2.53-1.fc28", "rpm", "binary"},
{"libssh", "0.8.2-1.fc28", "rpm", "source"},
{"python3-librepo", "1.8.1-7.fc28", "rpm", "binary"},
{"gobject-introspection", "1.56.1-1.fc28", "rpm", "binary"},
{"rpm", "4.14.1-9.fc28", "rpm", "source"},
{"libgcrypt", "1.8.3-1.fc28", "rpm", "source"},
{"curl", "7.59.0-6.fc28", "rpm", "source"},
{"tzdata", "2018e-1.fc28", "rpm", "source"},
{"krb5", "1.16.1-13.fc28", "rpm", "source"},
{"librepo", "1.8.1-7.fc28", "rpm", "source"},
{"python3-gpg", "1.10.0-4.fc28", "rpm", "binary"},
{"nettle", "3.4-2.fc28", "rpm", "source"},
{"libgcrypt", "1.8.3-1.fc28", "rpm", "binary"},
{"python3", "3.6.6-1.fc28", "rpm", "binary"},
{"python3-libcomps", "0.1.8-11.fc28", "rpm", "binary"},
{"rpm-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"nspr", "4.19.0-1.fc28", "rpm", "binary"},
{"argon2", "20161029-5.fc28", "rpm", "source"},
{"tar", "1.30-3.fc28", "rpm", "source"},
{"qrencode-libs", "3.4.4-5.fc28", "rpm", "binary"},
{"gmp", "6.1.2-7.fc28", "rpm", "source"},
{"libverto", "0.3.0-5.fc28", "rpm", "binary"},
{"python3", "3.6.6-1.fc28", "rpm", "source"},
{"libksba", "1.3.5-7.fc28", "rpm", "binary"},
{"readline", "7.0-11.fc28", "rpm", "binary"},
{"rpm-build-libs", "4.14.1-9.fc28", "rpm", "binary"},
{"npth", "1.5-4.fc28", "rpm", "binary"},
{"rootfiles", "8.1-22.fc28", "rpm", "binary"},
{"rpm-plugin-systemd-inhibit", "4.14.1-9.fc28", "rpm", "binary"},
{"systemd-libs", "238-9.git0e0aa59.fc28", "rpm", "binary"},
{"nss-util", "3.38.0-1.0.fc28", "rpm", "binary"},
} }
func TestRpmFeatureDetection(t *testing.T) { func TestRpmFeatureDetection(t *testing.T) {
@ -206,8 +334,10 @@ func TestRpmFeatureDetection(t *testing.T) {
"valid small case", "valid small case",
map[string]string{"var/lib/rpm/Packages": "rpm/testdata/valid"}, map[string]string{"var/lib/rpm/Packages": "rpm/testdata/valid"},
[]database.Feature{ []database.Feature{
{"centos-release", "7-1.1503.el7.centos.2.8", "centos-release", "7-1.1503.el7.centos.2.8", rpm.ParserName}, {"centos-release", "7-1.1503.el7.centos.2.8", "rpm", "binary"},
{"filesystem", "3.2-18.el7", "filesystem", "3.2-18.el7", rpm.ParserName}, {"filesystem", "3.2-18.el7", "rpm", "binary"},
{"centos-release", "7-1.1503.el7.centos.2.8", "rpm", "source"},
{"filesystem", "3.2-18.el7", "rpm", "source"},
}, },
}, },
{ {
@ -248,15 +378,14 @@ func TestParseSourceRPM(t *testing.T) {
// actual expected: name="lua", version="5.3.4", release="10.fc-28" // actual expected: name="lua", version="5.3.4", release="10.fc-28"
{"lua-5.3.4-10.fc-28.src.rpm", "lua-5.3.4", "10.fc-28", ""}, {"lua-5.3.4-10.fc-28.src.rpm", "lua-5.3.4", "10.fc-28", ""},
} { } {
pkg := database.Feature{} name, version, release, _, err := parseSourceRPM(test.sourceRPM)
err := parseSourceRPM(test.sourceRPM, &pkg)
if test.expectedErr != "" { if test.expectedErr != "" {
require.EqualError(t, err, test.expectedErr) require.EqualError(t, err, test.expectedErr)
continue continue
} }
require.Nil(t, err) require.Nil(t, err)
require.Equal(t, test.expectedName, pkg.SourceName) require.Equal(t, test.expectedName, name)
require.Equal(t, test.expectedVersion, pkg.SourceVersion) require.Equal(t, test.expectedVersion, version+"-"+release)
} }
} }

@ -40,7 +40,7 @@ const (
nvdURLPrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" nvdURLPrefix = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
// affected type indicates if the affected feature hint is for binary or // affected type indicates if the affected feature hint is for binary or
// source package. // source package.
affectedType = database.AffectBinaryPackage affectedType = database.BinaryPackage
) )
func init() { func init() {
@ -177,7 +177,7 @@ func (file *secDB) Vulnerabilities() (vulns []database.VulnerabilityWithAffected
vuln.Affected = []database.AffectedFeature{ vuln.Affected = []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
FeatureName: pkg.Pkg.Name, FeatureName: pkg.Pkg.Name,
AffectedVersion: version, AffectedVersion: version,
FixedInVersion: fixedInVersion, FixedInVersion: fixedInVersion,

@ -38,7 +38,7 @@ const (
url = "https://security-tracker.debian.org/tracker/data/json" url = "https://security-tracker.debian.org/tracker/data/json"
cveURLPrefix = "https://security-tracker.debian.org/tracker" cveURLPrefix = "https://security-tracker.debian.org/tracker"
updaterFlag = "debianUpdater" updaterFlag = "debianUpdater"
affectedType = database.AffectSourcePackage affectedType = database.SourcePackage
) )
type jsonData map[string]map[string]jsonVuln type jsonData map[string]map[string]jsonVuln
@ -215,7 +215,7 @@ func parseDebianJSON(data *jsonData) (vulnerabilities []database.VulnerabilityWi
// Create and add the feature version. // Create and add the feature version.
pkg := database.AffectedFeature{ pkg := database.AffectedFeature{
AffectedType: affectedType, FeatureType: affectedType,
FeatureName: pkgName, FeatureName: pkgName,
AffectedVersion: version, AffectedVersion: version,
FixedInVersion: fixedInVersion, FixedInVersion: fixedInVersion,

@ -41,7 +41,7 @@ func TestDebianParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:8", Name: "debian:8",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -50,7 +50,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: versionfmt.MaxVersion, AffectedVersion: versionfmt.MaxVersion,
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:unstable", Name: "debian:unstable",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -71,7 +71,7 @@ func TestDebianParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:8", Name: "debian:8",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -81,7 +81,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: "0.7.0", AffectedVersion: "0.7.0",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:unstable", Name: "debian:unstable",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -91,7 +91,7 @@ func TestDebianParser(t *testing.T) {
AffectedVersion: "0.7.0", AffectedVersion: "0.7.0",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "debian:8", Name: "debian:8",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,

@ -41,7 +41,7 @@ const (
ovalURI = "https://linux.oracle.com/oval/" ovalURI = "https://linux.oracle.com/oval/"
elsaFilePrefix = "com.oracle.elsa-" elsaFilePrefix = "com.oracle.elsa-"
updaterFlag = "oracleUpdater" updaterFlag = "oracleUpdater"
affectedType = database.AffectBinaryPackage affectedType = database.BinaryPackage
) )
var ( var (
@ -365,7 +365,7 @@ func toFeatures(criteria criteria) []database.AffectedFeature {
} else if strings.Contains(c.Comment, " is earlier than ") { } else if strings.Contains(c.Comment, " is earlier than ") {
const prefixLen = len(" is earlier than ") const prefixLen = len(" is earlier than ")
featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")]) featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
featureVersion.AffectedType = affectedType featureVersion.FeatureType = affectedType
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:] version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
err := versionfmt.Valid(rpm.ParserName, version) err := versionfmt.Valid(rpm.ParserName, version)
if err != nil { if err != nil {

@ -43,7 +43,7 @@ func TestOracleParserOneCve(t *testing.T) {
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "oracle:7", Name: "oracle:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -53,7 +53,7 @@ func TestOracleParserOneCve(t *testing.T) {
AffectedVersion: "0:3.1.1-7.el7_1", AffectedVersion: "0:3.1.1-7.el7_1",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "oracle:7", Name: "oracle:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -63,7 +63,7 @@ func TestOracleParserOneCve(t *testing.T) {
AffectedVersion: "0:3.1.1-7.el7_1", AffectedVersion: "0:3.1.1-7.el7_1",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "oracle:7", Name: "oracle:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,

@ -43,7 +43,7 @@ const (
ovalURI = "https://www.redhat.com/security/data/oval/" ovalURI = "https://www.redhat.com/security/data/oval/"
rhsaFilePrefix = "com.redhat.rhsa-" rhsaFilePrefix = "com.redhat.rhsa-"
updaterFlag = "rhelUpdater" updaterFlag = "rhelUpdater"
affectedType = database.AffectBinaryPackage affectedType = database.BinaryPackage
) )
var ( var (
@ -333,7 +333,7 @@ func toFeatures(criteria criteria) []database.AffectedFeature {
} else if strings.Contains(c.Comment, " is earlier than ") { } else if strings.Contains(c.Comment, " is earlier than ") {
const prefixLen = len(" is earlier than ") const prefixLen = len(" is earlier than ")
featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")]) featureVersion.FeatureName = strings.TrimSpace(c.Comment[:strings.Index(c.Comment, " is earlier than ")])
featureVersion.AffectedType = affectedType featureVersion.FeatureType = affectedType
version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:] version := c.Comment[strings.Index(c.Comment, " is earlier than ")+prefixLen:]
err := versionfmt.Valid(rpm.ParserName, version) err := versionfmt.Valid(rpm.ParserName, version)
if err != nil { if err != nil {

@ -46,7 +46,7 @@ func TestRHELParserMultipleCVE(t *testing.T) {
database.MediumSeverity, database.MediumSeverity} database.MediumSeverity, database.MediumSeverity}
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "centos:6", Name: "centos:6",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -56,7 +56,7 @@ func TestRHELParserMultipleCVE(t *testing.T) {
AffectedVersion: "0:38.1.0-1.el6_6", AffectedVersion: "0:38.1.0-1.el6_6",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "centos:7", Name: "centos:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -96,7 +96,7 @@ func TestRHELParserOneCVE(t *testing.T) {
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "centos:7", Name: "centos:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -106,7 +106,7 @@ func TestRHELParserOneCVE(t *testing.T) {
FixedInVersion: "0:3.1.1-7.el7_1", FixedInVersion: "0:3.1.1-7.el7_1",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "centos:7", Name: "centos:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,
@ -116,7 +116,7 @@ func TestRHELParserOneCVE(t *testing.T) {
FixedInVersion: "0:3.1.1-7.el7_1", FixedInVersion: "0:3.1.1-7.el7_1",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "centos:7", Name: "centos:7",
VersionFormat: rpm.ParserName, VersionFormat: rpm.ParserName,

@ -39,7 +39,7 @@ const (
trackerURI = "https://git.launchpad.net/ubuntu-cve-tracker" trackerURI = "https://git.launchpad.net/ubuntu-cve-tracker"
updaterFlag = "ubuntuUpdater" updaterFlag = "ubuntuUpdater"
cveURL = "http://people.ubuntu.com/~ubuntu-security/cve/%s" cveURL = "http://people.ubuntu.com/~ubuntu-security/cve/%s"
affectedType = database.AffectSourcePackage affectedType = database.SourcePackage
) )
var ( var (
@ -335,7 +335,7 @@ func parseUbuntuCVE(fileContent io.Reader) (vulnerability database.Vulnerability
// Create and add the new package. // Create and add the new package.
featureVersion := database.AffectedFeature{ featureVersion := database.AffectedFeature{
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: releaseName, Name: releaseName,
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,

@ -46,7 +46,7 @@ func TestUbuntuParser(t *testing.T) {
expectedFeatures := []database.AffectedFeature{ expectedFeatures := []database.AffectedFeature{
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "ubuntu:14.04", Name: "ubuntu:14.04",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -55,7 +55,7 @@ func TestUbuntuParser(t *testing.T) {
AffectedVersion: versionfmt.MaxVersion, AffectedVersion: versionfmt.MaxVersion,
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "ubuntu:15.04", Name: "ubuntu:15.04",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,
@ -65,7 +65,7 @@ func TestUbuntuParser(t *testing.T) {
AffectedVersion: "0.4-3", AffectedVersion: "0.4-3",
}, },
{ {
AffectedType: affectedType, FeatureType: affectedType,
Namespace: database.Namespace{ Namespace: database.Namespace{
Name: "ubuntu:15.10", Name: "ubuntu:15.10",
VersionFormat: dpkg.ParserName, VersionFormat: dpkg.ParserName,

@ -499,7 +499,7 @@ func doVulnerabilitiesNamespacing(vulnerabilities []database.VulnerabilityWithAf
for _, fv := range namespacedFeatures { for _, fv := range namespacedFeatures {
// validate vulnerabilities, throw out the invalid vulnerabilities // validate vulnerabilities, throw out the invalid vulnerabilities
if fv.AffectedType == "" || fv.AffectedVersion == "" || fv.FeatureName == "" || fv.Namespace.Name == "" || fv.Namespace.VersionFormat == "" { if fv.FeatureType == "" || fv.AffectedVersion == "" || fv.FeatureName == "" || fv.Namespace.Name == "" || fv.Namespace.VersionFormat == "" {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"Name": fv.FeatureName, "Name": fv.FeatureName,
"Affected Version": fv.AffectedVersion, "Affected Version": fv.AffectedVersion,

@ -183,7 +183,7 @@ func newmockUpdaterDatastore() *mockUpdaterDatastore {
func TestDoVulnerabilitiesNamespacing(t *testing.T) { func TestDoVulnerabilitiesNamespacing(t *testing.T) {
fv1 := database.AffectedFeature{ fv1 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage, FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace1"}, Namespace: database.Namespace{Name: "Namespace1"},
FeatureName: "Feature1", FeatureName: "Feature1",
FixedInVersion: "0.1", FixedInVersion: "0.1",
@ -191,7 +191,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
} }
fv2 := database.AffectedFeature{ fv2 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage, FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace2"}, Namespace: database.Namespace{Name: "Namespace2"},
FeatureName: "Feature1", FeatureName: "Feature1",
FixedInVersion: "0.2", FixedInVersion: "0.2",
@ -199,7 +199,7 @@ func TestDoVulnerabilitiesNamespacing(t *testing.T) {
} }
fv3 := database.AffectedFeature{ fv3 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage, FeatureType: database.SourcePackage,
Namespace: database.Namespace{Name: "Namespace2"}, Namespace: database.Namespace{Name: "Namespace2"},
FeatureName: "Feature2", FeatureName: "Feature2",
FixedInVersion: "0.3", FixedInVersion: "0.3",
@ -237,9 +237,9 @@ func TestCreatVulnerabilityNotification(t *testing.T) {
VersionFormat: vf1, VersionFormat: vf1,
} }
af1 := database.AffectedFeature{ af1 := database.AffectedFeature{
AffectedType: database.AffectSourcePackage, FeatureType: database.SourcePackage,
Namespace: ns1, Namespace: ns1,
FeatureName: "feature 1", FeatureName: "feature 1",
} }
v1 := database.VulnerabilityWithAffected{ v1 := database.VulnerabilityWithAffected{

@ -284,11 +284,15 @@ func TestProcessAncestryWithDistUpgrade(t *testing.T) {
{Name: "db", Version: "5.1.29-5"}, {Name: "db", Version: "5.1.29-5"},
{Name: "ustr", Version: "1.0.4-3"}, {Name: "ustr", Version: "1.0.4-3"},
{Name: "xz-utils", Version: "5.1.1alpha+20120614-2"}, {Name: "xz-utils", Version: "5.1.1alpha+20120614-2"},
{Name: "libdb5.1", Version: "5.1.29-5"},
} }
nonUpgradedMap := map[database.Feature]struct{}{} nonUpgradedMap := map[database.Feature]struct{}{}
for _, f := range nonUpgradedFeatures { for _, f := range nonUpgradedFeatures {
f.VersionFormat = "dpkg" f.VersionFormat = "dpkg"
f.Type = database.SourcePackage
nonUpgradedMap[f] = struct{}{}
f.Type = database.BinaryPackage
nonUpgradedMap[f] = struct{}{} nonUpgradedMap[f] = struct{}{}
} }
@ -318,12 +322,12 @@ func TestProcessAncestryWithDistUpgrade(t *testing.T) {
features = append(features, l.Features...) features = append(features, l.Features...)
} }
assert.Len(t, features, 74) assert.Len(t, features, 161)
for _, f := range features { for _, f := range features {
if _, ok := nonUpgradedMap[f.Feature]; ok { if _, ok := nonUpgradedMap[f.Feature]; ok {
assert.Equal(t, "debian:7", f.Namespace.Name) assert.Equal(t, "debian:7", f.Namespace.Name, "%#v", f)
} else { } else {
assert.Equal(t, "debian:8", f.Namespace.Name) assert.Equal(t, "debian:8", f.Namespace.Name, "#%v", f)
} }
} }
} }
@ -352,8 +356,8 @@ func TestProcessLayers(t *testing.T) {
assert.Len(t, LayerWithContents[1].Namespaces, 1) assert.Len(t, LayerWithContents[1].Namespaces, 1)
assert.Len(t, LayerWithContents[2].Namespaces, 1) assert.Len(t, LayerWithContents[2].Namespaces, 1)
assert.Len(t, LayerWithContents[0].Features, 0) assert.Len(t, LayerWithContents[0].Features, 0)
assert.Len(t, LayerWithContents[1].Features, 52) assert.Len(t, LayerWithContents[1].Features, 132)
assert.Len(t, LayerWithContents[2].Features, 74) assert.Len(t, LayerWithContents[2].Features, 191)
// Ensure each layer has expected namespaces and features detected // Ensure each layer has expected namespaces and features detected
if blank, ok := datastore.layers["blank"]; ok { if blank, ok := datastore.layers["blank"]; ok {
@ -371,7 +375,7 @@ func TestProcessLayers(t *testing.T) {
{database.Namespace{"debian:7", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")}, {database.Namespace{"debian:7", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")},
}, wheezy.Namespaces) }, wheezy.Namespaces)
assert.Len(t, wheezy.Features, 52) assert.Len(t, wheezy.Features, 132)
} else { } else {
assert.Fail(t, "wheezy is not stored") assert.Fail(t, "wheezy is not stored")
return return
@ -382,7 +386,7 @@ func TestProcessLayers(t *testing.T) {
assert.Equal(t, []database.LayerNamespace{ assert.Equal(t, []database.LayerNamespace{
{database.Namespace{"debian:8", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")}, {database.Namespace{"debian:8", dpkg.ParserName}, database.NewNamespaceDetector("os-release", "1.0")},
}, jessie.Namespaces) }, jessie.Namespaces)
assert.Len(t, jessie.Features, 74) assert.Len(t, jessie.Features, 191)
} else { } else {
assert.Fail(t, "jessie is not stored") assert.Fail(t, "jessie is not stored")
return return

Loading…
Cancel
Save