def is_sysconfig_default(): """Check if the memcached sysconfig file was not modified since installation.""" try: result = run(['rpm', '-V', '--nomtime', 'memcached'], checked=False) return sysconfig_path not in result['stdout'] except OSError as e: api.current_logger().warn("rpm verification failed: %s", str(e)) return True
def enable_rhsm_repos(): """ Try enabling all the RHEL 8 repositories that have been used for the upgrade transaction. In case of custom repositories, the subscription-manager reports an error that it doesn't know them, but it enables the known repositories. """ if rhsm.skip_rhsm(): api.current_logger().debug('Skipping enabling repositories through subscription-manager due to the use of' ' LEAPP_DEVEL_SKIP_RHSM.') return try: run(get_submgr_cmd(get_repos_to_enable())) except CalledProcessError as err: api.current_logger().warning('The subscription-manager could not enable some repositories.\n' 'It is expected behavior in case of custom repositories unknown to' ' the subscription-manager - these need to be enabled manually.\n{0}' .format(str(err)))
def call_with_oserror_handled(cmd): """ Perform run with already handled OSError for some convenience. """ try: run(cmd) except OSError as e: if cmd: raise StopActorExecutionError( message=str(e), details={ 'hint': 'Please ensure that {} is installed and executable.'.format(cmd[0]) } ) else: raise StopActorExecutionError( message='Failed to execute command {} with: {}'.format(''.join(cmd), str(e)) )
def get_booted_kernel(): """Get version and release of the currently used kernel in one string.""" try: return run(['/usr/bin/uname', '-r'])['stdout'].strip() except CalledProcessError as e: raise StopActorExecutionError( message='Unable to obtain release of the booted kernel.', details={'details': str(e), 'stderr': e.stderr} )
def test_no_encoding(): """ Test the output is base64 encoded after getting binary data :return: Pass/Fail """ cmd = ['echo', '-n', '-e', '\\xeb'] result = run(cmd, encoding=None) assert isinstance(result['stdout'], six.binary_type)
def scan_pci_devices(producer): ''' Scan system PCI Devices ''' try: output = run(['lspci', '-mm'], split=True)['stdout'] except CalledProcessError: output = [] devices = parse_pci_devices(output) produce_pci_devices(producer, devices)
def get_rhel8_kernel_version(self): kernels = run(["rpm", "-q", "kernel"], split=True)["stdout"] for kernel in kernels: version = kernel.split("-", 1)[1] if "el8" in version: return version raise StopActorExecutionError( "Cannot get version of the installed RHEL-8 kernel", details={"details": "\n".join(kernels)})
def _run_cmd(cmd, logmsg="", split=False): try: return run(cmd, split=split).get("stdout", "") except CalledProcessError as e: # Only report issues when they are explicitly described. # This way expected failures are not reported. if logmsg: api.current_logger().warning("%s: %s", logmsg, str(e.stderr)) return None
def test_stdin_fd(): r, w = os.pipe() # The string we write here should not exceed `/proc/sys/fs/pipe-max-size` # which represents the size of the kernel buffer backing the pipe os.write(w, b'LOREM IPSUM') os.close(w) ret = run(('bash', '-c', 'read MSG; echo "<$MSG>"'), stdin=r) os.close(r) assert ret['stdout'] == '<LOREM IPSUM>\n'
def process(): kernel_version = next(api.consume(InstalledTargetKernelVersion), None) if kernel_version: for arg in api.consume(KernelCmdlineArg): cmd = ['grubby', '--update-kernel=/boot/vmlinuz-{}'.format(kernel_version.version), '--args={}={}'.format(arg.key, arg.value)] try: stdlib.run(cmd) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the entry is not updated in the ZIPL bootloader # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 stdlib.run(['/usr/sbin/zipl']) except (OSError, stdlib.CalledProcessError) as e: raise StopActorExecutionError( "Failed to append extra arguments to kernel command line.", details={"details": str(e)})
def _get_firewall_status(service_name): try: ret_list = run(['systemctl', 'is-active', service_name], split=True)['stdout'] active = ret_list[0] == 'active' except CalledProcessError: active = False logger.debug('The %s service is likely not active' % service_name) try: ret_list = run(['systemctl', 'is-enabled', service_name], split=True)['stdout'] enabled = ret_list[0] == 'enabled' except CalledProcessError: enabled = False logger.debug('The %s service is likely not enabled nor running' % service_name) return FirewallStatus( active=active, enabled=enabled, )
def process(self): service_name = 'leapp_resume.service' if os.path.isfile('/etc/systemd/system/{}'.format(service_name)): run(['systemctl', 'disable', service_name]) try: os.unlink('/etc/systemd/system/{}'.format(service_name)) os.unlink('/etc/systemd/system/default.target.wants/{}'.format( service_name)) except OSError as e: if e.errno != errno.ENOENT: raise create_report([ reporting.Title('"{}" service deleted'.format(service_name)), reporting.Summary( '"{}" was taking care of resuming upgrade process ' 'after the first reboot.'.format(service_name)), reporting.Tags([reporting.Tags.UPGRADE_PROCESS]), ])
def read_rpm_modifications(): """Asks RPM database whether the configuration file was modified.""" try: return run(['rpm', '-Vf', CONFIG], split=True, checked=False)['stdout'] except OSError as err: error = 'Failed to check the modification status of the {}: {}' \ ''.format(CONFIG, str(err)) api.current_logger().error(error) return []
def unit_enabled(self, name): try: ret = run(['systemctl', 'is-enabled', name], split=True)['stdout'] if len(ret) > 0: enabled = ret[0] == 'enabled' else: enabled = False except (OSError, CalledProcessError): enabled = False return enabled
def process(self): cmdline = run(['cat', '/proc/cmdline'])['stdout'].strip() parameters = [] for parameter in cmdline.split(' '): if '=' in parameter: kv = parameter.split('=') parameters.append(KernelCmdlineArg(key=kv[0], value=kv[1])) else: parameters.append(KernelCmdlineArg(key=parameter)) self.produce(KernelCmdline(parameters=parameters))
def remove_custom_modules(): # remove custom SElinux modules - to be reinstalled after the upgrade for semodules in api.consume(SELinuxModules): api.current_logger().info( "Removing custom SELinux policy modules. Count: %d", len(semodules.modules)) for module in semodules.modules: api.current_logger().info("Removing %s on priority %d.", module.name, module.priority) try: run([ 'semodule', '-X', str(module.priority), '-r', module.name ]) except CalledProcessError as e: api.current_logger().warning( "Failed to remove module %s on priority %d: %s", module.name, module.priority, str(e.stderr)) continue
def process(self): model = next(self.consume(Authselect)) decision = next(self.consume(AuthselectDecision)) if not decision.confirmed or model.profile is None: return command = ['authselect', 'select', '--force', model.profile ] + model.features try: run(command) except CalledProcessError as err: report_generic(title='Authselect call failed.', summary=str(err)) return report_generic(title='System was converted to authselect.', summary='System was converted to authselect with the ' 'following call: "{}"'.format(' '.join(command)))
def test_split(): """ Test the output lines are split into lists. :return: Pass/Fail """ cmd = ['ls'] result = run(cmd, split=True) assert isinstance(result['stdout'], list) assert len(result['stdout']) > 1
def check_module(name): """ Check if given module contains one of removed types and comment out corresponding lines. The function expects a text file "$name" containing cil policy to be present in the current directory. Returns a list of invalid lines. """ try: removed = run(['grep', '-w', '-E', "|".join(REMOVED_TYPES_), name], split=True) # Add ";" at the beginning of invalid lines (comment them out) run([ 'sed', '-i', '/{}/s/^/;/g'.format(r'\|'.join(REMOVED_TYPES_)), name ]) return removed.get("stdout", []) except CalledProcessError: return []
def test_repos_mapping(current_actor_context): repos_data = [ RepositoryData(repoid='rhel-7-server-rpms', name='RHEL 7 Server'), RepositoryData(repoid='rhel-7-blacklisted-rpms', name='RHEL 7 Blacklisted') ] repos_files = [ RepositoryFile(file='/etc/yum.repos.d/redhat.repo', data=repos_data) ] facts = RepositoriesFacts(repositories=repos_files) arch = stdlib.run(['uname', '-m'])['stdout'].strip() mapping = [ RepositoryMap(from_repoid='rhel-7-server-rpms', to_repoid='rhel-8-for-{}-baseos-htb-rpms'.format(arch), to_pes_repo='rhel8-baseos', from_minor_version='all', to_minor_version='all', arch=arch, repo_type='rpm'), RepositoryMap( from_repoid='rhel-7-server-rpms', to_repoid='rhel-8-for-{}-appstream-htb-rpms'.format(arch), to_pes_repo='rhel8-appstream', from_minor_version='all', to_minor_version='all', arch=arch, repo_type='rpm'), RepositoryMap(from_repoid='rhel-7-blacklist-rpms', to_repoid='rhel-8-blacklist-rpms', to_pes_repo='rhel8-blacklist', from_minor_version='all', to_minor_version='all', arch=arch, repo_type='rpm') ] repos_map = RepositoriesMap(repositories=mapping) repos_blacklisted = RepositoriesBlacklisted( repoids=['rhel-8-blacklisted-rpms']) current_actor_context.feed(facts) current_actor_context.feed(repos_map) current_actor_context.feed(repos_blacklisted) current_actor_context.run() assert current_actor_context.consume(TargetRepositories) rhel_repos = current_actor_context.consume( TargetRepositories)[0].rhel_repos assert len(rhel_repos) == 2 assert {repo.repoid for repo in rhel_repos} == { 'rhel-8-for-x86_64-baseos-htb-rpms', 'rhel-8-for-x86_64-appstream-htb-rpms' }
def copy_dracut_modules(modules): """ Copy every dracut module with specified path into the expected directory. original content is overwritten if exists """ # FIXME: use just python functions instead of shell cmds for module in modules: if not module.module_path: continue try: # context.copytree_to(module.module_path, os.path.join(DRACUT_DIR, os.path.basename(module.module_path))) run(['cp', '-f', '-a', module.module_path, DRACUT_DIR]) except CalledProcessError as e: api.current_logger().error('Failed to copy dracut module "{name}" from "{source}" to "{target}"'.format( name=module.name, source=module.module_path, target=DRACUT_DIR), exc_info=True) # FIXME: really do we want to raise the error and stop execution completely??.... raise StopActorExecutionError( message='Failed to install dracut modules required in the target initramfs. Error: {}'.format(str(e)) )
def cleanup_scratch(scratch_dir, mounts_dir): """ Function to cleanup the scratch directory """ api.current_logger().debug('Cleaning up mounts') if os.path.ismount(mounts_dir): api.current_logger().debug( 'Mounts directory is a mounted disk image - Unmounting.') try: run(['/bin/umount', '-fl', mounts_dir]) api.current_logger().debug('Unmounted mounted disk image.') except (OSError, CalledProcessError) as e: api.current_logger().warn('Failed to umount %s - message: %s', mounts_dir, str(e)) api.current_logger().debug('Recursively removing scratch directory %s.', scratch_dir) shutil.rmtree(scratch_dir, onerror=utils.report_and_ignore_shutil_rmtree_error) api.current_logger().debug('Recursively removed scratch directory %s.', scratch_dir)
def guard_call(cmd, guards=(), print_output=False): try: if print_output: return run(cmd, callback_raw=_logging_handler), None return run(cmd, split=True)['stdout'], None except CalledProcessError as e: # return custom error if process failed error = ErrorData(details=str(e)) # collect output from guards for possible spurious failure guard_errors = [] for guard in guards: err = guard() if err: guard_errors.append(err) if guard_errors: error.spurious = '. Possible spurious failure: {cause}'.format(cause=' '.join(guard_errors)) return None, error
def get_boot_partition(): """ Get /boot partition """ try: # call grub2-probe to identify /boot partition result = run(['grub2-probe', '--target=device', '/boot']) except CalledProcessError: api.current_logger().warning( 'Could not get name of underlying /boot partition') raise StopActorExecution() return result['stdout'].strip()
def _get_kernel_version(kernel_name): try: kernels = run(['rpm', '-q', kernel_name], split=True)['stdout'] except CalledProcessError: return '' for kernel in kernels: # name-version-release - we want the last two fields only version = '-'.join(kernel.split('-')[-2:]) if 'el8' in version: return version return ''
def add_boot_entry(configs=None): debug = 'debug' if os.getenv('LEAPP_DEBUG', '0') == '1' else '' kernel_dst_path, initram_dst_path = get_boot_file_paths() try: _remove_old_upgrade_boot_entry(kernel_dst_path, configs=configs) cmd = [ '/usr/sbin/grubby', '--add-kernel', '{0}'.format(kernel_dst_path), '--initrd', '{0}'.format(initram_dst_path), '--title', 'RHEL-Upgrade-Initramfs', '--copy-default', '--make-default', '--args', '{DEBUG} enforcing=0 rd.plymouth=0 plymouth.enable=0'.format( DEBUG=debug) ] if configs: for config in configs: run(cmd + ['-c', config]) else: run(cmd) if architecture.matches_architecture(architecture.ARCH_S390X): # on s390x we need to call zipl explicitly because of issue in grubby, # otherwise the new boot entry will not be set as default # See https://bugzilla.redhat.com/show_bug.cgi?id=1764306 run(['/usr/sbin/zipl']) except CalledProcessError as e: raise StopActorExecutionError( 'Cannot configure bootloader.', details={'details': '{}: {}'.format(str(e), e.stderr)})
def process(): if config.is_debug: try: # the following command prints output of grubenv for debugging purposes and is repeated after setting # default kernel so we can be sure we have the right saved entry # # saved_entry=63... # kernelopts=root=/dev/mapper... # # # boot_success and boot_indeterminate parameters are added later by one-shot systemd service stdlib.run(['grub2-editenv', 'list']) except stdlib.CalledProcessError: api.current_logger().error( 'Failed to execute "grub2-editenv list" command') message = next(api.consume(InstalledTargetKernelVersion), None) if not message: api.current_logger().warning(( 'Skipped - Forcing checking and setting default boot entry to target kernel' ' version due to missing message')) return try: current_default_kernel = stdlib.run(['grubby', '--default-kernel' ])['stdout'].strip() except (OSError, stdlib.CalledProcessError): api.current_logger().warning( 'Failed to query grubby for default kernel', exc_info=True) return for type_ in ('index', 'title'): try: stdlib.run(['grubby', '--default-{}'.format(type_)]) except (OSError, stdlib.CalledProcessError): api.current_logger().warning( 'Failed to query grubby for default {}'.format(type_), exc_info=True) return kernel_info = get_kernel_info(message) if not kernel_info: return if current_default_kernel != kernel_info.kernel_path: api.current_logger().warning(( 'Current default boot entry not target kernel version: Current default: %s.' 'Forcing default kernel to %s'), current_default_kernel, kernel_info.kernel_path) update_default_kernel(kernel_info) if config.is_debug: try: stdlib.run(['grub2-editenv', 'list']) except stdlib.CalledProcessError: api.current_logger().error( 'Failed to execute "grub2-editenv list" command')
def remove_boot_entry(): # we need to make sure /boot is mounted before trying to remove the boot entry facts_msg = api.consume(FirmwareFacts) facts = next(facts_msg, None) if not facts: raise StopActorExecutionError('Could not identify system firmware', details={'details': 'Actor did not receive FirmwareFacts message.'}) mount_points_per_firmware = { 'bios': ['/boot'], 'efi': ['/boot', '/boot/efi'] } for mp in mount_points_per_firmware[facts.firmware]: try: run(['/bin/mount', mp]) except CalledProcessError: # partitions have been most likely already mounted pass kernel_filepath = get_upgrade_kernel_filepath() run([ '/usr/sbin/grubby', '--remove-kernel={0}'.format(kernel_filepath) ]) # TODO: Move calling `mount -a` to a separate actor as it is not really related to removing the upgrade boot entry. # It's worth to call it after removing the boot entry to avoid boot loop in case mounting fails. run([ '/bin/mount', '-a' ])
def process(self): model = next(self.consume(Authselect)) decision = next(self.consume(AuthselectDecision)) if not decision.confirmed or model.profile is None: return command = ['authselect', 'select', '--force', model.profile ] + model.features try: run(command) except CalledProcessError as err: create_report([ # pylint: disable-msg=too-many-arguments reporting.Title('Authselect call failed'), reporting.Summary(str(err)), reporting.Severity(reporting.Severity.MEDIUM), reporting.Tags([ reporting.Tags.AUTHENTICATION, reporting.Tags.SECURITY, reporting.Tags.TOOLS ]), reporting.Flags([reporting.Flags.FAILURE]) ] + resources) # pylint: disable-msg=too-many-arguments return try: run(['systemctl', 'enable', 'oddjobd.service']) except (OSError, CalledProcessError) as e: self.log.warning('Error enabling oddjobd.service: {}'.format(e)) create_report([ # pylint: disable-msg=too-many-arguments reporting.Title('System was converted to authselect.'), reporting.Summary('System was converted to authselect with the ' 'following call: "{}"'.format( ' '.join(command))), reporting.Tags([ reporting.Tags.AUTHENTICATION, reporting.Tags.SECURITY, reporting.Tags.TOOLS ]) ] + resources) # pylint: disable-msg=too-many-arguments
def get_installed_rpms(): rpm_cmd = [ '/bin/rpm', '-qa', '--queryformat', r'%{NAME}|%{VERSION}|%{RELEASE}|%|EPOCH?{%{EPOCH}}:{(none)}||%|PACKAGER?{%{PACKAGER}}:{(none)}||%|' r'ARCH?{%{ARCH}}:{}||%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{(none)}|}|\n' ] try: return stdlib.run(rpm_cmd, split=True)['stdout'] except stdlib.CalledProcessError as err: error = 'Execution of {CMD} returned {RC}. Unable to find installed packages.'.format( CMD=err.command, RC=err.exit_code) stdlib.api.current_logger().error(error) return []