From 4af3e03d3b809b2c37c18d2cc5ad56bc418ffdc8 Mon Sep 17 00:00:00 2001 From: yue9944882 <291271447@qq.com> Date: Thu, 10 Sep 2020 19:34:25 +0800 Subject: [PATCH] kubectl get implementation --- .../e2e/kubectl/KubectlApplyTest.groovy | 5 +- .../e2e/kubectl/KubectlCreateTest.groovy | 5 +- .../client/extended/kubectl/Kubectl.java | 52 ++++-- .../client/extended/kubectl/KubectlGet.java | 135 ++++++++++++++ .../extended/kubectl/KubectlGetTest.java | 176 ++++++++++++++++++ 5 files changed, 355 insertions(+), 18 deletions(-) create mode 100644 extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlGet.java create mode 100644 extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlGetTest.java diff --git a/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlApplyTest.groovy b/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlApplyTest.groovy index 3285c703aa..7b2ae04167 100644 --- a/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlApplyTest.groovy +++ b/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlApplyTest.groovy @@ -20,7 +20,10 @@ class KubectlApplyTest extends Specification { .metadata(new V1ObjectMeta().name("apply-foo"))) .execute() expect: - appliedNamespace != null + Kubectl.get(V1Namespace.class) + .apiClient(apiClient) + .name("apply-foo") + .execute() != null } } diff --git a/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlCreateTest.groovy b/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlCreateTest.groovy index 5cf07ed635..2d7d96165b 100644 --- a/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlCreateTest.groovy +++ b/e2e/src/test/groovy/io/kubernetes/client/e2e/kubectl/KubectlCreateTest.groovy @@ -18,7 +18,10 @@ class KubectlCreateTest extends Specification { .metadata(new V1ObjectMeta().name("create-foo"))) .execute() expect: - createdNamespace != null + Kubectl.get(V1Namespace.class) + .apiClient(apiClient) + .name("create-foo") + .execute() != null } } diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java index 698cdc65ee..f2ead2c25b 100644 --- a/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java @@ -28,6 +28,13 @@ * kubectl commands. */ public class Kubectl { + + /** Equivalent for `kubectl get` */ + public static KubectlGet get( + Class apiTypeClass) { + return new KubectlGet<>(apiTypeClass); + } + /** Equivalent for `kubectl drain` */ public static KubectlDrain drain() { return new KubectlDrain(); @@ -60,6 +67,7 @@ public static KubectlCreate create() { public static KubectlApply apply() { return new KubectlApply(); } + /** * Equivalent for `kubectl top` * @@ -198,15 +206,38 @@ protected void refreshDiscovery() throws KubectlException { } } - protected GenericKubernetesApi getGenericApi( - Class apiTypeClass) { + protected + GenericKubernetesApi getGenericApi( + Class apiTypeClass) throws KubectlException { + + // load list type class dynamically from class-loader + String apiListTypeClassName = apiTypeClass.getName() + "List"; + try { + Class apiTypeListClass; + apiTypeListClass = + (Class) + apiTypeClass.getClassLoader().loadClass(apiListTypeClassName); + return getGenericApi(apiTypeClass, apiTypeListClass); + } catch (ClassNotFoundException e) { + throw new KubectlException( + new StringBuilder() + .append("No such api list type class ") + .append(apiListTypeClassName) + .append(", consider explicitly load the class by apiListTypeClass()?") + .toString()); + } + } + + protected + GenericKubernetesApi getGenericApi( + Class apiTypeClass, Class apiListTypeClass) { GroupVersionResource groupVersionResource = ModelMapper.getGroupVersionResourceByClass(apiTypeClass); - GenericKubernetesApi api = + GenericKubernetesApi api = new GenericKubernetesApi<>( apiTypeClass, - KubernetesListObject.class, + apiListTypeClass, groupVersionResource.getGroup(), groupVersionResource.getVersion(), groupVersionResource.getResource(), @@ -241,18 +272,7 @@ public T name(String name) { } protected GenericKubernetesApi getGenericApi() { - GroupVersionResource groupVersionResource = - ModelMapper.getGroupVersionResourceByClass(apiTypeClass); - - GenericKubernetesApi api = - new GenericKubernetesApi<>( - apiTypeClass, - KubernetesListObject.class, - groupVersionResource.getGroup(), - groupVersionResource.getVersion(), - groupVersionResource.getResource(), - apiClient); - return api; + return getGenericApi(apiTypeClass, KubernetesListObject.class); } } diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlGet.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlGet.java new file mode 100644 index 0000000000..32a6012cda --- /dev/null +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlGet.java @@ -0,0 +1,135 @@ +/* +Copyright 2020 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.extended.kubectl.exception.KubectlException; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.util.List; +import org.apache.commons.lang.StringUtils; + +public class KubectlGet + extends Kubectl.ApiClientBuilder> + implements Kubectl.Executable> { + + private String namespace; + private ListOptions listOptions; + private Class apiTypeClass; + private Class apiTypeListClass; + + KubectlGet(Class apiTypeClass) { + this.apiTypeClass = apiTypeClass; + this.listOptions = new ListOptions(); + } + + public KubectlGet apiListTypeClass( + Class apiTypeListClass) { + this.apiTypeListClass = apiTypeListClass; + return this; + } + + public KubectlGet options(ListOptions listOptions) { + this.listOptions = listOptions; + return this; + } + + public KubectlGet namespace(String namespace) { + this.namespace = namespace; + return this; + } + + public KubectlGetSingle name(String name) { + return new KubectlGetSingle(name); + } + + @Override + public List execute() throws KubectlException { + GenericKubernetesApi api = + apiTypeListClass == null + ? getGenericApi(apiTypeClass) + : getGenericApi(apiTypeClass, apiTypeListClass); + try { + if (isNamespaced()) { + return (List) + api.list(namespace, listOptions) + .onFailure( + errorStatus -> { + throw new ApiException(errorStatus.toString()); + }) + .getObject() + .getItems(); + + } else { + return (List) + api.list(listOptions) + .onFailure( + errorStatus -> { + throw new ApiException(errorStatus.toString()); + }) + .getObject() + .getItems(); + } + } catch (ApiException e) { + throw new KubectlException(e); + } + } + + private boolean isNamespaced() { + return !StringUtils.isEmpty(namespace); + } + + public class KubectlGetSingle extends Kubectl.ResourceBuilder + implements Kubectl.Executable { + + private KubectlGetSingle(String name) { + super(KubectlGet.this.apiTypeClass); + KubectlGetSingle.this.name = name; + KubectlGetSingle.this.namespace = KubectlGet.this.namespace; + KubectlGetSingle.this.apiClient = KubectlGet.this.apiClient; + KubectlGetSingle.this.skipDiscovery = KubectlGet.this.skipDiscovery; + } + + private boolean isNamespaced() { + return !StringUtils.isEmpty(namespace); + } + + @Override + public ApiType execute() throws KubectlException { + GenericKubernetesApi api = + getGenericApi(KubectlGetSingle.this.apiTypeClass); + try { + if (isNamespaced()) { + return api.get(KubectlGetSingle.this.namespace, KubectlGetSingle.this.name) + .onFailure( + errorStatus -> { + throw new ApiException(errorStatus.toString()); + }) + .getObject(); + + } else { + return api.get(KubectlGetSingle.this.name) + .onFailure( + errorStatus -> { + throw new ApiException(errorStatus.toString()); + }) + .getObject(); + } + } catch (ApiException e) { + throw new KubectlException(e); + } + } + } +} diff --git a/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlGetTest.java b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlGetTest.java new file mode 100644 index 0000000000..17e8cefcc2 --- /dev/null +++ b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlGetTest.java @@ -0,0 +1,176 @@ +/* +Copyright 2020 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import io.kubernetes.client.extended.kubectl.exception.KubectlException; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.ModelMapper; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class KubectlGetTest { + + private ApiClient apiClient; + + @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + @Before + public void setup() throws IOException { + ModelMapper.addModelMap("", "v1", "Pod", "pods", true, V1Pod.class); + ModelMapper.addModelMap("", "v1", "Node", "nodes", false, V1Node.class); + apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build(); + } + + @Test + public void testGetAllNamespacePods() throws KubectlException { + V1PodList podList = + new V1PodList() + .items( + Arrays.asList( + new V1Pod().metadata(new V1ObjectMeta().namespace("default").name("foo1")), + new V1Pod().metadata(new V1ObjectMeta().namespace("default").name("foo2")))); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/pods")) + .willReturn( + aResponse().withStatus(200).withBody(apiClient.getJSON().serialize(podList)))); + + List pods = Kubectl.get(V1Pod.class).apiClient(apiClient).skipDiscovery().execute(); + + wireMockRule.verify(1, getRequestedFor(urlPathEqualTo("/api/v1/pods"))); + assertEquals(2, pods.size()); + } + + @Test + public void testGetDefaultNamespacePods() throws KubectlException { + V1PodList podList = + new V1PodList() + .items( + Arrays.asList( + new V1Pod().metadata(new V1ObjectMeta().namespace("default").name("foo1")), + new V1Pod().metadata(new V1ObjectMeta().namespace("default").name("foo2")))); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/namespaces/default/pods")) + .willReturn( + aResponse().withStatus(200).withBody(apiClient.getJSON().serialize(podList)))); + + List pods = + Kubectl.get(V1Pod.class) + .apiClient(apiClient) + .skipDiscovery() + .namespace("default") + .execute(); + + wireMockRule.verify(1, getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/pods"))); + assertEquals(2, pods.size()); + } + + @Test + public void testGetDefaultNamespaceOnePod() throws KubectlException { + V1Pod pod = new V1Pod().metadata(new V1ObjectMeta().namespace("default").name("foo1")); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/namespaces/default/pods/foo1")) + .willReturn(aResponse().withStatus(200).withBody(apiClient.getJSON().serialize(pod)))); + + Kubectl.get(V1Pod.class) + .apiClient(apiClient) + .skipDiscovery() + .namespace("default") + .name("foo1") + .execute(); + + Kubectl.get(V1Pod.class) + .apiClient(apiClient) + .skipDiscovery() + .name("foo1") + .namespace("default") + .execute(); + + wireMockRule.verify(2, getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/pods/foo1"))); + } + + @Test + public void testGetDefaultNamespaceOnePodForbiddenShouldThrowException() { + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/namespaces/default/pods/foo1")) + .willReturn( + aResponse() + .withStatus(403) + .withBody(apiClient.getJSON().serialize(new V1Status().code(403))))); + try { + V1Pod getPod = + Kubectl.get(V1Pod.class) + .apiClient(apiClient) + .skipDiscovery() + .namespace("default") // no namespace specified + .name("foo1") + .execute(); + } catch (KubectlException e) { + assertTrue(e.getCause() instanceof ApiException); + return; + } finally { + wireMockRule.verify( + 1, getRequestedFor(urlPathEqualTo("/api/v1/namespaces/default/pods/foo1"))); + } + fail(); + } + + @Test + public void testGetAllNodes() throws KubectlException { + V1NodeList nodeList = + new V1NodeList() + .items( + Arrays.asList( + new V1Node().metadata(new V1ObjectMeta().name("foo1")), + new V1Node().metadata(new V1ObjectMeta().name("foo2")))); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/nodes")) + .willReturn( + aResponse().withStatus(200).withBody(apiClient.getJSON().serialize(nodeList)))); + + List nodes = Kubectl.get(V1Node.class).apiClient(apiClient).skipDiscovery().execute(); + + wireMockRule.verify(1, getRequestedFor(urlPathEqualTo("/api/v1/nodes"))); + assertEquals(2, nodes.size()); + } + + @Test + public void testGetOneNode() throws KubectlException { + V1Node node = new V1Node().metadata(new V1ObjectMeta().name("foo1")); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1/nodes/foo1")) + .willReturn(aResponse().withStatus(200).withBody(apiClient.getJSON().serialize(node)))); + + V1Node getNode = + Kubectl.get(V1Node.class).apiClient(apiClient).skipDiscovery().name("foo1").execute(); + + wireMockRule.verify(1, getRequestedFor(urlPathEqualTo("/api/v1/nodes/foo1"))); + assertEquals(node, getNode); + } +}