Browse Source

enough: update inventory/hosts.yml when a host is created/deleted

Fixes: main/infrastructure#152
keep-around/7653ece294da44ccf595a418f3753dcd3cb3e175
Loïc Dachary 2 years ago
committed by Loic Dachary
parent
commit
f27f5ea4bb
Signed by: dachary GPG Key ID: 992D23B392F9E4F2
  1. 14
      enough/cli/host.py
  2. 48
      enough/common/dotenough.py
  3. 10
      enough/common/host.py
  4. 3
      enough/common/hosting.py
  5. 35
      enough/common/openstack.py
  6. 1
      enough/common/service.py
  7. 1
      setup.cfg
  8. 27
      tests/enough/common/test_dotenough.py
  9. 55
      tests/enough/common/test_hosting.py
  10. 19
      tests/enough/test_host.py

14
enough/cli/host.py

@ -47,17 +47,3 @@ class Delete(Command):
args.update(vars(parsed_args))
host = host_factory(**args)
host.delete()
class Inventory(Command):
"Write an ansible compatible inventory of all hosts"
def get_parser(self, prog_name):
parser = super().get_parser(prog_name)
return set_common_options(parser)
def take_action(self, parsed_args):
args = vars(self.app.options)
args.update(vars(parsed_args))
host = host_factory(**args)
host.write_inventory()

48
enough/common/dotenough.py

@ -6,6 +6,48 @@ import shutil
import yaml
class Hosts(object):
def __init__(self, config_dir):
self.d = f'{config_dir}/inventory'
self.f = f'{self.d}/hosts.yml'
self.load()
def load(self):
if os.path.exists(self.f):
self.hosts = yaml.load(open(self.f).read())['all']['hosts']
else:
self.hosts = {}
def save(self):
if not os.path.exists(self.d):
os.makedirs(self.d)
content = yaml.dump(
{
'all': {
'hosts': self.hosts,
},
}
)
open(self.f, 'w').write(content)
def missings(self, names):
return [name for name in names if name not in self.hosts]
def create_or_update(self, name, ipv4):
if self.hosts.get(name, {}).get('ansible_host') != ipv4:
self.hosts[name] = {'ansible_host': ipv4}
self.save()
return True
else:
return False
def delete(self, name):
if name in self.hosts:
del self.hosts[name]
self.save()
class DotEnough(object):
def __init__(self, config_dir, domain):
@ -30,12 +72,6 @@ class DotEnough(object):
def private_key(self):
return self.ensure_ssh_key()
def populate_hosts_file(self, inventory):
d = f'{self.config_dir}/inventory'
if not os.path.exists(d):
os.makedirs(d)
open(f'{d}/hosts.yml', 'w').write(inventory)
def populate_config(self, certificate_authority):
d = f'{self.config_dir}/inventory/group_vars/all'
if not os.path.exists(d):

10
enough/common/host.py

@ -24,10 +24,6 @@ class Host(ABC):
def delete(self):
pass
@abstractmethod
def write_inventory(self):
pass
class HostDocker(Host):
@ -76,9 +72,6 @@ class HostDocker(Host):
for id in self.d.docker.ps('--filter', f'label=enough={domain}', '-q', _iter=True):
self.d.docker.rm('-f', id.strip())
def write_inventory(self):
pass
class HostOpenStack(Host):
@ -104,9 +97,6 @@ class HostOpenStack(Host):
h.get_stack_definition(name))
s.delete()
def write_inventory(self):
openstack.Heat(self.config_dir, self.args['clouds']).write_inventory()
def host_factory(**kwargs):
if kwargs['driver'] == 'openstack':

3
enough/common/hosting.py

@ -15,8 +15,7 @@ class Hosting(dotenough.DotEnoughOpenStack):
def create_hosts(self, public_key):
names = ('bind-host', 'icinga-host', 'postfix-host', 'wazuh-host')
h = openstack.Heat(self.config_dir, self.clouds_file)
inventory = h.to_inventory(h.create_or_update(names, public_key))
self.populate_hosts_file(inventory)
h.create_or_update(names, public_key)
return names
def populate_config(self):

35
enough/common/openstack.py

@ -14,6 +14,7 @@ import yaml
from enough import settings
from enough.common.retry import retry
from enough.common.dotenough import Hosts
log = logging.getLogger(__name__)
@ -95,6 +96,7 @@ class Stack(OpenStackBase):
self.create()
info = self.update()
self.wait_for_ssh(info['ipv4'], info['port'])
Hosts(self.config_dir).create_or_update(self.definition['name'], info['ipv4'])
return info
def create_internal_network(self):
@ -144,6 +146,8 @@ class Stack(OpenStackBase):
assert name not in self.list(), f'{name} deletion in progress'
wait_is_deleted()
Hosts(self.config_dir).delete(self.definition['name'])
network = self.definition.get('internal_network', OpenStackBase.INTERNAL_NETWORK)
o = OpenStack(self.config_dir, self.config_file)
o.network_delete_if_not_used(network)
@ -189,13 +193,7 @@ class Heat(OpenStackBase):
return True
def create_missings(self, names, public_key):
hosts_file = f'{self.config_dir}/inventory/hosts.yml'
if os.path.exists(hosts_file):
hosts = yaml.load(open(hosts_file).read())['all']['hosts']
missings = [name for name in names if name in hosts]
else:
missings = names
return self.create_or_update(missings, public_key)
return self.create_or_update(Hosts(self.config_dir).missings(names), public_key)
def create_or_update(self, names, public_key):
r = {}
@ -205,29 +203,6 @@ class Heat(OpenStackBase):
r[name] = s.create_or_update()
return r
def to_inventory(self, stacks):
hosts = {}
for name, info in stacks.items():
hosts[name] = {
'ansible_host': info['ipv4'],
}
return yaml.dump(
{
'all': {
'hosts': hosts,
},
}
)
def write_inventory(self):
names = Stack(self.config_dir, self.config_file).list()
inventory = self.to_inventory(self.create_or_update(
names, f'{self.config_dir}/infrastructure_key.pub'))
d = f'{self.config_dir}/inventory'
if not os.path.exists(d):
os.makedirs(d)
open(f'{d}/hosts.yml', 'w').write(inventory)
def create_test_subdomain(self, domain):
# exclusively when running from molecule
assert os.path.exists('molecule.yml')

1
enough/common/service.py

@ -76,7 +76,6 @@ class ServiceOpenStack(Service):
hosts = self.service2hosts[self.args['name']]
h = openstack.Heat(self.config_dir, self.dotenough.clouds_file)
h.create_missings(hosts, self.dotenough.public_key())
h.write_inventory()
playbook = ansible_utils.Playbook(self.config_dir, self.share_dir)
playbook.run([
f'--private-key={self.dotenough.private_key()}',

1
setup.cfg

@ -73,7 +73,6 @@ enough.cli =
service_create = enough.cli.service:Create
host_create = enough.cli.host:Create
host_delete = enough.cli.host:Delete
host_inventory = enough.cli.host:Inventory
enough.internal.cli =
build_image = enough.internal.cli.docker:Build

27
tests/enough/common/test_dotenough.py

@ -1,9 +1,34 @@
import os
import yaml
from enough.common.dotenough import DotEnough, DotEnoughOpenStack
from enough.common.dotenough import Hosts, DotEnough, DotEnoughOpenStack
#
# Hosts
#
def test_hosts_create_delete(tmpdir):
config_dir = str(tmpdir)
h = Hosts(config_dir)
host = 'HOST'
ip = '1.2.3.4'
assert h.create_or_update(host, ip) is True
assert h.create_or_update(host, ip) is False
assert os.path.exists(f'{config_dir}/inventory/hosts.yml')
h = Hosts(config_dir)
assert h.create_or_update(host, ip) is False
assert h.hosts[host]['ansible_host'] == ip
assert h.missings([host, 'MISSING']) == ['MISSING']
h.delete(host)
h = Hosts(config_dir)
assert h.hosts == {}
#
# DotEnough
#
def test_remap():
p = '.enough/thing'
unchanged = '/foo/bar/'

55
tests/enough/common/test_hosting.py

@ -1,55 +0,0 @@
import os
import yaml
from enough.common.hosting import Hosting
def test_ensure_ssh_key(tmpdir):
h = Hosting('NAME')
h.config_dir = tmpdir
h.ensure_ssh_key()
assert os.path.exists(f'{tmpdir}/infrastructure_key.pub')
def test_populate_config(tmpdir):
h = Hosting('NAME')
h.config_dir = tmpdir
h.populate_config()
domain = yaml.load(open(f'{tmpdir}/inventory/group_vars/all/certificate.yml'))
assert 'certificate_authority' in domain
def test_create_hosts(tmpdir, mocker):
h = Hosting({'name': 'NAME'})
h.config_dir = tmpdir
mocker.patch('enough.common.openstack.Heat.get_stack_definition',
return_value={})
created = [
{'ipv4': '1.2.3.10'},
{'ipv4': '1.2.3.11'},
{'ipv4': '1.2.3.12'},
{'ipv4': '1.2.3.13'},
]
mocker.patch('enough.common.openstack.Stack.create_or_update',
side_effect=lambda: created.pop(0))
mocker.patch('enough.common.openstack.Stack.set_public_key',
return_value='PUBLIC KEY CONTENT')
h.create_hosts('PUBLIC_KEY_PATH')
hosts = yaml.load(open(f'{tmpdir}/inventory/hosts.yml'))
assert hosts == {
'all': {
'hosts': {
'bind-host': {'ansible_host': '1.2.3.10'},
'icinga-host': {'ansible_host': '1.2.3.11'},
'postfix-host': {'ansible_host': '1.2.3.12'},
'wazuh-host': {'ansible_host': '1.2.3.13'},
},
},
}

19
tests/enough/test_host.py

@ -1,26 +1,7 @@
import argparse
import os
import yaml
from enough.cmd import main
from enough.cli import host
def test_inventory(tmpdir, mocker):
mocker.patch('enough.settings.CONFIG_DIR', str(tmpdir))
mocker.patch('enough.common.openstack.Stack.list',
return_value=['some-host'])
ip = '1.2.3.4'
mocker.patch('enough.common.openstack.Heat.create_or_update',
return_value={
'some-host': {'ipv4': ip},
})
assert main(['--debug', 'host', 'inventory']) == 0
assert os.path.exists(f'{tmpdir}/inventory/hosts.yml')
h = yaml.load(open(f'{tmpdir}/inventory/hosts.yml'))
assert h['all']['hosts']['some-host']['ansible_host'] == ip
def test_set_common_options():
parser = argparse.ArgumentParser()
assert host.set_common_options(parser) == parser

Loading…
Cancel
Save