From be25833f5afcfcd493369d4aa5a8c7794544525c Mon Sep 17 00:00:00 2001 From: Ben Claussen Date: Mon, 14 May 2018 16:46:12 -0400 Subject: [PATCH] Updates --- .gitignore | 2 +- Functions/Helpers.ps1 | 167 +++++- Functions/IPAM/IPAM.ps1 | 201 +++----- Functions/Setup.ps1 | 19 +- Functions/Virtualization/Virtualization.ps1 | 129 +++-- NetboxPS.psm1 | 20 +- NetboxPS.psproj | 1 + Tests/IPAM.Tests.ps1 | 74 ++- Tests/IPAMChoices.json | 1 + Tests/Virtualization.Tests.ps1 | 7 +- Tests/VirtualizationChoices.json | 1 + dist/NetboxPS.psm1 | 536 ++++++++++++-------- dist/Tests/IPAM.Tests.ps1 | 94 +++- dist/Tests/Virtualization.Tests.ps1 | 7 +- 14 files changed, 790 insertions(+), 469 deletions(-) create mode 100644 Tests/IPAMChoices.json create mode 100644 Tests/VirtualizationChoices.json diff --git a/.gitignore b/.gitignore index 9a03a7d..06796ed 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ *.Package.ps1 CustomMenu.inf Test-Module.ps1 -Staging/* \ No newline at end of file +Staging/ \ No newline at end of file diff --git a/Functions/Helpers.ps1 b/Functions/Helpers.ps1 index cb95ebf..170bcb4 100644 --- a/Functions/Helpers.ps1 +++ b/Functions/Helpers.ps1 @@ -89,17 +89,17 @@ function BuildNewURI { } if ($HTTPS) { - Write-Verbose "Setting scheme to HTTPS" + Write-Verbose " Setting scheme to HTTPS" $Scheme = 'https' } else { - Write-Warning "Connecting via non-secure HTTP is not-recommended" + Write-Warning " Connecting via non-secure HTTP is not-recommended" - Write-Verbose "Setting scheme to HTTP" + Write-Verbose " Setting scheme to HTTP" $Scheme = 'http' if (-not $PSBoundParameters.ContainsKey('Port')) { # Set the port to 80 if the user did not supply it - Write-Verbose "Setting port to 80 as default because it was not supplied by the user" + Write-Verbose " Setting port to 80 as default because it was not supplied by the user" $Port = 80 } } @@ -110,25 +110,142 @@ function BuildNewURI { # Generate the path by trimming excess slashes and whitespace from the $segments[] and joining together $uriBuilder.Path = "api/{0}/" -f ($Segments.ForEach({$_.trim('/').trim()}) -join '/') - Write-Verbose "URIPath: $($uriBuilder.Path)" + Write-Verbose " URIPath: $($uriBuilder.Path)" if ($parameters) { # Loop through the parameters and use the HttpUtility to create a Query string [System.Collections.Specialized.NameValueCollection]$URIParams = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($param in $Parameters.GetEnumerator()) { - Write-Verbose "Adding URI parameter $($param.Key):$($param.Value)" + Write-Verbose " Adding URI parameter $($param.Key):$($param.Value)" $URIParams[$param.Key] = $param.Value } $uriBuilder.Query = $URIParams.ToString() } - Write-Verbose "Completed building URIBuilder" + Write-Verbose " Completed building URIBuilder" # Return the entire UriBuilder object $uriBuilder } +function BuildURIComponents { + [CmdletBinding()] + [OutputType([hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.ArrayList]$URISegments, + + [Parameter(Mandatory = $true)] + [object]$ParametersDictionary, + + [string[]]$SkipParameterByName + ) + + Write-Verbose "Building URI components" + + $URIParameters = [System.Collections.Hashtable]::new() + + foreach ($CmdletParameterName in $ParametersDictionary.Keys) { + if ($CmdletParameterName -in $CommonParameterNames) { + # These are common parameters and should not be appended to the URI + Write-Debug "Skipping parameter $CmdletParameterName" + continue + } + + if ($CmdletParameterName -in $SkipParameterByName) { + Write-Debug "Skipping parameter $CmdletParameterName" + continue + } + + if ($CmdletParameterName -eq 'Id') { + # Check if there is one or more values for Id and build a URI or query as appropriate + if (@($ParametersDictionary[$CmdletParameterName]).Count -gt 1) { + Write-Verbose " Joining IDs for parameter" + $URIParameters['id__in'] = $Id -join ',' + } else { + Write-Verbose " Adding ID to segments" + [void]$uriSegments.Add($ParametersDictionary[$CmdletParameterName]) + } + } elseif ($CmdletParameterName -eq 'Query') { + Write-Verbose " Adding query parameter" + $URIParameters['q'] = $ParametersDictionary[$CmdletParameterName] + } else { + Write-Verbose " Adding $($CmdletParameterName.ToLower()) parameter" + $URIParameters[$CmdletParameterName.ToLower()] = $ParametersDictionary[$CmdletParameterName] + } + } + + return @{ + 'Segments' = [System.Collections.ArrayList]$URISegments + 'Parameters' = $URIParameters + } +} + +function GetChoiceValidValues { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [string]$MajorObject, + + [Parameter(Mandatory = $true)] + [object]$Choice + ) + + $ValidValues = New-Object System.Collections.ArrayList + + if (-not $script:NetboxConfig.Choices.$MajorObject.$Choice) { + throw "Missing choices for $Choice" + } + + [void]$ValidValues.AddRange($script:NetboxConfig.Choices.$MajorObject.$Choice.value) + [void]$ValidValues.AddRange($script:NetboxConfig.Choices.$MajorObject.$Choice.label) + + if ($ValidValues.Count -eq 0) { + throw "Missing valid values for $MajorObject.$Choice" + } + + return [System.Collections.ArrayList]$ValidValues +} + +function ValidateChoice { + [CmdletBinding()] + [OutputType([uint16])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('Circuits', 'Extras', 'IPAM', 'Virtualization', IgnoreCase = $true)] + [string]$MajorObject, + + [Parameter(Mandatory = $true)] + [string]$ChoiceName + ) + + $ValidValues = GetChoiceValidValues -MajorObject $MajorObject -Choice $ChoiceName + + Write-Verbose "Validating $ChoiceName" + Write-Verbose "Checking '$ProvidedValue' against $($ValidValues -join ', ')" + + if ($ValidValues -inotcontains $ProvidedValue) { + throw "Invalid value '$ProvidedValue' for '$ChoiceName'. Must be one of: $($ValidValues -join ', ')" + } + + # Convert the ProvidedValue to the integer value + try { + $intVal = [uint16]"$ProvidedValue" + } catch { + # It must not be a number, get the value from the label + $intVal = [uint16]$script:NetboxConfig.Choices.$MajorObject.$ChoiceName.Where({ + $_.Label -eq $ProvidedValue + }).Value + } + + return $intVal +} + + function GetNetboxAPIErrorBody { param ( @@ -233,9 +350,14 @@ function InvokeNetboxRequest { Write-Verbose "Did NOT find results property on data, returning raw result" return $result } - } + } } + + + +#region Troubleshooting commands + function ThrowNetboxRESTError { $uriSegments = [System.Collections.ArrayList]::new(@('fake', 'url')) @@ -246,8 +368,33 @@ function ThrowNetboxRESTError { InvokeNetboxRequest -URI $uri -Raw } - - +function CreateEnum { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [string]$EnumName, + + [Parameter(Mandatory = $true)] + [pscustomobject]$Values, + + [switch]$PassThru + ) + + $definition = @" +public enum $EnumName +{`n$(foreach ($value in $values) {"`t$($value.label) = $($value.value),`n"}) +} +"@ + if (-not ([System.Management.Automation.PSTypeName]"$EnumName").Type) { + #Write-Host $definition -ForegroundColor Green + Add-Type -TypeDefinition $definition -PassThru:$PassThru + } else { + Write-Warning "EnumType $EnumName already exists." + } +} + +#endregion Troubleshooting commands diff --git a/Functions/IPAM/IPAM.ps1 b/Functions/IPAM/IPAM.ps1 index 4a79fe0..7167c0a 100644 --- a/Functions/IPAM/IPAM.ps1 +++ b/Functions/IPAM/IPAM.ps1 @@ -28,7 +28,7 @@ function VerifyIPAMChoices { Internal function to verify provided values for static choices .DESCRIPTION - When users connect to the API, choices for each major object are cached to the config variable. + When users connect to the API, choices for each major object are cached to the config variable. These values are then utilized to verify if the provided value from a user is valid. .PARAMETER ProvidedValue @@ -63,16 +63,20 @@ function VerifyIPAMChoices { .EXAMPLE PS C:\> VerifyIPAMChoices -ProvidedValue 'Loopback' -IPAddressFamily - >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + + .OUTPUTS + This function returns the integer value if valid. Otherwise, it will throw an error. + + .NOTES + Additional information about the function. .FUNCTIONALITY This cmdlet is intended to be used internally and not exposed to the user - - .OUTPUT - This function returns nothing if the value is valid. Otherwise, it will throw an error. #> - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'service:protocol')] + [OutputType([uint16])] param ( [Parameter(Mandatory = $true)] @@ -111,32 +115,7 @@ function VerifyIPAMChoices { [switch]$ServiceProtocol ) - $ValidValues = New-Object System.Collections.ArrayList - - if (-not $script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName)) { - throw "Missing choices for $($PSCmdlet.ParameterSetName)" - } - - [void]$ValidValues.AddRange($script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).value) - [void]$ValidValues.AddRange($script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).label) - - if ($ValidValues.Count -eq 0) { - throw "Missing valid values for $($PSCmdlet.ParameterSetName)" - } - - if ($ValidValues -inotcontains $ProvidedValue) { - throw "Invalid value '$ProvidedValue' for '$($PSCmdlet.ParameterSetName)'. Must be one of: $($ValidValues -join ', ')" - } - - # Convert the ProvidedValue to the integer value - try { - $intVal = [uint16]"$ProvidedValue" - } catch { - # It must not be a number, get the value from the label - $intVal = [uint16]$script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).Where({$_.Label -eq $ProvidedValue}).Value - } - - return $intVal + ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName } @@ -148,7 +127,8 @@ function Get-NetboxIPAMAggregate { [uint16]$Offset, - [string]$Family, + [ValidateNotNullOrEmpty()] + [object]$Family, [datetime]$Date_Added, @@ -163,32 +143,15 @@ function Get-NetboxIPAMAggregate { [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'aggregates')) - - $URIParameters = @{} - - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } + if ($Family -ne $null) { + $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -AggregateFamily } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'aggregates')) + + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -236,44 +199,23 @@ function Get-NetboxIPAMAddress { [switch]$Raw ) - if ($Family) { + if ($Family -ne $null) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily } - if ($Status) { + if ($Status -ne $null) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus } - if ($Role) { + if ($Role -ne $null) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) - $URIParameters = @{} + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -309,21 +251,17 @@ function Get-NetboxIPAMAvailableIP { [Parameter(Mandatory = $true)] [uint16]$Prefix_ID, - [Alias('Limit')] - [uint16]$NumberOfIPs, + [Alias('NumberOfIPs')] + [uint16]$Limit, [switch]$Raw ) $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes', $Prefix_ID, 'available-ips')) - $uriParameters = @{} + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters -SkipParameterByName 'prefix_id' - if ($NumberOfIPs) { - [void]$uriParameters.Add('limit', $NumberOfIPs) - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $uriParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -458,45 +396,65 @@ function Get-NetboxIPAMPrefix { [switch]$Raw ) - if ($Family) { + if ($Family -ne $null) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -PrefixFamily } - if ($Status) { + if ($Status -ne $null) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -PrefixStatus } - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) - $URIParameters = @{ - } + $URIComponents = BuildURIComponents -ParametersDictionary $PSBoundParameters -URISegments $Segments - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } +function Add-NetboxIPAMAddress { + [CmdletBinding(DefaultParameterSetName = 'CIDR')] + [OutputType([pscustomobject])] + param + ( + [Parameter(Mandatory = $true)] + [string]$Address, + + [object]$Status = 'Active', + + [uint16]$Tenant, + + [uint16]$VRF, + + [object]$Role, + + [uint16]$NAT_Inside, + + [hashtable]$Custom_Fields, + + [uint16]$Interface, + + [string]$Description, + + [switch]$Raw + ) + + $segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + + if ($Role) { + $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole + } + + $URIComponents = BuildURIComponents -URISegments $segments -ParametersDictionary $PSBoundParameters + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Method POST -Body $URIComponents.Parameters -Raw:$Raw +} + @@ -508,8 +466,3 @@ function Get-NetboxIPAMPrefix { - - - - - diff --git a/Functions/Setup.ps1 b/Functions/Setup.ps1 index 1701844..b98177a 100644 --- a/Functions/Setup.ps1 +++ b/Functions/Setup.ps1 @@ -24,15 +24,7 @@ function SetupNetboxConfigVariable { Write-Verbose "Creating NetboxConfig hashtable" $script:NetboxConfig = @{ 'Connected' = $false - 'Choices' = @{ - 'Circuits' = $null - 'DCIM' = $null - 'Extras' = $null - 'IPAM' = $null - 'Secrets' = $null - 'Tenancy' = $null - 'Virtualization' = $null - } + 'Choices' = @{} } } @@ -183,14 +175,16 @@ function Connect-NetboxAPI { } } + Write-Verbose "Caching static choices" $script:NetboxConfig.Choices.Circuits = Get-NetboxCircuitsChoices - #$script:NetboxConfig.Choices.DCIM = Get-NetboxDCIMChoices + #$script:NetboxConfig.Choices.DCIM = Get-NetboxDCIMChoices # Not completed yet $script:NetboxConfig.Choices.Extras = Get-NetboxExtrasChoices $script:NetboxConfig.Choices.IPAM = Get-NetboxIPAMChoices - #$script:NetboxConfig.Choices.Secrets = Get-NetboxSecretsChoices - #$script:NetboxConfig.Choices.Tenancy = Get-NetboxTenancyChoices + #$script:NetboxConfig.Choices.Secrets = Get-NetboxSecretsChoices # Not completed yet + #$script:NetboxConfig.Choices.Tenancy = Get-NetboxTenancyChoices # Not completed yet $script:NetboxConfig.Choices.Virtualization = Get-NetboxVirtualizationChoices + Write-Verbose "Connection process completed" } @@ -201,4 +195,3 @@ function Connect-NetboxAPI { - diff --git a/Functions/Virtualization/Virtualization.ps1 b/Functions/Virtualization/Virtualization.ps1 index c306285..7906201 100644 --- a/Functions/Virtualization/Virtualization.ps1 +++ b/Functions/Virtualization/Virtualization.ps1 @@ -11,6 +11,70 @@ Virtualization object functions #> +function VerifyVirtualizationChoices { +<# + .SYNOPSIS + Internal function to verify provided values for static choices + + .DESCRIPTION + When users connect to the API, choices for each major object are cached to the config variable. + These values are then utilized to verify if the provided value from a user is valid. + + .PARAMETER ProvidedValue + The value to validate against static choices + + .PARAMETER AggregateFamily + Verify against aggregate family values + + .PARAMETER PrefixFamily + Verify against prefix family values + + .PARAMETER PrefixStatus + Verify against prefix status values + + .PARAMETER IPAddressFamily + Verify against ip-address family values + + .PARAMETER IPAddressStatus + Verify against ip-address status values + + .PARAMETER IPAddressRole + Verify against ip-address role values + + .PARAMETER VLANStatus + Verify against VLAN status values + + .PARAMETER ServiceProtocol + Verify against service protocol values + + .EXAMPLE + PS C:\> VerifyIPAMChoices -ProvidedValue 'loopback' -IPAddressRole + + .EXAMPLE + PS C:\> VerifyIPAMChoices -ProvidedValue 'Loopback' -IPAddressFamily + >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + + .FUNCTIONALITY + This cmdlet is intended to be used internally and not exposed to the user + + .OUTPUT + This function returns nothing if the value is valid. Otherwise, it will throw an error. +#> + + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [object]$ProvidedValue, + + [Parameter(ParameterSetName = 'virtual-machine:status', + Mandatory = $true)] + [switch]$VirtualMachineStatus + ) + + ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName +} + #region GET commands function Get-NetboxVirtualizationChoices { @@ -123,7 +187,7 @@ function Get-NetboxVirtualMachine { [Alias('id__in')] [uint16[]]$Id, - [NetboxVirtualMachineStatus]$Status, + [object]$Status, [string]$Tenant, @@ -154,34 +218,15 @@ function Get-NetboxVirtualMachine { [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) - - $URIParameters = @{} - - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } elseif ($CmdletParameterName -eq 'Status') { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].value__ - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } + if ($Status -ne $null) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) + + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -457,7 +502,7 @@ function Add-NetboxVirtualMachine { [uint16]$Tenant, - [NetboxVirtualMachineStatus]$Status = 'Active', + [object]$Status = 'Active', [uint16]$Role, @@ -474,31 +519,17 @@ function Add-NetboxVirtualMachine { [string]$Comments ) + if ($Status -ne $null) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus + } + $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) - $Body = @{} + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters - if (-not $PSBoundParameters.ContainsKey('Status')) { - [void]$PSBoundParameters.Add('Status', $Status) - } + $uri = BuildNewURI -Segments $URIComponents.Segments - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Status') { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].value__ - } else { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments - - InvokeNetboxRequest -URI $uri -Method POST -Body $Body + InvokeNetboxRequest -URI $uri -Method POST -Body $URIComponents.Parameters } function Add-NetboxVirtualInterface { diff --git a/NetboxPS.psm1 b/NetboxPS.psm1 index 2c338e4..9fb03d8 100644 --- a/NetboxPS.psm1 +++ b/NetboxPS.psm1 @@ -6,16 +6,16 @@ $script:CommonParameterNames = New-Object System.Collections.ArrayList SetupNetboxConfigVariable -if (-not ([System.Management.Automation.PSTypeName]'NetboxVirtualMachineStatus').Type) { - Add-Type -TypeDefinition @" -public enum NetboxVirtualMachineStatus -{ - Offline = 0, - Active = 1, - Staged = 3 -} -"@ -} +#if (-not ([System.Management.Automation.PSTypeName]'NetboxVirtualMachineStatus').Type) { +# Add-Type -TypeDefinition @" +#public enum NetboxVirtualMachineStatus +#{ +# Offline = 0, +# Active = 1, +# Staged = 3 +#} +#"@ +#} Export-ModuleMember -Function * diff --git a/NetboxPS.psproj b/NetboxPS.psproj index ee2d4b8..6bafb75 100644 --- a/NetboxPS.psproj +++ b/NetboxPS.psproj @@ -26,6 +26,7 @@ Tests\Virtualization.Tests.ps1 Tests\IPAM.Tests.ps1 Functions\IPAM\IPAM.ps1 + Tests\IPAMChoices.json.txt R:\Netbox\NetboxPS\Test-Module.ps1 \ No newline at end of file diff --git a/Tests/IPAM.Tests.ps1 b/Tests/IPAM.Tests.ps1 index 8ec8aa6..fa76b77 100644 --- a/Tests/IPAM.Tests.ps1 +++ b/Tests/IPAM.Tests.ps1 @@ -47,6 +47,8 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { } InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + $script:NetboxConfig.Choices.IPAM = (Get-Content "$PSScriptRoot\IPAMChoices.json" -ErrorAction Stop | ConvertFrom-Json) + Context -Name "Get-NetboxIPAMAggregate" -Fixture { It "Should request the default number of aggregates" { $Result = Get-NetboxIPAMAggregate @@ -182,31 +184,25 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Authorization | Should -Be "Token faketoken" } - #region TODO: Figure out how to mock/test Verification appropriately... - <# It "Should request with a family number" { - Mock -CommandName 'Get-NetboxIPAMChoices' -ModuleName 'NetboxPS' -MockWith { - return @" -{"aggregate:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:status":[{"label":"Container","value":0},{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"ip-address:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"ip-address:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3},{"label":"DHCP","value":5}],"ip-address:role":[{"label":"Loopback","value":10},{"label":"Secondary","value":20},{"label":"Anycast","value":30},{"label":"VIP","value":40},{"label":"VRRP","value":41},{"label":"HSRP","value":42},{"label":"GLBP","value":43},{"label":"CARP","value":44}],"vlan:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"service:protocol":[{"label":"TCP","value":6},{"label":"UDP","value":17}]} -"@ | ConvertFrom-Json - } - - Mock -CommandName 'Connect-NetboxAPI' -ModuleName 'NetboxPS' -MockWith { - $script:NetboxConfig.Connected = $true - $script:NetboxConfig.Choices.IPAM = Get-NetboxIPAMChoices - } - Connect-NetboxAPI - $Result = Get-NetboxIPAMAddress -Role 4 + $Result = Get-NetboxIPAMAddress -Family 4 Assert-VerifiableMock - Assert-MockCalled -CommandName "Get-NetboxIPAMChoices" $Result.Method | Should -Be 'GET' - $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?role=4' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?family=4' + $Result.Headers.Keys.Count | Should -BeExactly 1 + } + + It "Should request with a family name" { + $Result = Get-NetboxIPAMAddress -Family 'IPv4' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'GET' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?family=4' $Result.Headers.Keys.Count | Should -BeExactly 1 } - #> - #endregion } Context -Name "Get-NetboxIPAMAvailableIP" -Fixture { @@ -233,7 +229,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { } } - Context -Name "Get-NetboxIPAMPrefix" { + Context -Name "Get-NetboxIPAMPrefix" -Fixture { It "Should request the default number of prefixes" { $Result = Get-NetboxIPAMPrefix @@ -311,11 +307,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Authorization | Should -Be "Token faketoken" } - <# It "Should request with family of 4" { - Mock -CommandName "VerifyIPAMChoices" -ModuleName 'NetboxPS' -MockWith { - return 4 - } -Verifiable $Result = Get-NetboxIPAMPrefix -Family 4 Assert-VerifiableMock @@ -325,7 +317,6 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Keys.Count | Should -BeExactly 1 $Result.Headers.Authorization | Should -Be "Token faketoken" } - #> It "Should throw because the mask length is too large" { { @@ -356,6 +347,41 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Authorization | Should -Be "Token faketoken" } } + + Context -Name "Add-NetboxIPAMAddress" -Fixture { + It "Should add a basic IP address" { + $Result = Add-NetboxIPAMAddress -Address '10.0.0.1/24' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":1,"address":"10.0.0.1/24"}' + } + + It "Should add an IP with a status and role names" { + $Result = Add-NetboxIPAMAddress -Address '10.0.0.1/24' -Status 'Reserved' -Role 'Anycast' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":2,"role":30,"address":"10.0.0.1/24"}' + } + + It "Should add an IP with a status and role values" { + $Result = Add-NetboxIPAMAddress -Address '10.0.1.1/24' -Status '1' -Role '10' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":1,"role":10,"address":"10.0.1.1/24"}' + } + } } } diff --git a/Tests/IPAMChoices.json b/Tests/IPAMChoices.json new file mode 100644 index 0000000..3e05e53 --- /dev/null +++ b/Tests/IPAMChoices.json @@ -0,0 +1 @@ +{"aggregate:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:status":[{"label":"Container","value":0},{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"ip-address:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"ip-address:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3},{"label":"DHCP","value":5}],"ip-address:role":[{"label":"Loopback","value":10},{"label":"Secondary","value":20},{"label":"Anycast","value":30},{"label":"VIP","value":40},{"label":"VRRP","value":41},{"label":"HSRP","value":42},{"label":"GLBP","value":43},{"label":"CARP","value":44}],"vlan:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"service:protocol":[{"label":"TCP","value":6},{"label":"UDP","value":17}]} \ No newline at end of file diff --git a/Tests/Virtualization.Tests.ps1 b/Tests/Virtualization.Tests.ps1 index 8174020..9d55c57 100644 --- a/Tests/Virtualization.Tests.ps1 +++ b/Tests/Virtualization.Tests.ps1 @@ -46,6 +46,8 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + $script:NetboxConfig.Choices.Virtualization = (Get-Content "$PSScriptRoot\VirtualizationChoices.json" -ErrorAction Stop | ConvertFrom-Json) + Context -Name "Get-NetboxVirtualMachine" -Fixture { It "Should request the default number of VMs" { $Result = Get-NetboxVirtualMachine @@ -305,6 +307,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } Context -Name "Add-NetboxVirtualMachine" -Fixture { + It "Should add a basic VM" { $Result = Add-NetboxVirtualMachine -Name 'testvm' -Cluster 1 @@ -313,7 +316,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"cluster":1,"name":"testvm","status":1}' + $Result.Body | Should -Be '{"name":"testvm","cluster":1,"status":1}' } It "Should add a VM with CPUs, Memory, Disk, tenancy, and comments" { @@ -324,7 +327,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"tenant":11,"comments":"these are comments","disk":50,"memory":4096,"name":"testvm","cluster":1,"status":1,"vcpus":4}' + $Result.Body | Should -Be '{"tenant":11,"name":"testvm","comments":"these are comments","cluster":1,"status":1,"memory":4096,"vcpus":4,"disk":50}' } } diff --git a/Tests/VirtualizationChoices.json b/Tests/VirtualizationChoices.json new file mode 100644 index 0000000..235ed6c --- /dev/null +++ b/Tests/VirtualizationChoices.json @@ -0,0 +1 @@ +{"virtual-machine:status":[{"label":"Active","value":1},{"label":"Offline","value":0},{"label":"Staged","value":3}]} \ No newline at end of file diff --git a/dist/NetboxPS.psm1 b/dist/NetboxPS.psm1 index 4e5c1d3..1b85d37 100644 --- a/dist/NetboxPS.psm1 +++ b/dist/NetboxPS.psm1 @@ -1,8 +1,8 @@ <# .NOTES -------------------------------------------------------------------------------- - Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.150 - Generated on: 5/11/2018 4:30 PM + Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.152 + Generated on: 5/14/2018 4:35 PM Generated by: Ben Claussen Organization: NEOnet -------------------------------------------------------------------------------- @@ -103,17 +103,17 @@ } if ($HTTPS) { - Write-Verbose "Setting scheme to HTTPS" + Write-Verbose " Setting scheme to HTTPS" $Scheme = 'https' } else { - Write-Warning "Connecting via non-secure HTTP is not-recommended" + Write-Warning " Connecting via non-secure HTTP is not-recommended" - Write-Verbose "Setting scheme to HTTP" + Write-Verbose " Setting scheme to HTTP" $Scheme = 'http' if (-not $PSBoundParameters.ContainsKey('Port')) { # Set the port to 80 if the user did not supply it - Write-Verbose "Setting port to 80 as default because it was not supplied by the user" + Write-Verbose " Setting port to 80 as default because it was not supplied by the user" $Port = 80 } } @@ -124,25 +124,142 @@ # Generate the path by trimming excess slashes and whitespace from the $segments[] and joining together $uriBuilder.Path = "api/{0}/" -f ($Segments.ForEach({$_.trim('/').trim()}) -join '/') - Write-Verbose "URIPath: $($uriBuilder.Path)" + Write-Verbose " URIPath: $($uriBuilder.Path)" if ($parameters) { # Loop through the parameters and use the HttpUtility to create a Query string [System.Collections.Specialized.NameValueCollection]$URIParams = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($param in $Parameters.GetEnumerator()) { - Write-Verbose "Adding URI parameter $($param.Key):$($param.Value)" + Write-Verbose " Adding URI parameter $($param.Key):$($param.Value)" $URIParams[$param.Key] = $param.Value } $uriBuilder.Query = $URIParams.ToString() } - Write-Verbose "Completed building URIBuilder" + Write-Verbose " Completed building URIBuilder" # Return the entire UriBuilder object $uriBuilder } + function BuildURIComponents { + [CmdletBinding()] + [OutputType([hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.ArrayList]$URISegments, + + [Parameter(Mandatory = $true)] + [object]$ParametersDictionary, + + [string[]]$SkipParameterByName + ) + + Write-Verbose "Building URI components" + + $URIParameters = [System.Collections.Hashtable]::new() + + foreach ($CmdletParameterName in $ParametersDictionary.Keys) { + if ($CmdletParameterName -in $CommonParameterNames) { + # These are common parameters and should not be appended to the URI + Write-Debug "Skipping parameter $CmdletParameterName" + continue + } + + if ($CmdletParameterName -in $SkipParameterByName) { + Write-Debug "Skipping parameter $CmdletParameterName" + continue + } + + if ($CmdletParameterName -eq 'Id') { + # Check if there is one or more values for Id and build a URI or query as appropriate + if (@($ParametersDictionary[$CmdletParameterName]).Count -gt 1) { + Write-Verbose " Joining IDs for parameter" + $URIParameters['id__in'] = $Id -join ',' + } else { + Write-Verbose " Adding ID to segments" + [void]$uriSegments.Add($ParametersDictionary[$CmdletParameterName]) + } + } elseif ($CmdletParameterName -eq 'Query') { + Write-Verbose " Adding query parameter" + $URIParameters['q'] = $ParametersDictionary[$CmdletParameterName] + } else { + Write-Verbose " Adding $($CmdletParameterName.ToLower()) parameter" + $URIParameters[$CmdletParameterName.ToLower()] = $ParametersDictionary[$CmdletParameterName] + } + } + + return @{ + 'Segments' = [System.Collections.ArrayList]$URISegments + 'Parameters' = $URIParameters + } + } + + function GetChoiceValidValues { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [string]$MajorObject, + + [Parameter(Mandatory = $true)] + [object]$Choice + ) + + $ValidValues = New-Object System.Collections.ArrayList + + if (-not $script:NetboxConfig.Choices.$MajorObject.$Choice) { + throw "Missing choices for $Choice" + } + + [void]$ValidValues.AddRange($script:NetboxConfig.Choices.$MajorObject.$Choice.value) + [void]$ValidValues.AddRange($script:NetboxConfig.Choices.$MajorObject.$Choice.label) + + if ($ValidValues.Count -eq 0) { + throw "Missing valid values for $MajorObject.$Choice" + } + + return [System.Collections.ArrayList]$ValidValues + } + + function ValidateChoice { + [CmdletBinding()] + [OutputType([uint16])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('Circuits', 'Extras', 'IPAM', 'Virtualization', IgnoreCase = $true)] + [string]$MajorObject, + + [Parameter(Mandatory = $true)] + [string]$ChoiceName + ) + + $ValidValues = GetChoiceValidValues -MajorObject $MajorObject -Choice $ChoiceName + + Write-Verbose "Validating $ChoiceName" + Write-Verbose "Checking '$ProvidedValue' against $($ValidValues -join ', ')" + + if ($ValidValues -inotcontains $ProvidedValue) { + throw "Invalid value '$ProvidedValue' for '$ChoiceName'. Must be one of: $($ValidValues -join ', ')" + } + + # Convert the ProvidedValue to the integer value + try { + $intVal = [uint16]"$ProvidedValue" + } catch { + # It must not be a number, get the value from the label + $intVal = [uint16]$script:NetboxConfig.Choices.$MajorObject.$ChoiceName.Where({ + $_.Label -eq $ProvidedValue + }).Value + } + + return $intVal + } + + function GetNetboxAPIErrorBody { param ( @@ -247,9 +364,14 @@ Write-Verbose "Did NOT find results property on data, returning raw result" return $result } - } + } } + + + + #region Troubleshooting commands + function ThrowNetboxRESTError { $uriSegments = [System.Collections.ArrayList]::new(@('fake', 'url')) @@ -260,8 +382,33 @@ InvokeNetboxRequest -URI $uri -Raw } + function CreateEnum { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [string]$EnumName, + + [Parameter(Mandatory = $true)] + [pscustomobject]$Values, + + [switch]$PassThru + ) + + $definition = @" +public enum $EnumName +{`n$(foreach ($value in $values) {"`t$($value.label) = $($value.value),`n"}) +} +"@ + if (-not ([System.Management.Automation.PSTypeName]"$EnumName").Type) { + #Write-Host $definition -ForegroundColor Green + Add-Type -TypeDefinition $definition -PassThru:$PassThru + } else { + Write-Warning "EnumType $EnumName already exists." + } + } - + #endregion Troubleshooting commands @@ -297,15 +444,7 @@ Write-Verbose "Creating NetboxConfig hashtable" $script:NetboxConfig = @{ 'Connected' = $false - 'Choices' = @{ - 'Circuits' = $null - 'DCIM' = $null - 'Extras' = $null - 'IPAM' = $null - 'Secrets' = $null - 'Tenancy' = $null - 'Virtualization' = $null - } + 'Choices' = @{} } } @@ -456,14 +595,16 @@ } } + Write-Verbose "Caching static choices" $script:NetboxConfig.Choices.Circuits = Get-NetboxCircuitsChoices - #$script:NetboxConfig.Choices.DCIM = Get-NetboxDCIMChoices + #$script:NetboxConfig.Choices.DCIM = Get-NetboxDCIMChoices # Not completed yet $script:NetboxConfig.Choices.Extras = Get-NetboxExtrasChoices $script:NetboxConfig.Choices.IPAM = Get-NetboxIPAMChoices - #$script:NetboxConfig.Choices.Secrets = Get-NetboxSecretsChoices - #$script:NetboxConfig.Choices.Tenancy = Get-NetboxTenancyChoices + #$script:NetboxConfig.Choices.Secrets = Get-NetboxSecretsChoices # Not completed yet + #$script:NetboxConfig.Choices.Tenancy = Get-NetboxTenancyChoices # Not completed yet $script:NetboxConfig.Choices.Virtualization = Get-NetboxVirtualizationChoices + Write-Verbose "Connection process completed" } @@ -474,7 +615,6 @@ - #endregion #region Invoke-Extras_ps1 @@ -626,6 +766,70 @@ Virtualization object functions #> + function VerifyVirtualizationChoices { + <# + .SYNOPSIS + Internal function to verify provided values for static choices + + .DESCRIPTION + When users connect to the API, choices for each major object are cached to the config variable. + These values are then utilized to verify if the provided value from a user is valid. + + .PARAMETER ProvidedValue + The value to validate against static choices + + .PARAMETER AggregateFamily + Verify against aggregate family values + + .PARAMETER PrefixFamily + Verify against prefix family values + + .PARAMETER PrefixStatus + Verify against prefix status values + + .PARAMETER IPAddressFamily + Verify against ip-address family values + + .PARAMETER IPAddressStatus + Verify against ip-address status values + + .PARAMETER IPAddressRole + Verify against ip-address role values + + .PARAMETER VLANStatus + Verify against VLAN status values + + .PARAMETER ServiceProtocol + Verify against service protocol values + + .EXAMPLE + PS C:\> VerifyIPAMChoices -ProvidedValue 'loopback' -IPAddressRole + + .EXAMPLE + PS C:\> VerifyIPAMChoices -ProvidedValue 'Loopback' -IPAddressFamily + >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + + .FUNCTIONALITY + This cmdlet is intended to be used internally and not exposed to the user + + .OUTPUT + This function returns nothing if the value is valid. Otherwise, it will throw an error. + #> + + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [object]$ProvidedValue, + + [Parameter(ParameterSetName = 'virtual-machine:status', + Mandatory = $true)] + [switch]$VirtualMachineStatus + ) + + ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName + } + #region GET commands function Get-NetboxVirtualizationChoices { @@ -738,7 +942,7 @@ [Alias('id__in')] [uint16[]]$Id, - [NetboxVirtualMachineStatus]$Status, + [object]$Status, [string]$Tenant, @@ -769,34 +973,15 @@ [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) - - $URIParameters = @{} - - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } elseif ($CmdletParameterName -eq 'Status') { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].value__ - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } + if ($Status -ne $null) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) + + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -1072,7 +1257,7 @@ [uint16]$Tenant, - [NetboxVirtualMachineStatus]$Status = 'Active', + [object]$Status = 'Active', [uint16]$Role, @@ -1089,31 +1274,17 @@ [string]$Comments ) + if ($Status -ne $null) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus + } + $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) - $Body = @{} + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters - if (-not $PSBoundParameters.ContainsKey('Status')) { - [void]$PSBoundParameters.Add('Status', $Status) - } + $uri = BuildNewURI -Segments $URIComponents.Segments - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Status') { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].value__ - } else { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments - - InvokeNetboxRequest -URI $uri -Method POST -Body $Body + InvokeNetboxRequest -URI $uri -Method POST -Body $URIComponents.Parameters } function Add-NetboxVirtualInterface { @@ -1198,7 +1369,7 @@ Internal function to verify provided values for static choices .DESCRIPTION - When users connect to the API, choices for each major object are cached to the config variable. + When users connect to the API, choices for each major object are cached to the config variable. These values are then utilized to verify if the provided value from a user is valid. .PARAMETER ProvidedValue @@ -1233,16 +1404,20 @@ .EXAMPLE PS C:\> VerifyIPAMChoices -ProvidedValue 'Loopback' -IPAddressFamily - >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + >> Invalid value Loopback for ip-address:family. Must be one of: 4, 6, IPv4, IPv6 + + .OUTPUTS + This function returns the integer value if valid. Otherwise, it will throw an error. + + .NOTES + Additional information about the function. .FUNCTIONALITY This cmdlet is intended to be used internally and not exposed to the user - - .OUTPUT - This function returns nothing if the value is valid. Otherwise, it will throw an error. #> - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'service:protocol')] + [OutputType([uint16])] param ( [Parameter(Mandatory = $true)] @@ -1281,32 +1456,7 @@ [switch]$ServiceProtocol ) - $ValidValues = New-Object System.Collections.ArrayList - - if (-not $script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName)) { - throw "Missing choices for $($PSCmdlet.ParameterSetName)" - } - - [void]$ValidValues.AddRange($script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).value) - [void]$ValidValues.AddRange($script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).label) - - if ($ValidValues.Count -eq 0) { - throw "Missing valid values for $($PSCmdlet.ParameterSetName)" - } - - if ($ValidValues -inotcontains $ProvidedValue) { - throw "Invalid value '$ProvidedValue' for '$($PSCmdlet.ParameterSetName)'. Must be one of: $($ValidValues -join ', ')" - } - - # Convert the ProvidedValue to the integer value - try { - $intVal = [uint16]"$ProvidedValue" - } catch { - # It must not be a number, get the value from the label - $intVal = [uint16]$script:NetboxConfig.Choices.IPAM.$($PSCmdlet.ParameterSetName).Where({$_.Label -eq $ProvidedValue}).Value - } - - return $intVal + ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName } @@ -1318,7 +1468,8 @@ [uint16]$Offset, - [string]$Family, + [ValidateNotNullOrEmpty()] + [object]$Family, [datetime]$Date_Added, @@ -1333,32 +1484,15 @@ [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'aggregates')) - - $URIParameters = @{} - - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } + if ($Family -ne $null) { + $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -AggregateFamily } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'aggregates')) + + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -1406,44 +1540,23 @@ [switch]$Raw ) - if ($Family) { + if ($Family -ne $null) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily } - if ($Status) { + if ($Status -ne $null) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus } - if ($Role) { + if ($Role -ne $null) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) - $URIParameters = @{} + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -1479,21 +1592,17 @@ [Parameter(Mandatory = $true)] [uint16]$Prefix_ID, - [Alias('Limit')] - [uint16]$NumberOfIPs, + [Alias('NumberOfIPs')] + [uint16]$Limit, [switch]$Raw ) $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes', $Prefix_ID, 'available-ips')) - $uriParameters = @{} + $URIComponents = BuildURIComponents -URISegments $uriSegments -ParametersDictionary $PSBoundParameters -SkipParameterByName 'prefix_id' - if ($NumberOfIPs) { - [void]$uriParameters.Add('limit', $NumberOfIPs) - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $uriParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -1628,49 +1737,64 @@ [switch]$Raw ) - if ($Family) { + if ($Family -ne $null) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -PrefixFamily } - if ($Status) { + if ($Status -ne $null) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -PrefixStatus } - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) - $URIParameters = @{ - } + $URIComponents = BuildURIComponents -ParametersDictionary $PSBoundParameters -URISegments $Segments - foreach ($CmdletParameterName in $PSBoundParameters.Keys) { - if ($CmdletParameterName -in $CommonParameterNames) { - # These are common parameters and should not be appended to the URI - Write-Debug "Skipping parameter $CmdletParameterName" - continue - } - - if ($CmdletParameterName -eq 'Id') { - # Check if there is one or more values for Id and build a URI or query as appropriate - if (@($PSBoundParameters[$CmdletParameterName]).Count -gt 1) { - $URIParameters['id__in'] = $Id -join ',' - } else { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } - } elseif ($CmdletParameterName -eq 'Query') { - $URIParameters['q'] = $PSBoundParameters[$CmdletParameterName] - } else { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } - - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } - - - - + function Add-NetboxIPAMAddress { + [CmdletBinding(DefaultParameterSetName = 'CIDR')] + [OutputType([pscustomobject])] + param + ( + [Parameter(Mandatory = $true)] + [string]$Address, + + [object]$Status = 'Active', + + [uint16]$Tenant, + + [uint16]$VRF, + + [object]$Role, + + [uint16]$NAT_Inside, + + [hashtable]$Custom_Fields, + + [uint16]$Interface, + + [string]$Description, + + [switch]$Raw + ) + + $segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + + if ($Role) { + $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole + } + + $URIComponents = BuildURIComponents -URISegments $segments -ParametersDictionary $PSBoundParameters + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Method POST -Body $URIComponents.Parameters -Raw:$Raw + } @@ -1693,16 +1817,16 @@ $script:CommonParameterNames = New-Object System.Collections.ArrayList SetupNetboxConfigVariable -if (-not ([System.Management.Automation.PSTypeName]'NetboxVirtualMachineStatus').Type) { - Add-Type -TypeDefinition @" -public enum NetboxVirtualMachineStatus -{ - Offline = 0, - Active = 1, - Staged = 3 -} -"@ -} +#if (-not ([System.Management.Automation.PSTypeName]'NetboxVirtualMachineStatus').Type) { +# Add-Type -TypeDefinition @" +#public enum NetboxVirtualMachineStatus +#{ +# Offline = 0, +# Active = 1, +# Staged = 3 +#} +#"@ +#} Export-ModuleMember -Function * diff --git a/dist/Tests/IPAM.Tests.ps1 b/dist/Tests/IPAM.Tests.ps1 index ef9b994..fa76b77 100644 --- a/dist/Tests/IPAM.Tests.ps1 +++ b/dist/Tests/IPAM.Tests.ps1 @@ -47,6 +47,8 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { } InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + $script:NetboxConfig.Choices.IPAM = (Get-Content "$PSScriptRoot\IPAMChoices.json" -ErrorAction Stop | ConvertFrom-Json) + Context -Name "Get-NetboxIPAMAggregate" -Fixture { It "Should request the default number of aggregates" { $Result = Get-NetboxIPAMAggregate @@ -182,31 +184,25 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Authorization | Should -Be "Token faketoken" } - #region TODO: Figure out how to mock/test Verification appropriately... - <# It "Should request with a family number" { - Mock -CommandName 'Get-NetboxIPAMChoices' -ModuleName 'NetboxPS' -MockWith { - return @" -{"aggregate:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"prefix:status":[{"label":"Container","value":0},{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"ip-address:family":[{"label":"IPv4","value":4},{"label":"IPv6","value":6}],"ip-address:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3},{"label":"DHCP","value":5}],"ip-address:role":[{"label":"Loopback","value":10},{"label":"Secondary","value":20},{"label":"Anycast","value":30},{"label":"VIP","value":40},{"label":"VRRP","value":41},{"label":"HSRP","value":42},{"label":"GLBP","value":43},{"label":"CARP","value":44}],"vlan:status":[{"label":"Active","value":1},{"label":"Reserved","value":2},{"label":"Deprecated","value":3}],"service:protocol":[{"label":"TCP","value":6},{"label":"UDP","value":17}]} -"@ | ConvertFrom-Json - } - - Mock -CommandName 'Connect-NetboxAPI' -ModuleName 'NetboxPS' -MockWith { - $script:NetboxConfig.Connected = $true - $script:NetboxConfig.Choices.IPAM = Get-NetboxIPAMChoices - } - Connect-NetboxAPI - $Result = Get-NetboxIPAMAddress -Role 4 + $Result = Get-NetboxIPAMAddress -Family 4 Assert-VerifiableMock - Assert-MockCalled -CommandName "Get-NetboxIPAMChoices" $Result.Method | Should -Be 'GET' - $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?role=4' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?family=4' + $Result.Headers.Keys.Count | Should -BeExactly 1 + } + + It "Should request with a family name" { + $Result = Get-NetboxIPAMAddress -Family 'IPv4' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'GET' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/?family=4' $Result.Headers.Keys.Count | Should -BeExactly 1 } - #> - #endregion } Context -Name "Get-NetboxIPAMAvailableIP" -Fixture { @@ -233,7 +229,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { } } - Context -Name "Get-NetboxIPAMPrefix" { + Context -Name "Get-NetboxIPAMPrefix" -Fixture { It "Should request the default number of prefixes" { $Result = Get-NetboxIPAMPrefix @@ -311,11 +307,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Authorization | Should -Be "Token faketoken" } - <# It "Should request with family of 4" { - Mock -CommandName "VerifyIPAMChoices" -ModuleName 'NetboxPS' -MockWith { - return 4 - } -Verifiable $Result = Get-NetboxIPAMPrefix -Family 4 Assert-VerifiableMock @@ -325,7 +317,24 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Keys.Count | Should -BeExactly 1 $Result.Headers.Authorization | Should -Be "Token faketoken" } - #> + + It "Should throw because the mask length is too large" { + { + Get-NetboxIPAMPrefix -Mask_length 128 + } | Should -Throw + } + + It "Should throw because the mask length is too small" { + { + Get-NetboxIPAMPrefix -Mask_length -1 + } | Should -Throw + } + + It "Should not throw because the mask length is just right" { + { + Get-NetboxIPAMPrefix -Mask_length 24 + } | Should -Not -Throw + } It "Should request with mask length 24" { $Result = Get-NetboxIPAMPrefix -Mask_length 24 @@ -337,11 +346,40 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Headers.Keys.Count | Should -BeExactly 1 $Result.Headers.Authorization | Should -Be "Token faketoken" } + } + + Context -Name "Add-NetboxIPAMAddress" -Fixture { + It "Should add a basic IP address" { + $Result = Add-NetboxIPAMAddress -Address '10.0.0.1/24' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":1,"address":"10.0.0.1/24"}' + } - It "Should throw because the mask length is too large" { - { - Get-NetboxIPAMPrefix -Mask_length 128 - } | Should -Throw + It "Should add an IP with a status and role names" { + $Result = Add-NetboxIPAMAddress -Address '10.0.0.1/24' -Status 'Reserved' -Role 'Anycast' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":2,"role":30,"address":"10.0.0.1/24"}' + } + + It "Should add an IP with a status and role values" { + $Result = Add-NetboxIPAMAddress -Address '10.0.1.1/24' -Status '1' -Role '10' + + Assert-VerifiableMock + + $Result.Method | Should -Be 'POST' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":1,"role":10,"address":"10.0.1.1/24"}' } } } diff --git a/dist/Tests/Virtualization.Tests.ps1 b/dist/Tests/Virtualization.Tests.ps1 index 8174020..9d55c57 100644 --- a/dist/Tests/Virtualization.Tests.ps1 +++ b/dist/Tests/Virtualization.Tests.ps1 @@ -46,6 +46,8 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + $script:NetboxConfig.Choices.Virtualization = (Get-Content "$PSScriptRoot\VirtualizationChoices.json" -ErrorAction Stop | ConvertFrom-Json) + Context -Name "Get-NetboxVirtualMachine" -Fixture { It "Should request the default number of VMs" { $Result = Get-NetboxVirtualMachine @@ -305,6 +307,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } Context -Name "Add-NetboxVirtualMachine" -Fixture { + It "Should add a basic VM" { $Result = Add-NetboxVirtualMachine -Name 'testvm' -Cluster 1 @@ -313,7 +316,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"cluster":1,"name":"testvm","status":1}' + $Result.Body | Should -Be '{"name":"testvm","cluster":1,"status":1}' } It "Should add a VM with CPUs, Memory, Disk, tenancy, and comments" { @@ -324,7 +327,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"tenant":11,"comments":"these are comments","disk":50,"memory":4096,"name":"testvm","cluster":1,"status":1,"vcpus":4}' + $Result.Body | Should -Be '{"tenant":11,"name":"testvm","comments":"these are comments","cluster":1,"status":1,"memory":4096,"vcpus":4,"disk":50}' } }