Skip to content

Commit eeaa08d

Browse files
committed
feat : support kubectl rollout history for deployment
1 parent b0392d2 commit eeaa08d

2 files changed

Lines changed: 174 additions & 1 deletion

File tree

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public static KubectlVersion version() {
133133
return new KubectlVersion();
134134
}
135135

136-
/*
136+
/**
137137
* Equivalent for `kubectl scale`
138138
*
139139
* @param <ApiType> the target api type
@@ -145,6 +145,18 @@ public static <ApiType extends KubernetesObject> KubectlScale<ApiType> scale(
145145
return new KubectlScale<>(apiTypeClass);
146146
}
147147

148+
/**
149+
* Equivalent for `kubectl rollout history`
150+
*
151+
* @param <ApiType> the target api type
152+
* @param apiTypeClass the api type class
153+
* @return the kubectl rollout history operator
154+
*/
155+
public static <ApiType extends KubernetesObject> KubectlRolloutHistory<ApiType> rolloutHistory(
156+
Class<ApiType> apiTypeClass) {
157+
return new KubectlRolloutHistory<>(apiTypeClass);
158+
}
159+
148160
/**
149161
* Equivalent for `kubectl exec`
150162
*
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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.ApiException;
19+
import io.kubernetes.client.openapi.apis.AppsV1Api;
20+
import io.kubernetes.client.openapi.models.V1Deployment;
21+
import io.kubernetes.client.openapi.models.V1ObjectMeta;
22+
import io.kubernetes.client.openapi.models.V1PodTemplateSpec;
23+
import io.kubernetes.client.openapi.models.V1ReplicaSet;
24+
import java.util.ArrayList;
25+
import java.util.HashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
public class KubectlRolloutHistory<ApiType extends KubernetesObject>
30+
extends Kubectl.ResourceBuilder<ApiType, KubectlRolloutHistory<ApiType>>
31+
implements Kubectl.Executable<ApiType> {
32+
33+
public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause";
34+
35+
public V1PodTemplateSpec getTemplate() {
36+
return template;
37+
}
38+
39+
public List<History> getHistories() {
40+
return histories;
41+
}
42+
43+
public static class History {
44+
private final long revision;
45+
private final String changeCause;
46+
47+
public long getRevision() {
48+
return revision;
49+
}
50+
51+
public String getChangeCause() {
52+
return changeCause;
53+
}
54+
55+
public History(long revision, String changeCause) {
56+
this.revision = revision;
57+
this.changeCause = changeCause;
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return "{revision :" + revision + ", changeCause :" + changeCause + "}";
63+
}
64+
}
65+
66+
private long revision;
67+
68+
private final List<History> histories;
69+
70+
private V1PodTemplateSpec template;
71+
72+
@Override
73+
public ApiType execute() throws KubectlException {
74+
validate();
75+
AppsV1Api api = new AppsV1Api(this.apiClient);
76+
try {
77+
if (apiTypeClass.equals(V1Deployment.class)) {
78+
V1Deployment deployment = api.readNamespacedDeployment(name, namespace, null, null, null);
79+
deploymentViewHistory(deployment, api);
80+
return (ApiType) deployment;
81+
} else {
82+
throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass);
83+
}
84+
} catch (ApiException ex) {
85+
throw new KubectlException(ex);
86+
}
87+
}
88+
89+
KubectlRolloutHistory(Class<ApiType> apiTypeClass) {
90+
super(apiTypeClass);
91+
revision = 0;
92+
histories = new ArrayList<>();
93+
}
94+
95+
public KubectlRolloutHistory<ApiType> revision(int revision) {
96+
this.revision = revision;
97+
return this;
98+
}
99+
100+
private void validate() throws KubectlException {
101+
StringBuilder msg = new StringBuilder();
102+
if (name == null) {
103+
msg.append("Missing name, ");
104+
}
105+
if (namespace == null) {
106+
msg.append("Missing namespace, ");
107+
}
108+
if (revision < 0) {
109+
msg.append("revision must be a positive integer: ").append(revision);
110+
}
111+
if (msg.length() > 0) {
112+
throw new KubectlException(msg.toString());
113+
}
114+
}
115+
116+
private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throws ApiException {
117+
List<V1ReplicaSet> allOldRSs = new ArrayList<>();
118+
List<V1ReplicaSet> oldRSs = new ArrayList<>();
119+
V1ReplicaSet newRs = DeploymentHelper.getAllReplicaSets(deployment, api, oldRSs, allOldRSs);
120+
if (newRs != null) {
121+
allOldRSs.add(newRs);
122+
}
123+
Map<Long, V1PodTemplateSpec> historyInfo = new HashMap<>();
124+
for (V1ReplicaSet rs : allOldRSs) {
125+
Long v = DeploymentHelper.revision(rs.getMetadata());
126+
historyInfo.put(v, rs.getSpec().getTemplate());
127+
String changeCause = getChangeCause(rs.getMetadata());
128+
if (historyInfo.get(v).getMetadata().getAnnotations() == null) {
129+
historyInfo.get(v).getMetadata().setAnnotations(new HashMap<>());
130+
}
131+
if (changeCause != null && changeCause.length() > 0) {
132+
historyInfo.get(v).getMetadata().getAnnotations().put(CHANGE_CAUSE_ANNOTATION, changeCause);
133+
}
134+
}
135+
136+
if (revision > 0) {
137+
// get details of a specific revision
138+
V1PodTemplateSpec template = historyInfo.get(revision);
139+
if (template == null) {
140+
throw new ApiException("unable to find the specified revision " + revision);
141+
}
142+
this.template = template;
143+
return;
144+
}
145+
List<Long> revisions = new ArrayList<>(historyInfo.keySet());
146+
revisions.sort(Long::compareTo);
147+
for (Long revision : revisions) {
148+
String changeCause =
149+
historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
150+
if (changeCause == null || changeCause.isEmpty()) {
151+
changeCause = "<none>";
152+
}
153+
histories.add(new History(revision, changeCause));
154+
}
155+
}
156+
157+
// getChangeCause returns the change-cause annotation of the input object
158+
private static String getChangeCause(V1ObjectMeta meta) {
159+
return meta.getAnnotations().get(CHANGE_CAUSE_ANNOTATION);
160+
}
161+
}

0 commit comments

Comments
 (0)