diff --git a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Patch.cs b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Patch.cs
new file mode 100644
index 00000000..d6f8b24d
--- /dev/null
+++ b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Patch.cs
@@ -0,0 +1,41 @@
+using k8s.Models;
+
+namespace k8s.kubectl.beta;
+
+public partial class AsyncKubectl
+{
+ ///
+ /// Patch a cluster-scoped Kubernetes resource.
+ ///
+ /// The type of Kubernetes resource to patch.
+ /// The patch to apply.
+ /// The name of the resource.
+ /// Cancellation token.
+ /// The patched resource.
+ public async Task PatchAsync(V1Patch patch, string name, CancellationToken cancellationToken = default)
+ where T : IKubernetesObject
+ {
+ var metadata = typeof(T).GetKubernetesTypeMetadata();
+ using var genericClient = new GenericClient(client, metadata.Group, metadata.ApiVersion, metadata.PluralName, disposeClient: false);
+
+ return await genericClient.PatchAsync(patch, name, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// Patch a namespaced Kubernetes resource.
+ ///
+ /// The type of Kubernetes resource to patch.
+ /// The patch to apply.
+ /// The namespace of the resource.
+ /// The name of the resource.
+ /// Cancellation token.
+ /// The patched resource.
+ public async Task PatchNamespacedAsync(V1Patch patch, string @namespace, string name, CancellationToken cancellationToken = default)
+ where T : IKubernetesObject
+ {
+ var metadata = typeof(T).GetKubernetesTypeMetadata();
+ using var genericClient = new GenericClient(client, metadata.Group, metadata.ApiVersion, metadata.PluralName, disposeClient: false);
+
+ return await genericClient.PatchNamespacedAsync(patch, @namespace, name, cancellationToken).ConfigureAwait(false);
+ }
+}
diff --git a/src/KubernetesClient.Kubectl/Beta/Kubectl.Patch.cs b/src/KubernetesClient.Kubectl/Beta/Kubectl.Patch.cs
new file mode 100644
index 00000000..de12a9f2
--- /dev/null
+++ b/src/KubernetesClient.Kubectl/Beta/Kubectl.Patch.cs
@@ -0,0 +1,33 @@
+using k8s.Models;
+
+namespace k8s.kubectl.beta;
+
+public partial class Kubectl
+{
+ ///
+ /// Patch a cluster-scoped Kubernetes resource.
+ ///
+ /// The type of Kubernetes resource to patch.
+ /// The patch to apply.
+ /// The name of the resource.
+ /// The patched resource.
+ public T Patch(V1Patch patch, string name)
+ where T : IKubernetesObject
+ {
+ return client.PatchAsync(patch, name).GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Patch a namespaced Kubernetes resource.
+ ///
+ /// The type of Kubernetes resource to patch.
+ /// The patch to apply.
+ /// The namespace of the resource.
+ /// The name of the resource.
+ /// The patched resource.
+ public T PatchNamespaced(V1Patch patch, string @namespace, string name)
+ where T : IKubernetesObject
+ {
+ return client.PatchNamespacedAsync(patch, @namespace, name).GetAwaiter().GetResult();
+ }
+}
diff --git a/tests/Kubectl.Tests/KubectlTests.Patch.cs b/tests/Kubectl.Tests/KubectlTests.Patch.cs
new file mode 100644
index 00000000..729e70ca
--- /dev/null
+++ b/tests/Kubectl.Tests/KubectlTests.Patch.cs
@@ -0,0 +1,310 @@
+using k8s.Autorest;
+using k8s.E2E;
+using k8s.kubectl.beta;
+using k8s.Models;
+using Xunit;
+
+namespace k8s.kubectl.Tests;
+
+public partial class KubectlTests
+{
+ [MinikubeFact]
+ public void PatchConfigMapWithStrategicMergePatch()
+ {
+ using var kubernetes = MinikubeTests.CreateClient();
+ var client = new Kubectl(kubernetes);
+ var namespaceParameter = "default";
+ var configMapName = "k8scsharp-e2e-patch-strategic";
+
+ // Create a test ConfigMap
+ var configMap = new V1ConfigMap
+ {
+ Metadata = new V1ObjectMeta
+ {
+ Name = configMapName,
+ NamespaceProperty = namespaceParameter,
+ },
+ Data = new Dictionary
+ {
+ { "key1", "value1" },
+ { "key2", "value2" },
+ },
+ };
+
+ try
+ {
+ kubernetes.CoreV1.CreateNamespacedConfigMap(configMap, namespaceParameter);
+
+ // Patch the ConfigMap using strategic merge patch
+ var patchData = new
+ {
+ data = new Dictionary
+ {
+ { "key3", "value3" },
+ },
+ };
+
+ var patch = new V1Patch(patchData, V1Patch.PatchType.StrategicMergePatch);
+ var patchedConfigMap = client.PatchNamespaced(patch, namespaceParameter, configMapName);
+
+ Assert.NotNull(patchedConfigMap);
+ Assert.Equal(configMapName, patchedConfigMap.Metadata.Name);
+ Assert.Equal(3, patchedConfigMap.Data.Count);
+ Assert.Equal("value1", patchedConfigMap.Data["key1"]);
+ Assert.Equal("value2", patchedConfigMap.Data["key2"]);
+ Assert.Equal("value3", patchedConfigMap.Data["key3"]);
+
+ // Explicitly get the resource to validate it was correctly patched
+ var retrievedConfigMap = client.Get(configMapName, namespaceParameter);
+
+ Assert.NotNull(retrievedConfigMap);
+ Assert.Equal(configMapName, retrievedConfigMap.Metadata.Name);
+ Assert.Equal(3, retrievedConfigMap.Data.Count);
+ Assert.Equal("value1", retrievedConfigMap.Data["key1"]);
+ Assert.Equal("value2", retrievedConfigMap.Data["key2"]);
+ Assert.Equal("value3", retrievedConfigMap.Data["key3"]);
+ }
+ finally
+ {
+ // Cleanup
+ try
+ {
+ kubernetes.CoreV1.DeleteNamespacedConfigMap(configMapName, namespaceParameter);
+ }
+ catch (HttpOperationException)
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ [MinikubeFact]
+ public void PatchConfigMapWithMergePatch()
+ {
+ using var kubernetes = MinikubeTests.CreateClient();
+ var client = new Kubectl(kubernetes);
+ var namespaceParameter = "default";
+ var configMapName = "k8scsharp-e2e-patch-merge";
+
+ // Create a test ConfigMap
+ var configMap = new V1ConfigMap
+ {
+ Metadata = new V1ObjectMeta
+ {
+ Name = configMapName,
+ NamespaceProperty = namespaceParameter,
+ Labels = new Dictionary
+ {
+ { "app", "test" },
+ },
+ },
+ Data = new Dictionary
+ {
+ { "key1", "value1" },
+ },
+ };
+
+ try
+ {
+ kubernetes.CoreV1.CreateNamespacedConfigMap(configMap, namespaceParameter);
+
+ // Patch the ConfigMap using merge patch
+ var patchData = new
+ {
+ metadata = new
+ {
+ labels = new Dictionary
+ {
+ { "app", "test" },
+ { "environment", "testing" },
+ },
+ },
+ data = new Dictionary
+ {
+ { "key1", "updatedValue1" },
+ { "key2", "value2" },
+ },
+ };
+
+ var patch = new V1Patch(patchData, V1Patch.PatchType.MergePatch);
+ var patchedConfigMap = client.PatchNamespaced(patch, namespaceParameter, configMapName);
+
+ Assert.NotNull(patchedConfigMap);
+ Assert.Equal(configMapName, patchedConfigMap.Metadata.Name);
+ Assert.Equal(2, patchedConfigMap.Metadata.Labels.Count);
+ Assert.Equal("test", patchedConfigMap.Metadata.Labels["app"]);
+ Assert.Equal("testing", patchedConfigMap.Metadata.Labels["environment"]);
+ Assert.Equal(2, patchedConfigMap.Data.Count);
+ Assert.Equal("updatedValue1", patchedConfigMap.Data["key1"]);
+ Assert.Equal("value2", patchedConfigMap.Data["key2"]);
+
+ // Explicitly get the resource to validate it was correctly patched
+ var retrievedConfigMap = client.Get(configMapName, namespaceParameter);
+
+ Assert.NotNull(retrievedConfigMap);
+ Assert.Equal(configMapName, retrievedConfigMap.Metadata.Name);
+ Assert.Equal(2, retrievedConfigMap.Metadata.Labels.Count);
+ Assert.Equal("test", retrievedConfigMap.Metadata.Labels["app"]);
+ Assert.Equal("testing", retrievedConfigMap.Metadata.Labels["environment"]);
+ Assert.Equal(2, retrievedConfigMap.Data.Count);
+ Assert.Equal("updatedValue1", retrievedConfigMap.Data["key1"]);
+ Assert.Equal("value2", retrievedConfigMap.Data["key2"]);
+ }
+ finally
+ {
+ // Cleanup
+ try
+ {
+ kubernetes.CoreV1.DeleteNamespacedConfigMap(configMapName, namespaceParameter);
+ }
+ catch (HttpOperationException)
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ [MinikubeFact]
+ public void PatchConfigMapWithJsonPatch()
+ {
+ using var kubernetes = MinikubeTests.CreateClient();
+ var client = new Kubectl(kubernetes);
+ var namespaceParameter = "default";
+ var configMapName = "k8scsharp-e2e-patch-json";
+
+ // Create a test ConfigMap
+ var configMap = new V1ConfigMap
+ {
+ Metadata = new V1ObjectMeta
+ {
+ Name = configMapName,
+ NamespaceProperty = namespaceParameter,
+ },
+ Data = new Dictionary
+ {
+ { "key1", "value1" },
+ },
+ };
+
+ try
+ {
+ kubernetes.CoreV1.CreateNamespacedConfigMap(configMap, namespaceParameter);
+
+ // Patch the ConfigMap using JSON patch
+ var patchData = new[]
+ {
+ new
+ {
+ op = "replace",
+ path = "/data/key1",
+ value = "updatedValue1",
+ },
+ new
+ {
+ op = "add",
+ path = "/data/key2",
+ value = "value2",
+ },
+ };
+
+ var patch = new V1Patch(patchData, V1Patch.PatchType.JsonPatch);
+ var patchedConfigMap = client.PatchNamespaced(patch, namespaceParameter, configMapName);
+
+ Assert.NotNull(patchedConfigMap);
+ Assert.Equal(configMapName, patchedConfigMap.Metadata.Name);
+ Assert.Equal(2, patchedConfigMap.Data.Count);
+ Assert.Equal("updatedValue1", patchedConfigMap.Data["key1"]);
+ Assert.Equal("value2", patchedConfigMap.Data["key2"]);
+
+ // Explicitly get the resource to validate it was correctly patched
+ var retrievedConfigMap = client.Get(configMapName, namespaceParameter);
+
+ Assert.NotNull(retrievedConfigMap);
+ Assert.Equal(configMapName, retrievedConfigMap.Metadata.Name);
+ Assert.Equal(2, retrievedConfigMap.Data.Count);
+ Assert.Equal("updatedValue1", retrievedConfigMap.Data["key1"]);
+ Assert.Equal("value2", retrievedConfigMap.Data["key2"]);
+ }
+ finally
+ {
+ // Cleanup
+ try
+ {
+ kubernetes.CoreV1.DeleteNamespacedConfigMap(configMapName, namespaceParameter);
+ }
+ catch (HttpOperationException)
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ [MinikubeFact]
+ public void PatchNamespace()
+ {
+ using var kubernetes = MinikubeTests.CreateClient();
+ var client = new Kubectl(kubernetes);
+ var namespaceName = "k8scsharp-e2e-patch-ns";
+
+ // Create a test namespace
+ var ns = new V1Namespace
+ {
+ Metadata = new V1ObjectMeta
+ {
+ Name = namespaceName,
+ Labels = new Dictionary
+ {
+ { "app", "test" },
+ },
+ },
+ };
+
+ try
+ {
+ kubernetes.CoreV1.CreateNamespace(ns);
+
+ // Patch the namespace (cluster-scoped resource)
+ var patchData = new
+ {
+ metadata = new
+ {
+ labels = new Dictionary
+ {
+ { "app", "test" },
+ { "patched", "true" },
+ },
+ },
+ };
+
+ var patch = new V1Patch(patchData, V1Patch.PatchType.MergePatch);
+ var patchedNamespace = client.Patch(patch, namespaceName);
+
+ Assert.NotNull(patchedNamespace);
+ Assert.Equal(namespaceName, patchedNamespace.Metadata.Name);
+ Assert.Equal(2, patchedNamespace.Metadata.Labels.Count);
+ Assert.Equal("test", patchedNamespace.Metadata.Labels["app"]);
+ Assert.Equal("true", patchedNamespace.Metadata.Labels["patched"]);
+
+ // Explicitly get the resource to validate it was correctly patched
+ var retrievedNamespace = client.Get(namespaceName);
+
+ Assert.NotNull(retrievedNamespace);
+ Assert.Equal(namespaceName, retrievedNamespace.Metadata.Name);
+ Assert.Equal(2, retrievedNamespace.Metadata.Labels.Count);
+ Assert.Equal("test", retrievedNamespace.Metadata.Labels["app"]);
+ Assert.Equal("true", retrievedNamespace.Metadata.Labels["patched"]);
+ }
+ finally
+ {
+ // Cleanup
+ try
+ {
+ kubernetes.CoreV1.DeleteNamespace(namespaceName);
+ }
+ catch (HttpOperationException)
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+}