From 0ab0fa6f996baf9b17812647ed0566a815f2a3b0 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 11:03:59 -0500 Subject: [PATCH 01/12] remove powershell script --- other/Sync-Vmware.ps1 | 544 ------------------------------------------ 1 file changed, 544 deletions(-) delete mode 100644 other/Sync-Vmware.ps1 diff --git a/other/Sync-Vmware.ps1 b/other/Sync-Vmware.ps1 deleted file mode 100644 index 5bc3a85..0000000 --- a/other/Sync-Vmware.ps1 +++ /dev/null @@ -1,544 +0,0 @@ -# -# NOTE: -# This script works with Netbox v2.9. It does not work with v2.10 in its current state. -# - -#Requires -Version 5 -<# -.SYNOPSIS - Synchronize Netbox Virtual Machines from VMware vCenter. -.DESCRIPTION - The Sync-Netbox cmdlet uses the Django Swagger REST API included in Netbox and VMware PowerCLI to synchronize data - from vCenter to Netbox. - Function skeleton adapted from https://gist.github.com/9to5IT/9620683 -.PARAMETER Token - Netbox REST API token -.NOTES - Version: 1.2 - Author: Joe Wegner - Original source: https://github.com/jwegner89/netbox-utilities - Creation Date: 2018-02-08 - Purpose/Change: Initial script development - License: GPLv3 - Note that this script relies heavily on the PersistentID field in vCenter, as that will uniquely identify the VM - You will need to create a vcenter_persistent_id custom field on your VM object in Netbox for this to work properly - removed PowerCLI requires header due to loading error - Updated to support Netbox v2.9 - #Requires -Version 5 -Modules VMware.PowerCLI -#> - -#---------------------------------------------------------[Initialisations]-------------------------------------------------------- - -#Set Error Action to Silently Continue -#$ErrorActionPreference = "SilentlyContinue" -# allow verbose messages to be recorded in transcript -$VerbosePreference = "Continue" - -#----------------------------------------------------------[Declarations]---------------------------------------------------------- - -# store common paths in variables for URI creation -# update for your Netbox instance -$URIBase = "https://netbox.example.com/api" -$ClustersPath = "/virtualization/clusters" -$VirtualMachinesPath = "/virtualization/virtual-machines" -$PlatformsPath = "/dcim/platforms" -$InterfacesPath = "/virtualization/interfaces" -$IPAddressesPath = "/ipam/ip-addresses" - -#-----------------------------------------------------------[Functions]------------------------------------------------------------ - -function Sync-Netbox { - param ( - [parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [String] - $Token - ) - - begin { - # setup headers for Netbox API calls - $TokenHeader = "Token " + $Token - $Headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" - $Headers.Add("Accept", "application/json") - $Headers.Add("Authorization", $TokenHeader) - - # first, we will clear out any VMs that are in Netbox but no longer in vCenter - - # get all VMs in vCenter and collect their persistent IDs - $VMs = Get-VM - $VMCount = "Retrieved $VMs.count from vCenter" - Write-Verbose $VMCount - $vCenterPersistentIDs = @() - foreach ($VM in $VMs) { - $vCenterPersistentIDs += $VM.PersistentID - } - - # retrieve all VMs from Netbox - $URI = $URIBase + $VirtualMachinesPath + "/?limit=0" - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - #ConvertTo-JSON $Response | Write-Verbose - - # check each Netbox VM against list from vCenter and delete if not present - foreach ($VM in $Response.Results) { - $PersistentID = $VM.custom_fields.vcenter_persistent_id - if ($vCenterPersistentIDs -notcontains $PersistentID) { - # Delete old VM from Netbox inventory - $NetboxID = $VM.ID - $URI = $URIBase + $VirtualMachinesPath + "/" + $NetboxID + "/" - $Response = Invoke-RESTMethod -Method DELETE -Headers $Headers -ContentType "application/json" -URI $URI - #ConvertTo-JSON $Response | Write-Verbose - $Message = "Deleting " + $VM.Name - Write-Verbose $Message - } - } - - # Create mapping of vCenter OSFullName to Netbox platform IDs - $NetboxPlatforms = @{} - $URI = $URIBase + $PlatformsPath + "/?limit=0" - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - - foreach ($Platform in $Response.Results) { - $NetboxPlatforms[$Platform.Name] = $Platform.ID - } - - # Create mapping of vCenter Cluster Names to Netbox cluster IDs - $NetboxClusters = @{} - $URI = $URIBase + $ClustersPath + "/?limit=0" - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - - foreach ($Cluster in $Response.Results) { - $NetboxClusters[$Cluster.Name] = $Cluster.ID - } - - # retrieve all clusters from vCenter - $Clusters = Get-Cluster - - # iterate through the clusters - foreach ($Cluster in $Clusters) { - # Retrive Netbox ID for cluster - $ClusterID = $NetboxClusters[$Cluster.Name] - - # Retrieve all VMs in cluster - $VMs = Get-VM -Location $Cluster - - # Iterate through each VM object - foreach ($VM in $VMs) { - # Query Netbox for VM using persistent ID from vCenter - $URI = $URIBase + $VirtualMachinesPath + "/?q=&cf_vcenter_persistent_id=" + $VM.PersistentID - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - - # A successful request will always have a results dictionary, though it may be empty - $NetboxInfo = $Response.Results - - # Retrieve Netbox ID for VM if available - $NetboxID = $NetboxInfo.ID - - # Create object to hold this VM's attributes for export - $vCenterInfo = @{} - - if ($Response.Count -eq 0) { - # A machine with this PersistentID does not exist yet, or was created manually - $vCenterInfo["custom_fields"] = @{ - "vcenter_persistent_id" = $VM.PersistentID - } - } elseif ($Response.Count -gt 1) { - # duplicate entries exit / something went wrong - Write-Warning -Message [String]::Format("{0} has {1} entries in Netbox, skipping...", $VM.Name, $Response.Count) - continue - } - # don't need to consider case where we have count -eq 1 since we already have the info set - # and count *shouldn't* be negative... - - # calculate values for comparison - $vCPUs = $VM.NumCPU - $Disk = [Math]::Round($VM.ProvisionedSpaceGB).ToString() - - # Match up VMHost with proper Netbox Cluster - $VMHost = Get-VMHost -VM $VM | Select-Object -Property Name - # Our VM hosts have prefixes that match the cluster name, so adjust as needed - if ($VMHost -match "CLUSTER1") { - $ClusterID = $NetboxClusters["CLUSTER1"] - } elseif ($VMHost -match "CLUSTER2") { - $ClusterID = $NetboxClusters["CLUSTER2"] - } - if ($NetboxInfo.Cluster) { - if ($NetboxInfo.Cluster.ID -ne $ClusterID) { $vCenterInfo["cluster"] = $ClusterID } - } else { - $vCenterInfo["cluster"] = $ClusterID - } - - if ($NetboxInfo.vCPUs -ne $vCPUs) { $vCenterInfo["vcpus"] = $vCPUs } - if ($NetboxInfo.Memory -ne $VM.MemoryMB) { $vCenterInfo["memory"] = $VM.MemoryMB } - if ($NetboxInfo.Disk -ne $Disk) { $vCenterInfo["disk"] = $Disk } - - if ($VM.PowerState -eq "PoweredOn") { - # Netbox status ID 1 = Active - if ($NetboxInfo.Status) { - if ($NetboxInfo.Status.Label -ne "Active") { $vCenterInfo["status"] = 1 } - } else { - $vCenterInfo["status"] = 1 - } - } else { - # VM is not powered on - # Netbox status ID 0 = Offline - if ($NetboxInfo.Status) { - if ($NetboxInfo.Status.Label -eq "Active") { $vCenterInfo["status"] = 0 } - } else { - $vCenterInfo["status"] = 0 - } - } - - # Retrieve guest information - $Guest = Get-VMGuest -VM $VM - - # canonicalize to lower case hostname - if ($Guest.Hostname) { - $Hostname = $Guest.Hostname.ToLower() - # Convert Guest OS name to Netbox ID - if ($NetboxInfo.Name -ne $Hostname) { $vCenterInfo["name"] = $Hostname } - } else { - # Use VM inventory name as a placeholder - uniquely identified by PersistentID - $Name = $VM.Name.ToLower() - if ($NetboxInfo.Name -ne $Name) { $vCenterInfo["name"] = $Name } - } - - # Lookup Netbox ID for platform - if ($Guest.OSFullName) { - $Platform = $Guest.OSFullName - # check that this platform exists in Netbox - if ($NetboxPlatforms.ContainsKey($Platform)) { - $PlatformID = $NetboxPlatforms[$Platform] - if ($NetboxInfo.Platform) { - if ($NetboxInfo.Platform.ID -ne $PlatformID) { $vCenterInfo["platform"] = $PlatformID } - } else { - $vCenterInfo["platform"] = $PlatformID - } - } else { - # platform not present in Netbox, need to create it - - # strip out bad character for friendly URL name - $Slug = $Platform.ToLower() - $Slug = $Slug -Replace "\s","-" - $Slug = $Slug -Replace "\.","" - $Slug = $Slug -Replace "\(","" - $Slug = $Slug -Replace "\)","" - $Slug = $Slug -Replace "/","" - Write-Verbose "Creating new platform:" - $PlatformInfo = @{ - "name" = $Platform - "slug" = $Slug - } - $PlatformJSON = ConvertTo-JSON $PlatformInfo - Write-Verbose $PlatformJSON - $URI = $URIBase + $PlatformsPath + "/" - $Response = Invoke-RESTMethod -Method POST -Headers $Headers -ContentType "application/json" -Body $PlatformJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - # add new id into platforms hashtable - $NetboxPlatforms[$Response.Name] = $Response.ID - } - } - - # Store results with defaults from previous request - $NetboxVM = $NetboxInfo - # Check if we have any changes to submit - if ($vCenterInfo.Count -gt 0) { - # Create JSON of data for POST/PATCH - $vCenterJSON = ConvertTo-JSON $vCenterInfo - if ($NetboxID) { - # VM already exists in Netbox, so update with any new info - Write-Verbose "Updating Netbox VM:" - Write-Verbose $vCenterJSON - $URI = $URIBase + $VirtualMachinesPath + "/$NetboxID/" - $Response = Invoke-RESTMethod -Method PATCH -Headers $Headers -ContentType "application/json" -Body $vCenterJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $NetboxVM = $Response - } else { - Write-Verbose "Creating new VM in Netbox:" - Write-Verbose $vCenterJSON - # VM does not exist in Netbox, so create new VM entry - $URI = $URIBase + $VirtualMachinesPath + "/" - $Response = Invoke-RESTMethod -Method POST -Headers $Headers -ContentType "application/json" -Body $vCenterJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $NetboxVM = $Response - } - } else { - $VMName = $NetboxInfo.Name - Write-Verbose "VM $VMName already exists in Netbox and no changes needed" - } - $NetboxID = $NetboxVM.ID - - # Create list to store collected NIC objects - $vCenterNICs = @() - if ($Guest.NICs) { - foreach ($NICInfo in $Guest.NICs) { - foreach ($NIC in $NICInfo) { - # Check that the device name exists - if ($NIC.Device.Name) { - # Process each IP in array - $IPs = @() - foreach ($IP in $NIC.IPAddress) { - $vCenterIP = [IPAddress]$IP - # Create temporary variable for IP - $TempIP = "127.0.0.1/32" - # Apply appropriate prefix for IP version - $AddressType = $vCenterIP | Select-Object -Property AddressFamily - if ([String]$AddressType -eq "@{AddressFamily=InterNetwork}") { - $TempIP = $IP + "/32" - } elseif ([String]$AddressType -eq "@{AddressFamily=InterNetworkV6}") { - $TempIP = $IP + "/128" - } else { - Write-Warning -Message [String]::Format("Address {0} is of type {1}, skipping...", $IP, $AddressType) - continue - } - $IPs += $TempIP - } - - $Interface = @{ - "enabled" = $NIC.Connected - "addresses" = $IPs - "name" = $NIC.Device.Name - "mac_address" = $NIC.MACAddress - "virtual_machine" = $NetboxID - } - $vCenterNICs += $Interface - } - } - } - } - - # Retrieve info on NICs present in Netbox - $URI = $URIBase + $InterfacesPath + "/?virtual_machine_id=$NetboxID" - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $NetboxNICs = $Response.Results - - # 3 conditions we're interested in: - # 1. Interface is in Netbox and not vCenter -> delete interface from Netbox - # 2. Interface is in vCenter and not Netbox -> create new Netbox interface - # 3. Interface is in both -> update info if necessary - - # create list of MACs for Netbox - $NetboxMACs = @() - foreach ($NetboxNIC in $NetboxNICs) { - $NetboxMACs += $NetboxNIC.mac_address - } - # create list of MACs for vCenter - $vCenterMACs = @() - foreach ($vCenterNIC in $vCenterNICs) { - $vCenterMACs += $vCenterNIC.mac_address - } - # Delete any interfaces in Netbox that are not present in vCenter - foreach ($NetboxNIC in $NetboxNICs) { - $vCenterContains = $vCenterMACs -contains $NetboxNIC.mac_address - if (-Not $vCenterContains) { - # Netbox interface does not match vCenter's, so remove it - $Message = "Deleting Netbox interface " + $NetboxNIC.name - Write-Verbose $Message - $URI = $URIBase + $InterfacesPath + "/" + $NetboxNIC.id + "/" - $Response = Invoke-RESTMethod -Method DELETE -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - } - } - # create hashtable mapping Netbox interface IDs to IP lists as we process them - $IPAssignments = @{} - foreach ($vCenterNIC in $vCenterNICs) { - $NetboxContains = $NetboxMACs -contains $vCenterNIC.mac_address - if (-Not $NetboxContains) { - # Interface is in vCenter but not Netbox, so create new interface in Netbox with details from vCenter - $Message = "Creating Netbox interface " + $vCenterNIC.name - Write-Verbose $Message - $vCenterNICJSON = ConvertTo-JSON $vCenterNIC - $URI = $URIBase + $InterfacesPath + "/" - $Response = Invoke-RESTMethod -Method POST -Headers $Headers -ContentType "application/json" -Body $vCenterNICJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $NIC = $Response - # Store interface ID - $NICID = [String]$NIC.ID - # Get list of addresses from hash table and delete - $IPs = $vCenterNIC.addresses - $vCenterNIC.Remove["addresses"] - # store IP list in Netbox interface ID to IP arrary hashtable - $IPAssignments[$NICID] = $IPs - } else { - # NIC exists in both, now identify which - foreach ($NetboxNIC in $NetboxNICs) { - $Message = [String]::Format("Comparing Netbox interface '{0}' and vCenter interface '{1}'", $NetboxNIC.name, $vCenterNIC.name) - Write-Verbose $Message - if ($vCenterNIC.mac_address -eq $NetboxNIC.mac_address) { - # Interfaces match, so only need to update if necessary - $NICUpdate = @{} - # Store interface ID - $NICID = [String]$NetboxNIC.id - # Currently we don't want to overwrite any custom name (e.g. from Ansible or manual) - #If ($NetboxNIC.Name -ne $vCenterNIC.Name) { $NICUpdate["name"] = $vCenterNIC.Name } - if ($NetboxNIC.enabled -ne $vCenterNIC.enabled) { $NICUpdate["enabled"] = $vCenterNIC.enabled } - # Get list of addresses from hash table and delete - $IPs = $vCenterNIC.addresses - $vCenterNIC.Remove["addresses"] - # store IP list in Netbox interface ID to IP arrary hashtable - $IPAssignments[$NICID] = $IPs - if ($NICUpdate.count -gt 0) { - # only want to patch if there is anything that needs to change - $Message = "Updating Netbox interface " + $NetboxNIC.name - Write-Verbose $Message - $NICUpdateJSON = ConvertTo-JSON $NICUpdate - $URI = $URIBase + $InterfacesPath + "/" + $NetboxNIC.id + "/" - $Response = Invoke-RESTMethod -Method PATCH -Headers $Headers -ContentType "application/json" -Body $NICUpdateJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - } - } - } - } - } - - ConvertTo-JSON $IPAssignments | Write-Verbose - - # situations to consider: - # 1. IP is assigned in Netbox and not configured in vCenter -> change IP status to "deprecated" in Netbox (just in case NIC was disabled, etc) - # 2. IP is configured in vCenter and not present in Netbox -> create new Netbox IP and assign to Netbox interface - # 3. IP is configured in both -> set to active in Netbox if it is not already and confirm interface - - # Create list of all IPs configured on vCenter VM - $ConfiguredIPs = @() - foreach ($InterfaceID in $IPAssignments.Keys) { - $ConfiguredIPs += $IPAssignments[$InterfaceID] - } - - # Retrieve all IPs assigned to virtual machine in Netbox - # helpful: https://groups.google.com/forum/#!topic/netbox-discuss/iREz7f9-bN0 - $URI = $URIBase + $IPAddressesPath + "/?virtual_machine_id=" + $NetboxID - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $NetboxIPs = $Response.Results - - # iterate through and store results in array - $AssignedIPs = @() - foreach ($NetboxIP in $NetboxIPs) { - $IP = $NetboxIP.address - if ($ConfiguredIPs -contains $IP) { - # vCenter VM has IP configured, so keep it - $AssignedIPs += $IP.address - } else { - # IP assigned in Netbox but not configured in vCenter, so set to "deprecated" - $Date = Get-Date -Format d - $Description = [String]::Format("{0} - inactive {1}", $NetboxVM.Name, $Date) - $IPPatch = @{ - "status" = "deprecated" - "description" = $Description - } - $IPPatchJSON = ConvertTo-JSON $IPPatch - $URI = $URIBase + $IPAddressesPath + "/" + $NetboxIP.id + "/" - $Response = Invoke-RESTMethod -Method PATCH -Headers $Headers -ContentType "application/json" -Body $IPPatchJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - } - } - - # create or update IPs for each interface as needed - foreach ($InterfaceID in $IPAssignments.Keys) { - # get list of IPs from vCenter - $vCenterIPs = $IPAssignments[$InterfaceID] - # Iterate through this interfaces's IPs and check if they are configured in Netbox - foreach ($vCenterIP in $vCenterIPs) { - if ($AssignedIPs -notcontains $vCenterIP) { - # IP not assigned to VM in Netbox, but need to check if it exists already - $URI = $URIBase + $IPAddressesPath + "/?q=" + $vCenterIP - $Response = Invoke-RESTMethod -Method GET -Headers $Headers -ContentType "application/json" -URI $URI - ConvertTo-JSON $Response | Write-Verbose - if ($Response.count -gt 0) { - # IP exists in Netbox, need to assign it to Netbox VM - $NetboxIP = $Response.results - # create details for patching IP in Netbox - $Description = $NetboxVM.Name - $IPPatch = @{ - "status" = "active" - "description" = $Description - "vminterface" = $InterfaceID - } - $IPPatchJSON = ConvertTo-JSON $IPPatch - $URI = $URIBase + $IPAddressesPath + "/" + $NetboxIP.id + "/" - $Response = Invoke-RESTMethod -Method PATCH -Headers $Headers -ContentType "application/json" -Body $IPPatchJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $AssignedIPs += $NetboxIP.address - } else { - # IP does not exist in Netbox, so we need to create it - $Description = $NetboxVM.Name - $IPPost = @{ - "address" = $vCenterIP - "status" = "active" - "description" = $Description - "vminterface" = $InterfaceID - } - $IPPostJSON = ConvertTo-JSON $IPPost - $URI = $URIBase + $IPAddressesPath + "/" - $Response = Invoke-RESTMethod -Method POST -Headers $Headers -ContentType "application/json" -Body $IPPostJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - $AssignedIPs += $Response.address - } - } else { - # IP exists in Netbox, make sure status is "Active" and that the interface is correct - # Search through Netbox IPs to find corresponding IP - foreach ($NetboxIP in $NetboxIPs) { - if ($vCenterIP -eq $NetboxIP.address) { - # we've found the corresponding entry so determine what data needs to be updated - $IPPatch = @{} - # check that the IP is on the correct interface - if ($NetboxIP.interface -ne $InterfaceID) { $IPPatch["vminterface"] = $InterfaceID } - # check that the status is active - if ($NetboxIP.status -ne "active") { $IPPatch["status"] = "active" } - # check that the description contains the hostname - $VMShortName = $NetboxVM.Name.Split('.')[0] - $DescriptionMatch = $NetboxIP.description -match $VMShortName - if (-not $DescriptionMatch) { - $IPPatch["status"] = "active" - } - # Only submit patches if anything has changed - if ($IPPatch.count -gt 0) { - $IPPatchJSON = ConvertTo-JSON $IPPatch - $URI = $URIBase + $IPAddressesPath + "/" + $NetboxIP.id + "/" - $Response = Invoke-RESTMethod -Method PATCH -Headers $Headers -ContentType "application/json" -Body $IPPatchJSON -URI $URI - ConvertTo-JSON $Response | Write-Verbose - } - } - } - } - } - } - } - } - } - - process { - } - - end { - } -} - - -#-----------------------------------------------------------[Execution]------------------------------------------------------------ - -# setup logging to file -$Date = Get-Date -UFormat "%Y-%m-%d" -$LogPath = "D:\logs\" + $Date + "_vcenter_netbox_sync.log" -Start-Transcript -Path $LogPath -# import the PowerCLI module -Import-Module VMware.PowerCLI -# Make sure that you are connected to the vCenter servers before running this manually -$Credential = Get-Credential -Connect-VIServer -Server vcenter.example.com -Credential $Credential - -# If running as a scheduled task, ideally you can use a service account -# that can login to both Windows and vCenter with the account's Kerberos ticket -# In that case, you can remove the -Credential from the above Connect-VIServer call - -# create your own token at your Netbox instance, e.g. https://netbox.example.com/user/api-tokens/ -# You may need to assign addtional user permissions at https://netbox.example.com/admin/auth/user/ -# since API permissions are not inherited from LDAP group permissions -$Token = "insert-token-generated-above" -Sync-Netbox -Token $Token -# If you want to see REST responses, add the Verbose flag -#Sync-Netbox -Verbose -Token $Token -Stop-Transcript From 0743b944253166d0f344e5968a39d882760022d9 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 11:04:14 -0500 Subject: [PATCH 02/12] update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ef6d021..6aa16f7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Community Reports + A collection of community submitted and maintained NetBox reports and custom scripts. ## Contributing + To contribute a new report, open a pull request. All reports go in the `reports/` directory. Each file should contain a summary of the report at the top of the file. To contribute a new script, open a pull request. All reports go in the `scripts/` directory. Each file should contain a summary of the script at the top of the file. @@ -9,7 +11,8 @@ To contribute a new script, open a pull request. All reports go in the `scripts/ Nothing in this repository comes with any explicit or implied warranty. For more information see [the license](LICENSE). ## Using Reports and Scripts + See the Netbox documentation for: -* [Reports](https://netbox.readthedocs.io/en/stable/additional-features/reports/) -* [Custom scripts](https://netbox.readthedocs.io/en/stable/additional-features/custom-scripts/) +* [Reports](https://netbox.readthedocs.io/en/stable/customization/reports/) +* [Custom scripts](https://netbox.readthedocs.io/en/stable/customization/custom-scripts//) From 3461e7bef82ca83c7ff77f27981961b7cb3e11cd Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 11:04:32 -0500 Subject: [PATCH 03/12] update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 894a44c..9df215a 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +# MISC +.DS_Store From 5f5ecccd9af4288c60d435d90a62a9676511571a Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 11:07:04 -0500 Subject: [PATCH 04/12] black and python import sorting --- .pre-commit-config.yaml | 33 ++++++ reports/circuit-reports/circuit_audits.py | 9 +- reports/circuit-reports/circuit_counts.py | 4 +- reports/dcim-reports/CheckCableLocality.py | 49 +++++--- reports/dcim-reports/CheckConsoleOOBPower.py | 25 +++-- reports/dcim-reports/CheckDeviceNaming.py | 21 ++-- reports/dcim-reports/DeviceRackingReport.py | 13 ++- reports/dcim-reports/RackGroupAssignment.py | 5 +- .../missing_device_type_components.py | 24 ++-- reports/ipam-reports/dns-reports.py | 9 +- reports/ipam-reports/ip-check-prefix.py | 46 +++++--- reports/ipam-reports/ip-duplicate.py | 32 ++++-- reports/ipam-reports/ip-primary-find.py | 75 ++++++++++--- reports/ipam-reports/ip-primary-missing.py | 23 +++- reports/misc/CustomFieldValue.py | 1 + reports/site/site_address.py | 22 ++-- reports/virtualization-reports/vm_counts.py | 3 +- scripts/add_device_type_components.py | 38 ++++--- scripts/create_vm.py | 51 ++++++--- scripts/geolocate_site.py | 64 +++++++---- scripts/multi_connect.py | 106 +++++++++++++----- 21 files changed, 462 insertions(+), 191 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c1b3962 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + name: Check YAML files + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt + rev: 0.1.0 + hooks: + - id: yamlfmt + args: [--mapping, '2', --sequence, '4', --offset, '2'] + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.26.3 + hooks: + - id: yamllint + name: Lint YAML files + args: [--format, parsable, --strict, -c=tests/yamllint.yaml] + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.30.0 + hooks: + - id: markdownlint + - repo: https://github.com/asottile/reorder_python_imports + rev: v2.7.1 + hooks: + - id: reorder-python-imports + - repo: https://github.com/ambv/black + rev: 22.1.0 + hooks: + - id: black + language_version: python3.9 diff --git a/reports/circuit-reports/circuit_audits.py b/reports/circuit-reports/circuit_audits.py index ac311da..405e3ab 100644 --- a/reports/circuit-reports/circuit_audits.py +++ b/reports/circuit-reports/circuit_audits.py @@ -1,7 +1,7 @@ import datetime -from circuits.models import Circuit from circuits.choices import CircuitStatusChoices +from circuits.models import Circuit from extras.reports import Report @@ -47,7 +47,8 @@ def test_check_deprovisioned(self): elif deprovision_date < one_month_ago: # older than 1 month self.log_warning( - circuit_obj, "Deprovisioned 1 month ago ({})".format(deprovision_date) + circuit_obj, + "Deprovisioned 1 month ago ({})".format(deprovision_date), ) else: @@ -55,7 +56,9 @@ def test_check_deprovisioned(self): def test_check_decommissioned(self): - decommed_circuits = Circuit.objects.filter(status=CircuitStatusChoices.STATUS_DECOMMISSIONED) + decommed_circuits = Circuit.objects.filter( + status=CircuitStatusChoices.STATUS_DECOMMISSIONED + ) today = datetime.datetime.utcnow().date() six_months_ago = today - datetime.timedelta(hours=MONTHS_IN_HOURS_6) diff --git a/reports/circuit-reports/circuit_counts.py b/reports/circuit-reports/circuit_counts.py index 7226dfd..e26a6ed 100644 --- a/reports/circuit-reports/circuit_counts.py +++ b/reports/circuit-reports/circuit_counts.py @@ -1,7 +1,7 @@ -from django.db.models import Count, Q - from dcim.choices import SiteStatusChoices from dcim.models import Site +from django.db.models import Count +from django.db.models import Q from extras.reports import Report diff --git a/reports/dcim-reports/CheckCableLocality.py b/reports/dcim-reports/CheckCableLocality.py index c5bed13..4163976 100644 --- a/reports/dcim-reports/CheckCableLocality.py +++ b/reports/dcim-reports/CheckCableLocality.py @@ -1,31 +1,52 @@ -from extras.reports import Report -from dcim.models import Cable, RearPort from dcim.choices import CableTypeChoices +from dcim.models import Cable +from dcim.models import RearPort +from extras.reports import Report CABLE_TYPES_OK_BETWEEN_RACKS = { CableTypeChoices.TYPE_DAC_PASSIVE, } + class CheckCableLocality(Report): description = "Warn on cables between racks, error on cables between sites" def test_cable_endpoints(self): - for cable in Cable.objects.prefetch_related('termination_a','termination_b').all(): - if not getattr(cable.termination_a, 'device', None) or not getattr(cable.termination_b, 'device', None): + for cable in Cable.objects.prefetch_related( + "termination_a", "termination_b" + ).all(): + if not getattr(cable.termination_a, "device", None) or not getattr( + cable.termination_b, "device", None + ): continue if cable.termination_a.device.site != cable.termination_b.device.site: - self.log_failure(cable, "Endpoints in different sites: {} ({}) and {} ({})".format( - cable.termination_a.device, cable.termination_a.device.site, - cable.termination_b.device, cable.termination_b.device.site, - )) + self.log_failure( + cable, + "Endpoints in different sites: {} ({}) and {} ({})".format( + cable.termination_a.device, + cable.termination_a.device.site, + cable.termination_b.device, + cable.termination_b.device.site, + ), + ) continue - if isinstance(cable.termination_a, RearPort) and isinstance(cable.termination_b, RearPort): + if isinstance(cable.termination_a, RearPort) and isinstance( + cable.termination_b, RearPort + ): self.log_success(cable) continue - if cable.termination_a.device.rack != cable.termination_b.device.rack and cable.type not in CABLE_TYPES_OK_BETWEEN_RACKS: - self.log_warning(cable, "Endpoints in different racks: {} ({}) and {} ({})".format( - cable.termination_a.device, cable.termination_a.device.rack, - cable.termination_b.device, cable.termination_b.device.rack, - )) + if ( + cable.termination_a.device.rack != cable.termination_b.device.rack + and cable.type not in CABLE_TYPES_OK_BETWEEN_RACKS + ): + self.log_warning( + cable, + "Endpoints in different racks: {} ({}) and {} ({})".format( + cable.termination_a.device, + cable.termination_a.device.rack, + cable.termination_b.device, + cable.termination_b.device.rack, + ), + ) continue self.log_success(cable) diff --git a/reports/dcim-reports/CheckConsoleOOBPower.py b/reports/dcim-reports/CheckConsoleOOBPower.py index 5e886da..b1abe39 100644 --- a/reports/dcim-reports/CheckConsoleOOBPower.py +++ b/reports/dcim-reports/CheckConsoleOOBPower.py @@ -1,10 +1,13 @@ from dcim.choices import DeviceStatusChoices -from dcim.models import ConsolePort, Device, PowerPort +from dcim.models import ConsolePort +from dcim.models import Device +from dcim.models import PowerPort from extras.reports import Report # This sample checks that every live device has a console connection, an out-of-band management connection, and two power connections # This sample is pulled directly from the example used at https://netbox.readthedocs.io/en/stable/additional-features/reports/ + class DeviceConnectionsReport(Report): description = "Validate the minimum physical connections for each device" @@ -12,16 +15,20 @@ def test_console_connection(self): # Check that every console port for every active device has a connection defined. active = DeviceStatusChoices.STATUS_ACTIVE - for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active): + for console_port in ConsolePort.objects.prefetch_related("device").filter( + device__status=active + ): if console_port.connected_endpoint is None: self.log_failure( console_port.device, - "No console connection defined for {}".format(console_port.name) + "No console connection defined for {}".format(console_port.name), ) elif not console_port.connection_status: self.log_warning( console_port.device, - "Console connection for {} marked as planned".format(console_port.name) + "Console connection for {} marked as planned".format( + console_port.name + ), ) else: self.log_success(console_port.device) @@ -37,12 +44,16 @@ def test_power_connections(self): if not power_port.connection_status: self.log_warning( device, - "Power connection for {} marked as planned".format(power_port.name) + "Power connection for {} marked as planned".format( + power_port.name + ), ) if connected_ports < 2: self.log_failure( device, - "{} connected power supplies found (2 needed)".format(connected_ports) + "{} connected power supplies found (2 needed)".format( + connected_ports + ), ) else: - self.log_success(device) \ No newline at end of file + self.log_success(device) diff --git a/reports/dcim-reports/CheckDeviceNaming.py b/reports/dcim-reports/CheckDeviceNaming.py index 07eb61a..6aeba07 100644 --- a/reports/dcim-reports/CheckDeviceNaming.py +++ b/reports/dcim-reports/CheckDeviceNaming.py @@ -7,13 +7,18 @@ # A modified John Anderson's NetBox Day 2020 Presentation by adding a check for all sites, not just LAX # All credit goes to @lampwins + class DeviceHostnameReport(Report): - description = "Verify each device conforms to naming convention Example: spin-(site_name)-0001 or leaf-(site_name)-0001-a" + description = "Verify each device conforms to naming convention Example: spin-(site_name)-0001 or leaf-(site_name)-0001-a" - def test_device_naming(self): - for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE): - # Change the naming standard based on the re.match - if re.match("[a-z]{4}-" + str(device.site.name) + "-[0-9]{4}(-[a-b])?", str(device.name) , re.IGNORECASE): - self.log_success(device) - else: - self.log_failure(device, "Hostname does not conform to standard!") + def test_device_naming(self): + for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE): + # Change the naming standard based on the re.match + if re.match( + "[a-z]{4}-" + str(device.site.name) + "-[0-9]{4}(-[a-b])?", + str(device.name), + re.IGNORECASE, + ): + self.log_success(device) + else: + self.log_failure(device, "Hostname does not conform to standard!") diff --git a/reports/dcim-reports/DeviceRackingReport.py b/reports/dcim-reports/DeviceRackingReport.py index ee30f9b..03d2df2 100644 --- a/reports/dcim-reports/DeviceRackingReport.py +++ b/reports/dcim-reports/DeviceRackingReport.py @@ -1,9 +1,12 @@ from dcim.choices import DeviceStatusChoices -from dcim.models import Device, Rack +from dcim.models import Device +from dcim.models import Rack from extras.reports import Report + class DeviceRackingReport(Report): description = "Verify each device is assigned to a Rack" + def test_device_racking(self): for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE): if device.rack_id is not None: @@ -11,8 +14,12 @@ def test_device_racking(self): self.log_success(device) elif device.device_type.is_child_device: - self.log_info(device, "Device is child device and therefore not racked itself") + self.log_info( + device, "Device is child device and therefore not racked itself" + ) else: - self.log_warning(device, "Device is racked, but not assigned a position") + self.log_warning( + device, "Device is racked, but not assigned a position" + ) else: self.log_failure(device, "Device is not racked") diff --git a/reports/dcim-reports/RackGroupAssignment.py b/reports/dcim-reports/RackGroupAssignment.py index 08c7be0..20f2837 100644 --- a/reports/dcim-reports/RackGroupAssignment.py +++ b/reports/dcim-reports/RackGroupAssignment.py @@ -1,8 +1,11 @@ -from dcim.models import Rack, RackGroup +from dcim.models import Rack +from dcim.models import RackGroup from extras.reports import Report + class RackGroupAssignmentReport(Report): description = "Verify each rack is assigned to a Rack Group" + def test_rack_group_assignment(self): for rack in Rack.objects.all(): if rack.group_id is not None: diff --git a/reports/dcim-reports/missing_device_type_components.py b/reports/dcim-reports/missing_device_type_components.py index 63e2091..3a0d7b6 100644 --- a/reports/dcim-reports/missing_device_type_components.py +++ b/reports/dcim-reports/missing_device_type_components.py @@ -1,25 +1,27 @@ # Identify devices which are missing components from the device type definition - -from extras.reports import Report from dcim.models import Device +from extras.reports import Report + class MissingDeviceTypeComponents(Report): name = "Missing Device Type Components" - description = "Find devices which are missing components that are in the device type template" + description = ( + "Find devices which are missing components that are in the device type template" + ) def test_add_ports(self): for device in Device.objects.all(): dt = device.device_type for item, templateitem in [ - ('consoleports', 'consoleporttemplates'), - ('consoleserverports', 'consoleserverporttemplates'), - ('powerports', 'powerporttemplates'), - ('poweroutlets', 'poweroutlettemplates'), - ('interfaces', 'interfacetemplates'), - ('rearports', 'rearporttemplates'), - ('frontports', 'frontporttemplates'), - ('devicebays', 'devicebaytemplates'), + ("consoleports", "consoleporttemplates"), + ("consoleserverports", "consoleserverporttemplates"), + ("powerports", "powerporttemplates"), + ("poweroutlets", "poweroutlettemplates"), + ("interfaces", "interfacetemplates"), + ("rearports", "rearporttemplates"), + ("frontports", "frontporttemplates"), + ("devicebays", "devicebaytemplates"), ]: names = {i.name for i in getattr(device, item).all()} templatenames = {i.name for i in getattr(dt, templateitem).all()} diff --git a/reports/ipam-reports/dns-reports.py b/reports/ipam-reports/dns-reports.py index 52b93ff..5f97d12 100644 --- a/reports/ipam-reports/dns-reports.py +++ b/reports/ipam-reports/dns-reports.py @@ -1,10 +1,12 @@ # Make sure to install the dnspython module for this to work (pip3 install dnspython) # Add `dnspython` to your `local-requirements.txt` to make sure it is included during updates. +import socket +import dns.resolver from dcim.choices import DeviceStatusChoices from dcim.models import Device from extras.reports import Report -import socket, dns.resolver + class Check_DNS_A_Record(Report): description = "Check if device's primary IPv4 has DNS records" @@ -23,7 +25,7 @@ def test_dna_a_record(self): if addr == ip4: self.log_success(device) else: - self.log_failure(device,"DNS: " + addr + " - Netbox: " + ip4) + self.log_failure(device, "DNS: " + addr + " - Netbox: " + ip4) except socket.gaierror as err: self.log_info(device, "No DNS Resolution") else: @@ -33,6 +35,7 @@ def test_dna_a_record(self): except socket.gaierror as err: self.log_info(device, "No IP or DNS found.") + class Check_DNS_AAAA_Record(Report): description = "Check if device's primary IPv6 has DNS records" @@ -51,7 +54,7 @@ def test_dns_aaaa_record(self): if addr == ip6: self.log_success(device) else: - self.log_failure(device,"DNS: " + addr + " - Netbox: " + ip6) + self.log_failure(device, "DNS: " + addr + " - Netbox: " + ip6) except dns.resolver.NoAnswer: self.log_info(device, "No AAAA Record") except dns.resolver.NXDOMAIN: diff --git a/reports/ipam-reports/ip-check-prefix.py b/reports/ipam-reports/ip-check-prefix.py index c0c9816..d0d6abf 100644 --- a/reports/ipam-reports/ip-check-prefix.py +++ b/reports/ipam-reports/ip-check-prefix.py @@ -1,6 +1,7 @@ -from ipam.choices import IPAddressRoleChoices -from ipam.models import IPAddress, Prefix from extras.reports import Report +from ipam.choices import IPAddressRoleChoices +from ipam.models import IPAddress +from ipam.models import Prefix LOOPBACK_ROLES = [ IPAddressRoleChoices.ROLE_LOOPBACK, @@ -15,7 +16,9 @@ class CheckPrefixLength(Report): def test_prefix_lengths(self): prefixes = list(Prefix.objects.all()) - prefixes.sort(key=lambda k: k.prefix) # overlapping subnets sort in order from largest to smallest + prefixes.sort( + key=lambda k: k.prefix + ) # overlapping subnets sort in order from largest to smallest for ipaddr in IPAddress.objects.all(): a = ipaddr.address if str(a).startswith("fe80"): @@ -25,9 +28,13 @@ def test_prefix_lengths(self): if ipaddr.role in LOOPBACK_ROLES and a.size == 1: self.log_success(ipaddr) continue - parents = [p for p in prefixes if - (p.vrf and p.vrf.id) == (ipaddr.vrf and ipaddr.vrf.id) and - p.prefix.version == a.version and a.ip in p.prefix] + parents = [ + p + for p in prefixes + if (p.vrf and p.vrf.id) == (ipaddr.vrf and ipaddr.vrf.id) + and p.prefix.version == a.version + and a.ip in p.prefix + ] if not parents: self.log_info(ipaddr, "No parent prefix") continue @@ -37,18 +44,27 @@ def test_prefix_lengths(self): self.log_success(ipaddr) continue if a.prefixlen != parent.prefix.prefixlen: - self.log_failure(ipaddr, "prefixlen (%d) inconsistent with parent prefix (%s)" % - (a.prefixlen, str(parent.prefix))) + self.log_failure( + ipaddr, + "prefixlen (%d) inconsistent with parent prefix (%s)" + % (a.prefixlen, str(parent.prefix)), + ) continue # if the parent prefix also contains child prefixes, that probably means that # an intermediate parent prefix is missing - pchildren = [p for p in prefixes if - (p.vrf and p.vrf.id) == (parent.vrf and parent.vrf.id) and - p.prefix.version == parent.prefix.version and - p.prefix != parent.prefix and - p.prefix in parent.prefix] + pchildren = [ + p + for p in prefixes + if (p.vrf and p.vrf.id) == (parent.vrf and parent.vrf.id) + and p.prefix.version == parent.prefix.version + and p.prefix != parent.prefix + and p.prefix in parent.prefix + ] if pchildren: - self.log_warning(ipaddr, "parent prefix (%s) contains %d other child prefix(es)" % - (str(parent.prefix), len(pchildren))) + self.log_warning( + ipaddr, + "parent prefix (%s) contains %d other child prefix(es)" + % (str(parent.prefix), len(pchildren)), + ) continue self.log_success(ipaddr) diff --git a/reports/ipam-reports/ip-duplicate.py b/reports/ipam-reports/ip-duplicate.py index 62d25f8..88feb8e 100644 --- a/reports/ipam-reports/ip-duplicate.py +++ b/reports/ipam-reports/ip-duplicate.py @@ -1,7 +1,8 @@ -from ipam.choices import IPAddressRoleChoices -from ipam.models import IPAddress, Prefix -from extras.reports import Report from django.db.models import Q +from extras.reports import Report +from ipam.choices import IPAddressRoleChoices +from ipam.models import IPAddress +from ipam.models import Prefix # UniqueIPReport was forked from https://gist.github.com/dgarros/acc23b4fd8d42844b8a41f695e6cb769 class UniqueIPReport(Report): @@ -9,27 +10,36 @@ class UniqueIPReport(Report): def test_unique_ip(self): already_found = [] - for ip in IPAddress.objects.exclude(Q(role=IPAddressRoleChoices.ROLE_ANYCAST) | Q(role=IPAddressRoleChoices.ROLE_VIP) | Q(role=IPAddressRoleChoices.ROLE_VRRP)): + for ip in IPAddress.objects.exclude( + Q(role=IPAddressRoleChoices.ROLE_ANYCAST) + | Q(role=IPAddressRoleChoices.ROLE_VIP) + | Q(role=IPAddressRoleChoices.ROLE_VRRP) + ): if ip.address in already_found: - continue + continue elif not ip.interface: continue duplicates = ip.get_duplicates() real_dup = 0 for duplicate in duplicates: if duplicate.interface: - real_dup +=1 + real_dup += 1 if real_dup != 0: already_found.append(ip.address) msg = "has %s duplicate ips" % real_dup - self.log_failure( ip, msg ) + self.log_failure(ip, msg) + class UniquePrefixReport(Report): - description = "Validate that we don't have a Prefix allocated multiple times in a VRF" + description = ( + "Validate that we don't have a Prefix allocated multiple times in a VRF" + ) def test_unique_prefix(self): for prefix in Prefix.objects.all(): - duplicate_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix=str(prefix.prefix)).exclude(pk=prefix.pk) - if len(duplicate_prefixes) > 0 : + duplicate_prefixes = Prefix.objects.filter( + vrf=prefix.vrf, prefix=str(prefix.prefix) + ).exclude(pk=prefix.pk) + if len(duplicate_prefixes) > 0: msg = "has %s duplicate prefix(es)" % len(duplicate_prefixes) - self.log_failure( prefix, msg ) + self.log_failure(prefix, msg) diff --git a/reports/ipam-reports/ip-primary-find.py b/reports/ipam-reports/ip-primary-find.py index 700a3ce..6c1ebd0 100644 --- a/reports/ipam-reports/ip-primary-find.py +++ b/reports/ipam-reports/ip-primary-find.py @@ -1,39 +1,61 @@ from dcim.choices import DeviceStatusChoices from dcim.models import Device +from extras.reports import Report +from ipam.choices import IPAddressStatusChoices from virtualization.choices import VirtualMachineStatusChoices from virtualization.models import VirtualMachine -from ipam.choices import IPAddressStatusChoices -from extras.reports import Report # CheckPrimaryAddress reports forked from https://gist.github.com/candlerb/5380a7cdd03b60fbd02a664feb266d44 class CheckPrimaryAddressDevice(Report): - description = "Check that every device with an assigned IP has a primary IP address assigned" + description = ( + "Check that every device with an assigned IP has a primary IP address assigned" + ) def test_device_primary_ips(self): - for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE).prefetch_related('interfaces__ip_addresses').all(): + for device in ( + Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE) + .prefetch_related("interfaces__ip_addresses") + .all() + ): fail = False intcount = 0 all_addrs = {4: [], 6: []} for interface in device.interfaces.all(): if not interface.mgmt_only: intcount += 1 - for addr in interface.ip_addresses.exclude(status=IPAddressStatusChoices.STATUS_DEPRECATED).all(): + for addr in interface.ip_addresses.exclude( + status=IPAddressStatusChoices.STATUS_DEPRECATED + ).all(): all_addrs[addr.address.version].append(addr) # There may be dumb devices with no interfaces / IP addresses, that's OK if not device.primary_ip4 and all_addrs[4]: - self.log_failure(device, "Device has no primary IPv4 address (could be %s)" % - " ".join([str(a) for a in all_addrs[4]])) + self.log_failure( + device, + "Device has no primary IPv4 address (could be %s)" + % " ".join([str(a) for a in all_addrs[4]]), + ) fail = True if not device.primary_ip6 and all_addrs[6]: - self.log_failure(device, "Device has no primary IPv6 address (could be %s)" % - " ".join([str(a) for a in all_addrs[6]])) + self.log_failure( + device, + "Device has no primary IPv6 address (could be %s)" + % " ".join([str(a) for a in all_addrs[6]]), + ) fail = True if not fail: # There may be dumb devices that are used as patch panels. Check for front/back ports - if intcount == 0 and device.frontports.count() > 0 and device.rearports.count() > 0: + if ( + intcount == 0 + and device.frontports.count() > 0 + and device.rearports.count() > 0 + ): self.log_success(device) # Or dumb PDUs - elif intcount == 0 and device.powerports.count() > 0 and device.poweroutlets.count() > 0: + elif ( + intcount == 0 + and device.powerports.count() > 0 + and device.poweroutlets.count() > 0 + ): self.log_success(device) elif intcount == 0: self.log_warning(device, "No interfaces assigned to device") @@ -43,17 +65,28 @@ def test_device_primary_ips(self): else: self.log_success(device) + class CheckPrimaryAddressVM(Report): - description = "Check that every vm with an assigned IP has a primary IP address assigned" + description = ( + "Check that every vm with an assigned IP has a primary IP address assigned" + ) def test_vm_primary_ips(self): - for vm in VirtualMachine.objects.filter(status=VirtualMachineStatusChoices.STATUS_ACTIVE).prefetch_related('interfaces__ip_addresses').all(): + for vm in ( + VirtualMachine.objects.filter( + status=VirtualMachineStatusChoices.STATUS_ACTIVE + ) + .prefetch_related("interfaces__ip_addresses") + .all() + ): fail = False intcount = 0 all_addrs = {4: [], 6: []} for interface in vm.interfaces.all(): intcount += 1 - for addr in interface.ip_addresses.exclude(status=IPAddressStatusChoices.STATUS_DEPRECATED).all(): + for addr in interface.ip_addresses.exclude( + status=IPAddressStatusChoices.STATUS_DEPRECATED + ).all(): all_addrs[addr.address.version].append(addr) # A VM is useless without an IP address if intcount == 0: @@ -63,12 +96,18 @@ def test_vm_primary_ips(self): self.log_failure(vm, "Virtual machine has no IP addresses") continue if not vm.primary_ip4 and all_addrs[4]: - self.log_failure(vm, "Virtual machine has no primary IPv4 address (could be %s)" % - " ".join([str(a) for a in all_addrs[4]])) + self.log_failure( + vm, + "Virtual machine has no primary IPv4 address (could be %s)" + % " ".join([str(a) for a in all_addrs[4]]), + ) fail = True if not vm.primary_ip6 and all_addrs[6]: - self.log_failure(vm, "Virtual machine has no primary IPv6 address (could be %s)" % - " ".join([str(a) for a in all_addrs[6]])) + self.log_failure( + vm, + "Virtual machine has no primary IPv6 address (could be %s)" + % " ".join([str(a) for a in all_addrs[6]]), + ) fail = True if not fail: self.log_success(vm) diff --git a/reports/ipam-reports/ip-primary-missing.py b/reports/ipam-reports/ip-primary-missing.py index cfc09cb..6fd71b1 100644 --- a/reports/ipam-reports/ip-primary-missing.py +++ b/reports/ipam-reports/ip-primary-missing.py @@ -2,8 +2,11 @@ from dcim.models import Device from extras.reports import Report + class DeviceIPReport(Report): - description = "Check that every device has either an IPv4 or IPv6 primary address assigned" + description = ( + "Check that every device has either an IPv4 or IPv6 primary address assigned" + ) def test_primary_ip4(self): for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE): @@ -15,9 +18,15 @@ def test_primary_ip4(self): if intcount == 0: if device.primary_ip4_id is not None: if device.primary_ip6_id is not None: - self.log_failure(device, "Device has primary IPv4 and IPv6 address but no interfaces") + self.log_failure( + device, + "Device has primary IPv4 and IPv6 address but no interfaces", + ) else: - self.log_warning(device, "Device has missing primary IPv4 addresses but no interfaces") + self.log_warning( + device, + "Device has missing primary IPv4 addresses but no interfaces", + ) else: self.log_success(device) elif device.primary_ip4_id is None: @@ -25,9 +34,13 @@ def test_primary_ip4(self): self.log_success(device) else: if device.primary_ip6_id is None: - self.log_failure(device, "Device is missing primary IPv4 and IPv6 address") + self.log_failure( + device, "Device is missing primary IPv4 and IPv6 address" + ) else: - self.log_warning(device, "Device is missing primary IPv4 addresses") + self.log_warning( + device, "Device is missing primary IPv4 addresses" + ) else: if device.device_type.is_child_device is True: self.log_success(device) diff --git a/reports/misc/CustomFieldValue.py b/reports/misc/CustomFieldValue.py index 0be343c..70be478 100644 --- a/reports/misc/CustomFieldValue.py +++ b/reports/misc/CustomFieldValue.py @@ -4,6 +4,7 @@ # This sample looks for a custom field named "Monitor" and then acts from there + class Check_IfMonitored(Report): description = "Check if device is flagged to be monitored" diff --git a/reports/site/site_address.py b/reports/site/site_address.py index e012117..ee83e5c 100644 --- a/reports/site/site_address.py +++ b/reports/site/site_address.py @@ -1,10 +1,9 @@ # site_address.py - # Make sure to add `geocoder` to your `local_requirements.txt` and make sure it is installed in your Python venv. - import geocoder -from extras.reports import Report from dcim.models import Site +from extras.reports import Report + class checkSiteAddress(Report): description = "Check if site has a physical address and/or geolocation information" @@ -24,8 +23,17 @@ def test_site_geo(self): if site.physical_address: g = geocoder.osm(site.physical_address) if g: - self.log_warning(site, f'Missing geo location - possible ({round(g.x,6)}, {round(g.y,6)})') + self.log_warning( + site, + f"Missing geo location - possible ({round(g.x,6)}, {round(g.y,6)})", + ) else: - self.log_warning(site, f'Missing geo location ({site.latitude}, {site.longitude})') - else: - self.log_failure(site, f'Missing geo location ({site.latitude}, {site.longitude})') + self.log_warning( + site, + f"Missing geo location ({site.latitude}, {site.longitude})", + ) + else: + self.log_failure( + site, + f"Missing geo location ({site.latitude}, {site.longitude})", + ) diff --git a/reports/virtualization-reports/vm_counts.py b/reports/virtualization-reports/vm_counts.py index 4847e61..3ac9c18 100644 --- a/reports/virtualization-reports/vm_counts.py +++ b/reports/virtualization-reports/vm_counts.py @@ -1,7 +1,6 @@ -from django.db.models import Count - from dcim.choices import SiteStatusChoices from dcim.models import Site +from django.db.models import Count from extras.reports import Report diff --git a/scripts/add_device_type_components.py b/scripts/add_device_type_components.py index 9764a9e..1710422 100644 --- a/scripts/add_device_type_components.py +++ b/scripts/add_device_type_components.py @@ -1,9 +1,18 @@ """ This script adds missing components from the device type to selected device(s) """ +from dcim.models import ConsolePort +from dcim.models import ConsoleServerPort +from dcim.models import Device +from dcim.models import DeviceBay +from dcim.models import FrontPort +from dcim.models import Interface +from dcim.models import PowerOutlet +from dcim.models import PowerPort +from dcim.models import RearPort +from extras.scripts import MultiObjectVar +from extras.scripts import Script -from dcim.models import Device, ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, RearPort, FrontPort, DeviceBay -from extras.scripts import Script, MultiObjectVar class AddDeviceTypeComponents(Script): class Meta: @@ -22,24 +31,25 @@ def run(self, data, commit): # "If this is a new Device, instantiate all of the related components per the DeviceType definition" # Note that ordering is important: e.g. PowerPort before PowerOutlet, RearPort before FrontPort for klass, item, templateitem in [ - (ConsolePort, 'consoleports', 'consoleporttemplates'), - (ConsoleServerPort, 'consoleserverports', 'consoleserverporttemplates'), - (PowerPort, 'powerports', 'powerporttemplates'), - (PowerOutlet, 'poweroutlets', 'poweroutlettemplates'), - (Interface, 'interfaces', 'interfacetemplates'), - (RearPort, 'rearports', 'rearporttemplates'), - (FrontPort, 'frontports', 'frontporttemplates'), - (DeviceBay,'devicebays', 'devicebaytemplates'), + (ConsolePort, "consoleports", "consoleporttemplates"), + (ConsoleServerPort, "consoleserverports", "consoleserverporttemplates"), + (PowerPort, "powerports", "powerporttemplates"), + (PowerOutlet, "poweroutlets", "poweroutlettemplates"), + (Interface, "interfaces", "interfacetemplates"), + (RearPort, "rearports", "rearporttemplates"), + (FrontPort, "frontports", "frontporttemplates"), + (DeviceBay, "devicebays", "devicebaytemplates"), ]: names = {i.name for i in getattr(device, item).all()} templates = getattr(dt, templateitem).all() items = [ - x.instantiate(device) - for x in templates - if x.name not in names + x.instantiate(device) for x in templates if x.name not in names ] if items: for i in items: i.full_clean() klass.objects.bulk_create(items) - self.log_success("%s (%d): created %d %s" % (device.name, device.id, len(items), item)) + self.log_success( + "%s (%d): created %d %s" + % (device.name, device.id, len(items), item) + ) diff --git a/scripts/create_vm.py b/scripts/create_vm.py index 83c9416..2c1e685 100644 --- a/scripts/create_vm.py +++ b/scripts/create_vm.py @@ -6,32 +6,57 @@ https://github.com/netbox-community/netbox/issues/1492 https://github.com/netbox-community/netbox/issues/648 """ - -from dcim.models import DeviceRole, Platform +from dcim.models import DeviceRole +from dcim.models import Platform from django.core.exceptions import ObjectDoesNotExist +from extras.scripts import ChoiceVar +from extras.scripts import IntegerVar +from extras.scripts import IPAddressWithMaskVar +from extras.scripts import ObjectVar +from extras.scripts import Script +from extras.scripts import StringVar +from extras.scripts import TextVar from ipam.choices import IPAddressStatusChoices -from ipam.models import IPAddress, VRF +from ipam.models import IPAddress +from ipam.models import VRF from tenancy.models import Tenant from virtualization.choices import VirtualMachineStatusChoices -from virtualization.models import Cluster, VirtualMachine, VMInterface -from extras.scripts import Script, StringVar, IPAddressWithMaskVar, ObjectVar, ChoiceVar, IntegerVar, TextVar +from virtualization.models import Cluster +from virtualization.models import VirtualMachine +from virtualization.models import VMInterface + 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'] + 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(model=VRF, 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) + status = ChoiceVar( + VirtualMachineStatusChoices, default=VirtualMachineStatusChoices.STATUS_ACTIVE + ) cluster = ObjectVar(model=Cluster) tenant = ObjectVar(model=Tenant, required=False) platform = ObjectVar(model=Platform, required=False) @@ -79,8 +104,8 @@ def add_addr(addr, expect_family): result = "Assigned" except ObjectDoesNotExist: a = IPAddress( - address=addr, - vrf=data.get("vrf"), + address=addr, + vrf=data.get("vrf"), ) result = "Created" a.status = IPAddressStatusChoices.STATUS_ACTIVE diff --git a/scripts/geolocate_site.py b/scripts/geolocate_site.py index 72d53a8..f899e68 100644 --- a/scripts/geolocate_site.py +++ b/scripts/geolocate_site.py @@ -1,59 +1,74 @@ # geolocate_site.py - # Make sure to add `geocoder` to your `local_requirements.txt` and make sure it is installed in your Python venv. - import geocoder -from dcim.models import Site, Region -from extras.scripts import Script, ObjectVar, BooleanVar +from dcim.models import Region +from dcim.models import Site +from extras.scripts import BooleanVar +from extras.scripts import ObjectVar +from extras.scripts import Script + +name = "Populate geolocation for sites" -name = 'Populate geolocation for sites' class SiteGeoAll(Script): class Meta: - name = 'All sites for a region' - description = 'Retrieve list of all sites and populate the latitude/longitude fields based on their physical address.' + name = "All sites for a region" + description = "Retrieve list of all sites and populate the latitude/longitude fields based on their physical address." commit_default = True - + region = ObjectVar(model=Region, display_field=name) - overwrite = BooleanVar(default=False, label='Override existing value', - description='If location already exists, update the value.') + overwrite = BooleanVar( + default=False, + label="Override existing value", + description="If location already exists, update the value.", + ) def run(self, data, commit): - for site in get_sites_for_region(data['region']): + for site in get_sites_for_region(data["region"]): update_site(self, site) class SiteGeoOne(Script): class Meta: - name = 'Specific site' - description = 'Populate the latitude/longitude fields for a specific site based on its physical address.' + name = "Specific site" + description = "Populate the latitude/longitude fields for a specific site based on its physical address." commit_default = True location = ObjectVar(model=Site, display_field=name) - overwrite = BooleanVar(default=False, label='Override existing value', - description='If location already exists, update the value.') + overwrite = BooleanVar( + default=False, + label="Override existing value", + description="If location already exists, update the value.", + ) def run(self, data, commit): - site = data['location'] - update_site(self, site, data['overwrite']) + site = data["location"] + update_site(self, site, data["overwrite"]) def update_site(script, site, overwrite=False): if site.physical_address: - if site.latitude and site.longitude and overwrite==False: - script.log_info(f'{site.name}: {site.physical_address} already at {site.longitude}, {site.latitude}') + if site.latitude and site.longitude and overwrite == False: + script.log_info( + f"{site.name}: {site.physical_address} already at {site.longitude}, {site.latitude}" + ) else: g = geocoder.osm(site.physical_address) if g: - script.log_success(f'{site.name} geolocation found: {round(g.y,6)}, {round(g.x,6)}') - site.latitude = round(g.y,6) - site.longitude = round(g.x,6) + script.log_success( + f"{site.name} geolocation found: {round(g.y,6)}, {round(g.x,6)}" + ) + site.latitude = round(g.y, 6) + site.longitude = round(g.x, 6) site.full_clean() site.save() else: - script.log_failure(f'{site.name} no geolocation found for {site.physical_address}') + script.log_failure( + f"{site.name} no geolocation found for {site.physical_address}" + ) else: - script.log_warning(f'No physical address for {site.name}') + script.log_warning(f"No physical address for {site.name}") + def get_sites_for_region(region): region_list = [region] @@ -64,6 +79,7 @@ def get_sites_for_region(region): site_list.append(site) return site_list + def get_child_regions(region, region_list): for sub_region in Region.objects.filter(parent=region): region_list.append(sub_region) diff --git a/scripts/multi_connect.py b/scripts/multi_connect.py index af8fbc5..4908f28 100644 --- a/scripts/multi_connect.py +++ b/scripts/multi_connect.py @@ -1,13 +1,21 @@ """ Add multiple connections from one device to another """ +import re -from dcim.choices import LinkStatusChoices, CableTypeChoices, CableLengthUnitChoices -from dcim.models import Device, Cable +from dcim.choices import CableLengthUnitChoices +from dcim.choices import CableTypeChoices +from dcim.choices import LinkStatusChoices +from dcim.models import Cable +from dcim.models import Device from django.db import transaction from extras.models import Tag -from extras.scripts import Script, ChoiceVar, ObjectVar, StringVar, IntegerVar, MultiObjectVar -import re +from extras.scripts import ChoiceVar +from extras.scripts import IntegerVar +from extras.scripts import MultiObjectVar +from extras.scripts import ObjectVar +from extras.scripts import Script +from extras.scripts import StringVar from netbox.settings import VERSION from tenancy.models import Tenant from utilities.choices import ColorChoices @@ -17,51 +25,77 @@ NO_CHOICE = () # https://github.com/netbox-community/netbox/issues/8228 # Only apply to Netbox < v3.1.5 -if [int(n) for n in VERSION.split('-')[0].split('.')] < [3, 1, 5]: - NO_CHOICE = ( - ('', '---------'), - ) +if [int(n) for n in VERSION.split("-")[0].split(".")] < [3, 1, 5]: + NO_CHOICE = (("", "---------"),) TERM_CHOICES = ( - ('interfaces', 'Interfaces'), - ('frontports', 'Front Ports'), - ('rearports', 'Rear Ports'), + ("interfaces", "Interfaces"), + ("frontports", "Front Ports"), + ("rearports", "Rear Ports"), ) + def expand_pattern(value): if not value: - return [''] + return [""] if re.search(ALPHANUMERIC_EXPANSION_PATTERN, value): return list(expand_alphanumeric_pattern(value)) return [value] + class MultiConnect(Script): class Meta: name = "Multi Connect" description = "Add multiple connections from one device to another" field_order = [ - 'device_a', 'termination_type_a', 'termination_name_a', - 'device_b', 'termination_type_b', 'termination_name_b', - 'cable_status', 'cable_type', 'cable_tenant', - 'cable_label', 'cable_color', 'cable_length', 'cable_length_unit' - 'cable_tags', + "device_a", + "termination_type_a", + "termination_name_a", + "device_b", + "termination_type_b", + "termination_name_b", + "cable_status", + "cable_type", + "cable_tenant", + "cable_label", + "cable_color", + "cable_length", + "cable_length_unit" "cable_tags", ] 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]") + 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]") + 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_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_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): @@ -73,21 +107,29 @@ def run(self, data, commit): 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)') + 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') + 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})') + 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})') + 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], @@ -106,6 +148,10 @@ def run(self, data, commit): cable.full_clean() cable.save() 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}') + 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]}') + self.log_success( + f"Created cable from {device_a.name}:{terms_a[i]} to {device_b.name}:{terms_b[i]}" + ) From cbfc426073d9bee4c6fae915bbfdb587382c4874 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 11:38:59 -0500 Subject: [PATCH 05/12] update markdown docs --- CONTRIBUTING.md | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d765d63 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,132 @@ +# Contributing + +## Getting Help + +If you encounter any issues installing or using any of these scripts or reports, try one of the +following resources to get assistance. Please **do not** open a GitHub issue +except to report bugs or submit script/report ideas for the community to take up. + +### GitHub Discussions + +GitHub's discussions are the best place to get help with an existing script, propose rough ideas for +new functionality that you are trying to draft, or help creating your own script. Their integration +with GitHub allows for easily cross-referencing and converting posts to issues as needed. There are several +categories for discussions: + +* **General** - General community discussion +* **Ideas** - Ideas for new functionality that isn't yet ready for a formal + feature request +* **Q&A** - Request help with installing or using NetBox + +### Slack + +For real-time chat, you can join the **#netbox** Slack channel on [NetDev Community](https://netdev.chat/). +Unfortunately, the Slack channel does not provide long-term retention of chat +history, so try to avoid it for any discussions would benefit from being +preserved for future reference. + +## Reporting Bugs + +* First, check that you are running the version the report or script states it supports in the source file. + +* Next, check the GitHub [issues list](https://github.com/netbox-community/reports/issues) +to see if the bug you've found has already been reported. If you think you may +be experiencing a reported issue that hasn't already been resolved, please +click "add a reaction" in the top right corner of the issue and add a thumbs +up (+1). You might also want to add a comment describing how it's affecting your +installation. This will allow us to prioritize bugs based on how many users are +affected. + +* When submitting an issue, please be as descriptive as possible. Be sure to +provide all information request in the issue template, including: + + * The environment (NetBox version, deployment method, & Python Version) in which NetBox is running + * The exact steps that can be taken to reproduce the issue + * Expected and observed behavior + * Any error messages generated + * Screenshots (if applicable) + +* Keep in mind that this is a community crowdsourced report, and maintainers are not +commiting to update or fix scripts submitted to the repo. We will try out best to help +and update submitted scripts. + +## Feature Requests + +* First, check the GitHub [issues list](https://github.com/netbox-community/reports/issues) +to see if the feature you're requesting is already listed. (Be sure to search +closed issues as well, since some feature requests have been rejected.) If the +feature you'd like to see has already been requested and is open, click "add a +reaction" in the top right corner of the issue and add a thumbs up (+1). This +ensures that the issue has a better chance of receiving attention. Also feel +free to add a comment with any additional justification for the feature. +(However, note that comments with no substance other than a "+1" will be +deleted. Please use GitHub's reactions feature to indicate your support.) + +* Before filing a new feature request, consider raising your idea in a +[GitHub discussion](https://github.com/netbox-community/netbox/discussions) +first. Feedback you receive there will help validate and shape the proposed +feature before filing a formal issue. + +* Good feature requests are very narrowly defined. Be sure to thoroughly +describe the functionality and data model(s) being proposed. The more effort +you put into writing a feature request, the better its chance is of being +implemented. Overly broad feature requests will be closed. + +* When submitting a feature request on GitHub, be sure to include all +information requested by the issue template, including: + + * A detailed description of the proposed functionality + * A use case for the feature; who would use it and what value it would add + to NetBox + * A rough description of changes necessary to the database schema (if + applicable) + * Any third-party libraries or other resources which would be involved + +* For more information on how feature requests are handled, please see our +[issue intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Policy). + +## Submitting Pull Requests + +* If you're interested in contributing to this repo, be sure to check out our +[getting started](https://netbox.readthedocs.io/en/stable/development/getting-started/) +documentation for tips on setting up your development environment. + +* Be sure to open an issue **before** starting work on a pull request, and +discuss your idea with the community before beginning work. This will +help prevent wasting time on something that might you might not be able to +implement. When suggesting a new feature, also make sure it won't conflict with +any work that's already in progress. + +* Once you've opened or identified an issue you'd like to work on, ask that it +be assigned to you so that others are aware it's being worked on. + +* Any pull request which does _not_ relate to an opened issue will be closed. + +* Any pull request that duplicates functionality from an existing script or report +will be asked to redirect their functionality updates to the existing script/report. + +* All new functionality must include relevant tests where applicable. + +* All code submissions should meet the following criteria (CI will enforce +these checks): + + * Python syntax is valid + * Black compliance is enforced + * PEP 8 compliance is enforced, with the exception that lines may be + greater than 80 characters in length + +## Commenting + +Only comment on an issue if you are sharing a relevant idea or constructive +feedback. **Do not** comment on an issue just to show your support (give the +top post a :+1: instead) or ask for an ETA. These comments will be deleted to +reduce noise in the discussion. + +## Maintainer Guidance + +* Maintainers are expected to help review Pull Request submissions, assist in + troubleshooting PR test failures, and handle general project housekeeping. + This can be employer-sponsored or individual time, with the understanding that + all contributions are submitted under the Apache 2.0 license and that your + employer may not make claim to any contributions. Contributions include code + work, issue management, and community support. diff --git a/README.md b/README.md index 6aa16f7..0bd2369 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ To contribute a new report, open a pull request. All reports go in the `reports/ To contribute a new script, open a pull request. All reports go in the `scripts/` directory. Each file should contain a summary of the script at the top of the file. -Nothing in this repository comes with any explicit or implied warranty. For more information see [the license](LICENSE). +__Nothing in this repository comes with any explicit or implied warranty. For more information see [the license](LICENSE).__ + +See more in the [CONTRIBUTING](CONTRIBUTING.md) doc. ## Using Reports and Scripts From 476c130fcc0d2b7d5bc8ad4fad91ed967a743bdf Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 12:17:21 -0500 Subject: [PATCH 06/12] issue templates --- .github/ISSUE_TEMPLATE/bug_report.yml | 86 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 15 ++++ .github/ISSUE_TEMPLATE/housekeeping.yml | 25 ++++++ .github/ISSUE_TEMPLATE/new_script.yml | 51 +++++++++++ .../ISSUE_TEMPLATE/script_feature_request.yml | 51 +++++++++++ 5 files changed, 228 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/housekeeping.yml create mode 100644 .github/ISSUE_TEMPLATE/new_script.yml create mode 100644 .github/ISSUE_TEMPLATE/script_feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..07f4d7a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,86 @@ +--- +name: 🐛 Bug Report +description: Report a reproducible bug in the current release of the NetBox Ansible Collection +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: > + **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox Collection + installation. + + - See the latest ansible module documentation: + https://netbox-ansible-collection.readthedocs.io + - Check the release notes: + https://github.com/netbox-community/ansible_modules/releases + - Look through the issues already resolved: + https://github.com/netbox-community/ansible_modules/issues?q=is%3Aclosed + - Post to Github Discussions if you need setup or usage help that is not a bug: + https://github.com/netbox-community/ansible_modules/discussions + - Join the `#ansible` channel on our Slack: + https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ + + - type: input + attributes: + label: Ansible NetBox Collection version + description: What version of the Ansible NetBox Collection are you currently running? + placeholder: v3.5.1 + validations: + required: true + - type: textarea + attributes: + label: Ansible version + description: > + What version of the Ansible NetBox Collection are you currently running? + Paste the output of "ansible --version" + value: > + ```bash + + + + ``` + validations: + required: true + - type: input + attributes: + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 + validations: + required: true + - type: dropdown + attributes: + label: Python version + description: What version of Python are you currently running? + options: + - "3.8" + - "3.9" + - "3.10" + validations: + required: true + - type: textarea + attributes: + label: Steps to Reproduce + description: > + Describe in detail the exact steps that someone else can take to + reproduce this bug using the current stable release of the Ansible NetBox collection. + Include any sanatized playbooks, variables, & tasks specifically showing just the issue. + #placeholder: | + + validations: + required: true + - type: textarea + attributes: + label: Expected Behavior + description: What did you expect to happen? + placeholder: A new widget should have been created with the specified attributes + validations: + required: true + - type: textarea + attributes: + label: Observed Behavior + description: What happened instead? + placeholder: A TypeError exception was raised + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f1d16c7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,15 @@ +# Reference: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser +blank_issues_enabled: false +contact_links: + - name: 📕 Project README + url: https://github.com/netbox-community/reports/blob/master/README.md + about: "Please refer to the documentation before raising a bug or feature request." + - name: 📖 Contributing Policy + url: https://github.com/netbox-community/reports/blob/master/CONTRIBUTING.md + about: "Please read through our contributing policy before opening an issue or pull request" + - name: ❓ Discussion + url: https://github.com/netbox-community/reports/discussions + about: "If you're just looking for help, try starting a discussion instead" + - name: 💬 Community Slack + url: https://netdev.chat/ + about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" diff --git a/.github/ISSUE_TEMPLATE/housekeeping.yml b/.github/ISSUE_TEMPLATE/housekeeping.yml new file mode 100644 index 0000000..f1f99a1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/housekeeping.yml @@ -0,0 +1,25 @@ +--- +name: 🏡 Housekeeping +description: A change pertaining to the codebase itself (developers only) +title: "[Housekeeping]: " +labels: ["housekeeping"] +body: + - type: markdown + attributes: + value: > + **NOTE:** This template is for use by maintainers only. Please do not submit + an issue using this template unless you have been specifically asked to do so. + - type: textarea + attributes: + label: Proposed Changes + description: > + Describe in detail the new feature or behavior you'd like to propose. + Include any specific changes to work flows, data models, or the user interface. + validations: + required: true + - type: textarea + attributes: + label: Justification + description: Please provide justification for the proposed change(s). + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/new_script.yml b/.github/ISSUE_TEMPLATE/new_script.yml new file mode 100644 index 0000000..8452a80 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_script.yml @@ -0,0 +1,51 @@ +--- +name: ✨ New Script Request +description: Propose a new NetBox Script or Report +title: "[Script Request]: " +labels: ["script_request"] +body: + - type: markdown + attributes: + value: > + **NOTE:** This form is only for submitting well-formed proposals to create a NetBox custom script or report. + If you're trying to solve a problem but can't figure out how, or if + you still need time to work on the details of a proposed new script, please start a + [discussion](https://github.com/netbox-community/reports/discussions) instead. + - type: input + attributes: + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 + validations: + required: true + - type: dropdown + attributes: + label: Request type + options: + - New NetBox Custom Script + - New NetBox Report + validations: + required: true + - type: textarea + attributes: + label: Proposed functionality + description: > + Describe in detail the new feature or behavior you are proposing. Include any specific introductions + to work flows, logic, and/or external data sources. The more detail you provide here, the + greater chance your proposal has of being discussed and taken up by a contributor. + validations: + required: true + - type: textarea + attributes: + label: Use case + description: > + Explain how creating this script/report would benefit NetBox users. What need does it address? + validations: + required: true + - type: textarea + attributes: + label: External dependencies + description: > + List any new dependencies on external libraries or services that this new feature would + introduce. For example, does the proposal require the installation of a new Python package, + access to another non-NetBox service? (Not all new features introduce new dependencies.) diff --git a/.github/ISSUE_TEMPLATE/script_feature_request.yml b/.github/ISSUE_TEMPLATE/script_feature_request.yml new file mode 100644 index 0000000..c27e35c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/script_feature_request.yml @@ -0,0 +1,51 @@ +--- +name: ✨ Existing Script Enhancement +description: Propose a new NetBox Script or Report +title: "[Script Enhancement]: " +labels: ["script_enhancement"] +body: + - type: markdown + attributes: + value: > + **NOTE:** This form is only for submitting well-formed proposals to create a NetBox custom script or report. + If you're trying to solve a problem but can't figure out how, or if + you still need time to work on the details of a proposed new script, please start a + [discussion](https://github.com/netbox-community/reports/discussions) instead. + - type: input + attributes: + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 + validations: + required: true + - type: dropdown + attributes: + label: Request type + options: + - Existing NetBox Custom Script + - Existing NetBox Report + validations: + required: true + - type: textarea + attributes: + label: Proposed functionality + description: > + Describe in detail the new feature or behavior you are proposing. Include any specific changes + to work flows, logic, and/or external data sources. The more detail you provide here, the + greater chance your proposal has of being discussed and taken up by a contributor. + validations: + required: true + - type: textarea + attributes: + label: Use case + description: > + Explain how creating this script/report would benefit NetBox users. What need does it address? + validations: + required: true + - type: textarea + attributes: + label: External dependencies + description: > + List any new dependencies on external libraries or services that this new feature would + introduce. For example, does the proposal require the installation of a new Python package, + access to another non-NetBox service? (Not all new features introduce new dependencies.) From 76e18521b4ab0f8483c63445197a9ef96816204d Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 12:47:06 -0500 Subject: [PATCH 07/12] issue template updates --- .github/ISSUE_TEMPLATE/bug_report.yml | 38 ++++----------------------- .github/ISSUE_TEMPLATE/config.yml | 5 +--- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 07f4d7a..9e8954a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,41 +7,13 @@ body: - type: markdown attributes: value: > - **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox Collection - installation. - - - See the latest ansible module documentation: - https://netbox-ansible-collection.readthedocs.io - - Check the release notes: - https://github.com/netbox-community/ansible_modules/releases - - Look through the issues already resolved: - https://github.com/netbox-community/ansible_modules/issues?q=is%3Aclosed - - Post to Github Discussions if you need setup or usage help that is not a bug: - https://github.com/netbox-community/ansible_modules/discussions - - Join the `#ansible` channel on our Slack: - https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ + **NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox Script or Report. - - type: input - attributes: - label: Ansible NetBox Collection version - description: What version of the Ansible NetBox Collection are you currently running? - placeholder: v3.5.1 - validations: - required: true - - type: textarea - attributes: - label: Ansible version - description: > - What version of the Ansible NetBox Collection are you currently running? - Paste the output of "ansible --version" - value: > - ```bash - - + - Look through the issues already resolved: + https://github.com/netbox-community/reports/issues?q=is%3Aclosed + - Join the `#netbox` channel on our Slack: + https://netdev.chat - ``` - validations: - required: true - type: input attributes: label: NetBox version diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index f1d16c7..50dfac1 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -7,9 +7,6 @@ contact_links: - name: 📖 Contributing Policy url: https://github.com/netbox-community/reports/blob/master/CONTRIBUTING.md about: "Please read through our contributing policy before opening an issue or pull request" - - name: ❓ Discussion - url: https://github.com/netbox-community/reports/discussions - about: "If you're just looking for help, try starting a discussion instead" - name: 💬 Community Slack - url: https://netdev.chat/ + url: https://netdev.chat about: "Join #netbox on the NetDev Community Slack for assistance with installation issues and other problems" From d9f86e1c6f7a1a5cbc1c4541e1258356cf92b16e Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 12:49:13 -0500 Subject: [PATCH 08/12] bug report tweak --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9e8954a..b8e7042 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -21,6 +21,12 @@ body: placeholder: v3.1.6 validations: required: true + - type: input + attributes: + label: Script name + description: What script or report are you having issues with? + validations: + required: true - type: dropdown attributes: label: Python version From 4ea4475555c18fb15c0e610c3fc617522ac6fa99 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 12:50:15 -0500 Subject: [PATCH 09/12] reorder bug template --- .github/ISSUE_TEMPLATE/bug_report.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b8e7042..56fdf50 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -16,15 +16,15 @@ body: - type: input attributes: - label: NetBox version - description: What version of NetBox are you currently running? - placeholder: v3.1.6 + label: Script name + description: What script or report are you having issues with? validations: required: true - type: input attributes: - label: Script name - description: What script or report are you having issues with? + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 validations: required: true - type: dropdown From f26039b026c497a2791cbd1f2720f694334396ca Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Sun, 30 Jan 2022 12:55:45 -0500 Subject: [PATCH 10/12] test templates --- .github/ISSUE_TEMPLATE/test1.yml | 59 ++++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/test2.yml | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/test1.yml create mode 100644 .github/ISSUE_TEMPLATE/test2.yml diff --git a/.github/ISSUE_TEMPLATE/test1.yml b/.github/ISSUE_TEMPLATE/test1.yml new file mode 100644 index 0000000..55bbfd2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test1.yml @@ -0,0 +1,59 @@ +name: 🐞 Bug +description: File a bug/issue +title: "[BUG] " +labels: [Bug, Needs Triage] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: false +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: false +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + validations: + required: false +- type: textarea + attributes: + label: Environment + description: | + examples: + - **OS**: Ubuntu 20.04 + - **Node**: 13.14.0 + - **npm**: 7.6.3 + value: | + - OS: + - Node: + - npm: + render: markdown + validations: + required: false +- type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/test2.yml b/.github/ISSUE_TEMPLATE/test2.yml new file mode 100644 index 0000000..9d0d058 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/test2.yml @@ -0,0 +1,62 @@ +name: Bug Report +description: File a bug report +title: "[Bug]: " +labels: ["bug", "triage"] +assignees: + - octocat +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: ex. email@example.com + validations: + required: false + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + value: "A bug happened!" + validations: + required: true + - type: dropdown + id: version + attributes: + label: Version + description: What version of our software are you running? + options: + - 1.0.2 (Default) + - 1.0.3 (Edge) + validations: + required: true + - type: dropdown + id: browsers + attributes: + label: What browsers are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - type: textarea + id: logs + attributes: + label: Relevant log output + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) + options: + - label: I agree to follow this project's Code of Conduct + required: true \ No newline at end of file From 25f1603cf5a2b5d3443575680365e14b44f38b22 Mon Sep 17 00:00:00 2001 From: ryanmerolle <ryan.merolle@gmail.com> Date: Sun, 30 Jan 2022 17:42:33 -0500 Subject: [PATCH 11/12] enhance bug report --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 ++-- .github/ISSUE_TEMPLATE/test1.yml | 59 ------------------------- .github/ISSUE_TEMPLATE/test2.yml | 62 --------------------------- 3 files changed, 5 insertions(+), 124 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/test1.yml delete mode 100644 .github/ISSUE_TEMPLATE/test2.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 56fdf50..a6f6bfe 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -43,9 +43,11 @@ body: description: > Describe in detail the exact steps that someone else can take to reproduce this bug using the current stable release of the Ansible NetBox collection. - Include any sanatized playbooks, variables, & tasks specifically showing just the issue. - #placeholder: | - + placeholder: | + 1. In this environment... + 2. With this input... + 3. Run '...' + 4. See error... validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/test1.yml b/.github/ISSUE_TEMPLATE/test1.yml deleted file mode 100644 index 55bbfd2..0000000 --- a/.github/ISSUE_TEMPLATE/test1.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: 🐞 Bug -description: File a bug/issue -title: "[BUG] <title>" -labels: [Bug, Needs Triage] -body: -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the bug you encountered. - options: - - label: I have searched the existing issues - required: true -- type: textarea - attributes: - label: Current Behavior - description: A concise description of what you're experiencing. - validations: - required: false -- type: textarea - attributes: - label: Expected Behavior - description: A concise description of what you expected to happen. - validations: - required: false -- type: textarea - attributes: - label: Steps To Reproduce - description: Steps to reproduce the behavior. - placeholder: | - 1. In this environment... - 2. With this config... - 3. Run '...' - 4. See error... - validations: - required: false -- type: textarea - attributes: - label: Environment - description: | - examples: - - **OS**: Ubuntu 20.04 - - **Node**: 13.14.0 - - **npm**: 7.6.3 - value: | - - OS: - - Node: - - npm: - render: markdown - validations: - required: false -- type: textarea - attributes: - label: Anything else? - description: | - Links? References? Anything that will give us more context about the issue you are encountering! - - Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. - validations: - required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/test2.yml b/.github/ISSUE_TEMPLATE/test2.yml deleted file mode 100644 index 9d0d058..0000000 --- a/.github/ISSUE_TEMPLATE/test2.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Bug Report -description: File a bug report -title: "[Bug]: " -labels: ["bug", "triage"] -assignees: - - octocat -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: input - id: contact - attributes: - label: Contact Details - description: How can we get in touch with you if we need more info? - placeholder: ex. email@example.com - validations: - required: false - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - value: "A bug happened!" - validations: - required: true - - type: dropdown - id: version - attributes: - label: Version - description: What version of our software are you running? - options: - - 1.0.2 (Default) - - 1.0.3 (Edge) - validations: - required: true - - type: dropdown - id: browsers - attributes: - label: What browsers are you seeing the problem on? - multiple: true - options: - - Firefox - - Chrome - - Safari - - Microsoft Edge - - type: textarea - id: logs - attributes: - label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: checkboxes - id: terms - attributes: - label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://example.com) - options: - - label: I agree to follow this project's Code of Conduct - required: true \ No newline at end of file From 5ca3dea3ced01128ec8b8ce113ca3aebea50606b Mon Sep 17 00:00:00 2001 From: ryanmerolle <ryan.merolle@gmail.com> Date: Sun, 30 Jan 2022 17:49:56 -0500 Subject: [PATCH 12/12] tweaks --- .github/ISSUE_TEMPLATE/new_script.yml | 14 ++++++------- .../ISSUE_TEMPLATE/script_feature_request.yml | 21 ++++++++++++------- README.md | 18 +++++++++------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/new_script.yml b/.github/ISSUE_TEMPLATE/new_script.yml index 8452a80..68c2462 100644 --- a/.github/ISSUE_TEMPLATE/new_script.yml +++ b/.github/ISSUE_TEMPLATE/new_script.yml @@ -11,13 +11,6 @@ body: If you're trying to solve a problem but can't figure out how, or if you still need time to work on the details of a proposed new script, please start a [discussion](https://github.com/netbox-community/reports/discussions) instead. - - type: input - attributes: - label: NetBox version - description: What version of NetBox are you currently running? - placeholder: v3.1.6 - validations: - required: true - type: dropdown attributes: label: Request type @@ -26,6 +19,13 @@ body: - New NetBox Report validations: required: true + - type: input + attributes: + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 + validations: + required: true - type: textarea attributes: label: Proposed functionality diff --git a/.github/ISSUE_TEMPLATE/script_feature_request.yml b/.github/ISSUE_TEMPLATE/script_feature_request.yml index c27e35c..bec51a4 100644 --- a/.github/ISSUE_TEMPLATE/script_feature_request.yml +++ b/.github/ISSUE_TEMPLATE/script_feature_request.yml @@ -11,13 +11,6 @@ body: If you're trying to solve a problem but can't figure out how, or if you still need time to work on the details of a proposed new script, please start a [discussion](https://github.com/netbox-community/reports/discussions) instead. - - type: input - attributes: - label: NetBox version - description: What version of NetBox are you currently running? - placeholder: v3.1.6 - validations: - required: true - type: dropdown attributes: label: Request type @@ -26,6 +19,20 @@ body: - Existing NetBox Report validations: required: true + - type: input + attributes: + label: Script/Report Name + description: project relative file path + placeholder: scripts/script.py + validations: + required: true + - type: input + attributes: + label: NetBox version + description: What version of NetBox are you currently running? + placeholder: v3.1.6 + validations: + required: true - type: textarea attributes: label: Proposed functionality diff --git a/README.md b/README.md index 0bd2369..a82bae5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ # Community Reports -A collection of community submitted and maintained NetBox reports and custom scripts. +A collection of community submitted and maintained NetBox [Reports](https://netbox.readthedocs.io/en/stable/customization/reports/) and [Custom Scripts](https://netbox.readthedocs.io/en/stable/customization/custom-scripts/). + +This is not a place to host powershell, bash, or python scripts not leveraging a built in [NetBox customization/extensibility feature](https://netbox.readthedocs.io/en/stable/customization/). + +## Using Reports and Scripts + +See the Netbox documentation for: + +* [Reports](https://netbox.readthedocs.io/en/stable/customization/reports/) +* [Custom scripts](https://netbox.readthedocs.io/en/stable/customization/custom-scripts/) ## Contributing @@ -11,10 +20,3 @@ To contribute a new script, open a pull request. All reports go in the `scripts/ __Nothing in this repository comes with any explicit or implied warranty. For more information see [the license](LICENSE).__ See more in the [CONTRIBUTING](CONTRIBUTING.md) doc. - -## Using Reports and Scripts - -See the Netbox documentation for: - -* [Reports](https://netbox.readthedocs.io/en/stable/customization/reports/) -* [Custom scripts](https://netbox.readthedocs.io/en/stable/customization/custom-scripts//)