k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cluster/gce/windows/common.psm1 (about) 1 # Copyright 2019 The Kubernetes Authors. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 <# 16 .SYNOPSIS 17 Library containing common variables and code used by other PowerShell modules 18 and scripts for configuring Windows nodes. 19 #> 20 21 # IMPORTANT PLEASE NOTE: 22 # Any time the file structure in the `windows` directory changes, 23 # `windows/BUILD` and `k8s.io/release/lib/releaselib.sh` must be manually 24 # updated with the changes. 25 # We HIGHLY recommend not changing the file structure, because consumers of 26 # Kubernetes releases depend on the release structure remaining stable. 27 28 # Disable progress bar to increase download speed. 29 $ProgressPreference = 'SilentlyContinue' 30 31 # REDO_STEPS affects the behavior of a node that is rebooted after initial 32 # bringup. When true, on a reboot the scripts will redo steps that were 33 # determined to have already been completed once (e.g. to overwrite 34 # already-existing config files). When false the scripts will perform the 35 # minimum required steps to re-join this node to the cluster. 36 $REDO_STEPS = $false 37 Export-ModuleMember -Variable REDO_STEPS 38 39 # Writes $Message to the console. Terminates the script if $Fatal is set. 40 function Log-Output { 41 param ( 42 [parameter(Mandatory=$true)] [string]$Message, 43 [switch]$Fatal 44 ) 45 Write-Host "${Message}" 46 if (${Fatal}) { 47 Exit 1 48 } 49 } 50 51 # Dumps detailed information about the specified service to the console output. 52 # $Delay can be set to a positive value to introduce some seconds of delay 53 # before querying the service information, which may produce more consistent 54 # results if this function is called immediately after changing a service's 55 # configuration. 56 function Write-VerboseServiceInfoToConsole { 57 param ( 58 [parameter(Mandatory=$true)] [string]$Service, 59 [parameter(Mandatory=$false)] [int]$Delay = 0 60 ) 61 if ($Delay -gt 0) { 62 Start-Sleep $Delay 63 } 64 Get-Service -ErrorAction Continue $Service | Select-Object * | Out-String 65 & sc.exe queryex $Service 66 & sc.exe qc $Service 67 & sc.exe qfailure $Service 68 } 69 70 # Checks if a file should be written or overwritten by testing if it already 71 # exists and checking the value of the global $REDO_STEPS variable. Emits an 72 # informative message if the file already exists. 73 # 74 # Returns $true if the file does not exist, or if it does but the global 75 # $REDO_STEPS variable is set to $true. Returns $false if the file exists and 76 # the caller should not overwrite it. 77 function ShouldWrite-File { 78 param ( 79 [parameter(Mandatory=$true)] [string]$Filename 80 ) 81 if (Test-Path $Filename) { 82 if ($REDO_STEPS) { 83 Log-Output "Warning: $Filename already exists, will overwrite it" 84 return $true 85 } 86 Log-Output "Skip: $Filename already exists, not overwriting it" 87 return $false 88 } 89 return $true 90 } 91 92 # Returns the GCE instance metadata value for $Key. If the key is not present 93 # in the instance metadata returns $Default if set, otherwise returns $null. 94 function Get-InstanceMetadata { 95 param ( 96 [parameter(Mandatory=$true)] [string]$Key, 97 [parameter(Mandatory=$false)] [string]$Default 98 ) 99 100 $url = "http://metadata.google.internal/computeMetadata/v1/instance/$Key" 101 try { 102 $client = New-Object Net.WebClient 103 $client.Headers.Add('Metadata-Flavor', 'Google') 104 return ($client.DownloadString($url)).Trim() 105 } 106 catch [System.Net.WebException] { 107 if ($Default) { 108 return $Default 109 } 110 else { 111 Log-Output "Failed to retrieve value for $Key." 112 return $null 113 } 114 } 115 } 116 117 # Returns the GCE instance metadata value for $Key where key is an "attribute" 118 # of the instance. If the key is not present in the instance metadata returns 119 # $Default if set, otherwise returns $null. 120 function Get-InstanceMetadataAttribute { 121 param ( 122 [parameter(Mandatory=$true)] [string]$Key, 123 [parameter(Mandatory=$false)] [string]$Default 124 ) 125 126 return Get-InstanceMetadata "attributes/$Key" $Default 127 } 128 129 function Validate-SHA { 130 param( 131 [parameter(Mandatory=$true)] [string]$Hash, 132 [parameter(Mandatory=$true)] [string]$Path, 133 [parameter(Mandatory=$true)] [string]$Algorithm 134 ) 135 $actual = Get-FileHash -Path $Path -Algorithm $Algorithm 136 # Note: Powershell string comparisons are case-insensitive by default, and this 137 # is important here because Linux shell scripts produce lowercase hashes but 138 # Powershell Get-FileHash produces uppercase hashes. This must be case-insensitive 139 # to work. 140 if ($actual.Hash -ne $Hash) { 141 Log-Output "$Path corrupted, $Algorithm $actual doesn't match expected $Hash" 142 Throw ("$Path corrupted, $Algorithm $actual doesn't match expected $Hash") 143 } 144 } 145 146 # Attempts to download the file from URLs, trying each URL until it succeeds. 147 # It will loop through the URLs list forever until it has a success. If 148 # successful, it will write the file to OutFile. You can optionally provide a 149 # Hash argument with an optional Algorithm, in which case it will attempt to 150 # validate the downloaded file against the hash. SHA512 will be used if 151 # -Algorithm is not provided. 152 # This function is idempotent, if OutFile already exists and has the correct Hash 153 # then the download will be skipped. If the Hash is incorrect, the file will be 154 # overwritten. 155 function MustDownload-File { 156 param ( 157 [parameter(Mandatory = $false)] [string]$Hash, 158 [parameter(Mandatory = $false)] [string]$Algorithm = 'SHA512', 159 [parameter(Mandatory = $true)] [string]$OutFile, 160 [parameter(Mandatory = $true)] [System.Collections.Generic.List[String]]$URLs, 161 [parameter(Mandatory = $false)] [System.Collections.IDictionary]$Headers = @{}, 162 [parameter(Mandatory = $false)] [int]$Attempts = 0 163 ) 164 165 # If the file is already downloaded and matches the expected hash, skip the download. 166 if ((Test-Path -Path $OutFile) -And -Not [string]::IsNullOrEmpty($Hash)) { 167 try { 168 Validate-SHA -Hash $Hash -Path $OutFile -Algorithm $Algorithm 169 Log-Output "Skip download of ${OutFile}, it already exists with expected hash." 170 return 171 } 172 catch { 173 # The hash does not match the file on disk. 174 # Proceed with the download and overwrite the file. 175 Log-Output "${OutFile} exists but had wrong hash. Redownloading." 176 } 177 } 178 179 $currentAttempt = 0 180 while ($true) { 181 foreach ($url in $URLs) { 182 if (($Attempts -ne 0) -And ($currentAttempt -Gt 5)) { 183 throw "Attempted to download ${url} ${currentAttempt} times. Giving up." 184 } 185 $currentAttempt++ 186 try { 187 Get-RemoteFile -OutFile $OutFile -Url $url -Headers $Headers 188 } 189 catch { 190 $message = $_.Exception.ToString() 191 Log-Output "Failed to download file from ${Url}. Will retry. Error: ${message}" 192 continue 193 } 194 # Attempt to validate the hash 195 if (-Not [string]::IsNullOrEmpty($Hash)) { 196 try { 197 Validate-SHA -Hash $Hash -Path $OutFile -Algorithm $Algorithm 198 } 199 catch { 200 $message = $_.Exception.ToString() 201 Log-Output "Hash validation of ${url} failed. Will retry. Error: ${message}" 202 continue 203 } 204 Log-Output "Downloaded ${url} (${Algorithm} = ${Hash})" 205 return 206 } 207 Log-Output "Downloaded ${url}" 208 return 209 } 210 } 211 } 212 213 # Downloads a file via HTTP/HTTPS. 214 # If the file is stored in GCS and this is running on a GCE node with a service account 215 # with credentials that have the devstore.read_only auth scope the bearer token will be 216 # automatically added to download the file. 217 function Get-RemoteFile { 218 param ( 219 [parameter(Mandatory = $true)] [string]$OutFile, 220 [parameter(Mandatory = $true)] [string]$Url, 221 [parameter(Mandatory = $false)] [System.Collections.IDictionary]$Headers = @{} 222 ) 223 224 # Load the System.Net.Http assembly if it's not loaded yet. 225 if ("System.Net.Http.HttpClient" -as [type]) {} else { 226 Add-Type -AssemblyName System.Net.Http 227 } 228 229 $timeout = New-TimeSpan -Minutes 5 230 231 try { 232 # Use HttpClient in favor of WebClient. 233 # https://docs.microsoft.com/en-us/dotnet/api/system.net.webclient?view=net-5.0#remarks 234 $httpClient = New-Object -TypeName System.Net.Http.HttpClient 235 $httpClient.Timeout = $timeout 236 foreach ($key in $Headers.Keys) { 237 $httpClient.DefaultRequestHeaders.Add($key, $Headers[$key]) 238 } 239 # If the URL is for GCS and the node has dev storage scope, add the 240 # service account OAuth2 bearer token to the request headers. 241 # https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances#applications 242 if (($Url -match "^https://storage`.googleapis`.com.*") -and $(Check-StorageScope)) { 243 $httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer $(Get-Credentials)") 244 } 245 246 # Attempt to download the file 247 $httpResponseMessage = $httpClient.GetAsync([System.Uri]::new($Url)) 248 $httpResponseMessage.Wait() 249 if (-not $httpResponseMessage.IsCanceled) { 250 # Check if the request was successful. 251 # 252 # DO NOT replace with EnsureSuccessStatusCode(), it prints the 253 # OAuth2 bearer token. 254 if (-not $httpResponseMessage.Result.IsSuccessStatusCode) { 255 $statusCode = $httpResponseMessage.Result.StatusCode 256 throw "Downloading ${Url} returned status code ${statusCode}, retrying." 257 } 258 try { 259 $outFileStream = [System.IO.FileStream]::new($OutFile, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write) 260 $copyResult = $httpResponseMessage.Result.Content.CopyToAsync($outFileStream) 261 $copyResult.Wait() 262 $outFileStream.Close() 263 if ($null -ne $copyResult.Exception) { 264 throw $copyResult.Exception 265 } 266 } 267 finally { 268 if ($null -ne $outFileStream) { 269 $outFileStream.Dispose() 270 } 271 } 272 } 273 } 274 finally { 275 if ($null -ne $httpClient) { 276 $httpClient.Dispose() 277 } 278 } 279 } 280 281 # Returns the default service account token for the VM, retrieved from 282 # the instance metadata. 283 function Get-Credentials { 284 While($true) { 285 $data = Get-InstanceMetadata -Key "service-accounts/default/token" 286 if ($data) { 287 return ($data | ConvertFrom-Json).access_token 288 } 289 Start-Sleep -Seconds 1 290 } 291 } 292 293 # Returns True if the VM has the dev storage scope, False otherwise. 294 function Check-StorageScope { 295 While($true) { 296 $data = Get-InstanceMetadata -Key "service-accounts/default/scopes" 297 if ($data) { 298 return ($data -match "auth/devstorage") -or ($data -match "auth/cloud-platform") 299 } 300 Start-Sleep -Seconds 1 301 } 302 } 303 304 # This compiles some C# code that can make syscalls, and pulls the 305 # result into our powershell environment so we can make syscalls from this script. 306 # We make syscalls directly, because whatever the powershell cmdlets do under the hood, 307 # they can't seem to open the log files concurrently with writers. 308 # See https://docs.microsoft.com/en-us/dotnet/framework/interop/marshaling-data-with-platform-invoke 309 # for details on which unmanaged types map to managed types. 310 $SyscallDefinitions = @' 311 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 312 public static extern IntPtr CreateFileW( 313 String lpFileName, 314 UInt32 dwDesiredAccess, 315 UInt32 dwShareMode, 316 IntPtr lpSecurityAttributes, 317 UInt32 dwCreationDisposition, 318 UInt32 dwFlagsAndAttributes, 319 IntPtr hTemplateFile 320 ); 321 322 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 323 public static extern bool SetFilePointer( 324 IntPtr hFile, 325 Int32 lDistanceToMove, 326 IntPtr lpDistanceToMoveHigh, 327 UInt32 dwMoveMethod 328 ); 329 330 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 331 public static extern bool SetEndOfFile( 332 IntPtr hFile 333 ); 334 335 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 336 public static extern bool CloseHandle( 337 IntPtr hObject 338 ); 339 '@ 340 $Kernel32 = Add-Type -MemberDefinition $SyscallDefinitions -Name 'Kernel32' -Namespace 'Win32' -PassThru 341 342 # Close-Handle closes the specified open file handle. 343 # On failure, throws an exception. 344 function Close-Handle { 345 param ( 346 [parameter(Mandatory=$true)] [System.IntPtr]$Handle 347 ) 348 $ret = $Kernel32::CloseHandle($Handle) 349 $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 350 if (-not $ret) { 351 throw "Failed to close open file handle ${Handle}, system error code: ${err}" 352 } 353 } 354 355 # Open-File tries to open the file at the specified path with ReadWrite access mode and ReadWrite file share mode. 356 # On success, returns an open file handle. 357 # On failure, throws an exception. 358 function Open-File { 359 param ( 360 [parameter(Mandatory=$true)] [string]$Path 361 ) 362 363 $lpFileName = $Path 364 $dwDesiredAccess = [System.IO.FileAccess]::ReadWrite 365 $dwShareMode = [System.IO.FileShare]::ReadWrite # Fortunately golang also passes these same flags when it creates the log files, so we can open it concurrently. 366 $lpSecurityAttributes = [System.IntPtr]::Zero 367 $dwCreationDisposition = [System.IO.FileMode]::Open 368 $dwFlagsAndAttributes = [System.IO.FileAttributes]::Normal 369 $hTemplateFile = [System.IntPtr]::Zero 370 371 $handle = $Kernel32::CreateFileW($lpFileName, $dwDesiredAccess, $dwShareMode, $lpSecurityAttributes, $dwCreationDisposition, $dwFlagsAndAttributes, $hTemplateFile) 372 $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 373 if ($handle -eq -1) { 374 throw "Failed to open file ${Path}, system error code: ${err}" 375 } 376 377 return $handle 378 } 379 380 # Truncate-File truncates the file in-place by opening it, moving the file pointer to the beginning, 381 # and setting the end of file to the file pointer's location. 382 # On failure, throws an exception. 383 # The file must have been originally created with FILE_SHARE_WRITE for this to be possible. 384 # Fortunately Go creates files with FILE_SHARE_READ|FILE_SHARE_WRITE by for all os.Open calls, 385 # so our log writers should be doing the right thing. 386 function Truncate-File { 387 param ( 388 [parameter(Mandatory=$true)] [string]$Path 389 ) 390 $INVALID_SET_FILE_POINTER = 0xffffffff 391 $NO_ERROR = 0 392 $FILE_BEGIN = 0 393 394 $handle = Open-File -Path $Path 395 396 # https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer 397 # Docs: Because INVALID_SET_FILE_POINTER is a valid value for the low-order DWORD of the new file pointer, 398 # you must check both the return value of the function and the error code returned by GetLastError to 399 # determine whether or not an error has occurred. If an error has occurred, the return value of SetFilePointer 400 # is INVALID_SET_FILE_POINTER and GetLastError returns a value other than NO_ERROR. 401 $ret = $Kernel32::SetFilePointer($handle, 0, [System.IntPtr]::Zero, $FILE_BEGIN) 402 $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 403 if ($ret -eq $INVALID_SET_FILE_POINTER -and $err -ne $NO_ERROR) { 404 Close-Handle -Handle $handle 405 throw "Failed to set file pointer for handle ${handle}, system error code: ${err}" 406 } 407 408 $ret = $Kernel32::SetEndOfFile($handle) 409 $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() 410 if ($ret -eq 0) { 411 Close-Handle -Handle $handle 412 throw "Failed to set end of file for handle ${handle}, system error code: ${err}" 413 } 414 Close-Handle -Handle $handle 415 } 416 417 # FileRotationConfig defines the common options for file rotation. 418 class FileRotationConfig { 419 # Force rotation, ignoring $MaxBackupInterval and $MaxSize criteria. 420 [bool]$Force 421 # Maximum time since last backup, after which file will be rotated. 422 # When no backups exist, Rotate-File acts as if -MaxBackupInterval has not elapsed, 423 # instead relying on the other criteria. 424 [TimeSpan]$MaxBackupInterval 425 # Maximum file size, after which file will be rotated. 426 [int]$MaxSize 427 # Maximum number of backup archives to maintain. 428 [int]$MaxBackups 429 } 430 431 # New-FileRotationConfig constructs a FileRotationConfig with default options. 432 function New-FileRotationConfig { 433 param ( 434 # Force rotation, ignoring $MaxBackupInterval and $MaxSize criteria. 435 [parameter(Mandatory=$false)] [switch]$Force, 436 # Maximum time since last backup, after which file will be rotated. 437 # When no backups exist, Rotate-File acts as if -MaxBackupInterval has not elapsed, 438 # instead relying on the other criteria. 439 # Defaults to daily rotations. 440 [parameter(Mandatory=$false)] [TimeSpan]$MaxBackupInterval = $(New-TimeSpan -Day 1), 441 # Maximum file size, after which file will be rotated. 442 [parameter(Mandatory=$false)] [int]$MaxSize = 100mb, 443 # Maximum number of backup archives to maintain. 444 [parameter(Mandatory=$false)] [int]$MaxBackups = 5 445 ) 446 $config = [FileRotationConfig]::new() 447 $config.Force = $Force 448 $config.MaxBackupInterval = $MaxBackupInterval 449 $config.MaxSize = $MaxSize 450 $config.MaxBackups = $MaxBackups 451 return $config 452 } 453 454 # Get-Backups returns a list of paths to backup files for the original file path -Path, 455 # assuming that backup files are in the same directory, with a prefix matching 456 # the original file name and a .zip suffix. 457 function Get-Backups { 458 param ( 459 # Original path of the file for which backups were created (no suffix). 460 [parameter(Mandatory=$true)] [string]$Path 461 ) 462 $parent = Split-Path -Parent -Path $Path 463 $leaf = Split-Path -Leaf -Path $Path 464 $files = Get-ChildItem -File -Path $parent | 465 Where-Object Name -like "${leaf}*.zip" 466 return $files 467 } 468 469 # Trim-Backups deletes old backups for the log file identified by -Path until only -Count remain. 470 # Deletes backups with the oldest CreationTime first. 471 function Trim-Backups { 472 param ( 473 [parameter(Mandatory=$true)] [int]$Count, 474 [parameter(Mandatory=$true)] [string]$Path 475 ) 476 if ($Count -lt 0) { 477 $Count = 0 478 } 479 # If creating a new backup will exceed $Count, delete the oldest files 480 # until we have one less than $Count, leaving room for the new one. 481 # If the pipe results in zero items, $backups is $null, and if it results 482 # in only one item, PowerShell doesn't wrap in an array, so we check both cases. 483 # In the latter case, this actually caused it to often trim all backups, because 484 # .Length is also a property of FileInfo (size of the file)! 485 $backups = Get-Backups -Path $Path | Sort-Object -Property CreationTime 486 if ($backups -and $backups.GetType() -eq @().GetType() -and $backups.Length -gt $Count) { 487 $num = $backups.Length - $Count 488 $rmFiles = $backups | Select-Object -First $num 489 ForEach ($file in $rmFiles) { 490 Remove-Item $file.FullName 491 } 492 } 493 } 494 495 # Backup-File creates a copy of the file at -Path. 496 # The name of the backup is the same as the file, 497 # with the suffix "-%Y%m%d-%s" to identify the time of the backup. 498 # Returns the path to the backup file. 499 function Backup-File { 500 param ( 501 [parameter(Mandatory=$true)] [string]$Path 502 ) 503 $date = Get-Date -UFormat "%Y%m%d-%s" 504 $dest = "${Path}-${date}" 505 Copy-Item -Path $Path -Destination $dest 506 return $dest 507 } 508 509 # Compress-BackupFile creates a compressed archive containing the file 510 # at -Path and subsequently deletes the file at -Path. We split backup 511 # and compression steps to minimize time between backup and truncation, 512 # which helps minimize log loss. 513 function Compress-BackupFile { 514 param ( 515 [parameter(Mandatory=$true)] [string]$Path 516 ) 517 Compress-Archive -Path $Path -DestinationPath "${Path}.zip" 518 Remove-Item -Path $Path 519 } 520 521 # Rotate-File rotates the log file at -Path by first making a compressed copy of the original 522 # log file with the suffix "-%Y%m%d-%s" to identify the time of the backup, then truncating 523 # the original file in-place. Rotation is performed according to the options in -Config. 524 function Rotate-File { 525 param ( 526 # Path to the log file to rotate. 527 [parameter(Mandatory=$true)] [string]$Path, 528 # Config for file rotation. 529 [parameter(Mandatory=$true)] [FileRotationConfig]$Config 530 ) 531 function rotate { 532 # If creating a new backup will exceed $MaxBackups, delete the oldest files 533 # until we have one less than $MaxBackups, leaving room for the new one. 534 Trim-Backups -Count ($Config.MaxBackups - 1) -Path $Path 535 536 $backupPath = Backup-File -Path $Path 537 Truncate-File -Path $Path 538 Compress-BackupFile -Path $backupPath 539 } 540 541 # Check Force 542 if ($Config.Force) { 543 rotate 544 return 545 } 546 547 # Check MaxSize. 548 $file = Get-Item $Path 549 if ($file.Length -gt $Config.MaxSize) { 550 rotate 551 return 552 } 553 554 # Check MaxBackupInterval. 555 $backups = Get-Backups -Path $Path | Sort-Object -Property CreationTime 556 if ($backups.Length -ge 1) { 557 $lastBackupTime = $backups[0].CreationTime 558 $now = Get-Date 559 if ($now - $lastBackupTime -gt $Config.MaxBackupInterval) { 560 rotate 561 return 562 } 563 } 564 } 565 566 # Rotate-Files rotates the log files in directory -Path that match -Pattern. 567 # Rotation is performed by Rotate-File, according to -Config. 568 function Rotate-Files { 569 param ( 570 # Pattern that file names must match to be rotated. Does not include parent path. 571 [parameter(Mandatory=$true)] [string]$Pattern, 572 # Path to the log directory containing files to rotate. 573 [parameter(Mandatory=$true)] [string]$Path, 574 # Config for file rotation. 575 [parameter(Mandatory=$true)] [FileRotationConfig]$Config 576 577 ) 578 $files = Get-ChildItem -File -Path $Path | Where-Object Name -match $Pattern 579 ForEach ($file in $files) { 580 try { 581 Rotate-File -Path $file.FullName -Config $Config 582 } catch { 583 Log-Output "Caught exception rotating $($file.FullName): $($_.Exception)" 584 } 585 } 586 } 587 588 # Schedule-LogRotation schedules periodic log rotation with the Windows Task Scheduler. 589 # Rotation is performed by Rotate-Files, according to -Pattern and -Config. 590 # The system will check whether log files need to be rotated at -RepetitionInterval. 591 function Schedule-LogRotation { 592 param ( 593 # Pattern that file names must match to be rotated. Does not include parent path. 594 [parameter(Mandatory=$true)] [string]$Pattern, 595 # Path to the log directory containing files to rotate. 596 [parameter(Mandatory=$true)] [string]$Path, 597 # Interval at which to check logs against rotation criteria. 598 # Minimum 1 minute, maximum 31 days (see https://docs.microsoft.com/en-us/windows/desktop/taskschd/taskschedulerschema-interval-repetitiontype-element). 599 [parameter(Mandatory=$true)] [TimeSpan]$RepetitionInterval, 600 # Config for file rotation. 601 [parameter(Mandatory=$true)] [FileRotationConfig]$Config 602 ) 603 # Write a powershell script to a file that imports this module ($PSCommandPath) 604 # and calls Rotate-Files with the configured arguments. 605 $scriptPath = "C:\rotate-kube-logs.ps1" 606 New-Item -Force -ItemType file -Path $scriptPath | Out-Null 607 Set-Content -Path $scriptPath @" 608 `$ErrorActionPreference = 'Stop' 609 Import-Module -Force ${PSCommandPath} 610 `$maxBackupInterval = New-Timespan -Days $($Config.MaxBackupInterval.Days) -Hours $($Config.MaxBackupInterval.Hours) -Minutes $($Config.MaxBackupInterval.Minutes) -Seconds $($Config.MaxBackupInterval.Seconds) 611 `$config = New-FileRotationConfig -Force:`$$($Config.Force) -MaxBackupInterval `$maxBackupInterval -MaxSize $($Config.MaxSize) -MaxBackups $($Config.MaxBackups) 612 Rotate-Files -Pattern '${Pattern}' -Path '${Path}' -Config `$config 613 "@ 614 # The task will execute the rotate-kube-logs.ps1 script created above. 615 # We explicitly set -WorkingDirectory to $Path for safety's sake, otherwise 616 # it runs in %windir%\system32 by default, which sounds dangerous. 617 $action = New-ScheduledTaskAction -Execute "powershell" -Argument "-NoLogo -NonInteractive -File ${scriptPath}" -WorkingDirectory $Path 618 # Start the task immediately, and trigger the task once every $RepetitionInterval. 619 $trigger = New-ScheduledTaskTrigger -Once -At $(Get-Date) -RepetitionInterval $RepetitionInterval 620 # Run the task as the same user who is currently running this script. 621 $principal = New-ScheduledTaskPrincipal $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) 622 # Just use the default task settings. 623 $settings = New-ScheduledTaskSettingsSet 624 # Create the ScheduledTask object from the above parameters. 625 $task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settings -Description "Rotate Kubernetes logs" 626 # Register the new ScheduledTask with the Task Scheduler. 627 # Always try to unregister and re-register, in case it already exists (e.g. across reboots). 628 $name = "RotateKubeLogs" 629 try { 630 Unregister-ScheduledTask -Confirm:$false -TaskName $name 631 } catch {} finally { 632 Register-ScheduledTask -TaskName $name -InputObject $task 633 } 634 } 635 636 # Returns true if this node is part of a test cluster (see 637 # cluster/gce/config-test.sh). $KubeEnv is a hash table containing the kube-env 638 # metadata keys+values. 639 function Test-IsTestCluster { 640 param ( 641 [parameter(Mandatory=$true)] [hashtable]$KubeEnv 642 ) 643 644 if ($KubeEnv.Contains('TEST_CLUSTER') -and ` 645 ($KubeEnv['TEST_CLUSTER'] -eq 'true')) { 646 return $true 647 } 648 return $false 649 } 650 651 # Permanently adds a directory to the $env:PATH environment variable. 652 function Add-MachineEnvironmentPath { 653 param ( 654 [parameter(Mandatory=$true)] [string]$Path 655 ) 656 # Verify that the $Path is not already in the $env:Path variable. 657 $pathForCompare = $Path.TrimEnd('\').ToLower() 658 foreach ($p in $env:Path.Split(";")) { 659 if ($p.TrimEnd('\').ToLower() -eq $pathForCompare) { 660 return 661 } 662 } 663 664 $newMachinePath = $Path + ";" + ` 665 [System.Environment]::GetEnvironmentVariable("Path","Machine") 666 [Environment]::SetEnvironmentVariable("Path", $newMachinePath, ` 667 [System.EnvironmentVariableTarget]::Machine) 668 $env:Path = $Path + ";" + $env:Path 669 } 670 671 # Export all public functions: 672 Export-ModuleMember -Function *-*