Skip to content

Commit

Permalink
add the posibility to upgrade while using a local repository
Browse files Browse the repository at this point in the history
Upgrade with a local repository required to host the repository locally
for it to be visible from target user-space container during the
upgrade. The added local_repos actor ensures that the local repository
will be visible from the container by adjusting the path to it simply by
prefixing a host root mount bind '/installroot' to it. The
local_repos_inhibit actor is no longer needed, thus was removed.
  • Loading branch information
PeterMocary committed Jul 20, 2023
1 parent 5015311 commit ca51920
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 171 deletions.
46 changes: 46 additions & 0 deletions repos/system_upgrade/common/actors/localrepos/actor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from leapp.actors import Actor
from leapp.libraries.actor import localrepos
from leapp.libraries.common import mounting
from leapp.models import (
TargetOSInstallationImage,
TargetUserSpaceInfo,
TMPTargetRepositoriesFacts,
UsedTargetRepositories
)
from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag


class LocalRepos(Actor):
"""
Adjust local repositories to the target user-space container.
Changes the path of local file urls (starting with 'file://') for 'baseurl' and
'mirrorlist' fields to the container space for the used repositories. This is
done by prefixing host root mount bind ('/installroot') to the path. It ensures
that the files will be accessible from the container and thus proper functionality
of the local repository.
"""

name = 'local_repos'
consumes = (TargetOSInstallationImage,
TargetUserSpaceInfo,
TMPTargetRepositoriesFacts,
UsedTargetRepositories)
produces = ()
tags = (IPUWorkflowTag, TargetTransactionChecksPhaseTag)

def process(self):
target_userspace_info = next(self.consume(TargetUserSpaceInfo), None)
used_target_repos = next(self.consume(UsedTargetRepositories), None)
target_repos_facts = next(self.consume(TMPTargetRepositoriesFacts), None)
target_iso = next(self.consume(TargetOSInstallationImage), None)

if not all([target_userspace_info, used_target_repos, target_repos_facts]):
return

target_repos_facts = target_repos_facts.repositories
iso_repoids = set(repo.repoid for repo in target_iso.repositories) if target_iso else set()
used_target_repoids = set(repo.repoid for repo in used_target_repos.repos)

with mounting.NspawnActions(base_dir=target_userspace_info.path) as context:
localrepos.process(context, target_repos_facts, iso_repoids, used_target_repoids)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import os

from leapp.libraries.stdlib import api

HOST_ROOT_MOUNT_BIND_PATH = '/installroot'
LOCAL_FILE_URL_PREFIX = 'file://'


def _adjust_local_file_url(repo_file_line):
"""
Adjusts a local file url to the target user-space container in a provided
repo file line by prefixing host root mount bind '/installroot' to it
when needed.
:param str repo_file_line: a line from a repo file
:returns str: adjusted line or the provided line if no changes are needed
"""
adjust_fields = ['baseurl', 'mirrorlist']

if LOCAL_FILE_URL_PREFIX in repo_file_line:
entry_field, entry_value = repo_file_line.strip().split('=', 1)
if not any(entry_field.startswith(field) for field in adjust_fields):
return repo_file_line

entry_value = entry_value.strip('\'\"')
path = entry_value[len(LOCAL_FILE_URL_PREFIX):]
new_entry_value = LOCAL_FILE_URL_PREFIX + os.path.join(HOST_ROOT_MOUNT_BIND_PATH, path.lstrip('/'))
new_repo_file_line = entry_field + '=' + new_entry_value
return new_repo_file_line
return repo_file_line


def _extract_repos_from_repofile(context, repo_file):
"""
Generator function that extracts repositories from a repo file in the given context
and yields them as list of lines that belong to the repository.
:param context: target user-space context
:param str repo_file: path to repository file (inside the provided context)
"""
with context.open(repo_file, 'r') as rf:
repo_file_lines = rf.read().split('\n')

if repo_file_lines == ['']:
return

current_repo = []
for line in repo_file_lines:
line = line.strip()

if line.startswith('[') and current_repo:
yield current_repo
current_repo = []

current_repo.append(line)
yield current_repo


def _adjust_local_repos_to_container(context, repo_file, local_repoids):
new_repo_file = []
for repo in _extract_repos_from_repofile(context, repo_file):
repoid = repo[0].strip('[]')
adjusted_repo = repo
if repoid in local_repoids:
adjusted_repo = [_adjust_local_file_url(line) for line in repo]
new_repo_file.append(adjusted_repo)

# Combine the repo file contents into a string and write it back to the file
new_repo_file = ['\n'.join(repo) for repo in new_repo_file]
new_repo_file = '\n'.join(new_repo_file)
with context.open(repo_file, 'w') as rf:
rf.write(new_repo_file)


def process(context, target_repos_facts, iso_repoids, used_target_repoids):
for repo_file_facts in target_repos_facts:
repo_file_path = repo_file_facts.file
local_repoids = set()
for repo in repo_file_facts.data:
# Skip repositories that aren't used or are provided by ISO
if repo.repoid not in used_target_repoids or repo.repoid in iso_repoids:
continue
# Note repositories that contain local file url
if repo.baseurl and LOCAL_FILE_URL_PREFIX in repo.baseurl or \
repo.mirrorlist and LOCAL_FILE_URL_PREFIX in repo.mirrorlist:
local_repoids.add(repo.repoid)

if local_repoids:
api.current_logger().debug(
'Adjusting following repos in the repo file - {}: {}'.format(repo_file_path,
', '.join(local_repoids)))
_adjust_local_repos_to_container(context, repo_file_path, local_repoids)
138 changes: 138 additions & 0 deletions repos/system_upgrade/common/actors/localrepos/tests/test_localrepos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import pytest

from leapp.libraries.actor import localrepos

REPO_FILE_1_LOCAL_REPOIDS = ['myrepo1']
REPO_FILE_1 = [{'repoid': 'myrepo1',
'name': 'mylocalrepo',
'baseurl': 'file:///home/user/.local/myrepos/repo1'
}]
REPO_FILE_1_ADJUSTED = [{'repoid': 'myrepo1',
'name': 'mylocalrepo',
'baseurl': 'file:///installroot/home/user/.local/myrepos/repo1'
}]

REPO_FILE_2_LOCAL_REPOIDS = ['myrepo3']
REPO_FILE_2 = [{'repoid': 'myrepo2',
'name': 'mynotlocalrepo',
'baseurl': 'https://www.notlocal.com/packages'
},
{'repoid': 'myrepo3',
'name': 'mylocalrepo',
'baseurl': 'file:///home/user/.local/myrepos/repo1',
'mirrorlist': 'file:///home/user/.local/mymirrors/repo1.txt'
},
]
REPO_FILE_2_ADJUSTED = [{'repoid': 'myrepo2',
'name': 'mynotlocalrepo',
'baseurl': 'https://www.notlocal.com/packages'
},
{'repoid': 'myrepo3',
'name': 'mylocalrepo',
'baseurl': 'file:///installroot/home/user/.local/myrepos/repo1',
'mirrorlist': 'file:///installroot/home/user/.local/mymirrors/repo1.txt'
}]

REPO_FILE_3_LOCAL_REPOIDS = ['myrepo4']
REPO_FILE_3 = [{'repoid': 'myrepo4',
'name': 'myrepowithlocalgpgkey',
'baseurl': '"file:///home/user/.local/myrepos/repo1"',
'gpgkey': 'file:///home/user/.local/pki/gpgkey',
'gpgcheck': '1'
}]
REPO_FILE_3_ADJUSTED = [{'repoid': 'myrepo4',
'name': 'myrepowithlocalgpgkey',
'baseurl': 'file:///installroot/home/user/.local/myrepos/repo1',
'gpgkey': 'file:///home/user/.local/pki/gpgkey',
'gpgcheck': '1'
}]
REPO_FILE_EMPTY = []


@pytest.mark.parametrize('repo_file_line, expected_adjusted_repo_file_line',
[('baseurl=file:///home/user/.local/repositories/repository',
'baseurl=file:///installroot/home/user/.local/repositories/repository'),
('baseurl="file:///home/user/my-repo"',
'baseurl=file:///installroot/home/user/my-repo'),
('baseurl=https://notlocal.com/packages',
'baseurl=https://notlocal.com/packages'),
('mirrorlist=file:///some_mirror_list.txt',
'mirrorlist=file:///installroot/some_mirror_list.txt'),
('gpgkey=file:///etc/pki/some.key',
'gpgkey=file:///etc/pki/some.key'),
('', ''),
('[repoid]', '[repoid]')])
def test_adjust_local_file_url(repo_file_line, expected_adjusted_repo_file_line):
adjusted_repo_file_line = localrepos._adjust_local_file_url(repo_file_line)
if 'file://' not in repo_file_line:
assert adjusted_repo_file_line == repo_file_line
return
assert adjusted_repo_file_line == expected_adjusted_repo_file_line


class MockedFileDescriptor(object):

def __init__(self, repo_file, expected_new_repo_file):
self.repo_file = repo_file
self.expected_new_repo_file = expected_new_repo_file

@staticmethod
def _create_repo_string(repo_as_dict):
repo = ['[{}]'.format(repo_as_dict['repoid'])]
for key in repo_as_dict.keys():
if key != 'repoid':
repo.append('{}={}'.format(key, repo_as_dict[key]))
return '\n'.join(repo)

def _create_repo_file_string(self, repo_file):
repo_strings = []
for repo in repo_file:
repo_strings.append(self._create_repo_string(repo))
return '\n'.join(repo_strings)

def __enter__(self):
return self

def __exit__(self, *args, **kwargs):
return

def read(self):
return self._create_repo_file_string(self.repo_file)

def write(self, new_contents):
assert self.expected_new_repo_file
expected_repo_file_contents = self._create_repo_file_string(self.expected_new_repo_file)
assert expected_repo_file_contents == new_contents


class MockedContext(object):

def __init__(self, repo_contents, expected_repo_contents):
self.repo_contents = repo_contents
self.expected_repo_contents = expected_repo_contents

def open(self, path, mode):
return MockedFileDescriptor(self.repo_contents, self.expected_repo_contents)


@pytest.mark.parametrize('repo_file, local_repoids, expected_repo_file',
[(REPO_FILE_1, REPO_FILE_1_LOCAL_REPOIDS, REPO_FILE_1_ADJUSTED),
(REPO_FILE_2, REPO_FILE_2_LOCAL_REPOIDS, REPO_FILE_2_ADJUSTED),
(REPO_FILE_3, REPO_FILE_3_LOCAL_REPOIDS, REPO_FILE_3_ADJUSTED)])
def test_adjust_local_repos_to_container(repo_file, local_repoids, expected_repo_file):
context = MockedContext(repo_file, expected_repo_file)
localrepos._adjust_local_repos_to_container(context, '<some_repo_path>', local_repoids)


@pytest.mark.parametrize('repo_file', [REPO_FILE_EMPTY, REPO_FILE_1, REPO_FILE_2])
def test_extract_repos_from_repofile(repo_file):
context = MockedContext(repo_file, None)
repo_gen = localrepos._extract_repos_from_repofile(context, '<some_repo_path>')
for repo in repo_file:
# Convert repo dict to list of lines
repo_as_list = ['[{}]'.format(repo['repoid'])]
for key in repo.keys():
if key != 'repoid':
repo_as_list.append('{}={}'.format(key, repo[key]))
assert repo_as_list == next(repo_gen, None)
assert next(repo_gen, None) is None
90 changes: 0 additions & 90 deletions repos/system_upgrade/common/actors/localreposinhibit/actor.py

This file was deleted.

0 comments on commit ca51920

Please sign in to comment.