From 3b1c4c4f878c503900ed79f50adfa892b9a779e4 Mon Sep 17 00:00:00 2001 From: Never <897013703@qq.com> Date: Fri, 20 Aug 2021 15:16:28 +0800 Subject: [PATCH 1/2] feat : support kubectl rollout history for daemonset --- .../client/extended/kubectl/History.java | 36 +++ .../client/extended/kubectl/Kubectl.java | 4 +- .../extended/kubectl/KubectlRollout.java | 271 ++++++++++++++++++ .../kubectl/KubectlRolloutHistory.java | 161 ----------- .../client/extended/kubectl/PatchHelper.java | 42 +++ .../kubectl/KubectlRolloutHistoryTest.java | 137 --------- 6 files changed, 351 insertions(+), 300 deletions(-) create mode 100644 extended/src/main/java/io/kubernetes/client/extended/kubectl/History.java create mode 100644 extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRollout.java delete mode 100644 extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistory.java create mode 100644 extended/src/main/java/io/kubernetes/client/extended/kubectl/PatchHelper.java delete mode 100644 extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistoryTest.java diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/History.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/History.java new file mode 100644 index 0000000000..7eb65f231f --- /dev/null +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/History.java @@ -0,0 +1,36 @@ +/* +Copyright 2021 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +public class History { + private final long revision; + private final String changeCause; + + public long getRevision() { + return revision; + } + + public String getChangeCause() { + return changeCause; + } + + public History(long revision, String changeCause) { + this.revision = revision; + this.changeCause = changeCause; + } + + @Override + public String toString() { + return "{revision :" + revision + ", changeCause :" + changeCause + "}"; + } +} diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java index e86a3df01d..e8d9460fa5 100644 --- a/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/Kubectl.java @@ -152,9 +152,9 @@ public static KubectlScale scale( * @param apiTypeClass the api type class * @return the kubectl rollout history operator */ - public static KubectlRolloutHistory rolloutHistory( + public static KubectlRollout rollout( Class apiTypeClass) { - return new KubectlRolloutHistory<>(apiTypeClass); + return new KubectlRollout<>(apiTypeClass); } /** diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRollout.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRollout.java new file mode 100644 index 0000000000..96236dfe0a --- /dev/null +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRollout.java @@ -0,0 +1,271 @@ +/* +Copyright 2021 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.extended.kubectl.exception.KubectlException; +import io.kubernetes.client.extended.kubectl.util.deployment.DeploymentHelper; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.AppsV1Api; +import io.kubernetes.client.openapi.models.V1ControllerRevision; +import io.kubernetes.client.openapi.models.V1ControllerRevisionList; +import io.kubernetes.client.openapi.models.V1DaemonSet; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1PodTemplateSpec; +import io.kubernetes.client.openapi.models.V1ReplicaSet; +import io.kubernetes.client.util.labels.LabelSelector; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class KubectlRollout { + + @FunctionalInterface + public interface PodTemplateParser { + V1PodTemplateSpec parse(V1ControllerRevision history) throws ApiException, KubectlException; + } + + KubectlRollout(Class apiTypeClass) { + this.apiTypeClass = apiTypeClass; + } + + private Class apiTypeClass; + + public KubectlRolloutHistory history() { + return new KubectlRolloutHistory(); + } + + class KubectlRolloutHistory extends Kubectl.ResourceBuilder + implements Kubectl.Executable> { + + KubectlRolloutHistory() { + super(KubectlRollout.this.apiTypeClass); + histories = new ArrayList<>(); + revision = 0; + } + + public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause"; + + private long revision; + + private final List histories; + + private V1PodTemplateSpec template; + + @Override + public List execute() throws KubectlException { + validate(); + AppsV1Api api = new AppsV1Api(this.apiClient); + refreshDiscovery(); + try { + if (apiTypeClass.equals(V1Deployment.class)) { + V1Deployment deployment = api.readNamespacedDeployment(name, namespace, null, null, null); + deploymentViewHistory(deployment, api); + } else if (apiTypeClass.equals(V1DaemonSet.class)) { + V1DaemonSet daemonSet = api.readNamespacedDaemonSet(name, namespace, null, null, null); + daemonSetViewHistory(daemonSet, api); + } else { + throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass); + } + return histories; + } catch (ApiException ex) { + throw new KubectlException(ex); + } + } + + public KubectlRolloutHistoryRevision revision(int revision) { + this.revision = revision; + return new KubectlRolloutHistoryRevision(); + } + + private void validate() throws KubectlException { + StringBuilder msg = new StringBuilder(); + if (name == null) { + msg.append("Missing name, "); + } + if (namespace == null) { + msg.append("Missing namespace, "); + } + if (revision < 0) { + msg.append("revision must be a positive integer: ").append(revision); + } + if (msg.length() > 0) { + throw new KubectlException(msg.toString()); + } + } + + private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throws ApiException { + List allOldRSs = new ArrayList<>(); + List oldRSs = new ArrayList<>(); + V1ReplicaSet newRs = DeploymentHelper.getAllReplicaSets(deployment, api, oldRSs, allOldRSs); + if (newRs != null) { + allOldRSs.add(newRs); + } + Map historyInfo = new HashMap<>(); + for (V1ReplicaSet rs : allOldRSs) { + Long v = DeploymentHelper.revision(rs.getMetadata()); + historyInfo.put(v, rs.getSpec().getTemplate()); + String changeCause = getChangeCause(rs.getMetadata()); + if (historyInfo.get(v).getMetadata().getAnnotations() == null) { + historyInfo.get(v).getMetadata().setAnnotations(new HashMap<>()); + } + if (changeCause != null && changeCause.length() > 0) { + historyInfo + .get(v) + .getMetadata() + .getAnnotations() + .put(CHANGE_CAUSE_ANNOTATION, changeCause); + } + } + + if (revision > 0) { + // get details of a specific revision + V1PodTemplateSpec template = historyInfo.get(revision); + if (template == null) { + throw new ApiException("unable to find the specified revision " + revision); + } + this.template = template; + return; + } + List revisions = new ArrayList<>(historyInfo.keySet()); + revisions.sort(Long::compareTo); + for (Long revision : revisions) { + String changeCause = + historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION); + if (changeCause == null || changeCause.isEmpty()) { + changeCause = ""; + } + histories.add(new History(revision, changeCause)); + } + } + + private void daemonSetViewHistory(V1DaemonSet daemonSet, AppsV1Api api) + throws ApiException, KubectlException { + LabelSelector selector = LabelSelector.parse(daemonSet.getSpec().getSelector()); + List historyList = controlledHistory(api, daemonSet, selector); + parseHistory( + historyList, + history -> { + V1DaemonSet dsOfHistory = applyDaemonSetHistory(history); + return dsOfHistory.getSpec().getTemplate(); + }); + } + + private V1DaemonSet applyDaemonSetHistory(V1ControllerRevision history) + throws KubectlException, ApiException { + String patch = apiClient.getJSON().serialize(history.getData()); + return (V1DaemonSet) + PatchHelper.dryRunStrategyMergePatch(getGenericApi(), patch, namespace, name); + } + + private void parseHistory(List historyList, PodTemplateParser parser) + throws ApiException, KubectlException { + Map historyInfo = new HashMap<>(); + for (V1ControllerRevision history : historyList) { + historyInfo.put(history.getRevision(), history); + } + + if (revision > 0) { + V1ControllerRevision history = historyInfo.get(revision); + if (history == null) { + throw new ApiException("unable to find the specified revision " + revision); + } + template = parser.parse(history); + return; + } + + List revisions = new ArrayList<>(historyInfo.keySet()); + revisions.sort(Long::compareTo); + for (Long revision : revisions) { + String changeCause = + historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION); + if (changeCause == null || changeCause.isEmpty()) { + changeCause = ""; + } + histories.add(new History(revision, changeCause)); + } + } + + // controlledHistories returns all ControllerRevisions in namespace that selected by selector + // and + // owned by accessor + private List controlledHistory( + AppsV1Api api, KubernetesObject accessor, LabelSelector selector) throws ApiException { + List result = new ArrayList<>(); + V1ControllerRevisionList historyList = + api.listNamespacedControllerRevision( + namespace, null, null, null, null, selector.toString(), null, null, null, null, null); + for (V1ControllerRevision history : historyList.getItems()) { + if (isControlledBy(history, accessor)) { + result.add(history); + } + } + return result; + } + + private boolean isControlledBy(KubernetesObject obj, KubernetesObject owner) { + return obj.getMetadata().getOwnerReferences().stream() + .filter(r -> r.getController() != null && r.getController()) + .findAny() + .map(v1OwnerReference -> v1OwnerReference.getUid().equals(owner.getMetadata().getUid())) + .orElse(false); + } + + // getChangeCause returns the change-cause annotation of the input object + private String getChangeCause(V1ObjectMeta meta) { + return meta.getAnnotations().get(CHANGE_CAUSE_ANNOTATION); + } + + class KubectlRolloutHistoryRevision + extends Kubectl.ResourceBuilder + implements Kubectl.Executable { + + KubectlRolloutHistoryRevision() { + super(KubectlRolloutHistory.this.apiTypeClass); + } + + @Override + public KubectlRolloutHistoryRevision name(String name) { + KubectlRolloutHistory.this.name(name); + return super.name(name); + } + + @Override + public KubectlRolloutHistoryRevision namespace(String namespace) { + KubectlRolloutHistory.this.namespace(namespace); + return super.namespace(namespace); + } + + @Override + public KubectlRolloutHistoryRevision apiClient(ApiClient apiClient) { + KubectlRolloutHistory.this.apiClient(apiClient); + return super.apiClient(apiClient); + } + + @Override + public KubectlRolloutHistoryRevision skipDiscovery() { + KubectlRolloutHistory.this.skipDiscovery(); + return super.skipDiscovery(); + } + + @Override + public V1PodTemplateSpec execute() throws KubectlException { + KubectlRolloutHistory.this.execute(); + return KubectlRolloutHistory.this.template; + } + } + } +} diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistory.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistory.java deleted file mode 100644 index 61357d3755..0000000000 --- a/extended/src/main/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistory.java +++ /dev/null @@ -1,161 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package io.kubernetes.client.extended.kubectl; - -import io.kubernetes.client.common.KubernetesObject; -import io.kubernetes.client.extended.kubectl.exception.KubectlException; -import io.kubernetes.client.extended.kubectl.util.deployment.DeploymentHelper; -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.AppsV1Api; -import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.openapi.models.V1PodTemplateSpec; -import io.kubernetes.client.openapi.models.V1ReplicaSet; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class KubectlRolloutHistory - extends Kubectl.ResourceBuilder> - implements Kubectl.Executable { - - public static final String CHANGE_CAUSE_ANNOTATION = "kubernetes.io/change-cause"; - - public V1PodTemplateSpec getTemplate() { - return template; - } - - public List getHistories() { - return histories; - } - - public static class History { - private final long revision; - private final String changeCause; - - public long getRevision() { - return revision; - } - - public String getChangeCause() { - return changeCause; - } - - public History(long revision, String changeCause) { - this.revision = revision; - this.changeCause = changeCause; - } - - @Override - public String toString() { - return "{revision :" + revision + ", changeCause :" + changeCause + "}"; - } - } - - private long revision; - - private final List histories; - - private V1PodTemplateSpec template; - - @Override - public ApiType execute() throws KubectlException { - validate(); - AppsV1Api api = new AppsV1Api(this.apiClient); - try { - if (apiTypeClass.equals(V1Deployment.class)) { - V1Deployment deployment = api.readNamespacedDeployment(name, namespace, null, null, null); - deploymentViewHistory(deployment, api); - return (ApiType) deployment; - } else { - throw new KubectlException("Unsupported class for rollout history: " + apiTypeClass); - } - } catch (ApiException ex) { - throw new KubectlException(ex); - } - } - - KubectlRolloutHistory(Class apiTypeClass) { - super(apiTypeClass); - revision = 0; - histories = new ArrayList<>(); - } - - public KubectlRolloutHistory revision(int revision) { - this.revision = revision; - return this; - } - - private void validate() throws KubectlException { - StringBuilder msg = new StringBuilder(); - if (name == null) { - msg.append("Missing name, "); - } - if (namespace == null) { - msg.append("Missing namespace, "); - } - if (revision < 0) { - msg.append("revision must be a positive integer: ").append(revision); - } - if (msg.length() > 0) { - throw new KubectlException(msg.toString()); - } - } - - private void deploymentViewHistory(V1Deployment deployment, AppsV1Api api) throws ApiException { - List allOldRSs = new ArrayList<>(); - List oldRSs = new ArrayList<>(); - V1ReplicaSet newRs = DeploymentHelper.getAllReplicaSets(deployment, api, oldRSs, allOldRSs); - if (newRs != null) { - allOldRSs.add(newRs); - } - Map historyInfo = new HashMap<>(); - for (V1ReplicaSet rs : allOldRSs) { - Long v = DeploymentHelper.revision(rs.getMetadata()); - historyInfo.put(v, rs.getSpec().getTemplate()); - String changeCause = getChangeCause(rs.getMetadata()); - if (historyInfo.get(v).getMetadata().getAnnotations() == null) { - historyInfo.get(v).getMetadata().setAnnotations(new HashMap<>()); - } - if (changeCause != null && changeCause.length() > 0) { - historyInfo.get(v).getMetadata().getAnnotations().put(CHANGE_CAUSE_ANNOTATION, changeCause); - } - } - - if (revision > 0) { - // get details of a specific revision - V1PodTemplateSpec template = historyInfo.get(revision); - if (template == null) { - throw new ApiException("unable to find the specified revision " + revision); - } - this.template = template; - return; - } - List revisions = new ArrayList<>(historyInfo.keySet()); - revisions.sort(Long::compareTo); - for (Long revision : revisions) { - String changeCause = - historyInfo.get(revision).getMetadata().getAnnotations().get(CHANGE_CAUSE_ANNOTATION); - if (changeCause == null || changeCause.isEmpty()) { - changeCause = ""; - } - histories.add(new History(revision, changeCause)); - } - } - - // getChangeCause returns the change-cause annotation of the input object - private static String getChangeCause(V1ObjectMeta meta) { - return meta.getAnnotations().get(CHANGE_CAUSE_ANNOTATION); - } -} diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/PatchHelper.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/PatchHelper.java new file mode 100644 index 0000000000..570a671ca2 --- /dev/null +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/PatchHelper.java @@ -0,0 +1,42 @@ +/* +Copyright 2021 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.custom.V1Patch; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.PatchOptions; + +class PatchHelper { + public static + ApiType dryRunStrategyMergePatch( + GenericKubernetesApi genericApi, + String patch, + String namespace, + String name) + throws ApiException { + PatchOptions options = new PatchOptions(); + options.setDryRun("All"); + return genericApi + .patch( + namespace, + name, + V1Patch.PATCH_FORMAT_STRATEGIC_MERGE_PATCH, + new V1Patch(patch), + options) + .throwsApiException() + .getObject(); + } +} diff --git a/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistoryTest.java b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistoryTest.java deleted file mode 100644 index fea056a79b..0000000000 --- a/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutHistoryTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* -Copyright 2021 The Kubernetes Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package io.kubernetes.client.extended.kubectl; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.Assert.assertThrows; - -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.github.tomakehurst.wiremock.matching.EqualToPattern; -import io.kubernetes.client.extended.kubectl.exception.KubectlException; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.models.V1Deployment; -import io.kubernetes.client.util.ClientBuilder; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -public class KubectlRolloutHistoryTest { - - private ApiClient apiClient; - - @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); - - private static final String DEPLOYMENT = - KubectlRolloutHistoryTest.class.getClassLoader().getResource("deployment.json").getPath(); - - private static final String REPLICASET_LIST = - KubectlRolloutHistoryTest.class - .getClassLoader() - .getResource("replicaset-list.json") - .getPath(); - - @Before - public void setup() throws IOException { - apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build(); - } - - @Test - public void testKubectlRolloutHistoryDeploymentShouldWork() throws KubectlException, IOException { - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); - KubectlRolloutHistory rolloutHistory = - Kubectl.rolloutHistory(V1Deployment.class) - .apiClient(apiClient) - .name("foo") - .namespace("default"); - rolloutHistory.execute(); - wireMockRule.verify( - 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")))); - wireMockRule.verify( - 1, - getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets"))) - .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); - Assert.assertEquals(3, rolloutHistory.getHistories().size()); - } - - @Test - public void testKubectlRolloutHistoryDeploymentWithRevisionShouldWork() - throws KubectlException, IOException { - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); - KubectlRolloutHistory rolloutHistory = - Kubectl.rolloutHistory(V1Deployment.class) - .apiClient(apiClient) - .name("foo") - .namespace("default") - .revision(3); - rolloutHistory.execute(); - wireMockRule.verify( - 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")))); - wireMockRule.verify( - 1, - getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets"))) - .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); - Assert.assertNotNull(rolloutHistory.getTemplate()); - } - - @Test - public void testKubectlRolloutHistoryWithInvalidRevisionShouldThrow() throws IOException { - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); - wireMockRule.stubFor( - get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) - .willReturn( - aResponse() - .withStatus(200) - .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); - KubectlRolloutHistory rolloutHistory = - Kubectl.rolloutHistory(V1Deployment.class) - .apiClient(apiClient) - .name("foo") - .namespace("default") - .revision(999); - assertThrows(KubectlException.class, rolloutHistory::execute); - rolloutHistory.revision(-1); - assertThrows(KubectlException.class, rolloutHistory::execute); - } -} From ade222fa87b1c4f95b281bf5c50980d66e10b2c2 Mon Sep 17 00:00:00 2001 From: Never <897013703@qq.com> Date: Fri, 20 Aug 2021 15:16:50 +0800 Subject: [PATCH 2/2] test : kubectl rollout history for daemonset --- .../extended/kubectl/KubectlRolloutTest.java | 253 ++++++++++++++++ .../daemonset-controllerrevision-list.json | 285 ++++++++++++++++++ extended/src/test/resources/daemonset.json | 156 ++++++++++ .../src/test/resources/patched-daemonset.json | 170 +++++++++++ 4 files changed, 864 insertions(+) create mode 100644 extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutTest.java create mode 100644 extended/src/test/resources/daemonset-controllerrevision-list.json create mode 100644 extended/src/test/resources/daemonset.json create mode 100644 extended/src/test/resources/patched-daemonset.json diff --git a/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutTest.java b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutTest.java new file mode 100644 index 0000000000..85aae082a2 --- /dev/null +++ b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlRolloutTest.java @@ -0,0 +1,253 @@ +/* +Copyright 2021 The Kubernetes Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package io.kubernetes.client.extended.kubectl; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.patch; +import static com.github.tomakehurst.wiremock.client.WireMock.patchRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertThrows; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.matching.EqualToPattern; +import io.kubernetes.client.extended.kubectl.exception.KubectlException; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.models.V1DaemonSet; +import io.kubernetes.client.openapi.models.V1DaemonSetList; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1DeploymentList; +import io.kubernetes.client.openapi.models.V1PodTemplateSpec; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.ModelMapper; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class KubectlRolloutTest { + + private ApiClient apiClient; + + @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + private static final String DEPLOYMENT = + KubectlRolloutTest.class.getClassLoader().getResource("deployment.json").getPath(); + + private static final String REPLICASET_LIST = + KubectlRolloutTest.class.getClassLoader().getResource("replicaset-list.json").getPath(); + + private static final String DAEMON_SET = + KubectlRolloutTest.class.getClassLoader().getResource("daemonset.json").getPath(); + + private static final String PATCHED_DAEMON_SET = + KubectlRolloutTest.class.getClassLoader().getResource("patched-daemonset.json").getPath(); + + private static final String DAEMON_SET_CONTROLLER_REVISION_LIST = + KubectlRolloutTest.class + .getClassLoader() + .getResource("daemonset-controllerrevision-list.json") + .getPath(); + + @Before + public void setup() throws IOException { + ModelMapper.addModelMap( + "apps", + "v1", + "Deployment", + "deployments", + true, + V1Deployment.class, + V1DeploymentList.class); + ModelMapper.addModelMap( + "apps", "v1", "DaemonSet", "daemonsets", true, V1DaemonSet.class, V1DaemonSetList.class); + apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build(); + } + + @Test + public void testKubectlRolloutHistoryDeploymentShouldWork() throws KubectlException, IOException { + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); + List histories = + Kubectl.rollout(V1Deployment.class) + .history() + .apiClient(apiClient) + .name("foo") + .namespace("default") + .skipDiscovery() + .execute(); + + wireMockRule.verify( + 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")))); + wireMockRule.verify( + 1, + getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets"))) + .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); + Assert.assertEquals(3, histories.size()); + } + + @Test + public void testKubectlRolloutHistoryDeploymentWithRevisionShouldWork() + throws KubectlException, IOException { + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); + V1PodTemplateSpec template = + Kubectl.rollout(V1Deployment.class) + .history() + .apiClient(apiClient) + .name("foo") + .namespace("default") + .revision(3) + .skipDiscovery() + .execute(); + wireMockRule.verify( + 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")))); + wireMockRule.verify( + 1, + getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets"))) + .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); + Assert.assertNotNull(template); + } + + @Test + public void testKubectlRolloutHistoryDaemonSetShouldWork() throws KubectlException, IOException { + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(DAEMON_SET)))))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions")) + .willReturn( + aResponse() + .withStatus(200) + .withBody( + new String( + Files.readAllBytes(Paths.get(DAEMON_SET_CONTROLLER_REVISION_LIST)))))); + List histories = + Kubectl.rollout(V1DaemonSet.class) + .history() + .apiClient(apiClient) + .name("foo") + .namespace("default") + .skipDiscovery() + .execute(); + wireMockRule.verify( + 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo")))); + wireMockRule.verify( + 1, + getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions"))) + .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); + Assert.assertEquals(3, histories.size()); + } + + @Test + public void testKubectlRolloutHistoryDaemonSetWithRevisionShouldWork() + throws KubectlException, IOException { + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(DAEMON_SET)))))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions")) + .willReturn( + aResponse() + .withStatus(200) + .withBody( + new String( + Files.readAllBytes(Paths.get(DAEMON_SET_CONTROLLER_REVISION_LIST)))))); + wireMockRule.stubFor( + patch(urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(PATCHED_DAEMON_SET)))))); + V1PodTemplateSpec template = + Kubectl.rollout(V1DaemonSet.class) + .history() + .apiClient(apiClient) + .name("foo") + .namespace("default") + .revision(2) + .skipDiscovery() + .execute(); + wireMockRule.verify( + 1, getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo")))); + wireMockRule.verify( + 1, + getRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/controllerrevisions"))) + .withQueryParam("labelSelector", new EqualToPattern("app = bar"))); + wireMockRule.verify( + 1, + patchRequestedFor((urlPathEqualTo("/apis/apps/v1/namespaces/default/daemonsets/foo"))) + .withQueryParam("dryRun", new EqualToPattern("All"))); + Assert.assertNotNull(template); + } + + @Test + public void testKubectlRolloutHistoryWithInvalidRevisionShouldThrow() throws IOException { + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/deployments/foo")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(DEPLOYMENT)))))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(new String(Files.readAllBytes(Paths.get(REPLICASET_LIST)))))); + + assertThrows( + KubectlException.class, + () -> + Kubectl.rollout(V1Deployment.class) + .history() + .apiClient(apiClient) + .name("foo") + .namespace("default") + .revision(999) + .skipDiscovery() + .execute()); + } +} diff --git a/extended/src/test/resources/daemonset-controllerrevision-list.json b/extended/src/test/resources/daemonset-controllerrevision-list.json new file mode 100644 index 0000000000..e8f99595d0 --- /dev/null +++ b/extended/src/test/resources/daemonset-controllerrevision-list.json @@ -0,0 +1,285 @@ +{ + "apiVersion": "apps/v1", + "items": [{ + "data": { + "spec": { + "template": { + "$patch": "replace", + "metadata": { + "labels": { + "app": "bar" + } + }, + "spec": { + "containers": [{ + "args": ["1000"], + "command": ["sleep"], + "image": "busybox:1.31.1", + "imagePullPolicy": "IfNotPresent", + "name": "busybox-host", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + }], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30.0 + } + } + } + }, + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "1", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{\"kubernetes.io/change-cause\":\"kubectl.exe apply --filename\u003d./daemonSet.Yaml --record\u003dtrue\"},\"name\":\"foo\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"bar\"}},\"spec\":{\"containers\":[{\"args\":[\"1000\"],\"command\":[\"sleep\"],\"image\":\"busybox:1.31.1\",\"name\":\"busybox-host\"}]}}}}\n", + "kubernetes.io/change-cause": "kubectl.exe apply --filename\u003d./daemonSet.Yaml --record\u003dtrue" + }, + "creationTimestamp": "2021-08-13T07:13:06.000000Z", + "labels": { + "app": "bar", + "controller-revision-hash": "69b77bc98d" + }, + "managedFields": [{ + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:data": {}, + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deprecated.daemonset.template.generation": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:kubernetes.io/change-cause": {} + }, + "f:labels": { + ".": {}, + "f:app": {}, + "f:controller-revision-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"d3774f2a-e492-4968-a888-23b71e4c13a9\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:revision": {} + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2021-08-13T07:13:06.000000Z" + }], + "name": "foo-69b77bc98d", + "namespace": "default", + "ownerReferences": [{ + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "DaemonSet", + "name": "foo", + "uid": "d3774f2a-e492-4968-a888-23b71e4c13a9" + }], + "resourceVersion": "96595", + "uid": "e684a7d1-34c1-4f59-8ebe-f026f5fb9910" + }, + "revision": 1 + }, { + "data": { + "spec": { + "template": { + "$patch": "replace", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": "2021-08-13T16:53:21+08:00" + }, + "labels": { + "app": "bar" + } + }, + "spec": { + "containers": [{ + "args": ["1000"], + "command": ["sleep"], + "image": "busybox:1.31.1", + "imagePullPolicy": "IfNotPresent", + "name": "busybox-host", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + }], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30.0 + } + } + } + }, + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "3", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"name\":\"foo\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"bar\"}},\"spec\":{\"containers\":[{\"args\":[\"1000\"],\"command\":[\"sleep\"],\"image\":\"busybox:1.31.1\",\"name\":\"busybox-host\"}]}}}}\n" + }, + "creationTimestamp": "2021-08-13T08:53:21.000000Z", + "labels": { + "app": "bar", + "controller-revision-hash": "797f8dc6b5" + }, + "managedFields": [{ + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:data": {}, + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deprecated.daemonset.template.generation": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:app": {}, + "f:controller-revision-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"d3774f2a-e492-4968-a888-23b71e4c13a9\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:revision": {} + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2021-08-13T08:53:21.000000Z" + }], + "name": "foo-797f8dc6b5", + "namespace": "default", + "ownerReferences": [{ + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "DaemonSet", + "name": "foo", + "uid": "d3774f2a-e492-4968-a888-23b71e4c13a9" + }], + "resourceVersion": "101207", + "uid": "37be558a-ba1e-4182-bc5a-ca0ed324cdf7" + }, + "revision": 3 + }, { + "data": { + "spec": { + "template": { + "$patch": "replace", + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": "2021-08-13T15:14:35+08:00" + }, + "labels": { + "app": "bar" + } + }, + "spec": { + "containers": [{ + "args": ["1000"], + "command": ["sleep"], + "image": "busybox:1.31.1", + "imagePullPolicy": "IfNotPresent", + "name": "busybox-host", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + }], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30.0 + } + } + } + }, + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "2", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{\"kubernetes.io/change-cause\":\"kubectl.exe apply --filename\u003d./daemonSet.Yaml --record\u003dtrue\"},\"name\":\"foo\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"bar\"}},\"spec\":{\"containers\":[{\"args\":[\"1000\"],\"command\":[\"sleep\"],\"image\":\"busybox:1.31.1\",\"name\":\"busybox-host\"}]}}}}\n", + "kubernetes.io/change-cause": "kubectl.exe apply --filename\u003d./daemonSet.Yaml --record\u003dtrue" + }, + "creationTimestamp": "2021-08-13T07:14:35.000000Z", + "labels": { + "app": "bar", + "controller-revision-hash": "7f9dcff8b8" + }, + "managedFields": [{ + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:data": {}, + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deprecated.daemonset.template.generation": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {}, + "f:kubernetes.io/change-cause": {} + }, + "f:labels": { + ".": {}, + "f:app": {}, + "f:controller-revision-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"d3774f2a-e492-4968-a888-23b71e4c13a9\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:revision": {} + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2021-08-13T07:14:35.000000Z" + }], + "name": "foo-7f9dcff8b8", + "namespace": "default", + "ownerReferences": [{ + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "DaemonSet", + "name": "foo", + "uid": "d3774f2a-e492-4968-a888-23b71e4c13a9" + }], + "resourceVersion": "96669", + "uid": "b8f45530-23a9-4fea-94b5-778f65117c43" + }, + "revision": 2 + }], + "kind": "ControllerRevisionList", + "metadata": { + "resourceVersion": "101602" + } +} \ No newline at end of file diff --git a/extended/src/test/resources/daemonset.json b/extended/src/test/resources/daemonset.json new file mode 100644 index 0000000000..3e1fa72fec --- /dev/null +++ b/extended/src/test/resources/daemonset.json @@ -0,0 +1,156 @@ +{ + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "3", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"name\":\"foo\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"bar\"}},\"spec\":{\"containers\":[{\"args\":[\"1000\"],\"command\":[\"sleep\"],\"image\":\"busybox:1.31.1\",\"name\":\"busybox-host\"}]}}}}\n" + }, + "creationTimestamp": "2021-08-13T07:13:06.000000Z", + "generation": 3, + "managedFields": [{ + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:template": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/restartedAt": {} + } + } + } + } + }, + "manager": "kubectl-rollout", + "operation": "Update", + "time": "2021-08-13T07:14:35.000000Z" + }, { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deprecated.daemonset.template.generation": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + } + }, + "f:spec": { + "f:revisionHistoryLimit": {}, + "f:selector": {}, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"busybox-host\"}": { + ".": {}, + "f:args": {}, + "f:command": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "f:updateStrategy": { + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + } + } + }, + "manager": "kubectl-client-side-apply", + "operation": "Update", + "time": "2021-08-13T07:15:15.000000Z" + }, { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:currentNumberScheduled": {}, + "f:desiredNumberScheduled": {}, + "f:numberAvailable": {}, + "f:numberReady": {}, + "f:observedGeneration": {}, + "f:updatedNumberScheduled": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2021-08-13T08:53:57.000000Z" + }], + "name": "foo", + "namespace": "default", + "resourceVersion": "101251", + "uid": "d3774f2a-e492-4968-a888-23b71e4c13a9" + }, + "spec": { + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app": "bar" + } + }, + "template": { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": "2021-08-13T16:53:21+08:00" + }, + "labels": { + "app": "bar" + } + }, + "spec": { + "containers": [{ + "args": ["1000"], + "command": ["sleep"], + "image": "busybox:1.31.1", + "imagePullPolicy": "IfNotPresent", + "name": "busybox-host", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + }], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + }, + "updateStrategy": { + "rollingUpdate": { + "maxUnavailable": 1 + }, + "type": "RollingUpdate" + } + }, + "status": { + "currentNumberScheduled": 1, + "desiredNumberScheduled": 1, + "numberAvailable": 1, + "numberMisscheduled": 0, + "numberReady": 1, + "observedGeneration": 3, + "updatedNumberScheduled": 1 + } +} \ No newline at end of file diff --git a/extended/src/test/resources/patched-daemonset.json b/extended/src/test/resources/patched-daemonset.json new file mode 100644 index 0000000000..80617f93fa --- /dev/null +++ b/extended/src/test/resources/patched-daemonset.json @@ -0,0 +1,170 @@ +{ + "apiVersion": "apps/v1", + "kind": "DaemonSet", + "metadata": { + "annotations": { + "deprecated.daemonset.template.generation": "4", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"DaemonSet\",\"metadata\":{\"annotations\":{},\"name\":\"foo\",\"namespace\":\"default\"},\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"bar\"}},\"spec\":{\"containers\":[{\"args\":[\"1000\"],\"command\":[\"sleep\"],\"image\":\"busybox:1.31.1\",\"name\":\"busybox-host\"}]}}}}\n" + }, + "creationTimestamp": "2021-08-13T07:13:06.000000Z", + "generation": 4, + "managedFields": [{ + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:template": { + "f:metadata": { + "f:annotations": {} + } + } + } + }, + "manager": "kubectl-rollout", + "operation": "Update", + "time": "2021-08-13T07:14:35.000000Z" + }, { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deprecated.daemonset.template.generation": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + } + }, + "f:spec": { + "f:revisionHistoryLimit": {}, + "f:selector": {}, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"busybox-host\"}": { + ".": {}, + "f:args": {}, + "f:command": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "f:updateStrategy": { + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + } + } + }, + "manager": "kubectl-client-side-apply", + "operation": "Update", + "time": "2021-08-13T07:15:15.000000Z" + }, { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:currentNumberScheduled": {}, + "f:desiredNumberScheduled": {}, + "f:numberAvailable": {}, + "f:numberReady": {}, + "f:observedGeneration": {}, + "f:updatedNumberScheduled": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2021-08-17T07:48:30.000000Z" + }, { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:template": { + "f:metadata": { + "f:annotations": { + "f:kubectl.kubernetes.io/restartedAt": {} + } + } + } + } + }, + "manager": "Kubernetes Java Client", + "operation": "Update", + "time": "2021-08-17T08:03:53.000000Z" + }], + "name": "foo", + "namespace": "default", + "resourceVersion": "108631", + "uid": "d3774f2a-e492-4968-a888-23b71e4c13a9" + }, + "spec": { + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app": "bar" + } + }, + "template": { + "metadata": { + "annotations": { + "kubectl.kubernetes.io/restartedAt": "2021-08-13T15:14:35+08:00" + }, + "labels": { + "app": "bar" + } + }, + "spec": { + "containers": [{ + "args": ["1000"], + "command": ["sleep"], + "image": "busybox:1.31.1", + "imagePullPolicy": "IfNotPresent", + "name": "busybox-host", + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + }], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + }, + "updateStrategy": { + "rollingUpdate": { + "maxUnavailable": 1 + }, + "type": "RollingUpdate" + } + }, + "status": { + "currentNumberScheduled": 1, + "desiredNumberScheduled": 1, + "numberAvailable": 1, + "numberMisscheduled": 0, + "numberReady": 1, + "observedGeneration": 3, + "updatedNumberScheduled": 1 + } +} \ No newline at end of file