Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions map.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,10 @@
"id": "linux-detect-nvme-readiness",
"path": "src/linux/linux-detect-nvme-readiness.sh",
"description": "Read-only check of whether a Linux OS disk can boot from an NVMe disk controller. Reports unstable /dev/sd* references in /etc/fstab, whether the initramfs contains an NVMe driver, and the dracut hostonly setting. Makes no changes. NOTE: use option --run-on-repair."
},
{
"id": "win-enable-nvme-boot-driver",
"path": "src/windows/win-enable-nvme-boot-driver.ps1",
"description": "Enables the Windows inbox stornvme boot-start driver on the offline OS disk of a VM that no longer boots after switching to an NVMe disk controller. Mode=Report (default) is read-only; Mode=Repair backs up, writes and verifies; Mode=Rollback restores the backup emitted by Repair. NOTE: use option --run-on-repair."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if a user runs it on the original VM? Should we add guardrails around it

}
]
4 changes: 4 additions & 0 deletions src/windows/win-enable-nvme-boot-driver.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
#
#########################################################################################################

# Mode and BackupFile are used inside the Invoke-WithHive script block below; PSScriptAnalyzer
# cannot see through that script block, so PSReviewUnusedParameter reports them as unused.
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '',
Justification = 'Mode and BackupFile are used inside the Invoke-WithHive script block below; PSScriptAnalyzer cannot see through that script block.')]
Param(
[Parameter(Mandatory = $false)][ValidateSet('Report', 'Repair', 'Rollback')][string]$Mode = 'Report',
[Parameter(Mandatory = $false)][ValidatePattern('^[A-Za-z]:?$')][string]$OsDriveLetter = '',
Expand Down
56 changes: 56 additions & 0 deletions tests/test-map-catalog.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Catalogue tests for map.json — the public run-id contract.
# Run from the repository root:
# pwsh -NoProfile -File ./tests/test-map-catalog.ps1

$ErrorActionPreference = 'Stop'
$repositoryRoot = Split-Path $PSScriptRoot -Parent
$mapPath = Join-Path $repositoryRoot 'map.json'

function Assert-True {
Param([bool]$Condition, [string]$Message)
if (-not $Condition) { throw "ASSERTION FAILED: $Message" }
}

# A run id that resolves to nothing, or to the wrong file, fails only at the moment an operator is
# running a repair against a broken VM. That is the worst possible time to discover it.
$catalog = @(Get-Content -LiteralPath $mapPath -Raw | ConvertFrom-Json)
Assert-True ($catalog.Count -gt 0) 'map.json must contain at least one entry.'

foreach ($entry in $catalog) {
Assert-True (-not [string]::IsNullOrWhiteSpace($entry.id)) 'Every entry needs an id.'
Assert-True (-not [string]::IsNullOrWhiteSpace($entry.path)) "Entry '$($entry.id)' needs a path."
Assert-True (-not [string]::IsNullOrWhiteSpace($entry.description)) "Entry '$($entry.id)' needs a description."
Assert-True ($entry.path -notmatch '\\') "Entry '$($entry.id)' must use forward slashes; the path is used in a URL."
Assert-True (Test-Path -LiteralPath (Join-Path $repositoryRoot $entry.path) -PathType Leaf) `
"Entry '$($entry.id)' points at '$($entry.path)', which does not exist."
}

$duplicates = @($catalog | Group-Object id | Where-Object Count -gt 1)
Assert-True ($duplicates.Count -eq 0) "Duplicate run ids: $(($duplicates | ForEach-Object Name) -join ', ')."

# The extension resolves a run id to a path and then downloads the whole bundle. An entry whose OS
# does not match its directory sends a PowerShell script to a Linux guest, or the reverse.
foreach ($entry in $catalog) {
if ($entry.path -like 'src/windows/*') {
Assert-True ($entry.path -like '*.ps1') "Windows entry '$($entry.id)' must point at a .ps1 file."
}
elseif ($entry.path -like 'src/linux/*') {
Assert-True ($entry.path -like '*.sh') "Linux entry '$($entry.id)' must point at a .sh file."
}
else {
throw "Entry '$($entry.id)' is outside src/windows and src/linux: '$($entry.path)'."
}
}

$nvmeRecovery = @($catalog | Where-Object id -eq 'win-enable-nvme-boot-driver')
Assert-True ($nvmeRecovery.Count -eq 1) 'win-enable-nvme-boot-driver must be registered exactly once.'
Assert-True ($nvmeRecovery[0].path -eq 'src/windows/win-enable-nvme-boot-driver.ps1') `
'win-enable-nvme-boot-driver must point at the Windows recovery script.'
Assert-True ($nvmeRecovery[0].description -match '--run-on-repair') `
'The recovery run id operates on an attached offline disk, so its description must say --run-on-repair.'

foreach ($detector in 'win-detect-nvme-readiness', 'linux-detect-nvme-readiness') {
Assert-True (@($catalog | Where-Object id -eq $detector).Count -eq 1) "$detector must stay registered."
}

Write-Host "PASS: map.json catalogue — $($catalog.Count) entries, unique ids, existing paths, OS/extension agreement, NVMe run ids registered."