From 29af80af24bdcb30478df14850e987d88cf11f69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 01:37:32 +0000 Subject: [PATCH 1/3] Initial plan From c5e4bbf6db6d035b71f4d0ee74362b8cafaea367 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 01:54:16 +0000 Subject: [PATCH 2/3] Implement List operations for KubectlGet Add List and ListAsync methods to Kubectl and AsyncKubectl classes that support: - Listing namespaced and cluster-scoped resources - Label selector filtering (e.g., "app=test,tier=frontend") - Field selector filtering (e.g., "metadata.name=my-pod") - Pagination with limit and continue token Also update GenericClient.ListAsync and ListNamespacedAsync to pass through the filtering parameters to the underlying CustomObjects API. Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- .../Beta/AsyncKubectl.List.cs | 32 ++ .../Beta/Kubectl.List.cs | 25 ++ src/KubernetesClient/GenericClient.cs | 8 +- tests/Kubectl.Tests/KubectlTests.List.cs | 381 ++++++++++++++++++ 4 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs create mode 100644 src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs create mode 100644 tests/Kubectl.Tests/KubectlTests.List.cs diff --git a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs new file mode 100644 index 000000000..866d8508b --- /dev/null +++ b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs @@ -0,0 +1,32 @@ +namespace k8s.kubectl.beta; + +public partial class AsyncKubectl +{ + /// + /// List Kubernetes resources of a specific type. + /// + /// The type of Kubernetes resource list to get (e.g., V1PodList). + /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. + /// A selector to restrict the list of returned objects by their labels. Defaults to everything. + /// A selector to restrict the list of returned objects by their fields. Defaults to everything. + /// Maximum number of responses to return for a list call. + /// The continue option should be set when retrieving more results from the server. + /// Cancellation token. + /// The list of requested resources. + public async Task ListAsync( + string? @namespace = null, + string? labelSelector = null, + string? fieldSelector = null, + int? limit = null, + string? continueToken = null, + CancellationToken cancellationToken = default) + where T : IKubernetesObject + { + var metadata = typeof(T).GetKubernetesTypeMetadata(); + using var genericClient = new GenericClient(client, metadata.Group, metadata.ApiVersion, metadata.PluralName, disposeClient: false); + + return @namespace != null + ? await genericClient.ListNamespacedAsync(@namespace, labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false) + : await genericClient.ListAsync(labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs b/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs new file mode 100644 index 000000000..265437e71 --- /dev/null +++ b/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs @@ -0,0 +1,25 @@ +namespace k8s.kubectl.beta; + +public partial class Kubectl +{ + /// + /// List Kubernetes resources of a specific type. + /// + /// The type of Kubernetes resource list to get (e.g., V1PodList). + /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. + /// A selector to restrict the list of returned objects by their labels. Defaults to everything. + /// A selector to restrict the list of returned objects by their fields. Defaults to everything. + /// Maximum number of responses to return for a list call. + /// The continue option should be set when retrieving more results from the server. + /// The list of requested resources. + public T List( + string? @namespace = null, + string? labelSelector = null, + string? fieldSelector = null, + int? limit = null, + string? continueToken = null) + where T : IKubernetesObject + { + return client.ListAsync(@namespace, labelSelector, fieldSelector, limit, continueToken).GetAwaiter().GetResult(); + } +} diff --git a/src/KubernetesClient/GenericClient.cs b/src/KubernetesClient/GenericClient.cs index a250aad22..9e295c970 100644 --- a/src/KubernetesClient/GenericClient.cs +++ b/src/KubernetesClient/GenericClient.cs @@ -42,17 +42,17 @@ public async Task CreateNamespacedAsync(T obj, string ns, CancellationToke return resp.Body; } - public async Task ListAsync(CancellationToken cancel = default) + public async Task ListAsync(string labelSelector = null, string fieldSelector = null, int? limit = null, string continueToken = null, CancellationToken cancel = default) where T : IKubernetesObject { - var resp = await kubernetes.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(group, version, plural, cancellationToken: cancel).ConfigureAwait(false); + var resp = await kubernetes.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(group, version, plural, labelSelector: labelSelector, fieldSelector: fieldSelector, limit: limit, continueParameter: continueToken, cancellationToken: cancel).ConfigureAwait(false); return resp.Body; } - public async Task ListNamespacedAsync(string ns, CancellationToken cancel = default) + public async Task ListNamespacedAsync(string ns, string labelSelector = null, string fieldSelector = null, int? limit = null, string continueToken = null, CancellationToken cancel = default) where T : IKubernetesObject { - var resp = await kubernetes.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(group, version, ns, plural, cancellationToken: cancel).ConfigureAwait(false); + var resp = await kubernetes.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(group, version, ns, plural, labelSelector: labelSelector, fieldSelector: fieldSelector, limit: limit, continueParameter: continueToken, cancellationToken: cancel).ConfigureAwait(false); return resp.Body; } diff --git a/tests/Kubectl.Tests/KubectlTests.List.cs b/tests/Kubectl.Tests/KubectlTests.List.cs new file mode 100644 index 000000000..7c2c727b9 --- /dev/null +++ b/tests/Kubectl.Tests/KubectlTests.List.cs @@ -0,0 +1,381 @@ +using k8s.Autorest; +using k8s.E2E; +using k8s.kubectl.beta; +using k8s.Models; +using Xunit; + +namespace k8s.kubectl.Tests; + +public partial class KubectlTests +{ + [MinikubeFact] + public void ListNamespaces() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + + // List all namespaces (cluster-scoped resources) + var namespaces = client.List(); + + Assert.NotNull(namespaces); + Assert.NotNull(namespaces.Items); + Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "default"); + Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "kube-system"); + } + + [MinikubeFact] + public void ListPods() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-list-pod"; + + // Create a test pod with a label + var pod = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podName, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-test" }, + { "test-type", "list" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); + + // List pods in the namespace + var pods = client.List(namespaceParameter); + + Assert.NotNull(pods); + Assert.NotNull(pods.Items); + Assert.Contains(pods.Items, p => p.Metadata.Name == podName); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors if pod was already deleted or doesn't exist + } + } + } + + [MinikubeFact] + public void ListPodsWithLabelSelector() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podNameA = "k8scsharp-e2e-list-selector-a"; + var podNameB = "k8scsharp-e2e-list-selector-b"; + + // Create two test pods with different labels + var podA = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podNameA, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-selector-test" }, + { "tier", "frontend" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + var podB = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podNameB, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-selector-test" }, + { "tier", "backend" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(podA, namespaceParameter); + kubernetes.CoreV1.CreateNamespacedPod(podB, namespaceParameter); + + // List pods with label selector for frontend tier only + var frontendPods = client.List( + namespaceParameter, + labelSelector: "tier=frontend,app=k8scsharp-e2e-selector-test"); + + Assert.NotNull(frontendPods); + Assert.NotNull(frontendPods.Items); + Assert.Single(frontendPods.Items); + Assert.Equal(podNameA, frontendPods.Items[0].Metadata.Name); + + // List pods with label selector for app only (should return both) + var allTestPods = client.List( + namespaceParameter, + labelSelector: "app=k8scsharp-e2e-selector-test"); + + Assert.NotNull(allTestPods); + Assert.NotNull(allTestPods.Items); + Assert.Equal(2, allTestPods.Items.Count); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podNameA, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podNameB, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListPodsWithFieldSelector() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-list-field"; + + // Create a test pod + var pod = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); + + // List pods with field selector for specific metadata.name + var pods = client.List( + namespaceParameter, + fieldSelector: $"metadata.name={podName}"); + + Assert.NotNull(pods); + Assert.NotNull(pods.Items); + Assert.Single(pods.Items); + Assert.Equal(podName, pods.Items[0].Metadata.Name); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListServicesInNamespace() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var serviceName = "k8scsharp-e2e-list-service"; + + // Create a test service + var service = new V1Service + { + Metadata = new V1ObjectMeta + { + Name = serviceName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1ServiceSpec + { + Ports = new[] + { + new V1ServicePort + { + Port = 80, + TargetPort = 80, + }, + }, + Selector = new Dictionary + { + { "app", "test" }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedService(service, namespaceParameter); + + // List services in the namespace + var services = client.List(namespaceParameter); + + Assert.NotNull(services); + Assert.NotNull(services.Items); + Assert.Contains(services.Items, s => s.Metadata.Name == serviceName); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedService(serviceName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListDeployments() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var deploymentName = "k8scsharp-e2e-list-deployment"; + + // Create a test deployment + var deployment = new V1Deployment + { + Metadata = new V1ObjectMeta + { + Name = deploymentName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1DeploymentSpec + { + Replicas = 1, + Selector = new V1LabelSelector + { + MatchLabels = new Dictionary + { + { "app", "test" }, + }, + }, + Template = new V1PodTemplateSpec + { + Metadata = new V1ObjectMeta + { + Labels = new Dictionary + { + { "app", "test" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }, + }, + }; + + try + { + kubernetes.AppsV1.CreateNamespacedDeployment(deployment, namespaceParameter); + + // List deployments in the namespace + var deployments = client.List(namespaceParameter); + + Assert.NotNull(deployments); + Assert.NotNull(deployments.Items); + Assert.Contains(deployments.Items, d => d.Metadata.Name == deploymentName); + } + finally + { + // Cleanup + try + { + kubernetes.AppsV1.DeleteNamespacedDeployment(deploymentName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } +} From 78e5790cd3da91884f90393984e2aacf52c06ca6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 02:17:52 +0000 Subject: [PATCH 3/3] Move List methods into Get files per review feedback - Merged List/ListAsync methods into Kubectl.Get.cs and AsyncKubectl.Get.cs - Moved List tests into KubectlTests.Get.cs - Deleted separate Kubectl.List.cs, AsyncKubectl.List.cs, and KubectlTests.List.cs files Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- .../Beta/AsyncKubectl.Get.cs | 28 ++ .../Beta/AsyncKubectl.List.cs | 32 -- .../Beta/Kubectl.Get.cs | 21 + .../Beta/Kubectl.List.cs | 25 -- tests/Kubectl.Tests/KubectlTests.Get.cs | 371 +++++++++++++++++ tests/Kubectl.Tests/KubectlTests.List.cs | 381 ------------------ 6 files changed, 420 insertions(+), 438 deletions(-) delete mode 100644 src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs delete mode 100644 src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs delete mode 100644 tests/Kubectl.Tests/KubectlTests.List.cs diff --git a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Get.cs b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Get.cs index a78f1cc67..bed8ee369 100644 --- a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Get.cs +++ b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.Get.cs @@ -20,4 +20,32 @@ public async Task GetAsync(string name, string? @namespace = null, Cancell ? await genericClient.ReadNamespacedAsync(@namespace, name, cancellationToken).ConfigureAwait(false) : await genericClient.ReadAsync(name, cancellationToken).ConfigureAwait(false); } + + /// + /// List Kubernetes resources of a specific type. + /// + /// The type of Kubernetes resource list to get (e.g., V1PodList). + /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. + /// A selector to restrict the list of returned objects by their labels. Defaults to everything. + /// A selector to restrict the list of returned objects by their fields. Defaults to everything. + /// Maximum number of responses to return for a list call. + /// The continue option should be set when retrieving more results from the server. + /// Cancellation token. + /// The list of requested resources. + public async Task ListAsync( + string? @namespace = null, + string? labelSelector = null, + string? fieldSelector = null, + int? limit = null, + string? continueToken = null, + CancellationToken cancellationToken = default) + where T : IKubernetesObject + { + var metadata = typeof(T).GetKubernetesTypeMetadata(); + using var genericClient = new GenericClient(client, metadata.Group, metadata.ApiVersion, metadata.PluralName, disposeClient: false); + + return @namespace != null + ? await genericClient.ListNamespacedAsync(@namespace, labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false) + : await genericClient.ListAsync(labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false); + } } diff --git a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs b/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs deleted file mode 100644 index 866d8508b..000000000 --- a/src/KubernetesClient.Kubectl/Beta/AsyncKubectl.List.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace k8s.kubectl.beta; - -public partial class AsyncKubectl -{ - /// - /// List Kubernetes resources of a specific type. - /// - /// The type of Kubernetes resource list to get (e.g., V1PodList). - /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. - /// A selector to restrict the list of returned objects by their labels. Defaults to everything. - /// A selector to restrict the list of returned objects by their fields. Defaults to everything. - /// Maximum number of responses to return for a list call. - /// The continue option should be set when retrieving more results from the server. - /// Cancellation token. - /// The list of requested resources. - public async Task ListAsync( - string? @namespace = null, - string? labelSelector = null, - string? fieldSelector = null, - int? limit = null, - string? continueToken = null, - CancellationToken cancellationToken = default) - where T : IKubernetesObject - { - var metadata = typeof(T).GetKubernetesTypeMetadata(); - using var genericClient = new GenericClient(client, metadata.Group, metadata.ApiVersion, metadata.PluralName, disposeClient: false); - - return @namespace != null - ? await genericClient.ListNamespacedAsync(@namespace, labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false) - : await genericClient.ListAsync(labelSelector, fieldSelector, limit, continueToken, cancellationToken).ConfigureAwait(false); - } -} diff --git a/src/KubernetesClient.Kubectl/Beta/Kubectl.Get.cs b/src/KubernetesClient.Kubectl/Beta/Kubectl.Get.cs index dda5acba4..96b0299c5 100644 --- a/src/KubernetesClient.Kubectl/Beta/Kubectl.Get.cs +++ b/src/KubernetesClient.Kubectl/Beta/Kubectl.Get.cs @@ -14,4 +14,25 @@ public T Get(string name, string? @namespace = null) { return client.GetAsync(name, @namespace).GetAwaiter().GetResult(); } + + /// + /// List Kubernetes resources of a specific type. + /// + /// The type of Kubernetes resource list to get (e.g., V1PodList). + /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. + /// A selector to restrict the list of returned objects by their labels. Defaults to everything. + /// A selector to restrict the list of returned objects by their fields. Defaults to everything. + /// Maximum number of responses to return for a list call. + /// The continue option should be set when retrieving more results from the server. + /// The list of requested resources. + public T List( + string? @namespace = null, + string? labelSelector = null, + string? fieldSelector = null, + int? limit = null, + string? continueToken = null) + where T : IKubernetesObject + { + return client.ListAsync(@namespace, labelSelector, fieldSelector, limit, continueToken).GetAwaiter().GetResult(); + } } diff --git a/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs b/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs deleted file mode 100644 index 265437e71..000000000 --- a/src/KubernetesClient.Kubectl/Beta/Kubectl.List.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace k8s.kubectl.beta; - -public partial class Kubectl -{ - /// - /// List Kubernetes resources of a specific type. - /// - /// The type of Kubernetes resource list to get (e.g., V1PodList). - /// The namespace to list resources from. If null, lists cluster-scoped resources or resources across all namespaces for namespaced resources. - /// A selector to restrict the list of returned objects by their labels. Defaults to everything. - /// A selector to restrict the list of returned objects by their fields. Defaults to everything. - /// Maximum number of responses to return for a list call. - /// The continue option should be set when retrieving more results from the server. - /// The list of requested resources. - public T List( - string? @namespace = null, - string? labelSelector = null, - string? fieldSelector = null, - int? limit = null, - string? continueToken = null) - where T : IKubernetesObject - { - return client.ListAsync(@namespace, labelSelector, fieldSelector, limit, continueToken).GetAwaiter().GetResult(); - } -} diff --git a/tests/Kubectl.Tests/KubectlTests.Get.cs b/tests/Kubectl.Tests/KubectlTests.Get.cs index 4782a0179..487898501 100644 --- a/tests/Kubectl.Tests/KubectlTests.Get.cs +++ b/tests/Kubectl.Tests/KubectlTests.Get.cs @@ -214,4 +214,375 @@ public void GetDeployment() } } } + + [MinikubeFact] + public void ListNamespaces() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + + // List all namespaces (cluster-scoped resources) + var namespaces = client.List(); + + Assert.NotNull(namespaces); + Assert.NotNull(namespaces.Items); + Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "default"); + Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "kube-system"); + } + + [MinikubeFact] + public void ListPods() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-list-pod"; + + // Create a test pod with a label + var pod = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podName, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-test" }, + { "test-type", "list" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); + + // List pods in the namespace + var pods = client.List(namespaceParameter); + + Assert.NotNull(pods); + Assert.NotNull(pods.Items); + Assert.Contains(pods.Items, p => p.Metadata.Name == podName); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors if pod was already deleted or doesn't exist + } + } + } + + [MinikubeFact] + public void ListPodsWithLabelSelector() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podNameA = "k8scsharp-e2e-list-selector-a"; + var podNameB = "k8scsharp-e2e-list-selector-b"; + + // Create two test pods with different labels + var podA = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podNameA, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-selector-test" }, + { "tier", "frontend" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + var podB = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podNameB, + NamespaceProperty = namespaceParameter, + Labels = new Dictionary + { + { "app", "k8scsharp-e2e-selector-test" }, + { "tier", "backend" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(podA, namespaceParameter); + kubernetes.CoreV1.CreateNamespacedPod(podB, namespaceParameter); + + // List pods with label selector for frontend tier only + var frontendPods = client.List( + namespaceParameter, + labelSelector: "tier=frontend,app=k8scsharp-e2e-selector-test"); + + Assert.NotNull(frontendPods); + Assert.NotNull(frontendPods.Items); + Assert.Single(frontendPods.Items); + Assert.Equal(podNameA, frontendPods.Items[0].Metadata.Name); + + // List pods with label selector for app only (should return both) + var allTestPods = client.List( + namespaceParameter, + labelSelector: "app=k8scsharp-e2e-selector-test"); + + Assert.NotNull(allTestPods); + Assert.NotNull(allTestPods.Items); + Assert.Equal(2, allTestPods.Items.Count); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podNameA, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podNameB, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListPodsWithFieldSelector() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-list-field"; + + // Create a test pod + var pod = new V1Pod + { + Metadata = new V1ObjectMeta + { + Name = podName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); + + // List pods with field selector for specific metadata.name + var pods = client.List( + namespaceParameter, + fieldSelector: $"metadata.name={podName}"); + + Assert.NotNull(pods); + Assert.NotNull(pods.Items); + Assert.Single(pods.Items); + Assert.Equal(podName, pods.Items[0].Metadata.Name); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListServicesInNamespace() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var serviceName = "k8scsharp-e2e-list-service"; + + // Create a test service + var service = new V1Service + { + Metadata = new V1ObjectMeta + { + Name = serviceName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1ServiceSpec + { + Ports = new[] + { + new V1ServicePort + { + Port = 80, + TargetPort = 80, + }, + }, + Selector = new Dictionary + { + { "app", "test" }, + }, + }, + }; + + try + { + kubernetes.CoreV1.CreateNamespacedService(service, namespaceParameter); + + // List services in the namespace + var services = client.List(namespaceParameter); + + Assert.NotNull(services); + Assert.NotNull(services.Items); + Assert.Contains(services.Items, s => s.Metadata.Name == serviceName); + } + finally + { + // Cleanup + try + { + kubernetes.CoreV1.DeleteNamespacedService(serviceName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } + + [MinikubeFact] + public void ListDeployments() + { + using var kubernetes = MinikubeTests.CreateClient(); + var client = new Kubectl(kubernetes); + var namespaceParameter = "default"; + var deploymentName = "k8scsharp-e2e-list-deployment"; + + // Create a test deployment + var deployment = new V1Deployment + { + Metadata = new V1ObjectMeta + { + Name = deploymentName, + NamespaceProperty = namespaceParameter, + }, + Spec = new V1DeploymentSpec + { + Replicas = 1, + Selector = new V1LabelSelector + { + MatchLabels = new Dictionary + { + { "app", "test" }, + }, + }, + Template = new V1PodTemplateSpec + { + Metadata = new V1ObjectMeta + { + Labels = new Dictionary + { + { "app", "test" }, + }, + }, + Spec = new V1PodSpec + { + Containers = new[] + { + new V1Container + { + Name = "test", + Image = "nginx:latest", + }, + }, + }, + }, + }, + }; + + try + { + kubernetes.AppsV1.CreateNamespacedDeployment(deployment, namespaceParameter); + + // List deployments in the namespace + var deployments = client.List(namespaceParameter); + + Assert.NotNull(deployments); + Assert.NotNull(deployments.Items); + Assert.Contains(deployments.Items, d => d.Metadata.Name == deploymentName); + } + finally + { + // Cleanup + try + { + kubernetes.AppsV1.DeleteNamespacedDeployment(deploymentName, namespaceParameter); + } + catch (HttpOperationException) + { + // Ignore cleanup errors + } + } + } } diff --git a/tests/Kubectl.Tests/KubectlTests.List.cs b/tests/Kubectl.Tests/KubectlTests.List.cs deleted file mode 100644 index 7c2c727b9..000000000 --- a/tests/Kubectl.Tests/KubectlTests.List.cs +++ /dev/null @@ -1,381 +0,0 @@ -using k8s.Autorest; -using k8s.E2E; -using k8s.kubectl.beta; -using k8s.Models; -using Xunit; - -namespace k8s.kubectl.Tests; - -public partial class KubectlTests -{ - [MinikubeFact] - public void ListNamespaces() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - - // List all namespaces (cluster-scoped resources) - var namespaces = client.List(); - - Assert.NotNull(namespaces); - Assert.NotNull(namespaces.Items); - Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "default"); - Assert.Contains(namespaces.Items, ns => ns.Metadata.Name == "kube-system"); - } - - [MinikubeFact] - public void ListPods() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - var namespaceParameter = "default"; - var podName = "k8scsharp-e2e-list-pod"; - - // Create a test pod with a label - var pod = new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = podName, - NamespaceProperty = namespaceParameter, - Labels = new Dictionary - { - { "app", "k8scsharp-e2e-test" }, - { "test-type", "list" }, - }, - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Name = "test", - Image = "nginx:latest", - }, - }, - }, - }; - - try - { - kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); - - // List pods in the namespace - var pods = client.List(namespaceParameter); - - Assert.NotNull(pods); - Assert.NotNull(pods.Items); - Assert.Contains(pods.Items, p => p.Metadata.Name == podName); - } - finally - { - // Cleanup - try - { - kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors if pod was already deleted or doesn't exist - } - } - } - - [MinikubeFact] - public void ListPodsWithLabelSelector() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - var namespaceParameter = "default"; - var podNameA = "k8scsharp-e2e-list-selector-a"; - var podNameB = "k8scsharp-e2e-list-selector-b"; - - // Create two test pods with different labels - var podA = new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = podNameA, - NamespaceProperty = namespaceParameter, - Labels = new Dictionary - { - { "app", "k8scsharp-e2e-selector-test" }, - { "tier", "frontend" }, - }, - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Name = "test", - Image = "nginx:latest", - }, - }, - }, - }; - - var podB = new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = podNameB, - NamespaceProperty = namespaceParameter, - Labels = new Dictionary - { - { "app", "k8scsharp-e2e-selector-test" }, - { "tier", "backend" }, - }, - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Name = "test", - Image = "nginx:latest", - }, - }, - }, - }; - - try - { - kubernetes.CoreV1.CreateNamespacedPod(podA, namespaceParameter); - kubernetes.CoreV1.CreateNamespacedPod(podB, namespaceParameter); - - // List pods with label selector for frontend tier only - var frontendPods = client.List( - namespaceParameter, - labelSelector: "tier=frontend,app=k8scsharp-e2e-selector-test"); - - Assert.NotNull(frontendPods); - Assert.NotNull(frontendPods.Items); - Assert.Single(frontendPods.Items); - Assert.Equal(podNameA, frontendPods.Items[0].Metadata.Name); - - // List pods with label selector for app only (should return both) - var allTestPods = client.List( - namespaceParameter, - labelSelector: "app=k8scsharp-e2e-selector-test"); - - Assert.NotNull(allTestPods); - Assert.NotNull(allTestPods.Items); - Assert.Equal(2, allTestPods.Items.Count); - } - finally - { - // Cleanup - try - { - kubernetes.CoreV1.DeleteNamespacedPod(podNameA, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors - } - - try - { - kubernetes.CoreV1.DeleteNamespacedPod(podNameB, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors - } - } - } - - [MinikubeFact] - public void ListPodsWithFieldSelector() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - var namespaceParameter = "default"; - var podName = "k8scsharp-e2e-list-field"; - - // Create a test pod - var pod = new V1Pod - { - Metadata = new V1ObjectMeta - { - Name = podName, - NamespaceProperty = namespaceParameter, - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Name = "test", - Image = "nginx:latest", - }, - }, - }, - }; - - try - { - kubernetes.CoreV1.CreateNamespacedPod(pod, namespaceParameter); - - // List pods with field selector for specific metadata.name - var pods = client.List( - namespaceParameter, - fieldSelector: $"metadata.name={podName}"); - - Assert.NotNull(pods); - Assert.NotNull(pods.Items); - Assert.Single(pods.Items); - Assert.Equal(podName, pods.Items[0].Metadata.Name); - } - finally - { - // Cleanup - try - { - kubernetes.CoreV1.DeleteNamespacedPod(podName, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors - } - } - } - - [MinikubeFact] - public void ListServicesInNamespace() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - var namespaceParameter = "default"; - var serviceName = "k8scsharp-e2e-list-service"; - - // Create a test service - var service = new V1Service - { - Metadata = new V1ObjectMeta - { - Name = serviceName, - NamespaceProperty = namespaceParameter, - }, - Spec = new V1ServiceSpec - { - Ports = new[] - { - new V1ServicePort - { - Port = 80, - TargetPort = 80, - }, - }, - Selector = new Dictionary - { - { "app", "test" }, - }, - }, - }; - - try - { - kubernetes.CoreV1.CreateNamespacedService(service, namespaceParameter); - - // List services in the namespace - var services = client.List(namespaceParameter); - - Assert.NotNull(services); - Assert.NotNull(services.Items); - Assert.Contains(services.Items, s => s.Metadata.Name == serviceName); - } - finally - { - // Cleanup - try - { - kubernetes.CoreV1.DeleteNamespacedService(serviceName, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors - } - } - } - - [MinikubeFact] - public void ListDeployments() - { - using var kubernetes = MinikubeTests.CreateClient(); - var client = new Kubectl(kubernetes); - var namespaceParameter = "default"; - var deploymentName = "k8scsharp-e2e-list-deployment"; - - // Create a test deployment - var deployment = new V1Deployment - { - Metadata = new V1ObjectMeta - { - Name = deploymentName, - NamespaceProperty = namespaceParameter, - }, - Spec = new V1DeploymentSpec - { - Replicas = 1, - Selector = new V1LabelSelector - { - MatchLabels = new Dictionary - { - { "app", "test" }, - }, - }, - Template = new V1PodTemplateSpec - { - Metadata = new V1ObjectMeta - { - Labels = new Dictionary - { - { "app", "test" }, - }, - }, - Spec = new V1PodSpec - { - Containers = new[] - { - new V1Container - { - Name = "test", - Image = "nginx:latest", - }, - }, - }, - }, - }, - }; - - try - { - kubernetes.AppsV1.CreateNamespacedDeployment(deployment, namespaceParameter); - - // List deployments in the namespace - var deployments = client.List(namespaceParameter); - - Assert.NotNull(deployments); - Assert.NotNull(deployments.Items); - Assert.Contains(deployments.Items, d => d.Metadata.Name == deploymentName); - } - finally - { - // Cleanup - try - { - kubernetes.AppsV1.DeleteNamespacedDeployment(deploymentName, namespaceParameter); - } - catch (HttpOperationException) - { - // Ignore cleanup errors - } - } - } -}