Skip to content

Commit

Permalink
fix(java-generator): java generator does not recognize fields in CRDs…
Browse files Browse the repository at this point in the history
… other than metadata, spec, and status (#6216)

* Integration tests for top level fields

* Added generation of classes for top level properties

* Added generation for getter/setters for top level properties

* Small refactorings, clean up.

* Extended integration test with alwaysPreserveUnknown=true

* Removed read_only access from kind and apiVersion to keep them out of additionalProperties

* Merged changelog

* Fixed format violations

* Added tests w/wo additionalProps + negative test, simplified test crd

* Fixed formatting issues in ITs.

* review: java-generator additional fields

Signed-off-by: Marc Nuri <[email protected]>

---------

Signed-off-by: Marc Nuri <[email protected]>
Co-authored-by: Andrea Peruffo <[email protected]>
Co-authored-by: Marc Nuri <[email protected]>
  • Loading branch information
3 people committed Sep 11, 2024
1 parent 725ee5c commit 32b3473
Show file tree
Hide file tree
Showing 21 changed files with 1,261 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#### Bugs
* Fix #6038: Support for Gradle configuration cache
* Fix #6214: Java generator does not recognize fields in CRDs other than metadata, spec, and status

#### Improvements
* Fix #5264: Remove deprecated `Config.errorMessages` field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CRGeneratorRunner {

private final Config config;
private static final Set<String> STD_PROPS = Stream.of("metadata", "spec", "status", "apiVersion", "kind")
.collect(Collectors.toSet());

public CRGeneratorRunner(Config config) {
this.config = config;
Expand All @@ -50,13 +56,11 @@ public List<WritableCRCompilationUnit> generate(CustomResourceDefinition crd, St
for (CustomResourceDefinitionVersion crdv : crSpec.getVersions()) {
String version = crdv.getName();

String pkg = Optional.ofNullable(basePackageName)
String pkgNotOverridden = Optional.ofNullable(basePackageName)
.map(p -> p + "." + version)
.orElse(version);

if (config.getPackageOverrides().containsKey(pkg)) {
pkg = config.getPackageOverrides().get(pkg);
}
final String pkg = config.getPackageOverrides().getOrDefault(pkgNotOverridden, pkgNotOverridden);

AbstractJSONSchema2Pojo specGenerator = null;

Expand All @@ -73,6 +77,17 @@ public List<WritableCRCompilationUnit> generate(CustomResourceDefinition crd, St
crName + "Status", status, pkg, config);
}

boolean preserveUnknownFields = Boolean.TRUE
.equals(crdv.getSchema().getOpenAPIV3Schema().getXKubernetesPreserveUnknownFields());

Map<String, JSONSchemaProps> topLevelProps = crdv.getSchema().getOpenAPIV3Schema().getProperties().entrySet().stream()
.filter(e -> !STD_PROPS.contains(e.getKey()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));

List<String> requiredTopLevelProps = crdv.getSchema().getOpenAPIV3Schema().getRequired().stream()
.filter(prop -> !STD_PROPS.contains(prop))
.collect(Collectors.toList());

AbstractJSONSchema2Pojo crGenerator = new JCRObject(
pkg,
crName,
Expand All @@ -81,6 +96,10 @@ public List<WritableCRCompilationUnit> generate(CustomResourceDefinition crd, St
scope,
crName + "Spec",
crName + "Status",
topLevelProps,
requiredTopLevelProps,
preserveUnknownFields,
crdv.getSchema().getOpenAPIV3Schema().getDescription(),
specGenerator != null,
statusGenerator != null,
crdv.getStorage(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import io.fabric8.java.generator.Config;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class JCRObject extends AbstractJSONSchema2Pojo implements JObjectExtraAnnotations {
public class JCRObject extends JObject implements JObjectExtraAnnotations {

private final String pkg;
private final String type;
private final String className;
private final String group;
private final String version;
private final String scope;
Expand All @@ -53,18 +54,20 @@ public JCRObject(
String scope,
String specClassName,
String statusClassName,
Map<String, JSONSchemaProps> toplevelProps,
List<String> required,
boolean preserveUnknownFields,
String description,
boolean withSpec,
boolean withStatus,
boolean storage,
boolean served,
String singular,
String plural,
Config config) {
super(config, null, false, null, null);

this.pkg = (pkg == null) ? "" : pkg.trim();
this.type = (this.pkg.isEmpty()) ? type : pkg + "." + type;
this.className = type;
super(pkg, type, toplevelProps, required, preserveUnknownFields, config, description, false, null);

this.group = group;
this.version = version;
this.scope = scope;
Expand Down Expand Up @@ -146,7 +149,13 @@ public GeneratorResult generateJava() {
if (config.isObjectExtraAnnotations()) {
addExtraAnnotations(clz);
}

List<GeneratorResult.ClassResult> buffer = generateJavaFields(clz);

List<GeneratorResult.ClassResult> results = new ArrayList<>();
results.add(new GeneratorResult.ClassResult(className, cu));
results.addAll(buffer);
return new GeneratorResult(
Collections.singletonList(new GeneratorResult.ClassResult(className, cu)));
Collections.unmodifiableList(results));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.PackageDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.EnumDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
Expand All @@ -45,9 +44,9 @@
public class JObject extends AbstractJSONSchema2Pojo implements JObjectExtraAnnotations {

public static final String DEPRECATED_FIELD_MARKER = "deprecated";
private final String type;
private final String className;
private final String pkg;
protected final String type;
protected final String className;
protected final String pkg;
private final Map<String, AbstractJSONSchema2Pojo> fields;
private final Set<String> required;
private final Set<String> deprecated = new HashSet<>();
Expand Down Expand Up @@ -142,7 +141,7 @@ private String getSortedFieldsAsParam(Set<String> list) {
StringBuilder sb = new StringBuilder();
sb.append("{");
while (!sortedFields.isEmpty()) {
sb.append("\"" + sortedFields.remove(0) + "\"");
sb.append("\"").append(sortedFields.remove(0)).append("\"");
if (!sortedFields.isEmpty()) {
sb.append(",");
}
Expand Down Expand Up @@ -188,6 +187,14 @@ public GeneratorResult generateJava() {

clz.addImplementedType(new ClassOrInterfaceType(null, "io.fabric8.kubernetes.api.model.KubernetesResource"));

List<GeneratorResult.ClassResult> buffer = generateJavaFields(clz);

buffer.add(new GeneratorResult.ClassResult(this.className, cu));

return new GeneratorResult(buffer);
}

protected List<GeneratorResult.ClassResult> generateJavaFields(ClassOrInterfaceDeclaration clz) {
List<GeneratorResult.ClassResult> buffer = new ArrayList<>(this.fields.size() + 1);

List<String> sortedKeys = this.fields.keySet().stream().sorted().collect(Collectors.toList());
Expand All @@ -201,12 +208,8 @@ public GeneratorResult generateJava() {
// For now the inner types are only for enums
boolean isEnum = !gr.getInnerClasses().isEmpty();
if (isEnum) {
for (GeneratorResult.ClassResult enumCR : gr.getInnerClasses()) {
Optional<EnumDeclaration> ed = enumCR.getEnumByName(enumCR.getName());
if (ed.isPresent()) {
clz.addMember(ed.get());
}
}
gr.getInnerClasses()
.forEach(enumCR -> enumCR.getEnumByName(enumCR.getName()).ifPresent(clz::addMember));
}
buffer.addAll(gr.getTopLevelClasses());

Expand Down Expand Up @@ -342,10 +345,7 @@ public GeneratorResult generateJava() {
additionalSetter
.setBody(new BlockStmt().addStatement(new NameExpr("this." + Keywords.ADDITIONAL_PROPERTIES + ".put(key, value)")));
}

buffer.add(new GeneratorResult.ClassResult(this.className, cu));

return new GeneratorResult(buffer);
return buffer;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ void testCR() {
// Arrange
JCRObject cro = new JCRObject(
"v1alpha1",
"t",
"Type",
"g",
"v",
"Namespaced",
"Spec",
"Status",
Collections.emptyMap(),
Collections.emptyList(),
false,
"",
true,
true,
true,
Expand All @@ -75,7 +79,7 @@ void testCR() {

// Assert
assertEquals(1, res.getTopLevelClasses().size());
assertEquals("t", res.getTopLevelClasses().get(0).getName());
assertEquals("Type", res.getTopLevelClasses().get(0).getName());
assertEquals("v1alpha1",
res.getTopLevelClasses().get(0).getPackageDeclaration().get().getNameAsString());
}
Expand All @@ -85,12 +89,16 @@ void testNamespacedCR() {
// Arrange
JCRObject cro = new JCRObject(
null,
"t",
"Type",
"g",
"v",
"Namespaced",
"Spec",
"Status",
Collections.emptyMap(),
Collections.emptyList(),
false,
"",
true,
true,
true,
Expand All @@ -104,20 +112,24 @@ void testNamespacedCR() {

// Assert
assertEquals("io.fabric8.kubernetes.api.model.Namespaced", res.getTopLevelClasses().get(0)
.getClassByName("t").get().getImplementedTypes().get(0).getNameWithScope());
.getClassByName("Type").get().getImplementedTypes().get(0).getNameWithScope());
}

@Test
void testClusterScopeCR() {
// Arrange
JCRObject cro = new JCRObject(
null,
"t",
"Type",
"g",
"v",
"Cluster",
"Spec",
"Status",
Collections.emptyMap(),
Collections.emptyList(),
false,
"",
true,
true,
true,
Expand All @@ -130,20 +142,24 @@ void testClusterScopeCR() {
GeneratorResult res = cro.generateJava();

// Assert
assertTrue(res.getTopLevelClasses().get(0).getClassByName("t").get().getImplementedTypes().isEmpty());
assertTrue(res.getTopLevelClasses().get(0).getClassByName("Type").get().getImplementedTypes().isEmpty());
}

@Test
void testCRWithoutNamespace() {
// Arrange
JCRObject cro = new JCRObject(
null,
"t",
"Type",
"g",
"v",
"Namespaced",
"Spec",
"Status",
Collections.emptyMap(),
Collections.emptyList(),
false,
"",
true,
true,
true,
Expand All @@ -157,7 +173,7 @@ void testCRWithoutNamespace() {

// Assert
assertEquals(1, res.getTopLevelClasses().size());
assertEquals("t", res.getTopLevelClasses().get(0).getName());
assertEquals("Type", res.getTopLevelClasses().get(0).getName());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# 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.
#

invoker.goals=test
Loading

0 comments on commit 32b3473

Please sign in to comment.