From b24cf5099d26fd2304ef69f0371a10b98bfef05e Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Tue, 3 Feb 2026 17:18:38 +0100 Subject: [PATCH 1/3] feat: add isSameResource on HasMetadata --- .../kubernetes/api/model/HasMetadata.java | 74 +++++++++++++++ .../kubernetes/api/model/HasMetadataTest.java | 89 +++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java index 715e3ea2a32..f0341926276 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java @@ -539,4 +539,78 @@ static ObjectMetaBuilder initMetadataBuilderNameAndNamespaceFrom(HasMetadata ori } return metaBuilder; } + + /** + * Same as {@code isSameResource(other, false)} + * + * @see #isSameResource(HasMetadata, boolean) + */ + default boolean isSameResource(HasMetadata other) { + return isSameResource(other, false); + } + + /** + * Whether the specified HasMetadata represents the same resource on the cluster as this one, i.e. kind, uid, name and + * namespace all point to the same logical resource. + * + *

+ * Note that this doesn't make any other assumption regarding other fields, and in particular, + * {@code resource1.isSameResource(resource2)} does NOT imply {@code resource1.equals(resource2)}. + *

+ * + * @param other the HasMetadata to check + * @param strict whether the checks should be strict: + * + * The only exceptions to this is if the two resources point to the same object (i.e. {@code this == other}) or if the + * UID is set, in which case stricter checks will not be enforced and similarity will only be assessed on the objects' + * identity (either Java identity or via UID). + * @return {@code true} if the specified HasMetadata points to the same cluster resource, {@code false} otherwise + */ + default boolean isSameResource(HasMetadata other, boolean strict) { + if (this == other) { + return true; + } + + if (other == null) { + return false; + } + + final var metadata = this.getMetadata(); + final var otherMetadata = other.getMetadata(); + if (metadata == null || otherMetadata == null) { + return false; + } + + // check UID first + final var uid = metadata.getUid(); + if (uid != null) { + return uid.equals(otherMetadata.getUid()); + } + + final var kind = getKind(); + final var differentKind = !Objects.equals(other.getKind(), kind); + if (differentKind) { + return false; + } + + // in strict mode, the kind needs to be set + if (strict && kind == null) { + return false; + } + + final var sameNameAndNS = Objects.equals(metadata.getName(), otherMetadata.getName()) + && Objects.equals(metadata.getNamespace(), otherMetadata.getNamespace()); + if (!sameNameAndNS) { + return false; + } + + if (strict) { + return Objects.equals(metadata.getResourceVersion(), otherMetadata.getResourceVersion()); + } else { + return true; + } + } } diff --git a/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java b/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java index 0a649aa6c49..bfd7e46f9eb 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java @@ -18,6 +18,7 @@ import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Plural; import io.fabric8.kubernetes.model.annotation.Version; +import io.sundr.deps.org.apache.commons.lang.RandomStringUtils; import org.junit.jupiter.api.Test; import java.util.Optional; @@ -316,6 +317,72 @@ void initNameAndNamespaceFromWithMissingNamespaceShouldFail() { assertEquals(HasMetadata.REQUIRES_NON_NULL_NAMESPACE, exception.getMessage()); } + @Test + void isSameResource() { + HasMetadata one = new TestHasMetadata(); + assertFalse(one.isSameResource(null)); + + HasMetadata two = new TestHasMetadata(); + assertTrue(one.isSameResource(one)); + assertTrue(one.isSameResource(one, true)); // should normally be false but since we're comparing the same object, it should always be true + + assertFalse(one.isSameResource(two)); // metadata is null so cannot check if resources are the same + assertFalse(one.isSameResource(two, true)); + + // generated name and namespace + TestHM t1 = new TestHM(); + TestHM t2 = new TestHM(); + checkDifferent(t1, t2); + + // if UID is set, nothing else is checked, regardless of strict mode or not + final var uid = RandomStringUtils.randomAlphanumeric(10); + t1.getMetadata().setUid(uid); + checkDifferent(t1, t2); + t2.getMetadata().setUid(uid); + baseTests(t1, t2); + assertTrue(t1.isSameResource(t2, true)); + assertTrue(t2.isSameResource(t1, true)); + + t1 = new TestHM(); + t2 = new TestHM(t1); // initialized with same name and namespace + baseTests(t1, t2); + assertTrue(t1.isSameResource(t2, true)); + assertTrue(t2.isSameResource(t1, true)); + + t1.getMetadata().setResourceVersion("t1"); + t2.getMetadata().setResourceVersion("t1"); + baseTests(t1, t2); + assertTrue(t1.isSameResource(t2, true)); + assertTrue(t2.isSameResource(t1, true)); + + t2.getMetadata().setResourceVersion("t2"); + baseTests(t1, t2); + assertFalse(t1.isSameResource(t2, true)); + assertFalse(t2.isSameResource(t1, true)); + + t2.getMetadata().setName("otherName"); + checkDifferent(t1, t2); + + t1 = new TestHM(); + t2 = new TestHM(t1); // initialized with same name and namespace + t2.getMetadata().setNamespace("otherNamespace"); + checkDifferent(t1, t2); + } + + private static void checkDifferent(TestHM t1, TestHM t2) { + assertFalse(t1.isSameResource(t2)); + assertFalse(t2.isSameResource(t1)); + assertFalse(t1.isSameResource(t2, true)); + assertFalse(t2.isSameResource(t1, true)); + } + + private static void baseTests(HasMetadata t1, HasMetadata t2) { + assertTrue(t1.isSameResource(t1)); + assertTrue(t1.isSameResource(t1, true)); + assertTrue(t1.isSameResource(t2)); + assertTrue(t2.isSameResource(t1)); + } + static class TestHasMetadata implements HasMetadata { private ObjectMeta metadata; @@ -442,6 +509,11 @@ private static class Default implements HasMetadata { meta.setName(name); } + Default(String name, String namespace) { + this(name); + meta.setNamespace(namespace); + } + @Override public ObjectMeta getMetadata() { return meta; @@ -468,6 +540,23 @@ public void setApiVersion(String version) { } } + @Group("fabric8.io") + @Version("v1") + private static class TestHM extends Default { + public TestHM(TestHM other) { + super(other.getMetadata().getName(), other.getMetadata().getNamespace()); + } + + public TestHM() { + super(RandomStringUtils.randomAlphabetic(5), RandomStringUtils.randomAlphabetic(5)); + } + + @Override + public String getKind() { + return "TestHM"; + } + } + private static class OwnerNamespaced extends Owner implements Namespaced { } From 984ff54a7c79974ec97db50b045c914c2b7418ab Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 27 Feb 2026 17:08:37 +0100 Subject: [PATCH 2/3] fix: improper uid test, remove use of RandomStringUtils --- .../kubernetes/api/model/HasMetadataTest.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java b/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java index bfd7e46f9eb..5651b58aea3 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/test/java/io/fabric8/kubernetes/api/model/HasMetadataTest.java @@ -18,10 +18,11 @@ import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Plural; import io.fabric8.kubernetes.model.annotation.Version; -import io.sundr.deps.org.apache.commons.lang.RandomStringUtils; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.Optional; +import java.util.Random; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -335,7 +336,9 @@ void isSameResource() { checkDifferent(t1, t2); // if UID is set, nothing else is checked, regardless of strict mode or not - final var uid = RandomStringUtils.randomAlphanumeric(10); + t1 = new TestHM(); + t2 = new TestHM(t1); + final var uid = randomString(); t1.getMetadata().setUid(uid); checkDifferent(t1, t2); t2.getMetadata().setUid(uid); @@ -369,6 +372,12 @@ void isSameResource() { checkDifferent(t1, t2); } + private static String randomString() { + byte[] array = new byte[10]; + new Random().nextBytes(array); + return new String(array, StandardCharsets.UTF_8); + } + private static void checkDifferent(TestHM t1, TestHM t2) { assertFalse(t1.isSameResource(t2)); assertFalse(t2.isSameResource(t1)); @@ -548,7 +557,7 @@ public TestHM(TestHM other) { } public TestHM() { - super(RandomStringUtils.randomAlphabetic(5), RandomStringUtils.randomAlphabetic(5)); + super(randomString(), randomString()); } @Override From fcee67a322c73fe4d965e6e3771b7320403d1a75 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Fri, 27 Feb 2026 17:09:38 +0100 Subject: [PATCH 3/3] fix: asymmetrical check of uid --- .../java/io/fabric8/kubernetes/api/model/HasMetadata.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java index f0341926276..53598889267 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/HasMetadata.java @@ -584,10 +584,11 @@ default boolean isSameResource(HasMetadata other, boolean strict) { return false; } - // check UID first + // check UID first, if either is not null, if both are null, look at other fields final var uid = metadata.getUid(); - if (uid != null) { - return uid.equals(otherMetadata.getUid()); + final var otherUID = otherMetadata.getUid(); + if (uid != null || otherUID != null) { + return Objects.equals(uid, otherUID); } final var kind = getKind();