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 394638bc43b..d0c40d59218 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 @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonFormat.Value; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; @@ -79,7 +78,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.fabric8.crdv2.generator.CRDGenerator.YAML_MAPPER; import static java.util.Optional.ofNullable; /** @@ -252,9 +250,9 @@ public void updateSchema(T schema) { if (defaultValue != null) { try { - schema.setDefault(YAML_MAPPER.readTree(defaultValue)); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML."); + schema.setDefault(resolvingContext.kubernetesSerialization.convertValue(defaultValue, JsonNode.class)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML.", e); } } if (nullable) { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index e2a4042ec51..ea734d1c1b2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -24,6 +24,8 @@ import io.fabric8.crdv2.generator.v1.CustomResourceHandler; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.utils.ApiVersionUtil; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; +import io.fabric8.kubernetes.client.utils.Serialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +35,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -58,6 +61,7 @@ public class CRDGenerator { private boolean parallel; private boolean implicitPreserveUnknownFields; private ObjectMapper objectMapper; + private KubernetesSerialization kubernetesSerialization; private Map infos; // TODO: why not rely on the standard fabric8 yaml mapping @@ -92,8 +96,9 @@ public CRDGenerator withParallelGenerationEnabled(boolean parallel) { return this; } - public CRDGenerator withObjectMapper(ObjectMapper mapper) { + public CRDGenerator withObjectMapper(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) { this.objectMapper = mapper; + this.kubernetesSerialization = kubernetesSerialization; return this; } @@ -173,7 +178,7 @@ public CRDGenerationInfo detailedGenerate() { if (this.objectMapper == null) { context = ResolvingContext.defaultResolvingContext(implicitPreserveUnknownFields); } else { - context = new ResolvingContext(this.objectMapper, implicitPreserveUnknownFields); + context = new ResolvingContext(this.objectMapper, this.kubernetesSerialization, implicitPreserveUnknownFields); } for (CustomResourceInfo info : infos.values()) { @@ -208,7 +213,8 @@ public void emitCrd(HasMetadata crd, CRDGenerationInfo crdGenerationInfo) { outputStream.write( "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" .getBytes()); - YAML_MAPPER.writeValue(outputStream, crd); + String yaml = Serialization.asYaml(crd); + outputStream.write(yaml.getBytes(StandardCharsets.UTF_8)); final URI fileURI = output.crdURI(outputName); crdGenerationInfo.add(crdName, version, fileURI); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index df7fccaf561..82cb28a9f4b 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -87,6 +87,7 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { final JsonSchemaGenerator generator; final ObjectMapper objectMapper; + final KubernetesSerialization kubernetesSerialization; final Map uriToJacksonSchema; final boolean implicitPreserveUnknownFields; @@ -105,21 +106,25 @@ public static ResolvingContext defaultResolvingContext(boolean implicitPreserveU if (DEFAULT_KUBERNETES_SERIALIZATION == null) { DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization(); } - return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), implicitPreserveUnknownFields); + return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION, + implicitPreserveUnknownFields); } public ResolvingContext forkContext() { - return new ResolvingContext(objectMapper, uriToJacksonSchema, implicitPreserveUnknownFields); + return new ResolvingContext(objectMapper, kubernetesSerialization, uriToJacksonSchema, implicitPreserveUnknownFields); } - public ResolvingContext(ObjectMapper mapper, boolean implicitPreserveUnknownFields) { - this(mapper, new ConcurrentHashMap<>(), implicitPreserveUnknownFields); + public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization, + boolean implicitPreserveUnknownFields) { + this(mapper, kubernetesSerialization, new ConcurrentHashMap<>(), implicitPreserveUnknownFields); } - private ResolvingContext(ObjectMapper mapper, Map uriToJacksonSchema, + private ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization, + Map uriToJacksonSchema, boolean implicitPreserveUnknownFields) { this.uriToJacksonSchema = uriToJacksonSchema; this.objectMapper = mapper; + this.kubernetesSerialization = kubernetesSerialization; this.implicitPreserveUnknownFields = implicitPreserveUnknownFields; generator = new JsonSchemaGenerator(mapper, new WrapperFactory() { diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml index 901cee2cbaa..bf47592f36e 100644 --- a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -15,200 +15,201 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: complexkinds.example.com + name: "complexkinds.example.com" spec: - group: example.com + group: "example.com" names: - kind: ComplexKind - plural: complexkinds - singular: complexkind - scope: Namespaced + kind: "ComplexKind" + plural: "complexkinds" + singular: "complexkind" + scope: "Namespaced" versions: - additionalPrinterColumns: - - jsonPath: .status.message - name: MESSAGE + - jsonPath: ".status.message" + name: "MESSAGE" priority: 0 - type: string - - jsonPath: .status.state - name: State + type: "string" + - jsonPath: ".status.state" + name: "State" priority: 0 - type: string - name: v1 + type: "string" + name: "v1" schema: openAPIV3Schema: properties: spec: properties: actuatorPort: - type: integer + type: "integer" configMapName: - type: string + type: "string" metricsPath: - type: string + type: "string" metricsPort: - type: integer + type: "integer" services: items: properties: metadata: - description: The metadata of this Service + description: "The metadata of this Service" properties: annotations: additionalProperties: - type: string - type: object + type: "string" + type: "object" creationTimestamp: - type: string + type: "string" deletionGracePeriodSeconds: - type: integer + type: "integer" deletionTimestamp: - type: string + type: "string" finalizers: items: - type: string - type: array + type: "string" + type: "array" generateName: - type: string + type: "string" generation: - type: integer + type: "integer" labels: additionalProperties: - type: string - type: object + type: "string" + type: "object" name: - type: string + type: "string" namespace: - type: string + type: "string" resourceVersion: - type: string + type: "string" selfLink: - type: string + type: "string" uid: - type: string - type: object + type: "string" + type: "object" spec: - description: The spec of this Service + description: "The spec of this Service" nullable: true properties: allocateLoadBalancerNodePorts: - type: boolean + type: "boolean" clusterIP: - type: string + type: "string" clusterIPs: items: - type: string - type: array + type: "string" + type: "array" externalIPs: items: - type: string - type: array + type: "string" + type: "array" externalName: - type: string + type: "string" externalTrafficPolicy: - type: string + type: "string" healthCheckNodePort: - type: integer + type: "integer" internalTrafficPolicy: - type: string + type: "string" ipFamilies: items: - type: string - type: array + type: "string" + type: "array" ipFamilyPolicy: - type: string + type: "string" loadBalancerClass: - type: string + type: "string" loadBalancerIP: - type: string + type: "string" loadBalancerSourceRanges: items: - type: string - type: array + type: "string" + type: "array" publishNotReadyAddresses: - type: boolean + type: "boolean" selector: additionalProperties: - type: string - type: object + type: "string" + type: "object" sessionAffinity: - type: string + type: "string" type: - type: string - type: object - type: object - type: array + type: "string" + type: "object" + type: "object" + type: "array" statefulSet: properties: metadata: - description: The metadata of this StatefulSet + description: "The metadata of this StatefulSet" properties: annotations: additionalProperties: - type: string - type: object + type: "string" + type: "object" creationTimestamp: - type: string + type: "string" deletionGracePeriodSeconds: - type: integer + type: "integer" deletionTimestamp: - type: string + type: "string" finalizers: items: - type: string - type: array + type: "string" + type: "array" generateName: - type: string + type: "string" generation: - type: integer + type: "integer" labels: additionalProperties: - type: string - type: object + type: "string" + type: "object" name: - type: string + type: "string" namespace: - type: string + type: "string" resourceVersion: - type: string + type: "string" selfLink: - type: string + type: "string" uid: - type: string - type: object + type: "string" + type: "object" spec: - description: The spec of this StatefulSet + description: "The spec of this StatefulSet" properties: minReadySeconds: - type: integer + type: "integer" podManagementPolicy: - type: string + type: "string" replicas: - type: integer + type: "integer" revisionHistoryLimit: - type: integer + type: "integer" serviceName: - type: string - type: object - type: object - type: object + type: "string" + type: "object" + type: "object" + type: "object" status: properties: message: - type: string + type: "string" state: enum: - - CREATED - - ERROR - - ROLLING_UPDATE - - RUNNING - - SCALING - - STARTING - type: string - type: object - type: object + - "CREATED" + - "ERROR" + - "ROLLING_UPDATE" + - "RUNNING" + - "SCALING" + - "STARTING" + type: "string" + type: "object" + type: "object" served: true storage: true subresources: diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml index 0ae072eb120..43c15f5cf34 100644 --- a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -15,19 +15,20 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: k8svalidations.samples.fabric8.io + name: "k8svalidations.samples.fabric8.io" spec: - group: samples.fabric8.io + group: "samples.fabric8.io" names: - kind: K8sValidation - plural: k8svalidations - singular: k8svalidation - scope: Cluster + kind: "K8sValidation" + plural: "k8svalidations" + singular: "k8svalidation" + scope: "Cluster" versions: - - name: v1alpha1 + - name: "v1alpha1" schema: openAPIV3Schema: properties: @@ -38,123 +39,123 @@ spec: deepLevel2: properties: simple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('deep-') + - rule: "self.startsWith('deep-')" valueL2: - type: string + type: "string" required: - - valueL2 - type: object + - "valueL2" + type: "object" valueL1: - type: string + type: "string" required: - - deepLevel2 - - valueL1 - type: object + - "deepLevel2" + - "valueL1" + type: "object" x-kubernetes-validations: - - messageExpression: '''valueL1 ('' + self.valueL1 + '') must be equal - to deepLevel2.valueL2 ('' + self.deepLevel2.valueL2 + '')''' - rule: self.valueL1 == self.deepLevel2.valueL2 + - messageExpression: "'valueL1 (' + self.valueL1 + ') must be equal\ + \ to deepLevel2.valueL2 (' + self.deepLevel2.valueL2 + ')'" + rule: "self.valueL1 == self.deepLevel2.valueL2" immutable: - type: string + type: "string" x-kubernetes-validations: - - message: cannot be changed once set - rule: self == oldSelf + - message: "cannot be changed once set" + rule: "self == oldSelf" maxReplicas: - type: integer + type: "integer" minReplicas: - type: integer + type: "integer" monotonicCounter: - type: integer + type: "integer" x-kubernetes-validations: - - message: cannot decrease value once set - reason: FieldValueForbidden - rule: self >= oldSelf + - message: "cannot decrease value once set" + reason: "FieldValueForbidden" + rule: "self >= oldSelf" multiple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" namePrefix: - type: string + type: "string" onAbstractClass: properties: dummy: - type: string + type: "string" required: - - dummy - type: object + - "dummy" + type: "object" x-kubernetes-validations: - - rule: self.dummy.startsWith('abstract-') + - rule: "self.dummy.startsWith('abstract-')" onAttributeAndClass: properties: dummy: - type: string + type: "string" required: - - dummy - type: object + - "dummy" + type: "object" x-kubernetes-validations: - - rule: self.dummy.startsWith('on-class-') - - rule: self.dummy.startsWith('on-attr-') + - rule: "self.dummy.startsWith('on-class-')" + - rule: "self.dummy.startsWith('on-attr-')" onAttributeAndGetter: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" onGetter: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('on-getter-') + - rule: "self.startsWith('on-getter-')" priority: enum: - - high - - low - - medium - type: string + - "high" + - "low" + - "medium" + type: "string" x-kubernetes-validations: - - message: cannot transition directly between 'low' and 'high' - rule: '!(self == ''high'' && oldSelf == ''low'') && !(self == ''low'' - && oldSelf == ''high'')' + - message: "cannot transition directly between 'low' and 'high'" + rule: "!(self == 'high' && oldSelf == 'low') && !(self == 'low'\ + \ && oldSelf == 'high')" replicas: - type: integer + type: "integer" simple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('simple-') + - rule: "self.startsWith('simple-')" required: - - deepLevel1 - - maxReplicas - - minReplicas - - multiple - - namePrefix - - onAbstractClass - - onAttributeAndClass - - onAttributeAndGetter - - onGetter - - priority - - replicas - - simple - type: object + - "deepLevel1" + - "maxReplicas" + - "minReplicas" + - "multiple" + - "namePrefix" + - "onAbstractClass" + - "onAttributeAndClass" + - "onAttributeAndGetter" + - "onGetter" + - "priority" + - "replicas" + - "simple" + type: "object" x-kubernetes-validations: - - fieldPath: .replicas - rule: self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas - - message: replicas must be greater than or equal to minReplicas - rule: self.minReplicas <= self.replicas - - message: replicas must be smaller than or equal to maxReplicas - rule: self.replicas <= self.maxReplicas + - fieldPath: ".replicas" + rule: "self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas" + - message: "replicas must be greater than or equal to minReplicas" + rule: "self.minReplicas <= self.replicas" + - message: "replicas must be smaller than or equal to maxReplicas" + rule: "self.replicas <= self.maxReplicas" status: properties: availableReplicas: - type: integer - type: object - type: object + type: "integer" + type: "object" + type: "object" x-kubernetes-validations: - - messageExpression: '''name must start with '' + self.spec.namePrefix' - reason: FieldValueForbidden - rule: self.metadata.name.startsWith(self.spec.namePrefix) - - message: updates not allowed in degraded state - rule: self.status.availableReplicas >= self.spec.minReplicas + - messageExpression: "'name must start with ' + self.spec.namePrefix" + reason: "FieldValueForbidden" + rule: "self.metadata.name.startsWith(self.spec.namePrefix)" + - message: "updates not allowed in degraded state" + rule: "self.status.availableReplicas >= self.spec.minReplicas" served: true storage: true subresources: diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml index 50a58ec3692..0ad05e879a0 100644 --- a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml @@ -15,43 +15,44 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: multiples.sample.fabric8.io + name: "multiples.sample.fabric8.io" spec: - group: sample.fabric8.io + group: "sample.fabric8.io" names: - kind: Multiple - plural: multiples - singular: multiple - scope: Cluster + kind: "Multiple" + plural: "multiples" + singular: "multiple" + scope: "Cluster" versions: - - name: v2 + - name: "v2" schema: openAPIV3Schema: properties: spec: properties: v2: - type: string - type: object + type: "string" + type: "object" status: - type: object - type: object + type: "object" + type: "object" served: true storage: true - - name: v1 + - name: "v1" schema: openAPIV3Schema: properties: spec: properties: v1: - type: string - type: object + type: "string" + type: "object" status: - type: object - type: object + type: "object" + type: "object" served: true storage: false