Skip to content

Commit 3588780

Browse files
authored
feat: allow specifying plural form of GVK in GenericKubernetesDependentResource (#2515)
* feat: add getPlural method on GroupVersionKind Signed-off-by: Chris Laprun <[email protected]> * fix: make getPlural return Optional to show whether plural is known Signed-off-by: Chris Laprun <[email protected]> * refactor: add GroupVersionKindPlural class, used by GenericKubernetesDependentResource Signed-off-by: Chris Laprun <[email protected]> * refactor: clean up, cache already resolved GVKs Signed-off-by: Chris Laprun <[email protected]> --------- Signed-off-by: Chris Laprun <[email protected]>
1 parent 8d5831e commit 3588780

File tree

5 files changed

+177
-19
lines changed

5 files changed

+177
-19
lines changed

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/GroupVersionKind.java

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package io.javaoperatorsdk.operator.processing;
22

3+
import java.util.Map;
34
import java.util.Objects;
5+
import java.util.concurrent.ConcurrentHashMap;
46

57
import io.fabric8.kubernetes.api.model.HasMetadata;
68

79
public class GroupVersionKind {
810
private final String group;
911
private final String version;
1012
private final String kind;
13+
private final String apiVersion;
14+
protected final static Map<Class<? extends HasMetadata>, GroupVersionKind> CACHE =
15+
new ConcurrentHashMap<>();
1116

1217
public GroupVersionKind(String apiVersion, String kind) {
1318
this.kind = kind;
@@ -19,17 +24,23 @@ public GroupVersionKind(String apiVersion, String kind) {
1924
this.group = groupAndVersion[0];
2025
this.version = groupAndVersion[1];
2126
}
27+
this.apiVersion = apiVersion;
28+
}
29+
30+
public static GroupVersionKind gvkFor(Class<? extends HasMetadata> resourceClass) {
31+
return CACHE.computeIfAbsent(resourceClass, GroupVersionKind::computeGVK);
32+
}
33+
34+
private static GroupVersionKind computeGVK(Class<? extends HasMetadata> rc) {
35+
return new GroupVersionKind(HasMetadata.getGroup(rc),
36+
HasMetadata.getVersion(rc), HasMetadata.getKind(rc));
2237
}
2338

2439
public GroupVersionKind(String group, String version, String kind) {
2540
this.group = group;
2641
this.version = version;
2742
this.kind = kind;
28-
}
29-
30-
public static GroupVersionKind gvkFor(Class<? extends HasMetadata> resourceClass) {
31-
return new GroupVersionKind(HasMetadata.getGroup(resourceClass),
32-
HasMetadata.getVersion(resourceClass), HasMetadata.getKind(resourceClass));
43+
this.apiVersion = (group == null || group.isBlank()) ? version : group + "/" + version;
3344
}
3445

3546
public String getGroup() {
@@ -45,7 +56,7 @@ public String getKind() {
4556
}
4657

4758
public String apiVersion() {
48-
return group == null || group.isBlank() ? version : group + "/" + version;
59+
return apiVersion;
4960
}
5061

5162
@Override
@@ -55,20 +66,18 @@ public boolean equals(Object o) {
5566
if (o == null || getClass() != o.getClass())
5667
return false;
5768
GroupVersionKind that = (GroupVersionKind) o;
58-
return Objects.equals(group, that.group) && Objects.equals(version, that.version)
59-
&& Objects.equals(kind, that.kind);
69+
return Objects.equals(apiVersion, that.apiVersion) && Objects.equals(kind, that.kind);
6070
}
6171

6272
@Override
6373
public int hashCode() {
64-
return Objects.hash(group, version, kind);
74+
return Objects.hash(apiVersion, kind);
6575
}
6676

6777
@Override
6878
public String toString() {
6979
return "GroupVersionKind{" +
70-
"group='" + group + '\'' +
71-
", version='" + version + '\'' +
80+
"apiVersion='" + apiVersion + '\'' +
7281
", kind='" + kind + '\'' +
7382
'}';
7483
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88
public class GenericKubernetesDependentResource<P extends HasMetadata>
99
extends KubernetesDependentResource<GenericKubernetesResource, P> {
1010

11-
private final GroupVersionKind groupVersionKind;
11+
private final GroupVersionKindPlural groupVersionKind;
1212

1313
public GenericKubernetesDependentResource(GroupVersionKind groupVersionKind) {
14+
this(GroupVersionKindPlural.from(groupVersionKind));
15+
}
16+
17+
public GenericKubernetesDependentResource(GroupVersionKindPlural groupVersionKind) {
1418
super(GenericKubernetesResource.class);
1519
this.groupVersionKind = groupVersionKind;
1620
}
@@ -20,7 +24,7 @@ protected InformerConfiguration.InformerConfigurationBuilder<GenericKubernetesRe
2024
}
2125

2226
@SuppressWarnings("unused")
23-
public GroupVersionKind getGroupVersionKind() {
27+
public GroupVersionKindPlural getGroupVersionKind() {
2428
return groupVersionKind;
2529
}
2630
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
2+
3+
import java.util.Optional;
4+
5+
import io.fabric8.kubernetes.api.Pluralize;
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.processing.GroupVersionKind;
8+
9+
/**
10+
* An extension of {@link GroupVersionKind} that also records the associated plural form which is
11+
* useful when dealing with Kubernetes RBACs. Downstream projects might leverage that information.
12+
*/
13+
public class GroupVersionKindPlural extends GroupVersionKind {
14+
private final String plural;
15+
16+
protected GroupVersionKindPlural(String group, String version, String kind, String plural) {
17+
super(group, version, kind);
18+
this.plural = plural;
19+
}
20+
21+
protected GroupVersionKindPlural(String apiVersion, String kind, String plural) {
22+
super(apiVersion, kind);
23+
this.plural = plural;
24+
}
25+
26+
protected GroupVersionKindPlural(GroupVersionKind gvk, String plural) {
27+
this(gvk.getGroup(), gvk.getVersion(), gvk.getKind(),
28+
plural != null ? plural
29+
: (gvk instanceof GroupVersionKindPlural ? ((GroupVersionKindPlural) gvk).plural
30+
: null));
31+
}
32+
33+
/**
34+
* Creates a new GroupVersionKindPlural from the specified {@link GroupVersionKind}.
35+
*
36+
* @param gvk a {@link GroupVersionKind} from which to create a new GroupVersionKindPlural object
37+
* @return a new GroupVersionKindPlural object matching the specified {@link GroupVersionKind}
38+
*/
39+
public static GroupVersionKindPlural from(GroupVersionKind gvk) {
40+
return gvk instanceof GroupVersionKindPlural ? ((GroupVersionKindPlural) gvk)
41+
: gvkWithPlural(gvk, null);
42+
}
43+
44+
/**
45+
* Creates a new GroupVersionKindPlural based on the specified {@link GroupVersionKind} instance
46+
* but specifying a plural form to use as well.
47+
*
48+
* @param gvk the base {@link GroupVersionKind} from which to derive a new GroupVersionKindPlural
49+
* @param plural the plural form to use for the new instance or {@code null} if the default plural
50+
* form is desired. Note that the specified plural form will override any existing plural
51+
* form for the specified {@link GroupVersionKind} (in particular, if the specified
52+
* {@link GroupVersionKind} was already an instance of GroupVersionKindPlural, its plural
53+
* form will only be considered in the new instance if the specified plural form is
54+
* {@code null}
55+
* @return a new GroupVersionKindPlural derived from the specified {@link GroupVersionKind} and
56+
* plural form
57+
*/
58+
public static GroupVersionKindPlural gvkWithPlural(GroupVersionKind gvk, String plural) {
59+
return new GroupVersionKindPlural(gvk, plural);
60+
}
61+
62+
/**
63+
* Creates a new GroupVersionKindPlural instance extracting the information from the specified
64+
* {@link HasMetadata} implementation
65+
*
66+
* @param resourceClass the {@link HasMetadata} from which group, version, kind and plural form
67+
* are extracted
68+
* @return a new GroupVersionKindPlural instance based on the specified {@link HasMetadata}
69+
* implementation
70+
*/
71+
public static GroupVersionKindPlural gvkFor(Class<? extends HasMetadata> resourceClass) {
72+
final var gvk = GroupVersionKind.gvkFor(resourceClass);
73+
return gvkWithPlural(gvk, HasMetadata.getPlural(resourceClass));
74+
}
75+
76+
/**
77+
* Retrieves the default plural form for the specified kind.
78+
*
79+
* @param kind the kind for which we want to get the default plural form
80+
* @return the default plural form for the specified kind
81+
*/
82+
public static String getDefaultPluralFor(String kind) {
83+
// todo: replace by Fabric8 version when available, see
84+
// https://github.com/fabric8io/kubernetes-client/pull/6314
85+
return kind != null ? Pluralize.toPlural(kind.toLowerCase()) : null;
86+
}
87+
88+
/**
89+
* Returns the plural form associated with the kind if it has been provided explicitly (either
90+
* manually by the user, or determined from the associated resource class definition)
91+
*
92+
* @return {@link Optional#empty()} if the plural form was not provided explicitly, or the plural
93+
* form if it was provided explicitly
94+
*/
95+
public Optional<String> getPlural() {
96+
return Optional.ofNullable(plural);
97+
}
98+
99+
/**
100+
* Returns the plural form associated with the kind if it was provided or a default, computed form
101+
* via {@link #getDefaultPluralFor(String)} (which should correspond to the actual plural form in
102+
* most cases but might not always be correct, especially if the resource's creator defined an
103+
* exotic plural form via the CRD.
104+
*
105+
* @return the plural form associated with the kind if provided or a default plural form otherwise
106+
*/
107+
@SuppressWarnings("unused")
108+
public String getPluralOrDefault() {
109+
return getPlural().orElse(getDefaultPluralFor(getKind()));
110+
}
111+
}

Diff for: operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/GroupVersionKindTest.java

+39-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import org.junit.jupiter.api.Test;
44

5+
import io.fabric8.kubernetes.api.model.ConfigMap;
6+
import io.fabric8.kubernetes.api.model.HasMetadata;
7+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GroupVersionKindPlural;
8+
59
import static org.assertj.core.api.Assertions.assertThat;
6-
import static org.junit.jupiter.api.Assertions.*;
710

811
class GroupVersionKindTest {
912

@@ -18,4 +21,39 @@ void testInitFromApiVersion() {
1821
assertThat(gvk.getVersion()).isEqualTo("v1");
1922
}
2023

24+
@Test
25+
void pluralShouldOnlyBeProvidedIfExplicitlySet() {
26+
final var kind = "ConfigMap";
27+
var gvk = GroupVersionKindPlural.from(new GroupVersionKind("v1", kind));
28+
assertThat(gvk.getPlural()).isEmpty();
29+
assertThat(gvk.getPluralOrDefault())
30+
.isEqualTo(GroupVersionKindPlural.getDefaultPluralFor(kind));
31+
32+
gvk = GroupVersionKindPlural.from(GroupVersionKind.gvkFor(ConfigMap.class));
33+
assertThat(gvk.getPlural()).isEmpty();
34+
assertThat(gvk.getPluralOrDefault()).isEqualTo(HasMetadata.getPlural(ConfigMap.class));
35+
36+
gvk = GroupVersionKindPlural.gvkFor(ConfigMap.class);
37+
assertThat(gvk.getPlural()).hasValue(HasMetadata.getPlural(ConfigMap.class));
38+
39+
gvk = GroupVersionKindPlural.from(gvk);
40+
assertThat(gvk.getPlural()).hasValue(HasMetadata.getPlural(ConfigMap.class));
41+
}
42+
43+
@Test
44+
void pluralShouldBeEmptyIfNotProvided() {
45+
final var kind = "MyKind";
46+
var gvk =
47+
GroupVersionKindPlural.gvkWithPlural(new GroupVersionKind("josdk.io", "v1", kind), null);
48+
assertThat(gvk.getPlural()).isEmpty();
49+
assertThat(gvk.getPluralOrDefault())
50+
.isEqualTo(GroupVersionKindPlural.getDefaultPluralFor(kind));
51+
}
52+
53+
@Test
54+
void pluralShouldOverrideDefaultComputedVersionIfProvided() {
55+
var gvk = GroupVersionKindPlural.gvkWithPlural(new GroupVersionKind("josdk.io", "v1", "MyKind"),
56+
"MyPlural");
57+
assertThat(gvk.getPlural()).hasValue("MyPlural");
58+
}
2159
}

Diff for: operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dynamicgenericeventsourceregistration/DynamicGenericEventSourceRegistrationReconciler.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,12 @@ private ConfigMap configMap(DynamicGenericEventSourceRegistrationCustomResource
6666
return cm;
6767
}
6868

69-
private GroupVersionKind gvkFor(Class<? extends HasMetadata> clazz) {
70-
return new GroupVersionKind(HasMetadata.getApiVersion(clazz), HasMetadata.getKind(clazz));
71-
}
72-
7369
private InformerEventSource<GenericKubernetesResource, DynamicGenericEventSourceRegistrationCustomResource> genericInformerFor(
7470
Class<? extends HasMetadata> clazz,
7571
Context<DynamicGenericEventSourceRegistrationCustomResource> context) {
7672

7773
return new InformerEventSource<>(
78-
InformerConfiguration.from(gvkFor(clazz),
74+
InformerConfiguration.from(GroupVersionKind.gvkFor(clazz),
7975
context.eventSourceRetriever().eventSourceContextForDynamicRegistration()).build(),
8076
context.eventSourceRetriever().eventSourceContextForDynamicRegistration());
8177
}

0 commit comments

Comments
 (0)