sau/sau/freebsd.py

177 lines
6.2 KiB
Python

import logging
import random
import re
import subprocess
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 ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
pkg, err = proc.communicate()
if not pkg:
raise sau.errors.UnknownServiceError("'{}' does not belong to any package".format(exe))
pkg = pkg.decode('utf-8').strip()
log.debug('{} belongs to package {}'.format(exe, pkg))
cmd = [ PKG_PATH, 'info', '-l', pkg ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = proc.communicate()
rc_scripts = set()
for line in out.decode('utf-8').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' ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = proc.communicate()
out = out.decode('utf-8')
err = err.decode('utf-8')
if proc.returncode != 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' ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
env = { 'PAGER': '/bin/cat' })
out, err = proc.communicate()
out = out.decode('utf-8')
err = err.decode('utf-8')
if proc.returncode == 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(proc.returncode))
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' ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = proc.communicate()
out = out.decode('utf-8')
err = err.decode('utf-8')
if proc.returncode == 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_diff = conf.getint('default', 'min_version_diff', fallback=2)
for pkg in upgrades:
pkg['upgrade_level'] = sau.helpers.version_diff(pkg['version_new'], pkg['version_old'])
log.info('pkg upgrade available {}'.format(pkg))
diff = conf.getint('packages', pkg['pkg'], fallback=default_version_diff)
if diff >= pkg['upgrade_level']:
log.debug('configured level {} >= pkg level {}'.format(diff, pkg['upgrade_level']))
pkg['upgrade'] = True
else:
log.debug('configured level {} < pkg level {}'.format(diff, 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' ]
log.debug('Executing "{}"'.format(' '.join(cmd)))
proc = subprocess.Popen(
cmd,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
out, err = proc.communicate()
out = out.decode('utf-8')
err = err.decode('utf-8')
if proc.returncode != 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