diff --git a/src/KubernetesClient/ModelExtensions.cs b/src/KubernetesClient/ModelExtensions.cs
index c559ebaf1..5b2f33d1f 100644
--- a/src/KubernetesClient/ModelExtensions.cs
+++ b/src/KubernetesClient/ModelExtensions.cs
@@ -1,7 +1,458 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Net;
namespace k8s.Models
{
+ /// Adds convenient extensions for Kubernetes objects.
+ public static class ModelExtensions
+ {
+ /// Adds the given finalizer to a Kubernetes object if it doesn't already exist.
+ /// Returns true if the finalizer was added and false if it already existed.
+ public static bool AddFinalizer(this IMetadata obj, string finalizer)
+ {
+ if (string.IsNullOrEmpty(finalizer))
+ {
+ throw new ArgumentNullException(nameof(finalizer));
+ }
+ if (EnsureMetadata(obj).Finalizers == null)
+ {
+ obj.Metadata.Finalizers = new List();
+ }
+ if (!obj.Metadata.Finalizers.Contains(finalizer))
+ {
+ obj.Metadata.Finalizers.Add(finalizer);
+ return true;
+ }
+ return false;
+ }
+
+ /// Extracts the Kubernetes API group from the .
+ public static string ApiGroup(this IKubernetesObject obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (obj.ApiVersion != null)
+ {
+ int slash = obj.ApiVersion.IndexOf('/');
+ return slash < 0 ? string.Empty : obj.ApiVersion.Substring(0, slash);
+ }
+ return null;
+ }
+
+ /// Extracts the Kubernetes API version (excluding the group) from the .
+ public static string ApiGroupVersion(this IKubernetesObject obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (obj.ApiVersion != null)
+ {
+ int slash = obj.ApiVersion.IndexOf('/');
+ return slash < 0 ? obj.ApiVersion : obj.ApiVersion.Substring(slash+1);
+ }
+ return null;
+ }
+
+ /// Splits the Kubernetes API version into the group and version.
+ public static (string, string) ApiGroupAndVersion(this IKubernetesObject obj)
+ {
+ string group, version;
+ GetApiGroupAndVersion(obj, out group, out version);
+ return (group, version);
+ }
+
+ /// Splits the Kubernetes API version into the group and version.
+ public static void GetApiGroupAndVersion(this IKubernetesObject obj, out string group, out string version)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (obj.ApiVersion == null)
+ {
+ group = version = null;
+ }
+ else
+ {
+ int slash = obj.ApiVersion.IndexOf('/');
+ if (slash < 0) (group, version) = (string.Empty, obj.ApiVersion);
+ else (group, version) = (obj.ApiVersion.Substring(0, slash), obj.ApiVersion.Substring(slash+1));
+ }
+ }
+
+ /// Gets the continuation token version of a Kubernetes list.
+ public static string Continue(this IMetadata list) => list.Metadata?.ContinueProperty;
+
+ /// Ensures that the metadata field is set, and returns it.
+ public static V1ListMeta EnsureMetadata(this IMetadata obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (obj.Metadata == null)
+ {
+ obj.Metadata = new V1ListMeta();
+ }
+ return obj.Metadata;
+ }
+
+ /// Gets the resource version of a Kubernetes list.
+ public static string ResourceVersion(this IMetadata list) => list.Metadata?.ResourceVersion;
+
+ /// Adds an owner reference to the object. No attempt is made to ensure the reference is correct or fits with the
+ /// other references.
+ ///
+ public static void AddOwnerReference(this IMetadata obj, V1OwnerReference ownerRef)
+ {
+ if (ownerRef == null)
+ {
+ throw new ArgumentNullException(nameof(ownerRef));
+ }
+ if (EnsureMetadata(obj).OwnerReferences == null)
+ {
+ obj.Metadata.OwnerReferences = new List();
+ }
+ obj.Metadata.OwnerReferences.Add(ownerRef);
+ }
+
+ /// Gets the annotations of a Kubernetes object.
+ public static IDictionary Annotations(this IMetadata obj) => obj.Metadata?.Annotations;
+
+ /// Gets the creation time of a Kubernetes object, or null if it hasn't been created yet.
+ public static DateTime? CreationTimestamp(this IMetadata obj) => obj.Metadata?.CreationTimestamp;
+
+ /// Gets the deletion time of a Kubernetes object, or null if it hasn't been scheduled for deletion.
+ public static DateTime? DeletionTimestamp(this IMetadata obj) => obj.Metadata?.DeletionTimestamp;
+
+ /// Ensures that the metadata field is set, and returns it.
+ public static V1ObjectMeta EnsureMetadata(this IMetadata obj)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (obj.Metadata == null)
+ {
+ obj.Metadata = new V1ObjectMeta();
+ }
+ return obj.Metadata;
+ }
+
+ /// Gets the of a Kubernetes object.
+ public static IList Finalizers(this IMetadata obj) => obj.Metadata?.Finalizers;
+
+ /// Gets the index of the that matches the given object, or -1 if no such
+ /// reference could be found.
+ ///
+ public static int FindOwnerReference(this IMetadata obj, IKubernetesObject owner) =>
+ FindOwnerReference(obj, r => r.Matches(owner));
+
+ /// Gets the index of the that matches the given predicate, or -1 if no such
+ /// reference could be found.
+ ///
+ public static int FindOwnerReference(this IMetadata obj, Predicate predicate)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (predicate == null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
+
+ var ownerRefs = obj.OwnerReferences();
+ if (ownerRefs != null)
+ {
+ for (int i = 0; i < ownerRefs.Count; i++)
+ {
+ if (predicate(ownerRefs[i])) return i;
+ }
+ }
+ return -1;
+ }
+
+ /// Gets the generation a Kubernetes object.
+ public static long? Generation(this IMetadata obj) => obj.Metadata?.Generation;
+
+ /// Returns the given annotation from a Kubernetes object or null if the annotation was not found.
+ public static string GetAnnotation(this IMetadata obj, string key)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ IDictionary annotations = obj.Annotations();
+ return annotations != null && annotations.TryGetValue(key, out string value) ? value : null;
+ }
+
+ /// Gets the for the controller of this object, or null if it couldn't be found.
+ public static V1OwnerReference GetController(this IMetadata obj) =>
+ obj.OwnerReferences()?.FirstOrDefault(r => r.Controller.GetValueOrDefault());
+
+ /// Returns the given label from a Kubernetes object or null if the label was not found.
+ public static string GetLabel(this IMetadata obj, string key)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ IDictionary labels = obj.Labels();
+ return labels != null && labels.TryGetValue(key, out string value) ? value : null;
+ }
+
+ /// Gets that matches the given object, or null if no matching reference exists.
+ public static V1OwnerReference GetOwnerReference(this IMetadata obj, IKubernetesObject owner) =>
+ GetOwnerReference(obj, r => r.Matches(owner));
+
+ /// Gets the that matches the given predicate, or null if no matching reference exists.
+ public static V1OwnerReference GetOwnerReference(this IMetadata obj, Predicate predicate)
+ {
+ int index = FindOwnerReference(obj, predicate);
+ return index >= 0 ? obj.Metadata.OwnerReferences[index] : null;
+ }
+
+ /// Determines whether the Kubernetes object has the given finalizer.
+ public static bool HasFinalizer(this IMetadata obj, string finalizer)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (string.IsNullOrEmpty(finalizer))
+ {
+ throw new ArgumentNullException(nameof(finalizer));
+ }
+
+ return obj.Finalizers() != null && obj.Metadata.Finalizers.Contains(finalizer);
+ }
+
+ /// Determines whether one object is owned by another.
+ public static bool IsOwnedBy(this IMetadata obj, IKubernetesObject owner) =>
+ FindOwnerReference(obj, owner) >= 0;
+
+ /// Gets the labels of a Kubernetes object.
+ public static IDictionary Labels(this IMetadata obj) => obj.Metadata?.Labels;
+
+ /// Gets the name of a Kubernetes object.
+ public static string Name(this IMetadata obj) => obj.Metadata?.Name;
+
+ /// Gets the namespace of a Kubernetes object.
+ public static string Namespace(this IMetadata obj) => obj.Metadata?.NamespaceProperty;
+
+ /// Gets the owner references of a Kubernetes object.
+ public static IList OwnerReferences(this IMetadata obj) => obj.Metadata?.OwnerReferences;
+
+ /// Removes the given finalizer from a Kubernetes object if it exists.
+ /// Returns true if the finalizer was removed and false if it didn't exist.
+ public static bool RemoveFinalizer(this IMetadata obj, string finalizer)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (string.IsNullOrEmpty(finalizer))
+ {
+ throw new ArgumentNullException(nameof(finalizer));
+ }
+
+ return obj.Finalizers() != null && obj.Metadata.Finalizers.Remove(finalizer);
+ }
+
+ /// Removes the first that matches the given object and returns it, or returns null if no
+ /// matching reference could be found.
+ ///
+ public static V1OwnerReference RemoveOwnerReference(this IMetadata obj, IKubernetesObject owner)
+ {
+ int index = FindOwnerReference(obj, owner);
+ V1OwnerReference ownerRef = index >= 0 ? obj.Metadata.OwnerReferences[index] : null;
+ if (index >= 0)
+ {
+ obj.Metadata.OwnerReferences.RemoveAt(index);
+ }
+ return ownerRef;
+ }
+
+ /// Removes all owner references that match the given predicate, and returns true if
+ /// any were removed.
+ ///
+ public static bool RemoveOwnerReferences(this IMetadata obj, Predicate predicate)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (predicate == null)
+ {
+ throw new ArgumentNullException(nameof(predicate));
+ }
+
+ bool removed = false;
+ IList refs = obj.Metadata?.OwnerReferences;
+ if (refs != null)
+ {
+ for (int i = refs.Count-1; i >= 0; i--)
+ {
+ if (predicate(refs[i]))
+ {
+ refs.RemoveAt(i);
+ removed = true;
+ }
+ }
+ }
+ return removed;
+ }
+
+ /// Removes all owner references that match the given object, and returns true if
+ /// any were removed.
+ ///
+ public static bool RemoveOwnerReferences(this IMetadata obj, IKubernetesObject owner) =>
+ RemoveOwnerReferences(obj, r => r.Matches(owner));
+
+ /// Gets the resource version of a Kubernetes object.
+ public static string ResourceVersion(this IMetadata obj) => obj.Metadata?.ResourceVersion;
+
+ /// Sets or removes an annotation on a Kubernetes object.
+ public static void SetAnnotation(this IMetadata obj, string key, string value)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (value != null)
+ {
+ obj.EnsureMetadata().EnsureAnnotations()[key] = value;
+ }
+ else
+ {
+ obj.Metadata?.Annotations?.Remove(key);
+ }
+ }
+
+ /// Sets or removes a label on a Kubernetes object.
+ public static void SetLabel(this IMetadata obj, string key, string value)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ if (key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if (value != null)
+ {
+ obj.EnsureMetadata().EnsureLabels()[key] = value;
+ }
+ else
+ {
+ obj.Metadata?.Labels?.Remove(key);
+ }
+ }
+
+ /// Gets the unique ID of a Kubernetes object.
+ public static string Uid(this IMetadata obj) => obj.Metadata?.Uid;
+
+ /// Ensures that the field is not null, and returns it.
+ public static IDictionary EnsureAnnotations(this V1ObjectMeta meta)
+ {
+ if (meta == null)
+ {
+ throw new ArgumentNullException(nameof(meta));
+ }
+ if (meta.Annotations == null)
+ {
+ meta.Annotations = new Dictionary();
+ }
+ return meta.Annotations;
+ }
+
+ /// Ensures that the field is not null, and returns it.
+ public static IList EnsureFinalizers(this V1ObjectMeta meta)
+ {
+ if (meta == null)
+ {
+ throw new ArgumentNullException(nameof(meta));
+ }
+ if (meta.Finalizers == null)
+ {
+ meta.Finalizers = new List();
+ }
+ return meta.Finalizers;
+ }
+
+ /// Ensures that the field is not null, and returns it.
+ public static IDictionary EnsureLabels(this V1ObjectMeta meta)
+ {
+ if (meta == null)
+ {
+ throw new ArgumentNullException(nameof(meta));
+ }
+ if (meta.Labels == null)
+ {
+ meta.Labels = new Dictionary();
+ }
+ return meta.Labels;
+ }
+
+ /// Gets the namespace from Kubernetes metadata.
+ public static string Namespace(this V1ObjectMeta meta) => meta.NamespaceProperty;
+
+ /// Sets the namespace from Kubernetes metadata.
+ public static void SetNamespace(this V1ObjectMeta meta, string ns) => meta.NamespaceProperty = ns;
+
+ /// Determines whether an object reference references the given object.
+ public static bool Matches(this V1ObjectReference objref, IKubernetesObject obj)
+ {
+ if (objref == null)
+ {
+ throw new ArgumentNullException(nameof(objref));
+ }
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ return objref.ApiVersion == obj.ApiVersion && objref.Kind == obj.Kind && objref.Name == obj.Name() && objref.Uid == obj.Uid() &&
+ objref.NamespaceProperty == obj.Namespace();
+ }
+
+ /// Determines whether an owner reference references the given object.
+ public static bool Matches(this V1OwnerReference owner, IKubernetesObject obj)
+ {
+ if (owner == null)
+ {
+ throw new ArgumentNullException(nameof(owner));
+ }
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+ return owner.ApiVersion == obj.ApiVersion && owner.Kind == obj.Kind && owner.Name == obj.Name() && owner.Uid == obj.Uid();
+ }
+ }
+
public partial class V1Status
{
/// Converts a object into a short description of the status.
diff --git a/tests/KubernetesClient.Tests/ModelExtensionTests.cs b/tests/KubernetesClient.Tests/ModelExtensionTests.cs
index e3bcf9910..9ea153528 100644
--- a/tests/KubernetesClient.Tests/ModelExtensionTests.cs
+++ b/tests/KubernetesClient.Tests/ModelExtensionTests.cs
@@ -1,3 +1,4 @@
+using System;
using k8s.Models;
using Xunit;
@@ -5,6 +6,154 @@ namespace k8s.Tests
{
public class ModelExtensionTests
{
+ [Fact]
+ public void TestMetadata()
+ {
+ // test getters on null metadata
+ var pod = new V1Pod();
+ Assert.Null(pod.Annotations());
+ Assert.Null(pod.ApiGroup());
+ var (g, v) = pod.ApiGroupAndVersion();
+ Assert.Null(g);
+ Assert.Null(v);
+ Assert.Null(pod.ApiGroupVersion());
+ Assert.Null(pod.CreationTimestamp());
+ Assert.Null(pod.DeletionTimestamp());
+ Assert.Null(pod.Finalizers());
+ Assert.Equal(-1, pod.FindOwnerReference(r => true));
+ Assert.Null(pod.Generation());
+ Assert.Null(pod.GetAnnotation("x"));
+ Assert.Null(pod.GetController());
+ Assert.Null(pod.GetLabel("x"));
+ Assert.Null(pod.GetOwnerReference(r => true));
+ Assert.False(pod.HasFinalizer("x"));
+ Assert.Null(pod.Labels());
+ Assert.Null(pod.Name());
+ Assert.Null(pod.Namespace());
+ Assert.Null(pod.OwnerReferences());
+ Assert.Null(pod.ResourceVersion());
+ Assert.Null(pod.Uid());
+ Assert.Null(pod.Metadata);
+
+ // test API version stuff
+ pod = new V1Pod() { ApiVersion = "v1" };
+ Assert.Equal("", pod.ApiGroup());
+ (g, v) = pod.ApiGroupAndVersion();
+ Assert.Equal("", g);
+ Assert.Equal("v1", v);
+ Assert.Equal("v1", pod.ApiGroupVersion());
+ pod.ApiVersion = "abc/v2";
+ Assert.Equal("abc", pod.ApiGroup());
+ (g, v) = pod.ApiGroupAndVersion();
+ Assert.Equal("abc", g);
+ Assert.Equal("v2", v);
+ Assert.Equal("v2", pod.ApiGroupVersion());
+
+ // test the Ensure*() functions
+ Assert.NotNull(pod.EnsureMetadata());
+ Assert.NotNull(pod.Metadata);
+ Assert.NotNull(pod.Metadata.EnsureAnnotations());
+ Assert.NotNull(pod.Metadata.Annotations);
+ Assert.NotNull(pod.Metadata.EnsureFinalizers());
+ Assert.NotNull(pod.Metadata.Finalizers);
+ Assert.NotNull(pod.Metadata.EnsureLabels());
+ Assert.NotNull(pod.Metadata.Labels);
+
+ // test getters with non-null values
+ DateTime ts = DateTime.UtcNow, ts2 = DateTime.Now;
+ pod.Metadata = new V1ObjectMeta()
+ {
+ CreationTimestamp = ts, DeletionTimestamp = ts2, Generation = 1, Name = "name", NamespaceProperty = "ns", ResourceVersion = "42", Uid = "id"
+ };
+ Assert.Equal(ts, pod.CreationTimestamp().Value);
+ Assert.Equal(ts2, pod.DeletionTimestamp().Value);
+ Assert.Equal(1, pod.Generation().Value);
+ Assert.Equal("name", pod.Name());
+ Assert.Equal("ns", pod.Namespace());
+ Assert.Equal("42", pod.ResourceVersion());
+ Assert.Equal("id", pod.Uid());
+
+ // test annotations and labels
+ pod.SetAnnotation("x", "y");
+ pod.SetLabel("a", "b");
+ Assert.Equal(1, pod.Annotations().Count);
+ Assert.Equal(1, pod.Labels().Count);
+ Assert.Equal("y", pod.GetAnnotation("x"));
+ Assert.Equal("y", pod.Metadata.Annotations["x"]);
+ Assert.Null(pod.GetAnnotation("a"));
+ Assert.Equal("b", pod.GetLabel("a"));
+ Assert.Equal("b", pod.Metadata.Labels["a"]);
+ Assert.Null(pod.GetLabel("x"));
+ pod.SetAnnotation("x", null);
+ Assert.Equal(0, pod.Annotations().Count);
+ pod.SetLabel("a", null);
+ Assert.Equal(0, pod.Labels().Count);
+
+ // test finalizers
+ Assert.False(pod.HasFinalizer("abc"));
+ Assert.True(pod.AddFinalizer("abc"));
+ Assert.True(pod.HasFinalizer("abc"));
+ Assert.False(pod.AddFinalizer("abc"));
+ Assert.False(pod.HasFinalizer("xyz"));
+ Assert.False(pod.RemoveFinalizer("xyz"));
+ Assert.True(pod.RemoveFinalizer("abc"));
+ Assert.False(pod.HasFinalizer("abc"));
+ Assert.False(pod.RemoveFinalizer("abc"));
+ }
+
+ [Fact]
+ public void TestReferences()
+ {
+ // test object references
+ var pod = new V1Pod() { ApiVersion = "v1", Kind = "Pod" };
+ pod.Metadata = new V1ObjectMeta() { Name = "name", NamespaceProperty = "ns", ResourceVersion = "ver", Uid = "id" };
+
+ var objr = new V1ObjectReference() { ApiVersion = pod.ApiVersion, Kind = pod.Kind, Name = pod.Name(), NamespaceProperty = pod.Namespace(), Uid = pod.Uid() };
+ Assert.True(objr.Matches(pod));
+
+ (pod.ApiVersion, pod.Kind) = (null, null);
+ Assert.False(objr.Matches(pod));
+ (pod.ApiVersion, pod.Kind) = ("v1", "Pod");
+ Assert.True(objr.Matches(pod));
+ pod.Metadata.Name = "nome";
+ Assert.False(objr.Matches(pod));
+
+ // test owner references
+ (pod.ApiVersion, pod.Kind) = ("abc/xyz", "sometimes");
+ var ownr = new V1OwnerReference() { ApiVersion = "abc/xyz", Kind = "sometimes", Name = pod.Name(), Uid = pod.Uid() };
+ Assert.True(ownr.Matches(pod));
+
+ (pod.ApiVersion, pod.Kind) = (null, null);
+ Assert.False(ownr.Matches(pod));
+ (ownr.ApiVersion, ownr.Kind) = ("v1", "Pod");
+ Assert.False(ownr.Matches(pod));
+ (pod.ApiVersion, pod.Kind) = (ownr.ApiVersion, ownr.Kind);
+ Assert.True(ownr.Matches(pod));
+ ownr.Name = "nim";
+ Assert.False(ownr.Matches(pod));
+ ownr.Name = pod.Name();
+
+ var svc = new V1Service();
+ svc.AddOwnerReference(ownr);
+ Assert.Equal(0, svc.FindOwnerReference(pod));
+ Assert.Equal(-1, svc.FindOwnerReference(svc));
+ Assert.Same(ownr, svc.GetOwnerReference(pod));
+ Assert.Null(svc.GetOwnerReference(svc));
+ Assert.Null(svc.GetController());
+ svc.OwnerReferences()[0].Controller = true;
+ Assert.Same(ownr, svc.GetController());
+ Assert.Same(ownr, svc.RemoveOwnerReference(pod));
+ Assert.Equal(0, svc.OwnerReferences().Count);
+ svc.AddOwnerReference(new V1OwnerReference() { ApiVersion = pod.ApiVersion, Kind = pod.Kind, Name = pod.Name(), Uid = pod.Uid(), Controller = true });
+ svc.AddOwnerReference(new V1OwnerReference() { ApiVersion = pod.ApiVersion, Kind = pod.Kind, Name = pod.Name(), Uid = pod.Uid(), Controller = false });
+ svc.AddOwnerReference(new V1OwnerReference() { ApiVersion = pod.ApiVersion, Kind = pod.Kind, Name = pod.Name(), Uid = pod.Uid() });
+ Assert.Equal(3, svc.OwnerReferences().Count);
+ Assert.NotNull(svc.RemoveOwnerReference(pod));
+ Assert.Equal(2, svc.OwnerReferences().Count);
+ Assert.True(svc.RemoveOwnerReferences(pod));
+ Assert.Equal(0, svc.OwnerReferences().Count);
+ }
+
[Fact]
public void TestV1Status()
{