from getpass import getpass from ncclient.transport.errors import AuthenticationError from paramiko import AuthenticationException from django.conf import settings from django.core.management.base import BaseCommand, CommandError from django.db import transaction from dcim.models import Device, InventoryItem, Site, STATUS_ACTIVE class Command(BaseCommand): help = "Update inventory information for specified devices" username = settings.NETBOX_USERNAME password = settings.NETBOX_PASSWORD def add_arguments(self, parser): parser.add_argument('-u', '--username', dest='username', help="Specify the username to use") parser.add_argument('-p', '--password', action='store_true', default=False, help="Prompt for password to use") parser.add_argument('-s', '--site', dest='site', action='append', help="Filter devices by site (include argument once per site)") parser.add_argument('-n', '--name', dest='name', help="Filter devices by name (regular expression)") parser.add_argument('--full', action='store_true', default=False, help="For inventory update for all devices") parser.add_argument('--fake', action='store_true', default=False, help="Do not actually update database") def handle(self, *args, **options): def create_inventory_items(inventory_items, parent=None): for item in inventory_items: i = InventoryItem(device=device, parent=parent, name=item['name'], part_id=item['part_id'], serial=item['serial'], discovered=True) i.save() create_inventory_items(item.get('items', []), parent=i) # Credentials if options['username']: self.username = options['username'] if options['password']: self.password = getpass("Password: ") # Attempt to inventory only active devices device_list = Device.objects.filter(status=STATUS_ACTIVE) # --site: Include only devices belonging to specified site(s) if options['site']: sites = Site.objects.filter(slug__in=options['site']) if sites: site_names = [s.name for s in sites] self.stdout.write("Running inventory for these sites: {}".format(', '.join(site_names))) else: raise CommandError("One or more sites specified but none found.") device_list = device_list.filter(site__in=sites) # --name: Filter devices by name matching a regex if options['name']: device_list = device_list.filter(name__iregex=options['name']) # --full: Gather inventory data for *all* devices if options['full']: self.stdout.write("WARNING: Running inventory for all devices! Prior data will be overwritten. (--full)") # --fake: Gathering data but not updating the database if options['fake']: self.stdout.write("WARNING: Inventory data will not be saved! (--fake)") device_count = device_list.count() self.stdout.write("** Found {} devices...".format(device_count)) for i, device in enumerate(device_list, start=1): self.stdout.write("[{}/{}] {}: ".format(i, device_count, device.name), ending='') # Skip inactive devices if not device.status: self.stdout.write("Skipped (not active)") continue # Skip devices without primary_ip set if not device.primary_ip: self.stdout.write("Skipped (no primary IP set)") continue # Skip devices which have already been inventoried if not doing a full update if device.serial and not options['full']: self.stdout.write("Skipped (Serial: {})".format(device.serial)) continue RPC = device.get_rpc_client() if not RPC: self.stdout.write("Skipped (no RPC client available for platform {})".format(device.platform)) continue # Connect to device and retrieve inventory info try: with RPC(device, self.username, self.password) as rpc_client: inventory = rpc_client.get_inventory() except KeyboardInterrupt: raise except (AuthenticationError, AuthenticationException): self.stdout.write("Authentication error!") continue except Exception as e: self.stdout.write("Error: {}".format(e)) continue if options['verbosity'] > 1: self.stdout.write("") self.stdout.write("\tSerial: {}".format(inventory['chassis']['serial'])) self.stdout.write("\tDescription: {}".format(inventory['chassis']['description'])) for item in inventory['items']: self.stdout.write("\tItem: {} / {} ({})".format(item['name'], item['part_id'], item['serial'])) else: self.stdout.write("{} ({})".format(inventory['chassis']['description'], inventory['chassis']['serial'])) if not options['fake']: with transaction.atomic(): # Update device serial if device.serial != inventory['chassis']['serial']: device.serial = inventory['chassis']['serial'] device.save() InventoryItem.objects.filter(device=device, discovered=True).delete() create_inventory_items(inventory.get('items', [])) self.stdout.write("Finished!")