Skip to content

Commit 6b43ab6

Browse files
authored
Merge pull request #1135 from brendandburns/kubectl
Clean up/refactor kubectl to share some code and add kubectl exec.
2 parents e2b1c7e + c5207c7 commit 6b43ab6

File tree

6 files changed

+167
-46
lines changed

6 files changed

+167
-46
lines changed

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

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

15+
import static io.kubernetes.client.extended.kubectl.Kubectl.exec;
1516
import static io.kubernetes.client.extended.kubectl.Kubectl.label;
1617
import static io.kubernetes.client.extended.kubectl.Kubectl.scale;
1718
import static io.kubernetes.client.extended.kubectl.Kubectl.version;
1819

1920
import io.kubernetes.client.common.KubernetesObject;
21+
import io.kubernetes.client.extended.kubectl.KubectlExec;
2022
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
2123
import io.kubernetes.client.openapi.ApiClient;
2224
import io.kubernetes.client.openapi.models.V1Deployment;
@@ -25,6 +27,7 @@
2527
import io.kubernetes.client.openapi.models.V1ReplicationController;
2628
import io.kubernetes.client.openapi.models.V1Service;
2729
import io.kubernetes.client.util.Config;
30+
import java.util.Arrays;
2831
import org.apache.commons.cli.CommandLine;
2932
import org.apache.commons.cli.DefaultParser;
3033
import org.apache.commons.cli.Option;
@@ -68,6 +71,7 @@ public static void main(String[] args)
6871
Options options = new Options();
6972
options.addOption(new Option("n", "namespace", true, "The namespace for the resource"));
7073
options.addOption(new Option("r", "replicas", true, "The number of replicas to scale to"));
74+
options.addOption(new Option("c", "container", true, "The container in a pod to connect to"));
7175
DefaultParser parser = new DefaultParser();
7276
CommandLine cli = parser.parse(options, args);
7377

@@ -104,6 +108,13 @@ public static void main(String[] args)
104108
}
105109
label(client, clazz).namespace(ns).name(name).addLabel(labelKey, labelValue);
106110
System.exit(0);
111+
case "exec":
112+
name = args[1];
113+
String[] command = Arrays.copyOfRange(args, 1, args.length);
114+
KubectlExec e =
115+
exec(client).namespace(ns).name(name).container(cli.getOptionValue("c", ""));
116+
e.execute();
117+
System.exit(e.exitCode());
107118
default:
108119
System.out.println("Unknown verb: " + verb);
109120
System.exit(-1);

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

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,26 @@ public static <ApiType extends KubernetesObject> KubectlScale<ApiType> scale(
9292
return new KubectlScale<>(apiClient, apiTypeClass);
9393
}
9494

95+
/**
96+
* Equivalent for `kubectl exec`
97+
*
98+
* @param apiClient The api client instance
99+
* @return the kubectl exec operator
100+
*/
101+
public static KubectlExec exec(ApiClient apiClient) {
102+
return new KubectlExec(apiClient);
103+
}
104+
105+
/**
106+
* Equivalent for `kubectl exec`
107+
*
108+
* @param apiClient The api client instance
109+
* @return the kubectl exec operator
110+
*/
111+
public static KubectlExec exec() {
112+
return exec(Configuration.getDefaultApiClient());
113+
}
114+
95115
/**
96116
* Executable executes a kubectl helper.
97117
*
@@ -107,4 +127,40 @@ public static interface Executable<OUTPUT> {
107127
*/
108128
OUTPUT execute() throws KubectlException;
109129
}
130+
131+
abstract static class ResourceBuilder<T extends ResourceBuilder<T>> {
132+
final ApiClient apiClient;
133+
final Class<?> apiTypeClass;
134+
String namespace;
135+
String name;
136+
137+
ResourceBuilder(ApiClient client, Class<?> apiTypeClass) {
138+
this.apiClient = client;
139+
this.apiTypeClass = apiTypeClass;
140+
}
141+
142+
public T name(String name) {
143+
this.name = name;
144+
return (T) this;
145+
}
146+
147+
public T namespace(String namespace) {
148+
this.namespace = namespace;
149+
return (T) this;
150+
}
151+
}
152+
153+
abstract static class ResourceAndContainerBuilder<T extends ResourceAndContainerBuilder<T>>
154+
extends ResourceBuilder<T> {
155+
String container;
156+
157+
ResourceAndContainerBuilder(ApiClient client, Class<?> apiTypeClass) {
158+
super(client, apiTypeClass);
159+
}
160+
161+
public T container(String container) {
162+
this.container = container;
163+
return (T) this;
164+
}
165+
}
110166
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 com.google.common.io.ByteStreams;
16+
import io.kubernetes.client.Exec;
17+
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
18+
import io.kubernetes.client.openapi.ApiClient;
19+
import io.kubernetes.client.openapi.ApiException;
20+
import io.kubernetes.client.openapi.models.V1ObjectMeta;
21+
import io.kubernetes.client.openapi.models.V1Pod;
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.io.OutputStream;
25+
26+
public class KubectlExec extends Kubectl.ResourceAndContainerBuilder<KubectlExec>
27+
implements Kubectl.Executable<V1Pod> {
28+
private String[] command;
29+
private boolean stdin;
30+
private boolean tty;
31+
private int result;
32+
33+
KubectlExec(ApiClient client) {
34+
super(client, V1Pod.class);
35+
}
36+
37+
public KubectlExec command(String[] command) {
38+
this.command = command;
39+
return this;
40+
}
41+
42+
public KubectlExec stdin(boolean stdin) {
43+
this.stdin = stdin;
44+
return this;
45+
}
46+
47+
public KubectlExec tty(boolean tty) {
48+
this.tty = tty;
49+
return this;
50+
}
51+
52+
public int exitCode() {
53+
return result;
54+
}
55+
56+
@Override
57+
public V1Pod execute() throws KubectlException {
58+
V1Pod pod = new V1Pod().metadata(new V1ObjectMeta().name(name).namespace(namespace));
59+
60+
Exec exec = new Exec(apiClient);
61+
try {
62+
Process proc = exec.exec(pod, command, container, stdin, tty);
63+
copyAsync(proc.getInputStream(), System.out);
64+
copyAsync(proc.getErrorStream(), System.err);
65+
if (stdin) {
66+
copyAsync(System.in, proc.getOutputStream());
67+
}
68+
this.result = proc.waitFor();
69+
} catch (InterruptedException | ApiException | IOException ex) {
70+
throw new KubectlException(ex);
71+
}
72+
return pod;
73+
}
74+
75+
private static void copyAsync(InputStream in, OutputStream out) {
76+
new Thread(
77+
new Runnable() {
78+
public void run() {
79+
try {
80+
ByteStreams.copy(in, out);
81+
} catch (IOException ex) {
82+
ex.printStackTrace();
83+
}
84+
}
85+
})
86+
.start();
87+
}
88+
}

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

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,16 @@
2323
import java.util.Map;
2424
import org.apache.commons.lang.StringUtils;
2525

26-
public class KubectlLabel<ApiType extends KubernetesObject> implements Kubectl.Executable<ApiType> {
27-
28-
private final ApiClient apiClient;
29-
private final Class<ApiType> apiTypeClass;
26+
public class KubectlLabel<ApiType extends KubernetesObject>
27+
extends Kubectl.ResourceBuilder<KubectlLabel<ApiType>> implements Kubectl.Executable<ApiType> {
3028
private final Map<String, String> addingLabels;
31-
3229
private String apiGroup;
3330
private String apiVersion;
3431
private String resourceNamePlural;
3532

36-
private String namespace;
37-
private String name;
38-
3933
KubectlLabel(ApiClient apiClient, Class<ApiType> apiTypeClass) {
34+
super(apiClient, apiTypeClass);
4035
this.addingLabels = new HashMap<>();
41-
this.apiTypeClass = apiTypeClass;
42-
this.apiClient = apiClient;
4336
}
4437

4538
public KubectlLabel<ApiType> apiGroup(String apiGroup) {
@@ -57,16 +50,6 @@ public KubectlLabel<ApiType> resourceNamePlural(String resourceNamePlural) {
5750
return this;
5851
}
5952

60-
public KubectlLabel<ApiType> namespace(String namespace) {
61-
this.namespace = namespace;
62-
return this;
63-
}
64-
65-
public KubectlLabel<ApiType> name(String name) {
66-
this.name = name;
67-
return this;
68-
}
69-
7053
public KubectlLabel<ApiType> addLabel(String key, String value) {
7154
this.addingLabels.put(key, value);
7255
return this;
@@ -81,7 +64,7 @@ public ApiType execute() throws KubectlException {
8164
verifyArguments();
8265
GenericKubernetesApi<ApiType, KubernetesListObject> api =
8366
new GenericKubernetesApi<>(
84-
apiTypeClass,
67+
(Class<ApiType>) apiTypeClass,
8568
KubernetesListObject.class,
8669
apiGroup,
8770
apiVersion,

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

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,17 @@
2222
import io.kubernetes.client.openapi.models.V1ReplicaSet;
2323
import io.kubernetes.client.util.PatchUtils;
2424

25-
public class KubectlScale<ApiType extends KubernetesObject> implements Kubectl.Executable<ApiType> {
26-
private final ApiClient apiClient;
27-
private final Class<ApiType> apiTypeClass;
25+
public class KubectlScale<ApiType extends KubernetesObject>
26+
extends Kubectl.ResourceBuilder<KubectlScale<ApiType>> implements Kubectl.Executable<ApiType> {
2827
private final AppsV1Api api;
29-
private String namespace;
30-
private String name;
3128
private int replicas;
3229

3330
KubectlScale(ApiClient client, Class<ApiType> apiTypeClass) {
34-
this.apiClient = client;
35-
this.apiTypeClass = apiTypeClass;
31+
super(client, apiTypeClass);
3632
this.api = new AppsV1Api(client);
3733
this.replicas = -1;
3834
}
3935

40-
public KubectlScale<ApiType> name(String name) {
41-
this.name = name;
42-
return this;
43-
}
44-
45-
public KubectlScale<ApiType> namespace(String namespace) {
46-
this.namespace = namespace;
47-
return this;
48-
}
49-
5036
public KubectlScale<ApiType> replicas(int replicas) {
5137
this.replicas = replicas;
5238
return this;
@@ -62,7 +48,7 @@ public ApiType execute() throws KubectlException {
6248
try {
6349
if (apiTypeClass.equals(V1Deployment.class)) {
6450
return PatchUtils.patch(
65-
apiTypeClass,
51+
(Class<ApiType>) apiTypeClass,
6652
() ->
6753
api.patchNamespacedDeploymentCall(
6854
name,
@@ -77,7 +63,7 @@ public ApiType execute() throws KubectlException {
7763
this.apiClient);
7864
} else if (apiTypeClass.equals(V1ReplicaSet.class)) {
7965
return PatchUtils.patch(
80-
apiTypeClass,
66+
(Class<ApiType>) apiTypeClass,
8167
() ->
8268
api.patchNamespacedReplicaSetCall(
8369
name, namespace, new V1Patch(jsonPatchStr), null, null, null, null, null),

extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlScaleTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,9 @@ public void testKubectlScaleReplicaSetShouldWork() throws KubectlException {
7171
aResponse()
7272
.withStatus(200)
7373
.withBody("{\"metadata\":{\"name\":\"foo\",\"namespace\":\"default\"}}")));
74-
V1ReplicaSet scaled =
75-
Kubectl.scale(apiClient, V1ReplicaSet.class)
76-
.name("foo")
77-
.namespace("default")
78-
.replicas(4)
79-
.execute();
74+
KubectlScale<V1ReplicaSet> s =
75+
Kubectl.scale(apiClient, V1ReplicaSet.class).replicas(4).name("foo").namespace("default");
76+
V1ReplicaSet scaled = s.execute();
8077
wireMockRule.verify(
8178
1,
8279
patchRequestedFor(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets/foo"))

0 commit comments

Comments
 (0)