sau/sau/freebsd.py

137 lines
4.9 KiB
Python

import logging
import random
import re
import time
import sau
import sau.errors
import sau.helpers
PKG_PATH='/usr/sbin/pkg'
SERVICE_PATH='/usr/sbin/service'
FREEBSD_UPDATE_PATH='/usr/sbin/freebsd-update'
# regex to identify rc.d-files
rc_script_re = re.compile(r'^(?:/usr/local)?/etc/rc\.d/(.*)$')
# regex to parse packages
pkg_upgrade_re = re.compile(r'^\s([^\s]*): ([^\s]*) -> ([^\s]*).*$')
def identify_service_from_bin(exe):
log = logging.getLogger(sau.LOGNAME)
cmd = [ PKG_PATH, 'which', '-q', exe ]
ret, pkg, err = sau.helpers.exec_cmd(cmd)
if not pkg:
raise sau.errors.UnknownServiceError("'{}' does not belong to any package".format(exe))
pkg = pkg.strip()
log.debug('{} belongs to package {}'.format(exe, pkg))
cmd = [ PKG_PATH, 'info', '-l', pkg ]
ret, out, err = sau.helpers.exec_cmd(cmd)
rc_scripts = set()
for line in out.splitlines():
match = re.match(rc_script_re, line.strip())
if match:
log.debug('found potential rc-script for {} at {}'.format(exe, match.group(1)))
rc_scripts.add(match.group(1))
if len(rc_scripts) < 1:
raise sau.errors.UnknownServiceError("'{}' belongs to package '{}', but no rc-script was found".format(exe, pkg))
if len(rc_scripts) > 1:
raise sau.errors.UnkownServiceError("'{}' belongs to package '{}, but it contains multiple rc-scripts: {}".format(exe, pkg, ', '.join(rc_scripts)))
return rc_scripts.pop()
def restart_service(service):
log = logging.getLogger(sau.LOGNAME)
cmd = [ SERVICE_PATH, service, 'restart' ]
ret, out, err = sau.helpers.exec_cmd(cmd)
if ret != 0:
log.warning("Restart of {} failed:".format(service))
for line in out.splitlines():
log.warning("stdout: {}".format(line))
for line in err.splitlines():
log.warning("stderr: {}".format(line))
else:
log.info("restarted service {}".format(service))
def system_upgrade():
log = logging.getLogger(sau.LOGNAME)
# this is how freebsd wants it...
time.sleep(random.randint(0, 3600))
# now we can lie without soiling our conscience too much
cmd = [ FREEBSD_UPDATE_PATH, '--not-running-from-cron', 'fetch', 'install' ]
ret, out, err = sau.helpers.exec_cmd(cmd, timeout=7200, env = { 'PAGER': '/bin/cat' })
if ret == 0:
if out.endswith('No updates are available to install.\n'):
log.info('No system updates are available')
else:
log.info('System updates installed:')
for line in out.splitlines():
log.info(line)
return True
else:
log.warning('System upgrade failed (return code {}):'.format(ret))
for line in out.splitlines():
log.warning('stdout: {}'.format(line))
for line in err.splitlines():
log.warning('stderr: {}'.format(line))
return False
def pkg_upgrade():
log = logging.getLogger(sau.LOGNAME)
conf = sau.config
cmd = [ PKG_PATH, 'upgrade', '-nq' ]
ret, out, err = sau.helpers.exec_cmd(cmd)
if ret == 0 and not out and not err:
log.info('No package upgrades available')
return False
upgrades = []
for line in out.splitlines():
match = re.match(pkg_upgrade_re, line)
if match:
upgrades.append({
'pkg': match.group(1),
'version_old': match.group(2),
'version_new': match.group(3)
})
if not upgrades or err:
log.warning('Could not parse pkg output:')
for line in out.splitlines():
log.warning('stdout: {}'.format(line))
for line in err.splitlines():
log.warning('stderr: {}'.format(line))
return False
default_version_sens = conf.getint('default', 'version_sensitivity', fallback=1)
for pkg in upgrades:
pkg['upgrade_level'] = sau.helpers.version_diff(pkg['version_new'], pkg['version_old'])
log.info('pkg upgrade available {}'.format(pkg))
sens = conf.getint('packages', pkg['pkg'], fallback=default_version_sens)
if sens <= pkg['upgrade_level']:
log.debug('configured level {} <= pkg level {}'.format(sens, pkg['upgrade_level']))
pkg['upgrade'] = True
else:
log.debug('configured level {} > pkg level {}'.format(sens, pkg['upgrade_level']))
pkg['upgrade'] = False
for pkg in [x for x in upgrades if not x['upgrade']]:
log.warning('Package require manual upgrade, no upgrades done: {} {} -> {}'.format(pkg['pkg'], pkg['version_old'], pkg['version_new']))
return False
cmd = [ PKG_PATH, 'upgrade', '-yq' ]
ret, out, err = sau.helpers.exec_cmd(cmd, timeout=3600)
if ret != 0 or err:
log.warning('{} failed:'.format(' '.join(cmd)))
for line in out.splitlines():
log.warning('stdout: {}'.format(line))
for line in err.splitlines():
log.warning('stderr: {}'.format(line))
return True