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  )