Skip to content

Proposal: Improve KubernetesClient API Organization for Better Discoverability #1622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ayrloong opened this issue Apr 16, 2025 · 9 comments

Comments

@ayrloong
Copy link
Contributor

ayrloong commented Apr 16, 2025

Background & Problem Statement

While working with the KubernetesClient (.NET client library), I noticed that all API methods are currently grouped under a single flat hierarchy. This design makes it challenging to discover specific methods, especially when working with different resource types across API groups/versions.

For example, in the official Go client (client-go), methods are logically organized with clear resource hierarchy:

clientset.CoreV1().Pods("default").Get(context.TODO(), "example-xxxxx", metav1.GetOptions{})

However, in the current .NET implementation, similar operations lack this intuitive grouping:

// Current structure
var pods = kubernetes.CoreV1.ListNamespacedPodWithHttpMessagesAsync(namespace);
var services = kubernetes.CoreV1.ListServiceForAllNamespacesWithHttpMessagesAsync(namespace);

This flat structure leads to:

  • Difficulty discovering related methods
  • Long method lists in IDE auto-completion
  • Inconsistent with Kubernetes API grouping conventions

Proposed Improvement

Adopt a hierarchical organization pattern similar to client-go's Clientset structure:

// Proposed structure
var pods = await kubernetes.CoreV1.Pods.ListAsync(namespace);
var deployments = await kubernetes.AppsV1.Deployments.ListAsync(namespace);

Key Benefits

  1. Improved Discoverability
    Methods are grouped by API group/version and resource types

  2. Consistency
    Aligns with Kubernetes API structure and client-go patterns

  3. Type Safety
    Stronger typing through dedicated resource classes

  4. Scalability
    Easier to maintain and extend for future API versions

Implementation Approach

  1. Hierarchical Structure:

    KubernetesClient
    ├── CoreV1
    │   ├── Pods
    │   ├── Services
    │   └── Namespaces
    ├── AppsV1
    │   ├── Deployments
    │   └── StatefulSets
    └── NetworkingV1
        ├── Ingresses
        └── NetworkPolicies
    
  2. Backward Compatibility
    Maintain existing flat methods (marked obsolete) while introducing new hierarchy

  3. Resource-Specific Classes
    Create dedicated classes for each resource type with relevant operations:

    public class PodOperations {
        Task<List<Pod>> ListAsync(string namespace);
        Task<Pod> GetAsync(string name, string namespace);
        // Other pod-specific operations
    }

Example Usage

var client = new KubernetesClient();

// List pods in default namespace
var pods = await client.CoreV1.Pods.ListAsync("default");

// Get specific deployment
var deployment = await client.AppsV1.Deployments.GetAsync("api-gateway", "production");

// Create network policy
await client.NetworkingV1.NetworkPolicies.CreateAsync(new NetworkPolicy(...), "secure-ns");

If we agree on this proposal, I would be happy to submit a PR. I'm genuinely enthusiastic about contributing to the community and would love to help implement this improvement.

@tg123
Copy link
Member

tg123 commented Apr 17, 2025

It is a long story.
The project originated with autorest code where all APIs consolidated into IKubernetes

the .XXX was introduced in #850, to maintain backward compatibility, IKubernetes implements everything, minimizing breaking changes for users who do not rely on the interface.

I hope the SDK bridge the gap with client-go and incorporate-friendly features. Perhaps a kubectl-like API could be a good starting point, especially if backward compatibility is essential.

The SDK no longer depends on autorest and now includes a built-in code generator, [LibubernetesGenerator](https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator). This provides the capability to transition to a more refined API structure.

@ayrloong
Copy link
Contributor Author

Thank you for your reply, I have seen LibubernetesGenerator and now all APIs are generated by it, which means we only need to change it. But if we are sure to change it, we can only do it in a major version.

@tg123
Copy link
Member

tg123 commented Apr 17, 2025

lets wait for more input from @brendandburns

@ayrloong
Copy link
Contributor Author

Thank you so much

@brendandburns
Copy link
Contributor

Is there any way that we could do both for a few releases?

fwiw, we have a more modular API structure in the Javascript client and we had a user complain that it was too complicated and they wanted a flat API:

kubernetes-client/javascript#2339

So whatever we do here, I'm not sure we're going to make everyone happy :)

@ayrloong
Copy link
Contributor Author

Is there any way that we could do both for a few releases?

fwiw, we have a more modular API structure in the Javascript client and we had a user complain that it was too complicated and they wanted a flat API:

kubernetes-client/javascript#2339

So whatever we do here, I'm not sure we're going to make everyone happy :)

Thank you for your reply. If we implement both methods at the same time, we may need to redefine the implementation of the IKubernetes interface. We provide both methods for users to choose from, or we can keep the previous method on the Kubernetes Object and re-add the method.

 /// Default client  
var kubernetes = new k8s.Kubernetes();

kubernetes.CoreV1.ListNamespacedPodWithHttpMessagesAsync()

/// Add new method
kubernetes.CoreV1.Pods.ListAsync("default");

/// New client 
var newKubernetes = new k8s.NewKubernetes();

await newKubernetes.CoreV1.Pods.ListAsync("default");

@brendandburns
Copy link
Contributor

I think that if we do this, it would make sense to make the new modular methods live in their own namespaces (e.g. using k8s.CoreV1) vs layering them on top of the IKubernetes object. We can then implement the IKubernetes methods in terms of the methods in that package

@ayrloong
Copy link
Contributor Author

Is this what you mean?

public interface ICoreV1
{
    public PodClient Pods { get; }
    public ServiceClient Services { get; }
}

public class CoreV1Client (IKubernetes  kubernetes) : ICoreV1
{
    public PodClient Pods { get; }= new PodClient(kubernetes);
}

public class PodClient
{
    private readonly IKubernetes _kubernetes;

    public PodClient(IKubernetes kubernetes)
    {
         _kubernetes = kubernertes;
   }
    Task<List<Pod>> ListAsync(string namespace)
    {
        /// Implement IKubernetes
    }

    Task<Pod> GetAsync(string name, string namespace)
    {
        /// Implement IKubernetes
    }
}


public class ServiceClient
{
    private readonly IKubernetes kubernetes;

    Task<List<Service>> ListAsync(string namespace)
    {
        /// Implement IKubernetes
    }

    Task<Service> GetAsync(string name, string namespace)
    {
        /// Implement IKubernetes
    }
}


IKubernetes client = new Kubernetes(config);
var core = new CoreV1Client (client);

core.Pods.ListAsync(namespace)

@brendandburns
Copy link
Contributor

Yeah, something like that looks good to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants