Skip to content

Commit

Permalink
osbuild: add org.osbuild.librepo source wrapper
Browse files Browse the repository at this point in the history
This commit adds a wrapper for the org.osbuild.librepo source
to download RPMs.
  • Loading branch information
mvo5 committed Jan 14, 2025
1 parent 1031975 commit e0aeb90
Show file tree
Hide file tree
Showing 2 changed files with 364 additions and 0 deletions.
126 changes: 126 additions & 0 deletions pkg/osbuild/librepo_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package osbuild

import (
"fmt"

"github.com/osbuild/images/pkg/rpmmd"
)

// LibrepoSource wraps the org.osbuild.librepo osbuild source
type LibrepoSource struct {
Items map[string]*LibrepoSourceItem `json:"items"`
Options *LibrepoSourceOptions `json:"options"`
}

func NewLibrepoSource() *LibrepoSource {
return &LibrepoSource{
Items: make(map[string]*LibrepoSourceItem),
Options: &LibrepoSourceOptions{
Mirrors: make(map[string]*LibrepoSourceMirror),
},
}
}

// AddPackage adds the given *depsolved* pkg to the downloading. It
// needs the *depsovled* repoConfig so that the repoID of the two can
// be matched up
func (source *LibrepoSource) AddPackage(pkg rpmmd.PackageSpec, repos []rpmmd.RepoConfig) error {
pkgRepo, err := findRepoById(repos, pkg.RepoID)
if err != nil {
return fmt.Errorf("cannot find repo-id for pkg %v: %v", pkg.Name, err)
}
if _, ok := source.Options.Mirrors[pkgRepo.Id]; !ok {
mirror, err := mirrorFromRepo(pkgRepo)
if err != nil {
return err
}
source.Options.Mirrors[pkgRepo.Id] = mirror
}
mirror := source.Options.Mirrors[pkgRepo.Id]
if pkg.IgnoreSSL {
mirror.Insecure = true
}
// this should never happen but we should still check to avoid
// potential security issues
if mirror.Insecure && !pkg.IgnoreSSL {
return fmt.Errorf("inconsistent SSL configuration: package %v requires SSL but mirror %v is configured to ignore SSL", pkg.Name, mirror.URL)
}
if pkg.Secrets == "org.osbuild.rhsm" {
mirror.Secrets = &URLSecrets{
Name: "org.osbuild.rhsm",
}
} else if pkg.Secrets == "org.osbuild.mtls" {
mirror.Secrets = &URLSecrets{
Name: "org.osbuild.mtls",
}
}

item := &LibrepoSourceItem{
Path: pkg.Path,
MirrorID: pkgRepo.Id,
}
source.Items[pkg.Checksum] = item
return nil
}

func (LibrepoSource) isSource() {}

type LibrepoSourceItem struct {
Path string `json:"path"`
MirrorID string `json:"mirror"`
}

func findRepoById(repos []rpmmd.RepoConfig, repoID string) (*rpmmd.RepoConfig, error) {
type info struct {
ID string
Name string
}
var repoInfo []info
for _, repo := range repos {
repoInfo = append(repoInfo, info{repo.Id, repo.Name})
if repo.Id == repoID {
return &repo, nil
}
}

return nil, fmt.Errorf("cannot find repo-id %v in %+v", repoID, repoInfo)
}

func mirrorFromRepo(repo *rpmmd.RepoConfig) (*LibrepoSourceMirror, error) {
switch {
case repo.Metalink != "":
return &LibrepoSourceMirror{
URL: repo.Metalink,
Type: "metalink",
}, nil
case repo.MirrorList != "":
return &LibrepoSourceMirror{
URL: repo.MirrorList,
Type: "mirrorlist",
}, nil
case len(repo.BaseURLs) > 0:
return &LibrepoSourceMirror{
// XXX: should we pick a random one instead?
URL: repo.BaseURLs[0],
Type: "baseurl",
}, nil
}

return nil, fmt.Errorf("cannot find metalink, mirrorlist or baseurl for %+v", repo)
}

// librepoSourceOptions are the JSON options for source org.osbuild.librepo
type LibrepoSourceOptions struct {
Mirrors map[string]*LibrepoSourceMirror `json:"mirrors"`
}

type LibrepoSourceMirror struct {
URL string `json:"url"`
Type string `json:"type"`

Insecure bool `json:"insecure,omitempty"`
Secrets *URLSecrets `json:"secrets,omitempty"`

MaxParallels *int `json:"max-parallels,omitempty"`
FastestMirror bool `json:"fastest-mirror,omitempty"`
}
238 changes: 238 additions & 0 deletions pkg/osbuild/librepo_source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
package osbuild_test

import (
"encoding/json"
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/rpmmd"
)

var (
opensslPkg = rpmmd.PackageSpec{
Name: "openssl-libs",
Epoch: 1,
Version: "3.0.1",
Release: "5.el9",
Arch: "x86_64",
RemoteLocation: "https://example.com/repo/Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
Checksum: "sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666",
Path: "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
RepoID: "repo_id_metalink",
}

pamPkg = rpmmd.PackageSpec{
Name: "pam",
Epoch: 0,
Version: "1.5.1",
Release: "9.el9",
Arch: "x86_64",
RemoteLocation: "https://example.com/repo/Packages/pam-1.5.1-9.el9.x86_64.rpm",
Checksum: "sha256:e64caedce811645ecdd78e7b4ae83c189aa884ff1ba6445374f39186c588c52c",
Path: "Packages/pam-1.5.1-9.el9.x86_64.rpm",
RepoID: "repo_id_mirrorlist",
}

dbusPkg = rpmmd.PackageSpec{
Name: "dbus",
Epoch: 1,
Version: "1.12.20",
Release: "5.el9",
Arch: "x86_64",
RemoteLocation: "https://example.com/repo/Packages/dbus-1.12.20-5.el9.x86_64.rpm",
Checksum: "sha256:bb85bd28cc162e98da53b756b988ffd9350f4dbcc186f4c6962ae047e27f83d3",
Path: "Packages/dbus-1.12.20-5.el9.x86_64.rpm",
RepoID: "repo_id_baseurls",
}
)

var fakeRepos = []rpmmd.RepoConfig{
{
Id: "repo_id_metalink",
Name: "repo1",
Metalink: "http://example.com/metalink",
},
{
Id: "repo_id_mirrorlist",
Name: "repo1",
MirrorList: "http://example.com/mirrorlist",
},
{
Id: "repo_id_baseurls",
Name: "repo1",
BaseURLs: []string{"http://example.com/baseurl1"},
},
}

func TestLibrepoAddPackage(t *testing.T) {
sources := osbuild.NewLibrepoSource()
err := sources.AddPackage(opensslPkg, fakeRepos)
assert.NoError(t, err)
err = sources.AddPackage(pamPkg, fakeRepos)
assert.NoError(t, err)
err = sources.AddPackage(dbusPkg, fakeRepos)
assert.NoError(t, err)

expectedJSON := `{
"items": {
"sha256:bb85bd28cc162e98da53b756b988ffd9350f4dbcc186f4c6962ae047e27f83d3": {
"path": "Packages/dbus-1.12.20-5.el9.x86_64.rpm",
"mirror": "repo_id_baseurls"
},
"sha256:e64caedce811645ecdd78e7b4ae83c189aa884ff1ba6445374f39186c588c52c": {
"path": "Packages/pam-1.5.1-9.el9.x86_64.rpm",
"mirror": "repo_id_mirrorlist"
},
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
"mirror": "repo_id_metalink"
}
},
"options": {
"mirrors": {
"repo_id_baseurls": {
"url": "http://example.com/baseurl1",
"type": "baseurl"
},
"repo_id_metalink": {
"url": "http://example.com/metalink",
"type": "metalink"
},
"repo_id_mirrorlist": {
"url": "http://example.com/mirrorlist",
"type": "mirrorlist"
}
}
}
}`
b, err := json.MarshalIndent(sources, "", " ")
assert.NoError(t, err)
assert.Equal(t, expectedJSON, string(b))
}

func TestLibrepoInsecure(t *testing.T) {
pkg := opensslPkg
pkg.IgnoreSSL = true

sources := osbuild.NewLibrepoSource()
err := sources.AddPackage(pkg, fakeRepos)
assert.NoError(t, err)

expectedJSON := `{
"items": {
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
"mirror": "repo_id_metalink"
}
},
"options": {
"mirrors": {
"repo_id_metalink": {
"url": "http://example.com/metalink",
"type": "metalink",
"insecure": true
}
}
}
}`
b, err := json.MarshalIndent(sources, "", " ")
assert.NoError(t, err)
assert.Equal(t, expectedJSON, string(b))
}

func TestLibrepoSecrets(t *testing.T) {
for _, secret := range []string{"org.osbuild.rhsm", "org.osbuild.mtls"} {
pkg := opensslPkg
pkg.Secrets = secret

sources := osbuild.NewLibrepoSource()
err := sources.AddPackage(pkg, fakeRepos)
assert.NoError(t, err)

expectedJSON := fmt.Sprintf(`{
"items": {
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
"mirror": "repo_id_metalink"
}
},
"options": {
"mirrors": {
"repo_id_metalink": {
"url": "http://example.com/metalink",
"type": "metalink",
"secrets": {
"name": "%s"
}
}
}
}
}`, secret)
b, err := json.MarshalIndent(sources, "", " ")
assert.NoError(t, err)
assert.Equal(t, expectedJSON, string(b))
}
}

func TestLibrepoJsonMinimal(t *testing.T) {
expectedJSON := `{
"url": "http://example.com",
"type": "metalink"
}`
sourceMirror := osbuild.LibrepoSourceMirror{
URL: "http://example.com",
Type: "metalink",
}
b, err := json.MarshalIndent(sourceMirror, "", " ")
assert.NoError(t, err)
assert.Equal(t, expectedJSON, string(b))
}

func TestLibrepoJsonFull(t *testing.T) {
expectedJSON := `{
"url": "http://example.com",
"type": "metalink",
"insecure": true,
"secrets": {
"name": "org.osbuild.mtls"
},
"max-parallels": 10,
"fastest-mirror": true
}`
sourceMirror := osbuild.LibrepoSourceMirror{
URL: "http://example.com",
Type: "metalink",
Insecure: true,
Secrets: &osbuild.URLSecrets{Name: "org.osbuild.mtls"},
MaxParallels: common.ToPtr(10),
FastestMirror: true,
}
b, err := json.MarshalIndent(sourceMirror, "", " ")
assert.NoError(t, err)
assert.Equal(t, expectedJSON, string(b))
}

func TestLibrepoRepoIdNotFound(t *testing.T) {
pkg := opensslPkg
pkg.RepoID = "invalid_repo_id"

sources := osbuild.NewLibrepoSource()
err := sources.AddPackage(pkg, fakeRepos)
assert.EqualError(t, err, `cannot find repo-id for pkg openssl-libs: cannot find repo-id invalid_repo_id in [{ID:repo_id_metalink Name:repo1} {ID:repo_id_mirrorlist Name:repo1} {ID:repo_id_baseurls Name:repo1}]`)
}

func TestLibrepoInconsistentSSLConfiguration(t *testing.T) {
pkg := opensslPkg
pkg.IgnoreSSL = true

sources := osbuild.NewLibrepoSource()
err := sources.AddPackage(pkg, fakeRepos)
assert.NoError(t, err)
pkg.IgnoreSSL = false
err = sources.AddPackage(pkg, fakeRepos)
assert.EqualError(t, err, `inconsistent SSL configuration: package openssl-libs requires SSL but mirror http://example.com/metalink is configured to ignore SSL`)
}

0 comments on commit e0aeb90

Please sign in to comment.