diff --git a/build/lib/netbox_proxmox_sync/__init__.py b/build/lib/netbox_proxmox_sync/__init__.py deleted file mode 100644 index 29948087aef9ab10f486f6e0f5b76391a0e1e1b0..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from netbox.plugins import PluginConfig - - -class NetBoxProxmoxSyncConfig(PluginConfig): - name = 'netbox_proxmox_sync' - verbose_name = 'NetBox Proxmox Sync' - description = 'Import cluster information from Proxmox into NetBox' - version = '2.0.0' - base_url = 'netbox-proxmox-sync' - - -config = NetBoxProxmoxSyncConfig diff --git a/build/lib/netbox_proxmox_sync/api/__init__.py b/build/lib/netbox_proxmox_sync/api/__init__.py deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/build/lib/netbox_proxmox_sync/api/config.py b/build/lib/netbox_proxmox_sync/api/config.py deleted file mode 100644 index 66474ce850bcae34dbda607c73f77601e4513e22..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/config.py +++ /dev/null @@ -1,88 +0,0 @@ -from proxmoxer import ProxmoxAPI -from pynetbox import api as NetboxAPI -from netbox.settings import PLUGINS_CONFIG - - -# Set default values -NETBOX_DEFAULT_CONFIG = { - 'domain': 'localhost', - 'port': 8001, - 'token': 'this should be set', - 'ssl': True, - 'settings': { - 'vm_role_id': 0, - 'site_id': 0, - 'cluster_type_id': 0, - 'cluster_description': 'A Proxmox Cluster.', - 'default_tag_color_hex': 'ffffff', - }, -} -PROXMOX_DEFAULT_CONFIG = { - 'domain': 'this should be set', - 'port': 8006, - 'user': 'this should be set', - 'token': { - 'name': 'this should be set', - 'value': 'this should be set', - }, - 'ssl': True, -} - -USER_PLUGINS_CONFIG = PLUGINS_CONFIG.get('netbox_proxmox_sync', {}) -PROXMOX_CONFIG = USER_PLUGINS_CONFIG.get('proxmox', {}) -NETBOX_CONFIG = USER_PLUGINS_CONFIG.get('netbox', {}) - -# TODO: throw errors for missing required fields (like token/user/etc) -# -> honestly... every field is required here (except maybe ssl) -# PROXMOX -# - Main -PROXMOX_DOMAIN = PROXMOX_CONFIG.get('domain', PROXMOX_DEFAULT_CONFIG['domain']) -PROXMOX_PORT = PROXMOX_CONFIG.get('port', PROXMOX_DEFAULT_CONFIG['port']) -PROXMOX_USER = PROXMOX_CONFIG.get('user', PROXMOX_DEFAULT_CONFIG['user']) -PROXMOX_SSL = PROXMOX_CONFIG.get('ssl', PROXMOX_DEFAULT_CONFIG['ssl']) -# - Token -PROXMOX_TOKEN = PROXMOX_CONFIG.get('token', PROXMOX_DEFAULT_CONFIG['token']) -PROXMOX_TOKEN_NAME = PROXMOX_TOKEN.get('name', PROXMOX_DEFAULT_CONFIG['token']['name']) -PROXMOX_TOKEN_VALUE = PROXMOX_TOKEN.get('value', PROXMOX_DEFAULT_CONFIG['token']['value']) - -# NETBOX -# - Main -NETBOX_DOMAIN = NETBOX_CONFIG.get('domain', NETBOX_DEFAULT_CONFIG['domain']) -NETBOX_PORT = NETBOX_CONFIG.get('port', NETBOX_DEFAULT_CONFIG['port']) -NETBOX_TOKEN = NETBOX_CONFIG.get('token', NETBOX_DEFAULT_CONFIG['token']) -NETBOX_SSL = NETBOX_CONFIG.get('ssl', NETBOX_DEFAULT_CONFIG['ssl']) -# - Settings -NETBOX_SETTINGS = NETBOX_CONFIG.get('settings', NETBOX_DEFAULT_CONFIG['settings']) -NETBOX_CLUSTER_TYPE_ID = NETBOX_SETTINGS.get('cluster_type_id', NETBOX_DEFAULT_CONFIG['settings']['cluster_type_id']) -NETBOX_CLUSTER_DESCRIPTION = NETBOX_SETTINGS.get('cluster_description', NETBOX_DEFAULT_CONFIG['settings']['cluster_description']) -NETBOX_VM_ROLE_ID = NETBOX_SETTINGS.get('vm_role_id', NETBOX_DEFAULT_CONFIG['settings']['vm_role_id']) -NETBOX_SITE_ID = NETBOX_SETTINGS.get('site_id', NETBOX_DEFAULT_CONFIG['settings']['site_id']) -NETBOX_DEFAULT_TAG_COLOR = NETBOX_SETTINGS.get('default_tag_color_hex', NETBOX_DEFAULT_CONFIG['settings']['default_tag_color_hex']) - -# Create connections -# TODO: more descriptive errors -try: - PX_API = ProxmoxAPI( - PROXMOX_DOMAIN, - user=PROXMOX_USER, - port=PROXMOX_PORT, - token_name=PROXMOX_TOKEN_NAME, - token_value=PROXMOX_TOKEN_VALUE, - verify_ssl=PROXMOX_SSL, - ) -except Exception: - raise RuntimeError('Could not connect to Proxmox Cluster! Verify your credentials!') - -try: - # TODO: allow to change default base path? - if NETBOX_SSL: - url = f'https://{NETBOX_DOMAIN}:{NETBOX_PORT}' - else: - url = f'http://{NETBOX_DOMAIN}:{NETBOX_PORT}' - NB_API = NetboxAPI( - url, - token=NETBOX_TOKEN, - threading=True, - ) -except Exception: - raise RuntimeError('Could not connect to NetBox! Verify your credentials!') diff --git a/build/lib/netbox_proxmox_sync/api/netbox/create.py b/build/lib/netbox_proxmox_sync/api/netbox/create.py deleted file mode 100644 index 5c0fc06b501a2d56666fd4ead327d19f019e2d88..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/netbox/create.py +++ /dev/null @@ -1,135 +0,0 @@ -# TODO: more descriptive errors -# FIXME: fetch_* is a shit name -from netbox_proxmox_sync.api.config import NB_API -from netbox_proxmox_sync.api.utils.errors import APIError, ValidationError -from netbox_proxmox_sync.api.proxmox.extract import Proxmox -from .delete import all as delete_everything - - -def _assert_nodes_exist(proxmox_nodes): - node_names = set([node['name'] for node in proxmox_nodes]) - existing_names = set([ - node.name for node in NB_API.dcim.devices.filter(name=node_names) - ]) - missing_nodes = list(node_names - existing_names) - if len(missing_nodes) > 0: - missing = [f'Node "{node_name}" is missing!' for node_name in missing_nodes] - raise ValidationError('Not all cluster nodes registered in NetBox!', missing) - - -def _assert_cluster_does_not_exist(proxmox_cluster): - cluster_name = proxmox_cluster['name'] - netbox_cluster_list = NB_API.virtualization.clusters.filter(name=cluster_name) - if len(netbox_cluster_list) != 0: - raise ValidationError(f'Virtualization cluster "{cluster_name}" already exists!') - - -def cluster(proxmox_cluster, proxmox_nodes): - try: - # Already formatted the data for this :) - netbox_cluster = NB_API.virtualization.clusters.create(**proxmox_cluster) - except Exception as e: - raise APIError(e) - # Update nodes' cluster - node_names = [node['name'] for node in proxmox_nodes] - netbox_nodes = list(NB_API.dcim.devices.filter(name=[node_names])) - for node in netbox_nodes: - node.cluster = {'name': proxmox_cluster['name']} - if not NB_API.dcim.devices.update(netbox_nodes): - # In case of error "rollback" - netbox_cluster.delete() - raise APIError('Failed to set Nodes\' cluster!') - # Return JSON serializable dict :) - return dict(netbox_cluster) - - -def tags(): - proxmox_tags = Proxmox.fetch_tags() - try: - existing_tags = NB_API.extras.tags.filter(content_types=[ - 'virtualization.virtualmachines', - ]) - except Exception as e: - raise APIError(e) - # Tags for VMs currently should only be managed by this plugin - # TODO: we could add a custom field to mark tags managed by the plugin, for instance - if len(existing_tags) > 0: - names = [tag.name for tag in existing_tags] - errors = [f'Tag "{name}" should not exist!' for name in names] - raise ValidationError('Some VM tags already exist!', errors) - try: - netbox_tags = NB_API.extras.tags.create(proxmox_tags) - except Exception as e: - raise APIError(e) - # Return JSON serializable list :) - return [dict(tag) for tag in netbox_tags] - - -def custom_fields(): - try: - vmid_already_exists = len(NB_API.extras.custom_fields.filter( - name='vmid', object_types='virtualization.virtualmachine' - )) > 0 - except Exception as e: - raise APIError(e) - if vmid_already_exists: - raise ValidationError('Custom field "vmid" already exists!') - custom_fields = [{ - 'name': 'vmid', - 'label': 'VMID', - 'description': '[Proxmox] VM/CT ID', - 'ui_editable': 'no', - 'ui_visible': 'always', - 'filter_logic': 'exact', - 'type': 'integer', - 'object_types': ['virtualization.virtualmachine'] - }] - # Create stuff :) - try: - netbox_custom_fields = NB_API.extras.custom_fields.create(custom_fields) - except Exception as e: - raise APIError(e) - # Return JSON serializable list :) - return [dict(custom_field) for custom_field in netbox_custom_fields] - - -def vms_and_interfaces(): - proxmox_vms, proxmox_interfaces = Proxmox.fetch_virtual_machines_and_interfaces() - try: - netbox_vms = NB_API.virtualization.virtual_machines.create(proxmox_vms) - netbox_interfaces = NB_API.virtualization.interfaces.create(proxmox_interfaces) - except Exception as e: - raise APIError(e) - # Return JSON serializable lists :) - return [dict(x) for x in netbox_vms], [dict(x) for x in netbox_interfaces] - - -def all(): - proxmox_cluster, proxmox_nodes = Proxmox.fetch_cluster_and_nodes() - _assert_nodes_exist(proxmox_nodes) - _assert_cluster_does_not_exist(proxmox_cluster) - # Leave cluster out first so the first error comes quickly, useful for API - # errors like invalid token for instance, which might be common. - # (netbox takes tooooo long to delete <stuff>, even if <stuff> does not exist) - netbox_cluster = cluster(proxmox_cluster, proxmox_nodes) - # In case the cluster() call succeeds we are sure that we have write access - # for the API, so any error that comes out of the following calls must be - # something else (or you messed up things). - # This is useful because we can be pretty sure that a "rollback" is possible - # (the delete_everything() call below *should* NOT raise an exception). - try: - netbox_tags = tags() - netbox_custom_fields = custom_fields() - netbox_vms, netbox_interfaces = vms_and_interfaces() - except Exception as e: - # "rollback" in case of any failure - # (if this function fails you probably messed up in a very interesting way) - delete_everything() - raise e - return { - 'cluster': netbox_cluster, - 'tags': netbox_tags, - 'custom_fields': netbox_custom_fields, - 'vms': netbox_vms, - 'interfaces': netbox_interfaces, - } diff --git a/build/lib/netbox_proxmox_sync/api/netbox/delete.py b/build/lib/netbox_proxmox_sync/api/netbox/delete.py deleted file mode 100644 index c01a199a2401b322f5af8c0ce9173aa9d441b1fe..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/netbox/delete.py +++ /dev/null @@ -1,91 +0,0 @@ -from netbox_proxmox_sync.api.config import NB_API -from netbox_proxmox_sync.api.utils.errors import APIError, ValidationError -from netbox_proxmox_sync.api.proxmox.extract import Proxmox - - -def cluster(proxmox_cluster): - cluster_name = proxmox_cluster['name'] - try: - netbox_clusters = NB_API.virtualization.clusters.filter(name=cluster_name) - except Exception as e: - raise APIError(e) - if (len(netbox_clusters)) == 0: - raise ValidationError(f'Cluster "{cluster_name}" does not exist!') - netbox_cluster = list(netbox_clusters)[0] - # Return JSON serializable dict :) - return dict(netbox_cluster) - - -def tags(): - try: - netbox_tags = NB_API.extras.tags.filter(content_types=[ - 'virtualization.virtualmachines', - ]) - except Exception as e: - raise APIError(e) - # Return JSON serializable list :) - return [dict(tag) for tag in netbox_tags] - - -def custom_fields(): - try: - netbox_custom_fields = NB_API.extras.custom_fields.filter( - name='vmid', - object_types='virtualization.virtualmachine', - ) - except Exception as e: - raise APIError(e) - # Return JSON serializable list :) - return [dict(custom_field) for custom_field in netbox_custom_fields] - - -def vms_and_interfaces(netbox_cluster): - try: - cluster_id = netbox_cluster['id'] - netbox_vms = NB_API.virtualization.virtual_machines.filter(cluster_id=cluster_id) - netbox_interfaces = NB_API.virtualization.interfaces.filter(cluster_id=cluster_id) - except Exception as e: - raise APIError(e) - # Return JSON serializable lists :) - return [dict(x) for x in netbox_vms], [dict(x) for x in netbox_interfaces] - - -def delete_stuff(cluster, tags, custom_fields, vms, interfaces): - # This order is important - rt = NB_API.extras.tags.delete([tag['id'] for tag in tags]) - rc = NB_API.extras.custom_fields.delete([field['id'] for field in custom_fields]) - ri = NB_API.virtualization.interfaces.delete([i['id'] for i in interfaces]) - rv = NB_API.virtualization.virtual_machines.delete([vm['id'] for vm in vms]) - rf = NB_API.virtualization.clusters.delete([cluster['id']]) - errors = [] - if rt is None: - errors.append('Could not delete tags!') - if rf is None: - errors.append('Could not delete custom_fields!') - if rc is None: - errors.append('Could not delete cluster!') - if rv is None: - errors.append('Could not delete virtual machines!') - if ri is None: - errors.append('Could not delete interfaces!') - if len(errors) > 0: - raise APIError('Could not delete plugin information!', errors=errors) - - -def all(): - proxmox_cluster, _ = Proxmox.fetch_cluster_and_nodes() - netbox_cluster = cluster(proxmox_cluster) - netbox_tags = tags() - netbox_custom_fields = custom_fields() - netbox_vms, netbox_interfaces = vms_and_interfaces(netbox_cluster) - delete_stuff( - netbox_cluster, netbox_tags, netbox_custom_fields, - netbox_vms, netbox_interfaces - ) - return { - 'cluster': netbox_cluster, - 'tags': netbox_tags, - 'custom_fields': netbox_custom_fields, - 'vms': netbox_vms, - 'interfaces': netbox_interfaces, - } diff --git a/build/lib/netbox_proxmox_sync/api/netbox/extract.py b/build/lib/netbox_proxmox_sync/api/netbox/extract.py deleted file mode 100644 index 077b70391c5f8052fbcb8a1b3d329ba2da722ec2..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/netbox/extract.py +++ /dev/null @@ -1,55 +0,0 @@ -from netbox_proxmox_sync.api.config import ( - NB_API, - NETBOX_SITE_ID, - NETBOX_CLUSTER_TYPE_ID, -) -from netbox_proxmox_sync.api.utils.errors import APIError -from netbox_proxmox_sync.api.utils.models import ( - tag_from_netbox, - vm_from_netbox, - interface_from_netbox, -) - - -class NetBox: - @staticmethod - def fetch_cluster(cluster_name): - try: - netbox_cluster = NB_API.virtualization.clusters.filter(name=cluster_name) - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - return { - 'id': netbox_cluster.id, - 'name': netbox_cluster.name, - 'type': NETBOX_CLUSTER_TYPE_ID, - 'description': netbox_cluster.description, - 'site': NETBOX_SITE_ID, - } - - @staticmethod - def fetch_tags(): - try: - netbox_tags = [dict(tag) for tag in NB_API.extras.tags.filter( - content_types='virtualization.virtualmachines', - )] - except Exception as e: - raise APIError(e) - - return [tag_from_netbox(tag) for tag in netbox_tags] - - @staticmethod - def fetch_virtual_machines_and_interfaces(cluster_id): - try: - netbox_vms = [ - dict(vm) for vm in NB_API.virtualization.virtual_machines.filter( - cluster_id=cluster_id - ) - ] - netbox_interfaces = [dict(i) for i in NB_API.virtualization.interfaces.all()] - except Exception as e: - raise APIError(e) - - vms = [vm_from_netbox(vm) for vm in netbox_vms] - interfaces = [interface_from_netbox(i) for i in netbox_interfaces] - - return vms, interfaces diff --git a/build/lib/netbox_proxmox_sync/api/netbox/update.py b/build/lib/netbox_proxmox_sync/api/netbox/update.py deleted file mode 100644 index 368105da90e4222fd8f100273682e689f1e64639..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/netbox/update.py +++ /dev/null @@ -1,123 +0,0 @@ -from netbox_proxmox_sync.api.config import NB_API -from netbox_proxmox_sync.api.utils.errors import APIError, ValidationError -from netbox_proxmox_sync.api.proxmox.extract import Proxmox -from netbox_proxmox_sync.api.netbox.extract import NetBox - - -# MERGE, get created_*, updated_*, deleted_* -def tags(): - proxmox_tags = Proxmox.fetch_tags() - netbox_tags = NetBox.fetch_tags() - px_tags = {tag['name']: tag for tag in proxmox_tags} - nb_tags = {tag['name']: tag for tag in netbox_tags} - # "merge" information, extract stuff to C/U/D - created = [tag for tag in proxmox_tags if tag['name'] not in nb_tags] - deleted = [tag for tag in netbox_tags if tag['name'] not in px_tags] - updated = [] - for name in px_tags: - # only thing that can really change for tags is their color - if name in nb_tags and px_tags[name]['color'] != nb_tags[name]['color']: - nb_tags[name]['color'] = px_tags[name]['color'] - updated.append(nb_tags[name]) - # FIXME: error handling - NB_API.extras.tags.create(created) - NB_API.extras.tags.update(updated) - NB_API.extras.tags.delete([d['id'] for d in deleted]) - return created, updated, deleted - - -# TODO: allow for updates of custom fields in case the plugin is updated to use more -# def custom_fields(): - - -def vms(proxmox_vms, netbox_vms): - px_vms = {vm['custom_fields']['vmid']: vm for vm in proxmox_vms} - nb_vms = {vm['custom_fields']['vmid']: vm for vm in netbox_vms} - # "merge" information, extract stuff to C/U/D - created = [vm for vm in proxmox_vms if vm['custom_fields']['vmid'] not in nb_vms] - deleted = [vm for vm in netbox_vms if vm['custom_fields']['vmid'] not in px_vms] - updated = [] - for name in px_vms: - changed = False - px_vm = px_vms[name] - if nb_vms.get(name) is None: - continue - nb_vm = nb_vms[name] - # Update all fields - for field in px_vm: - changed |= nb_vm[field] != px_vm[field] - nb_vm[field] = px_vm[field] - if changed: - updated.append(nb_vm) - return created, updated, deleted - - -def interfaces(proxmox_interfaces, netbox_interfaces): - # Note: if the MAC changes the IP for the VM is lost :) - px_interfaces = {i['mac_address']: i for i in proxmox_interfaces} - nb_interfaces = {i['mac_address']: i for i in netbox_interfaces} - created = [i for i in proxmox_interfaces if i['mac_address'] not in nb_interfaces] - deleted = [i for i in netbox_interfaces if i['mac_address'] not in px_interfaces] - updated = [] - for name in px_interfaces: - changed = False - # Update all fields - px_interface = px_interfaces[name] - if nb_interfaces.get(name) is None: - continue - nb_interface = nb_interfaces[name] - for field in px_interface: - changed |= nb_interface[field] != px_interface[field] - nb_interface[field] = px_interface[field] - if changed: - updated.append(nb_interface) - return created, updated, deleted - - -def vms_and_interfaces(cluster_id): - proxmox_vms, proxmox_interfaces = Proxmox.fetch_virtual_machines_and_interfaces() - netbox_vms, netbox_interfaces = NetBox.fetch_virtual_machines_and_interfaces(cluster_id) - - created_vms, updated_vms, deleted_vms = vms(proxmox_vms, netbox_vms) - created_i, updated_i, deleted_i = interfaces(proxmox_interfaces, netbox_interfaces) - - # FIXME: error handling - NB_API.virtualization.virtual_machines.create(created_vms) - NB_API.virtualization.virtual_machines.update(updated_vms) - NB_API.virtualization.virtual_machines.delete([d['id'] for d in deleted_vms]) - NB_API.virtualization.interfaces.create(created_i) - NB_API.virtualization.interfaces.update(updated_i) - # NB_API.virtualization.interfaces.delete([d['id'] for d in deleted]) - - return created_vms, updated_vms, deleted_vms, \ - created_i, updated_i, deleted_i - - -def all(): - proxmox_cluster = Proxmox.fetch_cluster() - cluster_name = proxmox_cluster['name'] - clusters = list(NB_API.virtualization.clusters.filter(name=cluster_name)) - if (len(clusters)) == 0: - raise ValidationError(f'Cluster "{cluster_name}" does not exist!') - netbox_cluster = clusters[0] - created_tags, updated_tags, deleted_tags = tags() - created_vms, updated_vms, deleted_vms, \ - created_interfaces, updated_interfaces, deleted_interfaces = \ - vms_and_interfaces(cluster_id=netbox_cluster.id) - return { - 'tags': { - 'created': created_tags, - 'updated': updated_tags, - 'deleted': deleted_tags, - }, - 'virtual_machines': { - 'created': created_vms, - 'updated': updated_vms, - 'deleted': deleted_vms, - }, - 'interfaces': { - 'created': created_interfaces, - 'updated': updated_interfaces, - 'deleted': deleted_interfaces, - } - } diff --git a/build/lib/netbox_proxmox_sync/api/proxmox/extract.py b/build/lib/netbox_proxmox_sync/api/proxmox/extract.py deleted file mode 100644 index 34590706509b564cc57125f92c70cf54f4b26c4a..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/proxmox/extract.py +++ /dev/null @@ -1,119 +0,0 @@ -from netbox_proxmox_sync.api.config import ( - PX_API, - NETBOX_SITE_ID, - NETBOX_CLUSTER_DESCRIPTION, - NETBOX_CLUSTER_TYPE_ID, -) -from netbox_proxmox_sync.api.utils.errors import APIError -from netbox_proxmox_sync.api.utils.models import ( - tag_from_proxmox, - vm_from_proxmox, - interface_from_proxmox, -) - - -# Utils for proper data extraction -def extract_vm_data(cluster_name, node_name, vm_status): - try: - vm_config = PX_API.nodes(node_name).qemu(vm_status['vmid']).config.get() - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - tags = vm_config['tags'].split(';') - vm_data = vm_from_proxmox(cluster_name, node_name, vm_status, tags) - interfaces_data = extract_vm_interfaces(vm_config) - - return vm_data, interfaces_data - - -def extract_vm_interfaces(proxmox_vm_config): - vm_name = proxmox_vm_config['name'] - interfaces = [] - for interface_name in [key for key in proxmox_vm_config if key.startswith('net')]: - interface_info = proxmox_vm_config[interface_name] - new_interface = interface_from_proxmox(vm_name, interface_name, interface_info) - interfaces.append(new_interface) - return interfaces - - -class Proxmox: - @staticmethod - def fetch_cluster(): - cluster, _ = Proxmox.fetch_cluster_and_nodes() - return cluster - - @staticmethod - def fetch_cluster_and_nodes(): - try: - proxmox_cluster_info = PX_API.cluster.status.get() - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - cluster = { - 'name': proxmox_cluster_info[0]['name'], - 'type': NETBOX_CLUSTER_TYPE_ID, - 'description': NETBOX_CLUSTER_DESCRIPTION, - 'site': NETBOX_SITE_ID, - } - return cluster, proxmox_cluster_info[1:] - - @staticmethod - def fetch_tags(): - try: - proxmox_tag_info = PX_API.cluster.options.get() - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - allowed_tags = proxmox_tag_info['allowed-tags'] - tag_colormap = proxmox_tag_info['tag-style']['color-map'].split(';') - tags = [] - # Create tags with no color defined - for tag_name in allowed_tags: - tags.append(tag_from_proxmox(tag_name)) - # Remap defined tag colors - for tag_info in tag_colormap: - tag_name = tag_info.split(':')[0] - tag_slug = tag_name.lower().replace(' ', '-').replace('.', '_') - tag_color = tag_info.split(':')[1].lower() - found = False - # Find existing tag and update its color - for tag in tags: - if tag['name'] == tag_name: - found = True - tag['color'] = tag_color - break - # ??? - if not found: - tags.append({ - 'name': tag_name, - 'slug': tag_slug, - 'color': tag_color, - 'object_types': ['virtualization.virtualmachine'] - }) - return tags - - @staticmethod - def fetch_virtual_machines_and_interfaces(): - try: - cluster_status = PX_API.cluster.status.get() - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - cluster_name = cluster_status[0]['name'] - proxmox_nodes = [node['name'] for node in cluster_status[1:]] - # List, because node information is stored in the VMs themselves - # (same for interfaces: vm info is stored in the interface itself) - virtual_machines = [] - interfaces = [] - for node_name in proxmox_nodes: - try: - node_vms = PX_API.nodes(node_name).qemu.get() - except Exception: - raise APIError('Failed to connect to Proxmox Cluster!') - for vm_status in node_vms: - # If the update runs as the VM is being cloned the vm_config will be - # messed up and our update will fail. - if vm_status.get('lock') is not None and vm_status['lock'] == 'clone': - continue - new_vm, new_interfaces = extract_vm_data( - cluster_name, node_name, vm_status - ) - virtual_machines.append(new_vm) - interfaces.extend(new_interfaces) - return virtual_machines, interfaces diff --git a/build/lib/netbox_proxmox_sync/api/urls.py b/build/lib/netbox_proxmox_sync/api/urls.py deleted file mode 100644 index 5acc2eee3044bd82d59eb564322762d26f45a688..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.urls import path -from . import views - -app_name = 'netbox_proxmox_sync' - -urlpatterns = [ - path('create/', views.CreateCluster.as_view(), name='proxmoxsync_create'), - path('update/', views.UpdateCluster.as_view(), name='proxmoxsync_update'), - path('delete/', views.DeleteCluster.as_view(), name='proxmoxsync_delete'), -] diff --git a/build/lib/netbox_proxmox_sync/api/utils/errors.py b/build/lib/netbox_proxmox_sync/api/utils/errors.py deleted file mode 100644 index 9b16ad5edb3ade134a6a7d39aa5c876c4ed1e07d..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/utils/errors.py +++ /dev/null @@ -1,15 +0,0 @@ -class APIError(Exception): - def __init__(self, message, status=500, errors=[]): - super().__init__(message) - self.status = status - self.errors = errors - - -class ValidationError(APIError): - def __init__(self, message, errors=[]): - super().__init__(message, 422, errors) - - -class UnauthorizedError(APIError): - def __init__(self, message): - super().__init__(message, 401) diff --git a/build/lib/netbox_proxmox_sync/api/utils/models.py b/build/lib/netbox_proxmox_sync/api/utils/models.py deleted file mode 100644 index cd974a3aaf0716c056911b5899562b666d33e9cd..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/utils/models.py +++ /dev/null @@ -1,167 +0,0 @@ -from netbox_proxmox_sync.api.config import ( - NETBOX_DEFAULT_TAG_COLOR, - NETBOX_VM_ROLE_ID, -) -# Classes to ensure NetBox and Proxmox data have the exact same format and fields, -# which makes updating stuff correctly easier - - -class VirtualMachine: - def __init__(self, vmid, name, status, node_name, cluster_name, vcpus, maxmem, - role_id, tags=[], maxdisk=None, description=None, id=None): - self.vmid = vmid - self.name = name - self.status = status - self.node_name = node_name - self.cluster_name = cluster_name - self.role_id = role_id - self.vcpus = vcpus - self.maxmem = maxmem - self.tags = sorted(tags) - self.maxdisk = maxdisk - self.description = description - self.id = id - - def to_dict(self): - # Trust me, it's important that they're sorted - vm_data = { - 'name': self.name, - 'status': self.status, - 'device': {'name': self.node_name}, - 'cluster': {'name': self.cluster_name}, - 'vcpus': self.vcpus, - 'memory': self.maxmem, - 'role': self.role_id, - 'tags': [{'name': tag} for tag in self.tags], - 'custom_fields': {'vmid': self.vmid} - } - # Both these fields may be None, so we have to set them separately - if self.maxdisk is not None and self.maxdisk > 0: - vm_data['disk'] = int(self.maxdisk) - if self.description is not None: - # NetBox only allows 200 char description, but our VMs have more - # so we store the description in the "comments" instead - vm_data['comments'] = self.description - if self.id is not None: - vm_data['id'] = self.id - return vm_data - - -class VirtualInterface: - def __init__(self, name, vm_name, mac_address, vlan_id, id=None): - self.name = name - self.vm_name = vm_name - self.mac_address = mac_address - self.vlan_id = vlan_id - self.id = id - - def to_dict(self): - interface = { - 'name': self.name, - 'virtual_machine': {'name': self.vm_name}, - 'mac_address': self.mac_address, - 'mode': 'access', - 'untagged_vlan': {'vid': self.vlan_id}, - # 'bridge': bridge - } - if self.id is not None: - interface['id'] = self.id - return interface - - -class VirtualMachineTag: - def __init__(self, name, id=None, color=NETBOX_DEFAULT_TAG_COLOR): - self.name = name - self.slug = name.lower().replace(' ', '-').replace('.', '_') - self.color = color - self.id = id - - def to_dict(self): - vm_tag = { - 'name': self.name, - 'slug': self.slug, - 'color': self.color, - 'object_types': ['virtualization.virtualmachine'], - } - if self.id is not None: - vm_tag['id'] = self.id - return vm_tag - - -def tag_from_netbox(netbox_tag): - return VirtualMachineTag( - id=netbox_tag['id'], - name=netbox_tag['name'], - # slug=netbox_tag.slug, - color=netbox_tag['color'], - ).to_dict() - - -def vm_from_netbox(netbox_vm): - role_id = None - if netbox_vm.get('role'): - role_id = netbox_vm['role']['id'] - return VirtualMachine( - id=netbox_vm['id'], - vmid=netbox_vm['custom_fields']['vmid'], - name=netbox_vm['name'], - status=netbox_vm['status']['value'], - # FIXME: some of these are tricky - node_name=netbox_vm['device']['name'], - cluster_name=netbox_vm['cluster']['name'], - role_id=role_id, - vcpus=netbox_vm['vcpus'], - maxmem=netbox_vm['memory'], - tags=[tag['name'] for tag in netbox_vm['tags']], - maxdisk=netbox_vm.get('disk'), - description=netbox_vm.get('comments'), - ).to_dict() - - -def interface_from_netbox(netbox_interface): - return VirtualInterface( - id=netbox_interface['id'], - name=netbox_interface['name'], - vm_name=netbox_interface['virtual_machine']['name'], - mac_address=netbox_interface['mac_address'].upper(), - vlan_id=netbox_interface['untagged_vlan']['vid'], - ).to_dict() - - -def tag_from_proxmox(tag_name, color=NETBOX_DEFAULT_TAG_COLOR): - return VirtualMachineTag( - tag_name, - color, - ).to_dict() - - -def vm_from_proxmox(cluster_name, proxmox_node_name, proxmox_vm, tags=[]): - maxdisk = proxmox_vm.get('maxdisk') - if maxdisk is not None: - maxdisk = int(maxdisk) / 2 ** 30 # B -> GB - memory = int(proxmox_vm['maxmem']) / 2**20 # B -> MB - return VirtualMachine( - vmid=proxmox_vm['vmid'], - name=proxmox_vm['name'], - status='active' if proxmox_vm['status'] == 'running' else 'offline', - node_name=proxmox_node_name, - cluster_name=cluster_name, - vcpus=proxmox_vm['cpus'], - role_id=NETBOX_VM_ROLE_ID, - maxmem=memory, - tags=tags, - maxdisk=maxdisk, - description=proxmox_vm.get('description'), - ).to_dict() - - -def interface_from_proxmox(proxmox_vm_name, interface_name, proxmox_interface): - # net[0-9]+: 'virtio=00:00:00:00:00:00,bridge=vmbr<VID>' - mac = proxmox_interface.split('virtio=')[1].split(',')[0] - vlan_id = int(proxmox_interface.split('bridge=vmbr')[1].split(',firewall')[0]) - return VirtualInterface( - name=f'{proxmox_vm_name}:{interface_name}', - vm_name=proxmox_vm_name, - mac_address=mac.upper(), - vlan_id=vlan_id, - ).to_dict() diff --git a/build/lib/netbox_proxmox_sync/api/views.py b/build/lib/netbox_proxmox_sync/api/views.py deleted file mode 100644 index 67f07669dd8969fbe3ec7e8bdf8931c632eeb9ef..0000000000000000000000000000000000000000 --- a/build/lib/netbox_proxmox_sync/api/views.py +++ /dev/null @@ -1,57 +0,0 @@ -from django.http import HttpResponse -from django.views import View -from django.contrib.auth.mixins import PermissionRequiredMixin -from netbox_proxmox_sync.api.utils.errors import APIError -from netbox_proxmox_sync.api.netbox import create, update, delete -import json -import traceback -# TODO: fix HTTP status codes -# TODO: proper error handling - - -class CreateCluster(PermissionRequiredMixin, View): - permission_required = "netbox_proxbox.sync_proxmox_cluster" - - def get(self, _): - try: - result = create.all() - json_result = json.dumps(result) - return HttpResponse(json_result, content_type='application/json') - except APIError as e: - raise e - json_result = json.dumps({'error': str(e), 'trace': traceback.print_exc()}) - return HttpResponse( - json_result, status=e.status, content_type='application/json' - ) - - -class UpdateCluster(PermissionRequiredMixin, View): - permission_required = "netbox_proxbox.sync_proxmox_cluster" - - def get(self, _): - try: - result = update.all() - json_result = json.dumps(result) - return HttpResponse(json_result, status=201, content_type='application/json') - except APIError as e: - raise e - json_result = json.dumps({'error': str(e), 'trace': traceback.print_exc()}) - return HttpResponse( - json_result, status=e.status, content_type='application/json' - ) - - -class DeleteCluster(PermissionRequiredMixin, View): - permission_required = "netbox_proxbox.reset_promox_cluster" - - def get(self, _): - try: - result = delete.all() - json_result = json.dumps(result) - return HttpResponse(json_result, status=201, content_type='application/json') - except APIError as e: - raise e - json_result = json.dumps({'error': str(e), 'trace': traceback.print_exc()}) - return HttpResponse( - json_result, status=e.status, content_type='application/json' - ) diff --git a/netbox_proxmox_sync.egg-info/PKG-INFO b/netbox_proxmox_sync.egg-info/PKG-INFO deleted file mode 100644 index 2169862fc9d7a335baf32ee5de0bf6fb1b3f851c..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/PKG-INFO +++ /dev/null @@ -1,4 +0,0 @@ -Metadata-Version: 2.1 -Name: netbox-proxmox-sync -Version: 2.0.0 -Summary: Import Proxmox cluster info into NetBox. diff --git a/netbox_proxmox_sync.egg-info/SOURCES.txt b/netbox_proxmox_sync.egg-info/SOURCES.txt deleted file mode 100644 index d91c4afd16667b62eaaa32a10638c4d2bf362f46..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/SOURCES.txt +++ /dev/null @@ -1,20 +0,0 @@ -README.md -setup.py -netbox_proxmox_sync/__init__.py -netbox_proxmox_sync.egg-info/PKG-INFO -netbox_proxmox_sync.egg-info/SOURCES.txt -netbox_proxmox_sync.egg-info/dependency_links.txt -netbox_proxmox_sync.egg-info/not-zip-safe -netbox_proxmox_sync.egg-info/requires.txt -netbox_proxmox_sync.egg-info/top_level.txt -netbox_proxmox_sync/api/__init__.py -netbox_proxmox_sync/api/config.py -netbox_proxmox_sync/api/urls.py -netbox_proxmox_sync/api/views.py -netbox_proxmox_sync/api/netbox/create.py -netbox_proxmox_sync/api/netbox/delete.py -netbox_proxmox_sync/api/netbox/extract.py -netbox_proxmox_sync/api/netbox/update.py -netbox_proxmox_sync/api/proxmox/extract.py -netbox_proxmox_sync/api/utils/errors.py -netbox_proxmox_sync/api/utils/models.py \ No newline at end of file diff --git a/netbox_proxmox_sync.egg-info/dependency_links.txt b/netbox_proxmox_sync.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/netbox_proxmox_sync.egg-info/not-zip-safe b/netbox_proxmox_sync.egg-info/not-zip-safe deleted file mode 100644 index 8b137891791fe96927ad78e64b0aad7bded08bdc..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/netbox_proxmox_sync.egg-info/requires.txt b/netbox_proxmox_sync.egg-info/requires.txt deleted file mode 100644 index 3240c1cb61a0381a70c378909e3c5be729663ab4..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/requires.txt +++ /dev/null @@ -1,2 +0,0 @@ -proxmoxer -pynetbox diff --git a/netbox_proxmox_sync.egg-info/top_level.txt b/netbox_proxmox_sync.egg-info/top_level.txt deleted file mode 100644 index 0bd3fead4416e20df81238981c3bbeae84dcb467..0000000000000000000000000000000000000000 --- a/netbox_proxmox_sync.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -netbox_proxmox_sync diff --git a/netbox_proxmox_sync/__init__.py b/netbox_proxmox_sync/__init__.py index 29948087aef9ab10f486f6e0f5b76391a0e1e1b0..ce56b1c71bbc31788665a29ac777977d759d8d6c 100644 --- a/netbox_proxmox_sync/__init__.py +++ b/netbox_proxmox_sync/__init__.py @@ -5,7 +5,7 @@ class NetBoxProxmoxSyncConfig(PluginConfig): name = 'netbox_proxmox_sync' verbose_name = 'NetBox Proxmox Sync' description = 'Import cluster information from Proxmox into NetBox' - version = '2.0.0' + version = '2.0.1' base_url = 'netbox-proxmox-sync' diff --git a/netbox_proxmox_sync/api/netbox/update.py b/netbox_proxmox_sync/api/netbox/update.py index 368105da90e4222fd8f100273682e689f1e64639..e7f64b854b22825533df5ffb0fcdb19200aa11a7 100644 --- a/netbox_proxmox_sync/api/netbox/update.py +++ b/netbox_proxmox_sync/api/netbox/update.py @@ -1,5 +1,5 @@ from netbox_proxmox_sync.api.config import NB_API -from netbox_proxmox_sync.api.utils.errors import APIError, ValidationError +from netbox_proxmox_sync.api.utils.errors import ValidationError from netbox_proxmox_sync.api.proxmox.extract import Proxmox from netbox_proxmox_sync.api.netbox.extract import NetBox @@ -20,9 +20,9 @@ def tags(): nb_tags[name]['color'] = px_tags[name]['color'] updated.append(nb_tags[name]) # FIXME: error handling - NB_API.extras.tags.create(created) - NB_API.extras.tags.update(updated) NB_API.extras.tags.delete([d['id'] for d in deleted]) + NB_API.extras.tags.update(updated) + NB_API.extras.tags.create(created) return created, updated, deleted @@ -82,12 +82,12 @@ def vms_and_interfaces(cluster_id): created_i, updated_i, deleted_i = interfaces(proxmox_interfaces, netbox_interfaces) # FIXME: error handling - NB_API.virtualization.virtual_machines.create(created_vms) - NB_API.virtualization.virtual_machines.update(updated_vms) NB_API.virtualization.virtual_machines.delete([d['id'] for d in deleted_vms]) - NB_API.virtualization.interfaces.create(created_i) - NB_API.virtualization.interfaces.update(updated_i) + NB_API.virtualization.virtual_machines.update(updated_vms) + NB_API.virtualization.virtual_machines.create(created_vms) # NB_API.virtualization.interfaces.delete([d['id'] for d in deleted]) + NB_API.virtualization.interfaces.update(updated_i) + NB_API.virtualization.interfaces.create(created_i) return created_vms, updated_vms, deleted_vms, \ created_i, updated_i, deleted_i diff --git a/setup.py b/setup.py index b59b584a527fb943a9e5275b27f0e474dc859826..b6afdab6b0ed784456cf80c8663397b24c706349 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import find_packages, setup setup( name='netbox-proxmox-sync', - version='2.0.0', + version='2.0.1', description='Import Proxmox cluster info into NetBox.', install_requires=['pynetbox', 'proxmoxer'], include_package_data=True,