Skip to content

Commit cc09486

Browse files
authored
Merge pull request #1819 from NeverRaR/feat/kubectl-rollout-history-statefulset
Feat/kubectl rollout history statefulset
2 parents 27fbfb9 + c5d9b5b commit cc09486

File tree

5 files changed

+967
-0
lines changed

5 files changed

+967
-0
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.kubernetes.client.openapi.models.V1ObjectMeta;
2626
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
2727
import io.kubernetes.client.openapi.models.V1ReplicaSet;
28+
import io.kubernetes.client.openapi.models.V1StatefulSet;
2829
import io.kubernetes.client.util.labels.LabelSelector;
2930
import java.util.ArrayList;
3031
import java.util.HashMap;
@@ -77,6 +78,10 @@ public List<History> execute() throws KubectlException {
7778
} else if (apiTypeClass.equals(V1DaemonSet.class)) {
7879
V1DaemonSet daemonSet = api.readNamespacedDaemonSet(name, namespace, null, null, null);
7980
daemonSetViewHistory(daemonSet, api);
81+
} else if (apiTypeClass.equals(V1StatefulSet.class)) {
82+
V1StatefulSet statefulSet =
83+
api.readNamespacedStatefulSet(name, namespace, null, null, null);
84+
statefulSetViewHistory(statefulSet, api);
8085
} else {
8186
throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass);
8287
}
@@ -171,6 +176,25 @@ private V1DaemonSet applyDaemonSetHistory(V1ControllerRevision history)
171176
PatchHelper.dryRunStrategyMergePatch(getGenericApi(), patch, namespace, name);
172177
}
173178

179+
private void statefulSetViewHistory(V1StatefulSet statefulSet, AppsV1Api api)
180+
throws ApiException, KubectlException {
181+
LabelSelector selector = LabelSelector.parse(statefulSet.getSpec().getSelector());
182+
List<V1ControllerRevision> historyList = controlledHistory(api, statefulSet, selector);
183+
parseHistory(
184+
historyList,
185+
history -> {
186+
V1StatefulSet stsOfHistory = applyStatefulSetHistory(history);
187+
return stsOfHistory.getSpec().getTemplate();
188+
});
189+
}
190+
191+
private V1StatefulSet applyStatefulSetHistory(V1ControllerRevision history)
192+
throws KubectlException, ApiException {
193+
String patch = apiClient.getJSON().serialize(history.getData());
194+
return (V1StatefulSet)
195+
PatchHelper.dryRunStrategyMergePatch(getGenericApi(), patch, namespace, name);
196+
}
197+
174198
private void parseHistory(List<V1ControllerRevision> historyList, PodTemplateParser parser)
175199
throws ApiException, KubectlException {
176200
Map<Long, V1ControllerRevision> historyInfo = new HashMap<>();

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import io.kubernetes.client.openapi.models.V1Deployment;
3131
import io.kubernetes.client.openapi.models.V1DeploymentList;
3232
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
33+
import io.kubernetes.client.openapi.models.V1StatefulSet;
34+
import io.kubernetes.client.openapi.models.V1StatefulSetList;
3335
import io.kubernetes.client.util.ClientBuilder;
3436
import io.kubernetes.client.util.ModelMapper;
3537
import java.io.IOException;
@@ -65,6 +67,18 @@ public class KubectlRolloutTest {
6567
.getResource("daemonset-controllerrevision-list.json")
6668
.getPath();
6769

70+
private static final String STATEFUL_SET =
71+
KubectlRolloutTest.class.getClassLoader().getResource("statefulset.json").getPath();
72+
73+
private static final String PATCHED_STATEFUL_SET =
74+
KubectlRolloutTest.class.getClassLoader().getResource("patched-statefulset.json").getPath();
75+
76+
private static final String STATEFUL_SET_CONTROLLER_REVISION_LIST =
77+
KubectlRolloutTest.class
78+
.getClassLoader()
79+
.getResource("statefulset-controllerrevision-list.json")
80+
.getPath();
81+
6882
@Before
6983
public void setup() throws IOException {
7084
ModelMapper.addModelMap(
@@ -77,6 +91,14 @@ public void setup() throws IOException {
7791
V1DeploymentList.class);
7892
ModelMapper.addModelMap(
7993
"apps", "v1", "DaemonSet", "daemonsets", true, V1DaemonSet.class, V1DaemonSetList.class);
94+
ModelMapper.addModelMap(
95+
"apps",
96+
"v1",
97+
"StatefulSet",
98+
"statefulsets",
99+
true,
100+
V1StatefulSet.class,
101+
V1StatefulSetList.class);
80102
apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build();
81103
}
82104

@@ -223,6 +245,87 @@ public void testKubectlRolloutHistoryDaemonSetWithRevisionShouldWork()
223245
Assert.assertNotNull(template);
224246
}
225247

248+
@Test
249+
public void testKubectlRolloutHistoryStatefulSetShouldWork()
250+
throws KubectlException, IOException {
251+
wireMockRule.stubFor(
252+
get(urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo"))
253+
.willReturn(
254+
aResponse()
255+
.withStatus(200)
256+
.withBody(new String(Files.readAllBytes(Paths.get(STATEFUL_SET))))));
257+
wireMockRule.stubFor(
258+
get(urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions"))
259+
.willReturn(
260+
aResponse()
261+
.withStatus(200)
262+
.withBody(
263+
new String(
264+
Files.readAllBytes(
265+
Paths.get(STATEFUL_SET_CONTROLLER_REVISION_LIST))))));
266+
List<History> histories =
267+
Kubectl.rollout(V1StatefulSet.class)
268+
.history()
269+
.apiClient(apiClient)
270+
.name("foo")
271+
.namespace("default")
272+
.skipDiscovery()
273+
.execute();
274+
wireMockRule.verify(
275+
1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo"))));
276+
wireMockRule.verify(
277+
1,
278+
getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions")))
279+
.withQueryParam("labelSelector", new EqualToPattern("app = bar")));
280+
Assert.assertEquals(3, histories.size());
281+
}
282+
283+
@Test
284+
public void testKubectlRolloutHistoryStatefulSetWithRevisionShouldWork()
285+
throws KubectlException, IOException {
286+
wireMockRule.stubFor(
287+
get(urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo"))
288+
.willReturn(
289+
aResponse()
290+
.withStatus(200)
291+
.withBody(new String(Files.readAllBytes(Paths.get(STATEFUL_SET))))));
292+
wireMockRule.stubFor(
293+
get(urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions"))
294+
.willReturn(
295+
aResponse()
296+
.withStatus(200)
297+
.withBody(
298+
new String(
299+
Files.readAllBytes(
300+
Paths.get(STATEFUL_SET_CONTROLLER_REVISION_LIST))))));
301+
wireMockRule.stubFor(
302+
patch(urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo"))
303+
.willReturn(
304+
aResponse()
305+
.withStatus(200)
306+
.withBody(new String(Files.readAllBytes(Paths.get(PATCHED_STATEFUL_SET))))));
307+
V1PodTemplateSpec template =
308+
Kubectl.rollout(V1StatefulSet.class)
309+
.history()
310+
.apiClient(apiClient)
311+
.name("foo")
312+
.namespace("default")
313+
.revision(2)
314+
.skipDiscovery()
315+
.execute();
316+
wireMockRule.verify(
317+
1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo"))));
318+
wireMockRule.verify(
319+
1,
320+
getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions")))
321+
.withQueryParam("labelSelector", new EqualToPattern("app = bar")));
322+
wireMockRule.verify(
323+
1,
324+
patchRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/statefulsets/foo")))
325+
.withQueryParam("dryRun", new EqualToPattern("All")));
326+
Assert.assertNotNull(template);
327+
}
328+
226329
@Test
227330
public void testKubectlRolloutHistoryWithInvalidRevisionShouldThrow() throws IOException {
228331
wireMockRule.stubFor(
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
{
2+
"apiVersion": "apps/v1",
3+
"kind": "StatefulSet",
4+
"metadata": {
5+
"annotations": {
6+
"kubernetes.io/change-cause": "kubectl.exe create --filename\u003dstatefulset.yaml --record\u003dtrue"
7+
},
8+
"creationTimestamp": "2021-08-13T09:44:24.000000Z",
9+
"generation": 4,
10+
"managedFields": [{
11+
"apiVersion": "apps/v1",
12+
"fieldsType": "FieldsV1",
13+
"fieldsV1": {
14+
"f:metadata": {
15+
"f:annotations": {
16+
".": {},
17+
"f:kubernetes.io/change-cause": {}
18+
}
19+
},
20+
"f:spec": {
21+
"f:podManagementPolicy": {},
22+
"f:replicas": {},
23+
"f:revisionHistoryLimit": {},
24+
"f:selector": {},
25+
"f:serviceName": {},
26+
"f:template": {
27+
"f:metadata": {
28+
"f:labels": {
29+
".": {},
30+
"f:app": {}
31+
}
32+
},
33+
"f:spec": {
34+
"f:containers": {
35+
"k:{\"name\":\"busybox-host\"}": {
36+
".": {},
37+
"f:args": {},
38+
"f:command": {},
39+
"f:image": {},
40+
"f:imagePullPolicy": {},
41+
"f:name": {},
42+
"f:resources": {},
43+
"f:terminationMessagePath": {},
44+
"f:terminationMessagePolicy": {}
45+
}
46+
},
47+
"f:dnsPolicy": {},
48+
"f:restartPolicy": {},
49+
"f:schedulerName": {},
50+
"f:securityContext": {},
51+
"f:terminationGracePeriodSeconds": {}
52+
}
53+
},
54+
"f:updateStrategy": {
55+
"f:rollingUpdate": {
56+
".": {},
57+
"f:partition": {}
58+
},
59+
"f:type": {}
60+
}
61+
}
62+
},
63+
"manager": "kubectl-create",
64+
"operation": "Update",
65+
"time": "2021-08-13T09:44:24.000000Z"
66+
}, {
67+
"apiVersion": "apps/v1",
68+
"fieldsType": "FieldsV1",
69+
"fieldsV1": {
70+
"f:spec": {
71+
"f:template": {
72+
"f:metadata": {
73+
"f:annotations": {}
74+
}
75+
}
76+
}
77+
},
78+
"manager": "kubectl-rollout",
79+
"operation": "Update",
80+
"time": "2021-08-13T09:47:22.000000Z"
81+
}, {
82+
"apiVersion": "apps/v1",
83+
"fieldsType": "FieldsV1",
84+
"fieldsV1": {
85+
"f:spec": {
86+
"f:template": {
87+
"f:metadata": {
88+
"f:annotations": {
89+
"f:kubectl.kubernetes.io/restartedAt": {}
90+
}
91+
}
92+
}
93+
}
94+
},
95+
"manager": "Kubernetes Java Client",
96+
"operation": "Update",
97+
"time": "2021-08-17T11:13:42.000000Z"
98+
}],
99+
"name": "foo",
100+
"namespace": "default",
101+
"resourceVersion": "103707",
102+
"uid": "9a968c95-4b78-4b72-baf9-fa18bab00155"
103+
},
104+
"spec": {
105+
"podManagementPolicy": "OrderedReady",
106+
"replicas": 2,
107+
"revisionHistoryLimit": 10,
108+
"selector": {
109+
"matchLabels": {
110+
"app": "bar"
111+
}
112+
},
113+
"serviceName": "busybox-service\"",
114+
"template": {
115+
"metadata": {
116+
"annotations": {
117+
"kubectl.kubernetes.io/restartedAt": "2021-08-13T17:47:22+08:00"
118+
},
119+
"labels": {
120+
"app": "bar"
121+
}
122+
},
123+
"spec": {
124+
"containers": [{
125+
"args": ["1000"],
126+
"command": ["sleep"],
127+
"image": "busybox:1.31.1",
128+
"imagePullPolicy": "IfNotPresent",
129+
"name": "busybox-host",
130+
"resources": {},
131+
"terminationMessagePath": "/dev/termination-log",
132+
"terminationMessagePolicy": "File"
133+
}],
134+
"dnsPolicy": "ClusterFirst",
135+
"restartPolicy": "Always",
136+
"schedulerName": "default-scheduler",
137+
"securityContext": {},
138+
"terminationGracePeriodSeconds": 30
139+
}
140+
},
141+
"updateStrategy": {
142+
"rollingUpdate": {
143+
"partition": 0
144+
},
145+
"type": "RollingUpdate"
146+
}
147+
},
148+
"status": {
149+
"replicas": 0
150+
}
151+
}

0 commit comments

Comments
 (0)