diff --git a/src/ALZ/Private/Tools/Checks/Test-NetworkConnectivity.ps1 b/src/ALZ/Private/Tools/Checks/Test-NetworkConnectivity.ps1 new file mode 100644 index 00000000..a12e8667 --- /dev/null +++ b/src/ALZ/Private/Tools/Checks/Test-NetworkConnectivity.ps1 @@ -0,0 +1,38 @@ +function Test-NetworkConnectivity { + [CmdletBinding()] + param() + + $results = @() + $hasFailure = $false + + $endpoints = @( + @{ Uri = "https://api.github.com"; Description = "GitHub API (release lookups)" }, + @{ Uri = "https://github.com"; Description = "GitHub (module downloads)" }, + @{ Uri = "https://api.releases.hashicorp.com"; Description = "HashiCorp Releases API (Terraform version)" }, + @{ Uri = "https://releases.hashicorp.com"; Description = "HashiCorp Releases (Terraform binary download)" }, + @{ Uri = "https://management.azure.com"; Description = "Azure Management API" }, + @{ Uri = "https://www.powershellgallery.com"; Description = "PowerShell Gallery (module installs/updates)" } + ) + + foreach ($endpoint in $endpoints) { + Write-Verbose "Testing network connectivity to $($endpoint.Uri)" + try { + Invoke-WebRequest -Uri $endpoint.Uri -Method Head -TimeoutSec 10 -SkipHttpErrorCheck -ErrorAction Stop -UseBasicParsing | Out-Null + $results += @{ + message = "Network connectivity to $($endpoint.Description) ($($endpoint.Uri)) is available." + result = "Success" + } + } catch { + $results += @{ + message = "Cannot reach $($endpoint.Description) ($($endpoint.Uri)). Check network/firewall settings. Error: $($_.Exception.Message)" + result = "Failure" + } + $hasFailure = $true + } + } + + return @{ + Results = $results + HasFailure = $hasFailure + } +} diff --git a/src/ALZ/Private/Tools/Test-Tooling.ps1 b/src/ALZ/Private/Tools/Test-Tooling.ps1 index 1fda2742..49a65fc7 100644 --- a/src/ALZ/Private/Tools/Test-Tooling.ps1 +++ b/src/ALZ/Private/Tools/Test-Tooling.ps1 @@ -2,7 +2,7 @@ function Test-Tooling { [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $false)] - [ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli")] + [ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli", "NetworkConnectivity")] [string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion"), [Parameter(Mandatory = $false)] [switch]$destroy @@ -91,6 +91,13 @@ function Test-Tooling { if ($result.HasFailure) { $hasFailure = $true } } + # Check Network Connectivity + if ($Checks -contains "NetworkConnectivity") { + $result = Test-NetworkConnectivity + $checkResults += $result.Results + if ($result.HasFailure) { $hasFailure = $true } + } + # Display results Write-Verbose "Showing check results" Write-Verbose $(ConvertTo-Json $checkResults -Depth 100) diff --git a/src/ALZ/Public/Deploy-Accelerator.ps1 b/src/ALZ/Public/Deploy-Accelerator.ps1 index 776c91bd..45a9ee02 100644 --- a/src/ALZ/Public/Deploy-Accelerator.ps1 +++ b/src/ALZ/Public/Deploy-Accelerator.ps1 @@ -250,6 +250,9 @@ function Deploy-Accelerator { $checks += "YamlModuleAutoInstall" } } + if (-not $skip_internet_checks.IsPresent) { + $checks += "NetworkConnectivity" + } $toolingResult = Test-Tooling -Checks $checks -destroy:$destroy.IsPresent } diff --git a/src/ALZ/Public/Test-AcceleratorRequirement.ps1 b/src/ALZ/Public/Test-AcceleratorRequirement.ps1 index b520782b..0e0698aa 100644 --- a/src/ALZ/Public/Test-AcceleratorRequirement.ps1 +++ b/src/ALZ/Public/Test-AcceleratorRequirement.ps1 @@ -3,7 +3,7 @@ function Test-AcceleratorRequirement { .SYNOPSIS Test that the Accelerator software requirements are met .DESCRIPTION - This will check for the pre-requisite software + This will check for the pre-requisite software and network connectivity to the external endpoints required by the Accelerator. .EXAMPLE C:\PS> Test-AcceleratorRequirement .EXAMPLE @@ -20,10 +20,10 @@ function Test-AcceleratorRequirement { param ( [Parameter( Mandatory = $false, - HelpMessage = "[OPTIONAL] Specifies which checks to run. Valid values: PowerShell, Git, AzureCli, AzureEnvVars, AzureCliOrEnvVars, AzureLogin, AlzModule, AlzModuleVersion, YamlModule, YamlModuleAutoInstall, GitHubCli, AzureDevOpsCli" + HelpMessage = "[OPTIONAL] Specifies which checks to run. Valid values: PowerShell, Git, AzureCli, AzureEnvVars, AzureCliOrEnvVars, AzureLogin, AlzModule, AlzModuleVersion, YamlModule, YamlModuleAutoInstall, GitHubCli, AzureDevOpsCli, NetworkConnectivity" )] - [ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli")] - [string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion") + [ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli", "NetworkConnectivity")] + [string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "NetworkConnectivity") ) Test-Tooling -Checks $Checks } diff --git a/src/Tests/Unit/Private/Test-NetworkConnectivity.Tests.ps1 b/src/Tests/Unit/Private/Test-NetworkConnectivity.Tests.ps1 new file mode 100644 index 00000000..bb3a65d3 --- /dev/null +++ b/src/Tests/Unit/Private/Test-NetworkConnectivity.Tests.ps1 @@ -0,0 +1,111 @@ +#------------------------------------------------------------------------- +Set-Location -Path $PSScriptRoot +#------------------------------------------------------------------------- +$ModuleName = 'ALZ' +$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1") +#------------------------------------------------------------------------- +if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') { + #if the module is already in memory, remove it + Remove-Module -Name $ModuleName -Force +} +Import-Module $PathToManifest -Force +#------------------------------------------------------------------------- + +InModuleScope 'ALZ' { + Describe 'Test-NetworkConnectivity Private Function Tests' -Tag Unit { + BeforeAll { + $WarningPreference = 'SilentlyContinue' + $ErrorActionPreference = 'SilentlyContinue' + } + + Context 'All endpoints are reachable' { + BeforeAll { + Mock -CommandName Invoke-WebRequest -MockWith { + [PSCustomObject]@{ StatusCode = 200 } + } + } + + It 'returns HasFailure = $false when all endpoints succeed' { + $result = Test-NetworkConnectivity + $result.HasFailure | Should -BeFalse + } + + It 'returns a Success result for every endpoint' { + $result = Test-NetworkConnectivity + $result.Results | ForEach-Object { + $_.result | Should -Be "Success" + } + } + + It 'returns one result per endpoint (6 total)' { + $result = Test-NetworkConnectivity + $result.Results.Count | Should -Be 6 + } + } + + Context 'One endpoint is unreachable' { + BeforeAll { + Mock -CommandName Invoke-WebRequest -ParameterFilter { $Uri -eq "https://api.github.com" } -MockWith { + throw "Unable to connect to the remote server" + } + Mock -CommandName Invoke-WebRequest -MockWith { + [PSCustomObject]@{ StatusCode = 200 } + } + } + + It 'returns HasFailure = $true' { + $result = Test-NetworkConnectivity + $result.HasFailure | Should -BeTrue + } + + It 'returns a Failure result for the unreachable endpoint' { + $result = Test-NetworkConnectivity + $failureResults = @($result.Results | Where-Object { $_.result -eq "Failure" }) + $failureResults.Count | Should -Be 1 + } + + It 'includes the error message in the Failure result' { + $result = Test-NetworkConnectivity + $failureResult = @($result.Results | Where-Object { $_.result -eq "Failure" })[0] + $failureResult.message | Should -Match "Cannot reach" + $failureResult.message | Should -Match "api.github.com" + } + + It 'still returns Success results for the reachable endpoints' { + $result = Test-NetworkConnectivity + $successResults = @($result.Results | Where-Object { $_.result -eq "Success" }) + $successResults.Count | Should -Be 5 + } + } + + Context 'All endpoints are unreachable' { + BeforeAll { + Mock -CommandName Invoke-WebRequest -MockWith { + throw "Network unreachable" + } + } + + It 'returns HasFailure = $true' { + $result = Test-NetworkConnectivity + $result.HasFailure | Should -BeTrue + } + + It 'returns a Failure result for every endpoint' { + $result = Test-NetworkConnectivity + $result.Results | ForEach-Object { + $_.result | Should -Be "Failure" + } + } + + It 'returns one result per endpoint (6 total)' { + $result = Test-NetworkConnectivity + $result.Results.Count | Should -Be 6 + } + + It 'checks all endpoints and does not stop at the first failure' { + $result = Test-NetworkConnectivity + Should -Invoke -CommandName Invoke-WebRequest -Times 6 -Scope It + } + } + } +}