Skip to content

Commit 1a189a9

Browse files
Copilotjtracey93
andauthored
feat: Add NetworkConnectivity pre-flight check to Test-AcceleratorRequirement (#527)
- [x] Understand current state of checks in `Deploy-Accelerator.ps1` and `Test-NetworkConnectivity.ps1` - [x] Add `https://www.powershellgallery.com` endpoint to `Test-NetworkConnectivity.ps1` - [x] Add `NetworkConnectivity` to the checks in `Deploy-Accelerator.ps1` (guarded by `skip_internet_checks`) - [x] Update `Test-NetworkConnectivity.Tests.ps1` endpoint count assertions: 5 → 6 - [x] All 47 unit tests pass <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> ## Summary Add a new `NetworkConnectivity` check to `Test-AcceleratorRequirement` (and the underlying `Test-Tooling` plumbing) that probes the external URLs the module must reach during a Bicep deployment, before any download or API call is attempted. ## Background Currently the module has no pre-flight network reachability check. It assumes connectivity is present and only surfaces failures at the point of use (e.g. inside `Invoke-WebRequest`/`Invoke-RestMethod`). This makes it hard for users in restricted environments to diagnose connectivity issues early. ## Changes Required ### 1. New file: `src/ALZ/Private/Tools/Checks/Test-NetworkConnectivity.ps1` Create a new check function following the same pattern as the existing checks (e.g. `Test-GitInstallation.ps1`). It should: - Probe each of the external endpoints the module calls during a Bicep deployment using `Invoke-WebRequest` with `-Method Head` (or a lightweight GET where HEAD is not supported), with a short timeout (e.g. 10 seconds) and `-SkipHttpErrorCheck` / `-ErrorAction SilentlyContinue` so it doesn't throw. - Return a `Results` array and a `HasFailure` bool in the same shape as all other checks. - Treat any endpoint that **cannot be reached** (connection error / timeout) as a **Failure**, and any reachable endpoint (even a non-200 status, which may just be auth) as a **Success** — the goal is reachability, not authentication. - The endpoints to check are: | Endpoint | Purpose | |---|---| | `https://api.github.com` | GitHub API (release tag lookups) | | `https://github.com` | Bootstrap & starter module downloads | | `https://api.releases.hashicorp.com` | Terraform version resolution | | `https://releases.hashicorp.com` | Terraform binary download | | `https://management.azure.com` | Azure Management API | Example skeleton (follow the pattern of `Test-GitInstallation.ps1`): ```powershell 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" } ) foreach ($endpoint in $endpoints) { Write-Verbose "Testing network connectivity to $($endpoint.Uri)" try { $response = Invoke-WebRequest -Uri $endpoint.Uri -Method Head -TimeoutSec 10 -SkipHttpErrorCheck -ErrorAction Stop -UseBasicParsing $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 } } ``` ### 2. Update `src/ALZ/Private/Tools/Test-Tooling.ps1` - Add `"NetworkConnectivity"` to the `[ValidateSet(...)]` on the `$Checks` parameter. - Add a new `if ($Checks -contains "NetworkConnectivity")` block that calls `Test-NetworkConnectivity` and accumulates results, following the same pattern as the other checks. ### 3. Update `src/ALZ/Public/Test-AcceleratorRequirement.ps1` - Add `"NetworkConnectivity"` to the `[ValidateSet(...)]` on the `$Checks` parameter. - Add `"NetworkConnectivity"` to the **default** value of `$Checks` so it runs automatically when `Test-AcceleratorRequirement` is called with no arguments. - Update the `.SYNOPSIS`/`.DESCRIPTION` doc comment to mention the network connectivity check. ### 4. Add unit tests: `src/Tests/Unit/Private/Test-NetworkConnectivity.Tests.ps1` Add Pester unit tests for `Test-NetworkConnectivity` following the pattern used by other tests in `src/Tests/Unit/`. Cover at minimum: - All endpoints reachable → no failure, all Success results. - One or more endpoints unreachable (mock `Invoke-WebRequest` to throw) → `HasFailure = $true`, correct Failure messages. ## Notes - **Do not** add `NetworkConnectivity` to the default checks inside `Deploy-Accelerator.ps1` — it already has its own `$skip_internet_checks` bypass path and calling a network check there would be redundant. The check belongs in `Test-AcceleratorRequirement` only. - Keep the check **non-blocking** in the sense that it should check all endpoints and report all failures, not stop at the first unreachable one. - Follow t... </details> <!-- START COPILOT CODING AGENT SUFFIX --> *This pull request was created from Copilot chat.* > <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/Azure/ALZ-PowerShell-Module/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jtracey93 <41163455+jtracey93@users.noreply.github.com>
1 parent 7e2bcb7 commit 1a189a9

5 files changed

Lines changed: 164 additions & 5 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function Test-NetworkConnectivity {
2+
[CmdletBinding()]
3+
param()
4+
5+
$results = @()
6+
$hasFailure = $false
7+
8+
$endpoints = @(
9+
@{ Uri = "https://api.github.com"; Description = "GitHub API (release lookups)" },
10+
@{ Uri = "https://github.com"; Description = "GitHub (module downloads)" },
11+
@{ Uri = "https://api.releases.hashicorp.com"; Description = "HashiCorp Releases API (Terraform version)" },
12+
@{ Uri = "https://releases.hashicorp.com"; Description = "HashiCorp Releases (Terraform binary download)" },
13+
@{ Uri = "https://management.azure.com"; Description = "Azure Management API" },
14+
@{ Uri = "https://www.powershellgallery.com"; Description = "PowerShell Gallery (module installs/updates)" }
15+
)
16+
17+
foreach ($endpoint in $endpoints) {
18+
Write-Verbose "Testing network connectivity to $($endpoint.Uri)"
19+
try {
20+
Invoke-WebRequest -Uri $endpoint.Uri -Method Head -TimeoutSec 10 -SkipHttpErrorCheck -ErrorAction Stop -UseBasicParsing | Out-Null
21+
$results += @{
22+
message = "Network connectivity to $($endpoint.Description) ($($endpoint.Uri)) is available."
23+
result = "Success"
24+
}
25+
} catch {
26+
$results += @{
27+
message = "Cannot reach $($endpoint.Description) ($($endpoint.Uri)). Check network/firewall settings. Error: $($_.Exception.Message)"
28+
result = "Failure"
29+
}
30+
$hasFailure = $true
31+
}
32+
}
33+
34+
return @{
35+
Results = $results
36+
HasFailure = $hasFailure
37+
}
38+
}

src/ALZ/Private/Tools/Test-Tooling.ps1

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ function Test-Tooling {
22
[CmdletBinding(SupportsShouldProcess = $true)]
33
param(
44
[Parameter(Mandatory = $false)]
5-
[ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli")]
5+
[ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli", "NetworkConnectivity")]
66
[string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion"),
77
[Parameter(Mandatory = $false)]
88
[switch]$destroy
@@ -91,6 +91,13 @@ function Test-Tooling {
9191
if ($result.HasFailure) { $hasFailure = $true }
9292
}
9393

94+
# Check Network Connectivity
95+
if ($Checks -contains "NetworkConnectivity") {
96+
$result = Test-NetworkConnectivity
97+
$checkResults += $result.Results
98+
if ($result.HasFailure) { $hasFailure = $true }
99+
}
100+
94101
# Display results
95102
Write-Verbose "Showing check results"
96103
Write-Verbose $(ConvertTo-Json $checkResults -Depth 100)

src/ALZ/Public/Deploy-Accelerator.ps1

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ function Deploy-Accelerator {
250250
$checks += "YamlModuleAutoInstall"
251251
}
252252
}
253+
if (-not $skip_internet_checks.IsPresent) {
254+
$checks += "NetworkConnectivity"
255+
}
253256
$toolingResult = Test-Tooling -Checks $checks -destroy:$destroy.IsPresent
254257
}
255258

src/ALZ/Public/Test-AcceleratorRequirement.ps1

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function Test-AcceleratorRequirement {
33
.SYNOPSIS
44
Test that the Accelerator software requirements are met
55
.DESCRIPTION
6-
This will check for the pre-requisite software
6+
This will check for the pre-requisite software and network connectivity to the external endpoints required by the Accelerator.
77
.EXAMPLE
88
C:\PS> Test-AcceleratorRequirement
99
.EXAMPLE
@@ -20,10 +20,10 @@ function Test-AcceleratorRequirement {
2020
param (
2121
[Parameter(
2222
Mandatory = $false,
23-
HelpMessage = "[OPTIONAL] Specifies which checks to run. Valid values: PowerShell, Git, AzureCli, AzureEnvVars, AzureCliOrEnvVars, AzureLogin, AlzModule, AlzModuleVersion, YamlModule, YamlModuleAutoInstall, GitHubCli, AzureDevOpsCli"
23+
HelpMessage = "[OPTIONAL] Specifies which checks to run. Valid values: PowerShell, Git, AzureCli, AzureEnvVars, AzureCliOrEnvVars, AzureLogin, AlzModule, AlzModuleVersion, YamlModule, YamlModuleAutoInstall, GitHubCli, AzureDevOpsCli, NetworkConnectivity"
2424
)]
25-
[ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli")]
26-
[string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion")
25+
[ValidateSet("PowerShell", "Git", "AzureCli", "AzureEnvVars", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "YamlModule", "YamlModuleAutoInstall", "GitHubCli", "AzureDevOpsCli", "NetworkConnectivity")]
26+
[string[]]$Checks = @("PowerShell", "Git", "AzureCliOrEnvVars", "AzureLogin", "AlzModule", "AlzModuleVersion", "NetworkConnectivity")
2727
)
2828
Test-Tooling -Checks $Checks
2929
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#-------------------------------------------------------------------------
2+
Set-Location -Path $PSScriptRoot
3+
#-------------------------------------------------------------------------
4+
$ModuleName = 'ALZ'
5+
$PathToManifest = [System.IO.Path]::Combine('..', '..', '..', $ModuleName, "$ModuleName.psd1")
6+
#-------------------------------------------------------------------------
7+
if (Get-Module -Name $ModuleName -ErrorAction 'SilentlyContinue') {
8+
#if the module is already in memory, remove it
9+
Remove-Module -Name $ModuleName -Force
10+
}
11+
Import-Module $PathToManifest -Force
12+
#-------------------------------------------------------------------------
13+
14+
InModuleScope 'ALZ' {
15+
Describe 'Test-NetworkConnectivity Private Function Tests' -Tag Unit {
16+
BeforeAll {
17+
$WarningPreference = 'SilentlyContinue'
18+
$ErrorActionPreference = 'SilentlyContinue'
19+
}
20+
21+
Context 'All endpoints are reachable' {
22+
BeforeAll {
23+
Mock -CommandName Invoke-WebRequest -MockWith {
24+
[PSCustomObject]@{ StatusCode = 200 }
25+
}
26+
}
27+
28+
It 'returns HasFailure = $false when all endpoints succeed' {
29+
$result = Test-NetworkConnectivity
30+
$result.HasFailure | Should -BeFalse
31+
}
32+
33+
It 'returns a Success result for every endpoint' {
34+
$result = Test-NetworkConnectivity
35+
$result.Results | ForEach-Object {
36+
$_.result | Should -Be "Success"
37+
}
38+
}
39+
40+
It 'returns one result per endpoint (6 total)' {
41+
$result = Test-NetworkConnectivity
42+
$result.Results.Count | Should -Be 6
43+
}
44+
}
45+
46+
Context 'One endpoint is unreachable' {
47+
BeforeAll {
48+
Mock -CommandName Invoke-WebRequest -ParameterFilter { $Uri -eq "https://api.github.com" } -MockWith {
49+
throw "Unable to connect to the remote server"
50+
}
51+
Mock -CommandName Invoke-WebRequest -MockWith {
52+
[PSCustomObject]@{ StatusCode = 200 }
53+
}
54+
}
55+
56+
It 'returns HasFailure = $true' {
57+
$result = Test-NetworkConnectivity
58+
$result.HasFailure | Should -BeTrue
59+
}
60+
61+
It 'returns a Failure result for the unreachable endpoint' {
62+
$result = Test-NetworkConnectivity
63+
$failureResults = @($result.Results | Where-Object { $_.result -eq "Failure" })
64+
$failureResults.Count | Should -Be 1
65+
}
66+
67+
It 'includes the error message in the Failure result' {
68+
$result = Test-NetworkConnectivity
69+
$failureResult = @($result.Results | Where-Object { $_.result -eq "Failure" })[0]
70+
$failureResult.message | Should -Match "Cannot reach"
71+
$failureResult.message | Should -Match "api.github.com"
72+
}
73+
74+
It 'still returns Success results for the reachable endpoints' {
75+
$result = Test-NetworkConnectivity
76+
$successResults = @($result.Results | Where-Object { $_.result -eq "Success" })
77+
$successResults.Count | Should -Be 5
78+
}
79+
}
80+
81+
Context 'All endpoints are unreachable' {
82+
BeforeAll {
83+
Mock -CommandName Invoke-WebRequest -MockWith {
84+
throw "Network unreachable"
85+
}
86+
}
87+
88+
It 'returns HasFailure = $true' {
89+
$result = Test-NetworkConnectivity
90+
$result.HasFailure | Should -BeTrue
91+
}
92+
93+
It 'returns a Failure result for every endpoint' {
94+
$result = Test-NetworkConnectivity
95+
$result.Results | ForEach-Object {
96+
$_.result | Should -Be "Failure"
97+
}
98+
}
99+
100+
It 'returns one result per endpoint (6 total)' {
101+
$result = Test-NetworkConnectivity
102+
$result.Results.Count | Should -Be 6
103+
}
104+
105+
It 'checks all endpoints and does not stop at the first failure' {
106+
$result = Test-NetworkConnectivity
107+
Should -Invoke -CommandName Invoke-WebRequest -Times 6 -Scope It
108+
}
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)