Skip to content

Commit 8975fc4

Browse files
committed
Add more model extensions; add unit tests
1 parent 6976558 commit 8975fc4

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

src/KubernetesClient/ModelExtensions.cs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ namespace k8s.Models
77
{
88
public static class ModelExtensions
99
{
10+
/// <summary>Adds the given finalizer to a Kubernetes object if it doesn't already exist.</summary>
11+
/// <returns>Returns true if the finalizer was added and false if it already existed.</returns>
12+
public static bool AddFinalizer(this IMetadata<V1ObjectMeta> obj, string finalizer)
13+
{
14+
if(string.IsNullOrEmpty(finalizer)) throw new ArgumentNullException(nameof(finalizer));
15+
if(obj.EnsureMetadata().Finalizers == null) obj.Metadata.Finalizers = new List<string>();
16+
if(obj.Metadata.Finalizers.Contains(finalizer)) return false;
17+
obj.Metadata.Finalizers.Add(finalizer);
18+
return true;
19+
}
20+
1021
/// <summary>Extracts the Kubernetes API group from the <see cref="IKubernetesObject.ApiVersion"/>.</summary>
1122
public static string ApiGroup(this IKubernetesObject obj)
1223
{
@@ -76,6 +87,24 @@ public static void AddOwnerReference(this IMetadata<V1ObjectMeta> obj, V1OwnerRe
7687
/// <summary>Gets the annotations of a Kubernetes object.</summary>
7788
public static IDictionary<string, string> Annotations(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.Annotations;
7889

90+
/// <summary>Creates a <see cref="V1OwnerReference"/> that refers to the given object.</summary>
91+
public static V1OwnerReference CreateOwnerReference(this IKubernetesObject<V1ObjectMeta> obj, bool? controller = null, bool? blockDeletion = null)
92+
{
93+
if(obj == null) throw new ArgumentNullException(nameof(obj));
94+
string apiVersion = obj.ApiVersion, kind = obj.Kind; // default to using the API version and kind from the object
95+
if(string.IsNullOrEmpty(apiVersion) || string.IsNullOrEmpty(kind)) // but if either of them is missing...
96+
{
97+
object[] attrs = obj.GetType().GetCustomAttributes(typeof(KubernetesEntityAttribute), true);
98+
if(attrs.Length == 0) throw new ArgumentException("Unable to determine the object's API version and Kind.");
99+
var attr = (KubernetesEntityAttribute)attrs[0];
100+
(apiVersion, kind) = (string.IsNullOrEmpty(attr.Group) ? attr.ApiVersion : attr.Group + "/" + attr.ApiVersion, attr.Kind);
101+
}
102+
return new V1OwnerReference()
103+
{
104+
ApiVersion = apiVersion, Kind = kind, Name = obj.Name(), Uid = obj.Uid(), Controller = controller, BlockOwnerDeletion = blockDeletion
105+
};
106+
}
107+
79108
/// <summary>Gets the creation time of a Kubernetes object, or null if it hasn't been created yet.</summary>
80109
public static DateTime? CreationTimestamp(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.CreationTimestamp;
81110

@@ -89,6 +118,9 @@ public static V1ObjectMeta EnsureMetadata(this IMetadata<V1ObjectMeta> obj)
89118
return obj.Metadata;
90119
}
91120

121+
/// <summary>Gets the <see cref="V1ObjectMeta.Finalizers"/> of a Kubernetes object.</summary>
122+
public static IList<string> Finalizers(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.Finalizers;
123+
92124
/// <summary>Gets the index of the <see cref="V1OwnerReference"/> that matches the given object, or -1 if no such
93125
/// reference could be found.
94126
/// </summary>
@@ -149,6 +181,13 @@ public static V1ObjectReference GetObjectReference(this IKubernetesObject<V1Obje
149181
};
150182
}
151183

184+
/// <summary>Determines whether the Kubernetes object has the given finalizer.</summary>
185+
public static bool HasFinalizer(this IMetadata<V1ObjectMeta> obj, string finalizer)
186+
{
187+
if(string.IsNullOrEmpty(finalizer)) throw new ArgumentNullException(nameof(finalizer));
188+
return obj.Finalizers() != null && obj.Metadata.Finalizers.Contains(finalizer);
189+
}
190+
152191
/// <summary>Gets the labels of a Kubernetes object.</summary>
153192
public static IDictionary<string, string> Labels(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.Labels;
154193

@@ -161,6 +200,53 @@ public static V1ObjectReference GetObjectReference(this IKubernetesObject<V1Obje
161200
/// <summary>Gets the owner references of a Kubernetes object.</summary>
162201
public static IList<V1OwnerReference> OwnerReferences(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.OwnerReferences;
163202

203+
/// <summary>Removes the given finalizer from a Kubernetes object if it exists.</summary>
204+
/// <returns>Returns true if the finalizer was removed and false if it didn't exist.</returns>
205+
public static bool RemoveFinalizer(this IMetadata<V1ObjectMeta> obj, string finalizer)
206+
{
207+
if(string.IsNullOrEmpty(finalizer)) throw new ArgumentNullException(nameof(finalizer));
208+
return obj.Finalizers() != null && obj.Metadata.Finalizers.Remove(finalizer);
209+
}
210+
211+
/// <summary>Removes the first <see cref="V1OwnerReference"/> that matches the given object and returns it, or returns null if no
212+
/// matching reference could be found.
213+
/// </summary>
214+
public static V1OwnerReference RemoveOwnerReference(this IMetadata<V1ObjectMeta> obj, IKubernetesObject<V1ObjectMeta> owner)
215+
{
216+
int index = obj.FindOwnerReference(owner);
217+
V1OwnerReference ownerRef = index >= 0 ? obj.Metadata.OwnerReferences[index] : null;
218+
if(index >= 0) obj.Metadata.OwnerReferences.RemoveAt(index);
219+
return ownerRef;
220+
}
221+
222+
/// <summary>Removes all <see cref="V1OwnerReference">owner references</see> that match the given predicate, and returns true if
223+
/// any were removed.
224+
/// </summary>
225+
public static bool RemoveOwnerReferences(this IMetadata<V1ObjectMeta> obj, Predicate<V1OwnerReference> predicate)
226+
{
227+
if(predicate == null) throw new ArgumentNullException(nameof(predicate));
228+
bool removed = false;
229+
IList<V1OwnerReference> refs = obj.Metadata?.OwnerReferences;
230+
if(refs != null)
231+
{
232+
for(int i = refs.Count-1; i >= 0; i--)
233+
{
234+
if(predicate(refs[i]))
235+
{
236+
refs.RemoveAt(i);
237+
removed = true;
238+
}
239+
}
240+
}
241+
return removed;
242+
}
243+
244+
/// <summary>Removes all <see cref="V1OwnerReference">owner references</see> that match the given object, and returns true if
245+
/// any were removed.
246+
/// </summary>
247+
public static bool RemoveOwnerReferences(this IMetadata<V1ObjectMeta> obj, IKubernetesObject<V1ObjectMeta> owner) =>
248+
RemoveOwnerReferences(obj, r => r.Matches(owner));
249+
164250
/// <summary>Gets the resource version of a Kubernetes object.</summary>
165251
public static string ResourceVersion(this IMetadata<V1ObjectMeta> obj) => obj.Metadata?.ResourceVersion;
166252

@@ -192,6 +278,13 @@ public static IDictionary<string, string> EnsureAnnotations(this V1ObjectMeta me
192278
return meta.Annotations;
193279
}
194280

281+
/// <summary>Ensures that the <see cref="V1ObjectMeta.Finalizers"/> field is not null, and returns it.</summary>
282+
public static IList<string> EnsureFinalizers(this V1ObjectMeta meta)
283+
{
284+
if(meta.Finalizers == null) meta.Finalizers = new List<string>();
285+
return meta.Finalizers;
286+
}
287+
195288
/// <summary>Ensures that the <see cref="V1ObjectMeta.Labels"/> field is not null, and returns it.</summary>
196289
public static IDictionary<string, string> EnsureLabels(this V1ObjectMeta meta)
197290
{
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using System;
2+
using k8s.Models;
3+
using Xunit;
4+
5+
namespace k8s.Tests
6+
{
7+
public class ModelExtensionTests
8+
{
9+
[Fact]
10+
public void TestMetadata()
11+
{
12+
// test getters on null metadata
13+
var pod = new V1Pod();
14+
Assert.Null(pod.Annotations());
15+
Assert.Null(pod.ApiGroup());
16+
var (g, v) = pod.ApiGroupAndVersion();
17+
Assert.Null(g);
18+
Assert.Null(v);
19+
Assert.Null(pod.ApiGroupVersion());
20+
Assert.Null(pod.CreationTimestamp());
21+
Assert.Null(pod.DeletionTimestamp());
22+
Assert.Null(pod.Finalizers());
23+
Assert.Null(pod.Generation());
24+
Assert.Null(pod.GetAnnotation("x"));
25+
Assert.Null(pod.GetController());
26+
Assert.Null(pod.GetLabel("x"));
27+
Assert.False(pod.HasFinalizer("x"));
28+
Assert.Null(pod.Labels());
29+
Assert.Null(pod.Name());
30+
Assert.Null(pod.Namespace());
31+
Assert.Null(pod.OwnerReferences());
32+
Assert.Null(pod.ResourceVersion());
33+
Assert.Null(pod.Uid());
34+
Assert.Null(pod.Metadata);
35+
36+
// test API version stuff
37+
pod = new V1Pod() { ApiVersion = "v1" };
38+
Assert.Equal("", pod.ApiGroup());
39+
(g, v) = pod.ApiGroupAndVersion();
40+
Assert.Equal("", g);
41+
Assert.Equal("v1", v);
42+
Assert.Equal("v1", pod.ApiGroupVersion());
43+
pod.ApiVersion = "abc/v2";
44+
Assert.Equal("abc", pod.ApiGroup());
45+
(g, v) = pod.ApiGroupAndVersion();
46+
Assert.Equal("abc", g);
47+
Assert.Equal("v2", v);
48+
Assert.Equal("v2", pod.ApiGroupVersion());
49+
50+
// test the Ensure*() functions
51+
Assert.NotNull(pod.EnsureMetadata());
52+
Assert.NotNull(pod.Metadata);
53+
Assert.NotNull(pod.Metadata.EnsureAnnotations());
54+
Assert.NotNull(pod.Metadata.Annotations);
55+
Assert.NotNull(pod.Metadata.EnsureFinalizers());
56+
Assert.NotNull(pod.Metadata.Finalizers);
57+
Assert.NotNull(pod.Metadata.EnsureLabels());
58+
Assert.NotNull(pod.Metadata.Labels);
59+
60+
// test getters with non-null values
61+
DateTime ts = DateTime.UtcNow, ts2 = DateTime.Now;
62+
pod.Metadata = new V1ObjectMeta()
63+
{
64+
CreationTimestamp = ts, DeletionTimestamp = ts2, Generation = 1, Name = "name", NamespaceProperty = "ns", ResourceVersion = "42", Uid = "id"
65+
};
66+
Assert.Equal(ts, pod.CreationTimestamp().Value);
67+
Assert.Equal(ts2, pod.DeletionTimestamp().Value);
68+
Assert.Equal(1, pod.Generation().Value);
69+
Assert.Equal("name", pod.Name());
70+
Assert.Equal("ns", pod.Namespace());
71+
Assert.Equal("42", pod.ResourceVersion());
72+
Assert.Equal("id", pod.Uid());
73+
74+
// test annotations and labels
75+
pod.SetAnnotation("x", "y");
76+
pod.SetLabel("a", "b");
77+
Assert.Equal(1, pod.Annotations().Count);
78+
Assert.Equal(1, pod.Labels().Count);
79+
Assert.Equal("y", pod.GetAnnotation("x"));
80+
Assert.Equal("y", pod.Metadata.Annotations["x"]);
81+
Assert.Null(pod.GetAnnotation("a"));
82+
Assert.Equal("b", pod.GetLabel("a"));
83+
Assert.Equal("b", pod.Metadata.Labels["a"]);
84+
Assert.Null(pod.GetLabel("x"));
85+
pod.SetAnnotation("x", null);
86+
Assert.Equal(0, pod.Annotations().Count);
87+
pod.SetLabel("a", null);
88+
Assert.Equal(0, pod.Labels().Count);
89+
90+
// test finalizers
91+
Assert.False(pod.HasFinalizer("abc"));
92+
Assert.True(pod.AddFinalizer("abc"));
93+
Assert.True(pod.HasFinalizer("abc"));
94+
Assert.False(pod.AddFinalizer("abc"));
95+
Assert.False(pod.HasFinalizer("xyz"));
96+
Assert.False(pod.RemoveFinalizer("xyz"));
97+
Assert.True(pod.RemoveFinalizer("abc"));
98+
Assert.False(pod.HasFinalizer("abc"));
99+
Assert.False(pod.RemoveFinalizer("abc"));
100+
}
101+
102+
[Fact]
103+
public void TestReferences()
104+
{
105+
// test object references
106+
var pod = new V1Pod() { ApiVersion = "abc/xyz", Kind = "sometimes" };
107+
pod.Metadata = new V1ObjectMeta() { Name = "name", NamespaceProperty = "ns", ResourceVersion = "ver", Uid = "id" };
108+
var objr = pod.GetObjectReference();
109+
Assert.Equal(pod.ApiVersion, objr.ApiVersion);
110+
Assert.Equal(pod.Kind, objr.Kind);
111+
Assert.Equal(pod.Name(), objr.Name);
112+
Assert.Equal(pod.Namespace(), objr.NamespaceProperty);
113+
Assert.Equal(pod.ResourceVersion(), objr.ResourceVersion);
114+
Assert.Equal(pod.Uid(), objr.Uid);
115+
Assert.True(objr.Matches(pod));
116+
117+
(pod.ApiVersion, pod.Kind) = (null, null);
118+
objr = pod.GetObjectReference();
119+
Assert.Equal("v1", objr.ApiVersion);
120+
Assert.Equal("Pod", objr.Kind);
121+
Assert.False(objr.Matches(pod));
122+
(pod.ApiVersion, pod.Kind) = (objr.ApiVersion, objr.Kind);
123+
Assert.True(objr.Matches(pod));
124+
pod.Metadata.Name = "nome";
125+
Assert.False(objr.Matches(pod));
126+
127+
// test owner references
128+
(pod.ApiVersion, pod.Kind) = ("abc/xyz", "sometimes");
129+
var ownr = pod.CreateOwnerReference(true, false);
130+
Assert.Equal(pod.ApiVersion, ownr.ApiVersion);
131+
Assert.Equal(pod.Kind, ownr.Kind);
132+
Assert.Equal(pod.Name(), ownr.Name);
133+
Assert.Equal(pod.Uid(), ownr.Uid);
134+
Assert.True(ownr.Controller.Value);
135+
Assert.False(ownr.BlockOwnerDeletion.Value);
136+
Assert.True(ownr.Matches(pod));
137+
138+
(pod.ApiVersion, pod.Kind) = (null, null);
139+
Assert.False(ownr.Matches(pod));
140+
ownr = pod.CreateOwnerReference();
141+
Assert.Equal("v1", ownr.ApiVersion);
142+
Assert.Equal("Pod", ownr.Kind);
143+
Assert.Null(ownr.Controller);
144+
Assert.Null(ownr.BlockOwnerDeletion);
145+
Assert.False(ownr.Matches(pod));
146+
(pod.ApiVersion, pod.Kind) = (ownr.ApiVersion, ownr.Kind);
147+
Assert.True(ownr.Matches(pod));
148+
ownr.Name = "nim";
149+
Assert.False(ownr.Matches(pod));
150+
ownr.Name = pod.Name();
151+
152+
var svc = new V1Service();
153+
svc.AddOwnerReference(ownr);
154+
Assert.Equal(0, svc.FindOwnerReference(pod));
155+
Assert.Equal(-1, svc.FindOwnerReference(svc));
156+
Assert.Null(svc.GetController());
157+
svc.OwnerReferences()[0].Controller = true;
158+
Assert.Same(ownr, svc.GetController());
159+
Assert.Same(ownr, svc.RemoveOwnerReference(pod));
160+
Assert.Equal(0, svc.OwnerReferences().Count);
161+
svc.AddOwnerReference(pod.CreateOwnerReference(true));
162+
svc.AddOwnerReference(pod.CreateOwnerReference(false));
163+
svc.AddOwnerReference(pod.CreateOwnerReference());
164+
Assert.Equal(3, svc.OwnerReferences().Count);
165+
Assert.NotNull(svc.RemoveOwnerReference(pod));
166+
Assert.Equal(2, svc.OwnerReferences().Count);
167+
Assert.True(svc.RemoveOwnerReferences(pod));
168+
Assert.Equal(0, svc.OwnerReferences().Count);
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)