Skip to content

Commit

Permalink
wiring up new top level options
Browse files Browse the repository at this point in the history
  • Loading branch information
shawkins committed Apr 26, 2024
1 parent 71a7c1d commit 73997e3
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
public abstract class AbstractCustomResourceHandler {

public abstract void handle(CustomResourceInfo config);
public abstract void handle(CustomResourceInfo config, ResolvingContext resolvingContext);

public abstract Stream<HasMetadata> finish();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private T resolveRoot(Class<?> definition) {
return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata");
}
return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null,
resolvingContext.serializationConfig.constructType(definition), schema);
resolvingContext.objectMapper.getSerializationConfig().constructType(definition), schema);
}

/**
Expand Down Expand Up @@ -282,9 +282,12 @@ private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwa
final InternalSchemaSwaps swaps = schemaSwaps;

GeneratorObjectSchema gos = (GeneratorObjectSchema) jacksonSchema.asObjectSchema();
AnnotationIntrospector ai = resolvingContext.serializationConfig.getAnnotationIntrospector();
BeanDescription bd = resolvingContext.serializationConfig.introspect(gos.javaType);
boolean preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null;
AnnotationIntrospector ai = resolvingContext.objectMapper.getSerializationConfig().getAnnotationIntrospector();
BeanDescription bd = resolvingContext.objectMapper.getSerializationConfig().introspect(gos.javaType);
boolean preserveUnknownFields = false;
if (resolvingContext.implicitPreserveUnknownFields) {
preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null;
}

consumeRepeatingAnnotation(gos.javaType.getRawClass(), SchemaSwap.class, ss -> {
swaps.registerSwap(gos.javaType.getRawClass(),
Expand Down Expand Up @@ -333,7 +336,7 @@ private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwa
continue;
}
propertySchema = toJsonSchema(propertyMetadata.schemaFrom);
type = resolvingContext.serializationConfig.constructType(propertyMetadata.schemaFrom);
type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom);
}

T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema);
Expand Down Expand Up @@ -423,7 +426,7 @@ private T resolveProperty(LinkedHashMap<String, String> visited, InternalSchemaS
} else if (jacksonSchema instanceof ReferenceSchema) {
// de-reference the reference schema - these can be naturally non-cyclic, for example siblings
ReferenceSchema ref = (ReferenceSchema) jacksonSchema;
GeneratorObjectSchema referenced = resolvingContext.seen.get(ref.get$ref());
GeneratorObjectSchema referenced = resolvingContext.uriToJacksonSchema.get(ref.get$ref());
Utils.checkNotNull(referenced, "Could not find previously generated schema");
jacksonSchema = referenced;
} else if (type.isMapLikeType()) {
Expand Down Expand Up @@ -469,11 +472,10 @@ private Set<String> findIngoredEnumConstants(JavaType type) {
Set<String> toIgnore = new HashSet<>();
for (Field field : fields) {
if (field.isEnumConstant() && field.getAnnotation(JsonIgnore.class) != null) {
// hack to figure out the enum constant
// hack to figure out the enum constant - this guards against some using both JsonIgnore and JsonProperty
try {
Object value = field.get(null);
toIgnore.add(resolvingContext.kubernetesSerialization
.unmarshal(resolvingContext.kubernetesSerialization.asJson(value), String.class));
toIgnore.add(resolvingContext.objectMapper.convertValue(value, String.class));
} catch (IllegalArgumentException | IllegalAccessException e) {
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
Expand All @@ -53,6 +56,8 @@ public class CRDGenerator {
private final Map<String, AbstractCustomResourceHandler> handlers = new HashMap<>(2);
private CRDOutput<? extends OutputStream> output;
private boolean parallel;
private boolean implicitPreserveUnknownFields;
private ObjectMapper objectMapper;
private Map<String, CustomResourceInfo> infos;

// TODO: why not rely on the standard fabric8 yaml mapping
Expand All @@ -77,11 +82,21 @@ public CRDGenerator withOutput(CRDOutput<? extends OutputStream> output) {
return this;
}

public CRDGenerator withImplicitPreserveUnknownFields(boolean implicitPreserveUnknownFields) {
this.implicitPreserveUnknownFields = implicitPreserveUnknownFields;
return this;
}

public CRDGenerator withParallelGenerationEnabled(boolean parallel) {
this.parallel = parallel;
return this;
}

public CRDGenerator withObjectMapper(ObjectMapper mapper) {
this.objectMapper = mapper;
return this;
}

public CRDGenerator forCRDVersions(List<String> versions) {
return versions != null && !versions.isEmpty() ? forCRDVersions(versions.toArray(new String[0]))
: this;
Expand Down Expand Up @@ -154,6 +169,13 @@ public CRDGenerationInfo detailedGenerate() {
forCRDVersions(CustomResourceHandler.VERSION);
}

final ResolvingContext context;
if (this.objectMapper == null) {
context = ResolvingContext.defaultResolvingContext(implicitPreserveUnknownFields);
} else {
context = new ResolvingContext(this.objectMapper, implicitPreserveUnknownFields);
}

for (CustomResourceInfo info : infos.values()) {
if (info != null) {
if (LOGGER.isInfoEnabled()) {
Expand All @@ -162,7 +184,13 @@ public CRDGenerationInfo detailedGenerate() {
info.specClassName().orElse("undetermined"),
info.statusClassName().orElse("undetermined"));
}
handlers.values().forEach(h -> h.handle(info));

if (parallel) {
handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext())))
.collect(Collectors.toList()).stream().forEach(ForkJoinTask::join);
} else {
handlers.values().stream().forEach(h -> h.handle(info, context));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor;
import com.fasterxml.jackson.module.jsonSchema.JsonSchema;
Expand All @@ -31,10 +30,15 @@
import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* Encapsulates the stateful Jackson details that allow for crd to be fully resolved by our logic
* - holds an association of uris to already generated jackson schemas
* - holds a Jackson SchemaGenerator which is not thread-safe
*/
public class ResolvingContext {

static final class GeneratorObjectSchema extends ObjectSchema {
Expand Down Expand Up @@ -76,15 +80,15 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) {
// so that we may directly mark preserve unknown
JsonObjectFormatVisitor result = super.expectObjectFormat(convertedType);
((GeneratorObjectSchema) schema).javaType = convertedType;
seen.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema);
uriToJacksonSchema.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema);
return result;
}
}

final JsonSchemaGenerator generator;
final SerializationConfig serializationConfig;
final KubernetesSerialization kubernetesSerialization;
final Map<String, GeneratorObjectSchema> seen = new HashMap<>();
final ObjectMapper objectMapper;
final Map<String, GeneratorObjectSchema> uriToJacksonSchema;
final boolean implicitPreserveUnknownFields;

private static class AccessibleKubernetesSerialization extends KubernetesSerialization {

Expand All @@ -97,16 +101,26 @@ public ObjectMapper getMapper() {

private static AccessibleKubernetesSerialization DEFAULT_KUBERNETES_SERIALIZATION;

public static ResolvingContext defaultResolvingContext() {
public static ResolvingContext defaultResolvingContext(boolean implicitPreserveUnknownFields) {
if (DEFAULT_KUBERNETES_SERIALIZATION == null) {
DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization();
}
return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION);
return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), implicitPreserveUnknownFields);
}

public ResolvingContext forkContext() {
return new ResolvingContext(objectMapper, uriToJacksonSchema, implicitPreserveUnknownFields);
}

public ResolvingContext(ObjectMapper mapper, boolean implicitPreserveUnknownFields) {
this(mapper, new ConcurrentHashMap<>(), implicitPreserveUnknownFields);
}

public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) {
serializationConfig = mapper.getSerializationConfig();
this.kubernetesSerialization = kubernetesSerialization;
private ResolvingContext(ObjectMapper mapper, Map<String, GeneratorObjectSchema> uriToJacksonSchema,
boolean implicitPreserveUnknownFields) {
this.uriToJacksonSchema = uriToJacksonSchema;
this.objectMapper = mapper;
this.implicitPreserveUnknownFields = implicitPreserveUnknownFields;
generator = new JsonSchemaGenerator(mapper, new WrapperFactory() {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,24 @@

import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CustomResourceHandler extends AbstractCustomResourceHandler {

private List<CustomResourceDefinition> crds = new CopyOnWriteArrayList<>();
private Queue<CustomResourceDefinition> crds = new ConcurrentLinkedQueue<>();

public static final String VERSION = "v1";

@Override
public void handle(CustomResourceInfo config) {
public void handle(CustomResourceInfo config, ResolvingContext resolvingContext) {
final String name = config.crdName();
final String version = config.version();

JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), config.definition());
JsonSchema resolver = new JsonSchema(resolvingContext, config.definition());
JSONSchemaProps schema = resolver.getSchema();

CustomResourceDefinitionVersionBuilder builder = new CustomResourceDefinitionVersionBuilder()
Expand Down Expand Up @@ -146,16 +147,16 @@ private CustomResourceDefinition combine(List<CustomResourceDefinition> definiti
.map(CustomResourceDefinitionVersion::getName)
.collect(Collectors.toList());

if (storageVersions.size() > 1) {
if (storageVersions.size() > 1) {
throw new IllegalStateException(String.format(
"'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.",
primary.getMetadata().getName(), storageVersions));
}
}

versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName);
versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName);

//TODO: we could double check that the top-level metadata is consistent across all versions
return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build();
//TODO: we could double check that the top-level metadata is consistent across all versions
return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static class V1JSONSchemaProps extends JSONSchemaProps implements Kuberne
}

public static JSONSchemaProps from(Class<?> definition) {
return new JsonSchema(ResolvingContext.defaultResolvingContext(), definition).getSchema();
return new JsonSchema(ResolvingContext.defaultResolvingContext(false), definition).getSchema();
}

public JsonSchema(ResolvingContext resolvingContext, Class<?> definition) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public enum AnnotatedEnum {
non("N"),
@JsonProperty("oui")
es("O"),
@JsonProperty("foo")
@JsonIgnore
Maybe("Maybe");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import io.fabric8.crdv2.example.extraction.NestedSchemaSwap;
import io.fabric8.crdv2.example.json.ContainingJson;
import io.fabric8.crdv2.example.person.Person;
import io.fabric8.crdv2.generator.ResolvingContext;
import io.fabric8.kubernetes.api.model.AnyType;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Quantity;
Expand Down Expand Up @@ -188,7 +189,7 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti

@Test
void shouldProduceKubernetesPreserveFields() {
JSONSchemaProps schema = JsonSchema.from(ContainingJson.class);
JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), ContainingJson.class).getSchema();
assertNotNull(schema);
Map<String, JSONSchemaProps> properties = assertSchemaHasNumberOfProperties(schema, 2);
final JSONSchemaProps specSchema = properties.get("spec");
Expand Down Expand Up @@ -344,7 +345,7 @@ public void setValues(Map<String, Object> values) {

@Test
void testPreserveUnknown() {
JSONSchemaProps schema = JsonSchema.from(PreserveUnknown.class);
JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), PreserveUnknown.class).getSchema();
assertNotNull(schema);
assertEquals(0, schema.getProperties().size());
assertEquals(Boolean.TRUE, schema.getXKubernetesPreserveUnknownFields());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ class SpecReplicasPathTest {

@Test
public void shoudDetectSpecReplicasPath() throws Exception {
JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithStatusProperty.class);
JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithStatusProperty.class);
Optional<String> path = resolver.getSinglePath(SpecReplicas.class);
assertTrue(path.isPresent());
assertEquals(".replicas", path.get());
}

@Test
public void shoudDetectNestedSpecReplicasPath() throws Exception {
JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithSpec.class);
JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithSpec.class);
Optional<String> path = resolver.getSinglePath(SpecReplicas.class);
assertTrue(path.isPresent());
assertEquals(".spec.replicas", path.get());
Expand Down
14 changes: 1 addition & 13 deletions crd-generator/test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-apt</artifactId>
</dependency>

<dependency>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-api-v2</artifactId>
</dependency>


<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down Expand Up @@ -81,13 +76,6 @@
<version>${keycloak-version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.5.3.Final</version>
<scope>test</scope>
</dependency>

</dependencies>
<build>
Expand Down
Loading

0 comments on commit 73997e3

Please sign in to comment.