-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add the posibility to upgrade while using a local repository
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
1 parent
948c782
commit 6a0d9f3
Showing
5 changed files
with
276 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
92 changes: 92 additions & 0 deletions
92
repos/system_upgrade/common/actors/localrepos/libraries/localrepos.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
138
repos/system_upgrade/common/actors/localrepos/tests/test_localrepos.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
90
repos/system_upgrade/common/actors/localreposinhibit/actor.py
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.