-
Notifications
You must be signed in to change notification settings - Fork 148
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 with 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 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
60190ff
commit e9f899c
Showing
6 changed files
with
302 additions
and
172 deletions.
There are no files selected for viewing
48 changes: 48 additions & 0 deletions
48
repos/system_upgrade/common/actors/adjustlocalrepos/actor.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,48 @@ | ||
from leapp.actors import Actor | ||
from leapp.libraries.actor import adjustlocalrepos | ||
from leapp.libraries.common import mounting | ||
from leapp.libraries.stdlib import api | ||
from leapp.models import ( | ||
TargetOSInstallationImage, | ||
TargetUserSpaceInfo, | ||
TMPTargetRepositoriesFacts, | ||
UsedTargetRepositories | ||
) | ||
from leapp.tags import IPUWorkflowTag, TargetTransactionChecksPhaseTag | ||
|
||
|
||
class AdjustLocalRepos(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 = 'adjust_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]): | ||
api.current_logger().error("Missing required information to proceed!") | ||
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: | ||
adjustlocalrepos.process(context, target_repos_facts, iso_repoids, used_target_repoids) |
100 changes: 100 additions & 0 deletions
100
repos/system_upgrade/common/actors/adjustlocalrepos/libraries/adjustlocalrepos.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,100 @@ | ||
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 and not repo_file_line.startswith('#'): | ||
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.readlines() | ||
|
||
# Detect repo and remove lines before first repoid | ||
repo_found = False | ||
for idx, line in enumerate(repo_file_lines): | ||
if line.startswith('['): | ||
repo_file_lines = repo_file_lines[idx:] | ||
repo_found = True | ||
break | ||
|
||
if not repo_found: | ||
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) |
151 changes: 151 additions & 0 deletions
151
repos/system_upgrade/common/actors/adjustlocalrepos/tests/test_adjustlocalrepos.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,151 @@ | ||
import pytest | ||
|
||
from leapp.libraries.actor import adjustlocalrepos | ||
|
||
REPO_FILE_1_LOCAL_REPOIDS = ['myrepo1'] | ||
REPO_FILE_1 = [['[myrepo1]', | ||
'name=mylocalrepo', | ||
'baseurl=file:///home/user/.local/myrepos/repo1' | ||
]] | ||
REPO_FILE_1_ADJUSTED = [['[myrepo1]', | ||
'name=mylocalrepo', | ||
'baseurl=file:///installroot/home/user/.local/myrepos/repo1' | ||
]] | ||
|
||
REPO_FILE_2_LOCAL_REPOIDS = ['myrepo3'] | ||
REPO_FILE_2 = [['[myrepo2]', | ||
'name=mynotlocalrepo', | ||
'baseurl=https://www.notlocal.com/packages' | ||
], | ||
['[myrepo3]', | ||
'name=mylocalrepo', | ||
'baseurl=file:///home/user/.local/myrepos/repo3', | ||
'mirrorlist=file:///home/user/.local/mymirrors/repo3.txt' | ||
]] | ||
REPO_FILE_2_ADJUSTED = [['[myrepo2]', | ||
'name=mynotlocalrepo', | ||
'baseurl=https://www.notlocal.com/packages' | ||
], | ||
['[myrepo3]', | ||
'name=mylocalrepo', | ||
'baseurl=file:///installroot/home/user/.local/myrepos/repo3', | ||
'mirrorlist=file:///installroot/home/user/.local/mymirrors/repo3.txt' | ||
]] | ||
|
||
REPO_FILE_3_LOCAL_REPOIDS = ['myrepo4', 'myrepo5'] | ||
REPO_FILE_3 = [['[myrepo4]', | ||
'name=myrepowithlocalgpgkey', | ||
'baseurl="file:///home/user/.local/myrepos/repo4"', | ||
'gpgkey=file:///home/user/.local/pki/gpgkey', | ||
'gpgcheck=1' | ||
], | ||
['[myrepo5]', | ||
'name=myrepowithcomment', | ||
'baseurl=file:///home/user/.local/myrepos/repo5', | ||
'#baseurl=file:///home/user/.local/myotherrepos/repo5', | ||
'enabled=1', | ||
'exclude=sed']] | ||
REPO_FILE_3_ADJUSTED = [['[myrepo4]', | ||
'name=myrepowithlocalgpgkey', | ||
'baseurl=file:///installroot/home/user/.local/myrepos/repo4', | ||
'gpgkey=file:///home/user/.local/pki/gpgkey', | ||
'gpgcheck=1' | ||
], | ||
['[myrepo5]', | ||
'name=myrepowithcomment', | ||
'baseurl=file:///installroot/home/user/.local/myrepos/repo5', | ||
'#baseurl=file:///home/user/.local/myotherrepos/repo5', | ||
'enabled=1', | ||
'exclude=sed']] | ||
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'), | ||
('#baseurl=file:///home/user/my-repo', | ||
'#baseurl=file:///home/user/my-repo'), | ||
('', ''), | ||
('[repoid]', '[repoid]')]) | ||
def test_adjust_local_file_url(repo_file_line, expected_adjusted_repo_file_line): | ||
adjusted_repo_file_line = adjustlocalrepos._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_file_lines(repo_file): | ||
repo_file_lines = [] | ||
for repo in repo_file: | ||
repo = [line+'\n' for line in repo] | ||
repo_file_lines += repo | ||
return repo_file_lines | ||
|
||
def __enter__(self): | ||
return self | ||
|
||
def __exit__(self, *args, **kwargs): | ||
return | ||
|
||
def readlines(self): | ||
return self._create_repo_file_lines(self.repo_file) | ||
|
||
def write(self, new_contents): | ||
assert self.expected_new_repo_file | ||
repo_file_lines = self._create_repo_file_lines(self.expected_new_repo_file) | ||
expected_repo_file_contents = ''.join(repo_file_lines).rstrip('\n') | ||
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): | ||
# The checks for expected_repo_file comparison to a adjusted form of the | ||
# repo_file can be found in the MockedFileDescriptor.write(). | ||
context = MockedContext(repo_file, expected_repo_file) | ||
adjustlocalrepos._adjust_local_repos_to_container(context, '<some_repo_file_path>', local_repoids) | ||
|
||
|
||
@pytest.mark.parametrize('expected_repo_file, add_empty_lines', [(REPO_FILE_EMPTY, False), | ||
(REPO_FILE_1, False), | ||
(REPO_FILE_2, True)]) | ||
def test_extract_repos_from_repofile(expected_repo_file, add_empty_lines): | ||
repo_file = expected_repo_file[:] | ||
if add_empty_lines: # add empty lines before the first repo | ||
repo_file[0] = ['', ''] + repo_file[0] | ||
|
||
context = MockedContext(repo_file, None) | ||
repo_gen = adjustlocalrepos._extract_repos_from_repofile(context, '<some_repo_file_path>') | ||
|
||
for repo in expected_repo_file: | ||
assert repo == next(repo_gen, None) | ||
|
||
assert next(repo_gen, None) is None |
89 changes: 0 additions & 89 deletions
89
repos/system_upgrade/common/actors/localreposinhibit/actor.py
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.