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
8 changes: 2 additions & 6 deletions modules/aliases-kubectl/Functions/helper.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,6 @@ function Set-KubectlLocal {
$LOCAL_BIN = [IO.Path]::Combine($HOME, '.local', 'bin')
$KUBECTL_LOCAL = [IO.Path]::Combine($LOCAL_BIN, $KUBECTL)
$KUBECTL_DIR = [IO.Path]::Combine($HOME, '.local', 'share', 'kubectl')
# initialize retry variable for kubectl download loop
$RETRY_COUNT = 0
}

process {
Expand All @@ -331,10 +329,8 @@ function Set-KubectlLocal {
} elseif ($IsMacOS) {
'darwin/arm64'
}
do {
[Net.WebClient]::new().DownloadFile("https://dl.k8s.io/release/${serverVersion}/bin/$dlSysArch/$KUBECTL", $kctlVer)
$RETRY_COUNT++
} until ((Test-Path $kctlVer -PathType Leaf) -or $RETRY_COUNT -ge 2)
# download, verify and place the binary atomically
Invoke-KubectlDownload -Version $serverVersion -SysArch $dlSysArch -Destination $kctlVer -BinaryName $KUBECTL | Out-Null
}

# replace existing ~/.local/bin/kubectl symbolic link
Expand Down
120 changes: 120 additions & 0 deletions modules/aliases-kubectl/Functions/internal.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,123 @@
<#
.SYNOPSIS
Download the kubectl binary for the specified server version, verify its integrity
and place it atomically at the destination path.
.DESCRIPTION
The binary is streamed to a temporary file with progress reporting and a request
timeout, then validated against the official SHA256 checksum published by the
kubernetes release server. Only a fully downloaded and verified file is moved to
the destination, so an interrupted or corrupted download never replaces a good
binary. Partial files are always cleaned up.

.PARAMETER Version
Kubernetes server version to download the matching kubectl client for (e.g. 'v1.35.6').
.PARAMETER SysArch
Download system architecture in '<os>/<arch>' format (e.g. 'linux/amd64').
.PARAMETER Destination
Full path the verified kubectl binary should be saved to.
.PARAMETER BinaryName
Name of the kubectl binary ('kubectl' or 'kubectl.exe').
#>
function Invoke-KubectlDownload {
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Version,

[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$SysArch,

[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]$Destination,

[ValidateNotNullOrEmpty()]
[string]$BinaryName = 'kubectl'
)

begin {
$baseUri = "https://dl.k8s.io/release/$Version/bin/$SysArch/$BinaryName"
# download to a temporary file so the destination is never left partial
$tempFile = "$Destination.$([IO.Path]::GetRandomFileName()).part"
}

process {
# get the expected checksum first, so a failure here aborts before downloading
try {
$expectedHash = (Invoke-RestMethod -Uri "$baseUri.sha256" -TimeoutSec 30).Trim()
} catch {
Write-Warning "Failed to retrieve kubectl checksum for $Version ($($_.Exception.Message))."
return $false
}

$client = [Net.Http.HttpClient]::new()
# bound the whole request so a stalled download cannot hang the terminal
$client.Timeout = [timespan]::FromMinutes(5)
try {
$client.DefaultRequestHeaders.UserAgent.ParseAdd('PowerShell')
$response = $client.GetAsync($baseUri, [Net.Http.HttpCompletionOption]::ResponseHeadersRead).Result
try {
if (-not $response.IsSuccessStatusCode) {
Write-Warning "Failed to download kubectl $Version. Status code: $($response.StatusCode)."
return $false
}

$totalBytes = $response.Content.Headers.ContentLength
$srcStream = $response.Content.ReadAsStreamAsync().Result
$dstStream = [IO.FileStream]::new($tempFile, [IO.FileMode]::Create, [IO.FileAccess]::Write)
try {
$buffer = [byte[]]::new(1MB)
$readTotal = 0
while (($read = $srcStream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$dstStream.Write($buffer, 0, $read)
$readTotal += $read
if ($totalBytes) {
$percent = [math]::Round($readTotal / $totalBytes * 100)
$status = "$([math]::Round($readTotal / 1MB, 1))/$([math]::Round($totalBytes / 1MB, 1)) MB"
Write-Progress -Activity "Downloading kubectl $Version" -Status $status -PercentComplete $percent
} else {
Write-Progress -Activity "Downloading kubectl $Version" -Status "$([math]::Round($readTotal / 1MB, 1)) MB"
}
}
} finally {
$dstStream.Dispose()
$srcStream.Dispose()
Write-Progress -Activity "Downloading kubectl $Version" -Completed
}
Comment thread
szymonos marked this conversation as resolved.
} finally {
$response.Dispose()
}
} catch {
Write-Warning "Failed to download kubectl $Version ($($_.Exception.InnerException.Message ?? $_.Exception.Message))."
Comment thread
szymonos marked this conversation as resolved.
return $false
} finally {
$client.Dispose()
}

# verify integrity before accepting the file
$actualHash = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash
if ($actualHash -ne $expectedHash) {
Write-Warning "Checksum mismatch for kubectl $Version - discarding download."
return $false
}

# atomically move the verified binary into place
[IO.File]::Move($tempFile, $Destination, $true)
return $true
Comment thread
szymonos marked this conversation as resolved.
}

clean {
# always remove the temporary file if it is still around
if (Test-Path $tempFile -PathType Leaf) {
Remove-Item $tempFile -Force -ErrorAction SilentlyContinue
}
}
}


<#
.SYNOPSIS
Write provided kubectl with its arguments and then execute it.
Expand Down
2 changes: 1 addition & 1 deletion modules/aliases-kubectl/aliases-kubectl.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'aliases-kubectl.psm1'

# Version number of this module.
ModuleVersion = '0.13.0'
ModuleVersion = '0.13.1'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down