github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/redhat/cataloger_test.go (about)

     1  package redhat
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"github.com/google/go-cmp/cmp/cmpopts"
     8  	"github.com/stretchr/testify/assert"
     9  	_ "modernc.org/sqlite"
    10  
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/pkg"
    14  	"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    15  )
    16  
    17  func Test_DBCataloger(t *testing.T) {
    18  
    19  	dbLocation := file.NewLocation("/var/lib/rpm/rpmdb.sqlite")
    20  	locations := file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
    21  
    22  	basePkg := pkg.Package{
    23  		Name:      "basesystem",
    24  		Version:   "11-13.el9",
    25  		Type:      pkg.RpmPkg,
    26  		Locations: locations,
    27  		Licenses:  pkg.NewLicenseSet(pkg.NewLicenseFromLocations("Public Domain", dbLocation)),
    28  		FoundBy:   "rpm-db-cataloger",
    29  		PURL:      "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
    30  		Metadata: pkg.RpmDBEntry{
    31  			Name:      "basesystem",
    32  			Version:   "11",
    33  			Arch:      "noarch",
    34  			Release:   "13.el9",
    35  			SourceRpm: "basesystem-11-13.el9.src.rpm",
    36  			Size:      0,
    37  			Vendor:    "Rocky Enterprise Software Foundation",
    38  			Provides:  []string{"basesystem"},
    39  			Requires: []string{
    40  				"filesystem",
    41  				"rpmlib(CompressedFileNames)",
    42  				"rpmlib(FileDigests)",
    43  				"rpmlib(PayloadFilesHavePrefix)",
    44  				"rpmlib(PayloadIsZstd)",
    45  				"setup",
    46  			},
    47  			ModularityLabel: strRef(""),
    48  		},
    49  	}
    50  	basePkg.SetID()
    51  
    52  	bashPkg := pkg.Package{
    53  		Name:      "bash",
    54  		Version:   "5.1.8-6.el9_1",
    55  		Type:      pkg.RpmPkg,
    56  		Locations: locations,
    57  		Licenses:  pkg.NewLicenseSet(pkg.NewLicenseFromLocations("GPLv3+", dbLocation)),
    58  		FoundBy:   "rpm-db-cataloger",
    59  		PURL:      "pkg:rpm/bash@5.1.8-6.el9_1?arch=x86_64&upstream=bash-5.1.8-6.el9_1.src.rpm",
    60  		Metadata: pkg.RpmDBEntry{
    61  			Name:            "bash",
    62  			Version:         "5.1.8",
    63  			Arch:            "x86_64",
    64  			Release:         "6.el9_1",
    65  			SourceRpm:       "bash-5.1.8-6.el9_1.src.rpm",
    66  			Size:            7738634,
    67  			ModularityLabel: strRef(""),
    68  			Vendor:          "Rocky Enterprise Software Foundation",
    69  			Provides: []string{
    70  				"/bin/bash",
    71  				"/bin/sh",
    72  				"bash",
    73  				"bash(x86-64)",
    74  				"config(bash)",
    75  			},
    76  			Requires: []string{
    77  				"/usr/bin/sh",
    78  				"config(bash)",
    79  				"filesystem",
    80  				"libc.so.6()(64bit)",
    81  				"libc.so.6(GLIBC_2.11)(64bit)",
    82  				"libc.so.6(GLIBC_2.14)(64bit)",
    83  				"libc.so.6(GLIBC_2.15)(64bit)",
    84  				"libc.so.6(GLIBC_2.2.5)(64bit)",
    85  				"libc.so.6(GLIBC_2.25)(64bit)",
    86  				"libc.so.6(GLIBC_2.3)(64bit)",
    87  				"libc.so.6(GLIBC_2.3.4)(64bit)",
    88  				"libc.so.6(GLIBC_2.33)(64bit)",
    89  				"libc.so.6(GLIBC_2.34)(64bit)",
    90  				"libc.so.6(GLIBC_2.4)(64bit)",
    91  				"libc.so.6(GLIBC_2.8)(64bit)",
    92  				"libtinfo.so.6()(64bit)",
    93  				"rpmlib(BuiltinLuaScripts)",
    94  				"rpmlib(CompressedFileNames)",
    95  				"rpmlib(FileDigests)",
    96  				"rpmlib(PayloadFilesHavePrefix)",
    97  				"rpmlib(PayloadIsZstd)",
    98  				"rtld(GNU_HASH)",
    99  			},
   100  		},
   101  	}
   102  	bashPkg.SetID()
   103  
   104  	filesystemPkg := pkg.Package{
   105  		Name:      "filesystem",
   106  		Version:   "3.16-2.el9",
   107  		Type:      pkg.RpmPkg,
   108  		Locations: locations,
   109  		Licenses:  pkg.NewLicenseSet(pkg.NewLicenseFromLocations("Public Domain", dbLocation)),
   110  		FoundBy:   "rpm-db-cataloger",
   111  		PURL:      "pkg:rpm/filesystem@3.16-2.el9?arch=x86_64&upstream=filesystem-3.16-2.el9.src.rpm",
   112  		Metadata: pkg.RpmDBEntry{
   113  			Name:            "filesystem",
   114  			Version:         "3.16",
   115  			Arch:            "x86_64",
   116  			Release:         "2.el9",
   117  			SourceRpm:       "filesystem-3.16-2.el9.src.rpm",
   118  			Size:            106,
   119  			ModularityLabel: strRef(""),
   120  			Vendor:          "Rocky Enterprise Software Foundation",
   121  			Provides: []string{
   122  				"filesystem",
   123  				"filesystem(x86-64)",
   124  				"filesystem-afs",
   125  			},
   126  			Requires: []string{
   127  				"/bin/sh",
   128  				"rpmlib(BuiltinLuaScripts)",
   129  				"rpmlib(CompressedFileNames)",
   130  				"rpmlib(FileDigests)",
   131  				"rpmlib(PayloadFilesHavePrefix)",
   132  				"rpmlib(PayloadIsZstd)",
   133  				"setup",
   134  			},
   135  		},
   136  	}
   137  	filesystemPkg.SetID()
   138  
   139  	expectedPackages := []pkg.Package{basePkg, bashPkg, filesystemPkg}
   140  
   141  	// Note that you'll see a cycle:
   142  	//   bash --(requires)--> filesystem
   143  	//   filesystem --(requires)--> bash
   144  	//
   145  	// This is not a bug!
   146  	//
   147  	// [root@c1a4773e8a8d /]# dnf repoquery --requires --resolve filesystem
   148  	//   bash-0:5.1.8-9.el9.aarch64
   149  	//   setup-0:2.13.7-10.el9.noarch
   150  	//
   151  	//[root@c1a4773e8a8d /]# dnf repoquery --requires --resolve bash
   152  	//  filesystem-0:3.16-2.el9.aarch64
   153  	//  glibc-0:2.34-100.el9.aarch64
   154  	//  ncurses-libs-0:6.2-10.20210508.el9.aarch64
   155  
   156  	expectedRelationships := []artifact.Relationship{
   157  		// though this is expressible in the RPM DB (package depends on itself), we do not allow for it in the SBOM
   158  		//{
   159  		//	From: bashPkg,
   160  		//	To:   bashPkg,
   161  		//	Type: artifact.DependencyOfRelationship,
   162  		//},
   163  		{
   164  			From: bashPkg,
   165  			To:   filesystemPkg,
   166  			Type: artifact.DependencyOfRelationship,
   167  		},
   168  		{
   169  			From: filesystemPkg,
   170  			To:   basePkg,
   171  			Type: artifact.DependencyOfRelationship,
   172  		},
   173  		{
   174  			From: filesystemPkg,
   175  			To:   bashPkg,
   176  			Type: artifact.DependencyOfRelationship,
   177  		},
   178  	}
   179  
   180  	pkgtest.NewCatalogTester().
   181  		WithImageResolver(t, "image-minimal").
   182  		IgnoreLocationLayer().                                               // this fixture can be rebuilt, thus the layer ID will change
   183  		WithCompareOptions(cmpopts.IgnoreFields(pkg.RpmDBEntry{}, "Files")). // this is rather long... ano not the point of the test
   184  		Expects(expectedPackages, expectedRelationships).
   185  		TestCataloger(t, NewDBCataloger())
   186  
   187  }
   188  
   189  func Test_DBCataloger_Globs(t *testing.T) {
   190  	tests := []struct {
   191  		name     string
   192  		fixture  string
   193  		expected []string
   194  	}{
   195  		{
   196  			name:    "obtain DB files",
   197  			fixture: "test-fixtures/glob-paths",
   198  			expected: []string{
   199  				"usr/share/rpm/Packages",
   200  				"usr/share/rpm/Packages.db",
   201  				"usr/share/rpm/rpmdb.sqlite",
   202  				"var/lib/rpm/Packages",
   203  				"var/lib/rpm/Packages.db",
   204  				"var/lib/rpm/rpmdb.sqlite",
   205  				"var/lib/rpmmanifest/container-manifest-2",
   206  				"usr/lib/sysimage/rpm/Packages",
   207  				"usr/lib/sysimage/rpm/Packages.db",
   208  				"usr/lib/sysimage/rpm/rpmdb.sqlite",
   209  			},
   210  		},
   211  	}
   212  
   213  	for _, test := range tests {
   214  		t.Run(test.name, func(t *testing.T) {
   215  			pkgtest.NewCatalogTester().
   216  				FromDirectory(t, test.fixture).
   217  				ExpectsResolverContentQueries(test.expected).
   218  				TestCataloger(t, NewDBCataloger())
   219  		})
   220  	}
   221  }
   222  
   223  func Test_RPMFileCataloger_Globs(t *testing.T) {
   224  	tests := []struct {
   225  		name     string
   226  		fixture  string
   227  		expected []string
   228  	}{
   229  		{
   230  			name:    "obtain rpm files",
   231  			fixture: "test-fixtures/glob-paths",
   232  			expected: []string{
   233  				"dive-0.10.0.rpm",
   234  			},
   235  		},
   236  	}
   237  
   238  	for _, test := range tests {
   239  		t.Run(test.name, func(t *testing.T) {
   240  			pkgtest.NewCatalogTester().
   241  				FromDirectory(t, test.fixture).
   242  				ExpectsResolverContentQueries(test.expected).
   243  				TestCataloger(t, NewArchiveCataloger())
   244  		})
   245  	}
   246  }
   247  
   248  func Test_denySelfReferences(t *testing.T) {
   249  
   250  	a := pkg.Package{
   251  		Name: "a",
   252  	}
   253  	a.SetID()
   254  	b := pkg.Package{
   255  		Name: "b",
   256  	}
   257  	b.SetID()
   258  	c := pkg.Package{
   259  		Name: "c",
   260  	}
   261  	c.SetID()
   262  
   263  	pkgs := []pkg.Package{a, b, c}
   264  
   265  	tests := []struct {
   266  		name              string
   267  		pkgs              []pkg.Package
   268  		rels              []artifact.Relationship
   269  		err               error
   270  		wantPkgs          int
   271  		wantRelationships int
   272  		wantErr           assert.ErrorAssertionFunc
   273  	}{
   274  		{
   275  			name: "no self references",
   276  			pkgs: pkgs,
   277  			rels: []artifact.Relationship{
   278  				{
   279  					From: a,
   280  					To:   b,
   281  					Type: artifact.DependencyOfRelationship,
   282  				},
   283  			},
   284  			wantPkgs:          3,
   285  			wantRelationships: 1,
   286  			wantErr:           assert.NoError,
   287  		},
   288  		{
   289  			name: "remove self references",
   290  			pkgs: pkgs,
   291  			rels: []artifact.Relationship{
   292  				{
   293  					From: a,
   294  					To:   a,
   295  					Type: artifact.DependencyOfRelationship,
   296  				},
   297  				{
   298  					From: a,
   299  					To:   b,
   300  					Type: artifact.DependencyOfRelationship,
   301  				},
   302  			},
   303  			wantPkgs:          3,
   304  			wantRelationships: 1,
   305  			wantErr:           assert.NoError,
   306  		},
   307  		{
   308  			name: "preserve errors",
   309  			pkgs: pkgs,
   310  			rels: []artifact.Relationship{
   311  				{
   312  					From: a,
   313  					To:   b,
   314  					Type: artifact.DependencyOfRelationship,
   315  				},
   316  			},
   317  			err:               errors.New("stop me!"),
   318  			wantPkgs:          3,
   319  			wantRelationships: 1,
   320  			wantErr:           assert.Error,
   321  		},
   322  	}
   323  	for _, tt := range tests {
   324  		t.Run(tt.name, func(t *testing.T) {
   325  			if tt.wantErr == nil {
   326  				tt.wantErr = assert.NoError
   327  			}
   328  
   329  			gotPkgs, gotRels, err := denySelfReferences(tt.pkgs, tt.rels, tt.err)
   330  
   331  			tt.wantErr(t, err)
   332  			assert.Len(t, gotPkgs, tt.wantPkgs)
   333  			assert.Len(t, gotRels, tt.wantRelationships)
   334  		})
   335  	}
   336  }