diff --git a/Functions/Circuits/Circuits.ps1 b/Functions/Circuits/Circuits.ps1 index 2235cc3..913b99b 100644 --- a/Functions/Circuits/Circuits.ps1 +++ b/Functions/Circuits/Circuits.ps1 @@ -25,8 +25,9 @@ function Get-NetboxCircuitsChoices { .NOTES Additional information about the function. #> - - [CmdletBinding()] + + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('circuits', '_choices')) diff --git a/Functions/Extras/Extras.ps1 b/Functions/Extras/Extras.ps1 index ecf8b30..63e7435 100644 --- a/Functions/Extras/Extras.ps1 +++ b/Functions/Extras/Extras.ps1 @@ -12,7 +12,8 @@ #> function Get-NetboxExtrasChoices { - [CmdletBinding()] + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('extras', '_choices')) diff --git a/Functions/Helpers.ps1 b/Functions/Helpers.ps1 index 685fac7..5a259d7 100644 --- a/Functions/Helpers.ps1 +++ b/Functions/Helpers.ps1 @@ -145,7 +145,7 @@ function BuildURIComponents { Write-Verbose "Building URI components" - $URIParameters = [System.Collections.Hashtable]::new() + $URIParameters = @{} foreach ($CmdletParameterName in $ParametersDictionary.Keys) { if ($CmdletParameterName -in $CommonParameterNames) { @@ -163,7 +163,7 @@ function BuildURIComponents { # 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 ',' + $URIParameters['id__in'] = $ParametersDictionary[$CmdletParameterName] -join ',' } else { Write-Verbose " Adding ID to segments" [void]$uriSegments.Add($ParametersDictionary[$CmdletParameterName]) @@ -185,6 +185,7 @@ function BuildURIComponents { function GetChoiceValidValues { [CmdletBinding()] + [OutputType([System.Collections.ArrayList])] param ( [Parameter(Mandatory = $true)] @@ -220,13 +221,16 @@ function ValidateChoice { [string]$MajorObject, [Parameter(Mandatory = $true)] - [string]$ChoiceName + [string]$ChoiceName, + + [Parameter(Mandatory = $true)] + [object]$ProvidedValue ) $ValidValues = GetChoiceValidValues -MajorObject $MajorObject -Choice $ChoiceName Write-Verbose "Validating $ChoiceName" - Write-Verbose "Checking '$ProvidedValue' against $($ValidValues -join ', ')" + Write-Verbose "Checking '$ProvidedValue' against [$($ValidValues -join ', ')]" if ($ValidValues -inotcontains $ProvidedValue) { throw "Invalid value '$ProvidedValue' for '$ChoiceName'. Must be one of: $($ValidValues -join ', ')" @@ -263,7 +267,7 @@ function GetNetboxAPIErrorBody { } function InvokeNetboxRequest { - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] diff --git a/Functions/IPAM/IPAM.ps1 b/Functions/IPAM/IPAM.ps1 index 0e42c2c..29ed607 100644 --- a/Functions/IPAM/IPAM.ps1 +++ b/Functions/IPAM/IPAM.ps1 @@ -13,6 +13,7 @@ function Get-NetboxIPAMChoices { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('ipam', '_choices')) @@ -115,12 +116,13 @@ function VerifyIPAMChoices { [switch]$ServiceProtocol ) - ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName + ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName -ProvidedValue $ProvidedValue } function Get-NetboxIPAMAggregate { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param ( [uint16]$Limit, @@ -142,7 +144,7 @@ function Get-NetboxIPAMAggregate { [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -AggregateFamily } @@ -198,15 +200,15 @@ function Get-NetboxIPAMAddress { [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily } - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus } - if ($Role -ne $null) { + if ($null -ne $Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } @@ -395,11 +397,11 @@ function Get-NetboxIPAMPrefix { [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -PrefixFamily } - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -PrefixStatus } @@ -485,7 +487,7 @@ function Add-NetboxIPAMAddress { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus - if ($Role -ne $null) { + if ($null -ne $Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } @@ -558,7 +560,7 @@ function Set-NetboxIPAMAddress { [string]$Address, - [object]$Status = 'Active', + [object]$Status, [uint16]$Tenant, @@ -577,7 +579,9 @@ function Set-NetboxIPAMAddress { [switch]$Force ) - $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + if ($Status) { + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + } if ($Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole diff --git a/Functions/Setup.ps1 b/Functions/Setup.ps1 index b98177a..38de303 100644 --- a/Functions/Setup.ps1 +++ b/Functions/Setup.ps1 @@ -36,15 +36,17 @@ function GetNetboxConfigVariable { } function Set-NetboxHostName { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [string]$Hostname - ) - - $script:NetboxConfig.Hostname = $Hostname.Trim() - $script:NetboxConfig.Hostname + [CmdletBinding(ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([string])] + param + ( + [Parameter(Mandatory = $true)] + [string]$Hostname + ) + + $script:NetboxConfig.Hostname = $Hostname.Trim() + $script:NetboxConfig.Hostname } function Get-NetboxHostname { @@ -52,7 +54,7 @@ function Get-NetboxHostname { param () Write-Verbose "Getting Netbox hostname" - if ($script:NetboxConfig.Hostname -eq $null) { + if ($null -eq $script:NetboxConfig.Hostname) { throw "Netbox Hostname is not set! You may set it with Set-NetboxHostname -Hostname 'hostname.domain.tld'" } @@ -60,34 +62,37 @@ function Get-NetboxHostname { } function Set-NetboxCredentials { - [CmdletBinding(DefaultParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'UserPass')] - param - ( - [Parameter(ParameterSetName = 'CredsObject', - Mandatory = $true)] - [pscredential]$Credentials, - - [Parameter(ParameterSetName = 'UserPass', - Mandatory = $true)] - [string]$Token - ) - - switch ($PsCmdlet.ParameterSetName) { - 'CredsObject' { - $script:NetboxConfig.Credentials = $Credentials - break - } - - 'UserPass' { - $securePW = ConvertTo-SecureString $Token -AsPlainText -Force - $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) - break - } - } - - $script:NetboxConfig.Credentials + [CmdletBinding(DefaultParameterSetName = 'CredsObject', + ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([pscredential], ParameterSetName = 'CredsObject')] + [OutputType([pscredential], ParameterSetName = 'UserPass')] + [OutputType([pscredential])] + param + ( + [Parameter(ParameterSetName = 'CredsObject', + Mandatory = $true)] + [pscredential]$Credentials, + + [Parameter(ParameterSetName = 'UserPass', + Mandatory = $true)] + [string]$Token + ) + + switch ($PsCmdlet.ParameterSetName) { + 'CredsObject' { + $script:NetboxConfig.Credentials = $Credentials + break + } + + 'UserPass' { + $securePW = ConvertTo-SecureString $Token -AsPlainText -Force + $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) + break + } + } + + $script:NetboxConfig.Credentials } function Get-NetboxCredentials { @@ -162,7 +167,7 @@ function Connect-NetboxAPI { try { Write-Verbose "Verifying API connectivity..." - $APIInfo = VerifyAPIConnectivity + $null = VerifyAPIConnectivity $script:NetboxConfig.Connected = $true Write-Verbose "Successfully connected!" } catch { diff --git a/Functions/Virtualization/Virtualization.ps1 b/Functions/Virtualization/Virtualization.ps1 index c4f27bd..e0fe5a3 100644 --- a/Functions/Virtualization/Virtualization.ps1 +++ b/Functions/Virtualization/Virtualization.ps1 @@ -72,13 +72,14 @@ function VerifyVirtualizationChoices { [switch]$VirtualMachineStatus ) - ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName + ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName -ProvidedValue $ProvidedValue } #region GET commands function Get-NetboxVirtualizationChoices { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', '_choices')) @@ -217,7 +218,7 @@ function Get-NetboxVirtualMachine { [switch]$Raw ) - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus } @@ -533,8 +534,69 @@ function Add-NetboxVirtualInterface { InvokeNetboxRequest -URI $uri -Method POST -Body $URIComponents.Parameters } - - #endregion ADD commands +#region SET commands + +function Set-NetboxVirtualMachine { + [CmdletBinding(ConfirmImpact = 'High', + SupportsShouldProcess = $true)] + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true)] + [uint16]$Id, + + [string]$Name, + + [uint16]$Role, + + [uint16]$Cluster, + + [object]$Status, + + [uint16]$Platform, + + [uint16]$Primary_IPv4, + + [uint16]$Primary_IPv6, + + [byte]$VCPUs, + + [uint16]$Memory, + + [uint16]$Disk, + + [uint16]$Tenant, + + [string]$Comments, + + [hashtable]$Custom_Fields, + + [switch]$Force + ) + + if ($Status) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus + } + + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines', $Id)) + + Write-Verbose "Obtaining VM from ID $Id" + + $CurrentVM = Get-NetboxVirtualMachine -Id $Id -ErrorAction Stop + + Write-Verbose "Finished obtaining VM" + + if ($Force -or $pscmdlet.ShouldProcess($CurrentVM.Name, "Set")) { + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters -SkipParameterByName 'Id', 'Force' + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Body $URIComponents.Parameters -Method PATCH + } +} + + +#endregion SET commands \ No newline at end of file diff --git a/NetboxPS.psproj b/NetboxPS.psproj index 6bafb75..50531cb 100644 --- a/NetboxPS.psproj +++ b/NetboxPS.psproj @@ -26,7 +26,8 @@ Tests\Virtualization.Tests.ps1 Tests\IPAM.Tests.ps1 Functions\IPAM\IPAM.ps1 - Tests\IPAMChoices.json.txt + Tests\IPAMChoices.json + Tests\VirtualizationChoices.json R:\Netbox\NetboxPS\Test-Module.ps1 \ No newline at end of file diff --git a/Staging/NetboxPS.psm1 b/Staging/NetboxPS.psm1 index 4e5c1d3..8968ea0 100644 --- a/Staging/NetboxPS.psm1 +++ b/Staging/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/18/2018 11:21 AM 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,146 @@ # 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 = @{} + + 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'] = $ParametersDictionary[$CmdletParameterName] -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()] + [OutputType([System.Collections.ArrayList])] + 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, + + [Parameter(Mandatory = $true)] + [object]$ProvidedValue + ) + + $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 ( @@ -160,7 +281,7 @@ } function InvokeNetboxRequest { - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -237,7 +358,7 @@ # If the user wants the raw value from the API... otherwise return only the actual result if ($Raw) { - Write-Verbose "Returning raw result" + Write-Verbose "Returning raw result by choice" return $result } else { if ($result.psobject.Properties.Name.Contains('results')) { @@ -247,9 +368,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 +386,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 +448,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' = @{} } } @@ -317,15 +460,17 @@ } function Set-NetboxHostName { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [string]$Hostname - ) - - $script:NetboxConfig.Hostname = $Hostname.Trim() - $script:NetboxConfig.Hostname + [CmdletBinding(ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([string])] + param + ( + [Parameter(Mandatory = $true)] + [string]$Hostname + ) + + $script:NetboxConfig.Hostname = $Hostname.Trim() + $script:NetboxConfig.Hostname } function Get-NetboxHostname { @@ -333,7 +478,7 @@ param () Write-Verbose "Getting Netbox hostname" - if ($script:NetboxConfig.Hostname -eq $null) { + if ($null -eq $script:NetboxConfig.Hostname) { throw "Netbox Hostname is not set! You may set it with Set-NetboxHostname -Hostname 'hostname.domain.tld'" } @@ -341,34 +486,37 @@ } function Set-NetboxCredentials { - [CmdletBinding(DefaultParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'UserPass')] - param - ( - [Parameter(ParameterSetName = 'CredsObject', - Mandatory = $true)] - [pscredential]$Credentials, - - [Parameter(ParameterSetName = 'UserPass', - Mandatory = $true)] - [string]$Token - ) - - switch ($PsCmdlet.ParameterSetName) { - 'CredsObject' { - $script:NetboxConfig.Credentials = $Credentials - break - } - - 'UserPass' { - $securePW = ConvertTo-SecureString $Token -AsPlainText -Force - $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) - break - } - } - - $script:NetboxConfig.Credentials + [CmdletBinding(DefaultParameterSetName = 'CredsObject', + ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([pscredential], ParameterSetName = 'CredsObject')] + [OutputType([pscredential], ParameterSetName = 'UserPass')] + [OutputType([pscredential])] + param + ( + [Parameter(ParameterSetName = 'CredsObject', + Mandatory = $true)] + [pscredential]$Credentials, + + [Parameter(ParameterSetName = 'UserPass', + Mandatory = $true)] + [string]$Token + ) + + switch ($PsCmdlet.ParameterSetName) { + 'CredsObject' { + $script:NetboxConfig.Credentials = $Credentials + break + } + + 'UserPass' { + $securePW = ConvertTo-SecureString $Token -AsPlainText -Force + $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) + break + } + } + + $script:NetboxConfig.Credentials } function Get-NetboxCredentials { @@ -443,7 +591,7 @@ try { Write-Verbose "Verifying API connectivity..." - $APIInfo = VerifyAPIConnectivity + $null = VerifyAPIConnectivity $script:NetboxConfig.Connected = $true Write-Verbose "Successfully connected!" } catch { @@ -456,14 +604,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 +624,6 @@ - #endregion #region Invoke-Extras_ps1 @@ -492,7 +641,8 @@ #> function Get-NetboxExtrasChoices { - [CmdletBinding()] + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('extras', '_choices')) @@ -531,8 +681,9 @@ .NOTES Additional information about the function. #> - - [CmdletBinding()] + + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('circuits', '_choices')) @@ -626,15 +777,80 @@ 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 -ProvidedValue $ProvidedValue + } + #region GET commands function Get-NetboxVirtualizationChoices { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', '_choices')) - $uri = BuildNewURI -Segments $uriSegments -Parameters $Parameters + $uri = BuildNewURI -Segments $uriSegments InvokeNetboxRequest -URI $uri } @@ -735,10 +951,9 @@ [string]$Name, - [Alias('id__in')] [uint16[]]$Id, - [NetboxVirtualMachineStatus]$Status, + [object]$Status, [string]$Tenant, @@ -769,34 +984,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 ($null -ne $Status) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) + + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -849,61 +1045,33 @@ [CmdletBinding()] param ( - [Parameter(ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true)] [uint16]$Limit, - [Parameter(ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true)] [uint16]$Offset, [Parameter(ValueFromPipeline = $true)] [uint16]$Id, - [Parameter(ValueFromPipeline = $true)] [string]$Name, - [Parameter(ValueFromPipeline = $true)] [boolean]$Enabled, - [Parameter(ValueFromPipeline = $true)] [uint16]$MTU, - [Parameter(ValueFromPipeline = $true)] [uint16]$Virtual_Machine_Id, - [Parameter(ValueFromPipeline = $true)] [string]$Virtual_Machine, - [Parameter(ValueFromPipeline = $true)] [string]$MAC_Address, - [Parameter(ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true)] [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'interfaces')) + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'interfaces')) - $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') { - [void]$uriSegments.Add($PSBoundParameters[$CmdletParameterName]) - } elseif ($CmdletParameterName -eq 'Enabled') { - $URIParameters[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].ToString().ToLower() - } 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 } @@ -1072,7 +1240,7 @@ [uint16]$Tenant, - [NetboxVirtualMachineStatus]$Status = 'Active', + [object]$Status = 'Active', [uint16]$Role, @@ -1089,31 +1257,15 @@ [string]$Comments ) - $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus - $Body = @{} + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines')) - if (-not $PSBoundParameters.ContainsKey('Status')) { - [void]$PSBoundParameters.Add('Status', $Status) - } + $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 'Status') { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName].value__ - } else { - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } - } + $URI = BuildNewURI -Segments $URIComponents.Segments - $uri = BuildNewURI -Segments $uriSegments - - InvokeNetboxRequest -URI $uri -Method POST -Body $Body + InvokeNetboxRequest -URI $URI -Method POST -Body $URIComponents.Parameters } function Add-NetboxVirtualInterface { @@ -1137,35 +1289,83 @@ [switch]$Raw ) - $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', 'interfaces')) + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'interfaces')) - $Body = @{} + $PSBoundParameters.Enabled = $Enabled - if (-not $PSBoundParameters.ContainsKey('Enabled')) { - [void]$PSBoundParameters.Add('enabled', $Enabled) - } + $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 - } - - $Body[$CmdletParameterName.ToLower()] = $PSBoundParameters[$CmdletParameterName] - } + $uri = BuildNewURI -Segments $URIComponents.Segments - $uri = BuildNewURI -Segments $uriSegments - - InvokeNetboxRequest -URI $uri -Method POST -Body $Body + InvokeNetboxRequest -URI $uri -Method POST -Body $URIComponents.Parameters } - - #endregion ADD commands - #endregion + #region SET commands + + function Set-NetboxVirtualMachine { + [CmdletBinding(ConfirmImpact = 'High', + SupportsShouldProcess = $true)] + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true)] + [uint16]$Id, + + [string]$Name, + + [uint16]$Role, + + [uint16]$Cluster, + + [object]$Status, + + [uint16]$Platform, + + [uint16]$Primary_IPv4, + + [uint16]$Primary_IPv6, + + [byte]$VCPUs, + + [uint16]$Memory, + + [uint16]$Disk, + + [uint16]$Tenant, + + [string]$Comments, + + [hashtable]$Custom_Fields, + + [switch]$Force + ) + + if ($Status) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus + } + + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines', $Id)) + + Write-Verbose "Obtaining VM from ID $Id" + + $CurrentVM = Get-NetboxVirtualMachine -Id $Id -ErrorAction Stop + + Write-Verbose "Finished obtaining VM" + + if ($Force -or $pscmdlet.ShouldProcess($CurrentVM.Name, "Set")) { + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters -SkipParameterByName 'Id', 'Force' + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Body $URIComponents.Parameters -Method PATCH + } + } + + + #endregion SET commands#endregion #region Invoke-IPAM_ps1 <# @@ -1198,7 +1398,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 +1433,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,44 +1485,20 @@ [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 -ProvidedValue $ProvidedValue } function Get-NetboxIPAMAggregate { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param ( [uint16]$Limit, [uint16]$Offset, - [string]$Family, + [object]$Family, [datetime]$Date_Added, @@ -1333,32 +1513,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 ($null -ne $Family) { + $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -AggregateFamily } - $uri = BuildNewURI -Segments $uriSegments -Parameters $URIParameters + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'aggregates')) + + $URIComponents = BuildURIComponents -URISegments $Segments.Clone() -ParametersDictionary $PSBoundParameters + + $uri = BuildNewURI -Segments $URIComponents.Segments -Parameters $URIComponents.Parameters InvokeNetboxRequest -URI $uri -Raw:$Raw } @@ -1406,44 +1569,23 @@ [switch]$Raw ) - if ($Family) { - $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily + if ($null -ne $Family) { + $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily } - if ($Status) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus } - if ($Role) { + if ($null -ne $Role) { $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 +1621,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')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes', $Prefix_ID, 'available-ips')) - $uriParameters = @{} + $URIComponents = BuildURIComponents -URISegments $Segments -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,51 +1766,209 @@ [switch]$Raw ) - if ($Family) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -PrefixFamily } - if ($Status) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -PrefixStatus } - $uriSegments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'prefixes')) - $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 } + function Add-NetboxIPAMAddress { + <# + .SYNOPSIS + Add a new IP address to Netbox + + .DESCRIPTION + Adds a new IP address to Netbox with a status of Active by default. + + .PARAMETER Address + IP address in CIDR notation: 192.168.1.1/24 + + .PARAMETER Status + Status of the IP. Defaults to Active + + .PARAMETER Tenant + Tenant ID + + .PARAMETER VRF + VRF ID + + .PARAMETER Role + Role such as anycast, loopback, etc... Defaults to nothing + + .PARAMETER NAT_Inside + ID of IP for NAT + + .PARAMETER Custom_Fields + Custom field hash table. Will be validated by the API service + + .PARAMETER Interface + ID of interface to apply IP + + .PARAMETER Description + Description of IP address + + .PARAMETER Raw + Return raw results from API service + + .EXAMPLE + PS C:\> Add-NetboxIPAMAddress + + .NOTES + Additional information about the function. + #> + + [CmdletBinding()] + [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 + ) + + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + + if ($null -ne $Role) { + $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole + } + + $segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + + $URIComponents = BuildURIComponents -URISegments $segments -ParametersDictionary $PSBoundParameters + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Method POST -Body $URIComponents.Parameters -Raw:$Raw + } + function Remove-NetboxIPAMAddress { + <# + .SYNOPSIS + Remove an IP address from Netbox + + .DESCRIPTION + Removes/deletes an IP address from Netbox by ID and optional other filters + + .PARAMETER Id + A description of the Id parameter. + + .PARAMETER Force + A description of the Force parameter. + + .PARAMETER Query + A description of the Query parameter. + + .EXAMPLE + PS C:\> Remove-NetboxIPAMAddress -Id $value1 + + .NOTES + Additional information about the function. + #> + + [CmdletBinding(ConfirmImpact = 'High', + SupportsShouldProcess = $true)] + param + ( + [Parameter(Mandatory = $true)] + [uint16[]]$Id, + + [switch]$Force + ) + + $CurrentIPs = @(Get-NetboxIPAMAddress -Id $Id -ErrorAction Stop) + + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses')) + + foreach ($IP in $CurrentIPs) { + if ($Force -or $pscmdlet.ShouldProcess($IP.Address, "Delete")) { + $URIComponents = BuildURIComponents -URISegments $Segments.Clone() -ParametersDictionary @{'id' = $IP.Id} + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Method DELETE + } + } + } - - - - + function Set-NetboxIPAMAddress { + [CmdletBinding(ConfirmImpact = 'High', + SupportsShouldProcess = $true)] + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true)] + [uint16]$Id, + + [string]$Address, + + [object]$Status, + + [uint16]$Tenant, + + [uint16]$VRF, + + [object]$Role, + + [uint16]$NAT_Inside, + + [hashtable]$Custom_Fields, + + [uint16]$Interface, + + [string]$Description, + + [switch]$Force + ) + + if ($Status) { + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + } + + if ($Role) { + $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole + } + + $Segments = [System.Collections.ArrayList]::new(@('ipam', 'ip-addresses', $Id)) + + Write-Verbose "Obtaining IPs from ID $Id" + $CurrentIP = Get-NetboxIPAMAddress -Id $Id -ErrorAction Stop + + if ($Force -or $PSCmdlet.ShouldProcess($($CurrentIP | Select-Object -ExpandProperty 'Address'), 'Set')) { + $URIComponents = BuildURIComponents -URISegments $Segments.Clone() -ParametersDictionary $PSBoundParameters -SkipParameterByName 'Id', 'Force' + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Body $URIComponents.Parameters -Method PATCH + } + } @@ -1693,16 +1989,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/Staging/Tests/Helpers.Tests.ps1 b/Staging/Tests/Helpers.Tests.ps1 index 395c666..331da0f 100644 --- a/Staging/Tests/Helpers.Tests.ps1 +++ b/Staging/Tests/Helpers.Tests.ps1 @@ -31,8 +31,8 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { return $true } -ModuleName 'NetboxPS' - Context "Building URI tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + Context -Name "Building URIBuilder" -Fixture { It "Should give a basic URI object" { BuildNewURI -HostName 'netbox.domain.com' | Should -BeOfType [System.UriBuilder] } @@ -85,10 +85,70 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $URIBuilder.URI.AbsoluteURI | Should -BeExactly 'https://netbox.domain.com/api/seg1/seg2/?param1=paramval1¶m2=paramval2' } } - } - - Context "Invoking request tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + + Context -Name "Building URI components" -Fixture { + It "Should give a basic hashtable" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + } + + It "Should add a single ID parameter to the segments" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = 123} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2", '123') + $URIComponents.Parameters.Count | Should -BeExactly 0 + $URIComponents.Parameters | Should -BeOfType [hashtable] + } + + It "Should add multiple IDs to the parameters id__in" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = "123", "456"} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['id__in'] | Should -Be '123,456' + } + + It "Should skip a particular parameter name" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1; 'param2' = 2} -SkipParameterByName 'param2' + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + $URIComponents.Parameters['param2'] | Should -BeNullOrEmpty + } + + It "Should add a query (q) parameter" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'query' = 'mytestquery'} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['q'] | Should -Be 'mytestquery' + } + } + + Context -Name "Invoking request tests" -Fixture { Mock -CommandName 'Invoke-RestMethod' -Verifiable -MockWith { # Return an object of the items we would normally pass to Invoke-RestMethod return [pscustomobject]@{ @@ -123,7 +183,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result = InvokeNetboxRequest -URI $URIBuilder -Raw Assert-VerifiableMock - + $Result.Method | Should -Be 'GET' $Result.Uri | Should -Be $URIBuilder.Uri.AbsoluteUri $Result.Headers | Should -BeOfType [System.Collections.HashTable] @@ -136,7 +196,9 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { It "Should generate a POST request with body" { $URIBuilder = BuildNewURI -Hostname "netbox.domain.com" -Segments 'seg1', 'seg2' - $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{'bodyparam1' = 'val1'} -Raw + $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{ + 'bodyparam1' = 'val1' + } -Raw Assert-VerifiableMock @@ -161,6 +223,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result.Method | Should -Be 'POST' $Result.Body | Should -Be '{"bodyparam1":"val1"}' + $Result.Headers.Count | Should -BeExactly 2 $Result.Headers.Authorization | Should -Be "Token faketoken" $Result.Headers.Connection | Should -Be "keep-alive" } @@ -179,6 +242,235 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { } | Should -Throw } } + + Context -Name "Validating choices" -Fixture { + $script:NetboxConfig.Choices.Virtualization = (Get-Content "$PSScriptRoot\VirtualizationChoices.json" -ErrorAction Stop | ConvertFrom-Json) + $script:NetboxConfig.Choices.IPAM = (Get-Content "$PSScriptRoot\IPAMChoices.json" -ErrorAction Stop | ConvertFrom-Json) + + Context -Name "Virtualization choices" -Fixture { + $MajorObject = 'Virtualization' + + It "Should return a valid integer for status when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer for status when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 0 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 0 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Fake' + } | Should -Throw + } + } + + Context -Name "IPAM choices" -Fixture { + $MajorObject = 'IPAM' + + Context -Name "aggregate:family" -Fixture { + $ChoiceName = 'aggregate:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:family" { + $ChoiceName = 'prefix:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:status" { + $ChoiceName = 'prefix:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:family" { + $ChoiceName = 'ip-address:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:status" { + $ChoiceName = 'ip-address:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:role" { + $ChoiceName = 'ip-address:role' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Anycast' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 30 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + } | Should -Throw + } + } + + Context -Name "vlan:status" { + $ChoiceName = 'vlan:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "service:protocol" { + $ChoiceName = 'service:protocol' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'TCP' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 6 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + } + + + } + + } } diff --git a/Staging/Tests/IPAM.Tests.ps1 b/Staging/Tests/IPAM.Tests.ps1 index ef9b994..fd21b2d 100644 --- a/Staging/Tests/IPAM.Tests.ps1 +++ b/Staging/Tests/IPAM.Tests.ps1 @@ -47,6 +47,12 @@ 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 "VerifyIPAMChoices" -Fixture { + #It "Should return a valid integer" + } + Context -Name "Get-NetboxIPAMAggregate" -Fixture { It "Should request the default number of aggregates" { $Result = Get-NetboxIPAMAggregate @@ -182,31 +188,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 +233,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 +311,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 +321,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 +350,118 @@ 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,"address":"10.0.0.1/24","role":30}' + } + + 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,"address":"10.0.1.1/24","role":10}' + } + } + + Context -Name "Remove-NetboxIPAMAddress" -Fixture { + It "Should remove a single IP" { + Mock -CommandName "Get-NetboxIPAMAddress" -ModuleName NetboxPS -MockWith { + return @{ + 'address' = '10.1.1.1/24' + 'id' = 4109 + } + } + + $Result = Remove-NetboxIPAMAddress -Id '4109' -Force + + Assert-VerifiableMock + Assert-MockCalled -CommandName "Get-NetboxIPAMAddress" -Times 1 + + $Result.Method | Should -Be 'DELETE' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be $null + } + + It "Should remove multiple IPs" { + Mock -CommandName "Get-NetboxIPAMAddress" -ModuleName NetboxPS -MockWith { + return @( + @{ + 'address' = '10.1.1.1/24' + 'id' = 4109 + }, + @{ + 'address' = '10.1.1.2/24' + 'id' = 4110 + } + ) + } + + $Result = Remove-NetboxIPAMAddress -Id 4109, 4110 -Force + + Assert-VerifiableMock + Assert-MockCalled -CommandName "Get-NetboxIPAMAddress" -Times 2 + + $Result.Method | Should -Be 'DELETE', 'DELETE' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/', 'https://netbox.domain.com/api/ipam/ip-addresses/4110/' + $Result.Headers.Keys.Count | Should -BeExactly 2 + } + } + + Context -Name "Set-NetboxIPAMAddress" -Fixture { + It "Should set an IP with a new status" { + Mock -CommandName "Get-NetboxIPAMAddress" -ModuleName NetboxPS -MockWith { + return @{ + 'address' = '10.1.1.1/24' + 'id' = 4109 + } + } + + $Result = Set-NetboxIPAMAddress -Id '4109' -Status 2 -Force + + Assert-VerifiableMock + Assert-MockCalled -CommandName "Get-NetboxIPAMAddress" -Times 1 + + $Result.Method | Should -Be 'PATCH' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"status":2}' + } + + It "Should set an IP with VRF, Tenant, and Description" { + $Result = Set-NetboxIPAMAddress -Id 4109 -VRF 10 -Tenant 14 -Description 'Test description' -Force + + Assert-VerifiableMock + Assert-MockCalled -CommandName "Get-NetboxIPAMAddress" -Times 1 + + $Result.Method | Should -Be 'PATCH' + $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"vrf":10,"description":"Test description","tenant":14}' } } } diff --git a/Staging/Tests/Virtualization.Tests.ps1 b/Staging/Tests/Virtualization.Tests.ps1 index 8174020..6b68819 100644 --- a/Staging/Tests/Virtualization.Tests.ps1 +++ b/Staging/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 @@ -128,7 +130,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } } - Context -Name "Get-VirtualMachineInterface" -Fixture { + Context -Name "Get-NetboxVirtualMachineInterface" -Fixture { It "Should request the default number of interfaces" { $Result = Get-NetboxVirtualMachineInterface @@ -190,7 +192,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } } - Context -Name "Get-VirtualMachineCluster" -Fixture { + Context -Name "Get-NetboxVirtualMachineCluster" -Fixture { It "Should request the default number of clusters" { $Result = Get-NetboxVirtualizationCluster @@ -262,7 +264,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { } } - Context -Name "Get-VirtualMachineClusterGroup" -Fixture { + Context -Name "Get-NetboxVirtualMachineClusterGroup" -Fixture { It "Should request the default number of cluster groups" { $Result = Get-NetboxVirtualizationClusterGroup @@ -351,6 +353,43 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Body | Should -Be '{"mtu":1500,"description":"Test description","enabled":true,"virtual_machine":10,"name":"Ethernet0","mac_address":"11:22:33:44:55:66"}' } } + + Context -Name "Set-NetboxVirtualMachine" -Fixture { + Mock -CommandName "Get-NetboxVirtualMachine" -ModuleName NetboxPS -MockWith { + return @{ + 'Id' = 1234 + 'Name' = 'TestVM' + } + } + + It "Should set a VM to a new name" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"name":"newtestname"}' + } + + It "Should set a VM with a new name, cluster, platform, and status" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Cluster 10 -Platform 15 -Status 'Offline' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"cluster":10,"platform":15,"name":"newtestname","status":0}' + } + + It "Should throw because of an invalid status" { + { Set-NetboxVirtualMachine -Id 1234 -Status 'Fake' -Force } | Should -Throw + + Assert-VerifiableMock + } + } } } diff --git a/Tests/Helpers.Tests.ps1 b/Tests/Helpers.Tests.ps1 index 395c666..331da0f 100644 --- a/Tests/Helpers.Tests.ps1 +++ b/Tests/Helpers.Tests.ps1 @@ -31,8 +31,8 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { return $true } -ModuleName 'NetboxPS' - Context "Building URI tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + Context -Name "Building URIBuilder" -Fixture { It "Should give a basic URI object" { BuildNewURI -HostName 'netbox.domain.com' | Should -BeOfType [System.UriBuilder] } @@ -85,10 +85,70 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $URIBuilder.URI.AbsoluteURI | Should -BeExactly 'https://netbox.domain.com/api/seg1/seg2/?param1=paramval1¶m2=paramval2' } } - } - - Context "Invoking request tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + + Context -Name "Building URI components" -Fixture { + It "Should give a basic hashtable" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + } + + It "Should add a single ID parameter to the segments" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = 123} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2", '123') + $URIComponents.Parameters.Count | Should -BeExactly 0 + $URIComponents.Parameters | Should -BeOfType [hashtable] + } + + It "Should add multiple IDs to the parameters id__in" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = "123", "456"} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['id__in'] | Should -Be '123,456' + } + + It "Should skip a particular parameter name" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1; 'param2' = 2} -SkipParameterByName 'param2' + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + $URIComponents.Parameters['param2'] | Should -BeNullOrEmpty + } + + It "Should add a query (q) parameter" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'query' = 'mytestquery'} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['q'] | Should -Be 'mytestquery' + } + } + + Context -Name "Invoking request tests" -Fixture { Mock -CommandName 'Invoke-RestMethod' -Verifiable -MockWith { # Return an object of the items we would normally pass to Invoke-RestMethod return [pscustomobject]@{ @@ -123,7 +183,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result = InvokeNetboxRequest -URI $URIBuilder -Raw Assert-VerifiableMock - + $Result.Method | Should -Be 'GET' $Result.Uri | Should -Be $URIBuilder.Uri.AbsoluteUri $Result.Headers | Should -BeOfType [System.Collections.HashTable] @@ -136,7 +196,9 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { It "Should generate a POST request with body" { $URIBuilder = BuildNewURI -Hostname "netbox.domain.com" -Segments 'seg1', 'seg2' - $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{'bodyparam1' = 'val1'} -Raw + $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{ + 'bodyparam1' = 'val1' + } -Raw Assert-VerifiableMock @@ -161,6 +223,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result.Method | Should -Be 'POST' $Result.Body | Should -Be '{"bodyparam1":"val1"}' + $Result.Headers.Count | Should -BeExactly 2 $Result.Headers.Authorization | Should -Be "Token faketoken" $Result.Headers.Connection | Should -Be "keep-alive" } @@ -179,6 +242,235 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { } | Should -Throw } } + + Context -Name "Validating choices" -Fixture { + $script:NetboxConfig.Choices.Virtualization = (Get-Content "$PSScriptRoot\VirtualizationChoices.json" -ErrorAction Stop | ConvertFrom-Json) + $script:NetboxConfig.Choices.IPAM = (Get-Content "$PSScriptRoot\IPAMChoices.json" -ErrorAction Stop | ConvertFrom-Json) + + Context -Name "Virtualization choices" -Fixture { + $MajorObject = 'Virtualization' + + It "Should return a valid integer for status when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer for status when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 0 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 0 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Fake' + } | Should -Throw + } + } + + Context -Name "IPAM choices" -Fixture { + $MajorObject = 'IPAM' + + Context -Name "aggregate:family" -Fixture { + $ChoiceName = 'aggregate:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:family" { + $ChoiceName = 'prefix:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:status" { + $ChoiceName = 'prefix:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:family" { + $ChoiceName = 'ip-address:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:status" { + $ChoiceName = 'ip-address:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:role" { + $ChoiceName = 'ip-address:role' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Anycast' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 30 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + } | Should -Throw + } + } + + Context -Name "vlan:status" { + $ChoiceName = 'vlan:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "service:protocol" { + $ChoiceName = 'service:protocol' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'TCP' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 6 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + } + + + } + + } } diff --git a/Tests/IPAM.Tests.ps1 b/Tests/IPAM.Tests.ps1 index 6e6485b..fd21b2d 100644 --- a/Tests/IPAM.Tests.ps1 +++ b/Tests/IPAM.Tests.ps1 @@ -49,6 +49,10 @@ 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 "VerifyIPAMChoices" -Fixture { + #It "Should return a valid integer" + } + Context -Name "Get-NetboxIPAMAggregate" -Fixture { It "Should request the default number of aggregates" { $Result = Get-NetboxIPAMAggregate @@ -368,7 +372,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $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"}' + $Result.Body | Should -Be '{"status":2,"address":"10.0.0.1/24","role":30}' } It "Should add an IP with a status and role values" { @@ -379,7 +383,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $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"}' + $Result.Body | Should -Be '{"status":1,"address":"10.0.1.1/24","role":10}' } } @@ -457,7 +461,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Method | Should -Be 'PATCH' $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"description":"Test description","status":1,"tenant":14,"vrf":10}' + $Result.Body | Should -Be '{"vrf":10,"description":"Test description","tenant":14}' } } } diff --git a/Tests/Virtualization.Tests.ps1 b/Tests/Virtualization.Tests.ps1 index 665b69a..6b68819 100644 --- a/Tests/Virtualization.Tests.ps1 +++ b/Tests/Virtualization.Tests.ps1 @@ -307,7 +307,6 @@ 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 @@ -316,7 +315,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 '{"name":"testvm","cluster":1,"status":1}' + $Result.Body | Should -Be '{"cluster":1,"name":"testvm","status":1}' } It "Should add a VM with CPUs, Memory, Disk, tenancy, and comments" { @@ -327,7 +326,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,"name":"testvm","comments":"these are comments","cluster":1,"status":1,"memory":4096,"vcpus":4,"disk":50}' + $Result.Body | Should -Be '{"tenant":11,"comments":"these are comments","disk":50,"memory":4096,"name":"testvm","cluster":1,"status":1,"vcpus":4}' } } @@ -340,7 +339,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/interfaces/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"virtual_machine":10,"enabled":true,"name":"Ethernet0"}' + $Result.Body | Should -Be '{"virtual_machine":10,"name":"Ethernet0","enabled":true}' } It "Should add an interface with a MAC, MTU, and Description" { @@ -351,7 +350,44 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/interfaces/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"description":"Test description","virtual_machine":10,"enabled":true,"name":"Ethernet0","mtu":1500,"mac_address":"11:22:33:44:55:66"}' + $Result.Body | Should -Be '{"mtu":1500,"description":"Test description","enabled":true,"virtual_machine":10,"name":"Ethernet0","mac_address":"11:22:33:44:55:66"}' + } + } + + Context -Name "Set-NetboxVirtualMachine" -Fixture { + Mock -CommandName "Get-NetboxVirtualMachine" -ModuleName NetboxPS -MockWith { + return @{ + 'Id' = 1234 + 'Name' = 'TestVM' + } + } + + It "Should set a VM to a new name" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"name":"newtestname"}' + } + + It "Should set a VM with a new name, cluster, platform, and status" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Cluster 10 -Platform 15 -Status 'Offline' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"cluster":10,"platform":15,"name":"newtestname","status":0}' + } + + It "Should throw because of an invalid status" { + { Set-NetboxVirtualMachine -Id 1234 -Status 'Fake' -Force } | Should -Throw + + Assert-VerifiableMock } } } diff --git a/dist/NetboxPS.psm1 b/dist/NetboxPS.psm1 index 105eddd..8968ea0 100644 --- a/dist/NetboxPS.psm1 +++ b/dist/NetboxPS.psm1 @@ -2,7 +2,7 @@ .NOTES -------------------------------------------------------------------------------- Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2018 v5.5.152 - Generated on: 5/15/2018 4:52 PM + Generated on: 5/18/2018 11:21 AM Generated by: Ben Claussen Organization: NEOnet -------------------------------------------------------------------------------- @@ -159,7 +159,7 @@ Write-Verbose "Building URI components" - $URIParameters = [System.Collections.Hashtable]::new() + $URIParameters = @{} foreach ($CmdletParameterName in $ParametersDictionary.Keys) { if ($CmdletParameterName -in $CommonParameterNames) { @@ -177,7 +177,7 @@ # 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 ',' + $URIParameters['id__in'] = $ParametersDictionary[$CmdletParameterName] -join ',' } else { Write-Verbose " Adding ID to segments" [void]$uriSegments.Add($ParametersDictionary[$CmdletParameterName]) @@ -199,6 +199,7 @@ function GetChoiceValidValues { [CmdletBinding()] + [OutputType([System.Collections.ArrayList])] param ( [Parameter(Mandatory = $true)] @@ -234,13 +235,16 @@ [string]$MajorObject, [Parameter(Mandatory = $true)] - [string]$ChoiceName + [string]$ChoiceName, + + [Parameter(Mandatory = $true)] + [object]$ProvidedValue ) $ValidValues = GetChoiceValidValues -MajorObject $MajorObject -Choice $ChoiceName Write-Verbose "Validating $ChoiceName" - Write-Verbose "Checking '$ProvidedValue' against $($ValidValues -join ', ')" + Write-Verbose "Checking '$ProvidedValue' against [$($ValidValues -join ', ')]" if ($ValidValues -inotcontains $ProvidedValue) { throw "Invalid value '$ProvidedValue' for '$ChoiceName'. Must be one of: $($ValidValues -join ', ')" @@ -277,7 +281,7 @@ } function InvokeNetboxRequest { - [CmdletBinding(SupportsShouldProcess = $true)] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -456,15 +460,17 @@ public enum $EnumName } function Set-NetboxHostName { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [string]$Hostname - ) - - $script:NetboxConfig.Hostname = $Hostname.Trim() - $script:NetboxConfig.Hostname + [CmdletBinding(ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([string])] + param + ( + [Parameter(Mandatory = $true)] + [string]$Hostname + ) + + $script:NetboxConfig.Hostname = $Hostname.Trim() + $script:NetboxConfig.Hostname } function Get-NetboxHostname { @@ -472,7 +478,7 @@ public enum $EnumName param () Write-Verbose "Getting Netbox hostname" - if ($script:NetboxConfig.Hostname -eq $null) { + if ($null -eq $script:NetboxConfig.Hostname) { throw "Netbox Hostname is not set! You may set it with Set-NetboxHostname -Hostname 'hostname.domain.tld'" } @@ -480,34 +486,37 @@ public enum $EnumName } function Set-NetboxCredentials { - [CmdletBinding(DefaultParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'CredsObject')] - [OutputType([pscredential], ParameterSetName = 'UserPass')] - param - ( - [Parameter(ParameterSetName = 'CredsObject', - Mandatory = $true)] - [pscredential]$Credentials, - - [Parameter(ParameterSetName = 'UserPass', - Mandatory = $true)] - [string]$Token - ) - - switch ($PsCmdlet.ParameterSetName) { - 'CredsObject' { - $script:NetboxConfig.Credentials = $Credentials - break - } - - 'UserPass' { - $securePW = ConvertTo-SecureString $Token -AsPlainText -Force - $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) - break - } - } - - $script:NetboxConfig.Credentials + [CmdletBinding(DefaultParameterSetName = 'CredsObject', + ConfirmImpact = 'Medium', + SupportsShouldProcess = $true)] + [OutputType([pscredential], ParameterSetName = 'CredsObject')] + [OutputType([pscredential], ParameterSetName = 'UserPass')] + [OutputType([pscredential])] + param + ( + [Parameter(ParameterSetName = 'CredsObject', + Mandatory = $true)] + [pscredential]$Credentials, + + [Parameter(ParameterSetName = 'UserPass', + Mandatory = $true)] + [string]$Token + ) + + switch ($PsCmdlet.ParameterSetName) { + 'CredsObject' { + $script:NetboxConfig.Credentials = $Credentials + break + } + + 'UserPass' { + $securePW = ConvertTo-SecureString $Token -AsPlainText -Force + $script:NetboxConfig.Credentials = [System.Management.Automation.PSCredential]::new('notapplicable', $securePW) + break + } + } + + $script:NetboxConfig.Credentials } function Get-NetboxCredentials { @@ -582,7 +591,7 @@ public enum $EnumName try { Write-Verbose "Verifying API connectivity..." - $APIInfo = VerifyAPIConnectivity + $null = VerifyAPIConnectivity $script:NetboxConfig.Connected = $true Write-Verbose "Successfully connected!" } catch { @@ -632,7 +641,8 @@ public enum $EnumName #> function Get-NetboxExtrasChoices { - [CmdletBinding()] + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('extras', '_choices')) @@ -671,8 +681,9 @@ public enum $EnumName .NOTES Additional information about the function. #> - - [CmdletBinding()] + + [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('circuits', '_choices')) @@ -827,13 +838,14 @@ public enum $EnumName [switch]$VirtualMachineStatus ) - ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName + ValidateChoice -MajorObject 'Virtualization' -ChoiceName $PSCmdlet.ParameterSetName -ProvidedValue $ProvidedValue } #region GET commands function Get-NetboxVirtualizationChoices { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param () $uriSegments = [System.Collections.ArrayList]::new(@('virtualization', '_choices')) @@ -972,7 +984,7 @@ public enum $EnumName [switch]$Raw ) - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus } @@ -1288,12 +1300,72 @@ public enum $EnumName InvokeNetboxRequest -URI $uri -Method POST -Body $URIComponents.Parameters } - - #endregion ADD commands - #endregion + #region SET commands + + function Set-NetboxVirtualMachine { + [CmdletBinding(ConfirmImpact = 'High', + SupportsShouldProcess = $true)] + param + ( + [Parameter(Mandatory = $true, + ValueFromPipelineByPropertyName = $true)] + [uint16]$Id, + + [string]$Name, + + [uint16]$Role, + + [uint16]$Cluster, + + [object]$Status, + + [uint16]$Platform, + + [uint16]$Primary_IPv4, + + [uint16]$Primary_IPv6, + + [byte]$VCPUs, + + [uint16]$Memory, + + [uint16]$Disk, + + [uint16]$Tenant, + + [string]$Comments, + + [hashtable]$Custom_Fields, + + [switch]$Force + ) + + if ($Status) { + $PSBoundParameters.Status = VerifyVirtualizationChoices -ProvidedValue $Status -VirtualMachineStatus + } + + $Segments = [System.Collections.ArrayList]::new(@('virtualization', 'virtual-machines', $Id)) + + Write-Verbose "Obtaining VM from ID $Id" + + $CurrentVM = Get-NetboxVirtualMachine -Id $Id -ErrorAction Stop + + Write-Verbose "Finished obtaining VM" + + if ($Force -or $pscmdlet.ShouldProcess($CurrentVM.Name, "Set")) { + $URIComponents = BuildURIComponents -URISegments $Segments -ParametersDictionary $PSBoundParameters -SkipParameterByName 'Id', 'Force' + + $URI = BuildNewURI -Segments $URIComponents.Segments + + InvokeNetboxRequest -URI $URI -Body $URIComponents.Parameters -Method PATCH + } + } + + + #endregion SET commands#endregion #region Invoke-IPAM_ps1 <# @@ -1413,12 +1485,13 @@ public enum $EnumName [switch]$ServiceProtocol ) - ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName + ValidateChoice -MajorObject 'IPAM' -ChoiceName $PSCmdlet.ParameterSetName -ProvidedValue $ProvidedValue } function Get-NetboxIPAMAggregate { [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification = "These are literally 'choices' in Netbox")] param ( [uint16]$Limit, @@ -1440,7 +1513,7 @@ public enum $EnumName [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -AggregateFamily } @@ -1496,15 +1569,15 @@ public enum $EnumName [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -IPAddressFamily } - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus } - if ($Role -ne $null) { + if ($null -ne $Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } @@ -1693,11 +1766,11 @@ public enum $EnumName [switch]$Raw ) - if ($Family -ne $null) { + if ($null -ne $Family) { $PSBoundParameters.Family = VerifyIPAMChoices -ProvidedValue $Family -PrefixFamily } - if ($Status -ne $null) { + if ($null -ne $Status) { $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -PrefixStatus } @@ -1783,7 +1856,7 @@ public enum $EnumName $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus - if ($Role -ne $null) { + if ($null -ne $Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole } @@ -1856,7 +1929,7 @@ public enum $EnumName [string]$Address, - [object]$Status = 'Active', + [object]$Status, [uint16]$Tenant, @@ -1875,7 +1948,9 @@ public enum $EnumName [switch]$Force ) - $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + if ($Status) { + $PSBoundParameters.Status = VerifyIPAMChoices -ProvidedValue $Status -IPAddressStatus + } if ($Role) { $PSBoundParameters.Role = VerifyIPAMChoices -ProvidedValue $Role -IPAddressRole diff --git a/dist/Tests/Helpers.Tests.ps1 b/dist/Tests/Helpers.Tests.ps1 index 395c666..331da0f 100644 --- a/dist/Tests/Helpers.Tests.ps1 +++ b/dist/Tests/Helpers.Tests.ps1 @@ -31,8 +31,8 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { return $true } -ModuleName 'NetboxPS' - Context "Building URI tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + Context -Name "Building URIBuilder" -Fixture { It "Should give a basic URI object" { BuildNewURI -HostName 'netbox.domain.com' | Should -BeOfType [System.UriBuilder] } @@ -85,10 +85,70 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $URIBuilder.URI.AbsoluteURI | Should -BeExactly 'https://netbox.domain.com/api/seg1/seg2/?param1=paramval1¶m2=paramval2' } } - } - - Context "Invoking request tests" { - InModuleScope -ModuleName 'NetboxPS' -ScriptBlock { + + Context -Name "Building URI components" -Fixture { + It "Should give a basic hashtable" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + } + + It "Should add a single ID parameter to the segments" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = 123} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2", '123') + $URIComponents.Parameters.Count | Should -BeExactly 0 + $URIComponents.Parameters | Should -BeOfType [hashtable] + } + + It "Should add multiple IDs to the parameters id__in" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'id' = "123", "456"} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['id__in'] | Should -Be '123,456' + } + + It "Should skip a particular parameter name" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'param1' = 1; 'param2' = 2} -SkipParameterByName 'param2' + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['param1'] | Should -Be 1 + $URIComponents.Parameters['param2'] | Should -BeNullOrEmpty + } + + It "Should add a query (q) parameter" { + $URIComponents = BuildURIComponents -URISegments @('segment1', 'segment2') -ParametersDictionary @{'query' = 'mytestquery'} + + $URIComponents | Should -BeOfType [hashtable] + $URIComponents.Keys.Count | Should -BeExactly 2 + $URIComponents.Keys | Should -Be @("Segments", "Parameters") + $URIComponents.Segments | Should -Be @("segment1", "segment2") + $URIComponents.Parameters.Count | Should -BeExactly 1 + $URIComponents.Parameters | Should -BeOfType [hashtable] + $URIComponents.Parameters['q'] | Should -Be 'mytestquery' + } + } + + Context -Name "Invoking request tests" -Fixture { Mock -CommandName 'Invoke-RestMethod' -Verifiable -MockWith { # Return an object of the items we would normally pass to Invoke-RestMethod return [pscustomobject]@{ @@ -123,7 +183,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result = InvokeNetboxRequest -URI $URIBuilder -Raw Assert-VerifiableMock - + $Result.Method | Should -Be 'GET' $Result.Uri | Should -Be $URIBuilder.Uri.AbsoluteUri $Result.Headers | Should -BeOfType [System.Collections.HashTable] @@ -136,7 +196,9 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { It "Should generate a POST request with body" { $URIBuilder = BuildNewURI -Hostname "netbox.domain.com" -Segments 'seg1', 'seg2' - $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{'bodyparam1' = 'val1'} -Raw + $Result = InvokeNetboxRequest -URI $URIBuilder -Method POST -Body @{ + 'bodyparam1' = 'val1' + } -Raw Assert-VerifiableMock @@ -161,6 +223,7 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { $Result.Method | Should -Be 'POST' $Result.Body | Should -Be '{"bodyparam1":"val1"}' + $Result.Headers.Count | Should -BeExactly 2 $Result.Headers.Authorization | Should -Be "Token faketoken" $Result.Headers.Connection | Should -Be "keep-alive" } @@ -179,6 +242,235 @@ Describe "Helpers tests" -Tag 'Core', 'Helpers' -Fixture { } | Should -Throw } } + + Context -Name "Validating choices" -Fixture { + $script:NetboxConfig.Choices.Virtualization = (Get-Content "$PSScriptRoot\VirtualizationChoices.json" -ErrorAction Stop | ConvertFrom-Json) + $script:NetboxConfig.Choices.IPAM = (Get-Content "$PSScriptRoot\IPAMChoices.json" -ErrorAction Stop | ConvertFrom-Json) + + Context -Name "Virtualization choices" -Fixture { + $MajorObject = 'Virtualization' + + It "Should return a valid integer for status when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer for status when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 0 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 0 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName 'virtual-machine:status' -ProvidedValue 'Fake' + } | Should -Throw + } + } + + Context -Name "IPAM choices" -Fixture { + $MajorObject = 'IPAM' + + Context -Name "aggregate:family" -Fixture { + $ChoiceName = 'aggregate:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:family" { + $ChoiceName = 'prefix:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "prefix:status" { + $ChoiceName = 'prefix:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:family" { + $ChoiceName = 'ip-address:family' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'IPv4' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 4 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 4 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:status" { + $ChoiceName = 'ip-address:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 10 + } | Should -Throw + } + } + + Context -Name "ip-address:role" { + $ChoiceName = 'ip-address:role' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Anycast' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 30 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 30 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + } | Should -Throw + } + } + + Context -Name "vlan:status" { + $ChoiceName = 'vlan:status' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'Active' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 1 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 1 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + + Context -Name "service:protocol" { + $ChoiceName = 'service:protocol' + + It "Should return a valid integer when provided a name" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 'TCP' + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should return a valid integer when provided an integer" { + $Result = ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 6 + + $Result | Should -BeOfType [uint16] + $Result | Should -BeExactly 6 + } + + It "Should throw because of an invalid choice" { + { + ValidateChoice -MajorObject $MajorObject -ChoiceName $ChoiceName -ProvidedValue 0 + } | Should -Throw + } + } + } + + + } + + } } diff --git a/dist/Tests/IPAM.Tests.ps1 b/dist/Tests/IPAM.Tests.ps1 index 6e6485b..fd21b2d 100644 --- a/dist/Tests/IPAM.Tests.ps1 +++ b/dist/Tests/IPAM.Tests.ps1 @@ -49,6 +49,10 @@ 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 "VerifyIPAMChoices" -Fixture { + #It "Should return a valid integer" + } + Context -Name "Get-NetboxIPAMAggregate" -Fixture { It "Should request the default number of aggregates" { $Result = Get-NetboxIPAMAggregate @@ -368,7 +372,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $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"}' + $Result.Body | Should -Be '{"status":2,"address":"10.0.0.1/24","role":30}' } It "Should add an IP with a status and role values" { @@ -379,7 +383,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $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"}' + $Result.Body | Should -Be '{"status":1,"address":"10.0.1.1/24","role":10}' } } @@ -457,7 +461,7 @@ Describe -Name "IPAM tests" -Tag 'Ipam' -Fixture { $Result.Method | Should -Be 'PATCH' $Result.Uri | Should -Be 'https://netbox.domain.com/api/ipam/ip-addresses/4109/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"description":"Test description","status":1,"tenant":14,"vrf":10}' + $Result.Body | Should -Be '{"vrf":10,"description":"Test description","tenant":14}' } } } diff --git a/dist/Tests/IPAMChoices.json b/dist/Tests/IPAMChoices.json new file mode 100644 index 0000000..3e05e53 --- /dev/null +++ b/dist/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/dist/Tests/Virtualization.Tests.ps1 b/dist/Tests/Virtualization.Tests.ps1 index 665b69a..6b68819 100644 --- a/dist/Tests/Virtualization.Tests.ps1 +++ b/dist/Tests/Virtualization.Tests.ps1 @@ -307,7 +307,6 @@ 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 @@ -316,7 +315,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 '{"name":"testvm","cluster":1,"status":1}' + $Result.Body | Should -Be '{"cluster":1,"name":"testvm","status":1}' } It "Should add a VM with CPUs, Memory, Disk, tenancy, and comments" { @@ -327,7 +326,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,"name":"testvm","comments":"these are comments","cluster":1,"status":1,"memory":4096,"vcpus":4,"disk":50}' + $Result.Body | Should -Be '{"tenant":11,"comments":"these are comments","disk":50,"memory":4096,"name":"testvm","cluster":1,"status":1,"vcpus":4}' } } @@ -340,7 +339,7 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/interfaces/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"virtual_machine":10,"enabled":true,"name":"Ethernet0"}' + $Result.Body | Should -Be '{"virtual_machine":10,"name":"Ethernet0","enabled":true}' } It "Should add an interface with a MAC, MTU, and Description" { @@ -351,7 +350,44 @@ Describe -Name "Virtualization tests" -Tag 'Virtualization' -Fixture { $Result.Method | Should -Be 'POST' $Result.Uri | Should -Be 'https://netbox.domain.com/api/virtualization/interfaces/' $Result.Headers.Keys.Count | Should -BeExactly 1 - $Result.Body | Should -Be '{"description":"Test description","virtual_machine":10,"enabled":true,"name":"Ethernet0","mtu":1500,"mac_address":"11:22:33:44:55:66"}' + $Result.Body | Should -Be '{"mtu":1500,"description":"Test description","enabled":true,"virtual_machine":10,"name":"Ethernet0","mac_address":"11:22:33:44:55:66"}' + } + } + + Context -Name "Set-NetboxVirtualMachine" -Fixture { + Mock -CommandName "Get-NetboxVirtualMachine" -ModuleName NetboxPS -MockWith { + return @{ + 'Id' = 1234 + 'Name' = 'TestVM' + } + } + + It "Should set a VM to a new name" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"name":"newtestname"}' + } + + It "Should set a VM with a new name, cluster, platform, and status" { + $Result = Set-NetboxVirtualMachine -Id 1234 -Name 'newtestname' -Cluster 10 -Platform 15 -Status 'Offline' -Force + + Assert-VerifiableMock + + $Result.Method | Should -Be 'PATCH' + $Result.URI | Should -Be 'https://netbox.domain.com/api/virtualization/virtual-machines/1234/' + $Result.Headers.Keys.Count | Should -BeExactly 1 + $Result.Body | Should -Be '{"cluster":10,"platform":15,"name":"newtestname","status":0}' + } + + It "Should throw because of an invalid status" { + { Set-NetboxVirtualMachine -Id 1234 -Status 'Fake' -Force } | Should -Throw + + Assert-VerifiableMock } } } diff --git a/dist/Tests/VirtualizationChoices.json b/dist/Tests/VirtualizationChoices.json new file mode 100644 index 0000000..235ed6c --- /dev/null +++ b/dist/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