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.

172 lines
6.8 KiB

import datetime
import json
import os
import sys
from functools import wraps
from itertools import chain
from leapp import FULL_VERSION
from leapp.libraries.stdlib.call import _call
from leapp.utils.audit import get_messages
try:
from json.decoder import JSONDecodeError # pylint: disable=ungrouped-imports
except ImportError:
JSONDecodeError = ValueError
def runs_in_container():
"""
Check if the current process is running inside a container
:return: True if the process is running inside a container, False otherwise
"""
return os.path.exists('/run/host/container-manager')
def _flattened(d):
""" Flatten nested dicts and lists into a single dict """
def expand(key, value):
if isinstance(value, dict):
return [(key + '.' + k, v) for k, v in _flattened(value).items()]
if isinstance(value, list):
return chain(*[expand(key + '.' + str(i), v) for i, v in enumerate(value)])
return [(key, value)]
items = [item for k, v in d.items() for item in expand(k, v)]
return dict(items)
class _BreadCrumbs(object):
def __init__(self, activity):
self._crumbs = {
'activity': activity,
'packages': self._get_packages(),
'leapp_file_changes': [],
'executed': ' '.join([v if ' ' not in v else '"{}"'.format(v) for v in sys.argv]),
'success': True,
'activity_started': datetime.datetime.utcnow().isoformat() + 'Z',
'activity_ended': datetime.datetime.utcnow().isoformat() + 'Z',
'source_os': '',
'target_os': '',
'env': dict(),
'run_id': '',
'version': FULL_VERSION,
}
def fail(self):
self._crumbs['success'] = False
def _save_rhsm_facts(self, activities):
if not os.path.isdir('/etc/rhsm/facts'):
if not os.path.exists('/etc/rhsm'):
# If there's no /etc/rhsm folder just skip it
return
os.mkdir('/etc/rhsm/facts')
try:
with open('/etc/rhsm/facts/leapp.facts', 'w') as f:
json.dump(_flattened({
'leapp': [
activity for activity in activities
if activity.get('activity', '') in ('preupgrade', 'upgrade')]
}), f, indent=4)
self._commit_rhsm_facts()
except OSError:
# We don't care about failing to 'create' the file here
# even though it shouldn't though, just ignore it
pass
def _commit_rhsm_facts(self):
if runs_in_container():
return
cmd = ['/usr/sbin/subscription-manager', 'facts', '--update']
try:
_call(cmd, lambda x, y: None, lambda x, y: None)
except (OSError, ValueError, TypeError):
# We don't care about errors here, just ignore them
pass
def save(self):
self._crumbs['run_id'] = os.environ.get('LEAPP_EXECUTION_ID', 'N/A')
self._crumbs['leapp_file_changes'].extend(self._verify_leapp_pkgs())
messages = get_messages(('IPUConfig',), self._crumbs['run_id'])
versions = json.loads((messages or [{}])[0].get('message', {}).get(
'data', '{}')).get('version', {'target': 'N/A', 'source': 'N/A'})
self._crumbs['target_os'] = 'Red Hat Enterprise Linux {target}'.format(**versions)
self._crumbs['source_os'] = 'Red Hat Enterprise Linux {source}'.format(**versions)
self._crumbs['activity_ended'] = datetime.datetime.utcnow().isoformat() + 'Z'
self._crumbs['env'] = {k: v for k, v in os.environ.items() if k.startswith('LEAPP_')}
try:
with open('/etc/migration-results', 'a+') as crumbs:
crumbs.seek(0)
doc = {'activities': []}
try:
content = json.load(crumbs)
if isinstance(content, dict):
if isinstance(content.get('activities', None), list):
doc = content
except JSONDecodeError:
# Expected to happen when /etc/migration-results is still empty or does not yet exist
pass
doc['activities'].append(self._crumbs)
crumbs.seek(0)
crumbs.truncate()
json.dump(doc, crumbs, indent=2, sort_keys=True)
crumbs.write('\n')
if os.environ.get('LEAPP_NO_RHSM_FACTS', '0') != '1':
self._save_rhsm_facts(doc['activities'])
except OSError:
sys.stderr.write('WARNING: Could not write to /etc/migration-results\n')
def _get_packages(self):
cmd = ['/bin/bash', '-c', 'rpm -qa --queryformat="%{nevra} %{SIGPGP:pgpsig}\n" | grep -Ee "leapp|snactor"']
res = _call(cmd, lambda x, y: None, lambda x, y: None)
if res.get('exit_code', None) == 0:
if res.get('stdout', None):
return [{'nevra': t[0], 'signature': t[1]}
for t in [line.strip().split(' ', 1) for line in res['stdout'].split('\n') if line.strip()]]
return []
def _verify_leapp_pkgs(self):
if not os.environ.get('LEAPP_IPU_IN_PROGRESS'):
return []
upg_path = os.environ.get('LEAPP_IPU_IN_PROGRESS').split('to')
cmd = ['/bin/bash', '-c', 'rpm -V leapp leapp-upgrade-el{}toel{}'.format(upg_path[0], upg_path[1])]
res = _call(cmd, lambda x, y: None, lambda x, y: None)
if res.get('exit_code', None) == 1:
if res.get('stdout', None):
return [{'result': t[0], 'file_name': t[1]}
for t in [line.strip().split(' ', 1) for line in res['stdout'].split('\n') if line.strip()]]
return []
def produces_breadcrumbs(f):
"""
Ensures that `/etc/migration-results` gets produced on every invocation of `leapp upgrade` & `leapp preupgrade`
Every execution of the upgrade will have their own entry in the /etc/migration-results file.
For a user flow like: leapp preupgrade && leapp upgrade && reboot there should be 5 new entries in the file:
1. leapp preupgrade
2. leapp upgrade (Source OS)
3. leapp upgrade (Initram Phase - Until including RPM transaction)
4. leapp upgrade (Initram Phase - Post RPM Transaction)
5. leapp upgrade (Target OS - First Boot)
Depending on future design changes of the IPU Worklow, the output may vary.
"""
@wraps(f)
def wrapper(*args, **kwargs):
breadcrumbs = _BreadCrumbs(activity=f.__name__)
try:
return f(*args, breadcrumbs=breadcrumbs, **kwargs)
except SystemExit as e:
if e.code != 0:
breadcrumbs.fail()
raise
except BaseException:
breadcrumbs.fail()
raise
finally:
breadcrumbs.save()
return wrapper