class DemoScript(Script): name = "Script Demo" description = "A quick demonstration of the available field types" my_string1 = StringVar( description="Input a string between 3 and 10 characters", min_length=3, max_length=10) my_string2 = StringVar( description= "This field enforces a regex: three letters followed by three numbers", regex=r'[a-z]{3}\d{3}') my_number = IntegerVar( description="Pick a number between 1 and 255 (inclusive)", min_value=1, max_value=255) my_boolean = BooleanVar( description="Use the checkbox to toggle true/false") my_object = ObjectVar(description="Select a NetBox site", queryset=Site.objects.all()) def run(self, data): self.log_info("Your string was {}".format(data['my_string1'])) self.log_info("Your second string was {}".format(data['my_string2'])) self.log_info("Your number was {}".format(data['my_number'])) if data['my_boolean']: self.log_info("You ticked the checkbox") else: self.log_info("You did not tick the checkbox") self.log_info("You chose the sites {}".format(data['my_object'])) return "Here's some output"
class TestScript(Script): class Meta: name = "Test script" var1 = StringVar() var2 = IntegerVar() var3 = BooleanVar() def run(self, data, commit=True): self.log_info(data['var1']) self.log_success(data['var2']) self.log_failure(data['var3']) return 'Script complete'
class New(Script): class Meta: name = "Create FTTB CPE w/ automatic uplink selection and monitoring" description = "Generate standard CPE router configuration \ and configure uplink switch and linkage" field_order = [ 'business_name', 'asset_tag', 'uplink_site', 'skip_zabbix', 'skip_uplink_port', 'confirmation_email', 'comments', 'rack_mount' ] # ##################Check to make sure you have these defined############## RACK_MOUNT_ID = DeviceType.objects.get(model="RB2011-RM").id WALL_MOUNT_ID = DeviceType.objects.get(model="RB2011").id SITE_ID = Site.objects.get(name="Customer Premise").id DEVICE_ROLE_ID = DeviceRole.objects.get(name="CPE").id AGG_ROLE_ID = DeviceRole.objects.get(name="Aggregation").id PLATFORM_ID = Platform.objects.get(name="RouterOS").id CHOICES = ((RACK_MOUNT_ID, "Rack Mount"), (WALL_MOUNT_ID, "Wall Mount")) INET_VLAN = VLAN.objects.get(vid=900) MGMT_VLAN = VLAN.objects.get(vid=107) #SITES = Site.objects.filter(region_id=1) REGION = 1 ZAPI = ZabbixAPI( url='http://zabbix-web-apache-mysql:8080/api_jsonrpc.php/', user='******', password='******') SNMP_COMMUNITY = "got" # ##################END################### rack_mount = ChoiceVar(choices=((True, "Rack Mount"), (False, "Wall Mount"))) business_name = StringVar(label="Business Name") asset_tag = StringVar(label="Asset Tag", required=False) hardware_choice = ChoiceVar(choices=CHOICES) comments = TextVar(label="Comments", required=False) uplink_site = ObjectVar(model=Site, query_params={'region_id': REGION}) skip_zabbix = BooleanVar(label="Disable Zabbix configuration") skip_uplink_port = BooleanVar(label="Disable upstream port selection") confirmation_email = BooleanVar(label="Send Confirmation Email") def run(self, data, commit): # Create the device device = Device(name=data['business_name'], device_role_id=self.DEVICE_ROLE_ID, device_type_id=data["hardware_choice"], platform_id=self.PLATFORM_ID, site_id=self.SITE_ID) device.save() interfaces = Interface.objects.filter(device_id=device.id) enabled_interfaces = [] mgmt_intf = interfaces.get(name="b107") enabled_interfaces.append(mgmt_intf) uplk_intf = interfaces.get(name="ether10") enabled_interfaces.append(uplk_intf) uplk_intf.mode = "tagged" uplk_intf.tagged_vlans.set([self.INET_VLAN, self.MGMT_VLAN]) uplk_intf.description = "Uplink" uplk_intf.save() inet_intf = interfaces.get(name="ether1") enabled_interfaces.append(inet_intf) inet_intf.description = "Internet" inet_intf.mode = "access" inet_intf.untagged_vlan = self.INET_VLAN inet_intf.save() mgmt_intf.save() for intf in interfaces: intf.enabled = False intf.save() for intf in enabled_interfaces: intf.enabled = True intf.mtu = 1500 intf.save() available_ip = Prefix.objects.get( vlan=self.MGMT_VLAN).get_first_available_ip() ip = IPAddress( address=available_ip, assigned_object_type=ContentType.objects.get_for_model(Interface), assigned_object_id=mgmt_intf.id) ip.save() device.primary_ip4_id = ip.id device.primary_ip_id = ip.id device.comments = data['comments'] device.save() # ############ if (not data["skip_zabbix"] and commit): # Post to Zabbix API to create host in mikrotik group and ICMP # template try: hostid = self.ZAPI.host.create( host=data["business_name"], interfaces=dict(type=2, main=1, useip=1, ip=available_ip.replace("/24", ""), port=161, dns="", details=dict( version="1", bulk="0", community=self.SNMP_COMMUNITY)), groups=dict(groupid=15), templates=dict(templateid=10186)) self.log_info("zabbix configured successfully") except Exception as e: self.log_info("failed to configure zabbix {0}".format(e)) if (not data["skip_uplink_port"] and commit): try: agg_switches = Device.objects.filter( site=data["uplink_site"], device_role_id=self.AGG_ROLE_ID, status="active") selected_interface = "" for agg_switch in agg_switches: interfaces = Interface.objects.filter( device_id=agg_switch.id) for interface in interfaces: if (interface.connection_status is not True and interface.enabled is True and interface.description == '' and interface.type == '1000base-x-sfp'): selected_interface = interface break if selected_interface != "": selected_interface.enabled = True selected_interface.description = device.name selected_interface.mode = "tagged" selected_interface.tagged_vlans.set( [self.INET_VLAN, self.MGMT_VLAN]) selected_interface.save() cable = Cable( termination_a=uplk_intf, termination_b=selected_interface, ) cable.save() self.log_info( "uplink switch chosen. Port {0} on {1}".format( selected_interface.name, agg_switch.name)) break if selected_interface == "": self.log_failure("No available aggregate port found. \ No aggregate port assigned.") except BaseException: self.log("failed to document uplink switch") self.log_success("Created {0}".format(device.name))
class NewVM(Script): class Meta: name = "New VM" description = "Create a new VM" field_order = [ 'vm_name', 'dns_name', 'primary_ip4', 'primary_ip6', #'vrf', 'role', 'status', 'cluster', #'tenant', 'platform', 'interface_name', 'mac_address', 'vcpus', 'memory', 'disk', 'comments' ] vm_name = StringVar(label="VM name") dns_name = StringVar(label="DNS name", required=False) primary_ip4 = IPAddressWithMaskVar(label="IPv4 address") primary_ip6 = IPAddressWithMaskVar(label="IPv6 address", required=False) #vrf = ObjectVar(VRF.objects, required=False) role = ObjectVar(DeviceRole.objects.filter(vm_role=True), required=False) status = ChoiceVar(VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE) cluster = ObjectVar(Cluster.objects) #tenant = ObjectVar(Tenant.objects, required=False) platform = ObjectVar(Platform.objects, required=False) interface_name = StringVar(default="eth0") mac_address = StringVar(label="MAC address", required=False) vcpus = IntegerVar(label="VCPUs", required=False) memory = IntegerVar(label="Memory (MB)", required=False) disk = IntegerVar(label="Disk (GB)", required=False) comments = TextVar(label="Comments", required=False) def run(self, data): vm = VirtualMachine( name=data["vm_name"], role=data["role"], status=data["status"], cluster=data["cluster"], platform=data["platform"], vcpus=data["vcpus"], memory=data["memory"], disk=data["disk"], comments=data["comments"], tenant=data.get("tenant"), ) vm.save() interface = Interface( name=data["interface_name"], type=InterfaceTypeChoices.TYPE_VIRTUAL, mac_address=data["mac_address"], virtual_machine=vm, ) interface.save() def add_addr(addr, expect_family): if not addr: return if addr.version != expect_family: raise RuntimeError("Wrong family for %r" % a) try: a = IPAddress.objects.get( address=addr, vrf=data.get("vrf"), ) result = "Assigned" except ObjectDoesNotExist: a = IPAddress( address=addr, vrf=data.get("vrf"), ) result = "Created" a.status = IPAddressStatusChoices.STATUS_ACTIVE a.dns_name = data["dns_name"] if a.interface: raise RuntimeError("Address %s is already assigned" % addr) a.interface = interface a.tenant = data.get("tenant") a.save() self.log_info("%s IP address %s %s" % (result, a.address, a.vrf or "")) setattr(vm, "primary_ip%d" % a.family, a) add_addr(data["primary_ip4"], 4) add_addr(data["primary_ip6"], 6) vm.save() self.log_success("Created VM %s" % vm.name)
class InterfaceUpdate(Script): """Script that can be used to auto generate interfaces from devices using NAPALM Args: Script (:obj:`extras.script.Script`): Netbox Script object that is needed for a class to be recognized as one """ class Meta: """Special class that is used for defining script information""" name = "Interface update" description = "Script that updates interfaces for device names provided" commit_default = False device_name = StringVar( label="Devices regex", default="(r6|v22)-leaf((1\d*|[2-9])|1)", description="Regex that will be used to select devices to update interfaces", ) ignore_interfaces = StringVar( label="Interfaces to ignore regex", default="Vlan.*", description="Regex that will ignore interfaces matching it (Leave blank to not ignore any)", ) def run(self, data, commit: bool): """The main method of the script that will be run when pressing the Run Script button 1. Grabs the data from Netbox about devices containing the devices by regex input by the user 2. Loops through the devices, grabs their current Netbox interfaces and then makes a NAPALM call to the device 3. Loops through NAPALM interfaces, while ignoring the ones matching the user supplied regex 4. If a mac_address is any kind of empty or null, it makes sure to set it to python None 5. Using get_or_create, grabs or creates the interface from Netbox while filtering by the specific NAPALM interface the loop is currently on 6. Notifies user if a interface was created and if it wasn't checks if the description in Netbox matches NAPALM description 7. Updates description if neccessary, notifying user of it. Args: data (dict): a dict that has the variables for user input. Defined using class variables commit (bool): a bool that determines to commit or not to commit the changes to database (since Netbox automatically reverts database changes on commit = False, we don't use it) Returns: str: output for the Output tab """ output = "" devices = Device.objects.filter(name__regex=data["device_name"]) for device in devices: netbox_interfaces = Interface.objects.filter(device=device.id) napalm_interfaces = napalm_call("get_interfaces", device.id, self.request) for napalm_interface in napalm_interfaces: # Blacklist interfaces if data["ignore_interfaces"] != "" and re.match( data["ignore_interfaces"], napalm_interface ): continue napalm_description = napalm_interfaces[napalm_interface]["description"] mac_address = napalm_interfaces[napalm_interface]["mac_address"] if ( mac_address == "None" and mac_address == "Unspecified" and mac_address == "" ): mac_address = None # We don't use update_or_create so we can inform the user when something actually updates # update_or_create will update even if nothing changes (netbox_interface, created) = netbox_interfaces.get_or_create( name=napalm_interface, defaults={ "type": InterfaceTypeChoices.TYPE_OTHER, "description": napalm_description, "device": device, "mac_address": mac_address, }, ) if created: self.log_success( f"`[{device.name}]` Created a new interface **({netbox_interface.name})**" ) else: if netbox_interface.description != napalm_description: old_description = netbox_interface.description netbox_interface.description = napalm_description netbox_interface.save() self.log_success( f"`[{device.name}]` Updated an interface's description **({netbox_interface.name})**: '{old_description}' -> '{napalm_description}'" ) return output
class MultiConnect(Script): class Meta: name = "Multi Connect" description = "Add multiple connections from one device to another" device_a = ObjectVar(model=Device, label="Device A") termination_type_a = ChoiceVar(choices=TERM_CHOICES, label="Device A port type") termination_name_a = StringVar(label="Device A port name pattern", description="Example: ge-0/0/[5,7,12-23]") device_b = ObjectVar(model=Device, label="Device B") termination_type_b = ChoiceVar(choices=TERM_CHOICES, label="Device B port type") termination_name_b = StringVar(label="Device B port name pattern", description="Example: ge-0/0/[5,7,12-23]") cable_status = ChoiceVar(choices=LinkStatusChoices.CHOICES, default=LinkStatusChoices.STATUS_CONNECTED, label="Cable Status") cable_type = ChoiceVar(choices=NO_CHOICE + CableTypeChoices.CHOICES, required=False, label="Cable Type") cable_tenant = ObjectVar(model=Tenant, required=False, label="Cable Tenant") cable_label = StringVar(label="Cable Label pattern", required=False) cable_color = ChoiceVar(choices=NO_CHOICE + ColorChoices.CHOICES, required=False, label="Cable Color") cable_length = IntegerVar( required=False, label="Cable Length") # unfortunately there is no DecimalVar cable_length_unit = ChoiceVar(choices=NO_CHOICE + CableLengthUnitChoices.CHOICES, required=False, label="Cable Length Unit") cable_tags = MultiObjectVar(model=Tag, required=False, label="Cable Tags") def run(self, data, commit): device_a = data["device_a"] device_b = data["device_b"] ports_a = getattr(device_a, data["termination_type_a"]).all() ports_b = getattr(device_b, data["termination_type_b"]).all() terms_a = expand_pattern(data["termination_name_a"]) terms_b = expand_pattern(data["termination_name_b"]) if len(terms_a) != len(terms_b): return self.log_failure( f'Mismatched number of ports: {len(terms_a)} (A) versus {len(terms_b)} (B)' ) labels = expand_pattern(data["cable_label"]) if len(labels) == 1: labels = [labels[0] for i in range(len(terms_a))] elif len(labels) != len(terms_a): return self.log_failure( f'Mismatched number of labels: {len(labels)} labels versus {len(terms_a)} ports' ) for i in range(len(terms_a)): term_a = [x for x in ports_a if x.name == terms_a[i]] if len(term_a) != 1: self.log_failure( f'Unable to find "{terms_a[i]}" in {data["termination_type_a"]} on device A ({device_a.name})' ) continue term_b = [x for x in ports_b if x.name == terms_b[i]] if len(term_b) != 1: self.log_failure( f'Unable to find "{terms_b[i]}" in {data["termination_type_b"]} on device B ({device_b.name})' ) continue cable = Cable( termination_a=term_a[0], termination_b=term_b[0], type=data["cable_type"], status=data["cable_status"], tenant=data["cable_tenant"], label=labels[i], color=data["cable_color"], length=data["cable_length"], length_unit=data["cable_length_unit"], ) try: with transaction.atomic(): cable.full_clean() cable.save() cable.tags.set(data["cable_tags"]) except Exception as e: self.log_failure( f'Unable to connect {device_a.name}:{terms_a[i]} to {device_b.name}:{terms_b[i]}: {e}' ) continue self.log_success( f'Created cable from {device_a.name}:{terms_a[i]} to {device_b.name}:{terms_b[i]}' )
class CableUpdate(Script): """Script that can be used to auto generate interfaces from devices using NAPALM Args: Script (:obj:`extras.script.Script`): Netbox Script object that is needed for a class to be recognized as one """ class Meta: """Special class that is used for defining script information""" name = "Cable update" description = "Script that updates cable connections between network interfaces" commit_default = False device_name = StringVar( label="Devices regex", default="(r6|v22)-leaf((1\d*|[2-9])|1)", description= "Regex that will be used to select devices that will have their interfaces cabled", required=True, ) non_existent = BooleanVar( label="Non-existent devices", default=False, description= "If the warnings about non-existent devices/interfaces will be shown", ) def run(self, data, commit: bool): """The main method of the script that will be run when pressing the Run Script button 1. Grabs the data from Netbox about devices containing the devices by regex input by the user 2. Loops through the devices, and makes a NAPALM `get_lldp_neighbors` call to gather local and their remote interfaces 3. Loops through all LLDP provided local interfaces. 4. If a mac_address is any kind of empty or null, it makes sure to set it to python None 5. Makes sure the local interface can be found in Netbox 6. If a cable is found, it moves to the next iteration and if the remote interface is correct, goes to next iteration 7. If a cable is found but the remote interface is not the same, makes sure to mark the cable for deletion (Netbox only allows 1 to 1 cable connections) 8. Makes sure the remote device and interface can be found in Netbox 9. Deletes the cable if one was found 10. Creates a new cable using the local and remote interface pair 11. Calls function to remove cables no longer found in LLDP Args: data (dict): a dict that has the variables for user input. Defined using class variables commit (bool): a bool that determines to commit or not to commit the changes to database (since Netbox automatically reverts database changes on commit = False, we don't use it) Returns: str: output for the Output tab """ output = "" devices = Device.objects.filter(name__regex=data["device_name"]) for device in devices: napalm_lldp_neighbors = napalm_call("get_lldp_neighbors", device.id, self.request) lldp_interface_names = [] for local_interface_name, remote_interface in napalm_lldp_neighbors.items( ): remote_device_name = remote_interface[0]["hostname"] remote_interface_name = remote_interface[0]["port"] lldp_interface_names.append(local_interface_name) try: netbox_local_interface = Interface.objects.get( device=device.id, name=local_interface_name) except Interface.DoesNotExist: if data["non_existent"]: self.log_warning( f"""`[{device.name}]` Local interface **({local_interface_name})** for device **({device.name})** could not be found in Netbox. Please run the interface update script to have all the interfaces for a device generated""" ) continue delete_cable = False if netbox_local_interface.cable is not None: if (netbox_local_interface._cable_peer.name == remote_interface_name and netbox_local_interface._cable_peer.device.name == remote_device_name): # Cable already exists so we continue on continue else: # A Netbox cable is connected but not to the interface the device is reporting delete_cable = True # Don't delete the cable immediately as the remote interface might not be there try: remote_device = Device.objects.get(name=remote_device_name) netbox_remote_interface = Interface.objects.get( device=remote_device.id, name=remote_interface_name) except Device.DoesNotExist: if data["non_existent"]: self.log_info( f"""`[{device.name}]` Remote device **({remote_device_name})** could not be found in Netbox Create the device in Netbox and add the **({remote_interface_name})** interface for a cable to be connected""" ) continue except Interface.DoesNotExist: if data["non_existent"]: self.log_info( f"""`[{device.name}]` Remote Interface **({remote_interface_name})** for device **({remote_device_name})** could not be found in Netbox Create the interface in Netbox for a cable to be connected""" ) continue if delete_cable: # Delete a cable that doesn't exist netbox_local_interface.cable.delete() self.log_success( f"`[{device.name}]` Deleting a no longer existing cable: " f"**{netbox_local_interface.name}** " f"({netbox_local_interface.device.name})" " <-> " f"**{netbox_local_interface._cable_peer.name}** " f"({netbox_local_interface._cable_peer.device.name})") # Create a new cable dcim_interface_type = ContentType.objects.get( app_label="dcim", model="interface") new_cable = Cable( termination_a_type=dcim_interface_type, termination_a_id=netbox_local_interface.id, termination_b_type=dcim_interface_type, termination_b_id=netbox_remote_interface.id, ) new_cable.save() self.log_success(f"`[{device.name}]` Creating a new cable: " f"**{netbox_local_interface.name}** " f"({netbox_local_interface.device.name})" " <-> " f"**{netbox_remote_interface.name}** " f"({netbox_remote_interface.device.name})") self.remove_old_cables(device, lldp_interface_names) return output def remove_old_cables(self, device, lldp_interface_names: List[str]): """Task that will remove cables that are no longer connected based on LLDP data 1. Grabs all the interfaces from the specific device, if it has a Netbox cable attached and if it wasn't one of the local interfaces returned from LLDP 2. Loops through them and deletes them Args: device (:obj:`dcim.models.Device`): A Netbox device model of the device that will be checked for old cables lldp_interface_names (:obj:`List[str]`): A List of local intefaces that have a cable attached from LLDP """ old_cable_interfaces = Interface.objects.filter( device=device.id, cable__isnull=False).exclude(name__in=lldp_interface_names) for oc_interface in old_cable_interfaces: try: old_cable = oc_interface.cable except: # The cable could have already been deleted if it was plugged in the same device continue old_cable.delete() self.log_success(f"`[{device.name}]` Deleting an old cable: " f"**{old_cable.termination_a.name}** " f"({old_cable.termination_a.device.name})" " <-> " f"**{old_cable.termination_b.name}** " f"({old_cable.termination_b.device.name})")
class NewVM(Script): class Meta: name = "New VM" description = "Create a new VM" field_order = [ 'vm_name', 'dns_name', 'primary_ip4', 'primary_ip6', #'vrf', 'role', 'status', 'cluster', #'tenant', 'platform', 'interface_name', 'mac_address', 'vcpus', 'memory', 'disk', 'comments', 'pve_host' ] vm_name = StringVar(label="VM name") dns_name = StringVar(label="DNS name", required=False) primary_ip4 = IPAddressWithMaskVar(label="IPv4 address") # primary_ip6 = IPAddressWithMaskVar(label="IPv6 address", required=False) # vrf = ObjectVar(VRF.objects, required=False) role = ObjectVar(DeviceRole.objects.filter(vm_role=True), required=False, default=8) status = ChoiceVar(VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE) # cluster = ObjectVar(Cluster.objects) # tenant = ObjectVar(Tenant.objects, required=False) # platform = ObjectVar(Platform.objects, required=False) interface_name = StringVar(default="eth0") # mac_address = StringVar(label="MAC address", required=False) vcpus = IntegerVar(label="VCPUs", required=True) memory = IntegerVar(label="Memory (MB)", required=True) disk = IntegerVar(label="Disk (GB)", required=True) comments = TextVar(label="Comments", required=False) # pve_host = ObjectVar(Device.objects.filter(cluster__name='Newtelco Cluster'), label="Proxmox Host", required=True) PVE_DEVICES = (('nt-pve', 'nt-pve'), ('nt-pve2', 'nt-pve2'), ('nt-pve5', 'nt-pve5'), ('nt-pve6', 'nt-pve6')) pve_host = ChoiceVar(choices=PVE_DEVICES) # pve_host_ip=Device.objects.filter(name=pve_host) def run(self, data, commit): pve_host = Device.objects.filter(name=data["pve_host"]) vm = VirtualMachine( name=data["vm_name"], role=data["role"], status=data["status"], vcpus=data["vcpus"], memory=data["memory"], disk=data["disk"], comments=data["comments"], ) if commit: vm.save() interface = Interface( name=data["interface_name"], type=InterfaceTypeChoices.TYPE_VIRTUAL, virtual_machine=vm, ) if commit: interface.save() def add_addr(addr, expect_family): if not addr: return if addr.version != expect_family: raise RuntimeError("Wrong family for %r" % a) try: a = IPAddress.objects.get( address=addr, family=addr.version, vrf=data.get("vrf"), ) result = "Assigned" except ObjectDoesNotExist: a = IPAddress( address=addr, family=addr.version, vrf=data.get("vrf"), ) result = "Created" a.status = IPAddressStatusChoices.STATUS_ACTIVE a.dns_name = data["dns_name"] if a.interface: raise RuntimeError("Address %s is already assigned" % addr) a.interface = interface a.tenant = data.get("tenant") a.save() self.log_info("%s IP address %s %s" % (result, a.address, a.vrf or "")) setattr(vm, "primary_ip%d" % a.family, a) def connect_pve(addr): self.log_info(addr) proxmox = ProxmoxAPI( addr, user='******', token_name='nb1', token_value='0cf6ab07-ff7e-41a3-80e4-e09e7fea6c7d', verify_ssl=False) self.log_success(proxmox.nodes.get()) # self.log_info(data["pve_host"]) pve_ip = str(pve_host.get().primary_ip4)[:-3] self.log_info(pve_ip) connect_pve(pve_ip) if commit: add_addr(data["primary_ip4"], 4) # add_addr(data["primary_ip6"], 6) vm.save() self.log_success("Created VM %s" % vm.name) else: self.log_success("Dry-run Success - Created VM %s" % vm.name)
class NewVM(Script): class Meta: name = "New VM" description = "Create a new VM" vm_name = StringVar(label="VM name") dns_name = StringVar(label="DNS name", required=False) vm_tags = MultiObjectVar(model=Tag, label="VM tags", required=False) primary_ip4 = IPAddressWithMaskVar(label="IPv4 address") #primary_ip4_tags = MultiObjectVar(model=Tag, label="IPv4 tags", required=False) primary_ip6 = IPAddressWithMaskVar(label="IPv6 address", required=False) #primary_ip6_tags = MultiObjectVar(model=Tag, label="IPv6 tags", required=False) #vrf = ObjectVar(model=VRF, required=False) role = ObjectVar(model=DeviceRole, query_params=dict(vm_role=True), required=False) status = ChoiceVar(VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE) cluster = ObjectVar(model=Cluster) tenant = ObjectVar(model=Tenant, required=False) platform = ObjectVar(model=Platform, required=False) interface_name = StringVar(default="eth0") mac_address = StringVar(label="MAC address", required=False) vcpus = IntegerVar(label="VCPUs", required=False) memory = IntegerVar(label="Memory (MB)", required=False) disk = IntegerVar(label="Disk (GB)", required=False) comments = TextVar(label="Comments", required=False) def run(self, data, commit): vm = VirtualMachine( name=data["vm_name"], role=data["role"], status=data["status"], cluster=data["cluster"], platform=data["platform"], vcpus=data["vcpus"], memory=data["memory"], disk=data["disk"], comments=data["comments"], tenant=data.get("tenant"), ) vm.full_clean() vm.save() vm.tags.set(data["vm_tags"]) vminterface = VMInterface( name=data["interface_name"], mac_address=data["mac_address"], virtual_machine=vm, ) vminterface.full_clean() vminterface.save() def add_addr(addr, family): if not addr: return if addr.version != family: raise RuntimeError(f"Wrong family for {a}") try: a = IPAddress.objects.get( address=addr, vrf=data.get("vrf"), ) result = "Assigned" except ObjectDoesNotExist: a = IPAddress( address=addr, vrf=data.get("vrf"), ) result = "Created" a.status = IPAddressStatusChoices.STATUS_ACTIVE a.dns_name = data["dns_name"] if a.assigned_object: raise RuntimeError(f"Address {addr} is already assigned") a.assigned_object = vminterface a.tenant = data.get("tenant") a.full_clean() a.save() #a.tags.set(data[f"primary_ip{family}_tags"]) self.log_info(f"{result} IP address {a.address} {a.vrf or ''}") setattr(vm, f"primary_ip{family}", a) add_addr(data["primary_ip4"], 4) add_addr(data["primary_ip6"], 6) vm.full_clean() vm.save() self.log_success( f"Created VM [{vm.name}](/virtualization/virtual-machines/{vm.id}/)" )