forked from msvsphere/leapp-repository
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
107 lines
3.6 KiB
107 lines
3.6 KiB
10 months ago
|
import json
|
||
|
import os
|
||
|
|
||
|
from leapp.libraries.common import mounting, utils
|
||
|
from leapp.libraries.stdlib import api
|
||
|
from leapp.models import fields, RepositoryData, RepositoryFile
|
||
|
|
||
|
try:
|
||
|
import dnf
|
||
|
except ImportError:
|
||
|
api.current_logger().warning('repofileutils.py: failed to import dnf')
|
||
|
|
||
|
|
||
|
def _parse_repository(repoid, repo_data):
|
||
|
def asbool(x):
|
||
|
return x == '1'
|
||
|
prepared = {'repoid': repoid, 'additional_fields': {}}
|
||
|
for key in repo_data.keys():
|
||
|
if key in RepositoryData.fields:
|
||
|
if isinstance(RepositoryData.fields[key], fields.Boolean):
|
||
|
repo_data[key] = asbool(repo_data[key])
|
||
|
prepared[key] = repo_data[key]
|
||
|
else:
|
||
|
prepared['additional_fields'][key] = repo_data[key]
|
||
|
prepared['additional_fields'] = json.dumps(prepared['additional_fields'])
|
||
|
return RepositoryData(**prepared)
|
||
|
|
||
|
|
||
|
def parse_repofile(repofile):
|
||
|
"""
|
||
|
Parse the given repo file.
|
||
|
|
||
|
:param repofile: Path to the repo file
|
||
|
:type repofile: str
|
||
|
:rtype: RepositoryFile
|
||
|
"""
|
||
|
data = []
|
||
|
with open(repofile, mode='r') as fp:
|
||
|
cp = utils.parse_config(fp, strict=False)
|
||
|
for repoid in cp.sections():
|
||
|
data.append(_parse_repository(repoid, dict(cp.items(repoid))))
|
||
|
return RepositoryFile(file=repofile, data=data)
|
||
|
|
||
|
|
||
|
def get_repodirs():
|
||
|
"""
|
||
|
Return all directories yum scans for repository files, if they exist.
|
||
|
By default, the possible paths on RHEL should be:
|
||
|
['/etc/yum.repos.d', '/etc/yum/repos.d', '/etc/distro.repos.d']
|
||
|
|
||
|
ATTENTION: Requires the dnf module to be present.
|
||
|
TODO: Get repodirs inside given context.
|
||
|
"""
|
||
|
with dnf.base.Base() as base:
|
||
|
base.conf.read(priority=dnf.conf.PRIO_MAINCONFIG)
|
||
|
return list({os.path.realpath(d) for d in base.conf.reposdir if os.path.isdir(d)})
|
||
|
|
||
|
|
||
|
def get_parsed_repofiles(context=mounting.NotIsolatedActions(base_dir='/')):
|
||
|
"""
|
||
|
Scan all repositories on the system.
|
||
|
|
||
|
Repositories are scanned under repository directories (as reported by dnf)
|
||
|
of the given context. By default the context is the host system.
|
||
|
|
||
|
ATTENTION: Do not forget to ensure the redhat.repo file is regenerated
|
||
|
by RHSM when used.
|
||
|
|
||
|
:param context: An instance of a mounting.IsolatedActions class
|
||
|
:type context: mounting.IsolatedActions class
|
||
|
:rtype: List(RepositoryFile)
|
||
|
"""
|
||
|
repofiles = []
|
||
|
cmd = ['find', '-L'] + get_repodirs() + ['-maxdepth', '1', '-type', 'f', '-name', '*.repo']
|
||
|
repofiles_paths = context.call(cmd, split=True)['stdout']
|
||
|
for repofile_path in repofiles_paths:
|
||
|
repofile = parse_repofile(context.full_path(repofile_path))
|
||
|
# we want full path in cotext, not the real full path
|
||
|
repofile.file = repofile_path
|
||
|
repofiles.append(repofile)
|
||
|
return repofiles
|
||
|
|
||
|
|
||
|
def _invert_dict(data):
|
||
|
"""{a: [b]} -> {b: [a]}"""
|
||
|
inv_dict = {}
|
||
|
for key in data.keys():
|
||
|
for value in data[key]:
|
||
|
inv_dict[value] = inv_dict.get(value, []) + [key]
|
||
|
return inv_dict
|
||
|
|
||
|
|
||
|
def get_duplicate_repositories(repofiles):
|
||
|
"""
|
||
|
Return dict of duplicate repositories {repoid: [repofile_path]}
|
||
|
|
||
|
A repository is defined multiple times if it exists in multiple repofiles.
|
||
|
Redefinition inside one repository file is ignored (same in DNF).
|
||
|
|
||
|
:param repofiles:
|
||
|
:type repofiles: List(RepositoryFile)
|
||
|
:rtype: dict {repoid: repofilepath}
|
||
|
"""
|
||
|
rf_repos = {repofile.file: [repo.repoid for repo in repofile.data] for repofile in repofiles}
|
||
|
repos = _invert_dict(rf_repos)
|
||
|
return {repo: set(rfiles) for repo, rfiles in repos.items() if len(set(rfiles)) > 1}
|