Skip to content

Commit f559c18

Browse files
gicmoachilleas-k
authored andcommitted
plugins: support for repo package sets
This adds support for specifing the package sets for repositories; on the command line this can be done via `--repo-package-set` with and argument of `;` separated package set names. This will result in repo information being transported via dict instead of plain strings. Thus the hub plugin's schema was modified accordingly. Last but not least, the builder plugin now can decode these dicts and setup the repos accordingly. Test were added for plugins as well as the integration test changed to use this new feature. The first upstream commit that supports this feature is pinned.
1 parent 5d2f6c6 commit f559c18

File tree

7 files changed

+191
-11
lines changed

7 files changed

+191
-11
lines changed

plugins/builder/osbuild.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import urllib.parse
2525

2626
from string import Template
27-
from typing import Dict, List, Optional
27+
from typing import Dict, List, Optional, Union
2828

2929
import requests
3030
import koji
@@ -80,11 +80,21 @@ def as_dict(self, arch: str = ""):
8080

8181

8282
class Repository:
83-
def __init__(self, baseurl: str, gpgkey: str = None):
83+
def __init__(self, baseurl: str):
8484
self.baseurl = baseurl
85-
self.gpgkey = gpgkey
85+
self.gpgkey = None
86+
self.package_sets: List[str] = None
8687
self.rhsm = False
8788

89+
@classmethod
90+
def from_data(cls, data: Union[str, Dict]) -> "Repository":
91+
if isinstance(data, str):
92+
return cls(data)
93+
baseurl = data["baseurl"]
94+
repo = cls(baseurl)
95+
repo.package_sets = data.get("package_sets")
96+
return repo
97+
8898
def as_dict(self, arch: str = ""):
8999
tmp = Template(self.baseurl)
90100
url = tmp.substitute(arch=arch)
@@ -95,6 +105,8 @@ def as_dict(self, arch: str = ""):
95105
if self.gpgkey:
96106
res["gpg_key"] = self.gpgkey
97107
res["check_gpg"] = True
108+
if self.package_sets:
109+
res["package_sets"] = self.package_sets
98110
return res
99111

100112

@@ -532,7 +544,7 @@ def make_repos_for_target(self, target_info):
532544

533545
def make_repos_for_user(self, repos):
534546
self.logger.debug("user repo override: %s", str(repos))
535-
return [Repository(r) for r in repos]
547+
return [Repository.from_data(r) for r in repos]
536548

537549
def map_koji_api_image_type(self, image_type: str) -> str:
538550
mapped = KOJIAPI_IMAGE_TYPES.get(image_type)

plugins/cli/osbuild.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,40 @@
66
"""
77

88

9+
import optparse # pylint: disable=deprecated-module
910
from pprint import pprint
1011

1112
import koji
1213
import koji_cli.lib as kl
1314
from koji.plugin import export_cli
1415

1516

17+
def parse_repo(_option, _opt, value, parser):
18+
repo = parser.values.repo
19+
if repo and isinstance(repo[0], dict):
20+
repo.append({"baseurl": value})
21+
return
22+
23+
if not repo:
24+
parser.values.repo = repo = []
25+
repo.append(value)
26+
27+
28+
def parse_repo_package_set(_option, opt, value, parser):
29+
if not parser.values.repo:
30+
raise optparse.OptionValueError(f"Need '--repo' for {opt}")
31+
32+
repo = parser.values.repo.pop()
33+
if not isinstance(repo, dict):
34+
repo = {
35+
"baseurl": repo
36+
}
37+
ps = repo.get("package_sets", [])
38+
vals = set(map(lambda x: x.strip(), value.split(";")))
39+
repo["package_sets"] = list(sorted(set(ps).union(vals)))
40+
parser.values.repo.append(repo)
41+
42+
1643
def parse_args(argv):
1744
usage = ("usage: %prog osbuild-image [options] <name> <version> "
1845
"<distro> <target> <arch> [<arch> ...]")
@@ -28,10 +55,15 @@ def parse_args(argv):
2855
parser.add_option("--ostree-url", type=str, dest="ostree_url",
2956
help="URL to the OSTree repo for OSTree commit image types")
3057
parser.add_option("--release", help="Forcibly set the release field")
31-
parser.add_option("--repo", action="append",
58+
parser.add_option("--repo", action="callback", callback=parse_repo, nargs=1, type=str,
3259
help=("Specify a repo that will override the repo used to install "
3360
"RPMs in the image. May be used multiple times. The "
3461
"build tag repo associated with the target is the default."))
62+
parser.add_option("--repo-package-sets", dest="repo", nargs=1, type=str,
63+
action="callback", callback=parse_repo_package_set,
64+
help=("Specify the package sets for the last repository. "
65+
"Individual set items are separated by ';'. "
66+
"Maybe be used multiple times"))
3567
parser.add_option("--image-type", metavar="TYPE",
3668
help='Request an image-type [default: guest-image]',
3769
type=str, action="append", default=[])

plugins/hub/osbuild.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@
4949
"$ref": "#/definitions/options"
5050
}],
5151
"definitions": {
52+
"repo": {
53+
"title": "Repository options",
54+
"type": "object",
55+
"additionalProperties": False,
56+
"properties": {
57+
"baseurl": {
58+
"type": "string"
59+
},
60+
"package_sets": {
61+
"type": "array",
62+
"description": "Repositories",
63+
"items": {
64+
"type": "string"
65+
}
66+
}
67+
}
68+
},
5269
"ostree": {
5370
"title": "OSTree specific options",
5471
"type": "object",
@@ -78,7 +95,10 @@
7895
"type": "array",
7996
"description": "Repositories",
8097
"items": {
81-
"type": "string"
98+
"oneOf": [
99+
{"type": "string"},
100+
{"$ref": "#/definitions/repo"}
101+
]
82102
}
83103
},
84104
"release": {

schutzbot/deploy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function retry {
2121
# Variables for where to find osbuild-composer RPMs to test against
2222
DNF_REPO_BASEURL=http://osbuild-composer-repos.s3-website.us-east-2.amazonaws.com
2323
OSBUILD_COMMIT=bb30ffa0629e16ecff103aaaeb7e931f3f8ff79e # release 46
24-
OSBUILD_COMPOSER_COMMIT=631bd21ffeea03e7d4849f4d34430bde5a1b9db9 # commit that contains the cloud API integration
24+
OSBUILD_COMPOSER_COMMIT=346486cd3f06856efee5e982553e28fb387558e6 # commit that contains repo package sets
2525

2626
# Get OS details.
2727
source /etc/os-release

test/integration/test_koji.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212

1313
REPOS = {
1414
"fedora": [
15-
"http://download.fedoraproject.org/pub/fedora/linux/releases/$release/Everything/$arch/os"
15+
{"url": "http://download.fedoraproject.org/pub/fedora/linux/releases/$release/Everything/$arch/os"}
1616
],
1717
"rhel": [
18-
"http://download.devel.redhat.com/released/RHEL-8/$release/BaseOS/x86_64/os/",
19-
"http://download.devel.redhat.com/released/RHEL-8/$release/AppStream/x86_64/os/",
18+
{"url": "http://download.devel.redhat.com/released/RHEL-8/$release/BaseOS/x86_64/os/",
19+
"package_sets": "blueprint; build; packages"},
20+
{"url": "http://download.devel.redhat.com/released/RHEL-8/$release/AppStream/x86_64/os/",
21+
"package_sets": "blueprint; build; packages"},
2022
]
2123
}
2224

@@ -98,9 +100,13 @@ def test_compose(self):
98100

99101
repos = []
100102
for repo in REPOS[name]:
101-
tpl = string.Template(repo)
103+
baseurl = repo["url"]
104+
package_sets = repo.get("package_sets")
105+
tpl = string.Template(baseurl)
102106
url = tpl.safe_substitute({"release": release})
103107
repos += ["--repo", url]
108+
if package_sets:
109+
repos += ["--repo-package-sets", package_sets]
104110

105111
package = f"{name.lower()}-guest"
106112

test/unit/test_builder.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# koji hub plugin unit tests
33
#
44

5+
#pylint: disable=too-many-lines
6+
57
import configparser
68
import json
79
import os
@@ -960,3 +962,52 @@ def test_ostree_compose(self):
960962
ireq_refs = [i["ostree"]["ref"] for i in ireqs]
961963
diff = set(f"osbuild/{a}/r" for a in arches) ^ set(ireq_refs)
962964
self.assertEqual(diff, set())
965+
966+
@httpretty.activate
967+
def test_compose_repo_complex(self):
968+
# Check we properly handle ostree compose requests
969+
session = self.mock_session()
970+
handler = self.make_handler(session=session)
971+
972+
arches = ["x86_64", "s390x"]
973+
repos = [
974+
{"baseurl": "https://first.repo/$arch",
975+
"package_sets": ["a", "b", "c", "d"]},
976+
{"baseurl": "https://second.repo/$arch",
977+
"package_sets": ["alpha"]},
978+
{"baseurl": "https://third.repo/$arch"}
979+
]
980+
args = ["name", "version", "distro",
981+
["image_type"],
982+
"fedora-candidate",
983+
arches,
984+
{"repo": repos}]
985+
986+
url = self.plugin.DEFAULT_COMPOSER_URL
987+
composer = MockComposer(url, architectures=arches)
988+
composer.httpretty_regsiter()
989+
990+
res = handler.handler(*args)
991+
assert res, "invalid compose result"
992+
compose_id = res["composer"]["id"]
993+
compose = composer.composes.get(compose_id)
994+
self.assertIsNotNone(compose)
995+
996+
ireqs = compose["request"]["image_requests"]
997+
998+
# Check we got all the requested architectures
999+
ireq_arches = [i["architecture"] for i in ireqs]
1000+
diff = set(arches) ^ set(ireq_arches)
1001+
self.assertEqual(diff, set())
1002+
1003+
for ir in ireqs:
1004+
arch = ir["architecture"]
1005+
repos = ir["repositories"]
1006+
assert len(repos) == 3
1007+
1008+
for r in repos:
1009+
baseurl = r["baseurl"]
1010+
assert baseurl.endswith(arch)
1011+
if baseurl.startswith("https://first.repo"):
1012+
ps = r.get("package_sets")
1013+
assert ps and ps == ["a", "b", "c", "d"]

test/unit/test_cli.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,65 @@ def test_ostree_options(self):
182182
r = self.plugin.handle_osbuild_image(options, session, argv)
183183
self.assertEqual(r, 0)
184184

185+
def test_repo_package_sets(self):
186+
# Check we properly handle ostree specific options
187+
188+
argv = [
189+
# the required positional arguments
190+
"name", "version", "distro", "target", "arch1",
191+
# optional keyword arguments
192+
"--repo", "https://first.repo",
193+
"--repo-package-sets", "a; b; c",
194+
"--repo-package-sets", "d",
195+
"--repo", "https://second.repo",
196+
"--repo-package-sets", "alpha",
197+
"--repo", "https://third.repo", # NB: no `--repo-package-set`
198+
"--release", "20200202.n2",
199+
]
200+
201+
expected_args = ["name", "version", "distro",
202+
['guest-image'], # the default image type
203+
"target",
204+
['arch1']]
205+
206+
expected_opts = {
207+
"release": "20200202.n2",
208+
"repo": [
209+
{"baseurl": "https://first.repo",
210+
"package_sets": ["a", "b", "c", "d"]},
211+
{"baseurl": "https://second.repo",
212+
"package_sets": ["alpha"]},
213+
{"baseurl": "https://third.repo"}
214+
],
215+
}
216+
217+
task_result = {"compose_id": "42", "build_id": 23}
218+
task_id = 1
219+
koji_lib = self.mock_koji_lib()
220+
221+
options = self.mock_options()
222+
session = flexmock()
223+
224+
self.mock_session_add_valid_tag(session)
225+
226+
session.should_receive("osbuildImage") \
227+
.with_args(*expected_args, opts=expected_opts) \
228+
.and_return(task_id) \
229+
.once()
230+
231+
session.should_receive("logout") \
232+
.with_args() \
233+
.once()
234+
235+
session.should_receive("getTaskResult") \
236+
.with_args(task_id) \
237+
.and_return(task_result) \
238+
.once()
239+
240+
setattr(self.plugin, "kl", koji_lib)
241+
r = self.plugin.handle_osbuild_image(options, session, argv)
242+
self.assertEqual(r, 0)
243+
185244
def test_target_check(self):
186245
# unknown build target
187246
session = flexmock()

0 commit comments

Comments
 (0)