diff --git a/opensuse-salt-formulas/README.md b/opensuse-salt-formulas/README.md new file mode 100644 index 0000000..47523fa --- /dev/null +++ b/opensuse-salt-formulas/README.md @@ -0,0 +1 @@ +These files eventually became [Scullery](https://git.com.de/Georg/scullery). diff --git a/opensuse-salt-formulas/Vagrantfile b/opensuse-salt-formulas/Vagrantfile new file mode 100644 index 0000000..6a75a6d --- /dev/null +++ b/opensuse-salt-formulas/Vagrantfile @@ -0,0 +1,66 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure("2") do |config| + config.vm.provider "libvirt" + config.vm.box = "tumbleweed-salt" + config.vm.box_url = "https://download.opensuse.org/repositories/home:/crameleon:/appliances/openSUSE_Tumbleweed/Tumbleweed.x86_64-libvirt.box" + config.vm.provision "shell", env: {"APPLY" => ENV['APPLY']}, inline: <<-SHELL + if [ ! -d /srv/formulas ] + then + mkdir /srv/formulas + fi + if [ ! -d /srv/pillar/samples ] + then + mkdir /srv/pillar/samples + fi + for formula in $(find /vagrant -mindepth 1 -maxdepth 1 -type d -name '*-formula' -printf '%P\\n') + do + echo "$formula" + fname="${formula%%-*}" + src_states="$formula/$fname" + src_formula="/vagrant/$src_states" + src_pillar="/vagrant/$formula/pillar.example" + src_test_pillar="/vagrant/$formula/test/pillar/default.sls" + if [ ! -d "$src_formula" ] + then + fname="${fname//_/-}" + src_states="$formula/$fname" + src_formula="/vagrant/$src_states" + fi + if [ ! -h "/srv/formulas/$fname" ] + then + ln -s "$src_formula" "/srv/formulas" + fi + dst_pillar="/srv/pillar/samples/$fname.sls" + if [ -f "$src_test_pillar" ] + then + cp "$src_test_pillar" "$dst_pillar" + elif [ -f "$src_pillar" ] + then + cp "$src_pillar" "$dst_pillar" + fi + done + printf 'file_roots:\n base:\n - /srv/salt\n - /srv/formulas\n' > /etc/salt/minion.d/roots.conf + tee /srv/pillar/top.sls >/dev/null </dev/null < /etc/salt/master.d/notsecure.conf + printf 'file_roots:\n base:\n - /srv/salt\n - /srv/formulas\n' > /etc/salt/master.d/roots.conf + systemctl enable --now salt-master + SHELL + end + [ + "minion1", + "minion2", + ].each do |vmname| + config.vm.define "#{vmname}" do |minion_config| + minion_config.vm.synced_folder '.', '/vagrant', disabled: true + minion_config.vm.hostname = "salt-#{vmname}" + minion_config.vm.provision "shell", inline: <<-SHELL + sed -i 's/^#master:.*/master: salt-master/' /etc/salt/minion + systemctl enable --now salt-minion + SHELL + end + end +end diff --git a/opensuse-salt-formulas/Vagrantfile-Template b/opensuse-salt-formulas/Vagrantfile-Template new file mode 100644 index 0000000..39ccc70 --- /dev/null +++ b/opensuse-salt-formulas/Vagrantfile-Template @@ -0,0 +1,51 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +if ! ( ENV['SCULLERY_BOX_NAME'] && ENV['SCULLERY_BOX_IMAGE'] ) || ! ( ENV['SCULLERY_MASTERS'] || ENV['SCULLERY_MINIONS'] ) + print('Missing parameters') + exit 1 +end + +salt_bootstrap = "test/bootstrap-salt-roots.sh" + +Vagrant.configure("2") do |config| + config.vm.provider "libvirt" + config.vm.box = ENV['SCULLERY_BOX_NAME'] + config.vm.box_url = ENV['SCULLERY_BOX_IMAGE'] + config.vm.provision "shell", inline: <<-SHELL + # for some reason, Avahi DNS doesn't work until the network stack is restarted + rcnetwork restart + SHELL + if ENV['SCULLERY_MASTERS'] + ENV['SCULLERY_MASTERS'].split(',').each do |vmname| + config.vm.define "#{vmname}", primary: true do |master_config| + master_config.vm.hostname = "#{vmname}" + master_config.vm.provider :libvirt do |libvirt| + libvirt.memory = 768 + end + master_config.vm.provision "shell", path: salt_bootstrap + master_config.vm.provision "shell", inline: <<-SHELL + printf 'auto_accept: True\n' > /etc/salt/master.d/notsecure.conf + printf 'file_roots:\n base:\n - /srv/salt\n - /srv/formulas\n' > /etc/salt/master.d/roots.conf + systemctl enable --now salt-master + SHELL + end + end + end + if ENV['SCULLERY_MINIONS'] + ENV['SCULLERY_MINIONS'].split(',').each do |vmname| + config.vm.define "#{vmname}" do |minion_config| + minion_config.vm.hostname = "#{vmname}" + if ENV['SCULLERY_MASTERS'] + minion_config.vm.synced_folder '.', '/vagrant', disabled: true + minion_config.vm.provision "shell", inline: <<-SHELL + sed -i 's/^#master:.*/master: scullery-master0/' /etc/salt/minion + systemctl enable --now salt-minion + SHELL + else + minion_config.vm.provision "shell", path: salt_bootstrap + end + end + end + end +end diff --git a/opensuse-salt-formulas/scullery.py b/opensuse-salt-formulas/scullery.py new file mode 100644 index 0000000..d976b78 --- /dev/null +++ b/opensuse-salt-formulas/scullery.py @@ -0,0 +1,166 @@ +#!/usr/bin/python3 + +from argparse import ArgumentParser +from configparser import ConfigParser +import logging +import os +import sys + +try: + import vagrant +except ImportError as myerror: + print('Could not load python-vagrant') + sys.exit(1) + +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.path.abspath(os.path.dirname(__file__)))) +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') + +args = argparser.parse_args() +config.read(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 vagrant_env(box_name, box_image, minions=None, masters=None, vagrantfile=None): + fh = open('.scullery_env', 'w') + if vagrantfile is not None: + env['VAGRANT_VAGRANTFILE'] = vagrantfile + fh.write('VAGRANT_VAGRANTFILE={}\n'.format(vagrantfile)) + env['SCULLERY_BOX_NAME'] = box_name + env['SCULLERY_BOX_IMAGE'] = box_image + fh.write('SCULLERY_BOX_NAME={}\nSCULLERY_BOX_IMAGE={}\n'.format(box_name, box_image)) + if masters is not None: + env_masters = ','.join(masters) + env['SCULLERY_MASTERS'] = env_masters + fh.write('SCULLERY_MASTERS={}\n'.format(env_masters)) + if minions is not None: + env_minions = ','.join(minions) + env['SCULLERY_MINIONS'] = env_minions + fh.write('SCULLERY_MINIONS={}\n'.format(env_minions)) + #log.debug('Environment is: {}'.format(str(env))) + fh.close() + + 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'] + 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']) + 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: + 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') + else: + log.info('Deployment is already running') + 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') + +if __name__ == '__main__': + logging.basicConfig(format='%(asctime)s %(levelname)s - %(funcName)s: %(message)s', datefmt='%H:%M:%S') + log = logging.getLogger('scullery') + log.setLevel(args.loglevel) + log.debug(args) + if args.loglevel == logging.INFO: + log_stderr = True + else: + log_stderr = False + v = _vagrant(log_stderr) + + main_interactive() +else: + v = _vagrant()