177 lines
6.2 KiB
Python
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
|
|
|