Skip to content

Commit d495309

Browse files
mcurlejFrostyX
authored andcommitted
Added bld2repo
Simple tool do download build requirements of a modular build from koji. Signed-off-by: Martin Curlej <[email protected]>
1 parent 69618f6 commit d495309

19 files changed

+7326
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/*/build/
44
/*/dist/
55
__pycache__/
6+
.vscode/

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ jobs:
2222
script: ./travis-stage.sh docker_pull && ./travis-stage.sh test
2323
- env: TOXENV=py39 MODULEMD_TOOL=modulemd_tools SITEPACKAGES=true
2424
script: ./travis-stage.sh docker_pull && ./travis-stage.sh test
25+
- env: TOXENV=py39 MODULEMD_TOOL=bld2repo SITEPACKAGES=true
26+
script: ./travis-stage.sh docker_pull && ./travis-stage.sh test
2527
- env: TOXENV=flake8
2628
script: ./travis-stage.sh docker_pull && ./travis-stage.sh test

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ For more information about `modulemd-merge`, please see
3939

4040

4141
### modulemd-generate-macros
42-
4342
Generate `module-build-macros` SRPM package, which is a central piece
4443
for building modules. It should be present in the buildroot before any
4544
other module packages are submitted to be built.
@@ -57,6 +56,13 @@ other tools yet, be cautious.**
5756
[modulemd_tools/README.md](modulemd_tools/README.md)
5857

5958

59+
### bld2repo
60+
Simple tool for dowloading build required RPMs of a modular build from koji.
61+
62+
For more information about `bld2repo`, please see
63+
[bld2repo/README.md](bld2repo/README.md)
64+
65+
6066
## Use cases
6167

6268
### Creating a module repository from a regular repository

bld2repo/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# bld2repo
2+
3+
Simple tool which will download modular build dependencies from a
4+
modular build in a koji instance and create a RPM repository out of it.
5+
6+
## usage
7+
8+
Provide a build id of modular build in koji and the cli tool will
9+
download all the rpms tagged in a build tag of a modular rpm build.
10+
11+
```
12+
$ bld2repo --build-id 1234
13+
```
14+
15+
After the download is finished the tool will call createrepo_c on the
16+
working directory, creating a rpm repository.
17+
18+
The defaults are set to the current fedora koji instance.
19+
If you are using a different koji instance please adjust those
20+
values through script arguments. For more information about script
21+
arguments please run:
22+
23+
```
24+
$ bld2repo -h
25+
```

bld2repo/bld2repo/__init__.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import os
2+
import sys
3+
import urllib.request
4+
import subprocess
5+
6+
import koji
7+
8+
9+
def get_buildrequire_pkgs_from_build(build_id, session, config):
10+
"""
11+
Function which queries koji for pkgs whom belong to a given build tag
12+
of a koji build and paires rpms with their respective package.
13+
14+
:param str build_id: build id of a build in koji.
15+
:param koji.ClientSession session: koji connection session object
16+
:return: list of pairings of package and rpms.
17+
:rtype: list
18+
"""
19+
print("Retriewing build metadata from: ", config.koji_host)
20+
build = session.getBuild(build_id)
21+
if not build:
22+
raise Exception("Build with id '{id}' has not been found.".format(id=build_id))
23+
24+
print("Build with the ID", build_id, "found.")
25+
tags = session.listTags(build["build_id"])
26+
27+
build_tag = [t["name"] for t in tags if t["name"].endswith("-build")]
28+
if not build_tag:
29+
raise Exception("Build with id '{id}' is not tagged in a 'build' tag.".format(id=build_id))
30+
31+
tag_data = session.listTaggedRPMS(build_tag[0], latest=True, inherit=True)
32+
33+
print("Found the build tag '", build_tag[0], "' associated with the build.")
34+
tagged_rpms = tag_data[0]
35+
tagged_pkgs = tag_data[1]
36+
pkgs = []
37+
archs = [config.arch, "noarch"]
38+
print("Gathering packages and rpms tagged in '", build_tag[0],"'.")
39+
for pkg in tagged_pkgs:
40+
pkg_md = {
41+
"package": pkg,
42+
"rpms": [],
43+
}
44+
45+
for rpm in tagged_rpms:
46+
if pkg["build_id"] == rpm["build_id"] and rpm["arch"] in archs:
47+
pkg_md["rpms"].append(rpm)
48+
tagged_rpms.remove(rpm)
49+
50+
if pkg_md["rpms"]:
51+
pkgs.append(pkg_md)
52+
print("Gathering done.")
53+
return pkgs
54+
55+
56+
def add_rpm_urls(pkgs, config):
57+
"""
58+
For each rpm from a package creates an download url and adds it to the package.
59+
60+
:param list pkgs: list of packages
61+
:return pkgs: list of packages and their rpms
62+
:rtype: list
63+
:return rpm_num: number of rpms
64+
:rtype: int
65+
"""
66+
rpm_num = 0
67+
for pkg in pkgs:
68+
build_path = koji.pathinfo.build(pkg["package"]).replace(koji.pathinfo.topdir, "")
69+
pkg["rpm_urls"] = []
70+
for rpm in pkg["rpms"]:
71+
rpm_num += 1
72+
rpm_filename = "-".join([rpm["name"], rpm["version"],
73+
rpm["release"]]) + "." + rpm["arch"] + ".rpm"
74+
rpm_url = config.koji_storage_host + build_path + "/" + rpm["arch"] + "/" + rpm_filename
75+
pkg["rpm_urls"].append(rpm_url)
76+
77+
78+
return pkgs, rpm_num
79+
80+
81+
def download_file(url, target_pkg_dir, filename):
82+
"""
83+
Wrapper function for downloading a file
84+
85+
:param str url: url to a file
86+
:param str target_pkg_dir: the dir where the file should be downloaded
87+
:param str filename: the name of the downloaded file
88+
"""
89+
abs_file_path = "/".join([target_pkg_dir, filename])
90+
try:
91+
urllib.request.urlretrieve(url, abs_file_path)
92+
except Exception as ex:
93+
raise Exception("HTTP error for url: {url}\nError message: {msg}\nHTTP code: {code}".format(
94+
url=ex.url, msg=ex.msg, code=ex.code))
95+
96+
97+
def rpm_bulk_download(pkgs, rpm_num, working_dir):
98+
"""
99+
Downloads all the rpms from which belong to a package.
100+
101+
:param list pkgs: list of pkgs with their rpms and urls to those rpms
102+
:param int rpm_num: number of all the rpms included in pkgs
103+
:param str working_dir: the dir where the rpms will be downloaded
104+
"""
105+
print("Starting bulk download of rpms...")
106+
rpm_dwnlded = 0
107+
108+
for pkg in pkgs:
109+
for url in pkg["rpm_urls"]:
110+
# we print the status of the download
111+
status = "[{rpms}/{left}]".format(rpms=rpm_num, left=rpm_dwnlded)
112+
print(status, end="\r", flush=True)
113+
# we store the rpm in a similar location as it is on the storage server
114+
url_parts = url.split("/")
115+
filename = url_parts[-1]
116+
arch = url_parts[-2]
117+
pkg_name = "-".join([url_parts[-5], url_parts[-4], url_parts[-3]])
118+
target_pkg_dir = "/".join([working_dir, pkg_name, arch])
119+
# we create the package dir if it is not created
120+
if not os.path.exists(target_pkg_dir):
121+
os.makedirs(target_pkg_dir)
122+
else:
123+
# if we downloaded the file already we skip
124+
file_path = target_pkg_dir + "/" + filename
125+
if os.path.exists(file_path):
126+
rpm_dwnlded += 1
127+
continue
128+
download_file(url, target_pkg_dir, filename)
129+
rpm_dwnlded += 1
130+
131+
# update the status last time to mark all of the rpms downloaded
132+
status = "[{rpms}/{left}]".format(rpms=rpm_num, left=rpm_dwnlded)
133+
print(status)
134+
print("Download successful.")
135+
136+
137+
def create_repo(working_dir):
138+
print("Calling createrepo_c...")
139+
args = ["createrepo_c", working_dir]
140+
subprocess.Popen(args, cwd=working_dir).communicate()
141+
print("Repo created.")
142+

bld2repo/bld2repo/cli.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import argparse
2+
import os
3+
4+
from bld2repo import (get_buildrequire_pkgs_from_build, add_rpm_urls, rpm_bulk_download,
5+
create_repo)
6+
from bld2repo.config import Config
7+
from bld2repo.utils import get_koji_session
8+
9+
10+
def get_arg_parser():
11+
description = (
12+
"When provided with a build id it will download all buildrequired RPMs"
13+
"of a modular koji build into the provided directory and create a repository out of it."
14+
)
15+
parser = argparse.ArgumentParser("bld2repo", description=description,
16+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
17+
parser.add_argument("-b", "--build-id", required=True, type=int, help="ID of a koji build.")
18+
parser.add_argument("-d", "--result-dir", help="Directory where the RPMs are downloaded.",
19+
default=".", type=str)
20+
parser.add_argument("-a", "--arch", help=("For which architecture the RPMs should be download"
21+
"ed. The 'noarch' is included automatically."),
22+
default="x86_64", type=str)
23+
parser.add_argument("-k", "--koji-host", type=str,
24+
default="https://koji.fedoraproject.org/kojihub",
25+
help="Koji host base url")
26+
parser.add_argument("-s", "--koji-storage-host", type=str,
27+
default="https://kojipkgs.fedoraproject.org",
28+
help=("Koji storage storage host base url. Server where the RPMs are "
29+
"stored. Required to be used together with `--koji-host`."))
30+
return parser
31+
32+
33+
def main():
34+
parser = get_arg_parser()
35+
args = parser.parse_args()
36+
37+
koji_host_dflt = parser.get_default("koji_host")
38+
39+
if args.koji_host != koji_host_dflt:
40+
koji_storage_dflt = parser.get_default("koji_storage_host")
41+
if args.koji_storage_host == koji_storage_dflt:
42+
parser.error("--koji-host and --koji-storage-host need to be used to together.")
43+
44+
config = Config(args.koji_host, args.koji_storage_host, args.arch, args.result_dir)
45+
session = get_koji_session(config)
46+
47+
pkgs = get_buildrequire_pkgs_from_build(args.build_id, session, config)
48+
49+
pkgs, rpm_num = add_rpm_urls(pkgs, config)
50+
51+
rpm_bulk_download(pkgs, rpm_num, config.arch)
52+
53+
create_repo(config.result_dir)
54+
55+
56+
if __name__ == "__main__":
57+
main()
58+

bld2repo/bld2repo/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Config():
2+
3+
def __init__(self, koji_host, koji_storage_host, arch, result_dir):
4+
self.koji_host = koji_host
5+
self.koji_storage_host = koji_storage_host
6+
self.arch = arch
7+
self.result_dir = result_dir
8+

bld2repo/bld2repo/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import koji
2+
3+
4+
def get_koji_session(config):
5+
6+
session = koji.ClientSession(config.koji_host)
7+
8+
return session
9+

bld2repo/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
koji

bld2repo/setup.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
import os.path
5+
6+
from setuptools import setup, find_packages
7+
8+
dirname = os.path.dirname(os.path.realpath(__file__))
9+
10+
with open(os.path.join(dirname, "README.md"), "r") as fh:
11+
long_description = fh.read()
12+
13+
with open(os.path.join(dirname, 'requirements.txt'), "r") as f:
14+
requires = f.read().splitlines()
15+
16+
setup(
17+
name='bld2repo',
18+
version='0.1',
19+
packages=find_packages(exclude=("tests",)),
20+
url='https://github.com/rpm-software-management/modulemd-tools',
21+
license='MIT',
22+
author='Martin Čurlej',
23+
author_email='[email protected]',
24+
description=('Tool to download modular build dependencies of '
25+
'a modular build from koji.'),
26+
long_description=long_description,
27+
long_description_content_type='text/markdown',
28+
install_requires=requires,
29+
entry_points={
30+
'console_scripts': [
31+
'bld2repo=bld2repo.cli:main'],
32+
},
33+
classifiers=[
34+
"Programming Language :: Python :: 3",
35+
"License :: OSI Approved :: MIT License",
36+
"Operating System :: POSIX :: Linux",
37+
],
38+
include_package_data=True,
39+
)
40+

0 commit comments

Comments
 (0)