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