Merge pull request #672 from KeyboardNerd/source_package/feature_type
Implement Feature types
This commit is contained in:
commit
73bc2bc36b
@ -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 ¬i, nil
|
return ¬i, 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}
|
||||||
|
}
|
52
database/feature_type.go
Normal file
52
database/feature_type.go
Normal file
@ -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,8 +219,8 @@ 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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
fmt.Printf("%d features, %d vulnerabilities are generated", len(nsFeatures), len(vulnerabilities))
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(2)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
tx, err := store.Begin()
|
tx, err := store.Begin()
|
||||||
if !assert.Nil(t, err) {
|
require.Nil(t, err)
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
|
require.Nil(t, tx.PersistNamespacedFeatures(nsFeatures))
|
||||||
fmt.Println("finished to insert namespaced features")
|
require.Nil(t, tx.Commit())
|
||||||
|
|
||||||
tx.Commit()
|
tx, err = store.Begin()
|
||||||
}()
|
require.Nil(t, tx.Commit())
|
||||||
|
|
||||||
go func() {
|
require.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
|
||||||
defer wg.Done()
|
require.Nil(t, tx.Commit())
|
||||||
tx, err := store.Begin()
|
|
||||||
if !assert.Nil(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Nil(t, tx.InsertVulnerabilities(vulnerabilities))
|
tx, err = store.Begin()
|
||||||
fmt.Println("finished to insert vulnerabilities")
|
require.Nil(t, err)
|
||||||
tx.Commit()
|
|
||||||
|
|
||||||
}()
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
tx, err := store.Begin()
|
|
||||||
if !assert.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
53
database/pgsql/feature_type.go
Normal file
53
database/pgsql/feature_type.go
Normal file
@ -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
|
||||||
|
}
|
38
database/pgsql/feature_type_test.go
Normal file
38
database/pgsql/feature_type_test.go
Normal file
@ -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;`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
60
database/pgsql/migrations_test.go
Normal file
60
database/pgsql/migrations_test.go
Normal file
@ -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 {
|
||||||
|
17
database/pgsql/testdata/data.sql
vendored
17
database/pgsql/testdata/data.sql
vendored
@ -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,45 +37,59 @@ var (
|
|||||||
|
|
||||||
type lister struct{}
|
type lister struct{}
|
||||||
|
|
||||||
|
func (l lister) RequiredFilenames() []string {
|
||||||
|
return []string{"var/lib/dpkg/status"}
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
featurefmt.RegisterLister("dpkg", "1.0", &lister{})
|
featurefmt.RegisterLister("dpkg", "1.0", &lister{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func valid(pkg *database.Feature) bool {
|
|
||||||
return pkg.Name != "" && pkg.Version != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func addSourcePackage(pkg *database.Feature) {
|
|
||||||
if pkg.SourceName == "" {
|
|
||||||
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) {
|
||||||
f, hasFile := files["var/lib/dpkg/status"]
|
f, hasFile := files["var/lib/dpkg/status"]
|
||||||
if !hasFile {
|
if !hasFile {
|
||||||
return []database.Feature{}, nil
|
return []database.Feature{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
packages := mapset.NewSet()
|
||||||
pkg = database.Feature{VersionFormat: dpkg.ParserName}
|
|
||||||
pkgs = mapset.NewSet()
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(strings.NewReader(string(f)))
|
scanner := bufio.NewScanner(strings.NewReader(string(f)))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := strings.TrimSpace(scanner.Text())
|
||||||
if strings.HasPrefix(line, "Package: ") {
|
if line == "" {
|
||||||
// Package line
|
continue
|
||||||
// Defines the name of the package
|
}
|
||||||
|
|
||||||
pkg.Name = strings.TrimSpace(strings.TrimPrefix(line, "Package: "))
|
binary, source := parseDpkgDB(scanner)
|
||||||
pkg.Version = ""
|
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 (
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
sourceName string
|
||||||
|
sourceVersion string
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "Package: ") {
|
||||||
|
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")
|
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
pkg.Version = version
|
binaryPackage = &database.Feature{name, version, dpkg.ParserName, database.BinaryPackage}
|
||||||
}
|
|
||||||
} else if line == "" {
|
|
||||||
pkg = database.Feature{VersionFormat: dpkg.ParserName}
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid(&pkg) {
|
|
||||||
addSourcePackage(&pkg)
|
|
||||||
pkgs.Add(pkg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return database.ConvertFeatureSetToFeatures(pkgs), nil
|
// Source version and names are computed from binary package names and versions
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lister) RequiredFilenames() []string {
|
if sourceVersion == "" {
|
||||||
return []string{"var/lib/dpkg/status"}
|
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}
|
|
||||||
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 {
|
|
||||||
log.WithError(err).WithField("sourcerpm", line[2]).Warning("skipped unparseable package")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if valid(&pkg) {
|
|
||||||
packages.Add(pkg)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return database.ConvertFeatureSetToFeatures(packages), nil
|
return database.ConvertFeatureSetToFeatures(packages), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l lister) RequiredFilenames() []string {
|
func parseRPMOutput(raw string) (rpmPackage *database.Feature, srpmPackage *database.Feature) {
|
||||||
return []string{"var/lib/rpm/Packages"}
|
line := strings.Split(raw, " ")
|
||||||
|
if len(line) != 3 {
|
||||||
|
// We may see warnings on some RPM versions:
|
||||||
|
// "warning: Generating 12 missing index(es), please wait..."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isIgnored(line[0]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
rpmPackage = &database.Feature{name, version, rpm.ParserName, database.BinaryPackage}
|
||||||
|
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
|
name = strutil.Substring(sourceRPM, 0, previousCheckPoint)
|
||||||
if err := versionfmt.Valid(rpm.ParserName, concatVersion); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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,7 +237,7 @@ 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",
|
||||||
}
|
}
|
||||||
|
@ -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…
Reference in New Issue
Block a user