Skip to content

Commit

Permalink
Shift inventory into faros-config.
Browse files Browse the repository at this point in the history
  • Loading branch information
jharmison-redhat committed Jan 29, 2021
1 parent 9724ac4 commit 70149d8
Show file tree
Hide file tree
Showing 2 changed files with 3 additions and 352 deletions.
353 changes: 2 additions & 351 deletions app/inventory.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,355 +1,6 @@
#!/usr/bin/env python3
import argparse
from collections import defaultdict
from faros_config import FarosConfig, PydanticEncoder
import ipaddress
import json
import os
import sys
import pickle

SSH_PRIVATE_KEY = '/data/id_rsa'
IP_RESERVATIONS = '/data/ip_addresses'
from faros_config.inventory.cli import main


class InventoryGroup(object):

def __init__(self, parent, name):
self._parent = parent
self._name = name

def add_group(self, name, **groupvars):
return(self._parent.add_group(name, self._name, **groupvars))

def add_host(self, name, hostname=None, **hostvars):
return(self._parent.add_host(name, self._name, hostname, **hostvars))

def host(self, name):
return self._parent.host(name)


class Inventory(object):

_modes = ['list', 'host', 'verify', 'none']
_data = {"_meta": {"hostvars": defaultdict(dict)}}

def __init__(self, mode=0, host=None):
if mode == 1:
# host info requested
# current, only list and none are implimented
raise NotImplementedError()

self._mode = mode
self._host = host

def host(self, name):
return self._data['_meta']['hostvars'].get(name)

def group(self, name):
if name in self._data:
return InventoryGroup(self, name)
else:
return None

def add_group(self, name, parent=None, **groupvars):
self._data[name] = {'hosts': [], 'vars': groupvars, 'children': []}

if parent:
if parent not in self._data:
self.add_group(parent)
self._data[parent]['children'].append(name)

return InventoryGroup(self, name)

def add_host(self, name, group=None, hostname=None, **hostvars):
if not group:
group = 'all'
if group not in self._data:
self.add_group(group)

if hostname:
hostvars.update({'ansible_host': hostname})

self._data[group]['hosts'].append(name)
self._data['_meta']['hostvars'][name].update(hostvars)

def to_json(self):
return json.dumps(self._data, sort_keys=True, indent=4,
separators=(',', ': '), cls=PydanticEncoder)


class IPAddressManager(dict):

def __init__(self, save_file, subnet):
super().__init__()
self._save_file = save_file

# parse the subnet definition into a static and dynamic pool
divided = subnet.subnets()
self._static_pool = next(divided)
self._dynamic_pool = next(divided)
self._generator = self._static_pool.hosts()

# calculate reverse dns zone
classful_prefix = [32, 24, 16, 8, 0]
classful = subnet
while classful.prefixlen not in classful_prefix:
classful = classful.supernet()
host_octets = classful_prefix.index(classful.prefixlen)
self._reverse_ptr_zone = \
'.'.join(classful.reverse_pointer.split('.')[host_octets:])

# load the last saved state
try:
restore = pickle.load(open(save_file, 'rb'))
except: # noqa: E722
restore = {}
self.update(restore)

# reserve the first ip for the bastion
_ = self['bastion']

def __getitem__(self, key):
key = key.lower()
try:
return super().__getitem__(key)
except KeyError:
new_ip = self._next_ip()
self[key] = new_ip
return new_ip

def __setitem__(self, key, value):
return super().__setitem__(key.lower(), value)

def _next_ip(self):
used_ips = list(self.values())
loop = True

while loop:
new_ip = next(self._generator).exploded
loop = new_ip in used_ips
return new_ip

def get(self, key, value=None):
if value and value not in self.values():
self[key] = value
return self[key]

def save(self):
with open(self._save_file, 'wb') as handle:
pickle.dump(dict(self), handle)

@property
def static_pool(self):
return str(self._static_pool)

@property
def dynamic_pool(self):
return str(self._dynamic_pool)

@property
def reverse_ptr_zone(self):
return str(self._reverse_ptr_zone)


class Config(object):
shim_var_keys = [
'WAN_INT',
'BASTION_IP_ADDR',
'BASTION_INTERFACES',
'BASTION_HOST_NAME',
'BASTION_SSH_USER',
'CLUSTER_DOMAIN',
'CLUSTER_NAME',
'BOOT_DRIVE',
]

def __init__(self, yaml_file):
self.shim_vars = {}
for var in self.shim_var_keys:
self.shim_vars[var] = os.getenv(var)
config = FarosConfig.from_yaml(yaml_file)
self.network = config.network
self.bastion = config.bastion
self.cluster = config.cluster
self.proxy = config.proxy


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--list', action='store_true')
parser.add_argument('--verify', action='store_true')
parser.add_argument('--host', action='store')
args = parser.parse_args()
return args


def main(config, ipam, inv):
# GATHER INFORMATION FOR EXTRA NODES
for node in config.network.lan.dhcp.extra_reservations:
addr = ipam.get(node.mac, str(node.ip))
node.ip = ipaddress.IPv4Address(addr)

# CREATE INVENTORY
inv.add_group(
'all', None,
ansible_ssh_private_key_file=SSH_PRIVATE_KEY,
cluster_name=config.shim_vars['CLUSTER_NAME'],
cluster_domain=config.shim_vars['CLUSTER_DOMAIN'],
admin_password=config.bastion.become_pass,
pull_secret=json.loads(config.cluster.pull_secret),
mgmt_provider=config.cluster.management.provider,
mgmt_user=config.cluster.management.user,
mgmt_password=config.cluster.management.password,
install_disk=config.shim_vars['BOOT_DRIVE'],
loadbalancer_vip=ipam['loadbalancer'],
dynamic_ip_range=ipam.dynamic_pool,
reverse_ptr_zone=ipam.reverse_ptr_zone,
subnet=str(config.network.lan.subnet.network_address),
subnet_mask=config.network.lan.subnet.prefixlen,
wan_ip=config.shim_vars['BASTION_IP_ADDR'],
extra_nodes=config.network.lan.dhcp.extra_reservations,
ignored_macs=config.network.lan.dhcp.ignore_macs,
dns_forwarders=config.network.lan.dns_forward_resolvers,
proxy=config.proxy is not None,
proxy_http=config.proxy.http if config.proxy is not None else '',
proxy_https=config.proxy.https if config.proxy is not None else '',
proxy_noproxy=config.proxy.noproxy if config.proxy is not None else [],
proxy_ca=config.proxy.ca if config.proxy is not None else ''
)

infra = inv.add_group('infra')
router = infra.add_group(
'router',
wan_interface=config.shim_vars['WAN_INT'],
lan_interfaces=config.network.lan.interfaces,
all_interfaces=config.shim_vars['BASTION_INTERFACES'].split(),
allowed_services=config.network.port_forward
)
# ROUTER INTERFACES
router.add_host(
'wan', config.shim_vars['BASTION_IP_ADDR'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)
router.add_host(
'lan',
ipam['bastion'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)
# DNS NODE
router.add_host(
'dns',
ipam['bastion'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)
# DHCP NODE
router.add_host(
'dhcp',
ipam['bastion'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)
# LOAD BALANCER NODE
router.add_host(
'loadbalancer',
ipam['loadbalancer'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)

# BASTION NODE
bastion = infra.add_group('bastion_hosts')
bastion.add_host(
config.shim_vars['BASTION_HOST_NAME'],
ipam['bastion'],
ansible_become_pass=config.bastion.become_pass,
ansible_ssh_user=config.shim_vars['BASTION_SSH_USER']
)

# CLUSTER NODES
cluster = inv.add_group('cluster')
# BOOTSTRAP NODE
ip = ipam['bootstrap']
cluster.add_host(
'bootstrap', ip,
ansible_ssh_user='core',
node_role='bootstrap'
)
# CLUSTER CONTROL PLANE NODES
cp = cluster.add_group('control_plane', node_role='master')
for count, node in enumerate(config.cluster.nodes):
ip = ipam[node.mac]
mgmt_ip = ipam[node.mgmt_mac]
cp.add_host(
node.name, ip,
mac_address=node.mac,
mgmt_mac_address=node.mgmt_mac,
mgmt_hostname=mgmt_ip,
ansible_ssh_user='core',
cp_node_id=count
)
if node.install_drive is not None:
cp.host(node['name'])['install_disk'] = node.install_drive

# VIRTUAL NODES
virt = inv.add_group(
'virtual',
mgmt_provider='kvm',
mgmt_hostname='bastion',
install_disk='vda'
)
virt.add_host('bootstrap')

# MGMT INTERFACES
mgmt = inv.add_group(
'management',
ansible_ssh_user=config.cluster.management.user,
ansible_ssh_pass=config.cluster.management.password
)
for node in config.cluster.nodes:
mgmt.add_host(
node.name + '-mgmt', ipam[node.mgmt_mac],
mac_address=node.mgmt_mac
)


if __name__ == "__main__":
# PARSE ARGUMENTS
args = parse_args()
if args.list:
mode = 0
elif args.verify:
mode = 2
else:
mode = 1

# INTIALIZE CONFIG
config = Config('/data/config.yml')

# INTIALIZE IPAM
ipam = IPAddressManager(
IP_RESERVATIONS,
config.network.lan.subnet
)

# INITIALIZE INVENTORY
inv = Inventory(mode, args.host)

# CREATE INVENTORY
try:
main(config, ipam, inv)
if mode == 0:
print(inv.to_json())

except Exception as e:
if mode == 2:
sys.stderr.write(config.error)
sys.exit(1)
raise(e)

# DONE
ipam.save()
sys.exit(0)
main(sys.argv[1:])
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ansible<2.11
faros-config==0.1.3
faros-config==0.2.0

# for management/ilo provider
python-hpilo==4.4.3
Expand Down

0 comments on commit 70149d8

Please sign in to comment.