Comments (14)
Hmm - I'm pretty sure we can do something along these lines. Let me have a think about it this afternoon and get back to you?
Another possibility might be to have DynamicResourceClientV1
which is effectively equivalent to KubeResourceClient<KubeResourceV1>
(but I think the idea needs a little TLC).
from dotnet-kube-client.
The tricky part is dynamically figuring out the API URLs for each resource type. But it can be done (there's an API for that 😉)
from dotnet-kube-client.
The tricky part is dynamically figuring out the API URLs for each resource type. But it can be done (there's an API for that 😉)
Looks promising:
https://gist.github.com/tintoy/49b7c400fb89325c3a914667af4427b5
from dotnet-kube-client.
And a quick-and-dirty metadata cache API:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace KubeClient.Metadata
{
using Models;
public sealed class KubeMetadataCache
{
static readonly IReadOnlyCollection<string> ApiGroupPrefixes = new string[] { "api", "apis" };
readonly object _stateLock = new object();
readonly Dictionary<string, KubeApiMetadata> _metadata = new Dictionary<string, KubeApiMetadata>();
public KubeMetadataCache()
{
}
public bool IsEmpty
{
get
{
lock (_stateLock)
{
return _metadata.Count == 0;
}
}
}
public KubeApiMetadata Get<TModel>()
where TModel : KubeObjectV1
{
return Get(
typeof(TModel)
);
}
public KubeApiMetadata Get(Type modelType)
{
if (modelType == null)
throw new ArgumentNullException(nameof(modelType));
(string kind, string apiVersion) = KubeObjectV1.GetKubeKind(modelType);
if (String.IsNullOrWhiteSpace(kind))
throw new ArgumentException($"Model type {modelType.FullName} has not been decorated with KubeResourceAttribute or KubeResourceListAttribute.", nameof(modelType));
return Get(kind, apiVersion);
}
public KubeApiMetadata Get(string kind, string apiVersion)
{
if (String.IsNullOrWhiteSpace(kind))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'kind'.", nameof(kind));
if (String.IsNullOrWhiteSpace(apiVersion))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'apiVersion'.", nameof(apiVersion));
lock (_stateLock)
{
string cacheKey = CreateCacheKey(kind, apiVersion);
if (_metadata.TryGetValue(cacheKey, out KubeApiMetadata metadata))
return metadata;
}
return null;
}
public string GetPrimaryPath<TModel>()
where TModel : KubeObjectV1
{
return GetPrimaryPath(
typeof(TModel)
);
}
public string GetPrimaryPath(Type modelType)
{
if (modelType == null)
throw new ArgumentNullException(nameof(modelType));
(string kind, string apiVersion) = KubeObjectV1.GetKubeKind(modelType);
if (String.IsNullOrWhiteSpace(kind))
throw new ArgumentException($"Model type {modelType.FullName} has not been decorated with KubeResourceAttribute or KubeResourceListAttribute.", nameof(modelType));
return GetPrimaryPath(kind, apiVersion);
}
public string GetPrimaryPath(string kind, string apiVersion)
{
if (String.IsNullOrWhiteSpace(kind))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'kind'.", nameof(kind));
if (String.IsNullOrWhiteSpace(apiVersion))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'apiVersion'.", nameof(apiVersion));
lock (_stateLock)
{
KubeApiMetadata metadata = Get(kind, apiVersion);
if (metadata == null)
throw new KeyNotFoundException($"No API metadata found for '{kind}/{apiVersion}'");
return metadata.Paths.Select(pathMetadata => pathMetadata.Path).FirstOrDefault();
}
}
public void Clear()
{
lock (_stateLock)
{
_metadata.Clear();
}
}
public async Task Load(KubeApiClient kubeClient, CancellationToken cancellationToken = default)
{
if (kubeClient == null)
throw new ArgumentNullException(nameof(kubeClient));
var loadedMetadata = new List<KubeApiMetadata>();
foreach (string apiGroupPrefix in ApiGroupPrefixes)
{
APIGroupListV1 apiGroups = await kubeClient.APIGroupsV1().List(apiGroupPrefix, cancellationToken);
if (apiGroupPrefix == "api")
{
// Special case for old-style ("api/v1") APIs.
apiGroups.Groups.Add(new APIGroupV1
{
Name = "Core",
PreferredVersion = new GroupVersionForDiscoveryV1
{
GroupVersion = "v1"
}
});
}
foreach (APIGroupV1 apiGroup in apiGroups.Groups)
{
List<string> groupVersions = new List<string>();
if (apiGroupPrefix == "api")
{
groupVersions.Add("v1");
}
else
{
groupVersions.AddRange(
apiGroup.Versions.Select(
version => version.GroupVersion
)
);
}
foreach (string groupVersion in groupVersions)
{
APIResourceListV1 apis = await kubeClient.APIResourcesV1().List(apiGroupPrefix, groupVersion, cancellationToken);
foreach (var apisForKind in apis.Resources.GroupBy(api => api.Kind))
{
string kind = apisForKind.Key;
var apiPaths = new List<KubeApiPathMetadata>();
foreach (APIResourceV1 api in apisForKind)
{
string apiPath = $"{apiGroupPrefix}/{apiGroup.PreferredVersion.GroupVersion}/{api.Name}";
apiPaths.Add(
new KubeApiPathMetadata(apiPath,
verbs: api.Verbs.ToArray()
)
);
}
loadedMetadata.Add(
new KubeApiMetadata(kind, groupVersion, apiPaths)
);
}
}
}
}
lock (_stateLock)
{
_metadata.Clear();
foreach (KubeApiMetadata apiMetadata in loadedMetadata)
{
string cacheKey = CreateCacheKey(apiMetadata.Kind, apiMetadata.ApiVersion);
_metadata[cacheKey] = apiMetadata;
}
}
}
static string CreateCacheKey(string kind, string apiVersion)
{
if (String.IsNullOrWhiteSpace(kind))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'kind'.", nameof(kind));
if (String.IsNullOrWhiteSpace(apiVersion))
throw new ArgumentException("Argument cannot be null, empty, or entirely composed of whitespace: 'apiVersion'.", nameof(apiVersion));
return $"{apiVersion}/{kind}";
}
}
public class KubeApiMetadata
{
public KubeApiMetadata(string kind, string apiVersion, IReadOnlyList<KubeApiPathMetadata> paths)
{
Kind = kind;
ApiVersion = apiVersion;
Paths = paths;
}
public string Kind { get; }
public string ApiVersion { get; }
public IReadOnlyList<KubeApiPathMetadata> Paths { get; }
}
public class KubeApiPathMetadata
{
public KubeApiPathMetadata(string path, IReadOnlyCollection<string> verbs)
{
Path = path;
Verbs = verbs;
}
public string Path { get; }
public IReadOnlyCollection<string> Verbs { get; }
}
}
from dotnet-kube-client.
So this API should enable you, given a resource type's kind
and apiVersion
, to find the primary API path for that resource type.
Currently this doesn't handle resource types that aren't served up by the K8s apiserver
but it'll do for a start.
Does the API look like it would do what you need?
If so, I can then create a ResourceClient
that will dynamically discover API paths and support basic operations only (CRUD, basically, where U = PATCH
).
from dotnet-kube-client.
You'd get at it by calling kubeClient.Dynamic().Get("Pod", "v1", "my-pod")
.
from dotnet-kube-client.
Or kubeClient.Dynamic().List("Pod", "v1", "my-namespace")
.
from dotnet-kube-client.
Ha, that's even more dynamic than I expected. I think I will still want to decode the YAML as the actual model classes, because I want the objects to be typed strongly, so they can be formatted with Format.ps1xml files. So I can give you the type dynamically with resource.GetType()
if that makes it easier? Of course, I can also call client.Patch(resource.Kind, resource.ApiVersion.split('/').Last(), patch)
.
from dotnet-kube-client.
If you have a resource object already then I can reflect on its Type
as required. Or are you deserialising and want a CLR Type
given kind
and apiVersion
?
from dotnet-kube-client.
I see it as a two step process. #26 would allow me to deserialise anything that has kind
and apiVersion
to model instances (I would want that in any case to implement a ConvertFrom-KubeYaml
cmdlet). Then once I have that, I can easily use it as the first step of the kubectl apply
implementation (i.e. Update-KubeResource
will take a strongly-typed model object as input, which you can get from ConvertFrom-KubeYaml
). The diffing logic in the apply implementation is written with reflection so it just takes in object
and generates a JsonPatchDocument
. The statically known type will be KubeResource
(since it's any subclass of that), which is why I can't use one of the static clients, but I can give you the runtime type through resource.GetType()
.
from dotnet-kube-client.
No worries - if the runtime type is something derived from KubeResourceV1
then the dynamic client can work with it, figuring out the API paths on the fly.
from dotnet-kube-client.
BTW, it should be pretty easy to write a JsonConverter
that uses the model-type lookups to dynamically select the type to deserialise based on kind
and apiVersion
in the incoming JSON.
Example: https://github.com/tintoy/aykay-deekay/blob/master/src/AKDK/Messages/DockerEvents/Converters/JsonCreationConverter.cs and https://github.com/tintoy/aykay-deekay/blob/master/src/AKDK/Messages/DockerEvents/Converters/DockerEventConverter.cs
from dotnet-kube-client.
Still need to implement create / delete but we're getting there :)
from dotnet-kube-client.
Will also work on making the API metadata cache read-through (and make the pre-load behaviour optional).
from dotnet-kube-client.
Related Issues (20)
- Project direction / contributions HOT 7
- How is the ApiEndPoint configured for this parameter HOT 5
- Can I debug and load configMap locally? HOT 26
- IEventClientV1 fails to convert time value HOT 15
- Error while trying to create HorizontalPodAutoscaler with DynamicResourceClient HOT 7
- Aysnc methods on resource clients do not use standard Async naming conventions HOT 3
- Access full details of STATUS column from "kubectl get pod" HOT 1
- No authentication needed for url HOT 2
- KubeClient.Models.ObjectMetaV1.Annotations Should not be read-only
- Metadata doesn't get built for write-only models HOT 3
- WatchAll is missing for some resources HOT 4
- Field selectors for pods not implemented HOT 6
- Credential Plugins (AKS / AAD) Support HOT 1
- Detect current Pod namespace HOT 4
- Cannot find resource API for kind for Istio CRD HOT 6
- Upgrade to a newer version of YamlDotNet HOT 1
- CVE-2018-8292 | System.Net.Http HOT 2
- ExecAndConnect example yields 403 Forbidden HOT 1
- Cannot get output when using ExecAndConnect HOT 3
- Periods in ConfigMap keys get replaced by colons without any possibility of escape. HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from dotnet-kube-client.