diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index b5b73fc98c1..24d408130d2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -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; @@ -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))); @@ -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(); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index 670e5a7c207..473f45ff5bc 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -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; @@ -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; @@ -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); } @@ -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<>()); } @@ -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"); } @@ -606,4 +615,8 @@ public Set<AdditionalPrinterColumn> getAdditionalPrinterColumns() { return additionalPrinterColumns; } + public Set<AdditionalSelectableField> getAdditionalSelectableFields() { + return additionalSelectableFields; + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index b55ee48564b..9ea7f87562a 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -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(); }); diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java index 4056d242379..458330908ac 100644 --- a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.java @@ -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; @@ -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(); } diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableField.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableField.java new file mode 100644 index 00000000000..921a7eac678 --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableField.java @@ -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> { +} diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableFieldSpec.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableFieldSpec.java new file mode 100644 index 00000000000..927f51b41bc --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/approvaltests/selectablefield/SelectableFieldSpec.java @@ -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; + } + +} diff --git a/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.selectablefields.sample.fabric8.io.v1.approved.yml b/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.selectablefields.sample.fabric8.io.v1.approved.yml new file mode 100644 index 00000000000..c3ac0aaa709 --- /dev/null +++ b/crd-generator/test/src/test/resources/io/fabric8/crd/generator/approvaltests/CRDGeneratorApprovalTest.approvalTest.selectablefields.sample.fabric8.io.v1.approved.yml @@ -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 diff --git a/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableField.java b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableField.java new file mode 100644 index 00000000000..8459407bb5a --- /dev/null +++ b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableField.java @@ -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(); + +} diff --git a/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableFields.java b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableFields.java new file mode 100644 index 00000000000..90c43189ee4 --- /dev/null +++ b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/AdditionalSelectableFields.java @@ -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(); +} diff --git a/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/SelectableField.java b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/SelectableField.java new file mode 100644 index 00000000000..9515495ea60 --- /dev/null +++ b/generator-annotations/src/main/java/io/fabric8/crd/generator/annotation/SelectableField.java @@ -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 { +}