a10bed
#!/usr/bin/env python3
a10bed
#
a10bed
# This script inspects a given json proving a list of addons, and
a10bed
# creates an addon for each key/value pair matching the given uki, distro and
a10bed
# arch provided in input.
a10bed
#
a10bed
# Usage: python uki_create_addons.py input_json out_dir uki distro arch
a10bed
#
a10bed
# This tool requires the systemd-ukify and systemd-boot packages.
a10bed
#
a10bed
# Addon file
a10bed
#-----------
a10bed
# Each addon terminates with .addon
a10bed
# Each addon contains only two types of lines:
a10bed
# Lines beginning with '#' are description and thus ignored
a10bed
# All other lines are command line to be added.
a10bed
# The name of the end resulting addon is taken from the json hierarchy.
a10bed
# For example, and addon in json['virt']['rhel']['x86_64']['hello.addon'] will
a10bed
# result in an UKI addon file generated in out_dir called
a10bed
# hello-virt.rhel.x86_64.addon.efi
a10bed
#
a10bed
# The common key, present in any sub-dict in the provided json (except the leaf dict)
a10bed
# is used as place for default addons when the same addon is not defined deep
a10bed
# in the hierarchy. For example, if we define test.addon (text: 'test1\n') in
a10bed
# json['common']['test.addon'] = ['test1\n'] and another test.addon (text: test2) in
a10bed
# json['virt']['common']['test.addon'] = ['test2'], any other uki except virt
a10bed
# will have a test.addon.efi with text "test1", and virt will have a
a10bed
# test.addon.efi with "test2"
a10bed
#
a10bed
# sbat.conf
a10bed
#----------
a10bed
# This dict is containing the sbat string for *all* addons being created.
a10bed
# This dict is optional, but when used has to be put in a sub-dict with
a10bed
# { 'sbat' : { 'sbat.conf' : ['your text here'] }}
a10bed
# It follows the same syntax as the addon files, meaning '#' is comment and
a10bed
# the rest is taken as sbat string and feed to ukify.
a10bed
a10bed
import os
a10bed
import sys
a10bed
import json
a10bed
import collections
a10bed
import subprocess
a10bed
a10bed
a10bed
UKIFY_PATH = '/usr/lib/systemd/ukify'
a10bed
a10bed
def usage(err):
a10bed
    print(f'Usage: {os.path.basename(__file__)} input_json output_dir uki distro arch')
a10bed
    print(f'Error:{err}')
a10bed
    sys.exit(1)
a10bed
a10bed
def check_clean_arguments(input_json, out_dir):
a10bed
    # Remove end '/'
a10bed
    if out_dir[-1:] == '/':
a10bed
        out_dir = out_dir[:-1]
a10bed
    if not os.path.isfile(input_json):
a10bed
        usage(f'input_json {input_json} is not a file, or does not exist!')
a10bed
    if not os.path.isdir(out_dir):
a10bed
        usage(f'out_dir_dir {out_dir} is not a dir, or does not exist!')
a10bed
    return out_dir
a10bed
a10bed
UKICmdlineAddon = collections.namedtuple('UKICmdlineAddon', ['name', 'cmdline'])
a10bed
uki_addons_list = []
a10bed
uki_addons = {}
a10bed
addon_sbat_string = None
a10bed
a10bed
def parse_lines(lines, rstrip=True):
a10bed
    cmdline = ''
a10bed
    for l in lines:
a10bed
        l = l.lstrip()
a10bed
        if not l:
a10bed
            continue
a10bed
        if l[0] == '#':
a10bed
            continue
a10bed
        # rstrip is used only for addons cmdline, not sbat.conf, as it replaces
a10bed
        # return lines with spaces.
a10bed
        if rstrip:
a10bed
            l = l.rstrip() + ' '
a10bed
        cmdline += l
a10bed
    if cmdline == '':
a10bed
        return ''
a10bed
    return cmdline
a10bed
a10bed
def parse_all_addons(in_obj):
a10bed
    global addon_sbat_string
a10bed
a10bed
    for el in in_obj.keys():
a10bed
        # addon found: copy it in our global dict uki_addons
a10bed
        if el.endswith('.addon'):
a10bed
            uki_addons[el] = in_obj[el]
a10bed
a10bed
    if 'sbat' in in_obj and 'sbat.conf' in in_obj['sbat']:
a10bed
        # sbat.conf found: override sbat with the most specific one found
a10bed
        addon_sbat_string = parse_lines(in_obj['sbat']['sbat.conf'], rstrip=False)
a10bed
a10bed
def recursively_find_addons(in_obj, folder_list):
a10bed
    # end of recursion, leaf directory. Search all addons here
a10bed
    if len(folder_list) == 0:
a10bed
        parse_all_addons(in_obj)
a10bed
        return
a10bed
a10bed
    # first, check for common folder
a10bed
    if 'common' in in_obj:
a10bed
        parse_all_addons(in_obj['common'])
a10bed
a10bed
    # second, check if there is a match with the searched folder
a10bed
    if folder_list[0] in in_obj:
a10bed
        folder_next = in_obj[folder_list[0]]
a10bed
        folder_list = folder_list[1:]
a10bed
        recursively_find_addons(folder_next, folder_list)
a10bed
a10bed
def parse_in_json(in_json, uki_name, distro, arch):
a10bed
    with open(in_json, 'r') as f:
a10bed
        in_obj = json.load(f)
a10bed
    recursively_find_addons(in_obj, [uki_name, distro, arch])
a10bed
a10bed
    for addon_name, cmdline in uki_addons.items():
a10bed
        addon_name = addon_name.replace(".addon","")
a10bed
        addon_full_name = f'{addon_name}-{uki_name}.{distro}.{arch}.addon.efi'
a10bed
        cmdline = parse_lines(cmdline).rstrip()
a10bed
        if cmdline:
a10bed
            uki_addons_list.append(UKICmdlineAddon(addon_full_name, cmdline))
a10bed
a10bed
def create_addons(out_dir):
a10bed
    for uki_addon in uki_addons_list:
a10bed
        out_path = os.path.join(out_dir, uki_addon.name)
a10bed
        cmd = [
a10bed
            f'{UKIFY_PATH}', 'build',
a10bed
            f'--cmdline="{uki_addon.cmdline}"',
a10bed
            f'--output={out_path}']
a10bed
        if addon_sbat_string:
a10bed
            cmd.append('--sbat="' + addon_sbat_string.rstrip() +'"')
a10bed
a10bed
        subprocess.check_call(cmd, text=True)
a10bed
a10bed
if __name__ == "__main__":
a10bed
    argc = len(sys.argv) - 1
a10bed
    if argc != 5:
a10bed
        usage('too few or too many parameters!')
a10bed
a10bed
    input_json = sys.argv[1]
a10bed
    out_dir = sys.argv[2]
a10bed
    uki_name = sys.argv[3]
a10bed
    distro = sys.argv[4]
a10bed
    arch = sys.argv[5]
a10bed
a10bed
    out_dir = check_clean_arguments(input_json, out_dir)
a10bed
    parse_in_json(input_json, uki_name, distro, arch)
a10bed
    create_addons(out_dir)
a10bed
a10bed