Skip to content

Commit 0e3cb94

Browse files
authored
Custom Resource example (#540)
* CR example * minor changes
1 parent 9a90881 commit 0e3cb94

File tree

9 files changed

+333
-0
lines changed

9 files changed

+333
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Collections.Generic;
2+
using k8s;
3+
using k8s.Models;
4+
using Newtonsoft.Json;
5+
6+
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "This is just an example.")]
7+
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is just an example.")]
8+
9+
namespace customResource
10+
{
11+
public class CustomResourceDefinition
12+
{
13+
public string Version { get; set; }
14+
15+
public string Group { get; set; }
16+
17+
public string PluralName { get; set; }
18+
19+
public string Kind { get; set; }
20+
21+
public string Namespace { get; set; }
22+
}
23+
24+
public abstract class CustomResource : KubernetesObject
25+
{
26+
[JsonProperty(PropertyName = "metadata")]
27+
public V1ObjectMeta Metadata { get; set; }
28+
}
29+
30+
public abstract class CustomResource<TSpec, TStatus> : CustomResource
31+
{
32+
[JsonProperty(PropertyName = "spec")]
33+
public TSpec Spec { get; set; }
34+
35+
[JsonProperty(PropertyName = "CStatus")]
36+
public TStatus CStatus { get; set; }
37+
}
38+
39+
public class CustomResourceList<T> : KubernetesObject
40+
where T : CustomResource
41+
{
42+
public V1ListMeta Metadata { get; set; }
43+
public List<T> Items { get; set; }
44+
}
45+
}

examples/customResource/Program.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using k8s;
4+
using k8s.Models;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.JsonPatch;
7+
8+
9+
namespace customResource
10+
{
11+
public class Program
12+
{
13+
private static async Task Main(string[] args)
14+
{
15+
Console.WriteLine("strating main()...");
16+
17+
// creating the k8s client
18+
var k8SClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile();
19+
IKubernetes client = new Kubernetes(k8SClientConfig);
20+
21+
// creating a K8s client for the CRD
22+
var myCRD = Utils.MakeCRD();
23+
Console.WriteLine("working with CRD: {0}.{1}", myCRD.PluralName, myCRD.Group);
24+
var generic = new GenericClient(k8SClientConfig, myCRD.Group, myCRD.Version, myCRD.PluralName);
25+
26+
// creating a sample custom resource content
27+
var myCr = Utils.MakeCResource();
28+
29+
try
30+
{
31+
Console.WriteLine("creating CR {0}", myCr.Metadata.Name);
32+
var response = await client.CreateNamespacedCustomObjectWithHttpMessagesAsync(
33+
myCr,
34+
myCRD.Group, myCRD.Version,
35+
myCr.Metadata.NamespaceProperty ?? "default",
36+
myCRD.PluralName).ConfigureAwait(false);
37+
}
38+
catch (Microsoft.Rest.HttpOperationException httpOperationException) when (httpOperationException.Message.Contains("422"))
39+
{
40+
var phase = httpOperationException.Response.ReasonPhrase;
41+
var content = httpOperationException.Response.Content;
42+
Console.WriteLine("response content: {0}", content);
43+
Console.WriteLine("response phase: {0}", phase);
44+
}
45+
catch (Microsoft.Rest.HttpOperationException)
46+
{
47+
}
48+
49+
// listing the cr instances
50+
Console.WriteLine("CR list:");
51+
var crs = await generic.ListNamespacedAsync<CustomResourceList<CResource>>(myCr.Metadata.NamespaceProperty ?? "default").ConfigureAwait(false);
52+
foreach (var cr in crs.Items)
53+
{
54+
Console.WriteLine("- CR Item {0} = {1}", crs.Items.IndexOf(cr), cr.Metadata.Name);
55+
}
56+
57+
// updating the custom resource
58+
myCr.Metadata.Labels.TryAdd("newKey", "newValue");
59+
var patch = new JsonPatchDocument<CResource>();
60+
patch.Replace(x => x.Metadata.Labels, myCr.Metadata.Labels);
61+
patch.Operations.ForEach(x => x.path = x.path.ToLower());
62+
var crPatch = new V1Patch(patch, V1Patch.PatchType.JsonPatch);
63+
try
64+
{
65+
var patchResponse = await client.PatchNamespacedCustomObjectAsync(
66+
crPatch,
67+
myCRD.Group,
68+
myCRD.Version,
69+
myCr.Metadata.NamespaceProperty ?? "default",
70+
myCRD.PluralName,
71+
myCr.Metadata.Name).ConfigureAwait(false);
72+
}
73+
catch (Microsoft.Rest.HttpOperationException httpOperationException)
74+
{
75+
var phase = httpOperationException.Response.ReasonPhrase;
76+
var content = httpOperationException.Response.Content;
77+
Console.WriteLine("response content: {0}", content);
78+
Console.WriteLine("response phase: {0}", phase);
79+
}
80+
81+
// getting the updated custom resource
82+
var fetchedCR = await generic.ReadNamespacedAsync<CResource>(
83+
myCr.Metadata.NamespaceProperty ?? "default",
84+
myCr.Metadata.Name).ConfigureAwait(false);
85+
86+
Console.WriteLine("fetchedCR = {0}", fetchedCR.ToString());
87+
88+
// deleting the custom resource
89+
try
90+
{
91+
myCr = await generic.DeleteNamespacedAsync<CResource>(
92+
myCr.Metadata.NamespaceProperty ?? "default",
93+
myCr.Metadata.Name).ConfigureAwait(false);
94+
95+
Console.WriteLine("Deleted the CR");
96+
}
97+
catch (Exception exception)
98+
{
99+
Console.WriteLine("Exception type {0}", exception);
100+
}
101+
}
102+
}
103+
}

examples/customResource/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Custom Resource Client Example
2+
3+
This example demonstrates how to use the C# Kubernetes Client library to create, get and list custom resources.
4+
5+
## Pre-requisits
6+
7+
Make sure your have added the library package
8+
9+
```shell
10+
dotnet add package KubernetesClient
11+
```
12+
13+
## Create Custom Resource Definition (CRD)
14+
15+
Make sure the [CRD](./config/crd.yaml) is created, in order to create an instance of it after.
16+
17+
```shell
18+
kubectl create -f ./crd.yaml
19+
```
20+
21+
You can test that the CRD is successfully added, by creating an [instance](./config/yaml-cr-instance.yaml) of it using kubectl:
22+
23+
```shell
24+
kubectl create -f ./config/yaml-cr-instance.yaml
25+
```
26+
27+
```shell
28+
kubectl get customresources.csharp.com
29+
```
30+
31+
## Execute the code
32+
33+
The client uses the `BuildConfigFromConfigFile()` function. If the KUBECONFIG environment variable is set, then that path to the k8s config file will be used.
34+
35+
`dotnet run`
36+
37+
Expected output:
38+
39+
```
40+
strating main()...
41+
working with CRD: customresources.csharp.com
42+
creating CR cr-instance-london
43+
CR list:
44+
- CR Item 0 = cr-instance-london
45+
- CR Item 1 = cr-instance-paris
46+
fetchedCR = cr-instance-london (Labels: {identifier : city, newKey : newValue}), Spec: London
47+
Deleted the CR
48+
```
49+
50+
## Under the hood
51+
52+
For more details, you can look at the Generic client [implementation](https://github.com/kubernetes-client/csharp/blob/master/src/KubernetesClient/GenericClient.cs)
53+

examples/customResource/Utils.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using k8s.Models;
2+
using System.Collections.Generic;
3+
namespace customResource
4+
{
5+
public class Utils
6+
{
7+
// creats a CRD definition
8+
public static CustomResourceDefinition MakeCRD()
9+
{
10+
var myCRD = new CustomResourceDefinition()
11+
{
12+
Kind = "CResource",
13+
Group = "csharp.com",
14+
Version = "v1alpha1",
15+
PluralName = "customresources",
16+
};
17+
18+
return myCRD;
19+
}
20+
21+
// creats a CR instance
22+
public static CResource MakeCResource()
23+
{
24+
var myCResource = new CResource()
25+
{
26+
Kind = "CResource",
27+
ApiVersion = "csharp.com/v1alpha1",
28+
Metadata = new V1ObjectMeta
29+
{
30+
Name = "cr-instance-london",
31+
NamespaceProperty = "default",
32+
Labels = new Dictionary<string, string>
33+
{
34+
{
35+
"identifier", "city"
36+
},
37+
},
38+
},
39+
// spec
40+
Spec = new CResourceSpec
41+
{
42+
CityName = "London",
43+
},
44+
};
45+
return myCResource;
46+
}
47+
}
48+
}

examples/customResource/cResource.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using k8s.Models;
2+
using System.Collections.Generic;
3+
using k8s;
4+
using Newtonsoft.Json;
5+
6+
namespace customResource
7+
{
8+
public class CResource : CustomResource<CResourceSpec, CResourceStatus>
9+
{
10+
public override string ToString()
11+
{
12+
var labels = "{";
13+
foreach (var kvp in Metadata.Labels)
14+
{
15+
labels += kvp.Key + " : " + kvp.Value + ", ";
16+
}
17+
18+
labels = labels.TrimEnd(',', ' ') + "}";
19+
20+
return $"{Metadata.Name} (Labels: {labels}), Spec: {Spec.CityName}";
21+
}
22+
}
23+
24+
public class CResourceSpec
25+
{
26+
[JsonProperty(PropertyName = "cityName")]
27+
public string CityName { get; set; }
28+
}
29+
30+
public class CResourceStatus : V1Status
31+
{
32+
[JsonProperty(PropertyName = "temperature", NullValueHandling = NullValueHandling.Ignore)]
33+
public string Temperature { get; set; }
34+
}
35+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: customresources.csharp.com
5+
spec:
6+
group: csharp.com
7+
version: v1alpha1
8+
names:
9+
kind: CResource
10+
plural: customresources
11+
scope: Namespaced
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: csharp.com/v1alpha1
2+
kind: CResource
3+
metadata:
4+
name: cr-instance-paris
5+
namespace: default
6+
spec:
7+
cityName: Paris
8+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
10+
<PackageReference Include="KubernetesClient" Version="3.0.16" />
11+
<PackageReference Include="Microsoft.Build" Version="16.8.0" />
12+
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.1" />
13+
</ItemGroup>
14+
15+
</Project>

kubernetes-client.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2E.Tests", "tests\E2E.Test
4141
EndProject
4242
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkipTestLogger", "tests\SkipTestLogger\SkipTestLogger.csproj", "{4D2AE427-F856-49E5-B61D-EA6B17D89051}"
4343
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "customResource", "examples\customResource\customResource.csproj", "{95672061-5799-4454-ACDB-D6D330DB1EC4}"
45+
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4648
Debug|Any CPU = Debug|Any CPU
@@ -231,6 +233,18 @@ Global
231233
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x64.Build.0 = Release|Any CPU
232234
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x86.ActiveCfg = Release|Any CPU
233235
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x86.Build.0 = Release|Any CPU
236+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
237+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
238+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x64.ActiveCfg = Debug|Any CPU
239+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x64.Build.0 = Debug|Any CPU
240+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x86.ActiveCfg = Debug|Any CPU
241+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x86.Build.0 = Debug|Any CPU
242+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
243+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|Any CPU.Build.0 = Release|Any CPU
244+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x64.ActiveCfg = Release|Any CPU
245+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x64.Build.0 = Release|Any CPU
246+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x86.ActiveCfg = Release|Any CPU
247+
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x86.Build.0 = Release|Any CPU
234248
EndGlobalSection
235249
GlobalSection(SolutionProperties) = preSolution
236250
HideSolutionNode = FALSE
@@ -251,6 +265,7 @@ Global
251265
{B9647AD4-F6B0-406F-8B79-6781E31600EC} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
252266
{5056C4A2-5E12-4C16-8DA7-8835DA58BFF2} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509}
253267
{4D2AE427-F856-49E5-B61D-EA6B17D89051} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509}
268+
{95672061-5799-4454-ACDB-D6D330DB1EC4} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
254269
EndGlobalSection
255270
GlobalSection(ExtensibilityGlobals) = postSolution
256271
SolutionGuid = {049A763A-C891-4E8D-80CF-89DD3E22ADC7}

0 commit comments

Comments
 (0)