-
Notifications
You must be signed in to change notification settings - Fork 3
/
KubeTidy.psm1
381 lines (313 loc) · 15.4 KB
/
KubeTidy.psm1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
#!/usr/bin/env pwsh
# MARKER: NEW PARAM BLOCK
# Dot Source all functions in all ps1 files located in this module
# Define the path to the local Private directory and Krew storage directory for KubeTidy
$localPrivateDir = "$PSScriptRoot/Private" # Local Private directory
$krewStorageDir = "$HOME/.krew/store/kubetidy" # Krew storage directory
$foundScripts = $false # Flag to track if any scripts were found and executed
# Check if the local Private directory exists and source scripts if available
if (Test-Path -Path $localPrivateDir) {
Write-Verbose "Executing scripts from local Private directory."
# Get all .ps1 files in the local Private directory
$localScripts = Get-ChildItem -Path "$localPrivateDir/*.ps1"
# Execute each .ps1 script found in the local Private directory
foreach ($script in $localScripts) {
Write-Verbose "Executing script: $($script.FullName)"
. $script.FullName # Call the script
$foundScripts = $true
}
}
# If no scripts found in local directory, check Krew storage directory
if (-not $foundScripts -and (Test-Path -Path $krewStorageDir)) {
Write-Verbose "Local Private directory empty or missing. Checking Krew storage directory."
# Get all version directories (assuming they follow a vX.X.X naming pattern)
$versionDirs = Get-ChildItem -Path $krewStorageDir -Directory | Where-Object { $_.Name -match '^v\d+\.\d+\.\d+$' }
# Check if any version directories were found
if ($versionDirs) {
# Get the latest version directory based on the version number
$latestVersionDir = $versionDirs | Sort-Object { [Version]$_.Name.Substring(1) } -Descending | Select-Object -First 1
Write-Verbose "Latest version found: $($latestVersionDir.Name)"
# Construct the path to the Private directory for the latest version
$kubePrivateDir = Join-Path -Path $latestVersionDir.FullName -ChildPath "Private"
# Check if the Private directory exists in the latest version
if (Test-Path -Path $kubePrivateDir) {
# Get all .ps1 files in the Private directory
$scripts = Get-ChildItem -Path "$kubePrivateDir/*.ps1"
# Execute each .ps1 script found
foreach ($script in $scripts) {
Write-Verbose "Executing script: $($script.FullName)"
. $script.FullName # Call the script
$foundScripts = $true
}
} else {
Write-Verbose "No Private directory found for the latest version: $($latestVersionDir.Name)."
}
} else {
Write-Verbose "No version directories found in $krewStorageDir."
}
}
# If no scripts were found and sourced, throw an error
if (-not $foundScripts) {
Write-Error "Error: Unable to source any .ps1 files from either the local Private directory or Krew storage directory. Exiting."
exit 1
}
# Define the function without parameters (parameters are passed via script-level param())
function Invoke-KubeTidy {
# START PARAM BLOCK
[CmdletBinding()]
param (
[string]$KubeConfigPath,
[array]$ExclusionList,
[bool]$Backup = $true,
[switch]$Force,
[switch]$ListClusters,
[switch]$ListContexts,
[string]$ExportContexts = "",
[array]$MergeConfigs,
[string]$DestinationConfig,
[switch]$DryRun,
[Alias("h")] [switch]$Help
)
# END PARAM BLOCK
# Only show help if the Help switch is passed
# OR if none of the actual action parameters are provided
if ($Help) {
Write-Host ""
Write-Host "Parameters:"
Write-Host " -KubeConfigPath Path to your kubeconfig file."
Write-Host " -ExclusionList Comma-separated list of clusters to exclude from cleanup."
Write-Host " -Force Force cleanup even if no clusters are reachable."
Write-Host " -ListClusters Display a list of all clusters in the kubeconfig file."
Write-Host " -ListContexts Display a list of all contexts in the kubeconfig file."
Write-Host " -ExportContexts Comma-separated list of contexts to export from the kubeconfig."
Write-Host " -MergeConfigs Array of kubeconfig files to merge."
Write-Host " -DestinationConfig Path to save the merged or exported kubeconfig file."
Write-Host " -DryRun Simulate the cleanup process without making changes."
Write-Host " -Help Display this help message."
return
}
<#
.SYNOPSIS
KubeTidy: A script to clean up your Kubernetes config file by removing unreachable clusters and associated users and contexts, or merge multiple config files.
.DESCRIPTION
This script removes unreachable clusters from the kubeconfig file and ensures that associated
users and contexts that reference the removed clusters are also removed. It can also merge multiple kubeconfig files.
.PARAMETER KubeConfigPath
Path to your kubeconfig file. Defaults to the default Kubernetes location if not specified.
.PARAMETER ExclusionList
A comma-separated list of cluster names to exclude from cleanup (useful for clusters on VPNs or temporary networks).
.PARAMETER Backup
Flag to create a backup before cleanup. Defaults to true.
.PARAMETER Force
Forces cleanup even if no clusters are reachable. Defaults to false.
.PARAMETER ListClusters
Displays a list of all clusters in the kubeconfig file without performing cleanup.
.PARAMETER MergeConfigs
An array of kubeconfig files to merge into the destination kubeconfig file.
.PARAMETER DestinationConfig
The path to save the merged kubeconfig file. Defaults to the default location if not specified.
.PARAMETER DryRun
If specified, the function will simulate the cleanup process without making any changes.
.PARAMETER Verbose
Enables verbose logging for detailed output.
#>
Show-KubeTidyBanner
# If ExclusionList is not provided, set it to an empty array
if (-not $ExclusionList) {
$ExclusionList = @()
}
else {
# Split ExclusionList into an array if provided as a string
$ExclusionList = $ExclusionList -split ',' | ForEach-Object { $_.Trim() }
}
# Check if no parameter was passed
if (-not $KubeConfigPath) {
Write-Verbose "No KubeConfigPath provided. Retrieving default path..."
$KubeConfigPath = Get-KubeConfigPath
}
# Check if the powershell-yaml module is installed; if not, install it
if (-not (Get-Module -ListAvailable -Name powershell-yaml)) {
Write-Verbose "powershell-yaml module not found. Installing powershell-yaml..."
Install-Module -Name powershell-yaml -Force -Scope CurrentUser
}
try {
Import-Module powershell-yaml -ErrorAction Stop
Write-Verbose "powershell-yaml module loaded successfully."
}
catch {
Write-Host "Failed to load powershell-yaml module. Please install it manually." -ForegroundColor Red
return
}
if ($MergeConfigs) {
if (-not $DestinationConfig) {
$DestinationConfig = "$env:USERPROFILE\.kube\config"
}
#Show-KubeTidyBanner
Write-Host "Merging kubeconfig files..." -ForegroundColor Yellow
# Call Merge-KubeConfigs with -DryRun only if $DryRun is True
if ($DryRun) {
Merge-KubeConfigs -MergeConfigs $MergeConfigs -DestinationConfig $DestinationConfig -DryRun
}
else {
Merge-KubeConfigs -MergeConfigs $MergeConfigs -DestinationConfig $DestinationConfig
}
return
}
if ($ExportContexts) {
if (-not $DestinationConfig) {
$DestinationConfig = "$HOME/.kube/filtered-config" # Default destination for export
}
#Show-KubeTidyBanner
Write-Host "Exporting specified contexts: `n$ExportContexts`n" -ForegroundColor Yellow
Export-KubeContexts -KubeConfigPath $KubeConfigPath -Contexts $ExportContexts -OutputFile $DestinationConfig
return
}
if ($ListClusters) {
#Show-KubeTidyBanner
Get-AllClusters -KubeConfigPath $KubeConfigPath
return
}
if ($ListContexts) {
#Show-KubeTidyBanner
Get-KubeContexts -KubeConfigPath $KubeConfigPath
return
}
#Show-KubeTidyBanner
Write-Host "Starting KubeTidy cleanup..." -ForegroundColor Yellow
Write-Host ""
Write-Verbose "Reading KubeConfig from $KubeConfigPath"
$kubeConfigContent = Get-Content -Raw -Path $KubeConfigPath
$kubeConfig = $kubeConfigContent | ConvertFrom-Yaml
# Backup original file before cleanup
if ($Backup -and -not $DryRun) {
Write-Verbose "Creating a backup of the KubeConfig file."
# Capture the result of the backup operation using -PassThru
$backupResult = New-Backup -KubeConfigPath $KubeConfigPath -PassThru
# If the backup failed, abort the cleanup process
if (-not $backupResult) {
Write-Host "Backup failed, aborting cleanup." -ForegroundColor Red
return
}
}
elseif ($DryRun) {
Write-Host "Dry run enabled: Skipping backup of the KubeConfig file." -ForegroundColor Yellow
}
$removedClusters = @()
$checkedClusters = 0
$reachableClusters = 0
$totalClusters = $kubeConfig.clusters.Count
$currentContext = $kubeConfig.'current-context' # Store the current context for later check
foreach ($cluster in $kubeConfig.clusters) {
$clusterName = $cluster.name
$clusterServer = $cluster.cluster.server
$checkedClusters++
Write-Progress -Activity "Checking Cluster" `
-Status "Checking $clusterName ($checkedClusters of $totalClusters)" `
-PercentComplete (($checkedClusters / $totalClusters) * 100)
if ($ExclusionList -contains $clusterName) {
Write-Verbose "Skipping cluster $clusterName as it is in the exclusion list."
continue
}
Write-Verbose "Checking reachability for cluster: $clusterName at $clusterServer"
if (-not (Test-ClusterReachability -ClusterServer $clusterServer)) {
Write-Verbose "$clusterName is NOT reachable via HTTPS. Marking for removal."
$removedClusters += $clusterName
}
else {
Write-Verbose "$clusterName is reachable via HTTPS."
$reachableClusters++
}
}
if ($reachableClusters -eq 0 -and -not $Force) {
Write-Host "No clusters are reachable. Perhaps the internet is down? Use `-Force` to proceed with cleanup." -ForegroundColor Yellow
Write-Verbose "No clusters are reachable. Aborting cleanup unless `-Force` is used."
return
}
if ($removedClusters.Count -gt 0) {
if ($DryRun) {
Write-Host "Dry run enabled: The following clusters would be removed: $($removedClusters -join ', ')" -ForegroundColor Yellow
}
else {
$retainedClusters = $kubeConfig.clusters | Where-Object { $removedClusters -notcontains $_.name }
$retainedContexts = $kubeConfig.contexts | Where-Object { $removedClusters -notcontains $_.context.cluster }
$removedUsers = $kubeConfig.contexts | Where-Object { $removedClusters -contains $_.context.cluster } | ForEach-Object { $_.context.user }
$retainedUsers = $kubeConfig.users | Where-Object { $removedUsers -notcontains $_.name }
$kubeConfig.clusters = $retainedClusters
$kubeConfig.contexts = $retainedContexts
$kubeConfig.users = $retainedUsers
# Check if the current-context belongs to a removed cluster
if ($currentContext) {
$currentContextCluster = ($kubeConfig.contexts | Where-Object { $_.name -eq $currentContext }).context.cluster
if ($removedClusters -contains $currentContextCluster) {
Write-Verbose "The current context ($currentContext) belongs to a removed cluster. Unsetting current-context."
$kubeConfig.'current-context' = $null # Unset the current context
}
}
Write-Host "Removed clusters, users, and contexts related to unreachable clusters." -ForegroundColor Green
Write-Verbose "Removed the following clusters: $($removedClusters -join ', ')"
}
}
else {
Write-Host "No clusters were removed." -ForegroundColor Yellow
Write-Verbose "No clusters were marked for removal."
}
# Manually build the YAML for clusters, contexts, and users
$clustersYaml = @"
clusters: `n
"@
foreach ($cluster in $kubeConfig.clusters) {
$clustersYaml += " - cluster:`n"
$clustersYaml += " certificate-authority-data: $($cluster.cluster.'certificate-authority-data')`n"
$clustersYaml += " server: $($cluster.cluster.server)`n"
$clustersYaml += " name: $($cluster.name)`n"
}
$contextsYaml = @"
contexts: `n
"@
foreach ($context in $kubeConfig.contexts) {
$contextsYaml += " - context:`n"
$contextsYaml += " cluster: $($context.context.cluster)`n"
$contextsYaml += " user: $($context.context.user)`n"
$contextsYaml += " name: $($context.name)`n"
}
$usersYaml = @"
users: `n
"@
foreach ($user in $kubeConfig.users) {
$usersYaml += " - name: $($user.name)`n"
$usersYaml += " user:`n"
$usersYaml += " client-certificate-data: $($user.user.'client-certificate-data')`n"
$usersYaml += " client-key-data: $($user.user.'client-key-data')`n"
}
# Add the current context if it still exists after cleanup
$currentContextYaml = ""
if ($kubeConfig.'current-context') {
$currentContextYaml = "current-context: $($kubeConfig.'current-context')`n"
}
$kubeConfigHeader = @"
apiVersion: v1
kind: Config
preferences: {} `n
"@ + $currentContextYaml
if (-not $DryRun) {
$fullKubeConfigYaml = $kubeConfigHeader + $clustersYaml + $contextsYaml + $usersYaml
$fullKubeConfigYaml | Set-Content -Path $KubeConfigPath
Write-Host "Kubeconfig cleaned and saved." -ForegroundColor Green
}
else {
Write-Host "Dry run enabled: Changes to the KubeConfig file were NOT saved." -ForegroundColor Yellow
}
$removedCount = $removedClusters.Count
$checkedClustersText = "{0,5}" -f $checkedClusters
$removedCountText = "{0,5}" -f $removedCount
$retainedCountText = "{0,5}" -f ($checkedClusters - $removedCount)
Write-Host ""
Write-Host "===============================================" -ForegroundColor Magenta
Write-Host " KubeTidy Summary " -ForegroundColor Magenta
Write-Host "===============================================" -ForegroundColor Magenta
Write-Host "Clusters Checked: $checkedClustersText" -ForegroundColor Yellow
Write-Host "Clusters Removed: $removedCountText" -ForegroundColor Red
Write-Host "Clusters Kept: $retainedCountText" -ForegroundColor Green
Write-Host "===============================================" -ForegroundColor Magenta
}
# MARKER: FUNCTION CALL