diff --git a/.vscode/settings.json b/.vscode/settings.json index 3f005cad..dd3d358d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -54,5 +54,8 @@ "pester.runTestsInNewProcess": true, "pester.pesterModulePath": "./output/RequiredModules/Pester", "powershell.pester.codeLens": true, - "pester.suppressCodeLensNotice": true + "pester.suppressCodeLensNotice": true, + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index eae3f5cc..9fd75df2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - DnsServerDsc.Common - Removed `Test-DnsDscParameterState` and associated localization entries. + ### Added + +- DnsServerDsc + - Added new resource + -DSC_DnsServerStubZone - Added a new resource to manage file-backed DNS Stub Zones since it didn't previously exist and has been requested - https://github.com/dsccommunity/DnsServerDsc/issues/30 + -Added on 4/10/2025 + ## [3.0.0] - 2021-05-26 ### Removed diff --git a/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.psm1 b/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.psm1 new file mode 100644 index 00000000..4bc787d7 --- /dev/null +++ b/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.psm1 @@ -0,0 +1,243 @@ +$script:dscResourceCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DscResource.Common' +$script:dnsServerDscCommonPath = Join-Path -Path $PSScriptRoot -ChildPath '..\..\Modules\DnsServerDsc.Common' + +Import-Module -Name $script:dscResourceCommonPath +Import-Module -Name $script:dnsServerDscCommonPath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' + +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $MasterServers, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ZoneFile = "$Name.dns", + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) + + Assert-Module -ModuleName 'DNSServer' + + Write-Verbose ($script:localizedData.CheckingZoneMessage -f $Name, $Ensure) + + $dnsServerZone = Get-DnsServerZone -Name $Name -ErrorAction SilentlyContinue + + $targetResource = @{ + Name = $Name + MasterServers = $dnsServerZone.MasterServers | ForEach-Object { $_.IPAddressToString } + ZoneFile = $dnsServerZone.ZoneFile + Ensure = if (-not $dnsServerZone) { 'Absent' } else { 'Present' } + } + + return $targetResource + +} #end function Get-TargetResource + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $MasterServers, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ZoneFile = "$Name.dns", + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) + + $targetResource = Get-TargetResource @PSBoundParameters + $targetResourceInCompliance = $true + $dnsServerZone = Get-DnsServerZone -Name $Name -ErrorAction SilentlyContinue + + #If we specify that we want to ensure the zone should be ABSENT, check compliance. + if ($Ensure -eq 'Absent') + { + #If the results of our Get-TargetResource shows that the zone is PRESENT, set compliance, because we want it to be absent. + if ($targetResource.Ensure -eq 'Present') + { + # Dns zone is present and should be absent. '{0}' '{1}' '{2}' <--- Definitions in the DSC_DnsServerStubZone.strings.psd1 + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'Ensure', 'Absent', 'Present') + + $targetResourceInCompliance = $false + return $targetResourceInCompliance + + } + } + #If we specify that we want to ensure the zone should be PRESENT, check compliance. + if ($Ensure -eq 'Present') + { + #If the results of our Get-TargetResource shows that the zone is PRESENT, set compliance, because we want it to be absent. + if ($targetResource.Ensure -eq 'Absent') + { + # Dns zone is absent and should be present. + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'Ensure', 'Present', 'Absent') + + $targetResourceInCompliance = $false + return $targetResourceInCompliance + } + + #If the results of our Get-TargetResource shows that the zone is PRESENT, move on to the next validation check. + if ($targetResource.Ensure -eq 'Present') + { + #If the Zone is AD integrated, set non-compliance. + if ($dnsServerZone.IsDSIntegrated -eq $true) + { + #Zone Storage differs from the desired configuration + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'ZoneStorageLocation', 'Active Directory', 'File') + + $targetResourceInCompliance = $false + return $targetResourceInCompliance + } + #If the ZoneType isn't of type Stub, set non-compliance. + if ($dnsServerZone.ZoneType -ne 'Stub') + { + #Zone Type differs from the desired configuration + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'ZoneType', 'Stub', $dnsServerZone.ZoneType) + + $targetResourceInCompliance = $false + return $targetResourceInCompliance + } + #If the Zone File name differ from the desired configuration, set non-compliance. + if ($targetResource.ZoneFile -ne $ZoneFile) + { + #ZoneFile name differs from the desired configuration + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'ZoneFile', $targetResource.ZoneFile, $ZoneFile) + + $targetResourceInCompliance = $false + return $targetResourceInCompliance + } + #If the Master Servers differ from the desired configuration, set non-compliance. + $Comparison = Compare-Object -ReferenceObject $MasterServers -DifferenceObject $targetResource.MasterServers + if ($Comparison) + { + #Zone Master Servers differ from the desired configuration + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'MasterServers', ($MasterServers -join ','), ($targetResource.MasterServers -join ',')) + $targetResourceInCompliance = $false + } + + return $targetResourceInCompliance + + } + + } + +} #end function Test-TargetResource + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String[]] + $MasterServers, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ZoneFile = "$Name.dns", + + [Parameter()] + [ValidateSet('Present','Absent')] + [System.String] + $Ensure = 'Present' + ) + + Assert-Module -ModuleName 'DNSServer' + $targetResource = Get-TargetResource @PSBoundParameters + $dnsServerZone = Get-DnsServerZone -Name $Name -ErrorAction SilentlyContinue + + if ($Ensure -eq 'Absent') + { + Write-Verbose ($script:localizedData.CheckingZoneMessage -f $Name, $Ensure) + + if ($dnsServerZone.type -eq 'Stub' -and $dnsServerZone.IsDSIntegrated -eq $false) + { + + # Remove the DNS Server zone. + Write-Verbose ($script:localizedData.RemovingZoneMessage -f $Name) + Get-DnsServerZone -Name $Name | Remove-DnsServerZone -Force + + } + else + { + if ($dnsServerZone.type -ne 'Stub') + { + + # Zone is not a stub zone. + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'ZoneType', 'Stub', $dnsServerZone.ZoneType) + } + if ($dnsServerZone.IsDSIntegrated -ne $false) + { + + # Zone is AD integrated. + Write-Verbose ($script:localizedData.NotDesiredPropertyMessage -f 'ZoneStorageLocation', 'Active Directory', 'File') + } + + } + } + + if ($Ensure -eq 'Present') + { + Write-Verbose ($script:localizedData.CheckingZoneMessage -f $Name, $Ensure) + + if ($dnsServerZone.ZoneType -eq 'Stub' -and $dnsServerZone.IsDSIntegrated -eq $false) + { + # Compare the Desired master servers to the Existing master servers - if Existing doesn't match Desired, update the master servers for the zone. + $Comparison = Compare-Object -ReferenceObject $MasterServers -DifferenceObject $targetResource.MasterServers + + #If the Master Servers differ from the desired configuration, set them to the desired configuration. + if ($Comparison) + { + # Update the Master Servers list + Set-DnsServerStubZone -Name $Name -MasterServers $MasterServers + + Write-Verbose ($script:localizedData.SetPropertyMessage -f 'MasterServers') + } + + } + elseif (-not $dnsServerZone) + { + # Create the DNS Server zone. + Add-DnsServerStubZone -Name $Name -ZoneFile $ZoneFile -MasterServers $MasterServers + + Write-Verbose ($script:localizedData.AddingZoneMessage -f $Name) + } + } + +} #end function Set-TargetResource diff --git a/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.schema.mof b/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.schema.mof new file mode 100644 index 00000000..ceb9a59c --- /dev/null +++ b/source/DSCResources/DSC_DNSServerStubZone/DSC_DnsServerStubZone.schema.mof @@ -0,0 +1,8 @@ +[ClassVersion("1.0.0.0"), FriendlyName("DnsServerStubZone")] +class DSC_DnsServerStubZone : OMI_BaseResource +{ + [Key, Description("Name of the DNS Server stub zone")] String Name; + [Write, Description("Name of the DNS Server stub zone file. If not specified, defaults to 'ZoneName.dns'.")] String ZoneFile; + [Required, Description("List of master server IP addresses used by the stub zone.")] String MasterServers[]; + [Write, Description("Whether the DNS zone should be present or absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; +}; diff --git a/source/DSCResources/DSC_DNSServerStubZone/README.md b/source/DSCResources/DSC_DNSServerStubZone/README.md new file mode 100644 index 00000000..0d79afed --- /dev/null +++ b/source/DSCResources/DSC_DNSServerStubZone/README.md @@ -0,0 +1,3 @@ +# Description + +The DnsServerStubZone DSC resource manages a standalone file-backed Stub zone on a given Domain Name System (DNS) server. diff --git a/source/DSCResources/DSC_DNSServerStubZone/en-US/DSC_DnsServerStubZone.strings.psd1 b/source/DSCResources/DSC_DNSServerStubZone/en-US/DSC_DnsServerStubZone.strings.psd1 new file mode 100644 index 00000000..b2e41d11 --- /dev/null +++ b/source/DSCResources/DSC_DNSServerStubZone/en-US/DSC_DnsServerStubZone.strings.psd1 @@ -0,0 +1,8 @@ +# culture="en-US" +ConvertFrom-StringData @' + CheckingZoneMessage = Checking DNS server zone with name '{0}' is '{1}'... + AddingZoneMessage = Adding DNS server zone '{0}' ... + RemovingZoneMessage = Removing DNS server zone '{0}' ... + NotDesiredPropertyMessage = DNS server zone property '{0}' is not correct. Expected '{1}', Actual '{2}' + SetPropertyMessage = DNS server zone property '{0}' is set +'@ diff --git a/source/DSCResources/DSC_DNSServerStubZone/en-US/about_DnsServerStubZone.help.txt b/source/DSCResources/DSC_DNSServerStubZone/en-US/about_DnsServerStubZone.help.txt new file mode 100644 index 00000000..6b1738b3 --- /dev/null +++ b/source/DSCResources/DSC_DNSServerStubZone/en-US/about_DnsServerStubZone.help.txt @@ -0,0 +1,137 @@ +.NAME + DnsServerStubZone + +.DESCRIPTION + The DnsServerStubZone DSC resource manages a file-backed DNS Stub zone on a Domain Name System (DNS) server. + Stub zones contain only the authoritative name server records (NS, SOA, A) from another DNS zone, and are typically used for forwarding queries to specific DNS servers for that zone. + +.PARAMETER Name + Key - String + Name of the DNS Server stub zone + +.PARAMETER ZoneFile + Write - String + Name of the DNS Server stub zone file. If not specified, defaults to 'ZoneName.dns'. + +.PARAMETER MasterServers + Write - String + Allowed values: Server IPs + +.PARAMETER Ensure + Write - String + Allowed values: Present, Absent + Whether the DNS zone should be present or absent + +.EXAMPLE 1 + +This configuration will add a file-backed classful reverse Stub zone +using the resource default parameter values. + +Configuration DnsServerStubZone_AddClassfulReverseStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'AddStubZone' + { + Name = '1.168.192.in-addr.arpa' + MasterServers = @('192.168.1.10', '192.168.1.11') + } + } +} + +.EXAMPLE 2 + +This configuration will add a file-backed classless reverse Stub zone +using the resource default parameter values. + +Configuration DnsServerStubZone_AddClasslessReverseStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'AddStubZone' + { + Name = '64-26.100.168.192.in-addr.arpa' + MasterServers = @('192.168.1.10', '192.168.1.11') + } + } +} + +.EXAMPLE 3 + +This configuration will add a file-backed Stub Zone using the resource +default parameter values. + +Configuration DnsServerStubZone_AddStubZoneUsingDefaults_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'AddStubZone' + { + Name = 'demo.contoso.com' + MasterServers = @('192.168.1.10', '192.168.1.11') + } + } +} + +.EXAMPLE 4 + +This configuration will add a file-backed Stub zone using the resource +default parameter values. + +Configuration DnsServerStubZone_AddStubZoneWithSpecificValues_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'AddStubZone' + { + Ensure = 'Present' + Name = 'demo.contoso.com' + ZoneFile = 'demo.contoso.com.dns' + MasterServers = @('192.168.1.10', '192.168.1.11') + } + } +} + +.EXAMPLE 5 + +This configuration will remove a file-backed Stub zone. + +Configuration DnsServerStubZone_RemoveStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'RemoveStubZone' + { + Ensure = 'Absent' + Name = 'demo.contoso.com' + } + } +} + +.EXAMPLE 6 + +This configuration will remove a file-backed Stub zone. + +Configuration DnsServerStubZone_RemoveReverseStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + Node localhost + { + DnsServerStubZone 'RemoveStubZone' + { + Ensure = 'Absent' + Name = '1.168.192.in-addr.arpa' + } + } +} diff --git a/tests/Integration/DSC_DnsServerStubZone.Integration.Tests.ps1 b/tests/Integration/DSC_DnsServerStubZone.Integration.Tests.ps1 new file mode 100644 index 00000000..2f438e84 --- /dev/null +++ b/tests/Integration/DSC_DnsServerStubZone.Integration.Tests.ps1 @@ -0,0 +1,282 @@ +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } + + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscModuleName = 'DnsServerDsc' + $script:dscResourceFriendlyName = 'DnsServerStubZone' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + + # Ensure that the tests can be performed on this computer + $script:skipIntegrationTests = $false +} + +BeforeAll { + $script:dscModuleName = 'DnsServerDsc' + $script:dscResourceFriendlyName = 'DnsServerStubZone' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Integration' +} + +AfterAll { + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddStubZoneUsingDefaultValues_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -BeNullOrEmpty + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.StubZoneName + $resourceCurrentState.MasterServers | Should -Be $ConfigurationData.AllNodes.StubMasterServers + $resourceCurrentState.ZoneFile | Should -BeNullOrEmpty + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveStubZone_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.StubZoneName + $resourceCurrentState.MasterServers | Should -BeNullOrEmpty + $resourceCurrentState.ZoneFile | Should -BeNullOrEmpty + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_AddStubZone_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.StubZoneName + $resourceCurrentState.MasterServers | Should -Be $ConfigurationData.AllNodes.StubMasterServers + $resourceCurrentState.ZoneFile | Should -Be $ConfigurationData.AllNodes.StubZoneFile + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_RemoveStubZone_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterAll { + Wait-ForIdleLcm -Clear + } + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.Name | Should -Be $ConfigurationData.AllNodes.StubZoneName + $resourceCurrentState.ZoneFile | Should -BeNullOrEmpty + $resourceCurrentState.DynamicUpdate | Should -BeNullOrEmpty + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + +} diff --git a/tests/Integration/DSC_DnsServerStubZone.config.ps1 b/tests/Integration/DSC_DnsServerStubZone.config.ps1 new file mode 100644 index 00000000..f99dfd2d --- /dev/null +++ b/tests/Integration/DSC_DnsServerStubZone.config.ps1 @@ -0,0 +1,75 @@ +$ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + # Stub zone + StubZoneName = 'dsc.test' + StubZoneFile = 'dsc.test.file.dns' + StubMasterServers = '192.168.1.1','192.168.1.2' + + } + ) +} + +<# + .SYNOPSIS + Creates a file-backed stub zone using the default values for parameters. +#> +configuration DSC_DnsServerStubZone_AddStubZoneUsingDefaultValues_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + node $AllNodes.NodeName + { + DnsServerStubZone 'Integration_Test' + { + Name = $Node.StubZoneName + MasterServers = $Node.StubMasterServers + } + } +} + +<# + .SYNOPSIS + Removes a file-backed stub zone. + + .NOTES + This configuration is used multiple times to remove the file-backed stub zone. +#> +configuration DSC_DnsServerStubZone_RemoveStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + node $AllNodes.NodeName + { + DnsServerStubZone 'Integration_Test' + { + Ensure = 'Absent' + Name = $Node.StubZoneName + MasterServers = $Node.StubMasterServers + ZoneFile = $Node.StubZoneFile + } + } +} + +<# + .SYNOPSIS + Creates a file-backed stub zone by specifying values for each parameter. +#> +configuration DSC_DnsServerStubZone_AddStubZone_Config +{ + Import-DscResource -ModuleName 'DnsServerDsc' + + node $AllNodes.NodeName + { + DnsServerStubZone 'Integration_Test' + { + Ensure = 'Present' + Name = $Node.StubZoneName + ZoneFile = $Node.StubZoneFile + MasterServers = $Node.StubMasterServers + } + } +} diff --git a/tests/Unit/DSC_DnsServerStubZone.Tests.ps1 b/tests/Unit/DSC_DnsServerStubZone.Tests.ps1 new file mode 100644 index 00000000..5b56f36b --- /dev/null +++ b/tests/Unit/DSC_DnsServerStubZone.Tests.ps1 @@ -0,0 +1,450 @@ +<# + .SYNOPSIS + Unit test for DSC_DnsServerStubZone DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 3>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'DnsServerDsc' + $script:dscResourceName = 'DSC_DnsServerStubZone' + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType 'Unit' + + Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'Stubs\DnsServer.psm1') -Force + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscResourceName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscResourceName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + Restore-TestEnvironment -TestEnvironment $script:testEnvironment + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscResourceName -All | Remove-Module -Force + + Remove-Module -Name DnsServer -Force +} + +Describe 'DSC_DnsServerStubZone\Get-TargetResource' -Tag 'Get' { + BeforeAll { + Mock -CommandName Assert-Module + + $testZoneName = 'example.com' + $testZoneFile = 'example.com.dns' + $testZoneMasterServers = '192.168.1.1','192.168.1.2' + $fakeDnsFileZone = [PSCustomObject] @{ + DistinguishedName = $null + ZoneName = $testZoneName + ZoneType = 'Stub' + MasterServers = $testZoneMasterServers + ReplicationScope = 'None' + DirectoryPartitionName = $null + ZoneFile = $testZoneFile + } + } + + BeforeEach { + InModuleScope -Parameters @{ + testZoneName = $testZoneName + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:testParams = @{ + Name = $testZoneName + Verbose = $false + } + } + } + + Context 'When DNS zone exists' { + BeforeAll { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + } + + It 'Should return a "System.Collections.Hashtable" object type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + Get-TargetResource @testParams | Should -BeOfType [System.Collections.Hashtable] + } + } + + It 'Should return "Present" when "Ensure" = "Present"' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'example.com.dns' + Ensure = 'Present' + } + + $targetResource = Get-TargetResource @testParams + $targetResource.Ensure | Should -Be 'Present' + } + } + + It 'Should return "Present" when "Ensure" = "Absent"' { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'example.com.dns' + Ensure = 'Absent' + } + + $targetResource = Get-TargetResource @testParams + $targetResource.Ensure | Should -Be 'Present' + } + } + } + + Context 'When DNS zone does not exist' { + BeforeAll { + Mock -CommandName Get-DnsServerZone + } + + It 'Should return "Absent" when "Ensure" = "Present"' { + + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'example.com.dns' + } + + $targetResource = Get-TargetResource @testParams + $targetResource.Ensure | Should -Be 'Absent' + } + } + + It 'Should return "Absent" when "Ensure" = "Absent"' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'example.com.dns' + Ensure = 'Absent' + } + + $targetResource = Get-TargetResource @testParams + $targetResource.Ensure | Should -Be 'Absent' + } + } + } +} + +Describe 'DSC_DnsServerStubZone\Test-TargetResource' -Tag 'Test' { + BeforeAll { + Mock -CommandName Assert-Module + + $testZoneName = 'example.com' + $testZoneFile = 'example.com.dns' + $testZoneMasterServers = '192.168.1.1','192.168.1.2' + $fakeDnsFileZone = [PSCustomObject] @{ + DistinguishedName = $null + ZoneName = $testZoneName + ZoneType = 'Stub' + MasterServers = $testZoneMasterServers + ReplicationScope = 'None' + DirectoryPartitionName = $null + ZoneFile = $testZoneFile + } + } + + BeforeEach { + InModuleScope -Parameters @{ + testZoneName = $testZoneName + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:testParams = @{ + Name = $testZoneName + Verbose = $false + } + } + } + + Context 'When the DNS zone exists' { + BeforeAll { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + } + + Context 'When the zone is in the desired state' { + It 'Should return a "System.Boolean" object type' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + Test-TargetResource @testParams | Should -BeOfType [System.Boolean] + } + } + + It 'Should be $true when "Ensure" = "Present"' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + } + + Test-TargetResource @testParams | Should -BeTrue + } + } + + It 'Should be $true "MasterServers" is correct' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + MasterServers = $testZoneMasterServers + } + + Test-TargetResource @testParams | Should -BeTrue + } + } + } + + Context 'When the zone is not in the desired state' { + It 'Should be $false "Ensure" = "Absent"' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Absent' + } + + Test-TargetResource @testParams | Should -BeFalse + } + } + + It 'Should be $false when "MasterServers" is incorrect' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'example.com.dns' + Ensure = 'Present' + MasterServers = '8.8.8.8' + } + + Test-TargetResource @testParams | Should -BeFalse + } + } + + It 'Should be $false when "ZoneFile" is incorrect' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + ZoneFile = 'nonexistent.com.dns' + Ensure = 'Present' + MasterServers = $testZoneMasterServers + } + + Test-TargetResource @testParams | Should -BeFalse + } + } + } + } + + Context 'When the DNS zone does not exist' { + BeforeAll { + Mock -CommandName Get-DnsServerZone + } + + Context 'When the zone is in the desired state' { + It 'Should be $true' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Absent' + } + + Test-TargetResource @testParams | Should -BeTrue + } + } + } + + Context 'When the zone is not in the desired state' { + It 'Should be $false' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + } + + Test-TargetResource @testParams | Should -BeFalse + } + } + } + } +} + +Describe 'DSC_DnsServerStubZone\Set-TargetResource' -Tag 'Set' { + BeforeAll { + Mock -CommandName 'Assert-Module' + + $testZoneName = 'example.com' + $testZoneFile = 'example.com.dns' + $testZoneMasterServers = '192.168.1.1','192.168.1.2' + $fakeDnsFileZone = [PSCustomObject] @{ + DistinguishedName = $null + ZoneName = $testZoneName + ZoneType = 'Stub' + MasterServers = $testZoneMasterServers + ReplicationScope = 'None' + DirectoryPartitionName = $null + ZoneFile = $testZoneFile + } + } + + BeforeEach { + InModuleScope -Parameters @{ + testZoneName = $testZoneName + } -ScriptBlock { + Set-StrictMode -Version 1.0 + + $script:testParams = @{ + Name = $testZoneName + Verbose = $false + } + } + } + + Context 'When the DNS zone does not exist' { + BeforeAll { + Mock -CommandName Get-DnsServerZone + Mock -CommandName Add-DnsServerStubZone -ParameterFilter { $Name -eq $testZoneName } + } + + It 'Should call expected mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + MasterServers = $testZoneMasterServers + ZoneFile = 'example.com.dns' + } + + Set-TargetResource @testParams + } + + Should -Invoke -CommandName Add-DnsServerStubZone -ParameterFilter { $Name -eq $testZoneName } -Scope It -Times 1 -Exactly + should -Invoke -CommandName Get-DnsServerZone -Scope It -Times 1 -Exactly + } + } + + Context 'When the DNS zone does exist' { + BeforeAll { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + Mock -CommandName Remove-DnsServerZone + } + + Context 'When the zone needs creating' { + It 'Should call expected mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Absent' + MasterServers = $testZoneMasterServers + ZoneFile = 'example.com.dns' + } + + Set-TargetResource @testParams + } + + Should -Invoke -CommandName Remove-DnsServerZone -Scope It -Times 1 -Exactly + Should -Invoke -CommandName Get-DnsServerZone -Scope It -Times 1 -Exactly + } + } + + Context 'When the zone needs updating' { + Context 'when DNS zone "MasterServers" are incorrect' { + BeforeAll { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + Mock -CommandName Set-DnsServerStubZone -ParameterFilter { $MasterServers -eq '8.8.8.8' } + } + + It 'Should call expected mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + MasterServers = $testZoneMasterServers + ZoneFile = 'example.com.dns' + } + + Set-TargetResource @testParams + } + + Should -Invoke -CommandName Set-DnsServerStubZone -ParameterFilter { $MasterServers -eq '8.8.8.8' } -Scope It -Times 1 -Exactly + Should -Invoke -CommandName Get-DnsServerZone -Scope It -Times 1 -Exactly + } + } + + Context 'When DNS zone "ZoneFile" is incorrect' { + BeforeAll { + Mock -CommandName Get-DnsServerZone -MockWith { return $fakeDnsFileZone } + Mock -CommandName Set-DnsServerStubZone -ParameterFilter { $ZoneFile -eq 'nonexistent.com.dns' } + } + + It 'Should call expected mocks' { + InModuleScope -ScriptBlock { + Set-StrictMode -Version 1.0 + + $testParams += @{ + Ensure = 'Present' + MasterServers = $testZoneMasterServers + ZoneFile = 'nonexistent.com.dns' + } + + Set-TargetResource @testParams + } + + Should -Invoke -CommandName Set-DnsServerStubZone -ParameterFilter { $ZoneFile -eq 'nonexistent.com.dns' } -Scope It -Times 1 -Exactly + Should -Invoke -CommandName Get-DnsServerZone -Scope It -Times 1 -Exactly + } + } + } + } +}