From 43b6dd6b7eb56c7d324df83f9dcc39344b9967be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 12 Apr 2022 13:37:49 +0200 Subject: [PATCH] Add command for migrating templates from rpmdb Fixes QubesOS/qubes-issues#7436 --- doc/manpages/qvm-template.rst | 20 +++++++++++++ qubesadmin/tools/qvm_template.py | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/doc/manpages/qvm-template.rst b/doc/manpages/qvm-template.rst index afeb1ff8..a55fee64 100644 --- a/doc/manpages/qvm-template.rst +++ b/doc/manpages/qvm-template.rst @@ -461,6 +461,26 @@ Options Show only disabled repos. +migrate-from-rpmdb +------------------ + +Migrate templates metadata from R4.0 format. This makes template originally +installed via rpm (qubes-dom0-update) available for qvm-template. +All templates gets `installed_by_rpm` property set to false. +If the operation fails for any reason, it is safe to retry. + +Synopsis +^^^^^^^^ + +:command:`qvm-template migrate-from-rpmdb` [-h] + +Options +^^^^^^^ + +.. option:: -h, --help + + Show help message and exit. + Template Spec ------------- diff --git a/qubesadmin/tools/qvm_template.py b/qubesadmin/tools/qvm_template.py index 2f12e892..31396c07 100644 --- a/qubesadmin/tools/qvm_template.py +++ b/qubesadmin/tools/qvm_template.py @@ -254,6 +254,10 @@ def parser_add_command(cmd, help_str): help='Show only disabled repos.') parser_repolist.add_argument('repos', nargs='*', metavar='REPOS') + # qvm-template migrate-from-rpmdb + parser_add_command('migrate-from-rpmdb', + help_str='Import R4.0 templates info to R4.1 format') + return parser_main @@ -1574,6 +1578,50 @@ def repolist(args: argparse.Namespace, app: qubesadmin.app.QubesBase) -> None: qubesadmin.tools.print_table(table) +def migrate_from_rpmdb(args, app): + """Migrate templates stored in rpmdb, into 'features' set on the VM itself. + """ + + if os.getuid() != 0: + parser.error('This command needs to be run as root') + ts = rpm.TransactionSet() + pkgs_to_remove = [] + for pkg in ts.dbMatch(): + if not pkg["name"].startswith('qubes-template-'): + continue + try: + vm = app.domains[pkg['name'][len('qubes-template-'):]] + except KeyError: + # no longer present, remove from rpmdb + pkgs_to_remove.append(pkg) + continue + if is_managed_template(vm): + # already migrated, cleanup + pkgs_to_remove.append(pkg) + continue + try: + vm.features['template-name'] = vm.name + vm.features['template-epoch'] = pkg[rpm.RPMTAG_EPOCHNUM] + vm.features['template-version'] = pkg[rpm.RPMTAG_VERSION] + vm.features['template-release'] = pkg[rpm.RPMTAG_RELEASE] + vm.features['template-reponame'] = '' + vm.features['template-buildtime'] = datetime.datetime.fromtimestamp( + pkg[rpm.RPMTAG_BUILDTIME]).strftime(DATE_FMT) + vm.features['template-installtime'] = pkg[rpm.RPMTAG_INSTALLTIME] + vm.features['template-license'] = pkg[rpm.RPMTAG_LICENSE] + vm.features['template-url'] = pkg[rpm.RPMTAG_URL] + vm.features['template-summary'] = pkg[rpm.RPMTAG_SUMMARY] + vm.features['template-description'] = \ + pkg[rpm.RPMTAG_DESCRIPTION].replace('\n', '|') + vm.installed_by_rpm = False + except Exception as e: + print('Failed to set template {} metadata: {}'.format(vm.name, e)) + continue + pkgs_to_remove.append(pkg) + subprocess.check_call( + ['rpm', '-e', '--justdb'] + [p['name'] for p in pkgs_to_remove]) + + def main(args: typing.Optional[typing.Sequence[str]] = None, app: typing.Optional[qubesadmin.app.QubesBase] = None) -> int: """Main routine of **qvm-template**. @@ -1636,6 +1684,8 @@ def main(args: typing.Optional[typing.Sequence[str]] = None, clean(p_args, app) elif p_args.command == 'repolist': repolist(p_args, app) + elif p_args.command == 'migrate-from-rpmdb': + migrate_from_rpmdb(p_args, app) else: parser.error(f'Command \'{p_args.command}\' not supported.') except Exception as e: # pylint: disable=broad-except