Skip to content

Commit 7da36ab

Browse files
authored
Merge pull request #1147 from yue9944882/kubectl-api-resource
kubectl(api-resources): api discovery implementation
2 parents 3419b73 + cbf0b84 commit 7da36ab

File tree

5 files changed

+419
-0
lines changed

5 files changed

+419
-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: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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.Optional;
32+
import java.util.Set;
33+
import java.util.stream.Collectors;
34+
import okhttp3.Call;
35+
36+
public class Discovery {
37+
38+
private final ApiClient apiClient;
39+
40+
public Discovery() {
41+
this(Configuration.getDefaultApiClient());
42+
}
43+
44+
public Discovery(ApiClient apiClient) {
45+
this.apiClient = apiClient;
46+
}
47+
48+
public Set<APIResource> findAll() throws ApiException {
49+
Set<APIResource> allResources = new HashSet<>();
50+
for (String version : legacyCoreApi().getVersions()) {
51+
allResources.addAll(findAll("", Arrays.asList(version), version, "/api/" + version));
52+
}
53+
for (V1APIGroup group : groupDiscovery("/apis").getGroups()) {
54+
allResources.addAll(
55+
findAll(
56+
group.getName(),
57+
group.getVersions().stream().map(v -> v.getVersion()).collect(Collectors.toList()),
58+
group.getPreferredVersion().getVersion()));
59+
}
60+
return allResources;
61+
}
62+
63+
public Set<APIResource> findAll(String group, List<String> versions, String preferredVersion)
64+
throws ApiException {
65+
return findAll(group, versions, preferredVersion, "/apis/" + group + "/" + preferredVersion);
66+
}
67+
68+
public Set<APIResource> findAll(
69+
String group, List<String> versions, String preferredVersion, String path)
70+
throws ApiException {
71+
V1APIResourceList resourceList = resourceDiscovery(path);
72+
return groupResourcesByName(group, versions, preferredVersion, resourceList);
73+
}
74+
75+
public Set<APIResource> groupResourcesByName(
76+
String group,
77+
List<String> versions,
78+
String preferredVersion,
79+
V1APIResourceList resourceList) {
80+
// parse raw discovery responses to APIResource for better readability
81+
Set<APIResource> resources =
82+
resourceList.getResources().stream()
83+
.filter(r -> !getSubResourceNameIfPossible(r.getName()).isPresent())
84+
.map(
85+
r ->
86+
new APIResource(
87+
group,
88+
versions,
89+
preferredVersion,
90+
r.getKind(),
91+
r.getNamespaced(),
92+
r.getName(),
93+
r.getSingularName()))
94+
.collect(Collectors.toSet());
95+
96+
// wiring up connections between major-resource and sub-resources
97+
Map<String, Set<String>> subResources = manageRelationFromResourceToSubResources(resourceList);
98+
resources.stream()
99+
.forEach(
100+
r -> {
101+
if (subResources.containsKey(r.getResourcePlural())) {
102+
r.subResources.addAll(subResources.get(r.getResourcePlural()));
103+
}
104+
});
105+
return resources;
106+
}
107+
108+
private Map<String, Set<String>> manageRelationFromResourceToSubResources(
109+
V1APIResourceList resourceList) {
110+
Map<String, Set<String>> subResources = new HashMap<>();
111+
resourceList.getResources().stream()
112+
.forEach(r -> subResources.put(r.getName(), new HashSet<>()));
113+
resourceList.getResources().stream()
114+
.forEach(
115+
r -> {
116+
getSubResourceNameIfPossible(r.getName())
117+
.ifPresent(
118+
subResourceName -> {
119+
subResources.get(getMajorResourceName(r.getName())).add(subResourceName);
120+
});
121+
});
122+
return subResources;
123+
}
124+
125+
private String getMajorResourceName(String discoveredResourceName) {
126+
String[] parts = discoveredResourceName.split("/", 2);
127+
return parts[0];
128+
}
129+
130+
private Optional<String> getSubResourceNameIfPossible(String discoveredResourceName) {
131+
boolean isSubResource = discoveredResourceName.contains("/");
132+
if (!isSubResource) {
133+
return Optional.empty();
134+
}
135+
String[] parts = discoveredResourceName.split("/", 2);
136+
return Optional.of(parts[1]);
137+
}
138+
139+
public V1APIVersions legacyCoreApi() throws ApiException {
140+
return versionDiscovery("/api");
141+
}
142+
143+
public V1APIGroupList groupDiscovery(String path) throws ApiException {
144+
return get(V1APIGroupList.class, path);
145+
}
146+
147+
public V1APIVersions versionDiscovery(String path) throws ApiException {
148+
return get(V1APIVersions.class, path);
149+
}
150+
151+
public V1APIResourceList resourceDiscovery(String path) throws ApiException {
152+
return get(V1APIResourceList.class, path);
153+
}
154+
155+
private <T> T get(Class<T> returnTypeClass, String urlPath) throws ApiException {
156+
Map<String, String> headers = new HashMap<>();
157+
headers.put("Content-Type", "application/json");
158+
Call call =
159+
apiClient.buildCall(
160+
urlPath,
161+
"GET",
162+
null,
163+
null,
164+
null,
165+
headers,
166+
Collections.emptyMap(),
167+
Collections.emptyMap(),
168+
new String[] {"BearerToken"},
169+
null);
170+
ApiResponse<T> resourceList = apiClient.execute(call, returnTypeClass);
171+
return resourceList.getData();
172+
}
173+
174+
public static class APIResource {
175+
176+
private final String group;
177+
private final String kind;
178+
private final List<String> versions;
179+
private final String preferredVersion;
180+
private final Boolean isNamespaced;
181+
private final String resourcePlural;
182+
private final String resourceSingular;
183+
private final List<String> subResources;
184+
185+
public APIResource(
186+
String group,
187+
List<String> versions,
188+
String preferredVersion,
189+
String kind,
190+
Boolean isNamespaced,
191+
String resourcePlural,
192+
String resourceSingular) {
193+
this.group = group;
194+
this.versions = versions;
195+
this.preferredVersion = preferredVersion;
196+
this.kind = kind;
197+
this.isNamespaced = isNamespaced;
198+
this.resourcePlural = resourcePlural;
199+
this.resourceSingular = resourceSingular;
200+
this.subResources = new ArrayList<>();
201+
}
202+
203+
public String getGroup() {
204+
return group;
205+
}
206+
207+
public List<String> getVersions() {
208+
return versions;
209+
}
210+
211+
public String getPreferredVersion() {
212+
return preferredVersion;
213+
}
214+
215+
public String getKind() {
216+
return kind;
217+
}
218+
219+
public Boolean getNamespaced() {
220+
return isNamespaced;
221+
}
222+
223+
public String getResourcePlural() {
224+
return resourcePlural;
225+
}
226+
227+
public String getResourceSingular() {
228+
return resourceSingular;
229+
}
230+
231+
public List<String> getSubResources() {
232+
return subResources;
233+
}
234+
235+
@Override
236+
public boolean equals(Object o) {
237+
if (this == o) return true;
238+
if (o == null || getClass() != o.getClass()) return false;
239+
APIResource that = (APIResource) o;
240+
return Objects.equal(group, that.group)
241+
&& Objects.equal(kind, that.kind)
242+
&& Objects.equal(versions, that.versions)
243+
&& Objects.equal(preferredVersion, that.preferredVersion)
244+
&& Objects.equal(isNamespaced, that.isNamespaced)
245+
&& Objects.equal(resourcePlural, that.resourcePlural)
246+
&& Objects.equal(resourceSingular, that.resourceSingular)
247+
&& Objects.equal(subResources, that.subResources);
248+
}
249+
250+
@Override
251+
public int hashCode() {
252+
return Objects.hashCode(
253+
group,
254+
kind,
255+
versions,
256+
preferredVersion,
257+
isNamespaced,
258+
resourcePlural,
259+
resourceSingular,
260+
subResources);
261+
}
262+
}
263+
}

0 commit comments

Comments
 (0)