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  }