Skip to content

Commit

Permalink
Add command for migrating templates from rpmdb
Browse files Browse the repository at this point in the history
  • Loading branch information
marmarek committed Apr 12, 2022
1 parent 2f5afdf commit 7ab25e7
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
20 changes: 20 additions & 0 deletions doc/manpages/qvm-template.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------

Expand Down
102 changes: 102 additions & 0 deletions qubesadmin/tests/tools/qvm_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -5417,3 +5417,105 @@ def test_300_repo_files_glob(self, mock_repolist):
mock_repolist.mock_calls[0][1][0].repo_files
)
self.assertAllCalled()

@mock.patch('os.getuid')
@mock.patch('subprocess.check_call')
@mock.patch('rpm.TransactionSet')
def test_400_migrate_from_rpmdb(self, mock_rpm_ts, mock_check_call, mock_getuid):
mock_getuid.return_value = 0
build_time = '2020-09-01 14:30:00' # 1598970600
install_time = '2020-09-01 13:30:00' # 1598967000
mock_rpm_ts.return_value.dbMatch.return_value = [
{
rpm.RPMTAG_NAME : 'non-template',
rpm.RPMTAG_BUILDTIME : 1598970600,
rpm.RPMTAG_INSTALLTIME : 1598967000,
rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
rpm.RPMTAG_EPOCHNUM : 2,
rpm.RPMTAG_LICENSE : 'GPL',
rpm.RPMTAG_RELEASE : '2020',
rpm.RPMTAG_SUMMARY : 'Summary',
rpm.RPMTAG_URL : 'https://qubes-os.org',
rpm.RPMTAG_VERSION : '4.1'
},
{
rpm.RPMTAG_NAME : 'qubes-template-test-existing',
rpm.RPMTAG_BUILDTIME : 1598970600,
rpm.RPMTAG_INSTALLTIME : 1598967000,
rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
rpm.RPMTAG_EPOCHNUM : 2,
rpm.RPMTAG_LICENSE : 'GPL',
rpm.RPMTAG_RELEASE : '2020',
rpm.RPMTAG_SUMMARY : 'Summary',
rpm.RPMTAG_URL : 'https://qubes-os.org',
rpm.RPMTAG_VERSION : '4.1'
},
{
rpm.RPMTAG_NAME : 'qubes-template-test-migrated',
rpm.RPMTAG_BUILDTIME : 1598970600,
rpm.RPMTAG_INSTALLTIME : 1598967000,
rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
rpm.RPMTAG_EPOCHNUM : 2,
rpm.RPMTAG_LICENSE : 'GPL',
rpm.RPMTAG_RELEASE : '2020',
rpm.RPMTAG_SUMMARY : 'Summary',
rpm.RPMTAG_URL : 'https://qubes-os.org',
rpm.RPMTAG_VERSION : '4.1'
},
{
rpm.RPMTAG_NAME : 'qubes-template-test-removed',
rpm.RPMTAG_BUILDTIME : 1598970600,
rpm.RPMTAG_INSTALLTIME : 1598967000,
rpm.RPMTAG_DESCRIPTION : 'Desc\ndesc',
rpm.RPMTAG_EPOCHNUM : 2,
rpm.RPMTAG_LICENSE : 'GPL',
rpm.RPMTAG_RELEASE : '2020',
rpm.RPMTAG_SUMMARY : 'Summary',
rpm.RPMTAG_URL : 'https://qubes-os.org',
rpm.RPMTAG_VERSION : '4.1'
},
]

self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00some-vm class=TemplateVM state=Halted\n' \
b'test-existing class=TemplateVM state=Halted\n' \
b'test-migrated class=TemplateVM state=Halted\n'
for key, val in [
('name', 'test-existing'),
('epoch', '2'),
('version', '4.1'),
('release', '2020'),
('reponame', ''),
('buildtime', build_time),
('installtime', install_time),
('license', 'GPL'),
('url', 'https://qubes-os.org'),
('summary', 'Summary'),
('description', 'Desc|desc')]:
self.app.expected_calls[(
'test-existing',
'admin.vm.feature.Set',
f'template-{key}',
val.encode())] = b'0\0'
self.app.expected_calls[(
'test-existing',
'admin.vm.property.Set',
f'installed_by_rpm',
b'False')] = b'0\0'
self.app.expected_calls[(
'test-migrated',
'admin.vm.feature.Get',
'template-name',
None)] = b'0\0test-migrated'
self.app.expected_calls[(
'test-existing',
'admin.vm.feature.Get',
'template-name',
None)] = b'2\0QubesFeatureNotFoundError\0\0No such feature\0'
qubesadmin.tools.qvm_template.migrate_from_rpmdb(self.app)
mock_check_call.assert_called_once_with([
'rpm', '-e', '--justdb',
'qubes-template-test-existing',
'qubes-template-test-migrated',
'qubes-template-test-removed'])
self.assertAllCalled()
56 changes: 56 additions & 0 deletions qubesadmin/tools/qvm_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -1574,6 +1578,56 @@ def repolist(args: argparse.Namespace, app: qubesadmin.app.QubesBase) -> None:
qubesadmin.tools.print_table(table)


def migrate_from_rpmdb(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')
rpm_ts = rpm.TransactionSet()
pkgs_to_remove = []
for pkg in rpm_ts.dbMatch():
if not pkg[rpm.RPMTAG_NAME].startswith('qubes-template-'):
continue
try:
vm = app.domains[pkg[rpm.RPMTAG_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], tz=datetime.timezone.utc).\
strftime(DATE_FMT)
vm.features['template-installtime'] = \
datetime.datetime.fromtimestamp(
pkg[rpm.RPMTAG_INSTALLTIME], tz=datetime.timezone.utc).\
strftime(DATE_FMT)
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: # pylint: disable=broad-except
print('Failed to set template {} metadata: {}'.format(vm.name, e))
continue
pkgs_to_remove.append(pkg)
subprocess.check_call(
['rpm', '-e', '--justdb'] +
[p[rpm.RPMTAG_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**.
Expand Down Expand Up @@ -1636,6 +1690,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(app)
else:
parser.error(f'Command \'{p_args.command}\' not supported.')
except Exception as e: # pylint: disable=broad-except
Expand Down

0 comments on commit 7ab25e7

Please sign in to comment.