github.com/quay/claircore@v1.5.28/test/rpmtest/manifest.go (about) 1 package rpmtest 2 3 import ( 4 "encoding/json" 5 "io" 6 "sort" 7 "strings" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 13 "github.com/quay/claircore" 14 ) 15 16 type Manifest struct { 17 RPM []ManifestRPM `json:"rpms"` 18 } 19 type ManifestRPM struct { 20 Name string `json:"name"` 21 Version string `json:"version"` 22 Release string `json:"release"` 23 Arch string `json:"architecture"` 24 Source string `json:"srpm_nevra"` 25 GPG string `json:"gpg"` 26 Module string `json:"module"` 27 } 28 29 func PackagesFromRPMManifest(t *testing.T, r io.Reader) []*claircore.Package { 30 t.Helper() 31 var m Manifest 32 if err := json.NewDecoder(r).Decode(&m); err != nil { 33 t.Fatal(err) 34 } 35 out := make([]*claircore.Package, 0, len(m.RPM)) 36 srcs := make([]claircore.Package, 0, len(m.RPM)) 37 src := make(map[string]*claircore.Package) 38 for _, rpm := range m.RPM { 39 p := claircore.Package{ 40 Name: rpm.Name, 41 Version: rpm.Version + "-" + rpm.Release, 42 Kind: "binary", 43 Arch: rpm.Arch, 44 RepositoryHint: "key:" + rpm.GPG, 45 Module: rpm.Module, 46 } 47 if s, ok := src[rpm.Source]; ok { 48 p.Source = s 49 } else { 50 s := strings.TrimSuffix(rpm.Source, ".src") 51 pos := len(s) 52 for i := 0; i < 2; i++ { 53 pos = strings.LastIndexByte(s[:pos], '-') 54 if pos == -1 { 55 t.Fatalf("malformed NEVRA: %q", rpm.Source) 56 } 57 } 58 idx := len(srcs) 59 srcs = append(srcs, claircore.Package{ 60 Kind: "source", 61 Name: s[:pos], 62 Version: strings.TrimPrefix(s[pos+1:], "0:"), 63 Module: rpm.Module, 64 }) 65 src[rpm.Source] = &srcs[idx] 66 p.Source = &srcs[idx] 67 } 68 out = append(out, &p) 69 } 70 return out 71 } 72 73 var Options = cmp.Options{ 74 HintCompare, 75 EpochCompare, 76 IgnorePackageDB, 77 SortPackages, 78 ModuleCompare, 79 } 80 81 // RPM Manifest doesn't have checksum information. It does have keyid information, 82 // so cook up a comparison function that understands the rpm package's packed format. 83 var HintCompare = cmp.FilterPath( 84 func(p cmp.Path) bool { return p.Last().String() == ".RepositoryHint" }, 85 cmpopts.AcyclicTransformer("NormalizeHint", func(h string) string { 86 n := [][2]string{} 87 for _, s := range strings.Split(h, "|") { 88 if s == "" { 89 continue 90 } 91 k, v, ok := strings.Cut(s, ":") 92 if !ok { 93 panic("odd format: " + s) 94 } 95 if k == "hash" { 96 continue 97 } 98 i := len(n) 99 n = append(n, [2]string{}) 100 n[i][0] = k 101 n[i][1] = v 102 } 103 sort.Slice(n, func(i, j int) bool { return n[i][0] < n[i][1] }) 104 var b strings.Builder 105 for i, s := range n { 106 if i != 0 { 107 b.WriteByte('|') 108 } 109 b.WriteString(s[0]) 110 b.WriteByte(':') 111 b.WriteString(s[1]) 112 } 113 return b.String() 114 }), 115 ) 116 117 // RPM Manifest doesn't have package epoch information. 118 // This checks if the VR string is contained in the EVR string. 119 var EpochCompare = cmp.FilterPath( 120 func(p cmp.Path) bool { return p.Last().String() == ".Version" }, 121 cmp.Comparer(func(a, b string) bool { 122 evr, vr := a, b 123 if len(b) > len(a) { 124 evr = b 125 vr = a 126 } 127 return strings.Contains(evr, vr) 128 }), 129 ) 130 131 // ModuleCompare allows one of the reported modules to be the empty string. 132 // This is needed because of [STONEBLD-1472]. 133 // 134 // [STONEBLD-1472]: https://issues.redhat.com/browse/STONEBLD-1472 135 var ModuleCompare = cmp.FilterPath( 136 func(p cmp.Path) bool { return p.Last().String() == ".Module" }, 137 cmp.FilterValues( 138 func(a, b string) bool { return a != "" && b == "" || a == "" && b != "" }, 139 cmp.Ignore(), 140 ), 141 ) 142 143 // Does what it says on the tin. 144 var ( 145 SortPackages = cmpopts.SortSlices(func(a, b *claircore.Package) bool { 146 return a.Name < b.Name 147 }) 148 IgnorePackageDB = cmpopts.IgnoreFields(claircore.Package{}, ".PackageDB") 149 )