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
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
|