Skip to content

Commit

Permalink
feat(crd-generator): Add support for selectable fields (fabric8io#6392)
Browse files Browse the repository at this point in the history
  • Loading branch information
baloo42 committed Nov 4, 2024
1 parent 4c37d54 commit 6542ad7
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.fabric8.crd.generator.annotation.AdditionalPrinterColumn;
import io.fabric8.crd.generator.annotation.AdditionalPrinterColumn.Format;
import io.fabric8.crd.generator.annotation.PrinterColumn;
import io.fabric8.crd.generator.annotation.SelectableField;
import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.utils.Utils;
Expand All @@ -44,6 +45,10 @@ void addPrinterColumn(String path, String column, String format,
int priority, String type, String description);
}

public interface SelectableFieldHandler {
void addSelectableField(String jsonPath);
}

protected void handlePrinterColumns(AbstractJsonSchema<?, ?> resolver, PrinterColumnHandler handler) {
TreeMap<String, AnnotationMetadata> sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
resolver.getAdditionalPrinterColumns().forEach(apc -> sortedCols.put(apc.jsonPath(), new AnnotationMetadata(apc, null)));
Expand Down Expand Up @@ -78,6 +83,14 @@ protected void handlePrinterColumns(AbstractJsonSchema<?, ?> resolver, PrinterCo
});
}

protected void handleSelectableField(AbstractJsonSchema<?, ?> resolver, SelectableFieldHandler handler) {
TreeMap<String, AnnotationMetadata> sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
resolver.getAdditionalSelectableFields()
.forEach(apc -> sortedCols.put(apc.jsonPath(), new AnnotationMetadata(apc, null)));
sortedCols.putAll(resolver.getAllPaths(SelectableField.class));
sortedCols.forEach((jsonPath, property) -> handler.addSelectableField(jsonPath));
}

public abstract Stream<Map.Entry<? extends HasMetadata, Set<String>>> finish();

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
import com.fasterxml.jackson.module.jsonSchema.types.StringSchema;
import com.fasterxml.jackson.module.jsonSchema.types.ValueTypeSchema;
import io.fabric8.crd.generator.annotation.AdditionalPrinterColumn;
import io.fabric8.crd.generator.annotation.AdditionalSelectableField;
import io.fabric8.crd.generator.annotation.PreserveUnknownFields;
import io.fabric8.crd.generator.annotation.PrinterColumn;
import io.fabric8.crd.generator.annotation.SchemaFrom;
import io.fabric8.crd.generator.annotation.SchemaSwap;
import io.fabric8.crd.generator.annotation.SelectableField;
import io.fabric8.crdv2.generator.InternalSchemaSwaps.SwapResult;
import io.fabric8.crdv2.generator.ResolvingContext.GeneratorObjectSchema;
import io.fabric8.generator.annotation.Default;
Expand Down Expand Up @@ -95,6 +97,7 @@ public abstract class AbstractJsonSchema<T extends KubernetesJSONSchemaProps, V
private T root;
private Set<String> dependentClasses = new HashSet<>();
private Set<AdditionalPrinterColumn> additionalPrinterColumns = new HashSet<>();
private Set<AdditionalSelectableField> additionalSelectableFields = new HashSet<>();

public static class AnnotationMetadata {
public final Annotation annotation;
Expand All @@ -111,8 +114,12 @@ public AnnotationMetadata(Annotation annotation, KubernetesJSONSchemaProps schem
public AbstractJsonSchema(ResolvingContext resolvingContext, Class<?> def) {
this.resolvingContext = resolvingContext;
// TODO: could make this configurable, and could stop looking for single valued ones - or warn
Stream.of(SpecReplicas.class, StatusReplicas.class, LabelSelector.class, PrinterColumn.class)
.forEach(clazz -> pathMetadata.put(clazz, new LinkedHashMap<>()));
Stream.of(
SpecReplicas.class,
StatusReplicas.class,
LabelSelector.class,
PrinterColumn.class,
SelectableField.class).forEach(clazz -> pathMetadata.put(clazz, new LinkedHashMap<>()));

this.root = resolveRoot(def);
}
Expand All @@ -129,7 +136,7 @@ public Optional<String> getSinglePath(Class<? extends Annotation> clazz) {
return ofNullable(pathMetadata.get(clazz)).flatMap(m -> m.keySet().stream().findFirst());
}

public Map<String, AnnotationMetadata> getAllPaths(Class<PrinterColumn> clazz) {
public Map<String, AnnotationMetadata> getAllPaths(Class<? extends Annotation> clazz) {
return ofNullable(pathMetadata.get(clazz)).orElse(new LinkedHashMap<>());
}

Expand All @@ -145,6 +152,8 @@ private T resolveRoot(Class<?> definition) {
JsonSchema schema = resolvingContext.toJsonSchema(definition);
consumeRepeatingAnnotation(definition, AdditionalPrinterColumn.class,
additionalPrinterColumns::add);
consumeRepeatingAnnotation(definition, AdditionalSelectableField.class,
additionalSelectableFields::add);
if (schema instanceof GeneratorObjectSchema) {
return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata");
}
Expand Down Expand Up @@ -606,4 +615,8 @@ public Set<AdditionalPrinterColumn> getAdditionalPrinterColumns() {
return additionalPrinterColumns;
}

public Set<AdditionalSelectableField> getAdditionalSelectableFields() {
return additionalSelectableFields;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public void addPrinterColumn(String path, String column, String format, int prio
}
});

handleSelectableField(resolver, jsonPath -> builder.addNewSelectableField()
.withJsonPath(jsonPath)
.endSelectableField());

resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> {
builder.editOrNewSubresources().editOrNewScale().withSpecReplicasPath(path).endScale().endSubresources();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.fabric8.crd.generator.approvaltests.nocyclic.NoCyclic;
import io.fabric8.crd.generator.approvaltests.printercolum.PrinterColumn;
import io.fabric8.crd.generator.approvaltests.replica.Replica;
import io.fabric8.crd.generator.approvaltests.selectablefield.SelectableField;
import io.fabric8.kubernetes.client.CustomResource;
import io.sundr.utils.Strings;
import org.approvaltests.Approvals;
Expand Down Expand Up @@ -181,6 +182,7 @@ static Stream<TestCase> crdApprovalCasesApiV2(String crdVersion) {
final List<TestCase> cases = new ArrayList<>();
for (boolean parallel : new boolean[] { false, true }) {
cases.add(new TestCase("printercolumns.sample.fabric8.io", crdVersion, parallel, PrinterColumn.class));
cases.add(new TestCase("selectablefields.sample.fabric8.io", crdVersion, parallel, SelectableField.class));
}
return cases.stream();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.approvaltests.selectablefield;

import io.fabric8.crd.generator.annotation.AdditionalSelectableField;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;

@Version("v1alpha1")
@Group("sample.fabric8.io")
@AdditionalSelectableField(jsonPath = ".spec.deepLevel1.name")
public class SelectableField extends CustomResource<SelectableFieldSpec, Void> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.approvaltests.selectablefield;

import io.fabric8.crd.generator.annotation.SelectableField;
import lombok.Data;

@Data
public class SelectableFieldSpec {

@SelectableField
private String id;

private DeepLevel1 deepLevel1;

@Data
static class DeepLevel1 {
// targeted from @AdditionalSelectableField
private String name;

@SelectableField
private Integer fromLevel1;

private DeepLevel2 deepLevel2;
}

@Data
static class DeepLevel2 {
@SelectableField
private Boolean fromLevel2;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!
apiVersion: "apiextensions.k8s.io/v1"
kind: "CustomResourceDefinition"
metadata:
name: "selectablefields.sample.fabric8.io"
spec:
group: "sample.fabric8.io"
names:
kind: "SelectableField"
plural: "selectablefields"
singular: "selectablefield"
scope: "Cluster"
versions:
- name: "v1alpha1"
schema:
openAPIV3Schema:
properties:
spec:
properties:
deepLevel1:
properties:
deepLevel2:
properties:
fromLevel2:
type: "boolean"
type: "object"
fromLevel1:
type: "integer"
name:
type: "string"
type: "object"
id:
type: "string"
type: "object"
status:
type: "object"
type: "object"
selectableFields:
- jsonPath: ".spec.deepLevel1.deepLevel2.fromLevel2"
- jsonPath: ".spec.deepLevel1.fromLevel1"
- jsonPath: ".spec.deepLevel1.name"
- jsonPath: ".spec.id"
served: true
storage: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Defines a selectable field. Must be placed at the root of the
* custom resource.
*
* @see <a href=
* "https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-selectors">Kubernetes
* Docs - Field Selectors</a>
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(AdditionalSelectableFields.class)
public @interface AdditionalSelectableField {

/**
* The JSON Path to the field.
*
* @return the JSON path
*/
String jsonPath();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AdditionalSelectableFields {
AdditionalSelectableField[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a field as selectable.
*
* @see <a href=
* "https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-selectors">Kubernetes
* Docs - Field Selectors</a>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SelectableField {
}

0 comments on commit 6542ad7

Please sign in to comment.