github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/redhat/package.go (about) 1 package redhat 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/anchore/packageurl-go" 9 "github.com/anchore/syft/syft/file" 10 "github.com/anchore/syft/syft/linux" 11 "github.com/anchore/syft/syft/pkg" 12 ) 13 14 func newDBPackage(dbOrRpmLocation file.Location, m pkg.RpmDBEntry, distro *linux.Release, licenses []string) pkg.Package { 15 p := pkg.Package{ 16 Name: m.Name, 17 Version: toELVersion(m.Epoch, m.Version, m.Release), 18 Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbOrRpmLocation, licenses...)...), 19 PURL: packageURL(m.Name, m.Arch, m.Epoch, m.SourceRpm, m.Version, m.Release, distro), 20 Locations: file.NewLocationSet(dbOrRpmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 21 Type: pkg.RpmPkg, 22 Metadata: m, 23 } 24 25 p.SetID() 26 return p 27 } 28 29 func newArchivePackage(archiveLocation file.Location, m pkg.RpmArchive, licenses []string) pkg.Package { 30 p := pkg.Package{ 31 Name: m.Name, 32 Version: toELVersion(m.Epoch, m.Version, m.Release), 33 Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(archiveLocation, licenses...)...), 34 PURL: packageURL(m.Name, m.Arch, m.Epoch, m.SourceRpm, m.Version, m.Release, nil), 35 Locations: file.NewLocationSet(archiveLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 36 Type: pkg.RpmPkg, 37 Metadata: m, 38 } 39 40 p.SetID() 41 return p 42 } 43 44 // newMetadataFromManifestLine parses an entry in an RPM manifest file as used in Mariner distroless containers. 45 // Each line is the output from: 46 // - rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n" 47 // - https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150 48 func newMetadataFromManifestLine(entry string) (*pkg.RpmDBEntry, error) { 49 parts := strings.Split(entry, "\t") 50 if len(parts) < 10 { 51 return nil, fmt.Errorf("unexpected number of fields in line: %s", entry) 52 } 53 54 versionParts := strings.Split(parts[1], "-") 55 if len(versionParts) != 2 { 56 return nil, fmt.Errorf("unexpected version field: %s", parts[1]) 57 } 58 version := versionParts[0] 59 release := versionParts[1] 60 61 converted, err := strconv.Atoi(parts[8]) 62 var epoch *int 63 if err != nil || parts[5] == "(none)" { 64 epoch = nil 65 } else { 66 epoch = &converted 67 } 68 69 converted, err = strconv.Atoi(parts[6]) 70 var size int 71 if err == nil { 72 size = converted 73 } 74 return &pkg.RpmDBEntry{ 75 Name: parts[0], 76 Version: version, 77 Epoch: epoch, 78 Arch: parts[7], 79 Release: release, 80 SourceRpm: parts[9], 81 Vendor: parts[4], 82 Size: size, 83 }, nil 84 } 85 86 // packageURL returns the PURL for the specific RHEL package (see https://github.com/package-url/purl-spec) 87 func packageURL(name, arch string, epoch *int, srpm string, version, release string, distro *linux.Release) string { 88 var namespace string 89 if distro != nil { 90 namespace = distro.ID 91 } 92 93 qualifiers := map[string]string{} 94 95 if arch != "" { 96 qualifiers[pkg.PURLQualifierArch] = arch 97 } 98 99 if epoch != nil { 100 qualifiers[pkg.PURLQualifierEpoch] = strconv.Itoa(*epoch) 101 } 102 103 if srpm != "" { 104 qualifiers[pkg.PURLQualifierUpstream] = srpm 105 } 106 107 return packageurl.NewPackageURL( 108 packageurl.TypeRPM, 109 namespace, 110 name, 111 // for purl the epoch is a qualifier, not part of the version 112 // see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst under the RPM section 113 fmt.Sprintf("%s-%s", version, release), 114 pkg.PURLQualifiers( 115 qualifiers, 116 distro, 117 ), 118 "", 119 ).ToString() 120 }