plugin/builder: create proper composer client
Make a proper client for the osbuild composer koji API and use it.
This commit is contained in:
parent
d5918bd789
commit
025d3b1902
1 changed files with 170 additions and 60 deletions
|
|
@ -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',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue