Skip to content

Commit 53b201e

Browse files
committed
Add an implementation of kubectl scale.
1 parent ce51991 commit 53b201e

File tree

6 files changed

+287
-3
lines changed

6 files changed

+287
-3
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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.examples;
14+
15+
import static io.kubernetes.client.extended.kubectl.Kubectl.scale;
16+
17+
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
18+
import io.kubernetes.client.openapi.ApiClient;
19+
import io.kubernetes.client.openapi.models.V1Deployment;
20+
import io.kubernetes.client.util.Config;
21+
22+
/**
23+
* A simple example of how to use the Java API
24+
*
25+
* <p>Easiest way to run this: mvn exec:java
26+
* -Dexec.mainClass="io.kubernetes.client.examples.Kubectl"
27+
*
28+
* <p>From inside $REPO_DIR/examples
29+
*/
30+
public class Kubectl {
31+
public static void main(String[] args) throws java.io.IOException, KubectlException {
32+
ApiClient client = Config.defaultClient();
33+
34+
String verb = args[0];
35+
String ns = args[1];
36+
String name = args[2];
37+
38+
switch (verb) {
39+
case "scale":
40+
int replicas = Integer.parseInt(args[3]);
41+
scale(client, V1Deployment.class).namespace(ns).name(name).replicas(replicas).execute();
42+
System.out.println("Deployment scaled.");
43+
System.exit(0);
44+
default:
45+
System.out.println("Unknown verb: " + verb);
46+
System.exit(-1);
47+
}
48+
}
49+
}

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,37 @@ public static KubectlVersion version(ApiClient apiClient) {
6767
return new KubectlVersion(apiClient);
6868
}
6969

70+
/*
71+
* Equivalent for `kubectl scale`
72+
*
73+
* @param <ApiType> the target api type
74+
* @param apiTypeClass the api type class
75+
* @return the kubectl scale operator
76+
*/
77+
public static <ApiType extends KubernetesObject> KubectlScale<ApiType> scale(
78+
Class<ApiType> apiTypeClass) {
79+
return scale(Configuration.getDefaultApiClient(), apiTypeClass);
80+
}
81+
82+
/**
83+
* Equivalent for `kubectl scale`
84+
*
85+
* @param <ApiType> the target api type
86+
* @param apiClient The api client instance
87+
* @param apiTypeClass the api type class
88+
* @return the kubectl scale operator
89+
*/
90+
public static <ApiType extends KubernetesObject> KubectlScale<ApiType> scale(
91+
ApiClient apiClient, Class<ApiType> apiTypeClass) {
92+
return new KubectlScale<>(apiClient, apiTypeClass);
93+
}
94+
7095
/**
7196
* Executable executes a kubectl helper.
7297
*
7398
* @param <OUTPUT> the type parameter
7499
*/
75-
public interface Executable<OUTPUT> {
100+
public static interface Executable<OUTPUT> {
76101

77102
/**
78103
* Run and retrieve the output from the kubectl helpers.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import java.util.Map;
2424
import org.apache.commons.lang.StringUtils;
2525

26-
class KubectlLabel<ApiType extends KubernetesObject> implements Kubectl.Executable<ApiType> {
26+
public class KubectlLabel<ApiType extends KubernetesObject> implements Kubectl.Executable<ApiType> {
2727

2828
private final ApiClient apiClient;
2929
private final Class<ApiType> apiTypeClass;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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.common.KubernetesObject;
16+
import io.kubernetes.client.custom.V1Patch;
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.apis.AppsV1Api;
21+
import io.kubernetes.client.openapi.models.V1Deployment;
22+
import io.kubernetes.client.openapi.models.V1ReplicaSet;
23+
import io.kubernetes.client.util.PatchUtils;
24+
25+
public class KubectlScale<ApiType extends KubernetesObject> implements Kubectl.Executable<ApiType> {
26+
private final ApiClient apiClient;
27+
private final Class<ApiType> apiTypeClass;
28+
private final AppsV1Api api;
29+
private String namespace;
30+
private String name;
31+
private int replicas;
32+
33+
KubectlScale(ApiClient client, Class<ApiType> apiTypeClass) {
34+
this.apiClient = client;
35+
this.apiTypeClass = apiTypeClass;
36+
this.api = new AppsV1Api(client);
37+
this.replicas = -1;
38+
}
39+
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+
50+
public KubectlScale<ApiType> replicas(int replicas) {
51+
this.replicas = replicas;
52+
return this;
53+
}
54+
55+
@Override
56+
public ApiType execute() throws KubectlException {
57+
validate();
58+
59+
String jsonPatchStr =
60+
String.format("[{\"op\":\"replace\",\"path\":\"/spec/replicas\",\"value\":%d}]", replicas);
61+
62+
try {
63+
if (apiTypeClass.equals(V1Deployment.class)) {
64+
return PatchUtils.patch(
65+
apiTypeClass,
66+
() ->
67+
api.patchNamespacedDeploymentCall(
68+
name,
69+
namespace,
70+
new V1Patch(jsonPatchStr),
71+
null,
72+
null,
73+
null, // field-manager is optional
74+
null,
75+
null),
76+
V1Patch.PATCH_FORMAT_JSON_PATCH,
77+
this.apiClient);
78+
} else if (apiTypeClass.equals(V1ReplicaSet.class)) {
79+
return PatchUtils.patch(
80+
apiTypeClass,
81+
() ->
82+
api.patchNamespacedReplicaSetCall(
83+
name, namespace, new V1Patch(jsonPatchStr), null, null, null, null, null),
84+
V1Patch.PATCH_FORMAT_JSON_PATCH,
85+
this.apiClient);
86+
} else {
87+
throw new KubectlException("Unsupported class for scale: " + apiTypeClass);
88+
}
89+
} catch (ApiException ex) {
90+
throw new KubectlException(ex);
91+
}
92+
}
93+
94+
private void validate() throws KubectlException {
95+
StringBuilder msg = new StringBuilder();
96+
if (name == null) {
97+
msg.append("Missing name, ");
98+
}
99+
if (namespace == null) {
100+
msg.append("Missing namespace, ");
101+
}
102+
if (replicas < 0) {
103+
msg.append("Invalid replicas");
104+
}
105+
if (msg.length() > 0) {
106+
throw new KubectlException(msg.toString());
107+
}
108+
}
109+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import io.kubernetes.client.util.version.Version;
2020
import java.io.IOException;
2121

22-
class KubectlVersion implements Kubectl.Executable<VersionInfo> {
22+
public class KubectlVersion implements Kubectl.Executable<VersionInfo> {
2323

2424
private final Version version;
2525

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 static com.github.tomakehurst.wiremock.client.WireMock.*;
16+
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
17+
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
18+
import static org.junit.Assert.assertNotNull;
19+
import static org.junit.Assert.assertThrows;
20+
21+
import com.github.tomakehurst.wiremock.junit.WireMockRule;
22+
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
23+
import io.kubernetes.client.openapi.ApiClient;
24+
import io.kubernetes.client.openapi.models.V1Deployment;
25+
import io.kubernetes.client.openapi.models.V1Pod;
26+
import io.kubernetes.client.openapi.models.V1ReplicaSet;
27+
import io.kubernetes.client.util.ClientBuilder;
28+
import java.io.IOException;
29+
import org.junit.Before;
30+
import org.junit.Rule;
31+
import org.junit.Test;
32+
33+
public class KubectlScaleTest {
34+
35+
private ApiClient apiClient;
36+
37+
@Rule public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort(), false);
38+
39+
@Before
40+
public void setup() throws IOException {
41+
apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build();
42+
}
43+
44+
@Test
45+
public void testKubectlScaleShouldWork() throws KubectlException {
46+
wireMockRule.stubFor(
47+
patch(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo"))
48+
.willReturn(
49+
aResponse()
50+
.withStatus(200)
51+
.withBody("{\"metadata\":{\"name\":\"foo\",\"namespace\":\"default\"}}")));
52+
V1Deployment scaled =
53+
Kubectl.scale(apiClient, V1Deployment.class)
54+
.name("foo")
55+
.namespace("default")
56+
.replicas(2)
57+
.execute();
58+
wireMockRule.verify(
59+
1,
60+
patchRequestedFor(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo"))
61+
.withRequestBody(
62+
equalToJson("[{\"op\":\"replace\",\"path\":\"/spec/replicas\",\"value\":2}]")));
63+
assertNotNull(scaled);
64+
}
65+
66+
@Test
67+
public void testKubectlScaleReplicaSetShouldWork() throws KubectlException {
68+
wireMockRule.stubFor(
69+
patch(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets/foo"))
70+
.willReturn(
71+
aResponse()
72+
.withStatus(200)
73+
.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();
80+
wireMockRule.verify(
81+
1,
82+
patchRequestedFor(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets/foo"))
83+
.withRequestBody(
84+
equalToJson("[{\"op\":\"replace\",\"path\":\"/spec/replicas\",\"value\":4}]")));
85+
assertNotNull(scaled);
86+
}
87+
88+
@Test
89+
public void testKubectlScaleShouldThrow() {
90+
assertThrows(
91+
KubectlException.class,
92+
() -> {
93+
V1Pod scaled =
94+
Kubectl.scale(apiClient, V1Pod.class)
95+
.name("foo")
96+
.namespace("default")
97+
.replicas(2)
98+
.execute();
99+
});
100+
}
101+
}

0 commit comments

Comments
 (0)