src/CompareKubeResourceCmdlet.cs

using System;
using System.Collections;
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.Compare, "KubeResource")]
    [OutputType(new[] { typeof(Operation) })]
    public sealed class CompareKubeResourceCmdlet : KubeCmdlet {
        [Parameter(Mandatory = true, Position = 0)]
        public object Original { get; set; }

        [Parameter(Mandatory = true, Position = 1)]

        public object Modified { get; set; }

        [Parameter(Mandatory = true, Position = 2, ParameterSetName = "ThreeWay")]
        public object Current { get; set; }

        [Parameter(Mandatory = true, ParameterSetName = "ThreeWayFromLastApplied")]
        public SwitchParameter ThreeWayFromLastApplied { get; set; }

        [Parameter(ParameterSetName = "ThreeWayFromLastApplied")]
        public SwitchParameter Annotate { get; set; }

        [Parameter(ParameterSetName = "TwoWay")]
        public SwitchParameter IgnoreDeletions { get; set; }

        [Parameter(ParameterSetName = "TwoWay")]
        public SwitchParameter IgnoreAdditionsAndModifications { get; set; }

        protected override async Task ProcessRecordAsync(CancellationToken cancellationToken) {
            await base.ProcessRecordAsync(cancellationToken);
            // 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);
            string apiGroupVersion = (string)Original.GetPropertyValue("ApiVersion");
            string apiVersion = apiGroupVersion.Split('/').Last();
            string kind = (string)Original.GetPropertyValue("Kind");
            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, null));
                return;
            }
            if (ThreeWayFromLastApplied) {
                comparer.CreateThreeWayPatchFromLastApplied(
                    current: Original,
                    modified: Modified,
                    type: type,
                    patch: patch,
                    annotate: Annotate
                );
            } else if (Current == null) {
                comparer.CreateTwoWayPatch(
                    original: Original,
                    modified: Modified,
                    type: type,
                    patch: patch,
                    ignoreDeletions: IgnoreDeletions,
                    ignoreAdditionsAndModifications: IgnoreAdditionsAndModifications
                );
            } else {
                comparer.CreateThreeWayPatch(
                    original: Original,
                    modified: Modified,
                    current: Current,
                    type: type,
                    patch: patch
                );
            }
            foreach (var operation in patch.Operations) {
                WriteObject(operation);
            }
        }
    }
}