fromextras.scriptsimportScript,ObjectVar,StringVarfromipam.modelsimportPrefix,IPAddressclassReserveIPv4(Script):classMeta:# (1)!name="Reserve IPv4"description="Find the first free IPv4 in the selected prefix and create an IPAddress"prefix=ObjectVar(# (2)!model=Prefix,required=True,label="IPv4 Prefix",description="Select the prefix to reserve the next free address from.",)dns_name=StringVar(# (3)!required=False,label="DNS name (optional)",description="Stored as DNS name, e.g., 'web01'.")def_pick_free_ip(self,prefix):# (4)!forhostinprefix.prefix.iter_hosts():ip_str=str(host)ifIPAddress.objects.filter(address__startswith=f"{ip_str}/").exists():self.log_info(f"{ip_str} -> in use (NetBox)")continueself.log_success(f"{ip_str} -> free, will be used")returnip_strreturnNonedefrun(self,data,commit):# (5)!prefix=data["prefix"]dns_name=(data.get("dns_name")or"").strip()ifprefix.prefix.version!=4:# (6)!self.log_failure(f"{prefix.prefix} is not an IPv4 prefix.")return"Aborted."ip_plain=self._pick_free_ip(prefix)# (7)!ifnotip_plain:self.log_failure(f"No free IP found in {prefix.prefix}.")return"Aborted."ip_with_mask=f"{ip_plain}/{prefix.prefix.prefixlen}"ip_obj=IPAddress(address=ip_with_mask,dns_name=dns_nameor"")# (8)!ifcommit:ip_obj.save()self.log_success(f"IPAddress created: {ip_obj.address} (dns_name='{ip_obj.dns_name}')")else:self.log_info(f"[Dry-Run] would create: {ip_with_mask} (dns_name='{dns_name}')")returnf"Prefix: {prefix.prefix}, IP: {ip_with_mask}, Commit={commit}"
Script metadata displayed in the NetBox UI.
Input field for selecting the prefix.
Optional input field for a hostname → stored as DNS name.
Helper method scans the prefix for the first unused IPv4.
Main function: reads inputs, validates, and drives the process.
Validation step: ensure only IPv4 prefixes are used.
Calls the helper to get the next free IP, otherwise aborts.
Creates and saves the IPAddress when Commit is on, or logs what would happen.