From e1e25e6021c353e987611146ab1a84729d948239 Mon Sep 17 00:00:00 2001 From: ale <ale@incal.net> Date: Mon, 3 May 2021 17:23:10 +0100 Subject: [PATCH] Refactor for simpler testing Remove the prod configuration, moved to a separate project. Add a "test.sh" script to programmatically create ephemeral test environments. --- Vagrantfile => Vagrantfile.in | 18 ++- ansible.cfg.in | 14 ++ ci/vmine.py | 150 ++++++++++++++++++ group_vars/all/admins.yml | 35 ---- group_vars/all/secrets.yml | 21 --- group_vars/all/vars.yml | 24 --- hosts-vagrant.ini | 23 --- hosts.ini | 30 ---- hosts.ini.in | 30 ++++ roles/base/tasks/main.yml | 8 +- .../templates/apt/50unattended-upgrades.j2 | 6 +- roles/base/templates/apt/90proxy.j2 | 4 + roles/monitor/defaults/main.yml | 2 +- roles/monitor/tasks/grafana.yml | 2 +- roles/network-config/defaults/main.yml | 7 - roles/network-config/tasks/main.yml | 8 + site.yml | 21 --- test.sh | 116 ++++++++++++++ 18 files changed, 347 insertions(+), 172 deletions(-) rename Vagrantfile => Vagrantfile.in (54%) create mode 100644 ansible.cfg.in create mode 100755 ci/vmine.py delete mode 100644 group_vars/all/admins.yml delete mode 100644 group_vars/all/secrets.yml delete mode 100644 group_vars/all/vars.yml delete mode 100644 hosts-vagrant.ini delete mode 100644 hosts.ini create mode 100644 hosts.ini.in create mode 100644 roles/base/templates/apt/90proxy.j2 delete mode 100644 roles/network-config/defaults/main.yml create mode 100755 test.sh diff --git a/Vagrantfile b/Vagrantfile.in similarity index 54% rename from Vagrantfile rename to Vagrantfile.in index c17f16d..231fcd4 100644 --- a/Vagrantfile +++ b/Vagrantfile.in @@ -1,8 +1,10 @@ NUM_HOSTS = 3 +RAM = 1024 + Vagrant.configure(2) do |config| - config.vm.box = "debian/buster64" + config.vm.box = "debian/@DIST@64" # Use the old insecure Vagrant SSH key for access. config.ssh.insert_key = false @@ -10,19 +12,25 @@ Vagrant.configure(2) do |config| # Disable synchronization of the /vagrant folder for faster startup. config.vm.synced_folder ".", "/vagrant", disabled: true - # Increase RAM to 1G. + # Set VM memory size (provider-dependant). config.vm.provider :virtualbox do |vb| - vb.customize ["modifyvm", :id, "--memory", "1024"] + vb.customize ["modifyvm", :id, "--memory", RAM.to_s] end config.vm.provider :libvirt do |libvirt| - libvirt.memory = 1024 + libvirt.memory = RAM + if !ENV['LIBVIRT_HOST'].nil? then + libvirt.remote_host = ENV['LIBVIRT_HOST'] + end + if !ENV['LIBVIRT_USER'].nil? then + libvirt.remote_user = ENV['LIBVIRT_USER'] + end end # Create progressively numbered hosts 'hostN', with IP 9+N. (1..NUM_HOSTS).each do |i| config.vm.define "host#{i}" do |m| m.vm.hostname = "host#{i}" - m.vm.network "private_network", ip: "10.236.82.#{9+i}", libvirt__dhcp_enabled: false + m.vm.network "private_network", ip: "@IP_NET@.#{9+i}", libvirt__dhcp_enabled: false end end end diff --git a/ansible.cfg.in b/ansible.cfg.in new file mode 100644 index 0000000..6c26efb --- /dev/null +++ b/ansible.cfg.in @@ -0,0 +1,14 @@ +[defaults] +roles_path = @REPO_ROOT@/roles +strategy_plugins = @MITOGEN_PATH@/plugins/strategy +strategy = mitogen_linear +display_skipped_hosts = False +nocows = 1 +force_handlers = True +host_key_checking = False + +[ssh_connection] +ssh_args = -C -o ControlMaster=auto -o ControlPersist=120s @SSH_OPTS@ +control_path_dir = ~/.ansible/cp +control_path = %(directory)s/%%h-%%r +pipelining = True diff --git a/ci/vmine.py b/ci/vmine.py new file mode 100755 index 0000000..d11b0a9 --- /dev/null +++ b/ci/vmine.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# +# Read a hosts.yml float inventory, and manage a VM group derived by it. +# This tool is meant to replace "vagrant up" in a CI pipeline. +# + +import argparse +import json +import os +import re +import shlex +import subprocess + + +# The Vagrant "insecure" SSH key that is used to log onto the VMs. +INSECURE_PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI +w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP +kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 +hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO +Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW +yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd +ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 +Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf +TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK +iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A +sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf +4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP +cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk +EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN +CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX +3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG +YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj +3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ +dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz +6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC +P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF +llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ +kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH ++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ +NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= +-----END RSA PRIVATE KEY----- +''' + + +def parse_inventory(spec, host_attrs): + hosts = [] + for s in spec: + name, addr = s.split('=') + hosts.append({'name': name, 'ip': addr}) + for h in hosts: + h.update(host_attrs) + + # We know that the network is a /24. + net = re.sub(r'\.[0-9]+$', '.0/24', hosts[0]['ip']) + return { + 'network': net, + 'hosts': hosts, + } + + +def do_request(url, ssh_gw, payload): + data = json.dumps(payload) + cmd = "curl -s -X POST -H 'Content-Type: application/json' -d %s %s" % ( + shlex.quote(data), url) + if ssh_gw: + cmd = "ssh %s %s" % (ssh_gw, shlex.quote(cmd)) + + output = subprocess.check_output(cmd, shell=True) + try: + return json.loads(output) + except json.decoder.JSONDecodeError: + print(f'server error: {output}') + raise + + +def install_ssh_key(): + # Install the SSH key as Vagrant would do, for compatibility. + key_path = os.path.join( + os.getenv('HOME'), '.vagrant.d', 'insecure_private_key') + if os.path.exists(key_path): + return + os.makedirs(os.path.dirname(key_path), mode=0o700, exist_ok=True) + with open(key_path, 'w') as fd: + fd.write(INSECURE_PRIVATE_KEY) + os.chmod(key_path, 0o600) + + +def main(): + ci_job_id = os.getenv('CI_JOB_ID') + parser = argparse.ArgumentParser() + parser.add_argument( + '--url', metavar='URL', default='http://127.0.0.1:4949', + help='URL of the vmine API server') + parser.add_argument( + '--ssh', metavar='USER@HOST', + help='proxy the vmine API request through SSH') + parser.add_argument( + '--state-file', metavar='FILE', + default=f'.vmine_group-{ci_job_id}' if ci_job_id else '.vmine_group', + help='state file to store the vmine group ID') + parser.add_argument( + '--image', metavar='NAME', + help='base image to use for the VMs') + parser.add_argument( + '--ram', type=int, + help='memory reservation for the VMs') + parser.add_argument( + '--ttl', metavar='DURATION', default='1h', + help='TTL for the virtual machines') + parser.add_argument( + 'cmd', + choices=['up', 'down']) + parser.add_argument( + 'inventory', nargs='*') + args = parser.parse_args() + + if args.cmd == 'up': + host_attrs = {} + if args.ram: + host_attrs['ram'] = args.ram + if args.image: + host_attrs['image'] = args.image + req = parse_inventory(args.inventory, host_attrs) + req['ttl'] = args.ttl + + print('creating VM group...') + resp = do_request(args.url + '/api/create-group', args.ssh, req) + group_id = resp['group_id'] + with open(args.state_file, 'w') as fd: + fd.write(group_id) + print(f'created VM group {group_id}') + + install_ssh_key() + + elif args.cmd == 'down': + try: + with open(args.state_file) as fd: + group_id = fd.read().strip() + except FileNotFoundError: + print('state file not found, exiting') + return + print(f'stopping VM group {group_id}...') + do_request(args.url + '/api/stop-group', args.ssh, + {'group_id': group_id}) + os.remove(args.state_file) + + +if __name__ == '__main__': + main() diff --git a/group_vars/all/admins.yml b/group_vars/all/admins.yml deleted file mode 100644 index 00872e2..0000000 --- a/group_vars/all/admins.yml +++ /dev/null @@ -1,35 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -61313436643761313439323963326433363436383861653934663464336437313036393636636465 -3234366165373836636463343333336532333838393938390a616131663734306431653937376233 -39613665393131636462376366386165393038636633393232643638663239396165396337386466 -6630636332366231660a666233653530653435303232313136376464373961373363356637613533 -61386134326335386661343930323131343935643431346239633935306333363030393238626234 -61313634346633313661666662633636643234343336666232376239303434343032333831343638 -39623564313036376130623037646232346633326536646564633635353238663164303537643532 -61313330346263376565313631643639363734333934623538383466353335656265666337643665 -61666163386265643065393334626136623637643030313563663336316366376463313361346231 -34316365326564396237656435303930653562656539643261623664346466386365346237346637 -30633132383936313664363066346165613737343739633334336337336236636539336562376662 -32663636666533333665626463653063326636343831313435633964313735623733373035663037 -63303738313161626163626466613831333536353336623566356462646339383435303438303639 -30366465666361333335343936626637653834646363343566646432383835623230326566623465 -38383430646163386235626435376439393337306565346462336530653663623166376333353530 -62313834373462646564646537366132653533313263383136653766396665666137366432386134 -66663965393035663465353031326232616432346263333562333738383661356464376435646539 -62663137326466636139663330656638376531343233333538313033613861633265353330316265 -65623834383339373466663239346636356232346565623131653361363932353264393263303361 -62326461373237383830316264653732343433393030643135646233343461666462653734663763 -63373035643438643835666563363533316337353866383635643233303430323134663132346465 -62376661323166333132383334396566666265313030333733306161636632323337393663616266 -34353234373262343034613136303365623262376535376264386634323762646661303937313165 -62396465343561393333373233643462326236396134626663393231303236363937663365646661 -39346462393465386532653934643839323633626435323266663662373466626237336632376433 -38366331643536376137313132316436303965383362376566323863663163613766396439663066 -31303136373536376265663636343131363033343063323536303032363864313163643763646233 -65383232363632373035643639346563653363383938633735633162326232306161303933393836 -37323032363532313263323764613966313761386530373732623236313137376430333264343564 -39323536396535333863343537343931336138643334343837383661393861346231663565613631 -33656466616635653836663236333632393365663262383464323963666665643266623866343065 -33326263313234313734393439386331396439666639636236383165386166373565383465383663 -63303037313631333164343365373463653737656435343031306231343137393837373838306664 -3165333435393564363937346534346662316661333561323366 diff --git a/group_vars/all/secrets.yml b/group_vars/all/secrets.yml deleted file mode 100644 index 0e94bd7..0000000 --- a/group_vars/all/secrets.yml +++ /dev/null @@ -1,21 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -36626434666634383462356231373263343366616136306264303332626261363963666131383730 -3138346236653531333433663935346331643562336666640a346533326363663939373365663363 -39633265306233333836346237346161356165386563363437353062343434353665326139356261 -6234386638633833380a616235333366363661656435366137333461666235363566666537346663 -66383564313137663464663231333433343964326230393931393730323365383937646532303538 -33353462386133646137386337313336613163383530376264313937623464303366356234386531 -36663765653334346165633138626166303363616366343261633738633766633064363231393263 -37643937633664333433333237646537313937303335323062656532396561663866373063346536 -36353739333765396633656665616366373464626164366161623664303033313331643336366563 -64323832313466376634383464313631396563383435636136353063356163386463323638373861 -32653261373433346432313863666139393730366637633439376330386431306638303166333364 -33313765626239633539356664356333313563616664346131663466623231343431616265303561 -61666565396532383362356536313234616135623134623331646638363363363661633635393262 -37326632396663663262356533376538353236353333353363336235383366303931326538326539 -37653565356534616666663566316565616536393931306535633935316561323837353632313766 -32636162363266303965313135643837306664383435303139643939643732373464626361316131 -30313533623063623234393562323866663061616164376631633430636130613164323733633861 -66393461623365333036363361643764643066306162346539653635666131376636633862623538 -37613735366334626335636234386435643163323734626634623263623865643032333765633239 -39616165623539643438 diff --git a/group_vars/all/vars.yml b/group_vars/all/vars.yml deleted file mode 100644 index ad33d2d..0000000 --- a/group_vars/all/vars.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- - -public_domain: "streampunk.cc" - -admin_emails: - - info@streampunk.cc - -smtp_server: "mail.gandi.net" -smtp_auth_user: "info@streampunk.cc" - -backup_repository: "s3:https://storage.incal.net:9000/streampunk/backup" - -ssl_additional_names: - - stream.auto.ondarossa.net - -radioprober_streams: - - /ondarossa.ogg - - /ondarossa.mp3 - - /wombat.ogg - -autoradio_nameservers: - - blimp.streampunk.cc - - giffard.streampunk.cc - - zeppelin.streampunk.cc diff --git a/hosts-vagrant.ini b/hosts-vagrant.ini deleted file mode 100644 index bb03ba3..0000000 --- a/hosts-vagrant.ini +++ /dev/null @@ -1,23 +0,0 @@ -[vagrant] -host1 ansible_host=10.236.82.10 -host2 ansible_host=10.236.82.11 -host3 ansible_host=10.236.82.12 - -[etcd:children] -vagrant - -[etcd-master:children] -vagrant - -[monitor] -host1 ansible_host=10.236.82.10 -host2 ansible_host=10.236.82.11 - -[test-source] -host3 ansible_host=10.236.82.12 - -[vagrant:vars] -ansible_become=true -ansible_ssh_user=vagrant -ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key - diff --git a/hosts.ini b/hosts.ini deleted file mode 100644 index c09f06c..0000000 --- a/hosts.ini +++ /dev/null @@ -1,30 +0,0 @@ -giffard ansible_host=giffard.streampunk.cc public_ip=49.12.77.64 -blimp ansible_host=blimp.streampunk.cc public_ip=116.203.245.27 -zeppelin ansible_host=zeppelin.streampunk.cc public_ip=95.217.20.254 - -[hetzner] -giffard -blimp -zeppelin - -[hetzner:vars] -internal_network_interface=enp7s0 -public_network_interface=eth0 - -[etcd:children] -hetzner - -[etcd-master:children] -hetzner - -[monitor] -giffard -blimp - -[test-source] -zeppelin - -[all:vars] -ansible_become=false -ansible_ssh_user=root - diff --git a/hosts.ini.in b/hosts.ini.in new file mode 100644 index 0000000..c34c497 --- /dev/null +++ b/hosts.ini.in @@ -0,0 +1,30 @@ +host1 ansible_host=@IP_NET@.10 peer_ip=@IP_NET@.10 +host2 ansible_host=@IP_NET@.11 peer_ip=@IP_NET@.11 +host3 ansible_host=@IP_NET@.12 peer_ip=@IP_NET@.12 + +[vagrant] +host1 +host2 +host3 + +[etcd:children] +vagrant + +[etcd-master:children] +vagrant + +[monitor] +host1 +host2 + +[test-source] +host3 + +[vagrant:vars] +ansible_become=true +ansible_ssh_user=vagrant +ansible_ssh_private_key_file=~/.vagrant.d/insecure_private_key + +[all:vars] +public_domain=streampunk.cc +apt_proxy=@APT_PROXY@ diff --git a/roles/base/tasks/main.yml b/roles/base/tasks/main.yml index 4ccb901..a7ad1e4 100644 --- a/roles/base/tasks/main.yml +++ b/roles/base/tasks/main.yml @@ -16,6 +16,10 @@ {{ hostvars[h].peer_ip }} monitor {% endfor %} +- apt: + name: man-db + state: absent + - apt: name: "{{ packages }}" state: present @@ -28,9 +32,10 @@ template: dest: "/etc/apt/apt.conf.d/{{ item }}" src: "apt/{{ item }}.j2" - with_items: + loop: - 20auto-upgrades - 50unattended-upgrades + - 90proxy - 99recommends - 99translations @@ -53,6 +58,7 @@ apt: name: "{{ packages }}" state: present + update_cache: true vars: packages: - bsd-mailx diff --git a/roles/base/templates/apt/50unattended-upgrades.j2 b/roles/base/templates/apt/50unattended-upgrades.j2 index f8bc1f9..e311579 100644 --- a/roles/base/templates/apt/50unattended-upgrades.j2 +++ b/roles/base/templates/apt/50unattended-upgrades.j2 @@ -4,9 +4,9 @@ Unattended-Upgrade::Origins-Pattern { // Note that this will silently match a different release after // migration to the specified archive (e.g. testing becomes the // new stable). -// "o=Debian,a=stable"; -// "o=Debian,a=stable-updates"; -// "o=Debian,a=proposed-updates"; + "o=Debian,a=stable"; + "o=Debian,a=stable-updates"; + "o=Debian,a=proposed-updates"; "origin=Debian,archive=stable,label=Debian-Security"; }; diff --git a/roles/base/templates/apt/90proxy.j2 b/roles/base/templates/apt/90proxy.j2 new file mode 100644 index 0000000..442e5e9 --- /dev/null +++ b/roles/base/templates/apt/90proxy.j2 @@ -0,0 +1,4 @@ +{% if apt_proxy is defined %} +Acquire::http::Proxy "http://{{ apt_proxy }}"; +Acquire::http::Proxy::{{ apt_proxy | regex_replace(':[0-9]*$', '') }} "DIRECT"; +{% endif %} diff --git a/roles/monitor/defaults/main.yml b/roles/monitor/defaults/main.yml index a01e536..4a4ef30 100644 --- a/roles/monitor/defaults/main.yml +++ b/roles/monitor/defaults/main.yml @@ -4,4 +4,4 @@ graphite_secret_key: "changeme" grafana_secret_key: "changeme" test_source_stream: "/test.ogg" prometheus_tsdb_retention_time: "180d" - +monitor_auth_password: "password" diff --git a/roles/monitor/tasks/grafana.yml b/roles/monitor/tasks/grafana.yml index 4de1f04..e519bd9 100644 --- a/roles/monitor/tasks/grafana.yml +++ b/roles/monitor/tasks/grafana.yml @@ -8,7 +8,7 @@ - name: Install Grafana APT repository apt_repository: - repo: "deb {% if apt_proxy is defined %}http://{{ apt_proxy }}/HTTPS/{% else %}https://{% endif %}packages.grafana.com/oss/deb stable main" + repo: "deb https://packages.grafana.com/oss/deb stable main" state: present - name: Install Grafana diff --git a/roles/network-config/defaults/main.yml b/roles/network-config/defaults/main.yml deleted file mode 100644 index 75f86a6..0000000 --- a/roles/network-config/defaults/main.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- - -# In order to support varied network topologies, every host needs a -# public and an internal (peer) IP address. We can autodetect them -# given a network interface name. -public_network_interface: "ens6" -internal_network_interface: "ens6" diff --git a/roles/network-config/tasks/main.yml b/roles/network-config/tasks/main.yml index 1f563de..e209e0a 100644 --- a/roles/network-config/tasks/main.yml +++ b/roles/network-config/tasks/main.yml @@ -1,5 +1,13 @@ --- +# Autodetect the interfaces if no parameters are specified. +- set_fact: + public_network_interface: "{{ ansible_default_ipv4.interface }}" + when: "public_ip is not defined and public_network_interface is not defined" +- set_fact: + internal_network_interface: "{{ ansible_default_ipv4.interface }}" + when: "peer_ip is not defined and internal_network_interface is not defined" + # Verify that the network configuration is set up properly, and # autodetect public/internal addresses if necessary. - set_fact: diff --git a/site.yml b/site.yml index b205245..9498a82 100644 --- a/site.yml +++ b/site.yml @@ -1,18 +1,5 @@ --- -# First update packages on all hosts, staggering execution so as to -# not restart all radiod nodes at once on package upgrades. -- hosts: all - tasks: - - name: Upgrade packages - apt: - update_cache: true - upgrade: true - serial: - - 1 - - 1 - - "100%" - - hosts: all roles: - base @@ -20,18 +7,10 @@ - hosts: etcd roles: - etcd - serial: - - 1 - - 1 - - "100%" - hosts: all roles: - autoradio - serial: - - 1 - - 1 - - "100%" - hosts: monitor roles: diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..db10059 --- /dev/null +++ b/test.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# +# Test driver for the streampunk Ansible configuration (and associated +# software). +# +# The tool will generate a test environment using ephemeral virtual +# machines, and run Ansible on it using the test configuration +# parameters in group_vars. +# +# Configuration is entirely controlled via environment variables: +# +# - DIST: select the Debian distribution to use (default "buster") +# +# - LIBVIRT_USER, LIBVIRT_HOST: use libvirt-over-SSH, needs SSH +# passwordless authentication to be set up. +# +# - APT_PROXY: set to a host:port address of apt-cacher-ng. When using +# this directive, ensure that the apt-cacher-ng configuration has a +# permissive PassThroughPattern to allow access to https:// +# repositories. +# +# - VM: select the VM manager to use, one of "vagrant" (default) or +# "vmine". +# +# - VERBOSE: set to a non-empty value to run ansible-playbook with +# increased verbosity. +# +# - AUTORADIO_SRC: if set, points at a local copy of the autoradio +# source repository which will be compiled and installed instead of +# using the default packages from the autoradio Debian repository. +# + +repo_root=$(dirname $(realpath "$0")) +target="${1:-.}" + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +wait_for_vms() { + # Wait at most 30 seconds for the vms to become reachable. + local i=0 + local ok=1 + while [ $i -lt 10 ]; do + sleep 1 + if ansible -i hosts.ini all -m ping; then + ok=0 + break + fi + i=$(($i + 1)) + done + return $ok +} + +vm_manager() { + local what="$1" + case "${VM:-vagrant}" in + vagrant) + vagrant ${what} + ;; + vmine) + local args= + if [ "${what}" = up ]; then + args="--image=${dist} host1=${ip_net_prefix}.10 host2=${ip_net_prefix}.11 host3=${ip_net_prefix}.12" + fi + ${repo_root}/ci/vmine.py ${libvirt_userhost:+--ssh ${libvirt_userhost}} ${what} ${args} + ;; + *) + die "Unsupported VM manager" + ;; + esac +} + +set -eu + +# Select the Debian distribution to use. +dist="${DIST:-buster}" + +# Choose a random network in the 10.x range. +ip_net_prefix="10.$(( $RANDOM % 255 )).$(( $RANDOM % 255 ))" + +# Find the path for Mitogen. +mitogen_path="$(python3 -c 'import ansible_mitogen;print(ansible_mitogen.__path__[0])')" +[ -n "${mitogen_path}" ] || die "Mitogen not found. Please run 'pip3 install mitogen'" + +# If LIBVIRT_* parameters are specified, set up SSH to go through the proxy host. +libvirt_userhost= +ssh_opts= +if [ -n "${LIBVIRT_HOST:-}" ]; then + libvirt_userhost="${LIBVIRT_USER:-}${LIBVIRT_USER:+@}${LIBVIRT_HOST}" + ssh_opts="-o ProxyJump=${libvirt_userhost}" +fi + +# Render the .in templates. +mkdir -p ${target} +for file in ansible.cfg Vagrantfile hosts.ini ; do + sed -e "s,@IP_NET@,${ip_net_prefix},g" \ + -e "s,@MITOGEN_PATH@,${mitogen_path},g" \ + -e "s,@SSH_OPTS@,${ssh_opts},g" \ + -e "s,@DIST@,${dist},g" \ + -e "s,@REPO_ROOT@,${repo_root},g" \ + -e "s,@APT_PROXY@,${APT_PROXY:-},g" \ + <${repo_root}/${file}.in >${target}/${file} +done +cp ${repo_root}/site.yml ${target}/site.yml + +# Environment is ready, start up the VMs and wait for them to be ready. +(cd ${target} && vm_manager up && wait_for_vms) + +# Run Ansible. +(cd ${target} && + ansible-playbook -i hosts.ini \ + ${VERBOSE:+-vv} \ + ${AUTORADIO_SRC:+-e source_repository_path=${AUTORADIO_SRC}} \ + site.yml) -- GitLab