Skip to content

Commit

Permalink
feat: add factory methods to help with SSA (6013)
Browse files Browse the repository at this point in the history
feat: add factory methods to help with SSA

Fixes fabric8io#6012
---
chore: don't use wildcard imports

Signed-off-by: Marc Nuri <marc@marcnuri.com>
  • Loading branch information
metacosm committed May 28, 2024
1 parent 52ccd57 commit e0647d7
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 94 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* Fix #5954: (crd-generator) Sort required properties to ensure deterministic output
* Fix #5973: CacheImpl locking for reading indexes (Cache.byIndex|indexKeys|index) was reduced
* Fix #5953: Made informer watch starting deterministic with respect to list processing
* Fix #6012: Add convenience methods on HasMetadata to help with SSA

#### Dependency Upgrade
* Fix #5695: Upgrade Fabric8 Kubernetes Model to Kubernetes v1.30.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -545,5 +545,4 @@ private static void schedule(Supplier<CompletableFuture<?>> runner, long delay,
});
}, delay, unit));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,9 @@ public interface HasMetadata extends KubernetesResource {
Pattern FINALIZER_NAME_MATCHER = Pattern.compile(
"^((" + DNS_LABEL_REGEXP + "\\.)+" + DNS_LABEL_START + 2 + DNS_LABEL_END + ")/"
+ DNS_LABEL_REGEXP);

ObjectMeta getMetadata();

void setMetadata(ObjectMeta metadata);
String REQUIRES_NON_NULL_METADATA = "requires non-null metadata";
String REQUIRES_NON_NULL_NAME = "requires non-null name";
String REQUIRES_NON_NULL_NAMESPACE = "requires non-null namespace";

/**
* Retrieves the kind associated with the specified HasMetadata implementation. If the implementation is annotated with
Expand All @@ -65,10 +64,6 @@ static String getKind(Class<?> clazz) {
return kind != null ? kind.value() : clazz.getSimpleName();
}

default String getKind() {
return getKind(getClass());
}

/**
* Computes the {@code apiVersion} associated with this HasMetadata implementation. The value is derived from the
* {@link Group} and {@link Version} annotations.
Expand Down Expand Up @@ -113,12 +108,6 @@ static String getVersion(Class<?> clazz) {
return version != null ? version.value() : null;
}

default String getApiVersion() {
return getApiVersion(getClass());
}

void setApiVersion(String version);

/**
* Retrieves the plural form associated with the specified class if annotated with {@link
* Plural} or computes a default value using the value returned by {@link #getSingular(Class)} as
Expand All @@ -133,11 +122,6 @@ static String getPlural(Class<?> clazz) {
: Pluralize.toPlural(getSingular(clazz)));
}

@JsonIgnore
default String getPlural() {
return getPlural(getClass());
}

/**
* Retrieves the singular form associated with the specified class as defined by the
* {@link Singular} annotation or computes a default value (lower-cased version of the value
Expand All @@ -153,11 +137,6 @@ static String getSingular(Class<?> clazz) {
.toLowerCase(Locale.ROOT);
}

@JsonIgnore
default String getSingular() {
return getSingular(getClass());
}

static String getFullResourceName(Class<?> clazz) {
final String plural = getPlural(clazz);
final String group = getGroup(clazz);
Expand All @@ -175,6 +154,98 @@ static String getFullResourceName(String plural, String group) {
return group.isEmpty() ? plural : plural + "." + group;
}

/**
* Determines whether the specified finalizer is valid according to the
* <a href=
* 'https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers'>finalizer
* specification</a>.
*
* @param finalizer the identifier of the finalizer which validity we want to check
* @return {@code true} if the identifier is valid, {@code false} otherwise
*/
static boolean validateFinalizer(String finalizer) {
if (finalizer == null) {
return false;
}
final Matcher matcher = FINALIZER_NAME_MATCHER.matcher(finalizer);
if (matcher.matches()) {
final String group = matcher.group(1);
return group.length() < 256;
} else {
return false;
}
}

/**
* Sanitizes and validates the specified {@link OwnerReference}, presumably to add it
*
* @param ownerReference the {@link OwnerReference} to sanitize and validate
* @return the sanitized and validated {@link OwnerReference} which should be used instead of the original one
*/
static OwnerReference sanitizeAndValidate(OwnerReference ownerReference) {
// validate required fields are present
final StringBuilder error = new StringBuilder(100);
error.append("Owner is missing required field(s): ");
final BiFunction<String, String, Optional<String>> trimmedFieldIfValid = (field, value) -> {
boolean isError = false;
if (value == null) {
isError = true;
} else {
value = value.trim();
if (value.isEmpty()) {
isError = true;
}
}
if (isError) {
error.append(field).append(" ");
return Optional.empty();
} else {
return Optional.of(value);
}
};
final Supplier<IllegalArgumentException> exceptionSupplier = () -> new IllegalArgumentException(
error.toString());

final Optional<String> uid = trimmedFieldIfValid.apply("uid", ownerReference.getUid());
final Optional<String> apiVersion = trimmedFieldIfValid.apply("apiVersion",
ownerReference.getApiVersion());
final Optional<String> name = trimmedFieldIfValid.apply("name", ownerReference.getName());
final Optional<String> kind = trimmedFieldIfValid.apply("kind", ownerReference.getKind());

// check that required values are present
ownerReference = new OwnerReferenceBuilder(ownerReference)
.withUid(uid.orElseThrow(exceptionSupplier))
.withApiVersion(apiVersion.orElseThrow(exceptionSupplier))
.withName(name.orElseThrow(exceptionSupplier))
.withKind(kind.orElseThrow(exceptionSupplier))
.build();
return ownerReference;
}

ObjectMeta getMetadata();

void setMetadata(ObjectMeta metadata);

default String getKind() {
return getKind(getClass());
}

default String getApiVersion() {
return getApiVersion(getClass());
}

void setApiVersion(String version);

@JsonIgnore
default String getPlural() {
return getPlural(getClass());
}

@JsonIgnore
default String getSingular() {
return getSingular(getClass());
}

@JsonIgnore
@SuppressWarnings("unused")
default String getFullResourceName() {
Expand Down Expand Up @@ -238,32 +309,9 @@ default boolean addFinalizer(String finalizer) {
}

/**
* Determines whether the specified finalizer is valid according to the
* <a href=
* 'https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers'>finalizer
* specification</a>.
*
* @param finalizer the identifier of the finalizer which validity we want to check
* @return {@code true} if the identifier is valid, {@code false} otherwise
*/
static boolean validateFinalizer(String finalizer) {
if (finalizer == null) {
return false;
}
final Matcher matcher = FINALIZER_NAME_MATCHER.matcher(finalizer);
if (matcher.matches()) {
final String group = matcher.group(1);
return group.length() < 256;
} else {
return false;
}
}

/**
* @see HasMetadata#validateFinalizer(String)
*
* @param finalizer the identifier of the finalizer which validity we want to check
* @return {@code true} if the identifier is valid, {@code false} otherwise
*/
default boolean isFinalizerValid(String finalizer) {
return HasMetadata.validateFinalizer(finalizer);
Expand Down Expand Up @@ -417,52 +465,6 @@ default OwnerReference addOwnerReference(OwnerReference ownerReference) {
return ownerReference;
}

/**
* Sanitizes and validates the specified {@link OwnerReference}, presumably to add it
*
* @param ownerReference the {@link OwnerReference} to sanitize and validate
* @return the sanitized and validated {@link OwnerReference} which should be used instead of the original one
*/
static OwnerReference sanitizeAndValidate(OwnerReference ownerReference) {
// validate required fields are present
final StringBuilder error = new StringBuilder(100);
error.append("Owner is missing required field(s): ");
final BiFunction<String, String, Optional<String>> trimmedFieldIfValid = (field, value) -> {
boolean isError = false;
if (value == null) {
isError = true;
} else {
value = value.trim();
if (value.isEmpty()) {
isError = true;
}
}
if (isError) {
error.append(field).append(" ");
return Optional.empty();
} else {
return Optional.of(value);
}
};
final Supplier<IllegalArgumentException> exceptionSupplier = () -> new IllegalArgumentException(
error.toString());

final Optional<String> uid = trimmedFieldIfValid.apply("uid", ownerReference.getUid());
final Optional<String> apiVersion = trimmedFieldIfValid.apply("apiVersion",
ownerReference.getApiVersion());
final Optional<String> name = trimmedFieldIfValid.apply("name", ownerReference.getName());
final Optional<String> kind = trimmedFieldIfValid.apply("kind", ownerReference.getKind());

// check that required values are present
ownerReference = new OwnerReferenceBuilder(ownerReference)
.withUid(uid.orElseThrow(exceptionSupplier))
.withApiVersion(apiVersion.orElseThrow(exceptionSupplier))
.withName(name.orElseThrow(exceptionSupplier))
.withKind(kind.orElseThrow(exceptionSupplier))
.build();
return ownerReference;
}

/**
* Removes the {@link OwnerReference} identified by the specified UID if it's part of this {@code HasMetadata}'s owner
* references
Expand Down Expand Up @@ -493,4 +495,40 @@ default void removeOwnerReference(HasMetadata owner) {
default Optional<ObjectMeta> optionalMetadata() {
return Optional.ofNullable(getMetadata());
}

/**
* Initializes this {@link ObjectMeta} field with name and namespace (if this instance represents a namespaced resource)
* provided by the specified HasMetadata instance. This is a convenience method to avoid boilerplate, notably when using
* Server-Side Apply, when creating a new instance with only some fields of the original one. Calls
* {@link #setMetadata(ObjectMeta)} when done, if you want to further configure this instance's metadata, please use
* {@link #initMetadataBuilderNameAndNamespaceFrom(HasMetadata)} instead, which <em>doesn't</em> sets the metadata, leaving it
* up to the user once configuration is finished.
*
* @param original a HasMetadata instance from which to retrieve the name and namespace
*/
default void initNameAndNamespaceFrom(HasMetadata original) {
Objects.requireNonNull(original);
final ObjectMeta meta = initMetadataBuilderNameAndNamespaceFrom(original).build();
setMetadata(meta);
}

/**
* Creates and initializes a new {@link ObjectMetaBuilder} with name and namespace (if the provided instance to initialize
* from represents a namespaced resource) provided by the specified HasMetadata instance. This is a convenience method to
* avoid boilerplate, notably when using Server-Side Apply, when creating a new instance with only some fields of the original
* one. This method assumes that further configuration will occur on the newly created ObjectMetaBuilder.
*
* @param original an HasMetadata instance from which to retrieve the name and namespace
* @return a new ObjectMetaBuilder instance initialized with the name and namespace (if needed) of the specified HasMetadata
*/
static ObjectMetaBuilder initMetadataBuilderNameAndNamespaceFrom(HasMetadata original) {
Objects.requireNonNull(original);
final ObjectMeta metadata = Objects.requireNonNull(original.getMetadata(), REQUIRES_NON_NULL_METADATA);
final ObjectMetaBuilder metaBuilder = new ObjectMetaBuilder();
metaBuilder.withName(Objects.requireNonNull(metadata.getName(), REQUIRES_NON_NULL_NAME));
if (original instanceof Namespaced) {
metaBuilder.withNamespace(Objects.requireNonNull(metadata.getNamespace(), REQUIRES_NON_NULL_NAMESPACE));
}
return metaBuilder;
}
}
Loading

0 comments on commit e0647d7

Please sign in to comment.