Skip to content

Commit

Permalink
feat(crd-generator): enable configuration of whether quotes are used …
Browse files Browse the repository at this point in the history
…in generated yaml (6773)

Enable configuration of if quotes are used in generated yaml

Signed-off-by: MichaelMorris <michael.morris@est.tech>
---
Fix formatting errors

Signed-off-by: MichaelMorris <michael.morris@est.tech>
---
Added testcases to CRDGenerator

Signed-off-by: MichaelMorris <michael.morris@est.tech>
---
Updated as per review comments

Added builder parameter to asYaml() instead of minQuotes to enable future expansion
Added to CLI

Signed-off-by: MichaelMorris <michael.morris@est.tech>
---
Updated for review comment

Signed-off-by: MichaelMorris <michael.morris@est.tech>
---
Merge branch 'main' into issue-6763
  • Loading branch information
MichaelMorrisEst authored Feb 10, 2025
1 parent 91d2e87 commit 7b55de6
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#### Improvements

* Fix #6863: ensuring SerialExecutor does not throw RejectedExecutionException to prevent unnecessary error logs
* Fix #6763: CRDGenerator: YAML output customization

#### Dependency Upgrade

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
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.YamlDumpSettings;
import io.fabric8.kubernetes.client.utils.YamlDumpSettingsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -54,6 +56,7 @@ public class CRDGenerator {
private ObjectMapper objectMapper;
private KubernetesSerialization kubernetesSerialization;
private Map<String, CustomResourceInfo> infos;
private boolean minQuotes = false;

public CRDGenerator inOutputDir(File outputDir) {
output = new DirCRDOutput(outputDir);
Expand All @@ -70,6 +73,11 @@ public CRDGenerator withImplicitPreserveUnknownFields(boolean implicitPreserveUn
return this;
}

public CRDGenerator withMinQuotes(boolean minQuotes) {
this.minQuotes = minQuotes;
return this;
}

public CRDGenerator withParallelGenerationEnabled(boolean parallel) {
this.parallel = parallel;
return this;
Expand Down Expand Up @@ -212,7 +220,8 @@ public void emitCrd(HasMetadata crd, Set<String> 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);
YamlDumpSettings yamlSettings = new YamlDumpSettingsBuilder().setMinimizeQuotes(minQuotes).build();
String yaml = kubernetesSerialization.asYaml(crd, yamlSettings);
// strip the explicit start added by default
writer.write(yaml.substring(4));
final URI fileURI = output.crdURI(outputName);
Expand Down Expand Up @@ -284,4 +293,5 @@ public URI crdURI(String crdName) {
return getCRDFile(crdName).toURI();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

import static io.fabric8.crdv2.generator.CRDGeneratorAssertions.assertFileEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand Down Expand Up @@ -103,6 +104,7 @@ void choosingCRDVersionsShouldWork() {
@Test
void addingCustomResourceInfosShouldWork() {
CRDGenerator generator = newCRDGenerator();

assertTrue(generator.getCustomResourceInfos().isEmpty());

generator.customResourceClasses();
Expand Down Expand Up @@ -477,6 +479,66 @@ void checkGenerationIsDeterministic() throws Exception {
assertTrue(outputDir.delete());
}

@Test
void checkMinQuotesDefault() throws Exception {
final File outputDir = Files.createTempDirectory("crd-").toFile();
final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName();
CRDGenerationInfo crdInfo = newCRDGenerator()
.inOutputDir(outputDir)
.forCRDVersions("v1")
.customResourceClasses(Complex.class)
.detailedGenerate();

File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
String crd = Files.readString(crdFile.toPath());
assertTrue(crd.contains("\"complexkinds.example.com\""));

// only delete the generated files if the test is successful
assertTrue(crdFile.delete());
assertTrue(outputDir.delete());
}

@Test
void checkMinQuotesFalse() throws Exception {
final File outputDir = Files.createTempDirectory("crd-").toFile();
final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName();
CRDGenerationInfo crdInfo = newCRDGenerator()
.inOutputDir(outputDir)
.forCRDVersions("v1")
.customResourceClasses(Complex.class)
.withMinQuotes(false)
.detailedGenerate();

File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
String crd = Files.readString(crdFile.toPath());
assertTrue(crd.contains("\"complexkinds.example.com\""));

// only delete the generated files if the test is successful
assertTrue(crdFile.delete());
assertTrue(outputDir.delete());
}

@Test
void checkMinQuotesTrue() throws Exception {
final File outputDir = Files.createTempDirectory("crd-").toFile();
final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName();
CRDGenerationInfo crdInfo = newCRDGenerator()
.inOutputDir(outputDir)
.forCRDVersions("v1")
.customResourceClasses(Complex.class)
.withMinQuotes(true)
.detailedGenerate();

File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
String crd = Files.readString(crdFile.toPath());
assertTrue(crd.contains("complexkinds.example.com"));
assertFalse(crd.contains("\"complexkinds.example.com\""));

// only delete the generated files if the test is successful
assertTrue(crdFile.delete());
assertTrue(outputDir.delete());
}

@RepeatedTest(value = 10)
void checkGenerationMultipleVersionsOfCRDsIsDeterministic() throws Exception {
// generated CRD
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ public class CRDGeneratorCLI implements Runnable {
// spotless:on
Boolean implicitPreserveUnknownFields;

// spotless:off
@CommandLine.Option(
names = {"--min-quotes"},
description = "If `true`, quotes will only be included where necessary",
defaultValue = "false"
)
// spotless:on
Boolean minQuotes;

// spotless:off
@CommandLine.Option(
names = {"--include-package"},
Expand Down Expand Up @@ -218,6 +227,7 @@ public void run() {
.customResourceClasses(customResourceClasses)
.withParallelGenerationEnabled(!parallelDisabled)
.withImplicitPreserveUnknownFields(implicitPreserveUnknownFields)
.withMinQuotes(minQuotes)
.inOutputDir(sanitizedOutputDirectory);

crdGenerationInfo = crdGenerator.detailedGenerate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,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;

Expand Down Expand Up @@ -178,6 +184,7 @@ public void execute() throws MojoExecutionException {
.customResourceClasses(customResourceClassesLoaded)
.withParallelGenerationEnabled(parallel)
.withImplicitPreserveUnknownFields(implicitPreserveUnknownFields)
.withMinQuotes(minimizeQuotes)
.inOutputDir(outputDirectory);

CRDGenerationInfo crdGenerationInfo = crdGenerator.detailedGenerate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,23 @@ public <T> String asJson(T object) {
* @return a String containing a JSON representation of the provided object.
*/
public <T> String asYaml(T object) {
return asYaml(object, new YamlDumpSettingsBuilder().build());
}

/**
* Returns a YAML representation of the given object.
*
* <p>
* 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 yamlDumpSettings configuration for YAML serialization.
* @param <T> the type of the object being serialized.
* @return a String containing a JSON representation of the provided object.
*/
public <T> String asYaml(T object, YamlDumpSettings yamlDumpSettings) {
DumpSettings settings = DumpSettings.builder()
.setExplicitStart(true).setDefaultFlowStyle(FlowStyle.BLOCK).build();
final Dump yaml = new Dump(settings, new StandardRepresenter(settings) {
Expand All @@ -207,7 +224,7 @@ protected NodeTuple representMappingEntry(java.util.Map.Entry<?, ?> entry) {
}
}
org.snakeyaml.engine.v2.nodes.Node nodeKey = representData(key);
quote = true;
quote = !yamlDumpSettings.isMinQuotes();
return new NodeTuple(nodeKey, representData(entry.getValue()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.kubernetes.client.utils;

/**
* Configuration for serialization to YAML.
*/
public class YamlDumpSettings {

private boolean minQuotes;

YamlDumpSettings(boolean minQuotes) {
this.minQuotes = minQuotes;
}

public boolean isMinQuotes() {
return minQuotes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.kubernetes.client.utils;

/**
* Builder for YamlDumpSettings.
*/
public class YamlDumpSettingsBuilder {

private boolean minQuotes = false;

/**
* Set whether use of quotes should be minimized.
*
* @param minQuotes if {@code true}, quotes will only be included where necessary.
*/
public YamlDumpSettingsBuilder setMinimizeQuotes(boolean minQuotes) {
this.minQuotes = minQuotes;
return this;
}

/**
* Create immutable YamlDumpSettings
*
* @return YamlDumpSettings with the provided values
*/
public YamlDumpSettings build() {
return new YamlDumpSettings(minQuotes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -65,6 +71,29 @@ void withRegisteredKubernetesResourceShouldDeserializeToPod() {
.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, new YamlDumpSettingsBuilder().build());
assertThat(result).asString().contains("\"widgets.test.fabric8.io\"");

result = kubernetesSerialization.asYaml(crd, new YamlDumpSettingsBuilder().setMinimizeQuotes(true).build());
assertThat(result).asString().contains("widgets.test.fabric8.io").doesNotContain("\"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")
void withCollidingRegisteredKubernetesResourceShouldDeserializeAppropriate(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.kubernetes.client.utils;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

class YamlDumpSettingsTest {

@Test
void createSettingsWithMinQuotes() {
YamlDumpSettingsBuilder builder = new YamlDumpSettingsBuilder();
builder.setMinimizeQuotes(true);
YamlDumpSettings settings = builder.build();

assertTrue(settings.isMinQuotes());
}
}

0 comments on commit 7b55de6

Please sign in to comment.