common.psm1 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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. .SYNOPSIS
  16. Library containing common variables and code used by other PowerShell modules
  17. and scripts for configuring Windows nodes.
  18. #>
  19. # IMPORTANT PLEASE NOTE:
  20. # Any time the file structure in the `windows` directory changes, `windows/BUILD`
  21. # and `k8s.io/release/lib/releaselib.sh` must be manually updated with the changes.
  22. # We HIGHLY recommend not changing the file structure, because consumers of
  23. # Kubernetes releases depend on the release structure remaining stable.
  24. # Disable progress bar to increase download speed.
  25. $ProgressPreference = 'SilentlyContinue'
  26. # REDO_STEPS affects the behavior of a node that is rebooted after initial
  27. # bringup. When true, on a reboot the scripts will redo steps that were
  28. # determined to have already been completed once (e.g. to overwrite
  29. # already-existing config files). When false the scripts will perform the
  30. # minimum required steps to re-join this node to the cluster.
  31. $REDO_STEPS = $false
  32. Export-ModuleMember -Variable REDO_STEPS
  33. # Writes $Message to the console. Terminates the script if $Fatal is set.
  34. function Log-Output {
  35. param (
  36. [parameter(Mandatory=$true)] [string]$Message,
  37. [switch]$Fatal
  38. )
  39. Write-Host "${Message}"
  40. if (${Fatal}) {
  41. Exit 1
  42. }
  43. }
  44. # Checks if a file should be written or overwritten by testing if it already
  45. # exists and checking the value of the global $REDO_STEPS variable. Emits an
  46. # informative message if the file already exists.
  47. #
  48. # Returns $true if the file does not exist, or if it does but the global
  49. # $REDO_STEPS variable is set to $true. Returns $false if the file exists and
  50. # the caller should not overwrite it.
  51. function ShouldWrite-File {
  52. param (
  53. [parameter(Mandatory=$true)] [string]$Filename
  54. )
  55. if (Test-Path $Filename) {
  56. if ($REDO_STEPS) {
  57. Log-Output "Warning: $Filename already exists, will overwrite it"
  58. return $true
  59. }
  60. Log-Output "Skip: $Filename already exists, not overwriting it"
  61. return $false
  62. }
  63. return $true
  64. }
  65. # Returns the GCE instance metadata value for $Key. If the key is not present
  66. # in the instance metadata returns $Default if set, otherwise returns $null.
  67. function Get-InstanceMetadata {
  68. param (
  69. [parameter(Mandatory=$true)] [string]$Key,
  70. [parameter(Mandatory=$false)] [string]$Default
  71. )
  72. $url = "http://metadata.google.internal/computeMetadata/v1/instance/$Key"
  73. try {
  74. $client = New-Object Net.WebClient
  75. $client.Headers.Add('Metadata-Flavor', 'Google')
  76. return ($client.DownloadString($url)).Trim()
  77. }
  78. catch [System.Net.WebException] {
  79. if ($Default) {
  80. return $Default
  81. }
  82. else {
  83. Log-Output "Failed to retrieve value for $Key."
  84. return $null
  85. }
  86. }
  87. }
  88. # Returns the GCE instance metadata value for $Key where key is an "attribute"
  89. # of the instance. If the key is not present in the instance metadata returns
  90. # $Default if set, otherwise returns $null.
  91. function Get-InstanceMetadataAttribute {
  92. param (
  93. [parameter(Mandatory=$true)] [string]$Key,
  94. [parameter(Mandatory=$false)] [string]$Default
  95. )
  96. return Get-InstanceMetadata "attributes/$Key" $Default
  97. }
  98. function Validate-SHA1 {
  99. param(
  100. [parameter(Mandatory=$true)] [string]$Hash,
  101. [parameter(Mandatory=$true)] [string]$Path
  102. )
  103. $actual = Get-FileHash -Path $Path -Algorithm SHA1
  104. # Note: Powershell string comparisons are case-insensitive by default, and this
  105. # is important here because Linux shell scripts produce lowercase hashes but
  106. # Powershell Get-FileHash produces uppercase hashes. This must be case-insensitive
  107. # to work.
  108. if ($actual.Hash -ne $Hash) {
  109. Log-Output "$Path corrupted, sha1 $actual doesn't match expected $Hash"
  110. Throw ("$Path corrupted, sha1 $actual doesn't match expected $Hash")
  111. }
  112. }
  113. # Attempts to download the file from URLs, trying each URL until it succeeds.
  114. # It will loop through the URLs list forever until it has a success. If
  115. # successful, it will write the file to OutFile. You can optionally provide a
  116. # SHA1 Hash argument, in which case it will attempt to validate the downloaded
  117. # file against the hash.
  118. function MustDownload-File {
  119. param (
  120. [parameter(Mandatory=$false)] [string]$Hash,
  121. [parameter(Mandatory=$true)] [string]$OutFile,
  122. [parameter(Mandatory=$true)] [System.Collections.Generic.List[String]]$URLs,
  123. [parameter(Mandatory=$false)] [System.Collections.IDictionary]$Headers = @{}
  124. )
  125. While($true) {
  126. ForEach($url in $URLs) {
  127. # If the URL is for GCS and the node has dev storage scope, add the
  128. # service account token to the request headers.
  129. if (($url -match "^https://storage`.googleapis`.com.*") -and $(Check-StorageScope)) {
  130. $Headers["Authorization"] = "Bearer $(Get-Credentials)"
  131. }
  132. # Attempt to download the file
  133. Try {
  134. # TODO(mtaufen): When we finally get a Windows version that has Powershell 6
  135. # installed we can set `-MaximumRetryCount 6 -RetryIntervalSec 10` to make this even more robust.
  136. $result = Invoke-WebRequest $url -Headers $Headers -OutFile $OutFile -TimeoutSec 300
  137. } Catch {
  138. $message = $_.Exception.ToString()
  139. Log-Output "Failed to download file from $url. Will retry. Error: $message"
  140. continue
  141. }
  142. # Attempt to validate the hash
  143. if ($Hash) {
  144. Try {
  145. Validate-SHA1 -Hash $Hash -Path $OutFile
  146. } Catch {
  147. $message = $_.Exception.ToString()
  148. Log-Output "Hash validation of $url failed. Will retry. Error: $message"
  149. continue
  150. }
  151. Log-Output "Downloaded $url (SHA1 = $Hash)"
  152. return
  153. }
  154. Log-Output "Downloaded $url"
  155. return
  156. }
  157. }
  158. }
  159. # Returns the default service account token for the VM, retrieved from
  160. # the instance metadata.
  161. function Get-Credentials {
  162. While($true) {
  163. $data = Get-InstanceMetadata -Key "service-accounts/default/token"
  164. if ($data) {
  165. return ($data | ConvertFrom-Json).access_token
  166. }
  167. Start-Sleep -Seconds 1
  168. }
  169. }
  170. # Returns True if the VM has the dev storage scope, False otherwise.
  171. function Check-StorageScope {
  172. While($true) {
  173. $data = Get-InstanceMetadata -Key "service-accounts/default/scopes"
  174. if ($data) {
  175. return ($data -match "auth/devstorage")
  176. }
  177. Start-Sleep -Seconds 1
  178. }
  179. }
  180. # This compiles some C# code that can make syscalls, and pulls the
  181. # result into our powershell environment so we can make syscalls from this script.
  182. # We make syscalls directly, because whatever the powershell cmdlets do under the hood,
  183. # they can't seem to open the log files concurrently with writers.
  184. # See https://docs.microsoft.com/en-us/dotnet/framework/interop/marshaling-data-with-platform-invoke
  185. # for details on which unmanaged types map to managed types.
  186. $SyscallDefinitions = @'
  187. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  188. public static extern IntPtr CreateFileW(
  189. String lpFileName,
  190. UInt32 dwDesiredAccess,
  191. UInt32 dwShareMode,
  192. IntPtr lpSecurityAttributes,
  193. UInt32 dwCreationDisposition,
  194. UInt32 dwFlagsAndAttributes,
  195. IntPtr hTemplateFile
  196. );
  197. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  198. public static extern bool SetFilePointer(
  199. IntPtr hFile,
  200. Int32 lDistanceToMove,
  201. IntPtr lpDistanceToMoveHigh,
  202. UInt32 dwMoveMethod
  203. );
  204. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  205. public static extern bool SetEndOfFile(
  206. IntPtr hFile
  207. );
  208. [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  209. public static extern bool CloseHandle(
  210. IntPtr hObject
  211. );
  212. '@
  213. $Kernel32 = Add-Type -MemberDefinition $SyscallDefinitions -Name 'Kernel32' -Namespace 'Win32' -PassThru
  214. # Close-Handle closes the specified open file handle.
  215. # On failure, throws an exception.
  216. function Close-Handle {
  217. param (
  218. [parameter(Mandatory=$true)] [System.IntPtr]$Handle
  219. )
  220. $ret = $Kernel32::CloseHandle($Handle)
  221. $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
  222. if (-not $ret) {
  223. throw "Failed to close open file handle ${Handle}, system error code: ${err}"
  224. }
  225. }
  226. # Open-File tries to open the file at the specified path with ReadWrite access mode and ReadWrite file share mode.
  227. # On success, returns an open file handle.
  228. # On failure, throws an exception.
  229. function Open-File {
  230. param (
  231. [parameter(Mandatory=$true)] [string]$Path
  232. )
  233. $lpFileName = $Path
  234. $dwDesiredAccess = [System.IO.FileAccess]::ReadWrite
  235. $dwShareMode = [System.IO.FileShare]::ReadWrite # Fortunately golang also passes these same flags when it creates the log files, so we can open it concurrently.
  236. $lpSecurityAttributes = [System.IntPtr]::Zero
  237. $dwCreationDisposition = [System.IO.FileMode]::Open
  238. $dwFlagsAndAttributes = [System.IO.FileAttributes]::Normal
  239. $hTemplateFile = [System.IntPtr]::Zero
  240. $handle = $Kernel32::CreateFileW($lpFileName, $dwDesiredAccess, $dwShareMode, $lpSecurityAttributes, $dwCreationDisposition, $dwFlagsAndAttributes, $hTemplateFile)
  241. $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
  242. if ($handle -eq -1) {
  243. throw "Failed to open file ${Path}, system error code: ${err}"
  244. }
  245. return $handle
  246. }
  247. # Truncate-File truncates the file in-place by opening it, moving the file pointer to the beginning,
  248. # and setting the end of file to the file pointer's location.
  249. # On failure, throws an exception.
  250. # The file must have been originally created with FILE_SHARE_WRITE for this to be possible.
  251. # Fortunately Go creates files with FILE_SHARE_READ|FILE_SHARE_WRITE by for all os.Open calls,
  252. # so our log writers should be doing the right thing.
  253. function Truncate-File {
  254. param (
  255. [parameter(Mandatory=$true)] [string]$Path
  256. )
  257. $INVALID_SET_FILE_POINTER = 0xffffffff
  258. $NO_ERROR = 0
  259. $FILE_BEGIN = 0
  260. $handle = Open-File -Path $Path
  261. # https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer
  262. # Docs: Because INVALID_SET_FILE_POINTER is a valid value for the low-order DWORD of the new file pointer,
  263. # you must check both the return value of the function and the error code returned by GetLastError to
  264. # determine whether or not an error has occurred. If an error has occurred, the return value of SetFilePointer
  265. # is INVALID_SET_FILE_POINTER and GetLastError returns a value other than NO_ERROR.
  266. $ret = $Kernel32::SetFilePointer($handle, 0, [System.IntPtr]::Zero, $FILE_BEGIN)
  267. $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
  268. if ($ret -eq $INVALID_SET_FILE_POINTER -and $err -ne $NO_ERROR) {
  269. Close-Handle -Handle $handle
  270. throw "Failed to set file pointer for handle ${handle}, system error code: ${err}"
  271. }
  272. $ret = $Kernel32::SetEndOfFile($handle)
  273. $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
  274. if ($ret -eq 0) {
  275. Close-Handle -Handle $handle
  276. throw "Failed to set end of file for handle ${handle}, system error code: ${err}"
  277. }
  278. Close-Handle -Handle $handle
  279. }
  280. # FileRotationConfig defines the common options for file rotation.
  281. class FileRotationConfig {
  282. # Force rotation, ignoring $MaxBackupInterval and $MaxSize criteria.
  283. [bool]$Force
  284. # Maximum time since last backup, after which file will be rotated.
  285. # When no backups exist, Rotate-File acts as if -MaxBackupInterval has not elapsed,
  286. # instead relying on the other criteria.
  287. [TimeSpan]$MaxBackupInterval
  288. # Maximum file size, after which file will be rotated.
  289. [int]$MaxSize
  290. # Maximum number of backup archives to maintain.
  291. [int]$MaxBackups
  292. }
  293. # New-FileRotationConfig constructs a FileRotationConfig with default options.
  294. function New-FileRotationConfig {
  295. param (
  296. # Force rotation, ignoring $MaxBackupInterval and $MaxSize criteria.
  297. [parameter(Mandatory=$false)] [switch]$Force,
  298. # Maximum time since last backup, after which file will be rotated.
  299. # When no backups exist, Rotate-File acts as if -MaxBackupInterval has not elapsed,
  300. # instead relying on the other criteria.
  301. # Defaults to daily rotations.
  302. [parameter(Mandatory=$false)] [TimeSpan]$MaxBackupInterval = $(New-TimeSpan -Day 1),
  303. # Maximum file size, after which file will be rotated.
  304. [parameter(Mandatory=$false)] [int]$MaxSize = 100mb,
  305. # Maximum number of backup archives to maintain.
  306. [parameter(Mandatory=$false)] [int]$MaxBackups = 5
  307. )
  308. $config = [FileRotationConfig]::new()
  309. $config.Force = $Force
  310. $config.MaxBackupInterval = $MaxBackupInterval
  311. $config.MaxSize = $MaxSize
  312. $config.MaxBackups = $MaxBackups
  313. return $config
  314. }
  315. # Get-Backups returns a list of paths to backup files for the original file path -Path,
  316. # assuming that backup files are in the same directory, with a prefix matching
  317. # the original file name and a .zip suffix.
  318. function Get-Backups {
  319. param (
  320. # Original path of the file for which backups were created (no suffix).
  321. [parameter(Mandatory=$true)] [string]$Path
  322. )
  323. $parent = Split-Path -Parent -Path $Path
  324. $leaf = Split-Path -Leaf -Path $Path
  325. $files = Get-ChildItem -File -Path $parent |
  326. Where-Object Name -like "${leaf}*.zip"
  327. return $files
  328. }
  329. # Trim-Backups deletes old backups for the log file identified by -Path until only -Count remain.
  330. # Deletes backups with the oldest CreationTime first.
  331. function Trim-Backups {
  332. param (
  333. [parameter(Mandatory=$true)] [int]$Count,
  334. [parameter(Mandatory=$true)] [string]$Path
  335. )
  336. if ($Count -lt 0) {
  337. $Count = 0
  338. }
  339. # If creating a new backup will exceed $Count, delete the oldest files
  340. # until we have one less than $Count, leaving room for the new one.
  341. # If the pipe results in zero items, $backups is $null, and if it results
  342. # in only one item, PowerShell doesn't wrap in an array, so we check both cases.
  343. # In the latter case, this actually caused it to often trim all backups, because
  344. # .Length is also a property of FileInfo (size of the file)!
  345. $backups = Get-Backups -Path $Path | Sort-Object -Property CreationTime
  346. if ($backups -and $backups.GetType() -eq @().GetType() -and $backups.Length -gt $Count) {
  347. $num = $backups.Length - $Count
  348. $rmFiles = $backups | Select-Object -First $num
  349. ForEach ($file in $rmFiles) {
  350. Remove-Item $file.FullName
  351. }
  352. }
  353. }
  354. # Backup-File creates a copy of the file at -Path.
  355. # The name of the backup is the same as the file,
  356. # with the suffix "-%Y%m%d-%s" to identify the time of the backup.
  357. # Returns the path to the backup file.
  358. function Backup-File {
  359. param (
  360. [parameter(Mandatory=$true)] [string]$Path
  361. )
  362. $date = Get-Date -UFormat "%Y%m%d-%s"
  363. $dest = "${Path}-${date}"
  364. Copy-Item -Path $Path -Destination $dest
  365. return $dest
  366. }
  367. # Compress-BackupFile creates a compressed archive containing the file
  368. # at -Path and subsequently deletes the file at -Path. We split backup
  369. # and compression steps to minimize time between backup and truncation,
  370. # which helps minimize log loss.
  371. function Compress-BackupFile {
  372. param (
  373. [parameter(Mandatory=$true)] [string]$Path
  374. )
  375. Compress-Archive -Path $Path -DestinationPath "${Path}.zip"
  376. Remove-Item -Path $Path
  377. }
  378. # Rotate-File rotates the log file at -Path by first making a compressed copy of the original
  379. # log file with the suffix "-%Y%m%d-%s" to identify the time of the backup, then truncating
  380. # the original file in-place. Rotation is performed according to the options in -Config.
  381. function Rotate-File {
  382. param (
  383. # Path to the log file to rotate.
  384. [parameter(Mandatory=$true)] [string]$Path,
  385. # Config for file rotation.
  386. [parameter(Mandatory=$true)] [FileRotationConfig]$Config
  387. )
  388. function rotate {
  389. # If creating a new backup will exceed $MaxBackups, delete the oldest files
  390. # until we have one less than $MaxBackups, leaving room for the new one.
  391. Trim-Backups -Count ($Config.MaxBackups - 1) -Path $Path
  392. $backupPath = Backup-File -Path $Path
  393. Truncate-File -Path $Path
  394. Compress-BackupFile -Path $backupPath
  395. }
  396. # Check Force
  397. if ($Config.Force) {
  398. rotate
  399. return
  400. }
  401. # Check MaxSize.
  402. $file = Get-Item $Path
  403. if ($file.Length -gt $Config.MaxSize) {
  404. rotate
  405. return
  406. }
  407. # Check MaxBackupInterval.
  408. $backups = Get-Backups -Path $Path | Sort-Object -Property CreationTime
  409. if ($backups.Length -ge 1) {
  410. $lastBackupTime = $backups[0].CreationTime
  411. $now = Get-Date
  412. if ($now - $lastBackupTime -gt $Config.MaxBackupInterval) {
  413. rotate
  414. return
  415. }
  416. }
  417. }
  418. # Rotate-Files rotates the log files in directory -Path that match -Pattern.
  419. # Rotation is performed by Rotate-File, according to -Config.
  420. function Rotate-Files {
  421. param (
  422. # Pattern that file names must match to be rotated. Does not include parent path.
  423. [parameter(Mandatory=$true)] [string]$Pattern,
  424. # Path to the log directory containing files to rotate.
  425. [parameter(Mandatory=$true)] [string]$Path,
  426. # Config for file rotation.
  427. [parameter(Mandatory=$true)] [FileRotationConfig]$Config
  428. )
  429. $files = Get-ChildItem -File -Path $Path | Where-Object Name -match $Pattern
  430. ForEach ($file in $files) {
  431. try {
  432. Rotate-File -Path $file.FullName -Config $Config
  433. } catch {
  434. Log-Output "Caught exception rotating $($file.FullName): $($_.Exception)"
  435. }
  436. }
  437. }
  438. # Schedule-LogRotation schedules periodic log rotation with the Windows Task Scheduler.
  439. # Rotation is performed by Rotate-Files, according to -Pattern and -Config.
  440. # The system will check whether log files need to be rotated at -RepetitionInterval.
  441. function Schedule-LogRotation {
  442. param (
  443. # Pattern that file names must match to be rotated. Does not include parent path.
  444. [parameter(Mandatory=$true)] [string]$Pattern,
  445. # Path to the log directory containing files to rotate.
  446. [parameter(Mandatory=$true)] [string]$Path,
  447. # Interval at which to check logs against rotation criteria.
  448. # Minimum 1 minute, maximum 31 days (see https://docs.microsoft.com/en-us/windows/desktop/taskschd/taskschedulerschema-interval-repetitiontype-element).
  449. [parameter(Mandatory=$true)] [TimeSpan]$RepetitionInterval,
  450. # Config for file rotation.
  451. [parameter(Mandatory=$true)] [FileRotationConfig]$Config
  452. )
  453. # Write a powershell script to a file that imports this module ($PSCommandPath)
  454. # and calls Rotate-Files with the configured arguments.
  455. $scriptPath = "C:\rotate-kube-logs.ps1"
  456. New-Item -Force -ItemType file -Path $scriptPath | Out-Null
  457. Set-Content -Path $scriptPath @"
  458. `$ErrorActionPreference = 'Stop'
  459. Import-Module -Force ${PSCommandPath}
  460. `$maxBackupInterval = New-Timespan -Days $($Config.MaxBackupInterval.Days) -Hours $($Config.MaxBackupInterval.Hours) -Minutes $($Config.MaxBackupInterval.Minutes) -Seconds $($Config.MaxBackupInterval.Seconds)
  461. `$config = New-FileRotationConfig -Force:`$$($Config.Force) -MaxBackupInterval `$maxBackupInterval -MaxSize $($Config.MaxSize) -MaxBackups $($Config.MaxBackups)
  462. Rotate-Files -Pattern '${Pattern}' -Path '${Path}' -Config `$config
  463. "@
  464. # The task will execute the rotate-kube-logs.ps1 script created above.
  465. # We explicitly set -WorkingDirectory to $Path for safety's sake, otherwise
  466. # it runs in %windir%\system32 by default, which sounds dangerous.
  467. $action = New-ScheduledTaskAction -Execute "powershell" -Argument "-NoLogo -NonInteractive -File ${scriptPath}" -WorkingDirectory $Path
  468. # Start the task immediately, and trigger the task once every $RepetitionInterval.
  469. $trigger = New-ScheduledTaskTrigger -Once -At $(Get-Date) -RepetitionInterval $RepetitionInterval
  470. # Run the task as the same user who is currently running this script.
  471. $principal = New-ScheduledTaskPrincipal $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
  472. # Just use the default task settings.
  473. $settings = New-ScheduledTaskSettingsSet
  474. # Create the ScheduledTask object from the above parameters.
  475. $task = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger -Settings $settings -Description "Rotate Kubernetes logs"
  476. # Register the new ScheduledTask with the Task Scheduler.
  477. # Always try to unregister and re-register, in case it already exists (e.g. across reboots).
  478. $name = "RotateKubeLogs"
  479. try {
  480. Unregister-ScheduledTask -Confirm:$false -TaskName $name
  481. } catch {} finally {
  482. Register-ScheduledTask -TaskName $name -InputObject $task
  483. }
  484. }
  485. # Returns true if this node is part of a test cluster (see
  486. # cluster/gce/config-test.sh). $KubeEnv is a hash table containing the kube-env
  487. # metadata keys+values.
  488. function Test-IsTestCluster {
  489. param (
  490. [parameter(Mandatory=$true)] [hashtable]$KubeEnv
  491. )
  492. if ($KubeEnv.Contains('TEST_CLUSTER') -and `
  493. ($KubeEnv['TEST_CLUSTER'] -eq 'true')) {
  494. return $true
  495. }
  496. return $false
  497. }
  498. # Export all public functions:
  499. Export-ModuleMember -Function *-*