switch to images/pkg/dnfjson and remove internal copy
COMPOSER-2068
This commit is contained in:
parent
98e3dab9c7
commit
76e686df10
23 changed files with 120 additions and 1158 deletions
|
|
@ -17,9 +17,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/osbuild/images/pkg/distro/test_distro"
|
||||
"github.com/osbuild/images/pkg/dnfjson"
|
||||
"github.com/osbuild/images/pkg/reporegistry"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
dnfjson_mock "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson"
|
||||
rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/weldr"
|
||||
|
|
|
|||
|
|
@ -1,325 +0,0 @@
|
|||
package dnfjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
)
|
||||
|
||||
// CleanupOldCacheDirs will remove cache directories for unsupported distros
|
||||
// eg. Once support for a fedora release stops and it is removed, this will
|
||||
// delete its directory under root.
|
||||
//
|
||||
// A happy side effect of this is that it will delete old cache directories
|
||||
// and files from before the switch to per-distro cache directories.
|
||||
//
|
||||
// NOTE: This does not return any errors. This is because the most common one
|
||||
// will be a nonexistant directory which will be created later, during initial
|
||||
// cache creation. Any other errors like permission issues will be caught by
|
||||
// later use of the cache. eg. touchRepo
|
||||
func CleanupOldCacheDirs(root string, distros []string) {
|
||||
dirs, _ := os.ReadDir(root)
|
||||
|
||||
for _, e := range dirs {
|
||||
if strSliceContains(distros, e.Name()) {
|
||||
// known distro
|
||||
continue
|
||||
}
|
||||
if e.IsDir() {
|
||||
// Remove the directory and everything under it
|
||||
_ = os.RemoveAll(filepath.Join(root, e.Name()))
|
||||
} else {
|
||||
_ = os.Remove(filepath.Join(root, e.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// strSliceContains returns true if the elem string is in the slc array
|
||||
func strSliceContains(slc []string, elem string) bool {
|
||||
for _, s := range slc {
|
||||
if elem == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// global cache locker
|
||||
var cacheLocks sync.Map
|
||||
|
||||
// A collection of directory paths, their total size, and their most recent
|
||||
// modification time.
|
||||
type pathInfo struct {
|
||||
paths []string
|
||||
size uint64
|
||||
mtime time.Time
|
||||
}
|
||||
|
||||
type rpmCache struct {
|
||||
// root path for the cache
|
||||
root string
|
||||
|
||||
// individual repository cache data
|
||||
repoElements map[string]pathInfo
|
||||
|
||||
// list of known repository IDs, sorted by mtime
|
||||
repoRecency []string
|
||||
|
||||
// total cache size
|
||||
size uint64
|
||||
|
||||
// max cache size
|
||||
maxSize uint64
|
||||
|
||||
// locker for this cache directory
|
||||
locker *sync.RWMutex
|
||||
}
|
||||
|
||||
func newRPMCache(path string, maxSize uint64) *rpmCache {
|
||||
absPath, err := filepath.Abs(path) // convert to abs if it's not already
|
||||
if err != nil {
|
||||
panic(err) // can only happen if the CWD does not exist and the path isn't already absolute
|
||||
}
|
||||
path = absPath
|
||||
locker := new(sync.RWMutex)
|
||||
if l, loaded := cacheLocks.LoadOrStore(path, locker); loaded {
|
||||
// value existed and was loaded
|
||||
locker = l.(*sync.RWMutex)
|
||||
}
|
||||
r := &rpmCache{
|
||||
root: path,
|
||||
repoElements: make(map[string]pathInfo),
|
||||
size: 0,
|
||||
maxSize: maxSize,
|
||||
locker: locker,
|
||||
}
|
||||
// collect existing cache paths and timestamps
|
||||
r.updateInfo()
|
||||
return r
|
||||
}
|
||||
|
||||
// updateInfo updates the repoPaths and repoRecency fields of the rpmCache.
|
||||
//
|
||||
// NOTE: This does not return any errors. This is because the most common one
|
||||
// will be a nonexistant directory which will be created later, during initial
|
||||
// cache creation. Any other errors like permission issues will be caught by
|
||||
// later use of the cache. eg. touchRepo
|
||||
func (r *rpmCache) updateInfo() {
|
||||
dirs, _ := os.ReadDir(r.root)
|
||||
for _, d := range dirs {
|
||||
r.updateCacheDirInfo(filepath.Join(r.root, d.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rpmCache) updateCacheDirInfo(path string) {
|
||||
// See updateInfo NOTE on error handling
|
||||
cacheEntries, _ := os.ReadDir(path)
|
||||
|
||||
// each repository has multiple cache entries (3 on average), so using the
|
||||
// number of cacheEntries to allocate the map and ID slice is a high upper
|
||||
// bound, but guarantees we wont need to grow and reallocate either.
|
||||
repos := make(map[string]pathInfo, len(cacheEntries))
|
||||
repoIDs := make([]string, 0, len(cacheEntries))
|
||||
|
||||
var totalSize uint64
|
||||
|
||||
// Collect the paths grouped by their repo ID
|
||||
// We assume the first 64 characters of a file or directory name are the
|
||||
// repository ID because we use a sha256 sum of the repository config to
|
||||
// create the ID (64 hex chars)
|
||||
for _, entry := range cacheEntries {
|
||||
eInfo, err := entry.Info()
|
||||
if err != nil {
|
||||
// skip it
|
||||
continue
|
||||
}
|
||||
|
||||
fname := entry.Name()
|
||||
if len(fname) < 64 {
|
||||
// unknown file in cache; ignore
|
||||
continue
|
||||
}
|
||||
repoID := fname[:64]
|
||||
repo, ok := repos[repoID]
|
||||
if !ok {
|
||||
// new repo ID
|
||||
repoIDs = append(repoIDs, repoID)
|
||||
}
|
||||
mtime := eInfo.ModTime()
|
||||
ePath := filepath.Join(path, entry.Name())
|
||||
|
||||
// calculate and add entry size
|
||||
size, err := dirSize(ePath)
|
||||
if err != nil {
|
||||
// skip it
|
||||
continue
|
||||
}
|
||||
repo.size += size
|
||||
totalSize += size
|
||||
|
||||
// add path
|
||||
repo.paths = append(repo.paths, ePath)
|
||||
|
||||
// if for some reason the mtimes of the various entries of a single
|
||||
// repository are out of sync, use the most recent one
|
||||
if repo.mtime.Before(mtime) {
|
||||
repo.mtime = mtime
|
||||
}
|
||||
|
||||
// update the collection
|
||||
repos[repoID] = repo
|
||||
}
|
||||
sortFunc := func(idx, jdx int) bool {
|
||||
ir := repos[repoIDs[idx]]
|
||||
jr := repos[repoIDs[jdx]]
|
||||
return ir.mtime.Before(jr.mtime)
|
||||
}
|
||||
|
||||
// sort IDs by mtime (oldest first)
|
||||
sort.Slice(repoIDs, sortFunc)
|
||||
|
||||
r.size = totalSize
|
||||
r.repoElements = repos
|
||||
r.repoRecency = repoIDs
|
||||
}
|
||||
|
||||
func (r *rpmCache) shrink() error {
|
||||
r.locker.Lock()
|
||||
defer r.locker.Unlock()
|
||||
|
||||
// start deleting until we drop below r.maxSize
|
||||
nDeleted := 0
|
||||
for idx := 0; idx < len(r.repoRecency) && r.size >= r.maxSize; idx++ {
|
||||
repoID := r.repoRecency[idx]
|
||||
nDeleted++
|
||||
repo, ok := r.repoElements[repoID]
|
||||
if !ok {
|
||||
// cache inconsistency?
|
||||
// ignore and let the ID be removed from the recency list
|
||||
continue
|
||||
}
|
||||
for _, gPath := range repo.paths {
|
||||
if err := os.RemoveAll(gPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.size -= repo.size
|
||||
delete(r.repoElements, repoID)
|
||||
}
|
||||
|
||||
// update recency list
|
||||
r.repoRecency = r.repoRecency[nDeleted:]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update file atime and mtime on the filesystem to time t for all files in the
|
||||
// root of the cache that match the repo ID. This should be called whenever a
|
||||
// repository is used.
|
||||
// This function does not update the internal cache info. A call to
|
||||
// updateInfo() should be made after touching one or more repositories.
|
||||
func (r *rpmCache) touchRepo(repoID string, t time.Time) error {
|
||||
repoGlob, err := glob.Compile(fmt.Sprintf("%s*", repoID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
distroDirs, err := os.ReadDir(r.root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, d := range distroDirs {
|
||||
// we only touch the top-level directories and files of the cache
|
||||
cacheEntries, err := os.ReadDir(filepath.Join(r.root, d.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, cacheEntry := range cacheEntries {
|
||||
if repoGlob.Match(cacheEntry.Name()) {
|
||||
path := filepath.Join(r.root, d.Name(), cacheEntry.Name())
|
||||
if err := os.Chtimes(path, t, t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dirSize(path string) (uint64, error) {
|
||||
var size uint64
|
||||
sizer := func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size += uint64(info.Size())
|
||||
return nil
|
||||
}
|
||||
err := filepath.Walk(path, sizer)
|
||||
return size, err
|
||||
}
|
||||
|
||||
// dnfResults holds the results of a dnfjson request
|
||||
// expire is the time the request was made, used to expire the entry
|
||||
type dnfResults struct {
|
||||
expire time.Time
|
||||
pkgs rpmmd.PackageList
|
||||
}
|
||||
|
||||
// dnfCache is a cache of results from dnf-json requests
|
||||
type dnfCache struct {
|
||||
results map[string]dnfResults
|
||||
timeout time.Duration
|
||||
*sync.RWMutex
|
||||
}
|
||||
|
||||
// NewDNFCache returns a pointer to an initialized dnfCache struct
|
||||
func NewDNFCache(timeout time.Duration) *dnfCache {
|
||||
return &dnfCache{
|
||||
results: make(map[string]dnfResults),
|
||||
timeout: timeout,
|
||||
RWMutex: new(sync.RWMutex),
|
||||
}
|
||||
}
|
||||
|
||||
// CleanCache deletes unused cache entries
|
||||
// This prevents the cache from growing for longer than the timeout interval
|
||||
func (d *dnfCache) CleanCache() {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
// Delete expired resultCache entries
|
||||
for k := range d.results {
|
||||
if time.Since(d.results[k].expire) > d.timeout {
|
||||
delete(d.results, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the package list and true if cached
|
||||
// or an empty list and false if not cached or if cache is timed out
|
||||
func (d *dnfCache) Get(hash string) (rpmmd.PackageList, bool) {
|
||||
d.RLock()
|
||||
defer d.RUnlock()
|
||||
|
||||
result, ok := d.results[hash]
|
||||
if !ok || time.Since(result.expire) >= d.timeout {
|
||||
return rpmmd.PackageList{}, false
|
||||
}
|
||||
return result.pkgs, true
|
||||
}
|
||||
|
||||
// Store saves the package list in the cache
|
||||
func (d *dnfCache) Store(hash string, pkgs rpmmd.PackageList) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
d.results[hash] = dnfResults{expire: time.Now(), pkgs: pkgs}
|
||||
}
|
||||
|
|
@ -1,384 +0,0 @@
|
|||
package dnfjson
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func truncate(path string, size int64) {
|
||||
fp, err := os.Create(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
if err := fp.Truncate(size); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// create a test cache based on the config, where the config keys are file
|
||||
// paths and the values are file sizes
|
||||
func createTestCache(root string, config testCache) uint64 {
|
||||
var totalSize uint64
|
||||
for path, fi := range config {
|
||||
fullPath := filepath.Join(root, path)
|
||||
parPath := filepath.Dir(fullPath)
|
||||
if err := os.MkdirAll(parPath, 0770); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
truncate(fullPath, int64(fi.size))
|
||||
mtime := time.Unix(fi.mtime, 0)
|
||||
if err := os.Chtimes(fullPath, mtime, mtime); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// if the path has multiple parts, touch the top level directory of the
|
||||
// element
|
||||
pathParts := strings.Split(path, "/")
|
||||
if len(pathParts) > 1 {
|
||||
top := pathParts[0]
|
||||
if err := os.Chtimes(filepath.Join(root, top), mtime, mtime); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
if len(path) >= 64 {
|
||||
// paths with shorter names will be ignored by the cache manager
|
||||
totalSize += fi.size
|
||||
}
|
||||
}
|
||||
|
||||
// add directory sizes to total
|
||||
sizer := func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path == root {
|
||||
// don't count root
|
||||
return nil
|
||||
}
|
||||
if info.IsDir() {
|
||||
totalSize += uint64(info.Size())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if err := filepath.Walk(root, sizer); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return totalSize
|
||||
}
|
||||
|
||||
type fileInfo struct {
|
||||
size uint64
|
||||
mtime int64
|
||||
}
|
||||
|
||||
type testCache map[string]fileInfo
|
||||
|
||||
var testCfgs = map[string]testCache{
|
||||
"rhel84-aarch64": { // real repo metadata file names and sizes
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc.solv": fileInfo{2095095, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-filenames.solvx": fileInfo{14473401, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/gen/groups.xml": fileInfo{1419587, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/3eabd1122210e4def18ae4b96a18aa5bcc186abf2ec14e2e8f1c1bb1ab4d11da-modules.yaml.gz": fileInfo{156314, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/90fd2e7463220a07457e76ae905e1bad754c29e22202bb3202c971a5ece28396-comps-AppStream.aarch64.xml.gz": fileInfo{199426, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/77a66c76b5f6ba51aaee6c0cf76d701601e8b622d1701d1781dabec434f27413-filelists.xml.gz": fileInfo{14370201, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/1941c723c94218eed43eac3174aa94cefbe921e15547c39251a95895024207ca-primary.xml.gz": fileInfo{11439375, 100},
|
||||
"9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc-33d346d177279673/repodata/repomd.xml": fileInfo{13285, 100},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62.solv": fileInfo{1147863, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-filenames.solvx": fileInfo{11133964, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-98177081b9162766/repodata/gen/groups.xml": fileInfo{1298102, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-98177081b9162766/repodata/d74783221709ab27d543c1cfc4c02562fde6edfaaaac33ac73a68ecf53188695-comps-BaseOS.aarch64.xml.gz": fileInfo{174076, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-98177081b9162766/repodata/5ded48b4c9e238288130c6670d99f5febdb7273e4a31ac213836a15a2076514d-filelists.xml.gz": fileInfo{11081612, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-98177081b9162766/repodata/8120caf8ebbb8c8b37f6f0dd027d866020ebe7acf9c9ce49ae9903b761986f0c-primary.xml.gz": fileInfo{1836471, 300},
|
||||
"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62-98177081b9162766/repodata/repomd.xml": fileInfo{12817, 300},
|
||||
},
|
||||
"fake-real": { // fake but resembling real data
|
||||
"1111111111111111111111111111111111111111111111111111111111111111.solv": fileInfo{100, 0},
|
||||
"1111111111111111111111111111111111111111111111111111111111111111-filenames.solv": fileInfo{200, 0},
|
||||
"1111111111111111111111111111111111111111111111111111111111111111.whatever": fileInfo{110, 0},
|
||||
"1111111111111111111111111111111111111111111111111111111111111111/repodata/a": fileInfo{1000, 0},
|
||||
"1111111111111111111111111111111111111111111111111111111111111111/repodata/b": fileInfo{3829, 0},
|
||||
"1111111111111111111111111111111111111111111111111111111111111111/repodata/c": fileInfo{831989, 0},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222.solv": fileInfo{120, 2},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222-filenames.solv": fileInfo{232, 2},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222.whatever": fileInfo{110, 2},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222/repodata/a": fileInfo{1000, 2},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222/repodata/b": fileInfo{3829, 2},
|
||||
"2222222222222222222222222222222222222222222222222222222222222222/repodata/c": fileInfo{831989, 2},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333.solv": fileInfo{105, 4},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333-filenames.solv": fileInfo{200, 4},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333.whatever": fileInfo{110, 4},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333/repodata/a": fileInfo{2390, 4},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333/repodata/b": fileInfo{1234890, 4},
|
||||
"3333333333333333333333333333333333333333333333333333333333333333/repodata/c": fileInfo{483, 4},
|
||||
},
|
||||
"completely-fake": { // just a mess of files (including files without a repo ID)
|
||||
"somefile": fileInfo{192, 10291920},
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-repofiley": fileInfo{29384, 11},
|
||||
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy-repofiley2": fileInfo{293, 31},
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-repofile": fileInfo{29384, 30},
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-repofileb": fileInfo{293, 45},
|
||||
},
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
cache testCache
|
||||
maxSize uint64
|
||||
minSizeAfterShrink uint64
|
||||
repoIDsAfterShrink []string
|
||||
}
|
||||
|
||||
func getRepoIDs(ct testCache) []string {
|
||||
idMap := make(map[string]bool)
|
||||
ids := make([]string, 0)
|
||||
for path := range ct {
|
||||
if len(path) >= 64 {
|
||||
id := path[:64]
|
||||
if !idMap[id] {
|
||||
idMap[id] = true
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func TestCacheRead(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
for name, cfg := range testCfgs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
testCacheRoot := t.TempDir()
|
||||
// Cache is now per-distro, use the name of the config as a distro name
|
||||
s := createTestCache(filepath.Join(testCacheRoot, name), cfg)
|
||||
|
||||
// Cache covers all distros, pass in top directory
|
||||
cache := newRPMCache(testCacheRoot, 1048576) // 1 MiB, but doesn't matter for this test
|
||||
|
||||
nrepos := len(getRepoIDs(cfg))
|
||||
assert.Equal(s, cache.size)
|
||||
assert.Equal(nrepos, len(cache.repoElements))
|
||||
assert.Equal(nrepos, len(cache.repoRecency))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sizeSum(cfg testCache, repoIDFilter ...string) uint64 {
|
||||
var sum uint64
|
||||
for path, info := range cfg {
|
||||
if len(path) < 64 {
|
||||
continue
|
||||
}
|
||||
rid := path[:64]
|
||||
if len(repoIDFilter) == 0 || (len(repoIDFilter) > 0 && strSliceContains(repoIDFilter, rid)) {
|
||||
sum += info.size
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func TestCacheCleanup(t *testing.T) {
|
||||
rhelRecentRepoSize := sizeSum(testCfgs["rhel84-aarch64"], "df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62")
|
||||
rhelTotalRepoSize := sizeSum(testCfgs["rhel84-aarch64"])
|
||||
|
||||
fakeRealSize2 := sizeSum(testCfgs["fake-real"], "2222222222222222222222222222222222222222222222222222222222222222")
|
||||
fakeRealSize3 := sizeSum(testCfgs["fake-real"], "3333333333333333333333333333333333333333333333333333333333333333")
|
||||
|
||||
testCases := map[string]testCase{
|
||||
// max size 1 byte -> clean will delete everything
|
||||
"fake-real-full-delete": {
|
||||
cache: testCfgs["fake-real"],
|
||||
maxSize: 1,
|
||||
minSizeAfterShrink: 0,
|
||||
},
|
||||
"rhel-full-delete": {
|
||||
cache: testCfgs["rhel84-aarch64"],
|
||||
maxSize: 1,
|
||||
minSizeAfterShrink: 0,
|
||||
},
|
||||
"completely-fake-full-delete": {
|
||||
cache: testCfgs["completely-fake"],
|
||||
maxSize: 1,
|
||||
minSizeAfterShrink: 0,
|
||||
},
|
||||
"completely-fake-full-delete-2": {
|
||||
cache: testCfgs["completely-fake"],
|
||||
maxSize: 100,
|
||||
minSizeAfterShrink: 0,
|
||||
},
|
||||
// max size a bit larger than most recent repo -> clean will delete older repos
|
||||
"completely-fake-half-delete": {
|
||||
cache: testCfgs["completely-fake"],
|
||||
maxSize: 29384 + 293 + 1, // one byte larger than the files of one repo
|
||||
minSizeAfterShrink: 29384 + 293, // size of files from one repo
|
||||
repoIDsAfterShrink: []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, // most recent repo timestamp (45)
|
||||
},
|
||||
"rhel-half-delete": {
|
||||
cache: testCfgs["rhel84-aarch64"],
|
||||
maxSize: rhelRecentRepoSize + 102400, // most recent repo file sizes + 100k buffer (for directories)
|
||||
minSizeAfterShrink: rhelRecentRepoSize, // after shrink it should be at least as big as the most recent repo
|
||||
repoIDsAfterShrink: []string{"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62"}, // most recent repo timestamp (45)
|
||||
},
|
||||
"fake-real-delete-1": {
|
||||
cache: testCfgs["fake-real"],
|
||||
maxSize: fakeRealSize3 + fakeRealSize2 + 102400,
|
||||
minSizeAfterShrink: fakeRealSize3 + fakeRealSize2,
|
||||
repoIDsAfterShrink: []string{"3333333333333333333333333333333333333333333333333333333333333333", "2222222222222222222222222222222222222222222222222222222222222222"},
|
||||
},
|
||||
"fake-real-delete-2": {
|
||||
cache: testCfgs["fake-real"],
|
||||
maxSize: fakeRealSize3 + 102400,
|
||||
minSizeAfterShrink: fakeRealSize3,
|
||||
repoIDsAfterShrink: []string{"3333333333333333333333333333333333333333333333333333333333333333"},
|
||||
},
|
||||
// max size is huge -> clean wont delete anything
|
||||
"rhel-no-delete": {
|
||||
cache: testCfgs["rhel84-aarch64"],
|
||||
maxSize: 45097156608, // 42 GiB
|
||||
minSizeAfterShrink: rhelTotalRepoSize,
|
||||
repoIDsAfterShrink: []string{"df2665154150abf76f4d86156228a75c39f3f31a79d4a861d76b1edd89814b62", "9adf133053f0691a0ec12e73cbf1875a90c9268b4f09162fc3387fd76ecb3bcc"},
|
||||
},
|
||||
}
|
||||
|
||||
for name, cfg := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
testCacheRoot := t.TempDir()
|
||||
// Cache is now per-distro, use the name of the config as a distro name
|
||||
createTestCache(filepath.Join(testCacheRoot, name), cfg.cache)
|
||||
|
||||
// Cache covers all distros, pass in top directory
|
||||
cache := newRPMCache(testCacheRoot, cfg.maxSize)
|
||||
|
||||
err := cache.shrink()
|
||||
assert.NoError(err)
|
||||
|
||||
// it's hard to predict the exact size after shrink because of directory sizes
|
||||
// so let's just check that the new size is between min and max
|
||||
assert.LessOrEqual(cfg.minSizeAfterShrink, cache.size)
|
||||
assert.Greater(cfg.maxSize, cache.size)
|
||||
assert.Equal(len(cfg.repoIDsAfterShrink), len(cache.repoElements))
|
||||
for _, id := range cfg.repoIDsAfterShrink {
|
||||
assert.Contains(cache.repoElements, id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Mock package list to use in testing
|
||||
var PackageList = rpmmd.PackageList{
|
||||
rpmmd.Package{
|
||||
Name: "package0",
|
||||
Summary: "package summary",
|
||||
Description: "package description",
|
||||
URL: "https://package-url/",
|
||||
Epoch: 0,
|
||||
Version: "1.0.0",
|
||||
Release: "3",
|
||||
Arch: "x86_64",
|
||||
License: "MIT",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDNFCacheStoreGet(t *testing.T) {
|
||||
cache := NewDNFCache(1 * time.Second)
|
||||
assert.Equal(t, cache.timeout, 1*time.Second)
|
||||
assert.NotNil(t, cache.RWMutex)
|
||||
|
||||
cache.Store("notreallyahash", PackageList)
|
||||
assert.Equal(t, 1, len(cache.results))
|
||||
pkgs, ok := cache.Get("notreallyahash")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "package0", pkgs[0].Name)
|
||||
}
|
||||
|
||||
func TestDNFCacheTimeout(t *testing.T) {
|
||||
cache := NewDNFCache(1 * time.Second)
|
||||
cache.Store("notreallyahash", PackageList)
|
||||
_, ok := cache.Get("notreallyahash")
|
||||
assert.True(t, ok)
|
||||
time.Sleep(2 * time.Second)
|
||||
_, ok = cache.Get("notreallyahash")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestDNFCacheCleanup(t *testing.T) {
|
||||
cache := NewDNFCache(1 * time.Second)
|
||||
cache.Store("notreallyahash", PackageList)
|
||||
time.Sleep(2 * time.Second)
|
||||
assert.Equal(t, 1, len(cache.results))
|
||||
cache.CleanCache()
|
||||
assert.Equal(t, 0, len(cache.results))
|
||||
_, ok := cache.Get("notreallyahash")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestCleanupOldCacheDirs(t *testing.T) {
|
||||
// Run the cleanup without the cache present and with dummy distro names
|
||||
CleanupOldCacheDirs("/var/tmp/test-no-cache-rpmmd/", []string{"fedora-37", "fedora-38"})
|
||||
|
||||
testCacheRoot := t.TempDir()
|
||||
// Make all the test caches under root, using their keys as a distro name.
|
||||
var distros []string
|
||||
for name, cfg := range testCfgs {
|
||||
// Cache is now per-distro, use the name of the config as a distro name
|
||||
createTestCache(filepath.Join(testCacheRoot, name), cfg)
|
||||
distros = append(distros, name)
|
||||
}
|
||||
sort.Strings(distros)
|
||||
|
||||
// Add the content of the 'fake-real' cache to the top directory
|
||||
// this will be used to simulate an old cache without distro subdirs
|
||||
createTestCache(testCacheRoot, testCfgs["fake-real"])
|
||||
|
||||
CleanupOldCacheDirs(testCacheRoot, distros)
|
||||
|
||||
// The fake-real files under the root directory should all be gone.
|
||||
for path := range testCfgs["fake-real"] {
|
||||
_, err := os.Stat(filepath.Join(testCacheRoot, path))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
// The distro cache files should all still be present
|
||||
for name, cfg := range testCfgs {
|
||||
for path := range cfg {
|
||||
_, err := os.Stat(filepath.Join(testCacheRoot, name, path))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the fake-real distro from the list
|
||||
// This simulates retiring an older distribution and cleaning up its cache
|
||||
distros = []string{}
|
||||
for name := range testCfgs {
|
||||
if name == "fake-real" {
|
||||
continue
|
||||
}
|
||||
distros = append(distros, name)
|
||||
}
|
||||
// Cleanup should now remove the fake-real subdirectory and files
|
||||
CleanupOldCacheDirs(testCacheRoot, distros)
|
||||
|
||||
// The remaining distro's cache files should all still be present
|
||||
for _, name := range distros {
|
||||
for path := range testCfgs[name] {
|
||||
_, err := os.Stat(filepath.Join(testCacheRoot, name, path))
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
// But the fake-real ones should be gone
|
||||
for path := range testCfgs["fake-real"] {
|
||||
_, err := os.Stat(filepath.Join(testCacheRoot, "fake-real", path))
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,671 +0,0 @@
|
|||
// Package dnfjson is an interface to the dnf-json Python script that is
|
||||
// packaged with the osbuild-composer project. The core component of this
|
||||
// package is the Solver type. The Solver can be configured with
|
||||
// distribution-specific values (platform ID, architecture, and version
|
||||
// information) and provides methods for dependency resolution (Depsolve) and
|
||||
// retrieving a full list of repository package metadata (FetchMetadata).
|
||||
//
|
||||
// Alternatively, a BaseSolver can be created which represents an un-configured
|
||||
// Solver. This type can't be used for depsolving, but can be used to create
|
||||
// configured Solver instances sharing the same cache directory.
|
||||
//
|
||||
// This package relies on the types defined in rpmmd to describe RPM package
|
||||
// metadata.
|
||||
package dnfjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/images/pkg/rhsm"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
)
|
||||
|
||||
// BaseSolver defines the basic solver configuration without platform
|
||||
// information. It can be used to create configured Solver instances with the
|
||||
// NewWithConfig() method. A BaseSolver maintains the global repository cache
|
||||
// directory.
|
||||
type BaseSolver struct {
|
||||
// Cache information
|
||||
cache *rpmCache
|
||||
|
||||
// Path to the dnf-json binary and optional args (default: "/usr/libexec/osbuild-composer/dnf-json")
|
||||
dnfJsonCmd []string
|
||||
|
||||
resultCache *dnfCache
|
||||
}
|
||||
|
||||
// Create a new unconfigured BaseSolver (without platform information). It can
|
||||
// be used to create configured Solver instances with the NewWithConfig()
|
||||
// method.
|
||||
func NewBaseSolver(cacheDir string) *BaseSolver {
|
||||
return &BaseSolver{
|
||||
cache: newRPMCache(cacheDir, 1024*1024*1024), // 1 GiB
|
||||
dnfJsonCmd: []string{"/usr/libexec/osbuild-composer/dnf-json"},
|
||||
resultCache: NewDNFCache(60 * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxCacheSize sets the maximum size for the global repository metadata
|
||||
// cache. This is the maximum size of the cache after a CleanCache()
|
||||
// call. Cache cleanup is never performed automatically.
|
||||
func (s *BaseSolver) SetMaxCacheSize(size uint64) {
|
||||
s.cache.maxSize = size
|
||||
}
|
||||
|
||||
// SetDNFJSONPath sets the path to the dnf-json binary and optionally any command line arguments.
|
||||
func (s *BaseSolver) SetDNFJSONPath(cmd string, args ...string) {
|
||||
s.dnfJsonCmd = append([]string{cmd}, args...)
|
||||
}
|
||||
|
||||
// NewWithConfig initialises a Solver with the platform information and the
|
||||
// BaseSolver's subscription info, cache directory, and dnf-json path.
|
||||
// Also loads system subscription information.
|
||||
func (bs *BaseSolver) NewWithConfig(modulePlatformID, releaseVer, arch, distro string) *Solver {
|
||||
s := new(Solver)
|
||||
s.BaseSolver = *bs
|
||||
s.modulePlatformID = modulePlatformID
|
||||
s.arch = arch
|
||||
s.releaseVer = releaseVer
|
||||
s.distro = distro
|
||||
subs, _ := rhsm.LoadSystemSubscriptions()
|
||||
s.subscriptions = subs
|
||||
return s
|
||||
}
|
||||
|
||||
// CleanCache deletes the least recently used repository metadata caches until
|
||||
// the total size of the cache falls below the configured maximum size (see
|
||||
// SetMaxCacheSize()).
|
||||
func (bs *BaseSolver) CleanCache() error {
|
||||
bs.resultCache.CleanCache()
|
||||
return bs.cache.shrink()
|
||||
}
|
||||
|
||||
// CleanupOldCacheDirs will remove cache directories for unsupported distros
|
||||
// eg. Once support for a fedora release stops and it is removed, this will
|
||||
// delete its directory under BaseSolver cache root.
|
||||
//
|
||||
// A happy side effect of this is that it will delete old cache directories
|
||||
// and files from before the switch to per-distro cache directories.
|
||||
//
|
||||
// NOTE: This does not return any errors. This is because the most common one
|
||||
// will be a nonexistant directory which will be created later, during initial
|
||||
// cache creation. Any other errors like permission issues will be caught by
|
||||
// later use of the cache. eg. touchRepo
|
||||
func (bs *BaseSolver) CleanupOldCacheDirs(distros []string) {
|
||||
CleanupOldCacheDirs(bs.cache.root, distros)
|
||||
}
|
||||
|
||||
// Solver is configured with system information in order to resolve
|
||||
// dependencies for RPM packages using DNF.
|
||||
type Solver struct {
|
||||
BaseSolver
|
||||
|
||||
// Platform ID, e.g., "platform:el8"
|
||||
modulePlatformID string
|
||||
|
||||
// System architecture
|
||||
arch string
|
||||
|
||||
// Release version of the distro. This is used in repo files on the host
|
||||
// system and required for subscription support.
|
||||
releaseVer string
|
||||
|
||||
// Full distribution string, eg. fedora-38, used to create separate dnf cache directories
|
||||
// for each distribution.
|
||||
distro string
|
||||
|
||||
subscriptions *rhsm.Subscriptions
|
||||
}
|
||||
|
||||
// Create a new Solver with the given configuration. Initialising a Solver also loads system subscription information.
|
||||
func NewSolver(modulePlatformID, releaseVer, arch, distro, cacheDir string) *Solver {
|
||||
s := NewBaseSolver(cacheDir)
|
||||
return s.NewWithConfig(modulePlatformID, releaseVer, arch, distro)
|
||||
}
|
||||
|
||||
// GetCacheDir returns a distro specific rpm cache directory
|
||||
// It ensures that the distro name is below the root cache directory, and if there is
|
||||
// a problem it returns the root cache intead of an error.
|
||||
func (s *Solver) GetCacheDir() string {
|
||||
b := filepath.Base(s.distro)
|
||||
if b == "." || b == "/" {
|
||||
return s.cache.root
|
||||
}
|
||||
|
||||
return filepath.Join(s.cache.root, s.distro)
|
||||
}
|
||||
|
||||
// Depsolve the list of required package sets with explicit excludes using
|
||||
// their associated repositories. Each package set is depsolved as a separate
|
||||
// transactions in a chain. It returns a list of all packages (with solved
|
||||
// dependencies) that will be installed into the system.
|
||||
func (s *Solver) Depsolve(pkgSets []rpmmd.PackageSet) ([]rpmmd.PackageSpec, error) {
|
||||
req, repoMap, err := s.makeDepsolveRequest(pkgSets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get non-exclusive read lock
|
||||
s.cache.locker.RLock()
|
||||
defer s.cache.locker.RUnlock()
|
||||
|
||||
output, err := run(s.dnfJsonCmd, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// touch repos to now
|
||||
now := time.Now().Local()
|
||||
for _, r := range repoMap {
|
||||
// ignore errors
|
||||
_ = s.cache.touchRepo(r.Hash(), now)
|
||||
}
|
||||
s.cache.updateInfo()
|
||||
|
||||
var result packageSpecs
|
||||
if err := json.Unmarshal(output, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result.toRPMMD(repoMap), nil
|
||||
}
|
||||
|
||||
// FetchMetadata returns the list of all the available packages in repos and
|
||||
// their info.
|
||||
func (s *Solver) FetchMetadata(repos []rpmmd.RepoConfig) (rpmmd.PackageList, error) {
|
||||
req, err := s.makeDumpRequest(repos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get non-exclusive read lock
|
||||
s.cache.locker.RLock()
|
||||
defer s.cache.locker.RUnlock()
|
||||
|
||||
// Is this cached?
|
||||
if pkgs, ok := s.resultCache.Get(req.Hash()); ok {
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
result, err := run(s.dnfJsonCmd, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// touch repos to now
|
||||
now := time.Now().Local()
|
||||
for _, r := range repos {
|
||||
// ignore errors
|
||||
_ = s.cache.touchRepo(r.Hash(), now)
|
||||
}
|
||||
s.cache.updateInfo()
|
||||
|
||||
var pkgs rpmmd.PackageList
|
||||
if err := json.Unmarshal(result, &pkgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sortID := func(pkg rpmmd.Package) string {
|
||||
return fmt.Sprintf("%s-%s-%s", pkg.Name, pkg.Version, pkg.Release)
|
||||
}
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return sortID(pkgs[i]) < sortID(pkgs[j])
|
||||
})
|
||||
|
||||
// Cache the results
|
||||
s.resultCache.Store(req.Hash(), pkgs)
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
// SearchMetadata searches for packages and returns a list of the info for matches.
|
||||
func (s *Solver) SearchMetadata(repos []rpmmd.RepoConfig, packages []string) (rpmmd.PackageList, error) {
|
||||
req, err := s.makeSearchRequest(repos, packages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get non-exclusive read lock
|
||||
s.cache.locker.RLock()
|
||||
defer s.cache.locker.RUnlock()
|
||||
|
||||
// Is this cached?
|
||||
if pkgs, ok := s.resultCache.Get(req.Hash()); ok {
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
result, err := run(s.dnfJsonCmd, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// touch repos to now
|
||||
now := time.Now().Local()
|
||||
for _, r := range repos {
|
||||
// ignore errors
|
||||
_ = s.cache.touchRepo(r.Hash(), now)
|
||||
}
|
||||
s.cache.updateInfo()
|
||||
|
||||
var pkgs rpmmd.PackageList
|
||||
if err := json.Unmarshal(result, &pkgs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sortID := func(pkg rpmmd.Package) string {
|
||||
return fmt.Sprintf("%s-%s-%s", pkg.Name, pkg.Version, pkg.Release)
|
||||
}
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
return sortID(pkgs[i]) < sortID(pkgs[j])
|
||||
})
|
||||
|
||||
// Cache the results
|
||||
s.resultCache.Store(req.Hash(), pkgs)
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
func (s *Solver) reposFromRPMMD(rpmRepos []rpmmd.RepoConfig) ([]repoConfig, error) {
|
||||
dnfRepos := make([]repoConfig, len(rpmRepos))
|
||||
for idx, rr := range rpmRepos {
|
||||
dr := repoConfig{
|
||||
ID: rr.Hash(),
|
||||
Name: rr.Name,
|
||||
BaseURLs: rr.BaseURLs,
|
||||
Metalink: rr.Metalink,
|
||||
MirrorList: rr.MirrorList,
|
||||
GPGKeys: rr.GPGKeys,
|
||||
MetadataExpire: rr.MetadataExpire,
|
||||
repoHash: rr.Hash(),
|
||||
}
|
||||
|
||||
if rr.CheckGPG != nil {
|
||||
dr.CheckGPG = *rr.CheckGPG
|
||||
}
|
||||
|
||||
if rr.CheckRepoGPG != nil {
|
||||
dr.CheckRepoGPG = *rr.CheckRepoGPG
|
||||
}
|
||||
|
||||
if rr.IgnoreSSL != nil {
|
||||
dr.IgnoreSSL = *rr.IgnoreSSL
|
||||
}
|
||||
|
||||
if rr.ModuleHotfixes != nil {
|
||||
dr.ModuleHotfixes = rr.ModuleHotfixes
|
||||
}
|
||||
|
||||
if rr.RHSM {
|
||||
if s.subscriptions == nil {
|
||||
return nil, fmt.Errorf("This system does not have any valid subscriptions. Subscribe it before specifying rhsm: true in sources.")
|
||||
}
|
||||
secrets, err := s.subscriptions.GetSecretsForBaseurl(rr.BaseURLs, s.arch, s.releaseVer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RHSM secrets not found on the host for this baseurl: %s", rr.BaseURLs)
|
||||
}
|
||||
dr.SSLCACert = secrets.SSLCACert
|
||||
dr.SSLClientKey = secrets.SSLClientKey
|
||||
dr.SSLClientCert = secrets.SSLClientCert
|
||||
}
|
||||
dnfRepos[idx] = dr
|
||||
}
|
||||
return dnfRepos, nil
|
||||
}
|
||||
|
||||
// Repository configuration for resolving dependencies for a set of packages. A
|
||||
// Solver needs at least one RPM repository configured to be able to depsolve.
|
||||
type repoConfig struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
BaseURLs []string `json:"baseurl,omitempty"`
|
||||
Metalink string `json:"metalink,omitempty"`
|
||||
MirrorList string `json:"mirrorlist,omitempty"`
|
||||
GPGKeys []string `json:"gpgkeys,omitempty"`
|
||||
CheckGPG bool `json:"gpgcheck"`
|
||||
CheckRepoGPG bool `json:"check_repogpg"`
|
||||
IgnoreSSL bool `json:"ignoressl"`
|
||||
SSLCACert string `json:"sslcacert,omitempty"`
|
||||
SSLClientKey string `json:"sslclientkey,omitempty"`
|
||||
SSLClientCert string `json:"sslclientcert,omitempty"`
|
||||
MetadataExpire string `json:"metadata_expire,omitempty"`
|
||||
ModuleHotfixes *bool `json:"module_hotfixes,omitempty"`
|
||||
// set the repo hass from `rpmmd.RepoConfig.Hash()` function
|
||||
// rather than re-calculating it
|
||||
repoHash string
|
||||
}
|
||||
|
||||
// use the hash calculated by the `rpmmd.RepoConfig.Hash()`
|
||||
// function rather than re-implementing the same code
|
||||
func (r *repoConfig) Hash() string {
|
||||
return r.repoHash
|
||||
}
|
||||
|
||||
// Helper function for creating a depsolve request payload.
|
||||
// The request defines a sequence of transactions, each depsolving one of the
|
||||
// elements of `pkgSets` in the order they appear. The `repoConfigs` are used
|
||||
// as the base repositories for all transactions. The extra repository configs
|
||||
// in `pkgsetsRepos` are used for each of the `pkgSets` with matching index.
|
||||
// The length of `pkgsetsRepos` must match the length of `pkgSets` or be empty
|
||||
// (nil or empty slice).
|
||||
//
|
||||
// NOTE: Due to implementation limitations of DNF and dnf-json, each package set
|
||||
// in the chain must use all of the repositories used by its predecessor.
|
||||
// An error is returned if this requirement is not met.
|
||||
func (s *Solver) makeDepsolveRequest(pkgSets []rpmmd.PackageSet) (*Request, map[string]rpmmd.RepoConfig, error) {
|
||||
|
||||
// dedupe repository configurations but maintain order
|
||||
// the order in which repositories are added to the request affects the
|
||||
// order of the dependencies in the result
|
||||
repos := make([]rpmmd.RepoConfig, 0)
|
||||
rpmRepoMap := make(map[string]rpmmd.RepoConfig)
|
||||
|
||||
for _, ps := range pkgSets {
|
||||
for _, repo := range ps.Repositories {
|
||||
id := repo.Hash()
|
||||
if _, ok := rpmRepoMap[id]; !ok {
|
||||
rpmRepoMap[id] = repo
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transactions := make([]transactionArgs, len(pkgSets))
|
||||
for dsIdx, pkgSet := range pkgSets {
|
||||
transactions[dsIdx] = transactionArgs{
|
||||
PackageSpecs: pkgSet.Include,
|
||||
ExcludeSpecs: pkgSet.Exclude,
|
||||
}
|
||||
|
||||
for _, jobRepo := range pkgSet.Repositories {
|
||||
transactions[dsIdx].RepoIDs = append(transactions[dsIdx].RepoIDs, jobRepo.Hash())
|
||||
}
|
||||
|
||||
// If more than one transaction, ensure that the transaction uses
|
||||
// all of the repos from its predecessor
|
||||
if dsIdx > 0 {
|
||||
prevRepoIDs := transactions[dsIdx-1].RepoIDs
|
||||
if len(transactions[dsIdx].RepoIDs) < len(prevRepoIDs) {
|
||||
return nil, nil, fmt.Errorf("chained packageSet %d does not use all of the repos used by its predecessor", dsIdx)
|
||||
}
|
||||
|
||||
for idx, repoID := range prevRepoIDs {
|
||||
if repoID != transactions[dsIdx].RepoIDs[idx] {
|
||||
return nil, nil, fmt.Errorf("chained packageSet %d does not use all of the repos used by its predecessor", dsIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dnfRepoMap, err := s.reposFromRPMMD(repos)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
args := arguments{
|
||||
Repos: dnfRepoMap,
|
||||
Transactions: transactions,
|
||||
}
|
||||
|
||||
req := Request{
|
||||
Command: "depsolve",
|
||||
ModulePlatformID: s.modulePlatformID,
|
||||
Arch: s.arch,
|
||||
CacheDir: s.GetCacheDir(),
|
||||
Arguments: args,
|
||||
}
|
||||
|
||||
return &req, rpmRepoMap, nil
|
||||
}
|
||||
|
||||
// Helper function for creating a dump request payload
|
||||
func (s *Solver) makeDumpRequest(repos []rpmmd.RepoConfig) (*Request, error) {
|
||||
dnfRepos, err := s.reposFromRPMMD(repos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := Request{
|
||||
Command: "dump",
|
||||
ModulePlatformID: s.modulePlatformID,
|
||||
Arch: s.arch,
|
||||
CacheDir: s.GetCacheDir(),
|
||||
Arguments: arguments{
|
||||
Repos: dnfRepos,
|
||||
},
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
// Helper function for creating a search request payload
|
||||
func (s *Solver) makeSearchRequest(repos []rpmmd.RepoConfig, packages []string) (*Request, error) {
|
||||
dnfRepos, err := s.reposFromRPMMD(repos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := Request{
|
||||
Command: "search",
|
||||
ModulePlatformID: s.modulePlatformID,
|
||||
Arch: s.arch,
|
||||
CacheDir: s.GetCacheDir(),
|
||||
Arguments: arguments{
|
||||
Repos: dnfRepos,
|
||||
Search: searchArgs{
|
||||
Packages: packages,
|
||||
},
|
||||
},
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
|
||||
// convert internal a list of PackageSpecs to the rpmmd equivalent and attach
|
||||
// key and subscription information based on the repository configs.
|
||||
func (pkgs packageSpecs) toRPMMD(repos map[string]rpmmd.RepoConfig) []rpmmd.PackageSpec {
|
||||
rpmDependencies := make([]rpmmd.PackageSpec, len(pkgs))
|
||||
for i, dep := range pkgs {
|
||||
repo, ok := repos[dep.RepoID]
|
||||
if !ok {
|
||||
panic("dependency repo ID not found in repositories")
|
||||
}
|
||||
dep := pkgs[i]
|
||||
rpmDependencies[i].Name = dep.Name
|
||||
rpmDependencies[i].Epoch = dep.Epoch
|
||||
rpmDependencies[i].Version = dep.Version
|
||||
rpmDependencies[i].Release = dep.Release
|
||||
rpmDependencies[i].Arch = dep.Arch
|
||||
rpmDependencies[i].RemoteLocation = dep.RemoteLocation
|
||||
rpmDependencies[i].Checksum = dep.Checksum
|
||||
if repo.CheckGPG != nil {
|
||||
rpmDependencies[i].CheckGPG = *repo.CheckGPG
|
||||
}
|
||||
if repo.IgnoreSSL != nil {
|
||||
rpmDependencies[i].IgnoreSSL = *repo.IgnoreSSL
|
||||
}
|
||||
if repo.RHSM {
|
||||
rpmDependencies[i].Secrets = "org.osbuild.rhsm"
|
||||
}
|
||||
}
|
||||
return rpmDependencies
|
||||
}
|
||||
|
||||
// Request command and arguments for dnf-json
|
||||
type Request struct {
|
||||
// Command should be either "depsolve" or "dump"
|
||||
Command string `json:"command"`
|
||||
|
||||
// Platform ID, e.g., "platform:el8"
|
||||
ModulePlatformID string `json:"module_platform_id"`
|
||||
|
||||
// System architecture
|
||||
Arch string `json:"arch"`
|
||||
|
||||
// Cache directory for the DNF metadata
|
||||
CacheDir string `json:"cachedir"`
|
||||
|
||||
// Arguments for the action defined by Command
|
||||
Arguments arguments `json:"arguments"`
|
||||
}
|
||||
|
||||
// Hash returns a hash of the unique aspects of the Request
|
||||
//
|
||||
//nolint:errcheck
|
||||
func (r *Request) Hash() string {
|
||||
h := sha256.New()
|
||||
|
||||
h.Write([]byte(r.Command))
|
||||
h.Write([]byte(r.ModulePlatformID))
|
||||
h.Write([]byte(r.Arch))
|
||||
for _, repo := range r.Arguments.Repos {
|
||||
h.Write([]byte(repo.Hash()))
|
||||
}
|
||||
h.Write([]byte(fmt.Sprintf("%T", r.Arguments.Search.Latest)))
|
||||
h.Write([]byte(strings.Join(r.Arguments.Search.Packages, "")))
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// arguments for a dnf-json request
|
||||
type arguments struct {
|
||||
// Repositories to use for depsolving
|
||||
Repos []repoConfig `json:"repos"`
|
||||
|
||||
// Search terms to use with search command
|
||||
Search searchArgs `json:"search"`
|
||||
|
||||
// Depsolve package sets and repository mappings for this request
|
||||
Transactions []transactionArgs `json:"transactions"`
|
||||
}
|
||||
|
||||
type searchArgs struct {
|
||||
// Only include latest NEVRA when true
|
||||
Latest bool `json:"latest"`
|
||||
|
||||
// List of package name globs to search for
|
||||
// If it has '*' it is passed to dnf glob search, if it has *name* it is passed
|
||||
// to substr matching, and if it has neither an exact match is expected.
|
||||
Packages []string `json:"packages"`
|
||||
}
|
||||
|
||||
type transactionArgs struct {
|
||||
// Packages to depsolve
|
||||
PackageSpecs []string `json:"package-specs"`
|
||||
|
||||
// Packages to exclude from results
|
||||
ExcludeSpecs []string `json:"exclude-specs"`
|
||||
|
||||
// IDs of repositories to use for this depsolve
|
||||
RepoIDs []string `json:"repo-ids"`
|
||||
}
|
||||
|
||||
type packageSpecs []PackageSpec
|
||||
|
||||
// Package specification
|
||||
type PackageSpec struct {
|
||||
Name string `json:"name"`
|
||||
Epoch uint `json:"epoch"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
Arch string `json:"arch,omitempty"`
|
||||
RepoID string `json:"repo_id,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
RemoteLocation string `json:"remote_location,omitempty"`
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
Secrets string `json:"secrets,omitempty"`
|
||||
}
|
||||
|
||||
// dnf-json error structure
|
||||
type Error struct {
|
||||
Kind string `json:"kind"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
func (err Error) Error() string {
|
||||
return fmt.Sprintf("DNF error occurred: %s: %s", err.Kind, err.Reason)
|
||||
}
|
||||
|
||||
// parseError parses the response from dnf-json into the Error type and appends
|
||||
// the name and URL of a repository to all detected repository IDs in the
|
||||
// message.
|
||||
func parseError(data []byte, repos []repoConfig) Error {
|
||||
var e Error
|
||||
if err := json.Unmarshal(data, &e); err != nil {
|
||||
// dumping the error into the Reason can get noisy, but it's good for troubleshooting
|
||||
return Error{
|
||||
Kind: "InternalError",
|
||||
Reason: fmt.Sprintf("Failed to unmarshal dnf-json error output %q: %s", string(data), err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
// append to any instance of a repository ID the URL (or metalink, mirrorlist, etc)
|
||||
for _, repo := range repos {
|
||||
idstr := fmt.Sprintf("'%s'", repo.ID)
|
||||
var nameURL string
|
||||
if len(repo.BaseURLs) > 0 {
|
||||
nameURL = strings.Join(repo.BaseURLs, ",")
|
||||
} else if len(repo.Metalink) > 0 {
|
||||
nameURL = repo.Metalink
|
||||
} else if len(repo.MirrorList) > 0 {
|
||||
nameURL = repo.MirrorList
|
||||
}
|
||||
|
||||
if len(repo.Name) > 0 {
|
||||
nameURL = fmt.Sprintf("%s: %s", repo.Name, nameURL)
|
||||
}
|
||||
e.Reason = strings.Replace(e.Reason, idstr, fmt.Sprintf("%s [%s]", idstr, nameURL), -1)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
func ParseError(data []byte) Error {
|
||||
var e Error
|
||||
if err := json.Unmarshal(data, &e); err != nil {
|
||||
// dumping the error into the Reason can get noisy, but it's good for troubleshooting
|
||||
return Error{
|
||||
Kind: "InternalError",
|
||||
Reason: fmt.Sprintf("Failed to unmarshal dnf-json error output %q: %s", string(data), err.Error()),
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func run(dnfJsonCmd []string, req *Request) ([]byte, error) {
|
||||
if len(dnfJsonCmd) == 0 {
|
||||
return nil, fmt.Errorf("dnf-json command undefined")
|
||||
}
|
||||
ex := dnfJsonCmd[0]
|
||||
args := make([]string, len(dnfJsonCmd)-1)
|
||||
if len(dnfJsonCmd) > 1 {
|
||||
args = dnfJsonCmd[1:]
|
||||
}
|
||||
cmd := exec.Command(ex, args...)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.Stderr = os.Stderr
|
||||
stdout := new(bytes.Buffer)
|
||||
cmd.Stdout = stdout
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.NewEncoder(stdin).Encode(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdin.Close()
|
||||
|
||||
err = cmd.Wait()
|
||||
output := stdout.Bytes()
|
||||
if runError, ok := err.(*exec.ExitError); ok && runError.ExitCode() != 0 {
|
||||
return nil, parseError(output, req.Arguments.Repos)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
|
@ -1,656 +0,0 @@
|
|||
package dnfjson
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/mocks/rpmrepo"
|
||||
)
|
||||
|
||||
var forceDNF = flag.Bool("force-dnf", false, "force dnf testing, making them fail instead of skip if dnf isn't installed")
|
||||
|
||||
func dnfInstalled() bool {
|
||||
cmd := exec.Command("python3", "-c", "import dnf")
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to import dnf: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestDepsolver(t *testing.T) {
|
||||
if !*forceDNF {
|
||||
// dnf tests aren't forced: skip them if the dnf sniff check fails
|
||||
if !dnfInstalled() {
|
||||
t.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
s := rpmrepo.NewTestServer()
|
||||
defer s.Close()
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
tmpdir := t.TempDir()
|
||||
solver := NewSolver("platform:el9", "9", "x86_64", "rhel9.0", tmpdir)
|
||||
solver.SetDNFJSONPath("../../dnf-json")
|
||||
|
||||
{ // single depsolve
|
||||
pkgsets := []rpmmd.PackageSet{{Include: []string{"kernel", "vim-minimal", "tmux", "zsh"}, Repositories: []rpmmd.RepoConfig{s.RepoConfig}}} // everything you'll ever need
|
||||
|
||||
deps, err := solver.Depsolve(pkgsets)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exp := expectedResult(s.RepoConfig)
|
||||
assert.Equal(deps, exp)
|
||||
}
|
||||
|
||||
{ // chain depsolve of the same packages in order should produce the same result (at least in this case)
|
||||
pkgsets := []rpmmd.PackageSet{
|
||||
{Include: []string{"kernel"}, Repositories: []rpmmd.RepoConfig{s.RepoConfig}},
|
||||
{Include: []string{"vim-minimal", "tmux", "zsh"}, Repositories: []rpmmd.RepoConfig{s.RepoConfig}},
|
||||
}
|
||||
deps, err := solver.Depsolve(pkgsets)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exp := expectedResult(s.RepoConfig)
|
||||
assert.Equal(deps, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeDepsolveRequest(t *testing.T) {
|
||||
|
||||
baseOS := rpmmd.RepoConfig{
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
}
|
||||
appstream := rpmmd.RepoConfig{
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
}
|
||||
userRepo := rpmmd.RepoConfig{
|
||||
Name: "user-repo",
|
||||
BaseURLs: []string{"https://example.org/user-repo"},
|
||||
}
|
||||
userRepo2 := rpmmd.RepoConfig{
|
||||
Name: "user-repo-2",
|
||||
BaseURLs: []string{"https://example.org/user-repo-2"},
|
||||
}
|
||||
modHotfixRepo := rpmmd.RepoConfig{
|
||||
Name: "nginx",
|
||||
BaseURLs: []string{"https://example.org/nginx"},
|
||||
ModuleHotfixes: common.ToPtr(true),
|
||||
}
|
||||
tests := []struct {
|
||||
packageSets []rpmmd.PackageSet
|
||||
args []transactionArgs
|
||||
wantRepos []repoConfig
|
||||
err bool
|
||||
}{
|
||||
// single transaction
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{
|
||||
baseOS,
|
||||
appstream,
|
||||
},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2 transactions + package set specific repo
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg3"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), userRepo.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
{
|
||||
ID: userRepo.Hash(),
|
||||
Name: "user-repo",
|
||||
BaseURLs: []string{"https://example.org/user-repo"},
|
||||
repoHash: "1d3b23c311a5597ae217a0023eab3a401e7ba569066a0b91ffdcae04795af184",
|
||||
},
|
||||
},
|
||||
},
|
||||
// module hotfixes flag is passed
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, modHotfixRepo},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), modHotfixRepo.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
{
|
||||
ID: modHotfixRepo.Hash(),
|
||||
Name: "nginx",
|
||||
BaseURLs: []string{"https://example.org/nginx"},
|
||||
ModuleHotfixes: common.ToPtr(true),
|
||||
repoHash: "b7d998ee8657964c17709e35ea7eaaffe4c84f9e41cc05250a1d16e8352d52e4",
|
||||
},
|
||||
},
|
||||
},
|
||||
// 2 transactions + no package set specific repos
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg3"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3 transactions + package set specific repo used by 2nd and 3rd transaction
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg4"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg3"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), userRepo.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg4"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), userRepo.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
{
|
||||
ID: userRepo.Hash(),
|
||||
Name: "user-repo",
|
||||
BaseURLs: []string{"https://example.org/user-repo"},
|
||||
repoHash: "1d3b23c311a5597ae217a0023eab3a401e7ba569066a0b91ffdcae04795af184",
|
||||
},
|
||||
},
|
||||
},
|
||||
// 3 transactions + package set specific repo used by 2nd and 3rd transaction
|
||||
// + 3rd transaction using another repo
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg4"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo, userRepo2},
|
||||
},
|
||||
},
|
||||
args: []transactionArgs{
|
||||
{
|
||||
PackageSpecs: []string{"pkg1"},
|
||||
ExcludeSpecs: []string{"pkg2"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg3"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), userRepo.Hash()},
|
||||
},
|
||||
{
|
||||
PackageSpecs: []string{"pkg4"},
|
||||
RepoIDs: []string{baseOS.Hash(), appstream.Hash(), userRepo.Hash(), userRepo2.Hash()},
|
||||
},
|
||||
},
|
||||
wantRepos: []repoConfig{
|
||||
{
|
||||
ID: baseOS.Hash(),
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://example.org/baseos"},
|
||||
repoHash: "f177f580cf201f52d1c62968d5b85cddae3e06cb9d5058987c07de1dbd769d4b",
|
||||
},
|
||||
{
|
||||
ID: appstream.Hash(),
|
||||
Name: "appstream",
|
||||
BaseURLs: []string{"https://example.org/appstream"},
|
||||
repoHash: "5c4a57bbb1b6a1886291819f2ceb25eb7c92e80065bc986a75c5837cf3d55a1f",
|
||||
},
|
||||
{
|
||||
ID: userRepo.Hash(),
|
||||
Name: "user-repo",
|
||||
BaseURLs: []string{"https://example.org/user-repo"},
|
||||
repoHash: "1d3b23c311a5597ae217a0023eab3a401e7ba569066a0b91ffdcae04795af184",
|
||||
},
|
||||
{
|
||||
ID: userRepo2.Hash(),
|
||||
Name: "user-repo-2",
|
||||
BaseURLs: []string{"https://example.org/user-repo-2"},
|
||||
repoHash: "9fca2ee4a26933d0b2f8e318b398d5e2bff53cb8c14d3c7a8c47f4429ccb4c41",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Error: 3 transactions + 3rd one not using repo used by 2nd one
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg4"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo2},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
// Error: 3 transactions but last one doesn't specify user repos in 2nd
|
||||
{
|
||||
packageSets: []rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"pkg1"},
|
||||
Exclude: []string{"pkg2"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg3"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream, userRepo, userRepo2},
|
||||
},
|
||||
{
|
||||
Include: []string{"pkg4"},
|
||||
Repositories: []rpmmd.RepoConfig{baseOS, appstream},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
solver := NewSolver("", "", "", "", "")
|
||||
for idx, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
||||
req, _, err := solver.makeDepsolveRequest(tt.packageSets)
|
||||
if tt.err {
|
||||
assert.NotNilf(t, err, "expected an error, but got 'nil' instead")
|
||||
assert.Nilf(t, req, "got non-nill request, but expected an error")
|
||||
} else {
|
||||
assert.Nilf(t, err, "expected 'nil', but got error instead")
|
||||
assert.NotNilf(t, req, "expected non-nill request, but got 'nil' instead")
|
||||
|
||||
assert.Equal(t, tt.args, req.Arguments.Transactions)
|
||||
assert.Equal(t, tt.wantRepos, req.Arguments.Repos)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func expectedResult(repo rpmmd.RepoConfig) []rpmmd.PackageSpec {
|
||||
// need to change the url for the RemoteLocation and the repo ID since the port is different each time and we don't want to have a fixed one
|
||||
expectedTemplate := []rpmmd.PackageSpec{
|
||||
{Name: "acl", Epoch: 0, Version: "2.3.1", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/acl-2.3.1-3.el9.x86_64.rpm", Checksum: "sha256:986044c3837eddbc9231d7be5e5fc517e245296978b988a803bc9f9172fe84ea", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "alternatives", Epoch: 0, Version: "1.20", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/alternatives-1.20-2.el9.x86_64.rpm", Checksum: "sha256:1851d5f64ebaeac67c5c2d9e4adc1e73aa6433b44a167268a3510c3d056062db", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "audit-libs", Epoch: 0, Version: "3.0.7", Release: "100.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/audit-libs-3.0.7-100.el9.x86_64.rpm", Checksum: "sha256:a4bdda48abaedffeb74398cd55afbd00cb4153ae24bd2a3e6de9d87462df5ffa", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "basesystem", Epoch: 0, Version: "11", Release: "13.el9", Arch: "noarch", RemoteLocation: "%s/Packages/basesystem-11-13.el9.noarch.rpm", Checksum: "sha256:a7a687ef39dd28d01d34fab18ea7e3e87f649f6c202dded82260b7ea625b9973", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "bash", Epoch: 0, Version: "5.1.8", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/bash-5.1.8-2.el9.x86_64.rpm", Checksum: "sha256:3d45552ea940db0556dd2dc73e92c20c0d7cbc9e617f251904f20475d4ecc6b6", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "bzip2-libs", Epoch: 0, Version: "1.0.8", Release: "8.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/bzip2-libs-1.0.8-8.el9.x86_64.rpm", Checksum: "sha256:fabd6b5c065c2b9d4a8d39a938ae577d801de2ddc73c8cdf6f7803db29c28d0a", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "ca-certificates", Epoch: 0, Version: "2020.2.50", Release: "94.el9", Arch: "noarch", RemoteLocation: "%s/Packages/ca-certificates-2020.2.50-94.el9.noarch.rpm", Checksum: "sha256:3099471d984fb7d9e1cf42406eb08c154b34b8560742ed1f5eb9139f059c2d09", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "centos-gpg-keys", Epoch: 0, Version: "9.0", Release: "9.el9", Arch: "noarch", RemoteLocation: "%s/Packages/centos-gpg-keys-9.0-9.el9.noarch.rpm", Checksum: "sha256:2785ab660c124c9bda4ef4057e72d7fc73e8ac254ddd09a5541a6d323740dad7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "centos-stream-release", Epoch: 0, Version: "9.0", Release: "9.el9", Arch: "noarch", RemoteLocation: "%s/Packages/centos-stream-release-9.0-9.el9.noarch.rpm", Checksum: "sha256:44246cc9b62ac0fb833ece49cff6ac0a932234fcba26b8c895f42baebf0a19c2", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "centos-stream-repos", Epoch: 0, Version: "9.0", Release: "9.el9", Arch: "noarch", RemoteLocation: "%s/Packages/centos-stream-repos-9.0-9.el9.noarch.rpm", Checksum: "sha256:90208bb7dd1558a3311a28ea06d75ad7e83be3f223c5fb2eff1b9ac47bb98ebe", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "coreutils", Epoch: 0, Version: "8.32", Release: "31.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/coreutils-8.32-31.el9.x86_64.rpm", Checksum: "sha256:647a3b9a52df25cb2aaf7f3715b219839b4cf71913638c88172d925173280812", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "coreutils-common", Epoch: 0, Version: "8.32", Release: "31.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/coreutils-common-8.32-31.el9.x86_64.rpm", Checksum: "sha256:864b166ac6d55cad5010da369fd7ad4872f81c2c111867dfbf96ccf4c8273c7e", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "cpio", Epoch: 0, Version: "2.13", Release: "16.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/cpio-2.13-16.el9.x86_64.rpm", Checksum: "sha256:216b76d33443b732be42fe1d443e106a17e632ac9ca465928c37a8c0ede596a4", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "cracklib", Epoch: 0, Version: "2.9.6", Release: "27.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/cracklib-2.9.6-27.el9.x86_64.rpm", Checksum: "sha256:be9deb2efd06b4b2c1c130acae94c687161d04830119e65a989d904ba9fd1864", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "cracklib-dicts", Epoch: 0, Version: "2.9.6", Release: "27.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/cracklib-dicts-2.9.6-27.el9.x86_64.rpm", Checksum: "sha256:01df2a72fcdf988132e82764ce1a22a5a9513fa253b54e17d23058bdb53c2d85", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "crypto-policies", Epoch: 0, Version: "20220203", Release: "1.gitf03e75e.el9", Arch: "noarch", RemoteLocation: "%s/Packages/crypto-policies-20220203-1.gitf03e75e.el9.noarch.rpm", Checksum: "sha256:28d73d3800cb895b265bc0755c41241c593ebd7551a7da7f001f1e254b85c662", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "cryptsetup-libs", Epoch: 0, Version: "2.4.3", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/cryptsetup-libs-2.4.3-1.el9.x86_64.rpm", Checksum: "sha256:6919d88afdd2cf89982ec8edd4401ff93394a81873f81cf89bb273384f39104f", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "dbus", Epoch: 1, Version: "1.12.20", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/dbus-1.12.20-5.el9.x86_64.rpm", Checksum: "sha256:bb85bd28cc162e98da53b756b988ffd9350f4dbcc186f4c6962ae047e27f83d3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "dbus-broker", Epoch: 0, Version: "28", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/dbus-broker-28-5.el9.x86_64.rpm", Checksum: "sha256:e9efdcdcfe430e474e3a7f09596a0a5a4314692d9ae846bb1ca86ff88ef81038", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "dbus-common", Epoch: 1, Version: "1.12.20", Release: "5.el9", Arch: "noarch", RemoteLocation: "%s/Packages/dbus-common-1.12.20-5.el9.noarch.rpm", Checksum: "sha256:150048b6fdafd4271bd6badab3f8a2e56b86967266f890770eab7578289cc773", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "device-mapper", Epoch: 9, Version: "1.02.181", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/device-mapper-1.02.181-3.el9.x86_64.rpm", Checksum: "sha256:5d1cd7733f147020ef3a9e08fa2e9d74a25e0ac89dfbadc69912541286146feb", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "device-mapper-libs", Epoch: 9, Version: "1.02.181", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/device-mapper-libs-1.02.181-3.el9.x86_64.rpm", Checksum: "sha256:a716ccca85fad2885af4d099f8c213eb4617d637d8ca6cf7d2b483b9de88a5d3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "dracut", Epoch: 0, Version: "055", Release: "10.git20210824.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/dracut-055-10.git20210824.el9.x86_64.rpm", Checksum: "sha256:54015283e7f85fbee9d8a814c3bd60c7f81a6eb2ff480ca689e4526637a81c83", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "elfutils-default-yama-scope", Epoch: 0, Version: "0.186", Release: "1.el9", Arch: "noarch", RemoteLocation: "%s/Packages/elfutils-default-yama-scope-0.186-1.el9.noarch.rpm", Checksum: "sha256:0d2dcfaa16f83de78a251cf0b9a4c5e4ec7d4deb2e8d1cae7209be7745fabeb5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "elfutils-libelf", Epoch: 0, Version: "0.186", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/elfutils-libelf-0.186-1.el9.x86_64.rpm", Checksum: "sha256:0e295e6150b6929408ac29792ec5f3ebeb4a20607eb553177f0e4899b3008d63", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "elfutils-libs", Epoch: 0, Version: "0.186", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/elfutils-libs-0.186-1.el9.x86_64.rpm", Checksum: "sha256:bcc47b8ab496d3d11d772b037e022bc3a4ce3b080b7d1c24fa7f999426a6b8f3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "expat", Epoch: 0, Version: "2.2.10", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/expat-2.2.10-5.el9.x86_64.rpm", Checksum: "sha256:f97cd3c1e79b4dfff232ba0208c2e1a7d557608c1c37e8303de4f75387be9bb7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "filesystem", Epoch: 0, Version: "3.16", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/filesystem-3.16-2.el9.x86_64.rpm", Checksum: "sha256:b69a472751268a1b9acd566dc7aa486fc1d6c8cb6d23f36d6a6dfead62e71475", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "findutils", Epoch: 1, Version: "4.8.0", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/findutils-4.8.0-5.el9.x86_64.rpm", Checksum: "sha256:552548e6d6f9623ccd9d31bb185bba3a66730da6e9d02296b417d501356c3848", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "gdbm-libs", Epoch: 1, Version: "1.19", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/gdbm-libs-1.19-4.el9.x86_64.rpm", Checksum: "sha256:8cd5a78cab8783dd241c52c4fcda28fb111c443887dd6d0fe38385e8383c98b3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "glibc", Epoch: 0, Version: "2.34", Release: "21.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/glibc-2.34-21.el9.x86_64.rpm", Checksum: "sha256:6e40002c40b2e142dac88fba59d9893054b364585b2bc4b63ebf4cb3066616e2", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "glibc-common", Epoch: 0, Version: "2.34", Release: "21.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/glibc-common-2.34-21.el9.x86_64.rpm", Checksum: "sha256:606fda6e7bbe188920afcae1529967fc13c10763ed727d8ac6ce1037a8549228", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "glibc-gconv-extra", Epoch: 0, Version: "2.34", Release: "21.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/glibc-gconv-extra-2.34-21.el9.x86_64.rpm", Checksum: "sha256:cc159162a083a3adf927bcf36fe4c053f3dd3640ff2f7c544018d354e046eccb", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "glibc-minimal-langpack", Epoch: 0, Version: "2.34", Release: "21.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/glibc-minimal-langpack-2.34-21.el9.x86_64.rpm", Checksum: "sha256:bfec403288415b69acb3fd4bd014561d639673c7002c6968e3722e88cb104bdc", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "gmp", Epoch: 1, Version: "6.2.0", Release: "10.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/gmp-6.2.0-10.el9.x86_64.rpm", Checksum: "sha256:1a6ededc80029ef258288ddbf24bcce7c6228647841416950c88e3f14b7258a2", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "grep", Epoch: 0, Version: "3.6", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/grep-3.6-5.el9.x86_64.rpm", Checksum: "sha256:10a41b66b1fbd6eb055178e22c37199e5b49b4852e77c806f7af7211044a4a55", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "gzip", Epoch: 0, Version: "1.10", Release: "8.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/gzip-1.10-8.el9.x86_64.rpm", Checksum: "sha256:3b5ce98a03a3336a3f32ac7a0867fbc23da702e8618bfd20d49d882d42a460f4", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "json-c", Epoch: 0, Version: "0.14", Release: "11.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/json-c-0.14-11.el9.x86_64.rpm", Checksum: "sha256:1a75404c6bc8c1369914077dc99480e73bf13a40f15fd1cd8afc792b8600adf8", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kbd", Epoch: 0, Version: "2.4.0", Release: "8.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kbd-2.4.0-8.el9.x86_64.rpm", Checksum: "sha256:9c7395caebf76e15f496d9dc7690d772cb34f29d3f6626086b578565e412df51", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kbd-misc", Epoch: 0, Version: "2.4.0", Release: "8.el9", Arch: "noarch", RemoteLocation: "%s/Packages/kbd-misc-2.4.0-8.el9.noarch.rpm", Checksum: "sha256:2dda3fe56c9a5bce5880dca58d905682c5e9f94ee023e43a3e311d2d411e1849", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kernel", Epoch: 0, Version: "5.14.0", Release: "55.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kernel-5.14.0-55.el9.x86_64.rpm", Checksum: "sha256:be5dba9121cda9eac9cc8f20b24f7e0d198364b370546c3665e4e6ce70a335b4", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kernel-core", Epoch: 0, Version: "5.14.0", Release: "55.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kernel-core-5.14.0-55.el9.x86_64.rpm", Checksum: "sha256:0afe6e35348485ae2696e6170dcf34370f33fcf42a357fc815e332d939dd1025", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kernel-modules", Epoch: 0, Version: "5.14.0", Release: "55.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kernel-modules-5.14.0-55.el9.x86_64.rpm", Checksum: "sha256:0914a0cbe0304289e224789f8e50e3e48a2525eba742ad764a1901e8c1351fb5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kmod", Epoch: 0, Version: "28", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kmod-28-7.el9.x86_64.rpm", Checksum: "sha256:3d4bc7935959a109a10020d0d19a5e059719ae4c99c5f32d3020ff6da47d53ea", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kmod-libs", Epoch: 0, Version: "28", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kmod-libs-28-7.el9.x86_64.rpm", Checksum: "sha256:0727ff3131223446158aaec88cbf8f894a9e3592e73f231a1802629518eeb64b", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "kpartx", Epoch: 0, Version: "0.8.7", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/kpartx-0.8.7-4.el9.x86_64.rpm", Checksum: "sha256:8f05761c418a55f811404dc1515b131bafe9b1e3fe56274be6d880c8822984b5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libacl", Epoch: 0, Version: "2.3.1", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libacl-2.3.1-3.el9.x86_64.rpm", Checksum: "sha256:fd829e9a03f6d321313002d6fcb37ee0434f548aa75fcd3ecdbdd891115de6a7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libattr", Epoch: 0, Version: "2.5.1", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libattr-2.5.1-3.el9.x86_64.rpm", Checksum: "sha256:d4db095a015e84065f27a642ee7829cd1690041ba8c51501f908cc34760c9409", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libblkid", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libblkid-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:f5cf36e8081c2d72e9dd64dd1614155857dd6e71ebb2237e5b0e11ace5481bac", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libcap", Epoch: 0, Version: "2.48", Release: "8.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libcap-2.48-8.el9.x86_64.rpm", Checksum: "sha256:c41f91075ee8ca480c2631a485bcc74876b9317b4dc9bd66566da32313621bd7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libcap-ng", Epoch: 0, Version: "0.8.2", Release: "6.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libcap-ng-0.8.2-6.el9.x86_64.rpm", Checksum: "sha256:0ee8b2d02fd362223fcf36c11297e1f9ae939f76cef09c0bce9cad5f53287122", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libdb", Epoch: 0, Version: "5.3.28", Release: "53.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libdb-5.3.28-53.el9.x86_64.rpm", Checksum: "sha256:3a44d15d695944bde4e7290800b815f98bfd9cd6f6f868cec3e8991606f556d5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libeconf", Epoch: 0, Version: "0.4.1", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libeconf-0.4.1-2.el9.x86_64.rpm", Checksum: "sha256:1d6fe169e74daff38ad5b0d6424c4d1b14545d5974c39e4421d20838a68f5892", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libevent", Epoch: 0, Version: "2.1.12", Release: "6.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libevent-2.1.12-6.el9.x86_64.rpm", Checksum: "sha256:82179f6f214ddf523e143c16c3474ccf8832551c6305faf89edfbd83b3424d48", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libfdisk", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libfdisk-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:a41bad6e261c719224abfd6745ccb1d2a0cac9d024ca9656904001a38d7cd8c7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libffi", Epoch: 0, Version: "3.4.2", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libffi-3.4.2-7.el9.x86_64.rpm", Checksum: "sha256:f0ac4b6454d4018833dd10e3f437d8271c7c6a628d99b37e75b83af890b86bc4", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libgcc", Epoch: 0, Version: "11.2.1", Release: "9.1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libgcc-11.2.1-9.1.el9.x86_64.rpm", Checksum: "sha256:6fc0ea086ecf7ae65bdfc2e9ba6503ee9d9bf717f3c0a55c4bc9c99e12608edf", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libgcrypt", Epoch: 0, Version: "1.10.0", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libgcrypt-1.10.0-1.el9.x86_64.rpm", Checksum: "sha256:059533802d440244c1fb6f777e20ed445220cdc85300e164d8ffb0ecfdfb42f4", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libgpg-error", Epoch: 0, Version: "1.42", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libgpg-error-1.42-5.el9.x86_64.rpm", Checksum: "sha256:a1883804c376f737109f4dff06077d1912b90150a732d11be7bc5b3b67e512fe", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libkcapi", Epoch: 0, Version: "1.3.1", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libkcapi-1.3.1-3.el9.x86_64.rpm", Checksum: "sha256:9b4733e8a790b51d845cedfa67e6321fd5a2923dd0fb7ce1f5e630aa382ba3c1", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libkcapi-hmaccalc", Epoch: 0, Version: "1.3.1", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libkcapi-hmaccalc-1.3.1-3.el9.x86_64.rpm", Checksum: "sha256:1b39f1faa4a8813cbfa2650e82f6ea06a4248e0c493ce7e4829c7d892e7757ed", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libmount", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libmount-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:26191af0cc7acf9bb335ebd8b4ed357582165ee3be78fce9f4395f84ad2805ce", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libpwquality", Epoch: 0, Version: "1.4.4", Release: "8.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libpwquality-1.4.4-8.el9.x86_64.rpm", Checksum: "sha256:93f00e5efac1e3f1ecbc0d6a4c068772cb12912cd20c9ea58716d6c0cd004886", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libseccomp", Epoch: 0, Version: "2.5.2", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libseccomp-2.5.2-2.el9.x86_64.rpm", Checksum: "sha256:d5c1c4473ebf5fd9c605eb866118d7428cdec9b188db18e45545801cc2a689c3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libselinux", Epoch: 0, Version: "3.3", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libselinux-3.3-2.el9.x86_64.rpm", Checksum: "sha256:8e589b8408b04cbc19564620b229b6768edbaeb9090885d2273d84b8fc2f172b", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libsemanage", Epoch: 0, Version: "3.3", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libsemanage-3.3-1.el9.x86_64.rpm", Checksum: "sha256:7e62a0ed0a508486b565e48794a146022f344aeb6801834ad7c5afe6a97ef065", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libsepol", Epoch: 0, Version: "3.3", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libsepol-3.3-2.el9.x86_64.rpm", Checksum: "sha256:fc508147fe876706b61941a6ce554d7f7786f1ec3d097c4411fd6c7511acd289", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libsigsegv", Epoch: 0, Version: "2.13", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libsigsegv-2.13-4.el9.x86_64.rpm", Checksum: "sha256:931bd0ec7050e8c3b37a9bfb489e30af32486a3c77203f1e9113eeceaa3b0a3a", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libsmartcols", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libsmartcols-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:c62433784604a2e6571e0fcbdd4a2d60f059c5c15624207998c5f03b18d9d382", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libtasn1", Epoch: 0, Version: "4.16.0", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libtasn1-4.16.0-7.el9.x86_64.rpm", Checksum: "sha256:656031558c53da4a5b3ccfd883bd6d55996037891323152b1f07e8d1d5377406", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libutempter", Epoch: 0, Version: "1.2.1", Release: "6.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libutempter-1.2.1-6.el9.x86_64.rpm", Checksum: "sha256:fab361a9cba04490fd8b5664049983d1e57ebf7c1080804726ba600708524125", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libuuid", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libuuid-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:ffd8317ccc6f80524b7bf15a8157d82f36a2b9c7478bb04eb4a34c18d019e6fa", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libxcrypt", Epoch: 0, Version: "4.4.18", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libxcrypt-4.4.18-3.el9.x86_64.rpm", Checksum: "sha256:97e88678b420f619a44608fff30062086aa1dd6931ecbd54f21bba005ff1de1a", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "libzstd", Epoch: 0, Version: "1.5.0", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/libzstd-1.5.0-2.el9.x86_64.rpm", Checksum: "sha256:8282f33f06743ab88e36fea978559ac617c44cda14eb65495cad37505fdace41", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "linux-firmware", Epoch: 0, Version: "20211216", Release: "124.el9", Arch: "noarch", RemoteLocation: "%s/Packages/linux-firmware-20211216-124.el9.noarch.rpm", Checksum: "sha256:0524c9cd96db4d57a5c190165ee2f8ade91854e21c19c61c6bd3504030ca24fa", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "linux-firmware-whence", Epoch: 0, Version: "20211216", Release: "124.el9", Arch: "noarch", RemoteLocation: "%s/Packages/linux-firmware-whence-20211216-124.el9.noarch.rpm", Checksum: "sha256:7c58504c14979118ea36352982aaa5814ba0f448e17c1baddb811b9511315a58", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "lz4-libs", Epoch: 0, Version: "1.9.3", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/lz4-libs-1.9.3-5.el9.x86_64.rpm", Checksum: "sha256:cba6a63054d070956a182e33269ee245bcfbe87e3e605c27816519db762a66ad", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "ncurses-base", Epoch: 0, Version: "6.2", Release: "8.20210508.el9", Arch: "noarch", RemoteLocation: "%s/Packages/ncurses-base-6.2-8.20210508.el9.noarch.rpm", Checksum: "sha256:e4cc4a4a479b8c27776debba5c20e8ef21dc4b513da62a25ed09f88386ac08a8", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "ncurses-libs", Epoch: 0, Version: "6.2", Release: "8.20210508.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/ncurses-libs-6.2-8.20210508.el9.x86_64.rpm", Checksum: "sha256:328f4d50e66b00f24344ebe239817204fda8e68b1d988c6943abb3c36231beaa", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "openssl", Epoch: 1, Version: "3.0.1", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/openssl-3.0.1-5.el9.x86_64.rpm", Checksum: "sha256:aa9ee73fe806ddeafab4a5b0e370256e6c61f67f67114101d0735aed3c9a5eda", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "openssl-libs", Epoch: 1, Version: "3.0.1", Release: "5.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm", Checksum: "sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "openssl-pkcs11", Epoch: 0, Version: "0.4.11", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/openssl-pkcs11-0.4.11-7.el9.x86_64.rpm", Checksum: "sha256:4be41142a5fb2b4cd6d812e126838cffa57b7c84e5a79d65f66bb9cf1d2830a3", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "p11-kit", Epoch: 0, Version: "0.24.1", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/p11-kit-0.24.1-2.el9.x86_64.rpm", Checksum: "sha256:da167e41efd19cf25fd1c708b6f123d0203824324b14dd32401d49f2aa0ef0a6", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "p11-kit-trust", Epoch: 0, Version: "0.24.1", Release: "2.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/p11-kit-trust-0.24.1-2.el9.x86_64.rpm", Checksum: "sha256:ae9a633c58980328bef6358c6aa3c9ce0a65130c66fbfa4249922ddf5a3e2bb1", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "pam", Epoch: 0, Version: "1.5.1", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/pam-1.5.1-9.el9.x86_64.rpm", Checksum: "sha256:e64caedce811645ecdd78e7b4ae83c189aa884ff1ba6445374f39186c588c52c", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "pcre", Epoch: 0, Version: "8.44", Release: "3.el9.3", Arch: "x86_64", RemoteLocation: "%s/Packages/pcre-8.44-3.el9.3.x86_64.rpm", Checksum: "sha256:4a3cb61eb08c4f24e44756b6cb329812fe48d5c65c1fba546fadfa975045a8c5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "pcre2", Epoch: 0, Version: "10.37", Release: "3.el9.1", Arch: "x86_64", RemoteLocation: "%s/Packages/pcre2-10.37-3.el9.1.x86_64.rpm", Checksum: "sha256:441e71f24e95b7c319f02264db53f88aa49778b2214f7dd5c75f1a3838e72dea", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "pcre2-syntax", Epoch: 0, Version: "10.37", Release: "3.el9.1", Arch: "noarch", RemoteLocation: "%s/Packages/pcre2-syntax-10.37-3.el9.1.noarch.rpm", Checksum: "sha256:55d7d2bc962334c236418b78199a496b05dea4efdc89e52453154bd1a5ad0e2e", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "procps-ng", Epoch: 0, Version: "3.3.17", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/procps-ng-3.3.17-4.el9.x86_64.rpm", Checksum: "sha256:3a7cc3f6d6dfdaeb9e7bfdb06d968c3ae78246ff4f793c2d2e2bd71156961d69", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "readline", Epoch: 0, Version: "8.1", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/readline-8.1-4.el9.x86_64.rpm", Checksum: "sha256:49945472925286ad89b0575657b43f9224777e36b442f0c88df67f0b61e26aee", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "sed", Epoch: 0, Version: "4.8", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/sed-4.8-9.el9.x86_64.rpm", Checksum: "sha256:a2c5d9a7f569abb5a592df1c3aaff0441bf827c9d0e2df0ab42b6c443dbc475f", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "setup", Epoch: 0, Version: "2.13.7", Release: "6.el9", Arch: "noarch", RemoteLocation: "%s/Packages/setup-2.13.7-6.el9.noarch.rpm", Checksum: "sha256:c0202712e8ec928cf61f3d777f23859ba6de2e85786e928ee5472fdde570aeee", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "shadow-utils", Epoch: 2, Version: "4.9", Release: "3.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/shadow-utils-4.9-3.el9.x86_64.rpm", Checksum: "sha256:46fca2ed21478e5143434da4fbd47ca4599a885fab9f8636f9c7ba54942dd27e", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "systemd", Epoch: 0, Version: "249", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/systemd-249-9.el9.x86_64.rpm", Checksum: "sha256:eb57c1242f8a7d68e6c258f40b048d8b7bd0749254ac97b7f399b3bc8011a81b", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "systemd-libs", Epoch: 0, Version: "249", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/systemd-libs-249-9.el9.x86_64.rpm", Checksum: "sha256:708fbc3c7fd77a21e0b391e2a80d5c344962de9865e79514b2c89210ef06ba39", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "systemd-pam", Epoch: 0, Version: "249", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/systemd-pam-249-9.el9.x86_64.rpm", Checksum: "sha256:eb7af981fb95425c68ccb0e4b95688672afd3032b57002e65fda8f734a089556", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "systemd-rpm-macros", Epoch: 0, Version: "249", Release: "9.el9", Arch: "noarch", RemoteLocation: "%s/Packages/systemd-rpm-macros-249-9.el9.noarch.rpm", Checksum: "sha256:3552f7cc9077d5831f859f6cf721d419eccc83cb381d14a7a1483512272bd586", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "systemd-udev", Epoch: 0, Version: "249", Release: "9.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/systemd-udev-249-9.el9.x86_64.rpm", Checksum: "sha256:d9c47e7088b8d279b8fd51e2274df957c3b68a265b42123ef9bbeb339d5ce3ba", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "tmux", Epoch: 0, Version: "3.2a", Release: "4.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/tmux-3.2a-4.el9.x86_64.rpm", Checksum: "sha256:68074b673bfac39af1fbfc85d43bc1c111456db01a563bda6400ad485de5eb70", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "tzdata", Epoch: 0, Version: "2021e", Release: "1.el9", Arch: "noarch", RemoteLocation: "%s/Packages/tzdata-2021e-1.el9.noarch.rpm", Checksum: "sha256:42d89577a0f887c4baa162250862dea2c1830b1ced56c45ced9645ad8e2a3671", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "util-linux", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/util-linux-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:4ca41a925461daa936db284a59bf325ea061cdb39d8738e288cc19afe30a8ae8", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "util-linux-core", Epoch: 0, Version: "2.37.2", Release: "1.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/util-linux-core-2.37.2-1.el9.x86_64.rpm", Checksum: "sha256:0313682867c1d07785a6d02ff87e1899f484bd1ce6348fa5c673eca78c0da2bd", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "vim-minimal", Epoch: 2, Version: "8.2.2637", Release: "11.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/vim-minimal-8.2.2637-11.el9.x86_64.rpm", Checksum: "sha256:ab6e48c8a118bed88dc734aaf21e743b57e94d448f9e38745c3b777af96809c7", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "xz", Epoch: 0, Version: "5.2.5", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/xz-5.2.5-7.el9.x86_64.rpm", Checksum: "sha256:b1c2d99961e50bb46400caa528aab9c7b361f5754427fd05ae22a7b551bf2ce5", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "xz-libs", Epoch: 0, Version: "5.2.5", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/xz-libs-5.2.5-7.el9.x86_64.rpm", Checksum: "sha256:770819da28cce56e2e2b141b0eee1694d7f3dcf78a5700e1469436461399f001", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "zlib", Epoch: 0, Version: "1.2.11", Release: "31.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/zlib-1.2.11-31.el9.x86_64.rpm", Checksum: "sha256:1c59b113fda8863e9066cc5d01c6d00bd9c50c4650e1c5b932082c8886e185d1", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
{Name: "zsh", Epoch: 0, Version: "5.8", Release: "7.el9", Arch: "x86_64", RemoteLocation: "%s/Packages/zsh-5.8-7.el9.x86_64.rpm", Checksum: "sha256:133da157fbd2b43e4a41af3ba7bb5267cf9ebed0aaf8124a76e5eca948c37572", Secrets: "", CheckGPG: false, IgnoreSSL: true},
|
||||
}
|
||||
|
||||
exp := []rpmmd.PackageSpec(expectedTemplate)
|
||||
for idx := range exp {
|
||||
urlTemplate := exp[idx].RemoteLocation
|
||||
exp[idx].RemoteLocation = fmt.Sprintf(urlTemplate, strings.Join(repo.BaseURLs, ","))
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
func TestErrorRepoInfo(t *testing.T) {
|
||||
if !*forceDNF {
|
||||
// dnf tests aren't forced: skip them if the dnf sniff check fails
|
||||
if !dnfInstalled() {
|
||||
t.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
type testCase struct {
|
||||
repo rpmmd.RepoConfig
|
||||
expMsg string
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
repo: rpmmd.RepoConfig{
|
||||
Name: "",
|
||||
BaseURLs: []string{"https://0.0.0.0/baseos/repo"},
|
||||
Metalink: "https://0.0.0.0/baseos/metalink",
|
||||
},
|
||||
expMsg: "[https://0.0.0.0/baseos/repo]",
|
||||
},
|
||||
{
|
||||
repo: rpmmd.RepoConfig{
|
||||
Name: "baseos",
|
||||
BaseURLs: []string{"https://0.0.0.0/baseos/repo"},
|
||||
Metalink: "https://0.0.0.0/baseos/metalink",
|
||||
},
|
||||
expMsg: "[baseos: https://0.0.0.0/baseos/repo]",
|
||||
},
|
||||
{
|
||||
repo: rpmmd.RepoConfig{
|
||||
Name: "fedora",
|
||||
Metalink: "https://0.0.0.0/f35/metalink",
|
||||
},
|
||||
expMsg: "[fedora: https://0.0.0.0/f35/metalink]",
|
||||
},
|
||||
{
|
||||
repo: rpmmd.RepoConfig{
|
||||
Name: "",
|
||||
MirrorList: "https://0.0.0.0/baseos/mirrors",
|
||||
},
|
||||
expMsg: "[https://0.0.0.0/baseos/mirrors]",
|
||||
},
|
||||
}
|
||||
|
||||
solver := NewSolver("f38", "38", "x86_64", "fedora-38", "/tmp/cache")
|
||||
solver.SetDNFJSONPath("../../dnf-json")
|
||||
for idx, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) {
|
||||
_, err := solver.Depsolve([]rpmmd.PackageSet{
|
||||
{
|
||||
Include: []string{"osbuild"},
|
||||
Exclude: nil,
|
||||
Repositories: []rpmmd.RepoConfig{tc.repo},
|
||||
},
|
||||
})
|
||||
assert.Error(err)
|
||||
assert.Contains(err.Error(), tc.expMsg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoConfigHash(t *testing.T) {
|
||||
repos := []rpmmd.RepoConfig{
|
||||
{
|
||||
Id: "repoid-1",
|
||||
Name: "A test repository",
|
||||
BaseURLs: []string{"https://arepourl/"},
|
||||
IgnoreSSL: common.ToPtr(false),
|
||||
},
|
||||
{
|
||||
BaseURLs: []string{"https://adifferenturl/"},
|
||||
},
|
||||
}
|
||||
|
||||
solver := NewSolver("f38", "38", "x86_64", "fedora-38", "/tmp/cache")
|
||||
solver.SetDNFJSONPath("../../dnf-json")
|
||||
|
||||
rcs, err := solver.reposFromRPMMD(repos)
|
||||
assert.Nil(t, err)
|
||||
|
||||
hash := rcs[0].Hash()
|
||||
assert.Equal(t, 64, len(hash))
|
||||
|
||||
assert.NotEqual(t, hash, rcs[1].Hash())
|
||||
}
|
||||
|
||||
func TestRequestHash(t *testing.T) {
|
||||
solver := NewSolver("f38", "38", "x86_64", "fedora-38", "/tmp/cache")
|
||||
repos := []rpmmd.RepoConfig{
|
||||
rpmmd.RepoConfig{
|
||||
Name: "A test repository",
|
||||
BaseURLs: []string{"https://arepourl/"},
|
||||
IgnoreSSL: common.ToPtr(false),
|
||||
},
|
||||
}
|
||||
|
||||
req, err := solver.makeDumpRequest(repos)
|
||||
assert.Nil(t, err)
|
||||
hash := req.Hash()
|
||||
assert.Equal(t, 64, len(hash))
|
||||
|
||||
req, err = solver.makeSearchRequest(repos, []string{"package0*"})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 64, len(req.Hash()))
|
||||
assert.NotEqual(t, hash, req.Hash())
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/osbuild/images/pkg/dnfjson"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
)
|
||||
|
||||
func generatePackageList() rpmmd.PackageList {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/osbuild/images/pkg/distro"
|
||||
"github.com/osbuild/images/pkg/distrofactory"
|
||||
"github.com/osbuild/images/pkg/distroidparser"
|
||||
"github.com/osbuild/images/pkg/dnfjson"
|
||||
"github.com/osbuild/images/pkg/osbuild"
|
||||
"github.com/osbuild/images/pkg/ostree"
|
||||
"github.com/osbuild/images/pkg/reporegistry"
|
||||
|
|
@ -43,7 +44,6 @@ import (
|
|||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
"github.com/osbuild/osbuild-composer/internal/store"
|
||||
"github.com/osbuild/osbuild-composer/internal/target"
|
||||
"github.com/osbuild/osbuild-composer/internal/worker"
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import (
|
|||
"github.com/osbuild/images/pkg/distro"
|
||||
"github.com/osbuild/images/pkg/distro/test_distro"
|
||||
"github.com/osbuild/images/pkg/distrofactory"
|
||||
"github.com/osbuild/images/pkg/dnfjson"
|
||||
"github.com/osbuild/images/pkg/ostree"
|
||||
"github.com/osbuild/images/pkg/ostree/mock_ostree_repo"
|
||||
"github.com/osbuild/images/pkg/reporegistry"
|
||||
"github.com/osbuild/images/pkg/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"github.com/osbuild/osbuild-composer/internal/common"
|
||||
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
||||
dnfjson_mock "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson"
|
||||
rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd"
|
||||
"github.com/osbuild/osbuild-composer/internal/store"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue