Skip to content

Commit e0aeb90

Browse files
committed
osbuild: add org.osbuild.librepo source wrapper
This commit adds a wrapper for the org.osbuild.librepo source to download RPMs.
1 parent 1031975 commit e0aeb90

2 files changed

Lines changed: 364 additions & 0 deletions

File tree

pkg/osbuild/librepo_source.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package osbuild
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/osbuild/images/pkg/rpmmd"
7+
)
8+
9+
// LibrepoSource wraps the org.osbuild.librepo osbuild source
10+
type LibrepoSource struct {
11+
Items map[string]*LibrepoSourceItem `json:"items"`
12+
Options *LibrepoSourceOptions `json:"options"`
13+
}
14+
15+
func NewLibrepoSource() *LibrepoSource {
16+
return &LibrepoSource{
17+
Items: make(map[string]*LibrepoSourceItem),
18+
Options: &LibrepoSourceOptions{
19+
Mirrors: make(map[string]*LibrepoSourceMirror),
20+
},
21+
}
22+
}
23+
24+
// AddPackage adds the given *depsolved* pkg to the downloading. It
25+
// needs the *depsovled* repoConfig so that the repoID of the two can
26+
// be matched up
27+
func (source *LibrepoSource) AddPackage(pkg rpmmd.PackageSpec, repos []rpmmd.RepoConfig) error {
28+
pkgRepo, err := findRepoById(repos, pkg.RepoID)
29+
if err != nil {
30+
return fmt.Errorf("cannot find repo-id for pkg %v: %v", pkg.Name, err)
31+
}
32+
if _, ok := source.Options.Mirrors[pkgRepo.Id]; !ok {
33+
mirror, err := mirrorFromRepo(pkgRepo)
34+
if err != nil {
35+
return err
36+
}
37+
source.Options.Mirrors[pkgRepo.Id] = mirror
38+
}
39+
mirror := source.Options.Mirrors[pkgRepo.Id]
40+
if pkg.IgnoreSSL {
41+
mirror.Insecure = true
42+
}
43+
// this should never happen but we should still check to avoid
44+
// potential security issues
45+
if mirror.Insecure && !pkg.IgnoreSSL {
46+
return fmt.Errorf("inconsistent SSL configuration: package %v requires SSL but mirror %v is configured to ignore SSL", pkg.Name, mirror.URL)
47+
}
48+
if pkg.Secrets == "org.osbuild.rhsm" {
49+
mirror.Secrets = &URLSecrets{
50+
Name: "org.osbuild.rhsm",
51+
}
52+
} else if pkg.Secrets == "org.osbuild.mtls" {
53+
mirror.Secrets = &URLSecrets{
54+
Name: "org.osbuild.mtls",
55+
}
56+
}
57+
58+
item := &LibrepoSourceItem{
59+
Path: pkg.Path,
60+
MirrorID: pkgRepo.Id,
61+
}
62+
source.Items[pkg.Checksum] = item
63+
return nil
64+
}
65+
66+
func (LibrepoSource) isSource() {}
67+
68+
type LibrepoSourceItem struct {
69+
Path string `json:"path"`
70+
MirrorID string `json:"mirror"`
71+
}
72+
73+
func findRepoById(repos []rpmmd.RepoConfig, repoID string) (*rpmmd.RepoConfig, error) {
74+
type info struct {
75+
ID string
76+
Name string
77+
}
78+
var repoInfo []info
79+
for _, repo := range repos {
80+
repoInfo = append(repoInfo, info{repo.Id, repo.Name})
81+
if repo.Id == repoID {
82+
return &repo, nil
83+
}
84+
}
85+
86+
return nil, fmt.Errorf("cannot find repo-id %v in %+v", repoID, repoInfo)
87+
}
88+
89+
func mirrorFromRepo(repo *rpmmd.RepoConfig) (*LibrepoSourceMirror, error) {
90+
switch {
91+
case repo.Metalink != "":
92+
return &LibrepoSourceMirror{
93+
URL: repo.Metalink,
94+
Type: "metalink",
95+
}, nil
96+
case repo.MirrorList != "":
97+
return &LibrepoSourceMirror{
98+
URL: repo.MirrorList,
99+
Type: "mirrorlist",
100+
}, nil
101+
case len(repo.BaseURLs) > 0:
102+
return &LibrepoSourceMirror{
103+
// XXX: should we pick a random one instead?
104+
URL: repo.BaseURLs[0],
105+
Type: "baseurl",
106+
}, nil
107+
}
108+
109+
return nil, fmt.Errorf("cannot find metalink, mirrorlist or baseurl for %+v", repo)
110+
}
111+
112+
// librepoSourceOptions are the JSON options for source org.osbuild.librepo
113+
type LibrepoSourceOptions struct {
114+
Mirrors map[string]*LibrepoSourceMirror `json:"mirrors"`
115+
}
116+
117+
type LibrepoSourceMirror struct {
118+
URL string `json:"url"`
119+
Type string `json:"type"`
120+
121+
Insecure bool `json:"insecure,omitempty"`
122+
Secrets *URLSecrets `json:"secrets,omitempty"`
123+
124+
MaxParallels *int `json:"max-parallels,omitempty"`
125+
FastestMirror bool `json:"fastest-mirror,omitempty"`
126+
}

pkg/osbuild/librepo_source_test.go

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package osbuild_test
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
10+
"github.com/osbuild/images/internal/common"
11+
"github.com/osbuild/images/pkg/osbuild"
12+
"github.com/osbuild/images/pkg/rpmmd"
13+
)
14+
15+
var (
16+
opensslPkg = rpmmd.PackageSpec{
17+
Name: "openssl-libs",
18+
Epoch: 1,
19+
Version: "3.0.1",
20+
Release: "5.el9",
21+
Arch: "x86_64",
22+
RemoteLocation: "https://example.com/repo/Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
23+
Checksum: "sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666",
24+
Path: "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
25+
RepoID: "repo_id_metalink",
26+
}
27+
28+
pamPkg = rpmmd.PackageSpec{
29+
Name: "pam",
30+
Epoch: 0,
31+
Version: "1.5.1",
32+
Release: "9.el9",
33+
Arch: "x86_64",
34+
RemoteLocation: "https://example.com/repo/Packages/pam-1.5.1-9.el9.x86_64.rpm",
35+
Checksum: "sha256:e64caedce811645ecdd78e7b4ae83c189aa884ff1ba6445374f39186c588c52c",
36+
Path: "Packages/pam-1.5.1-9.el9.x86_64.rpm",
37+
RepoID: "repo_id_mirrorlist",
38+
}
39+
40+
dbusPkg = rpmmd.PackageSpec{
41+
Name: "dbus",
42+
Epoch: 1,
43+
Version: "1.12.20",
44+
Release: "5.el9",
45+
Arch: "x86_64",
46+
RemoteLocation: "https://example.com/repo/Packages/dbus-1.12.20-5.el9.x86_64.rpm",
47+
Checksum: "sha256:bb85bd28cc162e98da53b756b988ffd9350f4dbcc186f4c6962ae047e27f83d3",
48+
Path: "Packages/dbus-1.12.20-5.el9.x86_64.rpm",
49+
RepoID: "repo_id_baseurls",
50+
}
51+
)
52+
53+
var fakeRepos = []rpmmd.RepoConfig{
54+
{
55+
Id: "repo_id_metalink",
56+
Name: "repo1",
57+
Metalink: "http://example.com/metalink",
58+
},
59+
{
60+
Id: "repo_id_mirrorlist",
61+
Name: "repo1",
62+
MirrorList: "http://example.com/mirrorlist",
63+
},
64+
{
65+
Id: "repo_id_baseurls",
66+
Name: "repo1",
67+
BaseURLs: []string{"http://example.com/baseurl1"},
68+
},
69+
}
70+
71+
func TestLibrepoAddPackage(t *testing.T) {
72+
sources := osbuild.NewLibrepoSource()
73+
err := sources.AddPackage(opensslPkg, fakeRepos)
74+
assert.NoError(t, err)
75+
err = sources.AddPackage(pamPkg, fakeRepos)
76+
assert.NoError(t, err)
77+
err = sources.AddPackage(dbusPkg, fakeRepos)
78+
assert.NoError(t, err)
79+
80+
expectedJSON := `{
81+
"items": {
82+
"sha256:bb85bd28cc162e98da53b756b988ffd9350f4dbcc186f4c6962ae047e27f83d3": {
83+
"path": "Packages/dbus-1.12.20-5.el9.x86_64.rpm",
84+
"mirror": "repo_id_baseurls"
85+
},
86+
"sha256:e64caedce811645ecdd78e7b4ae83c189aa884ff1ba6445374f39186c588c52c": {
87+
"path": "Packages/pam-1.5.1-9.el9.x86_64.rpm",
88+
"mirror": "repo_id_mirrorlist"
89+
},
90+
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
91+
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
92+
"mirror": "repo_id_metalink"
93+
}
94+
},
95+
"options": {
96+
"mirrors": {
97+
"repo_id_baseurls": {
98+
"url": "http://example.com/baseurl1",
99+
"type": "baseurl"
100+
},
101+
"repo_id_metalink": {
102+
"url": "http://example.com/metalink",
103+
"type": "metalink"
104+
},
105+
"repo_id_mirrorlist": {
106+
"url": "http://example.com/mirrorlist",
107+
"type": "mirrorlist"
108+
}
109+
}
110+
}
111+
}`
112+
b, err := json.MarshalIndent(sources, "", " ")
113+
assert.NoError(t, err)
114+
assert.Equal(t, expectedJSON, string(b))
115+
}
116+
117+
func TestLibrepoInsecure(t *testing.T) {
118+
pkg := opensslPkg
119+
pkg.IgnoreSSL = true
120+
121+
sources := osbuild.NewLibrepoSource()
122+
err := sources.AddPackage(pkg, fakeRepos)
123+
assert.NoError(t, err)
124+
125+
expectedJSON := `{
126+
"items": {
127+
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
128+
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
129+
"mirror": "repo_id_metalink"
130+
}
131+
},
132+
"options": {
133+
"mirrors": {
134+
"repo_id_metalink": {
135+
"url": "http://example.com/metalink",
136+
"type": "metalink",
137+
"insecure": true
138+
}
139+
}
140+
}
141+
}`
142+
b, err := json.MarshalIndent(sources, "", " ")
143+
assert.NoError(t, err)
144+
assert.Equal(t, expectedJSON, string(b))
145+
}
146+
147+
func TestLibrepoSecrets(t *testing.T) {
148+
for _, secret := range []string{"org.osbuild.rhsm", "org.osbuild.mtls"} {
149+
pkg := opensslPkg
150+
pkg.Secrets = secret
151+
152+
sources := osbuild.NewLibrepoSource()
153+
err := sources.AddPackage(pkg, fakeRepos)
154+
assert.NoError(t, err)
155+
156+
expectedJSON := fmt.Sprintf(`{
157+
"items": {
158+
"sha256:fcf2515ec9115551c99d552da721803ecbca23b7ae5a974309975000e8bef666": {
159+
"path": "Packages/openssl-libs-3.0.1-5.el9.x86_64.rpm",
160+
"mirror": "repo_id_metalink"
161+
}
162+
},
163+
"options": {
164+
"mirrors": {
165+
"repo_id_metalink": {
166+
"url": "http://example.com/metalink",
167+
"type": "metalink",
168+
"secrets": {
169+
"name": "%s"
170+
}
171+
}
172+
}
173+
}
174+
}`, secret)
175+
b, err := json.MarshalIndent(sources, "", " ")
176+
assert.NoError(t, err)
177+
assert.Equal(t, expectedJSON, string(b))
178+
}
179+
}
180+
181+
func TestLibrepoJsonMinimal(t *testing.T) {
182+
expectedJSON := `{
183+
"url": "http://example.com",
184+
"type": "metalink"
185+
}`
186+
sourceMirror := osbuild.LibrepoSourceMirror{
187+
URL: "http://example.com",
188+
Type: "metalink",
189+
}
190+
b, err := json.MarshalIndent(sourceMirror, "", " ")
191+
assert.NoError(t, err)
192+
assert.Equal(t, expectedJSON, string(b))
193+
}
194+
195+
func TestLibrepoJsonFull(t *testing.T) {
196+
expectedJSON := `{
197+
"url": "http://example.com",
198+
"type": "metalink",
199+
"insecure": true,
200+
"secrets": {
201+
"name": "org.osbuild.mtls"
202+
},
203+
"max-parallels": 10,
204+
"fastest-mirror": true
205+
}`
206+
sourceMirror := osbuild.LibrepoSourceMirror{
207+
URL: "http://example.com",
208+
Type: "metalink",
209+
Insecure: true,
210+
Secrets: &osbuild.URLSecrets{Name: "org.osbuild.mtls"},
211+
MaxParallels: common.ToPtr(10),
212+
FastestMirror: true,
213+
}
214+
b, err := json.MarshalIndent(sourceMirror, "", " ")
215+
assert.NoError(t, err)
216+
assert.Equal(t, expectedJSON, string(b))
217+
}
218+
219+
func TestLibrepoRepoIdNotFound(t *testing.T) {
220+
pkg := opensslPkg
221+
pkg.RepoID = "invalid_repo_id"
222+
223+
sources := osbuild.NewLibrepoSource()
224+
err := sources.AddPackage(pkg, fakeRepos)
225+
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}]`)
226+
}
227+
228+
func TestLibrepoInconsistentSSLConfiguration(t *testing.T) {
229+
pkg := opensslPkg
230+
pkg.IgnoreSSL = true
231+
232+
sources := osbuild.NewLibrepoSource()
233+
err := sources.AddPackage(pkg, fakeRepos)
234+
assert.NoError(t, err)
235+
pkg.IgnoreSSL = false
236+
err = sources.AddPackage(pkg, fakeRepos)
237+
assert.EqualError(t, err, `inconsistent SSL configuration: package openssl-libs requires SSL but mirror http://example.com/metalink is configured to ignore SSL`)
238+
}

0 commit comments

Comments
 (0)