Skip to content

Commit

Permalink
feat: allow specifying plural form of GVK in GenericKubernetesDepende…
Browse files Browse the repository at this point in the history
…ntResource (#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]>
  • Loading branch information
metacosm authored Aug 29, 2024
1 parent 8d5831e commit 3588780
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package io.javaoperatorsdk.operator.processing;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

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

public class GroupVersionKind {
private final String group;
private final String version;
private final String kind;
private final String apiVersion;
protected final static Map<Class<? extends HasMetadata>, GroupVersionKind> CACHE =
new ConcurrentHashMap<>();

public GroupVersionKind(String apiVersion, String kind) {
this.kind = kind;
Expand All @@ -19,17 +24,23 @@ public GroupVersionKind(String apiVersion, String kind) {
this.group = groupAndVersion[0];
this.version = groupAndVersion[1];
}
this.apiVersion = apiVersion;
}

public static GroupVersionKind gvkFor(Class<? extends HasMetadata> resourceClass) {
return CACHE.computeIfAbsent(resourceClass, GroupVersionKind::computeGVK);
}

private static GroupVersionKind computeGVK(Class<? extends HasMetadata> rc) {
return new GroupVersionKind(HasMetadata.getGroup(rc),
HasMetadata.getVersion(rc), HasMetadata.getKind(rc));
}

public GroupVersionKind(String group, String version, String kind) {
this.group = group;
this.version = version;
this.kind = kind;
}

public static GroupVersionKind gvkFor(Class<? extends HasMetadata> resourceClass) {
return new GroupVersionKind(HasMetadata.getGroup(resourceClass),
HasMetadata.getVersion(resourceClass), HasMetadata.getKind(resourceClass));
this.apiVersion = (group == null || group.isBlank()) ? version : group + "/" + version;
}

public String getGroup() {
Expand All @@ -45,7 +56,7 @@ public String getKind() {
}

public String apiVersion() {
return group == null || group.isBlank() ? version : group + "/" + version;
return apiVersion;
}

@Override
Expand All @@ -55,20 +66,18 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
GroupVersionKind that = (GroupVersionKind) o;
return Objects.equals(group, that.group) && Objects.equals(version, that.version)
&& Objects.equals(kind, that.kind);
return Objects.equals(apiVersion, that.apiVersion) && Objects.equals(kind, that.kind);
}

@Override
public int hashCode() {
return Objects.hash(group, version, kind);
return Objects.hash(apiVersion, kind);
}

@Override
public String toString() {
return "GroupVersionKind{" +
"group='" + group + '\'' +
", version='" + version + '\'' +
"apiVersion='" + apiVersion + '\'' +
", kind='" + kind + '\'' +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
public class GenericKubernetesDependentResource<P extends HasMetadata>
extends KubernetesDependentResource<GenericKubernetesResource, P> {

private final GroupVersionKind groupVersionKind;
private final GroupVersionKindPlural groupVersionKind;

public GenericKubernetesDependentResource(GroupVersionKind groupVersionKind) {
this(GroupVersionKindPlural.from(groupVersionKind));
}

public GenericKubernetesDependentResource(GroupVersionKindPlural groupVersionKind) {
super(GenericKubernetesResource.class);
this.groupVersionKind = groupVersionKind;
}
Expand All @@ -20,7 +24,7 @@ protected InformerConfiguration.InformerConfigurationBuilder<GenericKubernetesRe
}

@SuppressWarnings("unused")
public GroupVersionKind getGroupVersionKind() {
public GroupVersionKindPlural getGroupVersionKind() {
return groupVersionKind;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;

import java.util.Optional;

import io.fabric8.kubernetes.api.Pluralize;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.processing.GroupVersionKind;

/**
* An extension of {@link GroupVersionKind} that also records the associated plural form which is
* useful when dealing with Kubernetes RBACs. Downstream projects might leverage that information.
*/
public class GroupVersionKindPlural extends GroupVersionKind {
private final String plural;

protected GroupVersionKindPlural(String group, String version, String kind, String plural) {
super(group, version, kind);
this.plural = plural;
}

protected GroupVersionKindPlural(String apiVersion, String kind, String plural) {
super(apiVersion, kind);
this.plural = plural;
}

protected GroupVersionKindPlural(GroupVersionKind gvk, String plural) {
this(gvk.getGroup(), gvk.getVersion(), gvk.getKind(),
plural != null ? plural
: (gvk instanceof GroupVersionKindPlural ? ((GroupVersionKindPlural) gvk).plural
: null));
}

/**
* Creates a new GroupVersionKindPlural from the specified {@link GroupVersionKind}.
*
* @param gvk a {@link GroupVersionKind} from which to create a new GroupVersionKindPlural object
* @return a new GroupVersionKindPlural object matching the specified {@link GroupVersionKind}
*/
public static GroupVersionKindPlural from(GroupVersionKind gvk) {
return gvk instanceof GroupVersionKindPlural ? ((GroupVersionKindPlural) gvk)
: gvkWithPlural(gvk, null);
}

/**
* Creates a new GroupVersionKindPlural based on the specified {@link GroupVersionKind} instance
* but specifying a plural form to use as well.
*
* @param gvk the base {@link GroupVersionKind} from which to derive a new GroupVersionKindPlural
* @param plural the plural form to use for the new instance or {@code null} if the default plural
* form is desired. Note that the specified plural form will override any existing plural
* form for the specified {@link GroupVersionKind} (in particular, if the specified
* {@link GroupVersionKind} was already an instance of GroupVersionKindPlural, its plural
* form will only be considered in the new instance if the specified plural form is
* {@code null}
* @return a new GroupVersionKindPlural derived from the specified {@link GroupVersionKind} and
* plural form
*/
public static GroupVersionKindPlural gvkWithPlural(GroupVersionKind gvk, String plural) {
return new GroupVersionKindPlural(gvk, plural);
}

/**
* Creates a new GroupVersionKindPlural instance extracting the information from the specified
* {@link HasMetadata} implementation
*
* @param resourceClass the {@link HasMetadata} from which group, version, kind and plural form
* are extracted
* @return a new GroupVersionKindPlural instance based on the specified {@link HasMetadata}
* implementation
*/
public static GroupVersionKindPlural gvkFor(Class<? extends HasMetadata> resourceClass) {
final var gvk = GroupVersionKind.gvkFor(resourceClass);
return gvkWithPlural(gvk, HasMetadata.getPlural(resourceClass));
}

/**
* Retrieves the default plural form for the specified kind.
*
* @param kind the kind for which we want to get the default plural form
* @return the default plural form for the specified kind
*/
public static String getDefaultPluralFor(String kind) {
// todo: replace by Fabric8 version when available, see
// https://github.com/fabric8io/kubernetes-client/pull/6314
return kind != null ? Pluralize.toPlural(kind.toLowerCase()) : null;
}

/**
* Returns the plural form associated with the kind if it has been provided explicitly (either
* manually by the user, or determined from the associated resource class definition)
*
* @return {@link Optional#empty()} if the plural form was not provided explicitly, or the plural
* form if it was provided explicitly
*/
public Optional<String> getPlural() {
return Optional.ofNullable(plural);
}

/**
* Returns the plural form associated with the kind if it was provided or a default, computed form
* via {@link #getDefaultPluralFor(String)} (which should correspond to the actual plural form in
* most cases but might not always be correct, especially if the resource's creator defined an
* exotic plural form via the CRD.
*
* @return the plural form associated with the kind if provided or a default plural form otherwise
*/
@SuppressWarnings("unused")
public String getPluralOrDefault() {
return getPlural().orElse(getDefaultPluralFor(getKind()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import org.junit.jupiter.api.Test;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.GroupVersionKindPlural;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class GroupVersionKindTest {

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

@Test
void pluralShouldOnlyBeProvidedIfExplicitlySet() {
final var kind = "ConfigMap";
var gvk = GroupVersionKindPlural.from(new GroupVersionKind("v1", kind));
assertThat(gvk.getPlural()).isEmpty();
assertThat(gvk.getPluralOrDefault())
.isEqualTo(GroupVersionKindPlural.getDefaultPluralFor(kind));

gvk = GroupVersionKindPlural.from(GroupVersionKind.gvkFor(ConfigMap.class));
assertThat(gvk.getPlural()).isEmpty();
assertThat(gvk.getPluralOrDefault()).isEqualTo(HasMetadata.getPlural(ConfigMap.class));

gvk = GroupVersionKindPlural.gvkFor(ConfigMap.class);
assertThat(gvk.getPlural()).hasValue(HasMetadata.getPlural(ConfigMap.class));

gvk = GroupVersionKindPlural.from(gvk);
assertThat(gvk.getPlural()).hasValue(HasMetadata.getPlural(ConfigMap.class));
}

@Test
void pluralShouldBeEmptyIfNotProvided() {
final var kind = "MyKind";
var gvk =
GroupVersionKindPlural.gvkWithPlural(new GroupVersionKind("josdk.io", "v1", kind), null);
assertThat(gvk.getPlural()).isEmpty();
assertThat(gvk.getPluralOrDefault())
.isEqualTo(GroupVersionKindPlural.getDefaultPluralFor(kind));
}

@Test
void pluralShouldOverrideDefaultComputedVersionIfProvided() {
var gvk = GroupVersionKindPlural.gvkWithPlural(new GroupVersionKind("josdk.io", "v1", "MyKind"),
"MyPlural");
assertThat(gvk.getPlural()).hasValue("MyPlural");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,12 @@ private ConfigMap configMap(DynamicGenericEventSourceRegistrationCustomResource
return cm;
}

private GroupVersionKind gvkFor(Class<? extends HasMetadata> clazz) {
return new GroupVersionKind(HasMetadata.getApiVersion(clazz), HasMetadata.getKind(clazz));
}

private InformerEventSource<GenericKubernetesResource, DynamicGenericEventSourceRegistrationCustomResource> genericInformerFor(
Class<? extends HasMetadata> clazz,
Context<DynamicGenericEventSourceRegistrationCustomResource> context) {

return new InformerEventSource<>(
InformerConfiguration.from(gvkFor(clazz),
InformerConfiguration.from(GroupVersionKind.gvkFor(clazz),
context.eventSourceRetriever().eventSourceContextForDynamicRegistration()).build(),
context.eventSourceRetriever().eventSourceContextForDynamicRegistration());
}
Expand Down

0 comments on commit 3588780

Please sign in to comment.