diff --git a/CHANGELOG.md b/CHANGELOG.md index 4611fd5db5b..76644869673 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ #### Improvements +* Fix #6763: CRDGenerator: YAML output customization + #### Dependency Upgrade #### New Features 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 47a7f741f3d..62b010abac1 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 @@ -16,6 +16,7 @@ package io.fabric8.crdv2.generator; import com.fasterxml.jackson.databind.ObjectMapper; + import io.fabric8.crdv2.generator.v1.CustomResourceHandler; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.utils.ApiVersionUtil; @@ -54,6 +55,7 @@ public class CRDGenerator { private ObjectMapper objectMapper; private KubernetesSerialization kubernetesSerialization; private Map infos; + private boolean minQuotes = false; public CRDGenerator inOutputDir(File outputDir) { output = new DirCRDOutput(outputDir); @@ -69,6 +71,11 @@ public CRDGenerator withImplicitPreserveUnknownFields(boolean implicitPreserveUn this.implicitPreserveUnknownFields = implicitPreserveUnknownFields; return this; } + + public CRDGenerator withMinQuotes(boolean minQuotes) { + this.minQuotes = minQuotes; + return this; + } public CRDGenerator withParallelGenerationEnabled(boolean parallel) { this.parallel = parallel; @@ -212,7 +219,7 @@ public void emitCrd(HasMetadata crd, Set dependentClassNames, CRDGenerat final String outputName = getOutputName(crdName, version); try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) { writer.write("# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n"); - String yaml = kubernetesSerialization.asYaml(crd); + String yaml = kubernetesSerialization.asYaml(crd, minQuotes); // strip the explicit start added by default writer.write(yaml.substring(4)); final URI fileURI = output.crdURI(outputName); @@ -284,4 +291,5 @@ public URI crdURI(String crdName) { return getCRDFile(crdName).toURI(); } } + } diff --git a/crd-generator/maven-plugin/src/main/java/io/fabric8/crd/generator/maven/plugin/CrdGeneratorMojo.java b/crd-generator/maven-plugin/src/main/java/io/fabric8/crd/generator/maven/plugin/CrdGeneratorMojo.java index 3995d033c30..5009b0b64ca 100644 --- a/crd-generator/maven-plugin/src/main/java/io/fabric8/crd/generator/maven/plugin/CrdGeneratorMojo.java +++ b/crd-generator/maven-plugin/src/main/java/io/fabric8/crd/generator/maven/plugin/CrdGeneratorMojo.java @@ -128,6 +128,12 @@ public class CrdGeneratorMojo extends AbstractMojo { */ @Parameter(property = "fabric8.crd-generator.skip", defaultValue = "false") boolean skip; + + /** + * If {@code true}, quotes will only be included where necessary + */ + @Parameter(property = "fabric8.crd-generator.minimizeQuotes", defaultValue = "false") + boolean minimizeQuotes; private final CustomResourceCollector customResourceCollector; private final CRDGenerator crdGenerator; @@ -178,6 +184,7 @@ public void execute() throws MojoExecutionException { .customResourceClasses(customResourceClassesLoaded) .withParallelGenerationEnabled(parallel) .withImplicitPreserveUnknownFields(implicitPreserveUnknownFields) + .withMinQuotes(minimizeQuotes) .inOutputDir(outputDirectory); CRDGenerationInfo crdGenerationInfo = crdGenerator.detailedGenerate(); diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java index 7f7bb505178..a05f9947a6f 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java @@ -174,7 +174,7 @@ public String asJson(T object) { throw KubernetesClientException.launderThrowable(e); } } - + /** * Returns a YAML representation of the given object. * @@ -188,6 +188,23 @@ public String asJson(T object) { * @return a String containing a JSON representation of the provided object. */ public String asYaml(T object) { + return asYaml(object, false); + } + + /** + * Returns a YAML representation of the given object. + * + *

+ * If the provided object contains a JsonAnyGetter annotated method with a Map that contains an entry that + * overrides a field of the provided object, the Map entry will take precedence upon serialization. Properties won't + * be duplicated. + * + * @param object the object to serialize. + * @param minQuotes whether strings will be rendered without quotes (true) or with quotes (false). + * @param the type of the object being serialized. + * @return a String containing a JSON representation of the provided object. + */ + public String asYaml(T object, boolean minQuotes) { DumpSettings settings = DumpSettings.builder() .setExplicitStart(true).setDefaultFlowStyle(FlowStyle.BLOCK).build(); final Dump yaml = new Dump(settings, new StandardRepresenter(settings) { @@ -207,7 +224,7 @@ protected NodeTuple representMappingEntry(java.util.Map.Entry entry) { } } org.snakeyaml.engine.v2.nodes.Node nodeKey = representData(key); - quote = true; + quote = !minQuotes; return new NodeTuple(nodeKey, representData(entry.getValue())); } diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/KubernetesSerializationTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/KubernetesSerializationTest.java index 57a789ea127..159e3989960 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/KubernetesSerializationTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/KubernetesSerializationTest.java @@ -22,6 +22,7 @@ import io.fabric8.kubernetes.api.model.GenericKubernetesResource; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition; import io.fabric8.kubernetes.model.annotation.Group; import io.fabric8.kubernetes.model.annotation.Version; import org.junit.jupiter.api.BeforeEach; @@ -33,6 +34,11 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.stream.Collectors; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -64,6 +70,30 @@ void withRegisteredKubernetesResourceShouldDeserializeToPod() { assertThat(kubernetesSerialization. unmarshal("{\"kind\":\"Pod\", \"apiVersion\":\"v1\"}")) .isInstanceOf(io.fabric8.kubernetes.api.model.Pod.class); } + + @Test + void asYaml() throws Exception { + final String input = readYamlToString("/serialization/test-crd-schema.yml"); + final CustomResourceDefinition crd = Serialization.unmarshal(input, CustomResourceDefinition.class); + + String result = kubernetesSerialization.asYaml(crd); + assertThat(result).asString().contains("\"widgets.test.fabric8.io\""); + + result = kubernetesSerialization.asYaml(crd, false); + assertThat(result).asString().contains("\"widgets.test.fabric8.io\""); + + result = kubernetesSerialization.asYaml(crd, true); + assertThat(result).asString().doesNotContain("\"widgets.test.fabric8.io\""); + assertThat(result).asString().contains("widgets.test.fabric8.io"); + } + + private String readYamlToString(String path) throws IOException { + return Files.readAllLines( + new File(KubernetesSerializationTest.class.getResource(path).getFile()).toPath(), StandardCharsets.UTF_8) + .stream() + .filter(line -> !line.startsWith("#")) + .collect(Collectors.joining("\n")); + } @ParameterizedTest(name = "{index}: {0} {1} deserializes to {2}") @MethodSource("sameGVK")