src/UpdateKubeResourceCmdlet.cs
using System; using System.Collections.Generic; using System.Linq; using System.Management.Automation; using System.Reflection; using System.Threading; using System.Threading.Tasks; using KubeClient; using KubeClient.Models; using KubeClient.ResourceClients; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.JsonPatch.Operations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; namespace Kubectl { [Cmdlet(VerbsData.Update, "KubeResource", SupportsShouldProcess = true)] [OutputType(new[] { typeof(KubeResourceV1) })] public sealed class UpdateKubeResourceCmdlet : KubeApiCmdlet { private const string lastAppliedConfigAnnotation = "kubectl.kubernetes.io/last-applied-configuration"; [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] public object Resource; protected override async Task ProcessRecordAsync(CancellationToken cancellationToken) { await base.ProcessRecordAsync(cancellationToken); object modified = Resource; string kind = (string)modified.GetPropertyValue("Kind"); string apiGroupVersion = (string)modified.GetPropertyValue("ApiVersion"); string apiVersion = apiGroupVersion.Split('/').Last(); // Figure out the model class - needed for diffing Type type = modelTypes.GetValueOrDefault((kind, apiVersion)); if (type == null) { WriteError(new ErrorRecord(new Exception($"Unknown (kind: {kind}, apiVersion: {apiVersion}). {modelTypes.Count} Known:\n{String.Join("\n", modelTypes.Keys)}"), null, ErrorCategory.InvalidData, Resource)); return; } object metadata = modified.GetPropertyValue("Metadata"); string name = (string)metadata.GetPropertyValue("Name"); string kubeNamespace = (string)metadata.GetPropertyValue("Namespace"); // Get current resource state from server WriteVerbose($"Getting kind: {kind}, apiVersion: {apiVersion}, name: {name}, namespace: {kubeNamespace}"); object current = await client.Dynamic().Get(name, kind, apiVersion, kubeNamespace, cancellationToken); if (current == null) { WriteError(new ErrorRecord(new Exception($"{kind} ({apiVersion}) \"{name}\" does not exist in namespace \"{kubeNamespace}\""), null, ErrorCategory.InvalidData, Resource)); return; } // Generate three-way patch from current to modified // TODO do not pass the ContractResolver here once KubeClient allows customizing the serialisation var patch = new JsonPatchDocument(new List<Operation>(), new PSObjectAwareContractResolver()); var comparer = new KubeResourceComparer(LoggerFactory); comparer.CreateThreeWayPatchFromLastApplied(current, modified, type, patch, true); WriteVerbose("Patch: " + JsonConvert.SerializeObject(patch, new JsonSerializerSettings { Formatting = Formatting.Indented, Converters = new[] { new PSObjectJsonConverter() } })); // Send patch to server if (ShouldProcess($"Sending patch for {kind} \"{name}\" in namespace \"{kubeNamespace}\"", $"Send patch for {kind} \"{name}\" in namespace \"{kubeNamespace}\"?", "Confirm") && false) { var result = await client.Dynamic().Patch( name: name, kind: kind, apiVersion: apiVersion, patch: patch, kubeNamespace: kubeNamespace, cancellationToken: cancellationToken ); WriteObject(result); } } } } |