15
15
import io .kubernetes .client .common .KubernetesObject ;
16
16
import io .kubernetes .client .extended .kubectl .exception .KubectlException ;
17
17
import io .kubernetes .client .extended .kubectl .util .deployment .DeploymentHelper ;
18
+ import io .kubernetes .client .extended .kubectl .util .patch .PatchHelper ;
18
19
import io .kubernetes .client .openapi .ApiException ;
19
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 ;
20
24
import io .kubernetes .client .openapi .models .V1Deployment ;
21
25
import io .kubernetes .client .openapi .models .V1ObjectMeta ;
22
26
import io .kubernetes .client .openapi .models .V1PodTemplateSpec ;
23
27
import io .kubernetes .client .openapi .models .V1ReplicaSet ;
28
+ import io .kubernetes .client .util .labels .LabelSelector ;
24
29
import java .util .ArrayList ;
25
30
import java .util .HashMap ;
26
31
import java .util .List ;
@@ -30,6 +35,11 @@ public class KubectlRolloutHistory<ApiType extends KubernetesObject>
30
35
extends Kubectl .ResourceBuilder <ApiType , KubectlRolloutHistory <ApiType >>
31
36
implements Kubectl .Executable <ApiType > {
32
37
38
+ @ FunctionalInterface
39
+ public interface PodTemplateParser {
40
+ V1PodTemplateSpec parse (V1ControllerRevision history ) throws ApiException , KubectlException ;
41
+ }
42
+
33
43
public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause" ;
34
44
35
45
public V1PodTemplateSpec getTemplate () {
@@ -73,11 +83,16 @@ public String toString() {
73
83
public ApiType execute () throws KubectlException {
74
84
validate ();
75
85
AppsV1Api api = new AppsV1Api (this .apiClient );
86
+ refreshDiscovery ();
76
87
try {
77
88
if (apiTypeClass .equals (V1Deployment .class )) {
78
89
V1Deployment deployment = api .readNamespacedDeployment (name , namespace , null , null , null );
79
90
deploymentViewHistory (deployment , api );
80
91
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 ;
81
96
} else {
82
97
throw new KubectlException ("Unsupported class for rollout history: " + apiTypeClass );
83
98
}
@@ -154,6 +169,78 @@ private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throw
154
169
}
155
170
}
156
171
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
+
157
244
// getChangeCause returns the change-cause annotation of the input object
158
245
private static String getChangeCause (V1ObjectMeta meta ) {
159
246
return meta .getAnnotations ().get (CHANGE_CAUSE_ANNOTATION );
0 commit comments