diff --git a/extended/src/main/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelper.java b/extended/src/main/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelper.java index f7b428de68..78f275f0f7 100644 --- a/extended/src/main/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelper.java +++ b/extended/src/main/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelper.java @@ -138,10 +138,14 @@ private static V1ReplicaSet findNewReplicaSet( */ private static boolean equalIgnoreHash(V1PodTemplateSpec template1, V1PodTemplateSpec template2) { if (!Objects.equals(template1.getSpec(), template2.getSpec())) return false; - V1ObjectMeta m1Copy = objectMetaDeepCopy(template1.getMetadata()); - V1ObjectMeta m2Copy = objectMetaDeepCopy(template2.getMetadata()); - m1Copy.getLabels().remove(DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY); - m2Copy.getLabels().remove(DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY); + V1ObjectMeta m1Copy = objectMetaDeepCopyOrEmpty(template1.getMetadata()); + V1ObjectMeta m2Copy = objectMetaDeepCopyOrEmpty(template2.getMetadata()); + if (m1Copy.getLabels() != null) { + m1Copy.getLabels().remove(DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY); + } + if (m2Copy.getLabels() != null) { + m2Copy.getLabels().remove(DEFAULT_DEPLOYMENT_UNIQUE_LABEL_KEY); + } return m1Copy.equals(m2Copy); } @@ -171,4 +175,17 @@ private static V1ObjectMeta objectMetaDeepCopy(V1ObjectMeta meta) { String data = Yaml.dump(meta); return Yaml.loadAs(data, V1ObjectMeta.class); } + + /** + * Same as {@link #objectMetaDeepCopy(V1ObjectMeta)}, but returns a fresh empty {@link + * V1ObjectMeta} instead of throwing when {@code meta} is {@code null}. A pod template's {@code + * metadata} field is optional, so callers comparing two templates need to treat a missing + * metadata block as equivalent to an empty one rather than failing. + */ + private static V1ObjectMeta objectMetaDeepCopyOrEmpty(V1ObjectMeta meta) { + if (meta == null) { + return new V1ObjectMeta(); + } + return objectMetaDeepCopy(meta); + } } diff --git a/extended/src/test/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelperTest.java b/extended/src/test/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelperTest.java index 92da1c2346..d6ad9e9c64 100644 --- a/extended/src/test/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelperTest.java +++ b/extended/src/test/java/io/kubernetes/client/extended/kubectl/util/deployment/DeploymentHelperTest.java @@ -88,6 +88,39 @@ void getAllReplicaSetsShouldWork() throws IOException, ApiException { assertThat(allOldRSes).hasSize(2); } + @Test + void getAllReplicaSetsShouldWorkWhenTemplateHasNoLabels() throws IOException, ApiException { + // Regression test: pod template metadata with no labels at all (so getLabels() is null) + // used to throw a NullPointerException in DeploymentHelper.equalIgnoreHash. + String deploymentJson = + "{\"metadata\":{\"name\":\"foo\",\"namespace\":\"default\",\"uid\":\"dep-uid\"}," + + "\"spec\":{\"selector\":{\"matchLabels\":{\"app\":\"bar\"}}," + + "\"template\":{\"spec\":{\"containers\":[{\"name\":\"c\",\"image\":\"busybox\"}]}}}}"; + String replicaSetListJson = + "{\"items\":[{\"metadata\":{\"name\":\"foo-rs\",\"namespace\":\"default\",\"uid\":\"rs-uid\"," + + "\"creationTimestamp\":\"2021-08-05T08:20:22.000000Z\"," + + "\"ownerReferences\":[{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"name\":\"foo\"," + + "\"uid\":\"dep-uid\",\"controller\":true}]}," + + "\"spec\":{\"replicas\":3,\"selector\":{\"matchLabels\":{\"app\":\"bar\"}}," + + "\"template\":{\"spec\":{\"containers\":[{\"name\":\"c\",\"image\":\"busybox\"}]}}}}]}"; + + apiServer.stubFor( + get(urlPathEqualTo("/apis/apps/v1/namespaces/default/replicasets")) + .willReturn(aResponse().withStatus(200).withBody(replicaSetListJson))); + AppsV1Api api = new AppsV1Api(this.apiClient); + + V1Deployment deployment = new JSON().deserialize(deploymentJson, V1Deployment.class); + List oldRSes = new ArrayList<>(); + List allOldRSes = new ArrayList<>(); + + V1ReplicaSet newRs = DeploymentHelper.getAllReplicaSets(deployment, api, oldRSes, allOldRSes); + + assertThat(newRs).isNotNull(); + assertThat(newRs.getMetadata().getName()).isEqualTo("foo-rs"); + assertThat(oldRSes).isEmpty(); + assertThat(allOldRSes).isEmpty(); + } + @Test void revisionShouldWork() throws IOException { V1ReplicaSetList replicaSetList =