Skip to content

Commit

Permalink
Custom Resource example (#540)
Browse files Browse the repository at this point in the history
* CR example

* minor changes
  • Loading branch information
akanso committed Dec 19, 2020
1 parent 9a90881 commit 0e3cb94
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 0 deletions.
45 changes: 45 additions & 0 deletions examples/customResource/CustomResourceDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Collections.Generic;
using k8s;
using k8s.Models;
using Newtonsoft.Json;

[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "This is just an example.")]
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "This is just an example.")]

namespace customResource
{
public class CustomResourceDefinition
{
public string Version { get; set; }

public string Group { get; set; }

public string PluralName { get; set; }

public string Kind { get; set; }

public string Namespace { get; set; }
}

public abstract class CustomResource : KubernetesObject
{
[JsonProperty(PropertyName = "metadata")]
public V1ObjectMeta Metadata { get; set; }
}

public abstract class CustomResource<TSpec, TStatus> : CustomResource
{
[JsonProperty(PropertyName = "spec")]
public TSpec Spec { get; set; }

[JsonProperty(PropertyName = "CStatus")]
public TStatus CStatus { get; set; }
}

public class CustomResourceList<T> : KubernetesObject
where T : CustomResource
{
public V1ListMeta Metadata { get; set; }
public List<T> Items { get; set; }
}
}
103 changes: 103 additions & 0 deletions examples/customResource/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using k8s;
using k8s.Models;
using System.Threading.Tasks;
using Microsoft.AspNetCore.JsonPatch;


namespace customResource
{
public class Program
{
private static async Task Main(string[] args)
{
Console.WriteLine("strating main()...");

// creating the k8s client
var k8SClientConfig = KubernetesClientConfiguration.BuildConfigFromConfigFile();
IKubernetes client = new Kubernetes(k8SClientConfig);

// creating a K8s client for the CRD
var myCRD = Utils.MakeCRD();
Console.WriteLine("working with CRD: {0}.{1}", myCRD.PluralName, myCRD.Group);
var generic = new GenericClient(k8SClientConfig, myCRD.Group, myCRD.Version, myCRD.PluralName);

// creating a sample custom resource content
var myCr = Utils.MakeCResource();

try
{
Console.WriteLine("creating CR {0}", myCr.Metadata.Name);
var response = await client.CreateNamespacedCustomObjectWithHttpMessagesAsync(
myCr,
myCRD.Group, myCRD.Version,
myCr.Metadata.NamespaceProperty ?? "default",
myCRD.PluralName).ConfigureAwait(false);
}
catch (Microsoft.Rest.HttpOperationException httpOperationException) when (httpOperationException.Message.Contains("422"))
{
var phase = httpOperationException.Response.ReasonPhrase;
var content = httpOperationException.Response.Content;
Console.WriteLine("response content: {0}", content);
Console.WriteLine("response phase: {0}", phase);
}
catch (Microsoft.Rest.HttpOperationException)
{
}

// listing the cr instances
Console.WriteLine("CR list:");
var crs = await generic.ListNamespacedAsync<CustomResourceList<CResource>>(myCr.Metadata.NamespaceProperty ?? "default").ConfigureAwait(false);
foreach (var cr in crs.Items)
{
Console.WriteLine("- CR Item {0} = {1}", crs.Items.IndexOf(cr), cr.Metadata.Name);
}

// updating the custom resource
myCr.Metadata.Labels.TryAdd("newKey", "newValue");
var patch = new JsonPatchDocument<CResource>();
patch.Replace(x => x.Metadata.Labels, myCr.Metadata.Labels);
patch.Operations.ForEach(x => x.path = x.path.ToLower());
var crPatch = new V1Patch(patch, V1Patch.PatchType.JsonPatch);
try
{
var patchResponse = await client.PatchNamespacedCustomObjectAsync(
crPatch,
myCRD.Group,
myCRD.Version,
myCr.Metadata.NamespaceProperty ?? "default",
myCRD.PluralName,
myCr.Metadata.Name).ConfigureAwait(false);
}
catch (Microsoft.Rest.HttpOperationException httpOperationException)
{
var phase = httpOperationException.Response.ReasonPhrase;
var content = httpOperationException.Response.Content;
Console.WriteLine("response content: {0}", content);
Console.WriteLine("response phase: {0}", phase);
}

// getting the updated custom resource
var fetchedCR = await generic.ReadNamespacedAsync<CResource>(
myCr.Metadata.NamespaceProperty ?? "default",
myCr.Metadata.Name).ConfigureAwait(false);

Console.WriteLine("fetchedCR = {0}", fetchedCR.ToString());

// deleting the custom resource
try
{
myCr = await generic.DeleteNamespacedAsync<CResource>(
myCr.Metadata.NamespaceProperty ?? "default",
myCr.Metadata.Name).ConfigureAwait(false);

Console.WriteLine("Deleted the CR");
}
catch (Exception exception)
{
Console.WriteLine("Exception type {0}", exception);
}
}
}
}
53 changes: 53 additions & 0 deletions examples/customResource/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Custom Resource Client Example

This example demonstrates how to use the C# Kubernetes Client library to create, get and list custom resources.

## Pre-requisits

Make sure your have added the library package

```shell
dotnet add package KubernetesClient
```

## Create Custom Resource Definition (CRD)

Make sure the [CRD](./config/crd.yaml) is created, in order to create an instance of it after.

```shell
kubectl create -f ./crd.yaml
```

You can test that the CRD is successfully added, by creating an [instance](./config/yaml-cr-instance.yaml) of it using kubectl:

```shell
kubectl create -f ./config/yaml-cr-instance.yaml
```

```shell
kubectl get customresources.csharp.com
```

## Execute the code

The client uses the `BuildConfigFromConfigFile()` function. If the KUBECONFIG environment variable is set, then that path to the k8s config file will be used.

`dotnet run`

Expected output:

```
strating main()...
working with CRD: customresources.csharp.com
creating CR cr-instance-london
CR list:
- CR Item 0 = cr-instance-london
- CR Item 1 = cr-instance-paris
fetchedCR = cr-instance-london (Labels: {identifier : city, newKey : newValue}), Spec: London
Deleted the CR
```

## Under the hood

For more details, you can look at the Generic client [implementation](https://github.com/kubernetes-client/csharp/blob/master/src/KubernetesClient/GenericClient.cs)

48 changes: 48 additions & 0 deletions examples/customResource/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using k8s.Models;
using System.Collections.Generic;
namespace customResource
{
public class Utils
{
// creats a CRD definition
public static CustomResourceDefinition MakeCRD()
{
var myCRD = new CustomResourceDefinition()
{
Kind = "CResource",
Group = "csharp.com",
Version = "v1alpha1",
PluralName = "customresources",
};

return myCRD;
}

// creats a CR instance
public static CResource MakeCResource()
{
var myCResource = new CResource()
{
Kind = "CResource",
ApiVersion = "csharp.com/v1alpha1",
Metadata = new V1ObjectMeta
{
Name = "cr-instance-london",
NamespaceProperty = "default",
Labels = new Dictionary<string, string>
{
{
"identifier", "city"
},
},
},
// spec
Spec = new CResourceSpec
{
CityName = "London",
},
};
return myCResource;
}
}
}
35 changes: 35 additions & 0 deletions examples/customResource/cResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using k8s.Models;
using System.Collections.Generic;
using k8s;
using Newtonsoft.Json;

namespace customResource
{
public class CResource : CustomResource<CResourceSpec, CResourceStatus>
{
public override string ToString()
{
var labels = "{";
foreach (var kvp in Metadata.Labels)
{
labels += kvp.Key + " : " + kvp.Value + ", ";
}

labels = labels.TrimEnd(',', ' ') + "}";

return $"{Metadata.Name} (Labels: {labels}), Spec: {Spec.CityName}";
}
}

public class CResourceSpec
{
[JsonProperty(PropertyName = "cityName")]
public string CityName { get; set; }
}

public class CResourceStatus : V1Status
{
[JsonProperty(PropertyName = "temperature", NullValueHandling = NullValueHandling.Ignore)]
public string Temperature { get; set; }
}
}
11 changes: 11 additions & 0 deletions examples/customResource/config/crd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: customresources.csharp.com
spec:
group: csharp.com
version: v1alpha1
names:
kind: CResource
plural: customresources
scope: Namespaced
8 changes: 8 additions & 0 deletions examples/customResource/config/yaml-cr-instance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: csharp.com/v1alpha1
kind: CResource
metadata:
name: cr-instance-paris
namespace: default
spec:
cityName: Paris

15 changes: 15 additions & 0 deletions examples/customResource/customResource.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="KubernetesClient" Version="3.0.16" />
<PackageReference Include="Microsoft.Build" Version="16.8.0" />
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.1" />
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions kubernetes-client.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "E2E.Tests", "tests\E2E.Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkipTestLogger", "tests\SkipTestLogger\SkipTestLogger.csproj", "{4D2AE427-F856-49E5-B61D-EA6B17D89051}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "customResource", "examples\customResource\customResource.csproj", "{95672061-5799-4454-ACDB-D6D330DB1EC4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -231,6 +233,18 @@ Global
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x64.Build.0 = Release|Any CPU
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x86.ActiveCfg = Release|Any CPU
{4D2AE427-F856-49E5-B61D-EA6B17D89051}.Release|x86.Build.0 = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x64.ActiveCfg = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x64.Build.0 = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x86.ActiveCfg = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Debug|x86.Build.0 = Debug|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|Any CPU.Build.0 = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x64.ActiveCfg = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x64.Build.0 = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x86.ActiveCfg = Release|Any CPU
{95672061-5799-4454-ACDB-D6D330DB1EC4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -251,6 +265,7 @@ Global
{B9647AD4-F6B0-406F-8B79-6781E31600EC} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
{5056C4A2-5E12-4C16-8DA7-8835DA58BFF2} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509}
{4D2AE427-F856-49E5-B61D-EA6B17D89051} = {8AF4A5C2-F0CE-47D5-A4C5-FE4AB83CA509}
{95672061-5799-4454-ACDB-D6D330DB1EC4} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {049A763A-C891-4E8D-80CF-89DD3E22ADC7}
Expand Down

0 comments on commit 0e3cb94

Please sign in to comment.