From 2a29a087a4aac903966ec592935f4b5ea9bfa2a2 Mon Sep 17 00:00:00 2001 From: TinaMor Date: Thu, 30 Jan 2025 21:30:56 +0300 Subject: [PATCH 1/2] Stop execution if tool version already installed unless `-Force` is specified Make reinstall check reusable --- Tests/BuildkitTools.Tests.ps1 | 45 ++++++++++- Tests/ContainerdTools.Tests.ps1 | 45 ++++++++++- Tests/NerdctlTools.Tests.ps1 | 53 +++++++++++-- .../Private/CommonToolUtilities.psm1 | 74 ++++++++++++++++++- containers-toolkit/Public/BuildkitTools.psm1 | 48 +++++++----- .../Public/ContainerdTools.psm1 | 34 ++++++--- containers-toolkit/Public/NerdctlTools.psm1 | 44 +++++++---- 7 files changed, 290 insertions(+), 53 deletions(-) diff --git a/Tests/BuildkitTools.Tests.ps1 b/Tests/BuildkitTools.Tests.ps1 index 369d3bc..d705dce 100644 --- a/Tests/BuildkitTools.Tests.ps1 +++ b/Tests/BuildkitTools.Tests.ps1 @@ -55,6 +55,17 @@ Describe "BuildkitTools.psm1" { Mock Test-EmptyDirectory -ModuleName 'BuildkitTools' -MockWith { return $true } Mock Install-Buildkit -ModuleName 'BuildkitTools' Mock Remove-Item -ModuleName 'BuildkitTools' + + # Mock for Invoke-ExecutableCommand- "nerdctl --version" + $mockExecutablePath = "$TestDrive\Program Files\Buildkit\bin\buildkitd.exe" + $mockConfigStdOut = New-MockObject -Type 'System.IO.StreamReader' -Methods @{ ReadToEnd = { return "buildkitd github.com/moby/buildkit v1.0.0 c949t3ti0484" } } + $mockProcess = New-MockObject -Type 'System.Diagnostics.Process' -Properties @{ + StandardOutput = $mockConfigStdOut + ExitCode = 0 + } + Mock Invoke-ExecutableCommand -ModuleName "BuildkitTools" -MockWith { return $mockProcess } -ParameterFilter { + $Executable -eq "$mockExecutablePath" -and + $Arguments -eq "--version" } } AfterEach { @@ -77,6 +88,7 @@ Describe "BuildkitTools.psm1" { It "Should use defaults" { Install-Buildkit -Force -Confirm:$false + Should -Invoke Get-BuildkitLatestVersion -ModuleName 'BuildkitTools' -Times 1 -Exactly -Scope It Should -Invoke Uninstall-Buildkit -ModuleName 'BuildkitTools' -Times 0 -Exactly -Scope It Should -Invoke Get-InstallationFile -ModuleName 'BuildkitTools' -ParameterFilter { $fileParameters[0].Feature -eq "Buildkit" -and @@ -127,11 +139,35 @@ Describe "BuildkitTools.psm1" { -ParameterFilter { $BuildKitPath -eq "$Env:ProgramFiles\Buildkit" -and $WinCNIPath -eq "" } } + It "Should not reinstall tool if version already exists and force is not specified" { + Mock Test-EmptyDirectory -ModuleName 'BuildkitTools' -MockWith { return $false } + + # Mock for Get-ChildItem - "buildkitd.exe" + Mock Get-ChildItem -ModuleName 'BuildkitTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Buildkit" -and + $Recurse -eq $true + $Filter -eq "buildkitd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + Install-Buildkit -Confirm:$false + Should -Invoke Uninstall-Buildkit -ModuleName 'BuildkitTools' -Times 0 + Should -Invoke Install-RequiredFeature -ModuleName 'BuildkitTools' -Times 0 + } + It "Should uninstall tool if it is already installed" { Mock Test-EmptyDirectory -ModuleName 'BuildkitTools' -MockWith { return $false } + # Mock for Get-ChildItem - "buildkitd.exe" + Mock Get-ChildItem -ModuleName 'BuildkitTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Buildkit" -and + $Recurse -eq $true + $Filter -eq "buildkitd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + Install-Buildkit -Force -Confirm:$false + Should -Invoke Invoke-ExecutableCommand -ModuleName "BuildkitTools" ` + -ParameterFilter { ($Executable -eq $mockExecutablePath ) -and ($Arguments -eq "--version") } Should -Invoke Uninstall-Buildkit -ModuleName 'BuildkitTools' -Times 1 -Exactly -Scope It ` -ParameterFilter { $Path -eq "$Env:ProgramFiles\Buildkit" -and $force -eq $true } } @@ -140,7 +176,14 @@ Describe "BuildkitTools.psm1" { Mock Test-EmptyDirectory -ModuleName 'BuildkitTools' -MockWith { return $false } Mock Uninstall-Buildkit -ModuleName 'BuildkitTools' -MockWith { throw 'Error' } - { Install-Buildkit -Confirm:$false } | Should -Throw "Buildkit installation failed. Error" + # Mock for Get-ChildItem - "buildkitd.exe" + Mock Get-ChildItem -ModuleName 'BuildkitTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Buildkit" -and + $Recurse -eq $true + $Filter -eq "buildkitd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + { Install-Buildkit -Confirm:$false -Force } | Should -Throw "Buildkit installation failed. Error" } } diff --git a/Tests/ContainerdTools.Tests.ps1 b/Tests/ContainerdTools.Tests.ps1 index fd3893b..974396e 100644 --- a/Tests/ContainerdTools.Tests.ps1 +++ b/Tests/ContainerdTools.Tests.ps1 @@ -55,6 +55,17 @@ Describe "ContainerdTools.psm1" { Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $true } Mock Install-Containerd -ModuleName 'ContainerdTools' Mock Remove-Item -ModuleName 'ContainerdTools' + + # Mock for Invoke-ExecutableCommand- "nerdctl --version" + $mockExecutablePath = "$TestDrive\Program Files\Containerd\bin\containerd.exe" + $mockConfigStdOut = New-MockObject -Type 'System.IO.StreamReader' -Methods @{ ReadToEnd = { return "containerd github.com/containerd/containerd/v1 v1.0.0 c949t3ti0484" } } + $mockProcess = New-MockObject -Type 'System.Diagnostics.Process' -Properties @{ + StandardOutput = $mockConfigStdOut + ExitCode = 0 + } + Mock Invoke-ExecutableCommand -ModuleName "ContainerdTools" -MockWith { return $mockProcess } -ParameterFilter { + $Executable -eq "$mockExecutablePath" -and + $Arguments -eq "--version" } } It 'Should not process on implicit request for validation (WhatIfPreference)' { @@ -73,6 +84,7 @@ Describe "ContainerdTools.psm1" { It "Should use defaults" { Install-Containerd -Force -Confirm:$false + Should -Invoke Get-ContainerdLatestVersion -ModuleName 'ContainerdTools' -Times 1 -Exactly -Scope It Should -Invoke Uninstall-Containerd -ModuleName 'ContainerdTools' -Times 0 -Exactly -Scope It Should -Invoke Get-InstallationFile -ModuleName 'ContainerdTools' -ParameterFilter { ($fileParameters[0].Feature -eq "Containerd") -and @@ -119,11 +131,35 @@ Describe "ContainerdTools.psm1" { -ParameterFilter { $ContainerdPath -eq "$Env:ProgramFiles\Containerd" } } + It "Should not reinstall tool if version already exists and force is not specified" { + Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $false } + + # Mock for Get-ChildItem - "containerd.exe" + Mock Get-ChildItem -ModuleName 'ContainerdTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Containerd" -and + $Recurse -eq $true + $Filter -eq "containerd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + Install-Containerd -Confirm:$false + Should -Invoke Uninstall-Containerd -ModuleName 'ContainerdTools' -Times 0 + Should -Invoke Install-RequiredFeature -ModuleName 'ContainerdTools' -Times 0 + } + It "Should uninstall tool if it is already installed" { Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $false } + # Mock for Get-ChildItem - "containerd.exe" + Mock Get-ChildItem -ModuleName 'ContainerdTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Containerd" -and + $Recurse -eq $true + $Filter -eq "containerd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + Install-Containerd -Force -Confirm:$false + Should -Invoke Invoke-ExecutableCommand -ModuleName "ContainerdTools" ` + -ParameterFilter { ($Executable -eq $mockExecutablePath ) -and ($Arguments -eq "--version") } Should -Invoke Uninstall-Containerd -ModuleName 'ContainerdTools' -Times 1 -Exactly -Scope It ` -ParameterFilter { $Path -eq "$Env:ProgramFiles\Containerd" -and $force -eq $true } } @@ -132,7 +168,14 @@ Describe "ContainerdTools.psm1" { Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $false } Mock Uninstall-Containerd -ModuleName 'ContainerdTools' -MockWith { throw 'Error' } - { Install-Containerd -Confirm:$false } | Should -Throw "Containerd installation failed. Error" + # Mock for Get-ChildItem - "containerd.exe" + Mock Get-ChildItem -ModuleName 'ContainerdTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\Containerd" -and + $Recurse -eq $true + $Filter -eq "containerd.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + { Install-Containerd -Confirm:$false -Force } | Should -Throw "Containerd installation failed. Error" } } diff --git a/Tests/NerdctlTools.Tests.ps1 b/Tests/NerdctlTools.Tests.ps1 index 75e3f52..15a765e 100644 --- a/Tests/NerdctlTools.Tests.ps1 +++ b/Tests/NerdctlTools.Tests.ps1 @@ -14,8 +14,8 @@ Describe "NerdctlTools.psm1" { $RootPath = Split-Path -Parent $PSScriptRoot $ModuleParentPath = Join-Path -Path $RootPath -ChildPath 'Containers-Toolkit' Import-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force - Import-Module -Name "$ModuleParentPath\Public\NerdctlTools.psm1" Import-Module -Name "$ModuleParentPath\Public\ContainerdTools.psm1" + Import-Module -Name "$ModuleParentPath\Public\BuildkitTools.psm1" Import-Module -Name "$ModuleParentPath\Public\ContainerNetworkTools.psm1" Import-Module -Name "$ModuleParentPath\Public\NerdctlTools.psm1" -Force } @@ -26,8 +26,8 @@ Describe "NerdctlTools.psm1" { AfterAll { Remove-Module -Name "$ModuleParentPath\Private\CommonToolUtilities.psm1" -Force -ErrorAction Ignore - Remove-Module -Name "$ModuleParentPath\Public\BuildkitTools.psm1" -Force -ErrorAction Ignore Remove-Module -Name "$ModuleParentPath\Public\ContainerdTools.psm1" -Force -ErrorAction Ignore + Remove-Module -Name "$ModuleParentPath\Public\BuildkitTools.psm1" -Force -ErrorAction Ignore Remove-Module -Name "$ModuleParentPath\Public\ContainerNetworkTools.psm1" -Force -ErrorAction Ignore Remove-Module -Name "$ModuleParentPath\Public\NerdctlTools.psm1" -Force -ErrorAction Ignore } @@ -49,6 +49,17 @@ Describe "NerdctlTools.psm1" { Mock Install-WinCNIPlugin -ModuleName 'NerdctlTools' Mock Install-Nerdctl -ModuleName 'NerdctlTools' Mock Remove-Item -ModuleName 'NerdctlTools' + + # Mock for Invoke-ExecutableCommand- "nerdctl --version" + $mockExecutablePath = "$TestDrive\Program Files\nerdctl\nerdctl.exe" + $mockConfigStdOut = New-MockObject -Type 'System.IO.StreamReader' -Methods @{ ReadToEnd = { return "nerdctl version v7.9.8" } } + $mockProcess = New-MockObject -Type 'System.Diagnostics.Process' -Properties @{ + StandardOutput = $mockConfigStdOut + ExitCode = 0 + } + Mock Invoke-ExecutableCommand -ModuleName "NerdctlTools" -MockWith { return $mockProcess } -ParameterFilter { + $Executable -eq "$mockExecutablePath" -and + $Arguments -eq "--version" } } It 'Should not process on implicit request for validation (WhatIfPreference)' { @@ -67,6 +78,7 @@ Describe "NerdctlTools.psm1" { It "Should use defaults" { Install-Nerdctl -Force -Confirm:$false + Should -Invoke Get-NerdctlLatestVersion -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It Should -Invoke Uninstall-Nerdctl -ModuleName 'NerdctlTools' -Times 0 -Exactly -Scope It Should -Invoke Get-InstallationFile -ModuleName 'NerdctlTools' -ParameterFilter { $fileParameters[0].Feature -eq "nerdctl" -and @@ -117,11 +129,35 @@ Describe "NerdctlTools.psm1" { Should -Invoke Install-WinCNIPlugin -ModuleName 'NerdctlTools' -Times 0 -Exactly -Scope It } + It "Should not reinstall tool if version already exists and force is not specified" { + Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $false } + + # Mock for Get-ChildItem - "nerdctl.exe" + Mock Get-ChildItem -ModuleName 'NerdctlTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\nerdctl" -and + $Recurse -eq $true + $Filter -eq "nerdctl.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + Install-Nerdctl -Confirm:$false + Should -Invoke Uninstall-Nerdctl -ModuleName 'NerdctlTools' -Times 0 + Should -Invoke Install-RequiredFeature -ModuleName 'NerdctlTools' -Times 0 + } + It "Should uninstall tool if it is already installed" { Mock Test-EmptyDirectory -ModuleName 'NerdctlTools' -MockWith { return $false } + # Mock for Get-ChildItem - "nerdctl.exe" + Mock Get-ChildItem -ModuleName 'NerdctlTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\nerdctl" -and + $Recurse -eq $true + $Filter -eq "nerdctl.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + Install-Nerdctl -Force -Confirm:$false + Should -Invoke Invoke-ExecutableCommand -ModuleName "NerdctlTools" ` + -ParameterFilter { ($Executable -eq $mockExecutablePath ) -and ($Arguments -eq "--version") } Should -Invoke Uninstall-Nerdctl -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It ` -ParameterFilter { $Path -eq "$Env:ProgramFiles\nerdctl" } } @@ -130,11 +166,18 @@ Describe "NerdctlTools.psm1" { Mock Test-EmptyDirectory -ModuleName 'NerdctlTools' -MockWith { return $false } Mock Uninstall-Nerdctl -ModuleName 'NerdctlTools' -MockWith { throw 'Error' } - { Install-Nerdctl -Confirm:$false } | Should -Throw "nerdctl installation failed. Error" + # Mock for Get-ChildItem - "nerdctl.exe" + Mock Get-ChildItem -ModuleName 'NerdctlTools' -ParameterFilter { + $Path -eq "$Env:ProgramFiles\nerdctl" -and + $Recurse -eq $true + $Filter -eq "nerdctl.exe" + } -MockWith { return @{FullName = "$mockExecutablePath" } } + + { Install-Nerdctl -Confirm:$false -Force } | Should -Throw "nerdctl installation failed. Error" } It "Should install all dependencies if 'All' is specified" { - Install-Nerdctl -Dependencies 'All' -Confirm:$false + Install-Nerdctl -Dependencies 'All' -Confirm:$false -Force Should -Invoke Install-Containerd -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It Should -Invoke Install-Buildkit -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It @@ -142,7 +185,7 @@ Describe "NerdctlTools.psm1" { } It "Should install specified dependencies" { - Install-Nerdctl -Dependencies 'containerd' -Confirm:$false + Install-Nerdctl -Dependencies 'containerd' -Confirm:$false -Force Should -Invoke Install-Containerd -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It Should -Invoke Install-Buildkit -ModuleName 'NerdctlTools' -Times 0 -Exactly -Scope It diff --git a/containers-toolkit/Private/CommonToolUtilities.psm1 b/containers-toolkit/Private/CommonToolUtilities.psm1 index 1131b28..df7a4ee 100644 --- a/containers-toolkit/Private/CommonToolUtilities.psm1 +++ b/containers-toolkit/Private/CommonToolUtilities.psm1 @@ -88,7 +88,7 @@ function Get-LatestToolVersion($tool) { "nerdctl" { $NERDCTL_REPO } "wincniplugin" { $WINCNI_PLUGIN_REPO } "cloudnativecni" { $CLOUDNATIVE_CNI_REPO } - Default { Throw "Couldn't get latest $tool version. Invalid tool name: '$tool'." } + Default { Throw "Couldn't get $tool latest version. Invalid tool name: '$tool'." } } # Get the latest release version URL string @@ -107,6 +107,73 @@ function Get-LatestToolVersion($tool) { } } +function Test-IsLatestVersion($Tool, $Version) { + $targetVersion = [System.Version]$Version + + # Get the latest version of the tool + $latestVersion = Get-LatestToolVersion -Tool $Tool + Write-Debug "Latest $Tool version: $latestVersion" + $latestVersion = [System.Version](Get-LatestToolVersion -Tool $Tool) + + $isLatest = ($targetVersion -eq $latestVersion) + if (-not $isLatest) { + Write-Warning "A newer version of $tool is available. { Target Version: $targetVersion, Latest Version: $latestVersion }" + } + return $isLatest +} + +function Test-ToolReinstall { + param( + [string]$tool, + [string]$targetVersion, + [string]$executable + ) + + Write-Debug "Tool: $tool, Target Version: $targetVersion, Executable: $executable" + + # Check if the target version is the latest version + if ($targetVersion -eq 'latest') { + $targetVersion = Get-LatestToolVersion -Tool $tool + } + else { + # Check if a newer version is available + Test-IsLatestVersion -Tool "$tool" -Version $targetVersion | Out-Null + } + + # Check if the tool is already installed + Write-Debug "$tool executable: $executable" + $isInstalled = ($executable -and (Test-Path -Path $executable)) + + # If tool is not installed, the tool is a new installation. + if (-not $isInstalled) { + return $false + } + + # Check if the installed version is the same as the target version + $cmdOutput = Invoke-ExecutableCommand -Executable "$executable" -Arguments "--version" + if ($cmdOutput.ExitCode -ne 0) { + Write-Warning "Failed to get nerdctl version: $($cmdOutput.StandardError.ReadToEnd())" + return $true + } + + # Extract version from the output + # containerd github.com/containerd/containerd/v2 v2.0.2 c507a0257ea6462fbd6f5ba4f5c74facb04021f4 + # buildkitd github.com/moby/buildkit v0.19.0 3637d1b15a13fc3cdd0c16fcf3be0845ae68f53d + # nerdctl version v7.9.8 + $installedVersion = $cmdOutput.StandardOutput.ReadToEnd().Trim() + $installedVersion = ($installedVersion.Split(' ')[2]).TrimStart('v') + Write-Debug "{ Target Version: $targetVersion, Installed Version: $installedVersion }" + + # Compare the installed version with the target version + if ($targetVersion -eq $installedVersion) { + Write-Warning "Installed $tool version is the same as the requested version, '$installedVersion'." + } else { + Write-Warning "$tool version '$installedVersion' is installed." + } + + return $true +} + function Test-EmptyDirectory($path) { if (-not (Test-Path -Path $path)) { return $true @@ -536,6 +603,9 @@ function Test-FileChecksum { $found = $true return } + else { + Write-Debug "File name does not match. {checksum file: $filename, downloaded file: $downloadedFileName}" + } } if (-not $found) { @@ -913,6 +983,8 @@ function Invoke-ExecutableCommand { Export-ModuleMember -Variable CONTAINERD_REPO, BUILDKIT_REPO, NERDCTL_REPO, WINCNI_PLUGIN_REPO, CLOUDNATIVE_CNI_REPO Export-ModuleMember -Function Get-LatestToolVersion +Export-ModuleMember -Function Test-IsLatestVersion +Export-ModuleMember -Function Test-ToolReinstall Export-ModuleMember -Function Get-DefaultInstallPath Export-ModuleMember -Function Test-EmptyDirectory Export-ModuleMember -Function Get-InstallationFile diff --git a/containers-toolkit/Public/BuildkitTools.psm1 b/containers-toolkit/Public/BuildkitTools.psm1 index 133b2d1..7da0e5e 100644 --- a/containers-toolkit/Public/BuildkitTools.psm1 +++ b/containers-toolkit/Public/BuildkitTools.psm1 @@ -56,8 +56,11 @@ function Install-Buildkit { ) begin { + $tool = 'Buildkit' + # Check if Buildkit is alread installed - $isInstalled = -not (Test-EmptyDirectory -Path $InstallPath) + $blktdexe = (Get-ChildItem -Path $InstallPath -Recurse -Filter "buildkitd.exe" | Select-Object -First 1).FullName + $isInstalled = ($null -ne $blktdexe) $WhatIfMessage = "Buildkit will be installed at $InstallPath" if ($isInstalled) { @@ -71,37 +74,46 @@ function Install-Buildkit { process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { - # Check if tool already exists at specified location - if ($isInstalled) { - $errMsg = "Buildkit already exists at $InstallPath or the directory is not empty" + $latestVersion = Get-BuildkitLatestVersion + + # Get Buildkit version to install + if (!$Version) { + # Get default version + $Version = $latestVersion + } + $Version = $Version.TrimStart('v') + + # Check if we need to reinstall + $reinstall = Test-ToolReinstall $tool $Version $blktdexe + if ($reinstall) { + $errMsg = "$tool already exists at $InstallPath." Write-Warning $errMsg + if (!$Force) { + Write-Warning "Installation cancelled. $errMsg Please uninstall the existing version using 'Uninstall-Buildkit' or use -Force to reinstall." + return + } + # Uninstall if tool exists at specified location. Requires user consent try { - Uninstall-Buildkit -Path "$InstallPath" -Force:$Force -Confirm:$false | Out-Null + Uninstall-Buildkit -Path "$InstallPath" -Confirm:$false -Force:$Force | Out-Null } catch { - Throw "Buildkit installation failed. $_" + Throw "nerdctl installation failed. $_" } } - # Get Buildkit version to install - if (!$Version) { - $Version = Get-BuildkitLatestVersion - } - $Version = $Version.TrimStart('v') - Write-Output "Downloading and installing Buildkit v$Version at $InstallPath" # Download files $downloadParams = @{ - ToolName = "Buildkit" - Repository = "$BUILDKIT_REPO" - Version = $Version - OSArchitecture = $OSArchitecture - DownloadPath = $DownloadPath + ToolName = "Buildkit" + Repository = "moby/buildkit" + Version = $Version + OSArchitecture = $OSArchitecture + DownloadPath = $DownloadPath ChecksumSchemaFile = "$ModuleParentPath\Private\schemas\in-toto.sbom.schema.json" - FileFilterRegEx = $null + FileFilterRegEx = $null } $downloadParamsProperties = [FileDownloadParameters]::new( $downloadParams.ToolName, diff --git a/containers-toolkit/Public/ContainerdTools.psm1 b/containers-toolkit/Public/ContainerdTools.psm1 index 85bdc89..2052730 100644 --- a/containers-toolkit/Public/ContainerdTools.psm1 +++ b/containers-toolkit/Public/ContainerdTools.psm1 @@ -45,8 +45,11 @@ function Install-Containerd { ) begin { + $tool = 'Containerd' + # Check if Containerd is alread installed - $isInstalled = -not (Test-EmptyDirectory -Path $InstallPath) + $ctrexe = (Get-ChildItem -Path $InstallPath -Recurse -Filter "containerd.exe" | Select-Object -First 1).FullName + $isInstalled = ($null -ne $ctrexe) $WhatIfMessage = "Containerd will be installed at $InstallPath" if ($isInstalled) { @@ -60,26 +63,35 @@ function Install-Containerd { process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { - # Check if tool already exists at specified location - if ($isInstalled) { - $errMsg = "Containerd already exists at $InstallPath or the directory is not empty" + $latestVersion = Get-ContainerdLatestVersion + + # Get Containerd version to install + if (!$Version) { + # Get default version + $Version = $latestVersion + } + $Version = $Version.TrimStart('v') + + # Check if we need to reinstall + $reinstall = Test-ToolReinstall $tool $Version $ctrexe + if ($reinstall) { + $errMsg = "$tool already exists at $InstallPath." Write-Warning $errMsg + if (!$Force) { + Write-Warning "Installation cancelled. $errMsg Please uninstall the existing version using 'Uninstall-Containerd' or use -Force to reinstall." + return + } + # Uninstall if tool exists at specified location. Requires user consent try { Uninstall-Containerd -Path "$InstallPath" -Confirm:$false -Force:$Force | Out-Null } catch { - Throw "Containerd installation failed. $_" + Throw "nerdctl installation failed. $_" } } - # Get Containerd version to install - if (!$Version) { - # Get default version - $Version = Get-ContainerdLatestVersion - } - $Version = $Version.TrimStart('v') Write-Output "Downloading and installing Containerd v$version at $InstallPath" # Download files diff --git a/containers-toolkit/Public/NerdctlTools.psm1 b/containers-toolkit/Public/NerdctlTools.psm1 index cfb3767..626a4a7 100644 --- a/containers-toolkit/Public/NerdctlTools.psm1 +++ b/containers-toolkit/Public/NerdctlTools.psm1 @@ -86,8 +86,11 @@ function Install-Nerdctl { ) begin { + $tool = 'nerdctl' + # Check if Containerd is alread installed - $isInstalled = -not (Test-EmptyDirectory -Path $InstallPath) + $nerdctlexe = (Get-ChildItem -Path $InstallPath -Recurse -Filter "nerdctl.exe" | Select-Object -First 1).FullName + $isInstalled = ($null -ne $nerdctlexe) $toInstall = @("nerdctl") @@ -108,11 +111,26 @@ function Install-Nerdctl { process { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, $WhatIfMessage)) { - # Check if tool already exists at specified location - if ($isInstalled) { - $errMsg = "nerdctl already exists at $InstallPath or the directory is not empty" + $latestVersion = Get-NerdctlLatestVersion + + # Get nerdctl version to install + if (!$Version) { + # Get default version + $Version = $latestVersion + } + $Version = $Version.TrimStart('v') + + # Check if we need to reinstall + $reinstall = Test-ToolReinstall $tool $Version $nerdctlexe + if ($reinstall) { + $errMsg = "$tool already exists at $InstallPath." Write-Warning $errMsg + if (!$Force) { + Write-Warning "Installation cancelled. $errMsg Please uninstall the existing version using 'Uninstall-Nerdctl' or use -Force to reinstall." + return + } + # Uninstall if tool exists at specified location. Requires user consent try { Uninstall-Nerdctl -Path "$InstallPath" -Confirm:$false -Force:$Force | Out-Null @@ -122,23 +140,17 @@ function Install-Nerdctl { } } - # Get nerdctl version to install - if (!$Version) { - $Version = Get-NerdctlLatestVersion - } - $Version = $Version.TrimStart('v') - Write-Output "Downloading and installing nerdctl v$version at $InstallPath" # Download files $downloadParams = @{ - ToolName = "nerdctl" - Repository = "$NERDCTL_REPO" - Version = $version - OSArchitecture = $OSArchitecture - DownloadPath = $DownloadPath + ToolName = "nerdctl" + Repository = "containerd/nerdctl" + Version = $version + OSArchitecture = $OSArchitecture + DownloadPath = $DownloadPath ChecksumSchemaFile = $null - FileFilterRegEx = $null + FileFilterRegEx = $null } $downloadParamsProperties = [FileDownloadParameters]::new( $downloadParams.ToolName, From ddf149b8d5e5d5a97eed3ed0a0e3c846a7050195 Mon Sep 17 00:00:00 2001 From: TinaMor Date: Wed, 12 Mar 2025 18:23:12 +0300 Subject: [PATCH 2/2] Fix nerdctl tests --- Tests/NerdctlTools.Tests.ps1 | 34 ++++++++++++++----- .../Private/CommonToolUtilities.psm1 | 1 + 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/Tests/NerdctlTools.Tests.ps1 b/Tests/NerdctlTools.Tests.ps1 index 15a765e..38bfd07 100644 --- a/Tests/NerdctlTools.Tests.ps1 +++ b/Tests/NerdctlTools.Tests.ps1 @@ -36,8 +36,9 @@ Describe "NerdctlTools.psm1" { BeforeAll { $Script:nerdctlRepo = 'https://github.com/containerd/nerdctl/releases/download' $Script:TestDownloadPath = "$HOME\Downloads\nerdctl-7.9.8-windows-amd64.tar.gz" + $mockExecutablePath = "$TestDrive\Program Files\nerdctl\nerdctl.exe" - Mock Get-NerdctlLatestVersion { return '7.9.8' } -ModuleName 'NerdctlTools' + Mock Get-LatestToolVersion { return '7.9.8' } -ModuleName 'NerdctlTools' Mock Uninstall-Nerdctl -ModuleName "NerdctlTools" Mock Get-InstallationFile -ModuleName 'NerdctlTools' -MockWith { return $Script:TestDownloadPath } Mock Install-RequiredFeature -ModuleName 'NerdctlTools' @@ -49,9 +50,11 @@ Describe "NerdctlTools.psm1" { Mock Install-WinCNIPlugin -ModuleName 'NerdctlTools' Mock Install-Nerdctl -ModuleName 'NerdctlTools' Mock Remove-Item -ModuleName 'NerdctlTools' + Mock Test-Path -ModuleName 'NerdctlTools' -MockWith { return $false } -ParameterFilter { + $Path -eq "$mockExecutablePath" + } # Mock for Invoke-ExecutableCommand- "nerdctl --version" - $mockExecutablePath = "$TestDrive\Program Files\nerdctl\nerdctl.exe" $mockConfigStdOut = New-MockObject -Type 'System.IO.StreamReader' -Methods @{ ReadToEnd = { return "nerdctl version v7.9.8" } } $mockProcess = New-MockObject -Type 'System.Diagnostics.Process' -Properties @{ StandardOutput = $mockConfigStdOut @@ -76,6 +79,8 @@ Describe "NerdctlTools.psm1" { } It "Should use defaults" { + Mock Get-NerdctlLatestVersion -ModuleName 'NerdctlTools' -MockWith { return 'latest' } + Install-Nerdctl -Force -Confirm:$false Should -Invoke Get-NerdctlLatestVersion -ModuleName 'NerdctlTools' -Times 1 -Exactly -Scope It @@ -106,6 +111,7 @@ Describe "NerdctlTools.psm1" { # Mocks $MockDownloadPath = 'TestDrive:\Downloads\nerdctl-1.2.3-windows-amd64.tar.gz' Mock Get-InstallationFile -ModuleName 'NerdctlTools' -MockWith { return $MockDownloadPath } + Mock Get-LatestToolVersion { return '1.2.3' } -ModuleName 'NerdctlTools' # Test Install-Nerdctl -Version '1.2.3' -InstallPath 'TestDrive:\nerdctl' -DownloadPath 'TestDrive:\Downloads' -Dependencies 'containerd' -OSArchitecture "arm64" -Force -Confirm:$false @@ -130,8 +136,6 @@ Describe "NerdctlTools.psm1" { } It "Should not reinstall tool if version already exists and force is not specified" { - Mock Test-EmptyDirectory -ModuleName 'ContainerdTools' -MockWith { return $false } - # Mock for Get-ChildItem - "nerdctl.exe" Mock Get-ChildItem -ModuleName 'NerdctlTools' -ParameterFilter { $Path -eq "$Env:ProgramFiles\nerdctl" -and @@ -139,14 +143,17 @@ Describe "NerdctlTools.psm1" { $Filter -eq "nerdctl.exe" } -MockWith { return @{FullName = "$mockExecutablePath" } } + # Mock Test-Path: return true so that the tool is considered installed + Mock Test-Path -ModuleName 'NerdctlTools' -MockWith { return $true } -ParameterFilter { + $Path -eq "$mockExecutablePath" + } + Install-Nerdctl -Confirm:$false - Should -Invoke Uninstall-Nerdctl -ModuleName 'NerdctlTools' -Times 0 - Should -Invoke Install-RequiredFeature -ModuleName 'NerdctlTools' -Times 0 + Should -Invoke Uninstall-Nerdctl -ModuleName 'NerdctlTools' -Times 0 -Scope It + Should -Invoke Install-RequiredFeature -ModuleName 'NerdctlTools' -Times 0 -Scope It } It "Should uninstall tool if it is already installed" { - Mock Test-EmptyDirectory -ModuleName 'NerdctlTools' -MockWith { return $false } - # Mock for Get-ChildItem - "nerdctl.exe" Mock Get-ChildItem -ModuleName 'NerdctlTools' -ParameterFilter { $Path -eq "$Env:ProgramFiles\nerdctl" -and @@ -154,6 +161,11 @@ Describe "NerdctlTools.psm1" { $Filter -eq "nerdctl.exe" } -MockWith { return @{FullName = "$mockExecutablePath" } } + # Mock Test-Path: return true so that the tool is considered installed + Mock Test-Path -ModuleName 'NerdctlTools' -MockWith { return $true } -ParameterFilter { + $Path -eq "$mockExecutablePath" + } + Install-Nerdctl -Force -Confirm:$false Should -Invoke Invoke-ExecutableCommand -ModuleName "NerdctlTools" ` @@ -163,7 +175,6 @@ Describe "NerdctlTools.psm1" { } It "Should throw an error if uninstallation fails" { - Mock Test-EmptyDirectory -ModuleName 'NerdctlTools' -MockWith { return $false } Mock Uninstall-Nerdctl -ModuleName 'NerdctlTools' -MockWith { throw 'Error' } # Mock for Get-ChildItem - "nerdctl.exe" @@ -173,6 +184,11 @@ Describe "NerdctlTools.psm1" { $Filter -eq "nerdctl.exe" } -MockWith { return @{FullName = "$mockExecutablePath" } } + # Mock Test-Path: return true so that the tool is considered installed + Mock Test-Path -ModuleName 'NerdctlTools' -MockWith { return $true } -ParameterFilter { + $Path -eq "$mockExecutablePath" + } + { Install-Nerdctl -Confirm:$false -Force } | Should -Throw "nerdctl installation failed. Error" } diff --git a/containers-toolkit/Private/CommonToolUtilities.psm1 b/containers-toolkit/Private/CommonToolUtilities.psm1 index df7a4ee..f66a634 100644 --- a/containers-toolkit/Private/CommonToolUtilities.psm1 +++ b/containers-toolkit/Private/CommonToolUtilities.psm1 @@ -146,6 +146,7 @@ function Test-ToolReinstall { # If tool is not installed, the tool is a new installation. if (-not $isInstalled) { + Write-Debug "$tool is not installed. Proceeding with installation." return $false }