plugin/builder: create proper composer client

Make a proper client for the osbuild composer koji API and use it.
This commit is contained in:
Christian Kellner 2020-09-05 17:27:55 +02:00
parent d5918bd789
commit 025d3b1902

View file

@ -1,38 +1,167 @@
import urllib.request
#!/usr/bin/python3
import enum
import json
import sys
import time
import urllib.parse
import urllib.request
from string import Template
from typing import Dict, List
import koji
from koji.tasks import BaseTaskHandler
def compose_request(distro, koji):
req = {
"distribution": distro,
"koji": {
"server": koji
},
"image_requests": [{
"architecture": "x86_64",
"image_type": "qcow2",
"repositories": [{
"baseurl": "http://download.fedoraproject.org/pub/fedora/linux/releases/32/Everything/x86_64/os/"
}]
}]
}
class Repository:
def __init__(self, baseurl: str, gpgkey: str = None):
self.baseurl = baseurl
self.gpgkey = gpgkey
return req
def as_dict(self, arch: str = ""):
tmp = Template(self.baseurl)
url = tmp.substitute(arch=arch)
res = {"baseurl": url}
if self.gpgkey:
res["gpgkey"] = self.gpgkey
return res
class ImageRequest:
def __init__(self, arch: str, image_type: str, repos: List):
self.architecture = arch
self.image_type = image_type
self.repositories = repos
def as_dict(self):
arch = self.architecture
return {
"architecture": self.architecture,
"image_type": self.image_type,
"repositories": [
repo.as_dict(arch) for repo in self.repositories
]
}
class ComposeRequest:
def __init__(self, distro: str, images: ImageRequest, koji: str):
self.distribution = distro
self.image_requests = images
self.koji = koji
def as_dict(self):
return {
"distribution": self.distribution,
"koji": {
"server": str(self.koji)
},
"image_requests": [
img.as_dict() for img in self.image_requests
]
}
def to_json(self, encoding=None):
data = json.dumps(self.as_dict())
if encoding:
data = data.encode('utf-8')
return data
class ImageStatus(enum.Enum):
SUCCESS = "success"
FAILED = "failed"
PENDING = "pending"
BUILDING = "building"
UPLOADING = "uploading"
WAITING = "waiting"
FINISHED = "finished"
RUNNING = "running"
class ComposeStatus:
SUCCESS = "success"
FAILED = "failed"
PENDING = "pending"
RUNNING = "running"
WAITING = "waiting"
REGISTERING = "registering"
FINISHED = "finished"
def __init__(self, status: str, images: List, koji_task_id: str):
self.status = status
self.images = images
self.koji_task_id = koji_task_id
@classmethod
def from_dict(cls, data: Dict):
status = data["status"].lower()
koji_task_id = data["koji_task_id"]
images = [ImageStatus(s["status"].lower()) for s in data["image_statuses"]]
return cls(status, images, koji_task_id)
@property
def is_finished(self):
if self.is_success:
return True
return self.status in [self.FAILED]
@property
def is_success(self):
return self.status in [self.SUCCESS, self.FINISHED]
class Client:
def __init__(self, url):
self.url = url
def compose_create(self, distro: str, images: List[ImageRequest], koji: str):
url = urllib.parse.urljoin(self.url, f"/compose")
req = urllib.request.Request(url)
cro = ComposeRequest(distro, images, koji)
dat = json.dumps(cro.as_dict())
raw = dat.encode('utf-8')
req = urllib.request.Request(url, raw)
req.add_header('Content-Type', 'application/json')
req.add_header('Content-Length', len(raw))
with urllib.request.urlopen(req, raw) as res:
payload = res.read().decode('utf-8')
ps = json.loads(payload)
compose_id = ps["id"]
return compose_id
def compose_status(self, compose_id: str):
url = urllib.parse.urljoin(self.url, f"/compose/{compose_id}")
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as res:
data = res.read().decode('utf-8')
js = json.loads(data)
return ComposeStatus.from_dict(js)
def wait_for_compose(self, compose_id: str, *, sleep_time=2):
while True:
status = self.compose_status(compose_id)
if status.is_finished:
return status
time.sleep(sleep_time)
class OSBuildImage(BaseTaskHandler):
Methods = ['osbuildImage']
_taskWeight = 2.0
def __init__(self, task_id, method, params, session, options):
super().__init__(task_id, method, params, session, options)
self.composer_url = "http://composer:8701/"
self.koji_url = "https://localhost/kojihub"
self.client = Client(self.composer_url)
def handler(self, name, version, arches, target, opts):
self.logger.debug("Building image %s, %s, %s, %s",
self.logger.debug("Building image via osbuild %s, %s, %s, %s",
name, str(arches), str(target), str(opts))
#self.logger.debug("Event id: %s", str(self.event_id))
@ -48,56 +177,37 @@ class OSBuildImage(BaseTaskHandler):
#if buildconfig:
# self.logger.debug("build-config: %s", str(buildconfig))
# <<<>>>
client = self.client
cr = compose_request("fedora-32", "https://localhost/kojihub")
data = json.dumps(cr)
distro = f"{name}-{version}"
images = []
formats = ["qcow2"]
repo_url = "http://download.fedoraproject.org/pub/fedora/linux/releases/32/Everything/$arch/os/"
repos = [Repository(repo_url)]
for fmt in formats:
for arch in arches:
ireq = ImageRequest(arch, fmt, repos)
images.append(ireq)
req = urllib.request.Request("http://composer:8701/compose")
req.add_header('Content-Type', 'application/json')
raw = data.encode('utf-8')
req.add_header('Content-Length', len(raw))
with urllib.request.urlopen(req, raw) as res:
payload = res.read().decode('utf-8')
if res.status != 201:
self.logger.debug("Failed to create compose: %s", str(payload))
return {
'repositories': [],
'koji_builds': [],
'build': 'skipped',
}
ps = json.loads(payload)
compose_id = ps["id"]
self.logger.debug("Creating compose: %s\n koji: %s\n images: %s",
distro, self.koji_url,
str([i.as_dict() for i in images]))
req = urllib.request.Request(f"http://composer:8701/compose/{compose_id}")
while True:
with urllib.request.urlopen(req) as res:
payload = res.read().decode('utf-8')
if res.status != 200:
self.logger.debug("Failed to get compose status: %s", str(payload))
return {
'repositories': [],
'koji_builds': [],
'build': 'skipped',
}
cid = client.compose_create(distro, images, self.koji_url)
self.logger.info("Compose id: %s", cid)
ps = json.loads(payload)
status = ps["status"]
self.logger.debug("Compose status: %s", status)
if status != "RUNNING" and status != "WAITING":
break
time.sleep(2)
self.logger.debug("Waiting for comose to finish")
status = client.wait_for_compose(cid)
if status == "FAILED":
self.logger.debug("Compose failed: %s", str(payload))
if not status.is_success:
self.logger.error("Compose failed: %s", str(status))
return {
'repositories': [],
'koji_builds': [],
'build': 'skipped',
'koji_builds': []
}
return {
'repositories': [],
'koji_builds': [],
'build': f'{compose_id}-1',
'build': f'{cid}-1-1',
}