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
37Export-ModuleMember -Variable REDO_STEPS
38
39# Writes $Message to the console. Terminates the script if $Fatal is set.
40function 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.
56function 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.
77function 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.
94function 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.
120function 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
129function 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.
155function 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.
217function 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.
283function 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.
294function 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)]
312public 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)]
323public 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)]
331public static extern bool SetEndOfFile(
332 IntPtr hFile
333);
334
335[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
336public 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.
344function 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.
358function 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.
386function 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.
418class 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.
432function 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.
457function 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.
471function 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.
499function 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.
513function 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.
524function 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.
568function 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.
591function 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'
609Import-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)
612Rotate-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.
639function 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.
652function 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:
672Export-ModuleMember -Function *-*
View as plain text