Skip to content

Commit b47f10c

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

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
import io.kubernetes.client.common.KubernetesObject;
1616
import io.kubernetes.client.extended.kubectl.exception.KubectlException;
1717
import io.kubernetes.client.extended.kubectl.util.deployment.DeploymentHelper;
18+
import io.kubernetes.client.extended.kubectl.util.patch.PatchHelper;
1819
import io.kubernetes.client.openapi.ApiException;
1920
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;
2024
import io.kubernetes.client.openapi.models.V1Deployment;
2125
import io.kubernetes.client.openapi.models.V1ObjectMeta;
2226
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
2327
import io.kubernetes.client.openapi.models.V1ReplicaSet;
28+
import io.kubernetes.client.util.labels.LabelSelector;
2429
import java.util.ArrayList;
2530
import java.util.HashMap;
2631
import java.util.List;
@@ -30,6 +35,11 @@ public class KubectlRolloutHistory<ApiType extends KubernetesObject>
3035
extends Kubectl.ResourceBuilder<ApiType, KubectlRolloutHistory<ApiType>>
3136
implements Kubectl.Executable<ApiType> {
3237

38+
@FunctionalInterface
39+
public interface PodTemplateParser {
40+
V1PodTemplateSpec parse(V1ControllerRevision history) throws ApiException, KubectlException;
41+
}
42+
3343
public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause";
3444

3545
public V1PodTemplateSpec getTemplate() {
@@ -73,11 +83,16 @@ public String toString() {
7383
public ApiType execute() throws KubectlException {
7484
validate();
7585
AppsV1Api api = new AppsV1Api(this.apiClient);
86+
refreshDiscovery();
7687
try {
7788
if (apiTypeClass.equals(V1Deployment.class)) {
7889
V1Deployment deployment = api.readNamespacedDeployment(name, namespace, null, null, null);
7990
deploymentViewHistory(deployment, api);
8091
return (ApiType) deployment;
92+
} else if (apiTypeClass.equals(V1DaemonSet.class)) {
93+
V1DaemonSet daemonSet = api.readNamespacedDaemonSet(name, namespace, null, null, null);
94+
daemonSetViewHistory(daemonSet, api);
95+
return (ApiType) daemonSet;
8196
} else {
8297
throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass);
8398
}
@@ -154,6 +169,78 @@ private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throw
154169
}
155170
}
156171

172+
private void daemonSetViewHistory(V1DaemonSet daemonSet, AppsV1Api api)
173+
throws ApiException, KubectlException {
174+
LabelSelector selector = LabelSelector.parse(daemonSet.getSpec().getSelector());
175+
List<V1ControllerRevision> historyList = controlledHistory(api, daemonSet, selector);
176+
parseHistory(
177+
historyList,
178+
history -> {
179+
V1DaemonSet dsOfHistory = applyDaemonSetHistory(history);
180+
return dsOfHistory.getSpec().getTemplate();
181+
});
182+
}
183+
184+
private V1DaemonSet applyDaemonSetHistory(V1ControllerRevision history)
185+
throws KubectlException, ApiException {
186+
//todo use server-side-apply
187+
String patch = apiClient.getJSON().serialize(history.getData());
188+
return (V1DaemonSet)
189+
PatchHelper.dryRunStrategyMergePatch(getGenericApi(), patch, namespace, name);
190+
}
191+
192+
private void parseHistory(List<V1ControllerRevision> historyList, PodTemplateParser parser)
193+
throws ApiException, KubectlException {
194+
Map<Long, V1ControllerRevision> historyInfo = new HashMap<>();
195+
for (V1ControllerRevision history : historyList) {
196+
historyInfo.put(history.getRevision(), history);
197+
}
198+
199+
if (revision > 0) {
200+
V1ControllerRevision history = historyInfo.get(revision);
201+
if (history == null) {
202+
throw new ApiException("unable to find the specified revision " + revision);
203+
}
204+
template = parser.parse(history);
205+
return;
206+
}
207+
208+
List<Long> revisions = new ArrayList<>(historyInfo.keySet());
209+
revisions.sort(Long::compareTo);
210+
for (Long revision : revisions) {
211+
String changeCause =
212+
historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
213+
if (changeCause == null || changeCause.isEmpty()) {
214+
changeCause = "<none>";
215+
}
216+
histories.add(new History(revision, changeCause));
217+
}
218+
}
219+
220+
// controlledHistories returns all ControllerRevisions in namespace that selected by selector and
221+
// owned by accessor
222+
private List<V1ControllerRevision> controlledHistory(
223+
AppsV1Api api, KubernetesObject accessor, LabelSelector selector) throws ApiException {
224+
List<V1ControllerRevision> result = new ArrayList<>();
225+
V1ControllerRevisionList historyList =
226+
api.listNamespacedControllerRevision(
227+
namespace, null, null, null, null, selector.toString(), null, null, null, null, null);
228+
for (V1ControllerRevision history : historyList.getItems()) {
229+
if (isControlledBy(history, accessor)) {
230+
result.add(history);
231+
}
232+
}
233+
return result;
234+
}
235+
236+
private boolean isControlledBy(KubernetesObject obj, KubernetesObject owner) {
237+
return obj.getMetadata().getOwnerReferences().stream()
238+
.filter(r -> r.getController() != null && r.getController())
239+
.findAny()
240+
.map(v1OwnerReference -> v1OwnerReference.getUid().equals(owner.getMetadata().getUid()))
241+
.orElse(false);
242+
}
243+
157244
// getChangeCause returns the change-cause annotation of the input object
158245
private static String getChangeCause(V1ObjectMeta meta) {
159246
return meta.getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.util.patch;
14+
15+
import io.kubernetes.client.common.KubernetesListObject;
16+
import io.kubernetes.client.common.KubernetesObject;
17+
import io.kubernetes.client.custom.V1Patch;
18+
import io.kubernetes.client.openapi.ApiException;
19+
import io.kubernetes.client.util.generic.GenericKubernetesApi;
20+
import io.kubernetes.client.util.generic.options.PatchOptions;
21+
22+
public class PatchHelper {
23+
public static <ApiType extends KubernetesObject, ApiListType extends KubernetesListObject>
24+
ApiType dryRunStrategyMergePatch(
25+
GenericKubernetesApi<ApiType, ApiListType> genericApi,
26+
String patch,
27+
String namespace,
28+
String name)
29+
throws ApiException {
30+
PatchOptions options = new PatchOptions();
31+
options.setDryRun("All");
32+
return genericApi
33+
.patch(
34+
namespace,
35+
name,
36+
V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH,
37+
new V1Patch(patch),
38+
options)
39+
.throwsApiException()
40+
.getObject();
41+
}
42+
}

0 commit comments

Comments
 (0)