kiwi-el8/kiwi/solver/repository/base.py
Marcus Schäfer 1a5257a0e8 Prevent use of project relative import statements
For details on the motivation of this change please visit:
https://wiki.python.org/moin/FutureProofPython
2017-03-07 11:23:37 +01:00

288 lines
9.7 KiB
Python

# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
from base64 import b64encode
from six.moves.urllib.request import urlopen
from six.moves.urllib.request import Request
from tempfile import NamedTemporaryFile
from tempfile import mkdtemp
from lxml import etree
import random
import glob
import os
# project
from kiwi.exceptions import KiwiUriOpenError
from kiwi.path import Path
from kiwi.command import Command
from kiwi.defaults import Defaults
class SolverRepositoryBase(object):
"""
Base class interface for SAT solvable creation.
Attributes
* :attr:`uri`
Instance of Uri class
"""
def __init__(self, uri, user=None, secret=None):
self.uri = uri
self.user = user
self.secret = secret
self._init_temporary_dir_names()
def create_repository_solvable(
self, target_dir=Defaults.get_solvable_location()
):
"""
Create SAT solvable for this repository from previously
created intermediate solvables by merge and store the
result solvable in the specified target_dir
:param string target_dir: path name
:return: file path to solvable
:rtype: string
"""
Path.create(target_dir)
solvable = os.sep.join(
[target_dir, self.uri.alias()]
)
if not self.is_uptodate(target_dir):
self._setup_repository_metadata()
solvable = self._merge_solvables(target_dir)
self._cleanup()
return solvable
def timestamp(self):
"""
Return repository timestamp
The retrieval of the repository timestamp depends on the
type of the repository and is therefore supposed to be implemented
in the specialized Solver Repository classes. If no such
implementation exists the method returns the value 'static'
to indicate there is no timestamp information available.
:rtype: string
"""
return 'static'
def is_uptodate(self, target_dir=Defaults.get_solvable_location()):
"""
Check if repository metadata is up to date
:rtype: bool
"""
solvable_time_file = ''.join(
[target_dir, os.sep, self.uri.alias(), '.timestamp']
)
if os.path.exists(solvable_time_file):
with open(solvable_time_file) as solvable_time:
saved_time = solvable_time.read()
if saved_time == self.timestamp() and not saved_time == 'static':
return True
return False
def download_from_repository(self, repo_source, target):
"""
Download given source file from the repository and store
it as target file
The repo_source location is used relative to the repository
location and will be part of a mime type source like:
file://repo_path/repo_source
:param string source: source file in the repo
:param string target: file path
"""
try:
request = Request(
os.sep.join([self._get_mime_typed_uri(), repo_source])
)
if self.user and self.secret:
credentials = b64encode(
format(':'.join([self.user, self.secret])).encode()
)
request.add_header(
'Authorization', b'Basic ' + credentials
)
location = urlopen(request)
except Exception as e:
raise KiwiUriOpenError(
'{0}: {1}'.format(type(e).__name__, format(e))
)
with open(target, 'wb') as target_file:
target_file.write(location.read())
def _get_repomd_xml(self, lookup_path='repodata'):
"""
Parse repomd.xml file from lookup_path and return an etree
This method only applies to rpm-md type repositories
:rtype: XML etree
"""
xml_download = NamedTemporaryFile()
xml_setup_file = os.sep.join([lookup_path, 'repomd.xml'])
self.download_from_repository(xml_setup_file, xml_download.name)
return etree.parse(xml_download.name)
def _get_repomd_xpath(self, xml_data, expression):
"""
Call the provided xpath expression on the root element
of the xml_data which must be an XML etree parsed document
and return the result. This method only applies to
repomd.xml files of the correct namespaces:
* http://linux.duke.edu/metadata/repo
* http://linux.duke.edu/metadata/rpm
:rtype: list
"""
namespace_map = dict(
repo='http://linux.duke.edu/metadata/repo',
rpm='http://linux.duke.edu/metadata/rpm'
)
return xml_data.getroot().xpath(
expression, namespaces=namespace_map
)
def _setup_repository_metadata(self):
"""
Download all relevant repository metadata and create
intermediate solvables from the result. The metadata structure
depends on the type of the repository and must be implemented
in the specialized Solver Repository classes.
"""
raise NotImplementedError
def _create_solvables(self, metadata_dir, tool):
"""
Create intermediate (before merge) SAT solvables from the
data given in the metadata_dir and store the result in the
temporary repository_solvable_dir. The given tool must
match the solvable data structure. There are the following
tools to create a solvable from repository metadata:
* rpmmd2solv
solvable from repodata files
* susetags2solv
solvable from SUSE (yast2) repository files
* comps2solv
solvable from RHEL component files
* rpms2solv
solvable from rpm header files
:param string metadata_dir: path name
:param string tool: one of the above tools
"""
if not self.repository_solvable_dir:
self.repository_solvable_dir = mkdtemp(prefix='solvable_dir.')
if tool is 'rpms2solv':
# solvable is created from a bunch of rpm files
bash_command = [
tool, os.sep.join([metadata_dir, '*.rpm']),
'>', self._get_random_solvable_name()
]
Command.run(['bash', '-c', ' '.join(bash_command)])
else:
# each file in the metadata_dir is considered a valid
# solvable for the selected solv tool
for source in glob.iglob('/'.join([metadata_dir, '*'])):
bash_command = [
'gzip', '-cd', '--force', source, '|', tool,
'>', self._get_random_solvable_name()
]
Command.run(['bash', '-c', ' '.join(bash_command)])
def _merge_solvables(self, target_dir):
"""
Merge all intermediate SAT solvables into one and store
the result in the given target_dir. In addition an
info file containing the repo url and a timestamp file
is created
:param string target_dir: path name
"""
if self.repository_solvable_dir:
solvable = os.sep.join([target_dir, self.uri.alias()])
bash_command = [
'mergesolv', '/'.join([self.repository_solvable_dir, '*']),
'>', solvable
]
Command.run(['bash', '-c', ' '.join(bash_command)])
with open('.'.join([solvable, 'info']), 'w') as solvable_info:
solvable_info.write(''.join([self.uri.uri, os.linesep]))
with open('.'.join([solvable, 'timestamp']), 'w') as solvable_time:
solvable_time.write(self.timestamp())
return solvable
def _cleanup(self):
"""
Delete all temporary directories
"""
for metadata_dir in self.repository_metadata_dirs:
Path.wipe(metadata_dir)
if self.repository_solvable_dir:
Path.wipe(self.repository_solvable_dir)
self._init_temporary_dir_names()
def _get_mime_typed_uri(self):
return self.uri.translate() if self.uri.is_remote() else ''.join(
['file://', self.uri.translate()]
)
def _init_temporary_dir_names(self):
"""
Initialize data structures to store temporary directory names
required to hold the repository metadata and solvable files
until the final repository solvable got created
"""
self.repository_metadata_dirs = []
self.repository_solvable_dir = None
def _create_temporary_metadata_dir(self):
"""
Create and manage a temporary metadata directory
"""
metadata_dir = mkdtemp(prefix='metadata_dir.')
self.repository_metadata_dirs.append(metadata_dir)
return metadata_dir
def _get_random_solvable_name(self):
if self.repository_solvable_dir:
return '{0}/solvable-{1}{2}{3}{4}'.format(
self.repository_solvable_dir,
self._rand(), self._rand(), self._rand(), self._rand()
)
def _rand(self):
return '%02x' % random.randrange(1, 0xfe)
def __del__(self):
self._cleanup()