Skip to content

Commit e95901f

Browse files
committed
api discovery and kubectl api-resources implementation
1 parent dc681e8 commit e95901f

File tree

5 files changed

+350
-0
lines changed

5 files changed

+350
-0
lines changed

examples/src/main/java/io/kubernetes/client/examples/KubectlExample.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package io.kubernetes.client.examples;
1414

15+
import static io.kubernetes.client.extended.kubectl.Kubectl.apiResources;
1516
import static io.kubernetes.client.extended.kubectl.Kubectl.exec;
1617
import static io.kubernetes.client.extended.kubectl.Kubectl.label;
1718
import static io.kubernetes.client.extended.kubectl.Kubectl.log;
@@ -139,6 +140,14 @@ public static void main(String[] args)
139140
.command(command)
140141
.container(cli.getOptionValue("c", ""));
141142
System.exit(e.execute());
143+
case "api-resources":
144+
apiResources(client).execute().stream()
145+
.forEach(
146+
r ->
147+
System.out.printf(
148+
"%s\t\t%s\t\t%s\t\t%s\n",
149+
r.getResourcePlural(), r.getGroup(), r.getKind(), r.getNamespaced()));
150+
System.exit(0);
142151
default:
143152
System.out.println("Unknown verb: " + verb);
144153
System.exit(-1);

extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ public static KubectlPortForward portforward(ApiClient apiClient) {
163163
return new KubectlPortForward(apiClient);
164164
}
165165

166+
public static KubectlApiResources apiResources() {
167+
return apiResources(Configuration.getDefaultApiClient());
168+
}
169+
170+
public static KubectlApiResources apiResources(ApiClient apiClient) {
171+
return new KubectlApiResources(apiClient);
172+
}
173+
166174
/**
167175
* Executable executes a kubectl helper.
168176
*
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client.extended.kubectl;
14+
15+
import io.kubernetes.client.Discovery;
16+
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
17+
import io.kubernetes.client.openapi.ApiClient;
18+
import io.kubernetes.client.openapi.ApiException;
19+
import java.util.Set;
20+
21+
public class KubectlApiResources implements Kubectl.Executable<Set<Discovery.APIResource>> {
22+
23+
private final Discovery discovery;
24+
25+
KubectlApiResources(ApiClient apiClient) {
26+
this.discovery = new Discovery(apiClient);
27+
}
28+
29+
@Override
30+
public Set<Discovery.APIResource> execute() throws KubectlException {
31+
try {
32+
return this.discovery.findAll();
33+
} catch (ApiException e) {
34+
throw new KubectlException(e);
35+
}
36+
}
37+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client;
14+
15+
import com.google.common.base.Objects;
16+
import io.kubernetes.client.openapi.ApiClient;
17+
import io.kubernetes.client.openapi.ApiException;
18+
import io.kubernetes.client.openapi.ApiResponse;
19+
import io.kubernetes.client.openapi.Configuration;
20+
import io.kubernetes.client.openapi.models.V1APIGroup;
21+
import io.kubernetes.client.openapi.models.V1APIGroupList;
22+
import io.kubernetes.client.openapi.models.V1APIResourceList;
23+
import io.kubernetes.client.openapi.models.V1APIVersions;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.Set;
32+
import java.util.stream.Collectors;
33+
import okhttp3.Call;
34+
35+
public class Discovery {
36+
37+
private final ApiClient apiClient;
38+
39+
public Discovery() {
40+
this(Configuration.getDefaultApiClient());
41+
}
42+
43+
public Discovery(ApiClient apiClient) {
44+
this.apiClient = apiClient;
45+
}
46+
47+
public Set<APIResource> findAll() throws ApiException {
48+
Set<APIResource> allResources = new HashSet<>();
49+
for (String version : legacyCoreApi().getVersions()) {
50+
allResources.addAll(findAll("", Arrays.asList(version), version, "/api/" + version));
51+
}
52+
for (V1APIGroup group : groupDiscovery("/apis").getGroups()) {
53+
allResources.addAll(
54+
findAll(
55+
group.getName(),
56+
group.getVersions().stream().map(v -> v.getVersion()).collect(Collectors.toList()),
57+
group.getPreferredVersion().getVersion()));
58+
}
59+
return allResources;
60+
}
61+
62+
public Set<APIResource> findAll(String group, List<String> versions, String preferredVersion)
63+
throws ApiException {
64+
return findAll(group, versions, preferredVersion, "/apis/" + group + "/" + preferredVersion);
65+
}
66+
67+
public Set<APIResource> findAll(
68+
String group, List<String> versions, String preferredVersion, String path)
69+
throws ApiException {
70+
V1APIResourceList resourceList = resourceDiscovery(path);
71+
return sort(group, versions, preferredVersion, resourceList);
72+
}
73+
74+
private Set<APIResource> sort(
75+
String group, List<String> versions, String preferredVersion, V1APIResourceList resourceList)
76+
throws ApiException {
77+
Map<String, Set<String>> subResources = new HashMap<>();
78+
Set<APIResource> resources =
79+
resourceList.getResources().stream()
80+
.filter(
81+
r -> {
82+
boolean isSubResource = r.getName().contains("/");
83+
if (isSubResource) {
84+
String[] parts = r.getName().split("/", 2);
85+
if (!subResources.containsKey(parts[0])) {
86+
subResources.put(parts[0], new HashSet<>());
87+
}
88+
subResources.get(parts[0]).add(parts[1]);
89+
}
90+
return !isSubResource;
91+
})
92+
.map(
93+
r ->
94+
new APIResource(
95+
group,
96+
versions,
97+
preferredVersion,
98+
r.getKind(),
99+
r.getNamespaced(),
100+
r.getName(),
101+
r.getSingularName()))
102+
.collect(Collectors.toSet());
103+
resources.stream()
104+
.forEach(
105+
r -> {
106+
if (subResources.containsKey(r.getResourcePlural())) {
107+
r.subResources.addAll(subResources.get(r.getResourcePlural()));
108+
}
109+
});
110+
return resources;
111+
}
112+
113+
public V1APIVersions legacyCoreApi() throws ApiException {
114+
return versionDiscovery("/api");
115+
}
116+
117+
public V1APIGroupList groupDiscovery(String path) throws ApiException {
118+
return get(V1APIGroupList.class, path);
119+
}
120+
121+
public V1APIVersions versionDiscovery(String path) throws ApiException {
122+
return get(V1APIVersions.class, path);
123+
}
124+
125+
public V1APIResourceList resourceDiscovery(String path) throws ApiException {
126+
return get(V1APIResourceList.class, path);
127+
}
128+
129+
private <T> T get(Class<T> returnTypeClass, String urlPath) throws ApiException {
130+
Map<String, String> headers = new HashMap<>();
131+
headers.put("Content-Type", "application/json");
132+
Call call =
133+
apiClient.buildCall(
134+
urlPath,
135+
"GET",
136+
null,
137+
null,
138+
null,
139+
headers,
140+
Collections.emptyMap(),
141+
Collections.emptyMap(),
142+
new String[] {"BearerToken"},
143+
null);
144+
ApiResponse<T> resourceList = apiClient.execute(call, returnTypeClass);
145+
return resourceList.getData();
146+
}
147+
148+
public static class APIResource {
149+
150+
private final String group;
151+
private final String kind;
152+
private final List<String> versions;
153+
private final String preferredVersion;
154+
private final Boolean isNamespaced;
155+
private final String resourcePlural;
156+
private final String resourceSingular;
157+
private final List<String> subResources;
158+
159+
public APIResource(
160+
String group,
161+
List<String> versions,
162+
String preferredVersion,
163+
String kind,
164+
Boolean isNamespaced,
165+
String resourcePlural,
166+
String resourceSingular) {
167+
this.group = group;
168+
this.versions = versions;
169+
this.preferredVersion = preferredVersion;
170+
this.kind = kind;
171+
this.isNamespaced = isNamespaced;
172+
this.resourcePlural = resourcePlural;
173+
this.resourceSingular = resourceSingular;
174+
this.subResources = new ArrayList<>();
175+
}
176+
177+
public String getGroup() {
178+
return group;
179+
}
180+
181+
public List<String> getVersions() {
182+
return versions;
183+
}
184+
185+
public String getPreferredVersion() {
186+
return preferredVersion;
187+
}
188+
189+
public String getKind() {
190+
return kind;
191+
}
192+
193+
public Boolean getNamespaced() {
194+
return isNamespaced;
195+
}
196+
197+
public String getResourcePlural() {
198+
return resourcePlural;
199+
}
200+
201+
public String getResourceSingular() {
202+
return resourceSingular;
203+
}
204+
205+
public List<String> getSubResources() {
206+
return subResources;
207+
}
208+
209+
@Override
210+
public boolean equals(Object o) {
211+
if (this == o) return true;
212+
if (o == null || getClass() != o.getClass()) return false;
213+
APIResource that = (APIResource) o;
214+
return Objects.equal(group, that.group)
215+
&& Objects.equal(kind, that.kind)
216+
&& Objects.equal(versions, that.versions)
217+
&& Objects.equal(preferredVersion, that.preferredVersion)
218+
&& Objects.equal(isNamespaced, that.isNamespaced)
219+
&& Objects.equal(resourcePlural, that.resourcePlural)
220+
&& Objects.equal(resourceSingular, that.resourceSingular)
221+
&& Objects.equal(subResources, that.subResources);
222+
}
223+
224+
@Override
225+
public int hashCode() {
226+
return Objects.hashCode(
227+
group,
228+
kind,
229+
versions,
230+
preferredVersion,
231+
isNamespaced,
232+
resourcePlural,
233+
resourceSingular,
234+
subResources);
235+
}
236+
}
237+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client;
14+
15+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
16+
import static org.junit.Assert.*;
17+
18+
import com.github.tomakehurst.wiremock.junit.WireMockRule;
19+
import io.kubernetes.client.openapi.ApiClient;
20+
import io.kubernetes.client.openapi.ApiException;
21+
import io.kubernetes.client.openapi.models.V1APIVersions;
22+
import io.kubernetes.client.util.ClientBuilder;
23+
import java.io.IOException;
24+
import java.util.Arrays;
25+
import org.junit.Before;
26+
import org.junit.Rule;
27+
import org.junit.Test;
28+
29+
public class DiscoveryTest {
30+
31+
private ApiClient apiClient;
32+
33+
private static final int PORT = 8089;
34+
35+
@Rule public WireMockRule wireMockRule = new WireMockRule(PORT);
36+
37+
@Before
38+
public void setup() throws IOException {
39+
apiClient = new ClientBuilder().setBasePath("http://localhost:" + PORT).build();
40+
}
41+
42+
@Test
43+
public void testDiscoveryRequest() throws ApiException {
44+
wireMockRule.stubFor(
45+
get(urlPathEqualTo("/foo"))
46+
.willReturn(
47+
aResponse()
48+
.withStatus(200)
49+
.withHeader("Content-Type", "application/json")
50+
.withBody(
51+
apiClient
52+
.getJSON()
53+
.serialize(new V1APIVersions().versions(Arrays.asList("v1", "v2"))))));
54+
Discovery discovery = new Discovery(apiClient);
55+
V1APIVersions versions = discovery.versionDiscovery("/foo");
56+
verify(1, getRequestedFor(urlPathEqualTo("/foo")));
57+
assertEquals(2, versions.getVersions().size());
58+
}
59+
}

0 commit comments

Comments
 (0)