Skip to content

Commit

Permalink
feat(crd-generator): added support for selectable fields (6540)
Browse files Browse the repository at this point in the history
feat(crd-generator): Add support for selectable fields (#6392)
---
Include selectable field in unit test
---
Add docs about `@AdditionalPrinterColumn` annotation
---
Add docs about `@SelectableField` and `@AdditionalSelectableField` annotation
---
Add changelog
  • Loading branch information
baloo42 authored Nov 20, 2024
1 parent 5343445 commit 4beec62
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 26 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* Fix #6214: Java generator does not recognize fields in CRDs other than metadata, spec, and status

#### Improvements
* Fix #3069: added AdditionalPrinterColumn type annotation to completely specify additional printer columns
* Fix #3069: (crd-generator) Add `@AdditionalPrinterColumn` to specify a printer column by JSON path.
* Fix #6392: (crd-generator) Add `@AdditionalSelectableField` and `@SelectableField` to specify selectable fields.
* Fix #5264: Remove deprecated `Config.errorMessages` field
* Fix #6008: removing the optional dependency on bouncy castle
* Fix #6407: sundrio builder-annotations is not available via bom import
Expand Down
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 @@ -17,6 +17,7 @@

import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.fabric8.crd.generator.annotation.PrinterColumn;
import io.fabric8.crd.generator.annotation.SelectableField;

public class JokeRequestSpec {

Expand All @@ -39,6 +40,7 @@ public enum ExcludedTopic {
explicit
}

@SelectableField
@PrinterColumn(name = "jokeCategory", priority = 1)
@JsonPropertyDescription("category-description")
private Category category = Category.Any;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ void jokerequestCRDShouldWork() {
assertEquals(".status.category", columnDefinition.getJsonPath());
assertEquals("jokeCategory", columnDefinition.getName());
assertEquals(0, columnDefinition.getPriority());
assertEquals(".spec.category", version.getSelectableFields().get(0).getJsonPath());
CustomResourceValidation schema = version.getSchema();
assertNotNull(schema);
Map<String, JSONSchemaProps> properties = schema.getOpenAPIV3Schema().getProperties();
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
Loading

0 comments on commit 4beec62

Please sign in to comment.