Skip to content

Commit

Permalink
Refactor Show-ContainerTools
Browse files Browse the repository at this point in the history
    - Refactor Show-ContainerTools to reuse functions and rewrite the unit tests
    - Bug fixes
    - Spelling issues
  • Loading branch information
TinaMor committed Feb 6, 2025
1 parent 7cb550b commit 2603330
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 69 deletions.
121 changes: 108 additions & 13 deletions Tests/AllToolsUtilities.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,121 @@ Describe "AllToolsUtilities.psm1" {

Context "Show-ContainerTools" -Tag "Show-ContainerTools" {
BeforeAll {
Mock Get-InstalledVersion -ModuleName 'AllToolsUtilities'
# Mock get version
$mockConfigStdOut = New-MockObject -Type 'System.IO.StreamReader' -Methods @{ ReadToEnd = { return "tool version v1.0.1" } }
$mockConfigProcess = New-MockObject -Type 'System.Diagnostics.Process' -Properties @{
ExitCode = 0
StandardOutput = $mockConfigStdOut
}
Mock Invoke-ExecutableCommand -ModuleName "AllToolsUtilities" `
-ParameterFilter { $Arguments -eq "--version" } `
-MockWith { return $mockConfigProcess }
}

It "Should get containerd version" {
$executablePath = "TestDrive:\Program Files\Containerd\bin\containerd.exe"
Mock Get-Command -ModuleName 'AllToolsUtilities' -MockWith { @{ Name = 'containerd.exe'; Source = $executablePath } }
Mock Get-Service -ModuleName 'AllToolsUtilities'

$containerdVersion = Show-ContainerTools -ToolName 'containerd'

# Check the output
$expectedOutput = [PSCustomObject]@{
Tool = 'containerd'
Path = $executablePath
Installed = $true
Version = 'v1.0.1'
Daemon = 'containerd'
DaemonStatus = 'Unregistered'
}
# $containerdVersion | Should -Be $expectedOutput
# HACK: Should -Be does not work with PSCustomObject in PSv5.
# However PSv6 has support for this. To be investigated further.
foreach ($key in $expectedOutput.Keys) {
$expectedValue = $expectedOutput[$key]
$actualValue = $containerdVersion.$key
$actualValue | Should -Be $expectedValue
}

# Check the invocation
Should -Invoke Get-Command -ModuleName 'AllToolsUtilities' `
-Times 1 -Exactly -Scope It -ParameterFilter { $Name -eq 'containerd.exe' }
}

It "Should get buildkit version" {
$executablePath = "TestDrive:\Program Files\Buildkit\bin\buildkitd.exe"
$buildctlPath = "TestDrive:\Program Files\Buildkit\bin\buildctl.exe"

Mock Get-Service -ModuleName 'AllToolsUtilities' -MockWith { @{ Status = "Running" } }
Mock Get-Command -ModuleName 'AllToolsUtilities' -MockWith { @(
@{ Name = 'buildkitd.exe'; Source = $executablePath }
@{ Name = 'buildctl.exe'; Source = $buildctlPath }
) }

$buildkitVersion = Show-ContainerTools -ToolName 'buildkit'

# Check the output
$expectedOutput = [PSCustomObject]@{
Tool = 'buildkit'
Path = $executablePath
Installed = $true
Version = 'v1.0.1'
Daemon = 'buildkitd'
DaemonStatus = 'Running'
BuildctlPath = $buildctlPath
}
foreach ($key in $expectedOutput.Keys) {
$expectedValue = $expectedOutput[$key]
$actualValue = $buildkitVersion.$key
$actualValue | Should -Be $expectedValue
}

# Check the invocation
Should -Invoke Get-Command -ModuleName 'AllToolsUtilities' `
-Times 1 -Exactly -Scope It -ParameterFilter { $Name -eq "build*.exe" }
}

It "Should list all container tools and their install status" {
Show-ContainerTools
It "Should return basic info if the tool is not installed" {
Mock Get-Command -ModuleName 'AllToolsUtilities'

@("containerd", "buildkit", "nerdctl") | ForEach-Object {
Should -Invoke Get-InstalledVersion -ModuleName 'AllToolsUtilities' `
-Times 1 -Exactly -Scope It `
-ParameterFilter { $Feature -eq $_ }
$toolInfo = Show-ContainerTools

# Check the output
$expectedOutput = @(
[PSCustomObject]@{ Tool = 'containerd'; Installed = $false; Daemon = 'containerd'; DaemonStatus = 'Unregistered' }
[PSCustomObject]@{ Tool = 'buildkit'; Installed = $false; Daemon = 'buildkitd'; DaemonStatus = 'Unregistered' }
[PSCustomObject]@{ Tool = 'nerdctl'; Installed = $false }
)
$expectedOutput | ForEach-Object {
$tool = $_.Tool
$actualOutput = $toolInfo | Where-Object { $_.Tool -eq $tool }
foreach ($key in $_.Keys) {
$expectedValue = $_[$key]
$actualValue = $actualOutput.$key
$actualValue | Should -Be $expectedValue
}
}
}

It "Should list the latest available version for each tool" {
Show-ContainerTools -Latest
It "Should return latest version if Latest flag is specified" {
Mock Get-Command -ModuleName 'AllToolsUtilities'

$toolInfo = Show-ContainerTools -Latest

@("containerd", "buildkit", "nerdctl") | ForEach-Object {
Should -Invoke Get-InstalledVersion -ModuleName 'AllToolsUtilities' `
-Times 1 -Exactly -Scope It `
-ParameterFilter { $Feature -eq $_ -and $Latest -eq $true }
# Check the output
$expectedOutput = @(
[PSCustomObject]@{ Tool = 'containerd'; Installed = $false; Daemon = 'buildkitd'; DaemonStatus = 'Unregistered'; LatestVersion = 'v1.0.1' }
[PSCustomObject]@{ Tool = 'buildkit'; Installed = $false; Daemon = 'buildkitd'; DaemonStatus = 'Unregistered'; LatestVersion = 'v1.0.1' }
[PSCustomObject]@{ Tool = 'nerdctl'; Installed = $false; LatestVersion = 'v1.0.1' }
)
$expectedOutput | ForEach-Object {
$tool = $_.Tool
$actualOutput = $toolInfo | Where-Object { $_.Tool -eq $tool }
foreach ($key in $_.Keys) {
$expectedValue = $_[$key]
$actualValue = $actualOutput.$key
$actualValue | Should -Be $expectedValue
}
}
}
}
Expand Down
13 changes: 10 additions & 3 deletions Tests/CommonToolUtilities.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ Describe "CommonToolUtilities.psm1" {
}

Context "Get-LatestToolVersion" -Tag "Get-LatestToolVersion" {
BeforeEach {
$expectedUri = "https://api.github.com/repos/containerd/containerd/releases/latest"
}

It "Should return the latest version for a tool" {
$sampleOutput = @{
StatusCode = 200
Expand All @@ -98,17 +102,20 @@ Describe "CommonToolUtilities.psm1" {
}
Mock Invoke-WebRequest { $sampleOutput } -ModuleName "CommonToolUtilities"

$result = Get-LatestToolVersion -Repository "test/tool"
$result = Get-LatestToolVersion -Tool "containerd"

$expectedUri = "https://api.github.com/repos/test/tool/releases/latest"
Should -Invoke Invoke-WebRequest -ParameterFilter { $Uri -eq $expectedUri } -Exactly 1 -Scope It -ModuleName "CommonToolUtilities"
$result | Should -Be '0.12.3'
}

It "Should throw an error if invalid tool name is provided" {
{ Get-LatestToolVersion -Tool "invalid-tool" } | Should -Throw "Couldn't get latest invalid-tool version. Invalid tool name: 'invalid-tool'."
}

It "Should throw an error if API call fails" {
$errorMessage = "Response status code does not indicate success: 404 (Not Found)."
Mock Invoke-WebRequest -MockWith { Throw $errorMessage } -ModuleName "CommonToolUtilities"
{ Get-LatestToolVersion -Repository "test/tool" } | Should -Throw "Could not get tool latest version. $errorMessage"
{ Get-LatestToolVersion -Tool "containerd" } | Should -Throw "Couldn't get containerd latest version from $expectedUri. $errorMessage"
}
}

Expand Down
16 changes: 16 additions & 0 deletions Tests/ContainerNetworkTools.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ Describe "ContainerNetworkTools.psm1" {
Remove-Module -Name "$RootPath\Tests\TestData\MockClasses.psm1" -Force -ErrorAction Ignore
}

Context "Get-WinCNILatestVersion" -Tag "Get-WinCNILatestVersion" {
BeforeEach {
Mock Get-LatestToolVersion -ModuleName 'ContainerNetworkTools'
}

It "Should return the latest version of Windows CNI plugin" {
Get-WinCNILatestVersion
Should -Invoke Get-LatestToolVersion -Times 1 -Scope It -ModuleName 'ContainerNetworkTools' -ParameterFilter { $Tool -eq 'wincniplugin' }
}

It "Should return the latest version of Cloud Native CNI plugin" {
Get-WinCNILatestVersion -Repo 'containernetworking/plugins'
Should -Invoke Get-LatestToolVersion -Times 1 -Scope It -ModuleName 'ContainerNetworkTools' -ParameterFilter { $Tool -eq 'cloudnativecni' }
}
}

Context "Install-WinCNIPlugin" -Tag "Install-WinCNIPlugin" {
BeforeAll {
$Script:ToolName = 'WinCNIPlugin'
Expand Down
32 changes: 27 additions & 5 deletions containers-toolkit/Private/CommonToolUtilities.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,38 @@ $HASH_FUNCTIONS_STR = $HASH_FUNCTIONS -join '|' # SHA1|SHA256|SHA384|SHA512|MD5
$NERDCTL_CHECKSUM_FILE_PATTERN = "(?<hashfunction>(?:^({0})))" -f ($HASH_FUNCTIONS -join '|')
$NERDCTL_FILTER_SCRIPTBLOCK_STR = { (("{0}" -match "$NERDCTL_CHECKSUM_FILE_PATTERN") -and "{0}" -notmatch ".*.asc$") }.ToString()

function Get-LatestToolVersion($repository) {

Set-Variable -Option AllScope -scope Global -Visibility Public -Name "CONTAINERD_REPO" -Value "containerd/containerd" -Force
Set-Variable -Option AllScope -scope Global -Visibility Public -Name "BUILDKIT_REPO" -Value "moby/buildkit" -Force
Set-Variable -Option AllScope -scope Global -Visibility Public -Name "NERDCTL_REPO" -Value "containerd/nerdctl" -Force
Set-Variable -Option AllScope -scope Global -Visibility Public -Name "WINCNI_PLUGIN_REPO" -Value "microsoft/windows-container-networking" -Force
Set-Variable -Option AllScope -scope Global -Visibility Public -Name "CLOUDNATIVE_CNI_REPO" -Value "containernetworking/plugins" -Force


function Get-LatestToolVersion($tool) {
# Get the repository based on the tool
$repository = switch ($tool.ToLower()) {
"containerd" { $CONTAINERD_REPO }
"buildkit" { $BUILDKIT_REPO }
"nerdctl" { $NERDCTL_REPO }
"wincniplugin" { $WINCNI_PLUGIN_REPO }
"cloudnativecni" { $CLOUDNATIVE_CNI_REPO }
Default { Throw "Couldn't get latest $tool version. Invalid tool name: '$tool'." }
}

# Get the latest release version URL string
$uri = "https://api.github.com/repos/$repository/releases/latest"

Write-Debug "Getting the latest $tool version from $uri"

# Get the latest release version
try {
$uri = "https://api.github.com/repos/$repository/releases/latest"
$response = Invoke-WebRequest -Uri $uri -UseBasicParsing
$version = ($response.content | ConvertFrom-Json).tag_name
return $version.TrimStart("v")
}
catch {
$tool = ($repository -split "/")[1]
Throw "Could not get $tool latest version. $($_.Exception.Message)"
Throw "Couldn't get $tool latest version from $uri. $($_.Exception.Message)"
}
}

Expand Down Expand Up @@ -889,7 +911,7 @@ function Invoke-ExecutableCommand {
return $p
}


Export-ModuleMember -Variable CONTAINERD_REPO, BUILDKIT_REPO, NERDCTL_REPO, WINCNI_PLUGIN_REPO, CLOUDNATIVE_CNI_REPO
Export-ModuleMember -Function Get-LatestToolVersion
Export-ModuleMember -Function Get-DefaultInstallPath
Export-ModuleMember -Function Test-EmptyDirectory
Expand Down
50 changes: 36 additions & 14 deletions containers-toolkit/Public/AllToolsUtilities.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force
function Show-ContainerTools {
param (
[Parameter(HelpMessage = "Show latest release version")]
[Switch]$Latest
[Switch]$Latest,

[Parameter(HelpMessage = "Tool to show")]
[ValidateSet("containerd", "buildkit", "nerdctl")]
[String[]]$ToolName
)

$tools = @("containerd", "buildkit", "nerdctl")
$tools = if ($ToolName) { $ToolName } else { @("containerd", "buildkit", "nerdctl") }

$installedTools = @()
foreach ($tool in $tools) {
Expand Down Expand Up @@ -173,21 +177,27 @@ To register containerd and buildkitd services and create a NAT network, see help
}

function Get-InstalledVersion($feature, $Latest) {
$executable = $null
$sourceLocation = $null
$daemon = $null
$buildctlPath = $null
switch ($feature) {
"buildkit" {
$bktdExecutable = (Get-Command "build*.exe" | Where-Object { $_.Source -like "*buildkit*" }) | Select-Object Name
if ($bktdExecutable) {
$executable = ($bktdExecutable[0]).Name
$blktCommandInfo = Get-Command "build*.exe" | Where-Object { $_.Source -like "*buildkit*" }
if ($null -ne $blktCommandInfo) {
# Get buildkitd executable
$buldkitdCommandInfo = $blktCommandInfo | Where-Object { $_.Name -like "buildkitd.exe" }
$sourceLocation = $buldkitdCommandInfo.Source
}
$daemon = 'buildkitd'

if ($null -ne ($bktdExecutable | Where-Object { $_.Name -contains "buildkitd.exe" })) {
$daemon = 'buildkitd'
}
$buildctlPath = ($blktCommandInfo | Where-Object { $_.Name -like "buildctl.exe" }).Source
}
Default {
$executable = (Get-Command "$feature.exe" -ErrorAction Ignore).Name
$commandInfo = Get-Command "$feature.exe" -ErrorAction Ignore

if ($null -ne $commandInfo) {
$sourceLocation = $commandInfo.Source
}

if ($feature -eq 'containerd') {
$daemon = 'containerd'
Expand All @@ -199,16 +209,21 @@ function Get-InstalledVersion($feature, $Latest) {
Tool = $feature
Installed = $False
}
if ($executable) {
$result = getToolVersion -Executable $executable
if ($sourceLocation) {
$result = getToolVersion -Executable $sourceLocation
Add-Member -InputObject $result -Name 'Tool' -Value $feature -MemberType 'NoteProperty'
$result = $result | Select-Object Tool, Installed, Version
Add-Member -InputObject $result -Name 'Path' -Value $sourceLocation -MemberType 'NoteProperty'
$result = $result | Select-Object Tool, Path, Installed, Version

if ($daemon) {
Add-Member -InputObject $result -Name 'Daemon' -Value $daemon -MemberType 'NoteProperty'
Add-Member -InputObject $result -Name 'DaemonStatus' -MemberType 'NoteProperty' `
-Value (getDaemonStatus -Daemon $daemon)
}

if ($buildctlPath) {
$result | Add-Member -Name 'BuildctlPath' -Value $buildctlPath -MemberType 'NoteProperty'
}
}

# Get latest version
Expand All @@ -223,9 +238,16 @@ function Get-InstalledVersion($feature, $Latest) {
}

function getToolVersion($executable) {
$toolName = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetFileName($executable))

$installedVersion = $null
try {
$version = & $executable -v
$cmdOutput = Invoke-ExecutableCommand -Executable $executable -Arguments '--version'
if ($cmdOutput.ExitCode -ne 0) {
Throw "Couldn't get $toolName version. $($cmdOutput.StandardError.ReadToEnd())"
}

$version = $cmdOutput.StandardOutput.ReadToEnd()

$pattern = "(\d+\.)(\d+\.)(\*|\d+)"
$installedVersion = ($version | Select-String -Pattern $pattern).Matches.Value
Expand Down
4 changes: 2 additions & 2 deletions containers-toolkit/Public/BuildkitTools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ $ModuleParentPath = Split-Path -Parent $PSScriptRoot
Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force

function Get-BuildkitLatestVersion {
$latestVersion = Get-LatestToolVersion -Repository "moby/buildkit"
$latestVersion = Get-LatestToolVersion -Tool "buildkit"
return $latestVersion
}

Expand Down Expand Up @@ -96,7 +96,7 @@ function Install-Buildkit {
# Download files
$downloadParams = @{
ToolName = "Buildkit"
Repository = "moby/buildkit"
Repository = "$BUILDKIT_REPO"
Version = $Version
OSArchitecture = $OSArchitecture
DownloadPath = $DownloadPath
Expand Down
18 changes: 14 additions & 4 deletions containers-toolkit/Public/ContainerNetworkTools.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,19 @@ using module "..\Private\CommonToolUtilities.psm1"
$ModuleParentPath = Split-Path -Parent $PSScriptRoot
Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force

$WINCNI_PLUGIN_REPO = "microsoft/windows-container-networking"
$CLOUDNATIVE_CNI_REPO = "containernetworking/plugins"

function Get-WinCNILatestVersion {
$latestVersion = Get-LatestToolVersion -Repository "microsoft/windows-container-networking"
param (
[String]$repo = "microsoft/windows-container-networking"
)
$tool = switch ($repo.ToLower()) {
$WINCNI_PLUGIN_REPO { "wincniplugin" }
$CLOUDNATIVE_CNI_REPO { "cloudnativecni" }
Default { Throw "Invalid repository. Supported repositories are $WINCNI_PLUGIN_REPO and $CLOUDNATIVE_CNI_REPO" }
}
$latestVersion = Get-LatestToolVersion -Tool $tool
return $latestVersion
}

Expand Down Expand Up @@ -80,7 +91,7 @@ function Install-WinCNIPlugin {
# Get Windows CNI plugins version to install
if (!$WinCNIVersion) {
# Get default version
$WinCNIVersion = Get-WinCNILatestVersion
$WinCNIVersion = Get-WinCNILatestVersion -Repo $SourceRepo
}
$WinCNIVersion = $WinCNIVersion.TrimStart('v')
Write-Output "Downloading CNI plugin version $WinCNIVersion at $WinCNIPath"
Expand Down Expand Up @@ -385,8 +396,7 @@ function Install-MissingPlugin {
$consent = ([ActionConsent](Get-Host).UI.PromptForChoice($title, $question, $choices, 1)) -eq [ActionConsent]::Yes

if (-not $consent) {
$downloadPath = "https://github.com/microsoft/windows-container-networking"
Throw "Windows CNI plugins have not been installed. To install, run the command `"Install-WinCNIPlugin`" or download from $downloadPath."
Throw "Windows CNI plugins have not been installed. To install, run the command `"Install-WinCNIPlugin`"."
}
}

Expand Down
Loading

0 comments on commit 2603330

Please sign in to comment.