Skip to content

Commit 3b1c4c4

Browse files
committed
feat : support kubectl rollout history for daemonset
1 parent c26169c commit 3b1c4c4

File tree

6 files changed

+351
-300
lines changed

6 files changed

+351
-300
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2021 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+
public class History {
16+
private final long revision;
17+
private final String changeCause;
18+
19+
public long getRevision() {
20+
return revision;
21+
}
22+
23+
public String getChangeCause() {
24+
return changeCause;
25+
}
26+
27+
public History(long revision, String changeCause) {
28+
this.revision = revision;
29+
this.changeCause = changeCause;
30+
}
31+
32+
@Override
33+
public String toString() {
34+
return "{revision :" + revision + ", changeCause :" + changeCause + "}";
35+
}
36+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ public static <ApiType extends KubernetesObject> KubectlScale<ApiType> scale(
152152
* @param apiTypeClass the api type class
153153
* @return the kubectl rollout history operator
154154
*/
155-
public static <ApiType extends KubernetesObject> KubectlRolloutHistory<ApiType> rolloutHistory(
155+
public static <ApiType extends KubernetesObject> KubectlRollout<ApiType> rollout(
156156
Class<ApiType> apiTypeClass) {
157-
return new KubectlRolloutHistory<>(apiTypeClass);
157+
return new KubectlRollout<>(apiTypeClass);
158158
}
159159

160160
/**
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
Copyright 2021 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.extended.kubectl.exception.KubectlException;
17+
import io.kubernetes.client.extended.kubectl.util.deployment.DeploymentHelper;
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.V1ControllerRevision;
22+
import io.kubernetes.client.openapi.models.V1ControllerRevisionList;
23+
import io.kubernetes.client.openapi.models.V1DaemonSet;
24+
import io.kubernetes.client.openapi.models.V1Deployment;
25+
import io.kubernetes.client.openapi.models.V1ObjectMeta;
26+
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
27+
import io.kubernetes.client.openapi.models.V1ReplicaSet;
28+
import io.kubernetes.client.util.labels.LabelSelector;
29+
import java.util.ArrayList;
30+
import java.util.HashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
34+
public class KubectlRollout<ApiType extends KubernetesObject> {
35+
36+
@FunctionalInterface
37+
public interface PodTemplateParser {
38+
V1PodTemplateSpec parse(V1ControllerRevision history) throws ApiException, KubectlException;
39+
}
40+
41+
KubectlRollout(Class<ApiType> apiTypeClass) {
42+
this.apiTypeClass = apiTypeClass;
43+
}
44+
45+
private Class<ApiType> apiTypeClass;
46+
47+
public KubectlRolloutHistory history() {
48+
return new KubectlRolloutHistory();
49+
}
50+
51+
class KubectlRolloutHistory extends Kubectl.ResourceBuilder<ApiType, KubectlRolloutHistory>
52+
implements Kubectl.Executable<List<History>> {
53+
54+
KubectlRolloutHistory() {
55+
super(KubectlRollout.this.apiTypeClass);
56+
histories = new ArrayList<>();
57+
revision = 0;
58+
}
59+
60+
public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause";
61+
62+
private long revision;
63+
64+
private final List<History> histories;
65+
66+
private V1PodTemplateSpec template;
67+
68+
@Override
69+
public List<History> execute() throws KubectlException {
70+
validate();
71+
AppsV1Api api = new AppsV1Api(this.apiClient);
72+
refreshDiscovery();
73+
try {
74+
if (apiTypeClass.equals(V1Deployment.class)) {
75+
V1Deployment deployment = api.readNamespacedDeployment(name, namespace, null, null, null);
76+
deploymentViewHistory(deployment, api);
77+
} else if (apiTypeClass.equals(V1DaemonSet.class)) {
78+
V1DaemonSet daemonSet = api.readNamespacedDaemonSet(name, namespace, null, null, null);
79+
daemonSetViewHistory(daemonSet, api);
80+
} else {
81+
throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass);
82+
}
83+
return histories;
84+
} catch (ApiException ex) {
85+
throw new KubectlException(ex);
86+
}
87+
}
88+
89+
public KubectlRolloutHistoryRevision revision(int revision) {
90+
this.revision = revision;
91+
return new KubectlRolloutHistoryRevision();
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 (revision < 0) {
103+
msg.append("revision must be a positive integer: ").append(revision);
104+
}
105+
if (msg.length() > 0) {
106+
throw new KubectlException(msg.toString());
107+
}
108+
}
109+
110+
private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throws ApiException {
111+
List<V1ReplicaSet> allOldRSs = new ArrayList<>();
112+
List<V1ReplicaSet> oldRSs = new ArrayList<>();
113+
V1ReplicaSet newRs = DeploymentHelper.getAllReplicaSets(deployment, api, oldRSs, allOldRSs);
114+
if (newRs != null) {
115+
allOldRSs.add(newRs);
116+
}
117+
Map<Long, V1PodTemplateSpec> historyInfo = new HashMap<>();
118+
for (V1ReplicaSet rs : allOldRSs) {
119+
Long v = DeploymentHelper.revision(rs.getMetadata());
120+
historyInfo.put(v, rs.getSpec().getTemplate());
121+
String changeCause = getChangeCause(rs.getMetadata());
122+
if (historyInfo.get(v).getMetadata().getAnnotations() == null) {
123+
historyInfo.get(v).getMetadata().setAnnotations(new HashMap<>());
124+
}
125+
if (changeCause != null && changeCause.length() > 0) {
126+
historyInfo
127+
.get(v)
128+
.getMetadata()
129+
.getAnnotations()
130+
.put(CHANGE_CAUSE_ANNOTATION, changeCause);
131+
}
132+
}
133+
134+
if (revision > 0) {
135+
// get details of a specific revision
136+
V1PodTemplateSpec template = historyInfo.get(revision);
137+
if (template == null) {
138+
throw new ApiException("unable to find the specified revision " + revision);
139+
}
140+
this.template = template;
141+
return;
142+
}
143+
List<Long> revisions = new ArrayList<>(historyInfo.keySet());
144+
revisions.sort(Long::compareTo);
145+
for (Long revision : revisions) {
146+
String changeCause =
147+
historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
148+
if (changeCause == null || changeCause.isEmpty()) {
149+
changeCause = "<none>";
150+
}
151+
histories.add(new History(revision, changeCause));
152+
}
153+
}
154+
155+
private void daemonSetViewHistory(V1DaemonSet daemonSet, AppsV1Api api)
156+
throws ApiException, KubectlException {
157+
LabelSelector selector = LabelSelector.parse(daemonSet.getSpec().getSelector());
158+
List<V1ControllerRevision> historyList = controlledHistory(api, daemonSet, selector);
159+
parseHistory(
160+
historyList,
161+
history -> {
162+
V1DaemonSet dsOfHistory = applyDaemonSetHistory(history);
163+
return dsOfHistory.getSpec().getTemplate();
164+
});
165+
}
166+
167+
private V1DaemonSet applyDaemonSetHistory(V1ControllerRevision history)
168+
throws KubectlException, ApiException {
169+
String patch = apiClient.getJSON().serialize(history.getData());
170+
return (V1DaemonSet)
171+
PatchHelper.dryRunStrategyMergePatch(getGenericApi(), patch, namespace, name);
172+
}
173+
174+
private void parseHistory(List<V1ControllerRevision> historyList, PodTemplateParser parser)
175+
throws ApiException, KubectlException {
176+
Map<Long, V1ControllerRevision> historyInfo = new HashMap<>();
177+
for (V1ControllerRevision history : historyList) {
178+
historyInfo.put(history.getRevision(), history);
179+
}
180+
181+
if (revision > 0) {
182+
V1ControllerRevision history = historyInfo.get(revision);
183+
if (history == null) {
184+
throw new ApiException("unable to find the specified revision " + revision);
185+
}
186+
template = parser.parse(history);
187+
return;
188+
}
189+
190+
List<Long> revisions = new ArrayList<>(historyInfo.keySet());
191+
revisions.sort(Long::compareTo);
192+
for (Long revision : revisions) {
193+
String changeCause =
194+
historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
195+
if (changeCause == null || changeCause.isEmpty()) {
196+
changeCause = "<none>";
197+
}
198+
histories.add(new History(revision, changeCause));
199+
}
200+
}
201+
202+
// controlledHistories returns all ControllerRevisions in namespace that selected by selector
203+
// and
204+
// owned by accessor
205+
private List<V1ControllerRevision> controlledHistory(
206+
AppsV1Api api, KubernetesObject accessor, LabelSelector selector) throws ApiException {
207+
List<V1ControllerRevision> result = new ArrayList<>();
208+
V1ControllerRevisionList historyList =
209+
api.listNamespacedControllerRevision(
210+
namespace, null, null, null, null, selector.toString(), null, null, null, null, null);
211+
for (V1ControllerRevision history : historyList.getItems()) {
212+
if (isControlledBy(history, accessor)) {
213+
result.add(history);
214+
}
215+
}
216+
return result;
217+
}
218+
219+
private boolean isControlledBy(KubernetesObject obj, KubernetesObject owner) {
220+
return obj.getMetadata().getOwnerReferences().stream()
221+
.filter(r -> r.getController() != null && r.getController())
222+
.findAny()
223+
.map(v1OwnerReference -> v1OwnerReference.getUid().equals(owner.getMetadata().getUid()))
224+
.orElse(false);
225+
}
226+
227+
// getChangeCause returns the change-cause annotation of the input object
228+
private String getChangeCause(V1ObjectMeta meta) {
229+
return meta.getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
230+
}
231+
232+
class KubectlRolloutHistoryRevision
233+
extends Kubectl.ResourceBuilder<ApiType, KubectlRolloutHistoryRevision>
234+
implements Kubectl.Executable<V1PodTemplateSpec> {
235+
236+
KubectlRolloutHistoryRevision() {
237+
super(KubectlRolloutHistory.this.apiTypeClass);
238+
}
239+
240+
@Override
241+
public KubectlRolloutHistoryRevision name(String name) {
242+
KubectlRolloutHistory.this.name(name);
243+
return super.name(name);
244+
}
245+
246+
@Override
247+
public KubectlRolloutHistoryRevision namespace(String namespace) {
248+
KubectlRolloutHistory.this.namespace(namespace);
249+
return super.namespace(namespace);
250+
}
251+
252+
@Override
253+
public KubectlRolloutHistoryRevision apiClient(ApiClient apiClient) {
254+
KubectlRolloutHistory.this.apiClient(apiClient);
255+
return super.apiClient(apiClient);
256+
}
257+
258+
@Override
259+
public KubectlRolloutHistoryRevision skipDiscovery() {
260+
KubectlRolloutHistory.this.skipDiscovery();
261+
return super.skipDiscovery();
262+
}
263+
264+
@Override
265+
public V1PodTemplateSpec execute() throws KubectlException {
266+
KubectlRolloutHistory.this.execute();
267+
return KubectlRolloutHistory.this.template;
268+
}
269+
}
270+
}
271+
}

0 commit comments

Comments
 (0)