Skip to content

Commit 6c0e250

Browse files
authored
Merge pull request #1244 from yue9944882/refactor/discover-gvr
Refactor(kubectl): Automatic GVR discovery
2 parents 90188d5 + 0056fe0 commit 6c0e250

File tree

12 files changed

+253
-311
lines changed

12 files changed

+253
-311
lines changed

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

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
package io.kubernetes.client.extended.kubectl;
1414

1515
import io.kubernetes.client.Discovery;
16-
import io.kubernetes.client.apimachinery.GroupVersionKind;
16+
import io.kubernetes.client.apimachinery.GroupVersionResource;
17+
import io.kubernetes.client.common.KubernetesListObject;
1718
import io.kubernetes.client.common.KubernetesObject;
1819
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
1920
import io.kubernetes.client.openapi.ApiClient;
2021
import io.kubernetes.client.openapi.ApiException;
2122
import io.kubernetes.client.openapi.Configuration;
2223
import io.kubernetes.client.util.ModelMapper;
23-
import java.util.Optional;
24-
import java.util.Set;
24+
import io.kubernetes.client.util.generic.GenericKubernetesApi;
2525

2626
/**
2727
* Kubectl provides a set of helper functions that has the same functionalities as corresponding
@@ -185,41 +185,51 @@ public T namespace(String namespace) {
185185

186186
abstract static class ApiClientBuilder<T extends ApiClientBuilder> {
187187
ApiClient apiClient = Configuration.getDefaultApiClient();
188+
boolean skipDiscovery = false;
188189

189-
protected Discovery.APIResource recognize(KubernetesObject object)
190-
throws KubectlException, ApiException {
191-
Discovery discovery = new Discovery(apiClient);
192-
193-
Set<Discovery.APIResource> apiResources = ModelMapper.refresh(discovery);
194-
195-
GroupVersionKind gvk = ModelMapper.getGroupVersionKindByClass(object.getClass());
196-
197-
Optional<Discovery.APIResource> apiResource =
198-
apiResources.stream()
199-
.filter(r -> r.getKind().equals(gvk.getKind()) && r.getGroup().equals(gvk.getGroup()))
200-
.findFirst();
201-
if (!apiResource.isPresent()) {
202-
throw new KubectlException(
203-
"Cannot recognize such kubernetes resource from api-discovery: " + gvk.toString());
190+
protected void refreshDiscovery() throws KubectlException {
191+
if (skipDiscovery) {
192+
return;
193+
}
194+
try {
195+
ModelMapper.refresh(new Discovery(apiClient));
196+
} catch (ApiException e) {
197+
throw new KubectlException(e);
204198
}
199+
}
205200

206-
return apiResource.get();
201+
protected GenericKubernetesApi<? extends KubernetesObject, KubernetesListObject> getGenericApi(
202+
Class<? extends KubernetesObject> apiTypeClass) {
203+
GroupVersionResource groupVersionResource =
204+
ModelMapper.getGroupVersionResourceByClass(apiTypeClass);
205+
206+
GenericKubernetesApi<? extends KubernetesObject, KubernetesListObject> api =
207+
new GenericKubernetesApi<>(
208+
apiTypeClass,
209+
KubernetesListObject.class,
210+
groupVersionResource.getGroup(),
211+
groupVersionResource.getVersion(),
212+
groupVersionResource.getResource(),
213+
apiClient);
214+
return api;
207215
}
208216

209217
public T apiClient(ApiClient apiClient) {
210218
this.apiClient = apiClient;
211219
return (T) this;
212220
}
221+
222+
public T skipDiscovery() {
223+
this.skipDiscovery = true;
224+
return (T) this;
225+
}
213226
}
214227

215228
abstract static class ResourceBuilder<
216229
ApiType extends KubernetesObject, T extends ResourceBuilder<ApiType, T>>
217230
extends NamespacedApiClientBuilder<T> {
218231
final Class<ApiType> apiTypeClass;
219232
String name;
220-
String apiGroup;
221-
String apiVersion;
222-
String resourceNamePlural;
223233

224234
ResourceBuilder(Class<ApiType> apiTypeClass) {
225235
this.apiTypeClass = apiTypeClass;
@@ -230,19 +240,19 @@ public T name(String name) {
230240
return (T) this;
231241
}
232242

233-
public T apiGroup(String apiGroup) {
234-
this.apiGroup = apiGroup;
235-
return (T) this;
236-
}
237-
238-
public T apiVersion(String apiVersion) {
239-
this.apiVersion = apiVersion;
240-
return (T) this;
241-
}
242-
243-
public T resourceNamePlural(String resourceNamePlural) {
244-
this.resourceNamePlural = resourceNamePlural;
245-
return (T) this;
243+
protected GenericKubernetesApi<ApiType, KubernetesListObject> getGenericApi() {
244+
GroupVersionResource groupVersionResource =
245+
ModelMapper.getGroupVersionResourceByClass(apiTypeClass);
246+
247+
GenericKubernetesApi<ApiType, KubernetesListObject> api =
248+
new GenericKubernetesApi<>(
249+
apiTypeClass,
250+
KubernetesListObject.class,
251+
groupVersionResource.getGroup(),
252+
groupVersionResource.getVersion(),
253+
groupVersionResource.getResource(),
254+
apiClient);
255+
return api;
246256
}
247257
}
248258

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

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
*/
1313
package io.kubernetes.client.extended.kubectl;
1414

15-
import io.kubernetes.client.common.KubernetesListObject;
1615
import io.kubernetes.client.common.KubernetesObject;
1716
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
17+
import io.kubernetes.client.openapi.ApiException;
18+
import io.kubernetes.client.util.ModelMapper;
1819
import io.kubernetes.client.util.annotations.Annotations;
19-
import io.kubernetes.client.util.generic.GenericKubernetesApi;
2020
import io.kubernetes.client.util.generic.KubernetesApiResponse;
2121
import java.util.HashMap;
2222
import java.util.Map;
@@ -41,54 +41,63 @@ public KubectlAnnotate<ApiType> addAnnotation(String key, String value) {
4141
@Override
4242
public ApiType execute() throws KubectlException {
4343
verifyArguments();
44-
GenericKubernetesApi<ApiType, KubernetesListObject> api =
45-
new GenericKubernetesApi<>(
46-
apiTypeClass,
47-
KubernetesListObject.class,
48-
apiGroup,
49-
apiVersion,
50-
resourceNamePlural,
51-
apiClient);
44+
refreshDiscovery();
5245

53-
try {
54-
final KubernetesApiResponse<ApiType> getResponse;
55-
if (isNamespaced()) {
56-
getResponse = api.get(namespace, name);
57-
} else {
58-
getResponse = api.get(name);
46+
final ApiType currentObj;
47+
if (isNamespaced(apiTypeClass)) {
48+
try {
49+
currentObj =
50+
getGenericApi()
51+
.get(namespace, name)
52+
.onFailure(
53+
errorStatus -> {
54+
throw new ApiException(errorStatus.toString());
55+
})
56+
.getObject();
57+
} catch (ApiException e) {
58+
throw new KubectlException(e);
5959
}
60-
if (!getResponse.isSuccess()) {
61-
throw new KubectlException(getResponse.getStatus());
60+
} else {
61+
try {
62+
currentObj =
63+
getGenericApi()
64+
.get(name)
65+
.onFailure(
66+
errorStatus -> {
67+
throw new ApiException(errorStatus.toString());
68+
})
69+
.getObject();
70+
} catch (ApiException e) {
71+
throw new KubectlException(e);
6272
}
63-
ApiType obj = getResponse.getObject();
73+
}
6474

65-
Annotations.addAnnotations(obj, addingAnnotations);
75+
Annotations.addAnnotations(currentObj, addingAnnotations);
6676

67-
final KubernetesApiResponse<ApiType> updateResponse;
68-
updateResponse = api.update(obj);
69-
if (!updateResponse.isSuccess()) {
70-
throw new KubectlException(updateResponse.getStatus());
71-
}
72-
return updateResponse.getObject();
73-
} catch (Throwable t) {
74-
throw new KubectlException(t);
77+
final KubernetesApiResponse<ApiType> updateResponse;
78+
try {
79+
return getGenericApi()
80+
.update(currentObj)
81+
.onFailure(
82+
errorStatus -> {
83+
throw new ApiException(errorStatus.toString());
84+
})
85+
.getObject();
86+
} catch (ApiException e) {
87+
throw new KubectlException(e);
7588
}
7689
}
7790

78-
public boolean isNamespaced() {
79-
return !StringUtils.isEmpty(namespace);
91+
public boolean isNamespaced(Class<ApiType> apiTypeClass) {
92+
Boolean isNamespaced = ModelMapper.isNamespaced(apiTypeClass);
93+
if (isNamespaced == null) { // unknown
94+
return false;
95+
}
96+
97+
return isNamespaced || !StringUtils.isEmpty(namespace);
8098
}
8199

82100
private void verifyArguments() throws KubectlException {
83-
if (null == apiGroup) {
84-
throw new KubectlException("missing apiGroup argument");
85-
}
86-
if (null == apiVersion) {
87-
throw new KubectlException("missing apiVersion argument");
88-
}
89-
if (null == resourceNamePlural) {
90-
throw new KubectlException("missing resourceNamePlural argument");
91-
}
92101
if (null == name) {
93102
throw new KubectlException("missing name argument");
94103
}

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

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313
package io.kubernetes.client.extended.kubectl;
1414

1515
import com.google.common.base.Strings;
16-
import io.kubernetes.client.Discovery;
17-
import io.kubernetes.client.apimachinery.GroupVersion;
1816
import io.kubernetes.client.common.KubernetesListObject;
1917
import io.kubernetes.client.common.KubernetesObject;
2018
import io.kubernetes.client.custom.V1Patch;
2119
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
2220
import io.kubernetes.client.openapi.ApiException;
21+
import io.kubernetes.client.util.ModelMapper;
2322
import io.kubernetes.client.util.Namespaces;
2423
import io.kubernetes.client.util.generic.GenericKubernetesApi;
2524
import io.kubernetes.client.util.generic.KubernetesApiResponse;
@@ -67,50 +66,55 @@ public KubernetesObject execute() throws KubectlException {
6766
}
6867

6968
private KubernetesObject executeServerSideApply() throws KubectlException {
70-
try {
71-
Discovery.APIResource apiResource = recognize(this.targetObj);
72-
GroupVersion gv = GroupVersion.parse(this.targetObj);
69+
refreshDiscovery();
7370

74-
GenericKubernetesApi<KubernetesObject, KubernetesListObject> api =
75-
new GenericKubernetesApi(
76-
targetObj.getClass(),
77-
KubernetesListObject.class,
78-
gv.getGroup(),
79-
gv.getVersion(),
80-
apiResource.getResourcePlural(),
81-
apiClient);
71+
GenericKubernetesApi<KubernetesObject, KubernetesListObject> api =
72+
(GenericKubernetesApi<KubernetesObject, KubernetesListObject>)
73+
getGenericApi(this.targetObj.getClass());
8274

83-
PatchOptions patchOptions = new PatchOptions();
84-
patchOptions.setForce(this.forceConflict);
85-
patchOptions.setFieldManager(this.fieldManager);
75+
PatchOptions patchOptions = new PatchOptions();
76+
patchOptions.setForce(this.forceConflict);
77+
patchOptions.setFieldManager(this.fieldManager);
8678

87-
if (apiResource.getNamespaced()) {
88-
String targetNamespace =
89-
namespace != null
90-
? namespace
91-
: Strings.isNullOrEmpty(targetObj.getMetadata().getNamespace())
92-
? Namespaces.NAMESPACE_DEFAULT
93-
: targetObj.getMetadata().getNamespace();
79+
if (ModelMapper.isNamespaced(this.targetObj.getClass())) {
80+
String targetNamespace =
81+
namespace != null
82+
? namespace
83+
: Strings.isNullOrEmpty(targetObj.getMetadata().getNamespace())
84+
? Namespaces.NAMESPACE_DEFAULT
85+
: targetObj.getMetadata().getNamespace();
9486

95-
KubernetesApiResponse<KubernetesObject> response =
96-
api.patch(
87+
KubernetesApiResponse<KubernetesObject> response = null;
88+
try {
89+
return api.patch(
9790
targetNamespace,
9891
targetObj.getMetadata().getName(),
9992
V1Patch.PATCH_FORMAT_APPLY_YAML,
10093
new V1Patch(apiClient.getJSON().serialize(targetObj)),
101-
patchOptions);
102-
return response.getObject();
103-
} else {
104-
KubernetesApiResponse<KubernetesObject> response =
105-
api.patch(
94+
patchOptions)
95+
.onFailure(
96+
errorStatus -> {
97+
throw new ApiException(errorStatus.toString());
98+
})
99+
.getObject();
100+
} catch (ApiException e) {
101+
throw new KubectlException(e);
102+
}
103+
} else {
104+
try {
105+
return api.patch(
106106
targetObj.getMetadata().getName(),
107107
V1Patch.PATCH_FORMAT_APPLY_YAML,
108108
new V1Patch(apiClient.getJSON().serialize(targetObj)),
109-
patchOptions);
110-
return response.getObject();
109+
patchOptions)
110+
.onFailure(
111+
errorStatus -> {
112+
throw new ApiException(errorStatus.toString());
113+
})
114+
.getObject();
115+
} catch (ApiException e) {
116+
throw new KubectlException(e);
111117
}
112-
} catch (ApiException e) {
113-
throw new KubectlException(e);
114118
}
115119
}
116120
}

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

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
import io.kubernetes.client.custom.V1Patch;
1616
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
1717
import io.kubernetes.client.openapi.ApiException;
18-
import io.kubernetes.client.openapi.apis.CoreV1Api;
1918
import io.kubernetes.client.openapi.models.V1Node;
20-
import io.kubernetes.client.util.PatchUtils;
2119

2220
public class KubectlCordon extends Kubectl.ResourceAndContainerBuilder<V1Node, KubectlCordon>
2321
implements Kubectl.Executable<V1Node> {
@@ -36,28 +34,21 @@ public class KubectlCordon extends Kubectl.ResourceAndContainerBuilder<V1Node, K
3634

3735
@Override
3836
public V1Node execute() throws KubectlException {
39-
return performCordon(new CoreV1Api(apiClient));
37+
return performCordon();
4038
}
4139

42-
protected V1Node performCordon(CoreV1Api api) throws KubectlException {
40+
protected V1Node performCordon() throws KubectlException {
4341
String patch = this.cordon ? CORDON_PATCH_STR : UNCORDON_PATCH_STR;
44-
4542
try {
46-
return PatchUtils.patch(
47-
V1Node.class,
48-
() ->
49-
api.patchNodeCall(
50-
name,
51-
new V1Patch(patch),
52-
null,
53-
null,
54-
null, // field-manager is optional
55-
null,
56-
null),
57-
V1Patch.PATCH_FORMAT_JSON_PATCH,
58-
apiClient);
59-
} catch (ApiException ex) {
60-
throw new KubectlException(ex);
43+
return getGenericApi()
44+
.patch(name, V1Patch.PATCH_FORMAT_JSON_PATCH, new V1Patch(patch))
45+
.onFailure(
46+
errorStatus -> {
47+
throw new ApiException(errorStatus.toString());
48+
})
49+
.getObject();
50+
} catch (ApiException e) {
51+
throw new KubectlException(e);
6152
}
6253
}
6354
}

0 commit comments

Comments
 (0)