-
Notifications
You must be signed in to change notification settings - Fork 0
Add UpdateClustersCommand for cluster certificate updates #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,398 @@ | ||||||||||||||||
| using System.CommandLine; | ||||||||||||||||
| using System.IO.Abstractions; | ||||||||||||||||
| using k8s.Models; | ||||||||||||||||
| using KubeOps.KubernetesClient; | ||||||||||||||||
| using Vdk.Services; | ||||||||||||||||
| using IConsole = Vdk.Services.IConsole; | ||||||||||||||||
|
|
||||||||||||||||
| namespace Vdk.Commands; | ||||||||||||||||
|
|
||||||||||||||||
| public class UpdateClustersCommand : Command | ||||||||||||||||
| { | ||||||||||||||||
| private readonly IConsole _console; | ||||||||||||||||
| private readonly IKindClient _kind; | ||||||||||||||||
| private readonly IFileSystem _fileSystem; | ||||||||||||||||
| private readonly Func<string, IKubernetesClient> _clientFunc; | ||||||||||||||||
|
|
||||||||||||||||
| public UpdateClustersCommand( | ||||||||||||||||
| IConsole console, | ||||||||||||||||
| IKindClient kind, | ||||||||||||||||
| IFileSystem fileSystem, | ||||||||||||||||
| Func<string, IKubernetesClient> clientFunc) | ||||||||||||||||
| : base("clusters", "Update cluster configurations (certificates, etc.)") | ||||||||||||||||
| { | ||||||||||||||||
| _console = console; | ||||||||||||||||
| _kind = kind; | ||||||||||||||||
| _fileSystem = fileSystem; | ||||||||||||||||
| _clientFunc = clientFunc; | ||||||||||||||||
|
|
||||||||||||||||
| var verboseOption = new Option<bool>("--verbose") { Description = "Enable verbose output for debugging" }; | ||||||||||||||||
| verboseOption.Aliases.Add("-v"); | ||||||||||||||||
|
|
||||||||||||||||
| Options.Add(verboseOption); | ||||||||||||||||
| SetAction(parseResult => InvokeAsync(parseResult.GetValue(verboseOption))); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| public async Task InvokeAsync(bool verbose = false) | ||||||||||||||||
| { | ||||||||||||||||
| // Load local certificates | ||||||||||||||||
| var fullChainPath = _fileSystem.Path.Combine("Certs", "fullchain.pem"); | ||||||||||||||||
| var privKeyPath = _fileSystem.Path.Combine("Certs", "privkey.pem"); | ||||||||||||||||
|
|
||||||||||||||||
| if (!_fileSystem.File.Exists(fullChainPath) || !_fileSystem.File.Exists(privKeyPath)) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteError("Certificate files not found. Expected: Certs/fullchain.pem and Certs/privkey.pem"); | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| var localCert = await _fileSystem.File.ReadAllBytesAsync(fullChainPath); | ||||||||||||||||
| var localKey = await _fileSystem.File.ReadAllBytesAsync(privKeyPath); | ||||||||||||||||
|
|
||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Local certificate size: {localCert.Length} bytes"); | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Local private key size: {localKey.Length} bytes"); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Get all VDK clusters | ||||||||||||||||
| var clusters = _kind.ListClusters(); | ||||||||||||||||
| var vdkClusters = clusters.Where(c => c.isVdk).ToList(); | ||||||||||||||||
|
|
||||||||||||||||
| if (vdkClusters.Count == 0) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteWarning("No VDK clusters found."); | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| _console.WriteLine($"Found {vdkClusters.Count} VDK cluster(s) to check."); | ||||||||||||||||
|
|
||||||||||||||||
| foreach (var cluster in vdkClusters) | ||||||||||||||||
| { | ||||||||||||||||
| await UpdateClusterCertificates(cluster.name, localCert, localKey, verbose); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| _console.WriteLine("Cluster certificate update complete."); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private async Task UpdateClusterCertificates(string clusterName, byte[] localCert, byte[] localKey, bool verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"Checking cluster: {clusterName}"); | ||||||||||||||||
|
|
||||||||||||||||
| IKubernetesClient client; | ||||||||||||||||
| try | ||||||||||||||||
| { | ||||||||||||||||
| client = _clientFunc(clusterName); | ||||||||||||||||
| } | ||||||||||||||||
| catch (Exception ex) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteError($"Failed to connect to cluster '{clusterName}': {ex.Message}"); | ||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Exception: {ex}"); | ||||||||||||||||
| } | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Get all namespaces | ||||||||||||||||
| IList<V1Namespace> namespaces; | ||||||||||||||||
| try | ||||||||||||||||
| { | ||||||||||||||||
| namespaces = client.List<V1Namespace>(); | ||||||||||||||||
| } | ||||||||||||||||
| catch (Exception ex) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteError($"Failed to list namespaces in cluster '{clusterName}': {ex.Message}"); | ||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Exception: {ex}"); | ||||||||||||||||
| } | ||||||||||||||||
| return; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Found {namespaces.Count} namespace(s) in cluster '{clusterName}'"); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| var updatedSecrets = new List<(string Namespace, string SecretName)>(); | ||||||||||||||||
|
|
||||||||||||||||
| foreach (var ns in namespaces) | ||||||||||||||||
| { | ||||||||||||||||
| var nsName = ns.Metadata?.Name; | ||||||||||||||||
| if (string.IsNullOrEmpty(nsName)) continue; | ||||||||||||||||
|
|
||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Scanning namespace: {nsName}"); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| await UpdateNamespaceCertificates(client, clusterName, nsName, localCert, localKey, verbose, updatedSecrets); | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Restart gateways if any secrets were updated | ||||||||||||||||
| if (updatedSecrets.Count > 0) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($" Updated {updatedSecrets.Count} secret(s). Restarting affected gateways..."); | ||||||||||||||||
| await RestartGateways(client, clusterName, updatedSecrets, verbose); | ||||||||||||||||
| } | ||||||||||||||||
| else | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($" All certificates are up to date."); | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| private async Task UpdateNamespaceCertificates( | ||||||||||||||||
| IKubernetesClient client, | ||||||||||||||||
| string clusterName, | ||||||||||||||||
| string nsName, | ||||||||||||||||
| byte[] localCert, | ||||||||||||||||
|
Comment on lines
+144
to
+148
|
||||||||||||||||
| byte[] localKey, | ||||||||||||||||
| bool verbose, | ||||||||||||||||
| List<(string Namespace, string SecretName)> updatedSecrets) | ||||||||||||||||
| { | ||||||||||||||||
| IList<V1Secret> secrets; | ||||||||||||||||
| try | ||||||||||||||||
| { | ||||||||||||||||
| secrets = client.List<V1Secret>(nsName); | ||||||||||||||||
| } | ||||||||||||||||
| catch (Exception ex) | ||||||||||||||||
| { | ||||||||||||||||
| if (verbose) | ||||||||||||||||
| { | ||||||||||||||||
| _console.WriteLine($"[DEBUG] Failed to list secrets in namespace '{nsName}': {ex.Message}"); | ||||||||||||||||
|
Comment on lines
+160
to
+162
|
||||||||||||||||
| if (verbose) | |
| { | |
| _console.WriteLine($"[DEBUG] Failed to list secrets in namespace '{nsName}': {ex.Message}"); | |
| _console.WriteError($"Failed to list secrets in namespace '{nsName}' in cluster '{clusterName}': {ex.Message}"); | |
| if (verbose) | |
| { | |
| _console.WriteLine($"[DEBUG] Exception while listing secrets in namespace '{nsName}' in cluster '{clusterName}': {ex}"); |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await Task.CompletedTask; is redundant here and in other helper methods in this file. If the method has no real async work, prefer a non-async method returning Task.CompletedTask (or make it synchronous) to avoid unnecessary state machine/await noise.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says "Try to find Gateway resources (gateway.networking.k8s.io)", but the implementation is only scanning deployment names. Please update the comment (or implement actual Gateway API lookups) to avoid misleading future maintainers.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code unconditionally restarts any deployment whose name contains "gateway"/"envoy"/"ingress" within namespaces where a secret was updated, but it does not use secretNames at all. This can cause unnecessary/incorrect restarts (and secretNames will be an unused-parameter warning). Consider filtering restarts to deployments that actually reference the updated secrets (e.g., volumes/Projected volumes referencing those secrets) or remove the parameter and adjust the logic/comment accordingly.
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.