scullery/scullery.py
Georg Pfuetzenreuter ce0ad136aa
Implement refresh argument
Allows for re-provisioning of VM's.

Signed-off-by: Georg Pfuetzenreuter <mail@georg-pfuetzenreuter.net>
2023-05-20 18:01:13 +02:00

197 lines
6.9 KiB
Python
Executable File

#!/usr/bin/python3
"""
Copyright 2023, Georg Pfuetzenreuter
Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence").
You may not use this work except in compliance with the Licence.
An English copy of the Licence is shipped in a file called LICENSE along with this applications source code.
You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12.
---
Scullery - a SaltStack testing tool.
"""
from argparse import ArgumentParser
from configparser import ConfigParser
import logging
import os
import sys
argparser = ArgumentParser()
config = ConfigParser()
env = os.environ.copy()
arggroup = argparser.add_mutually_exclusive_group()
argparser.add_argument('--debug', help='Print extremely verbose output', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
argparser.add_argument('--config', help='Specify the configuration file to use', default='{}/scullery.ini'.format(os.getcwd()))
argparser.add_argument('--env', help='Write environment file for direct use of Vagrant', action='store_true')
argparser.add_argument('--suite', help='Specify the suite to run', required=True)
arggroup.add_argument('--stop', help='Stop running machines', action='store_true')
arggroup.add_argument('--test', help='Start machines and run tests', action='store_true')
arggroup.add_argument('--status', help='Get Vagrant deployment status', action='store_true')
arggroup.add_argument('--refresh', help='Re-sync files and re-run bootstrap scripts', action='store_true')
args = argparser.parse_args()
configfile = args.config
vmprefix = 'scullery'
def _abort(msg):
log.error(msg)
sys.exit(1)
def _config():
configmap = {'box': {}, 'suites': {}}
if not config.options('box'):
_abort('No "box" section found in the configuration file')
for option in config.options('box'):
configmap['box'][option] = config.get('box', option)
suites = [section for section in config.sections() if section.startswith('suite.')]
if not len(suites):
_abort('No suites configured')
log.debug('Suites: {}'.format(str(suites)))
for section in suites:
suite = section.replace('suite.', '')
configmap['suites'][suite] = {}
for option in config.options(section):
if option in ['masters', 'minions']:
value = config.getint(section, option)
else:
value = config.get(section, option)
configmap['suites'][suite][option] = value
log.debug('Config map: {}'.format(str(configmap)))
return configmap
def _vagrant(quiet=False):
return vagrant.Vagrant(quiet_stdout=False, quiet_stderr=quiet)
def genvms(flavor, amount):
vms = []
for i in range(amount):
vms.append('{}-{}{}'.format(vmprefix, flavor, i))
return vms
def _setenv(envmap, dump=False):
if dump:
log.debug('Writing environment variable file')
fh = open('.scullery_env', 'w')
for variable, value in envmap.items():
if value is not None:
if isinstance(value, list):
value = ','.join(value)
env[variable] = value
if dump:
fh.write(f'{variable}={value}\n')
if dump:
fh.close()
def vagrant_env(box_name, box_image, minions=None, masters=None, vagrantfile=None, bootstrap=None):
envmap = {'VAGRANT_VAGRANTFILE': vagrantfile, 'SCULLERY_BOX_NAME': box_name, 'SCULLERY_BOX_IMAGE': box_image,
'SCULLERY_MASTERS': masters, 'SCULLERY_MINIONS': minions, 'SCULLERY_BOOTSTRAP': bootstrap}
log.debug('Environment variable map: {}'.format(str(envmap)))
_setenv(envmap, args.env)
v.env = env
def vagrant_isup(suite):
ok = 0
nok = 0
statuses = v.status()
total = len(statuses)
for status in statuses:
if status.state == 'running':
ok += 1
else:
nok +=1
if ok == total:
return True, None
elif nok == total:
return False, True
else:
return False, False
def main_interactive():
configmap = _config()
box = configmap['box']
box_name = box.get('name', None)
box_image = box.get('image', None)
box_file = box.get('file', '{}/Vagrantfile-Template'.format(os.path.abspath(os.path.dirname(__file__))))
if any([box_name, box_image, box_file]) is None:
_abort('Box configuration is incomplete')
box_bootstrap = box.get('bootstrap', None)
suites = configmap['suites']
suite = args.suite
if suite not in suites:
_abort('No suite named {}'.format(suite))
suiteconf = configmap['suites'][suite]
minions = None
masters = None
if suiteconf.get('minions', 0) > 0:
minions = genvms('minion', suiteconf['minions'])
if suiteconf.get('masters', 0) > 0:
masters = genvms('master', suiteconf['masters'])
vagrant_env(box_name, box_image, minions, masters, box_file, box_bootstrap)
if args.status:
log.info('Status report: {}'.format(v.status()))
return True
status = vagrant_isup(suite)
if status[0] is True and status[1] is None or args.stop or args.refresh:
if args.stop is True:
log.info('Destroying machines ...')
v.destroy()
if vagrant_isup(suite)[0] is False:
log.debug('OK')
else:
_abort('Destruction failed')
elif not args.refresh:
log.info('Deployment is already running')
elif args.refresh:
log.info('Deployment is running, initiating refresh ...')
v.provision()
elif status[0] is False:
if status[1] is True:
log.debug('Deployment is not running')
elif status[1] is False:
log.warning('Deployment is in an inconsistent state, destroying ...')
try:
v.destroy()
except Exception as myerror:
log.exception(myerror)
_abort('Unhandled error')
if args.stop is False:
log.info('Launching {} ...'.format(suite))
v.up()
if vagrant_isup(suite)[0] is True:
log.debug('OK')
else:
_abort('Start failed')
logging.basicConfig(format='%(asctime)s %(levelname)s - %(funcName)s: %(message)s', datefmt='%H:%M:%S')
log = logging.getLogger('scullery')
if __name__ == '__main__':
log.setLevel(args.loglevel)
log.debug(args)
if args.loglevel == logging.WARNING:
quiet_stderr = True
else:
quiet_stderr = False
log.debug('Vagrant stderr: {}'.format(str(quiet_stderr)))
try:
import vagrant
except ImportError as myerror:
_abort('Could not load python-vagrant')
if os.path.isfile(configfile):
config.read(configfile)
else:
_abort('Unable to locate configuration file at {}'.format(configfile))
if __name__ == '__main__':
v = _vagrant(quiet_stderr)
main_interactive()
else:
v = _vagrant()