Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import io.fabric8.crd.generator.annotation.SelectableField;
import io.fabric8.crdv2.generator.InternalSchemaSwaps.SwapResult;
import io.fabric8.crdv2.generator.ResolvingContext.GeneratorObjectSchema;
import io.fabric8.crdv2.generator.v1.JsonSchema.V1JSONSchemaProps;
import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.generator.annotation.Default;
import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Min;
Expand All @@ -54,6 +56,7 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.api.model.runtime.RawExtension;
import io.fabric8.kubernetes.client.utils.Utils;
import io.fabric8.kubernetes.model.annotation.LabelSelector;
Expand Down Expand Up @@ -412,7 +415,7 @@ private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwa
String... ignore) {
Set<String> ignores = ignore.length > 0 ? new LinkedHashSet<>(Arrays.asList(ignore)) : Collections.emptySet();

final T objectSchema = singleProperty("object");
T objectSchema = singleProperty("object");

schemaSwaps = schemaSwaps.branchAnnotations();
final InternalSchemaSwaps swaps = schemaSwaps;
Expand Down Expand Up @@ -505,6 +508,28 @@ private T resolveObject(LinkedHashMap<String, String> visited, InternalSchemaSwa
consumeRepeatingAnnotation(rawClass, ValidationRule.class,
v -> validationRules.add(from(v)));
addToValidationRules(objectSchema, validationRules);
return handleSchemaCustomizer(objectSchema, rawClass);
}

private T handleSchemaCustomizer(T objectSchema, Class<?> rawClass) {
if (objectSchema instanceof JSONSchemaProps) {
JSONSchemaProps[] props = new JSONSchemaProps[] { (JSONSchemaProps) objectSchema };
consumeRepeatingAnnotation(rawClass, SchemaCustomizer.class, sc -> {
try {
props[0] = sc.value().getConstructor().newInstance().apply(props[0], sc.input(),
this.resolvingContext.kubernetesSerialization);
} catch (Exception e) {
if (!(e instanceof RuntimeException)) {
e = new RuntimeException(e);
}
throw (RuntimeException) e;
}
});
if (props[0] != objectSchema) {
// hack to convert back to V1JSONSchemaProps
objectSchema = (T) resolvingContext.kubernetesSerialization.convertValue(props[0], V1JSONSchemaProps.class);
}
}
return objectSchema;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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.crdv2.generator.v1;

import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SchemaCustomizer {

public interface Customizer {

/**
* Customizes the given {@link JSONSchemaProps}
*
* @param props the {@link JSONSchemaProps} to customize
* @param input the input String from the {@link SchemaCustomizer} annotation
* @param kubernetesSerialization
* @return the customized {@link JSONSchemaProps}
*/
JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization);

}

/**
* Replace the incoming schema with the given the input of JSONSchemaProps in yaml or json,
*/
public static class RawCustomizer implements Customizer {

@Override
public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) {
return kubernetesSerialization.unmarshal(input, JSONSchemaProps.class);
}

}

/**
* Patch the incoming schema with the given JSON merge patch input.
*/
public static class MergePatchCustomizer implements Customizer {

@Override
public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) {
kubernetesSerialization.mergePatch(props, input);
return props;
}

}

Class<? extends Customizer> value();

String input() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.crdv2.example.customized;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;

@SchemaCustomizer(input = "my description", value = DescriptionCustomizer.class)
public class Customized {

public int field;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.crdv2.example.customized;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;

public class DescriptionCustomizer implements SchemaCustomizer.Customizer {

@Override
public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) {
props.setDescription(input);
return props;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.crdv2.example.customized;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;

@SchemaCustomizer(input = "properties:\n"
+ " prop:\n"
+ " type: \"integer\"", value = SchemaCustomizer.RawCustomizer.class)
public class RawCustomized {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.crdv2.generator.v1;

import io.fabric8.crdv2.example.customized.Customized;
import io.fabric8.crdv2.example.customized.RawCustomized;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import org.junit.jupiter.api.Test;

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

class SchemaCustomizerTest {

@Test
void applyCustomizer() {
JSONSchemaProps schema = JsonSchema.from(Customized.class);
assertEquals("my description", schema.getDescription());
}

@Test
void applyRawCustomizer() {
JSONSchemaProps schema = JsonSchema.from(RawCustomized.class);
assertEquals(1, schema.getProperties().size());
assertEquals("integer", schema.getProperties().get("prop").getType());
}

}
5 changes: 5 additions & 0 deletions doc/CRD-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,11 @@ spec:
# [...]
```

### Schema Customization

In some instances the built-in set of annotations and logic may not produce the desired CRD output. There is a mechanism
included in the crd-generator-api-v2 module for this. See the `io.fabric8.crdv2.generator.v1.SchemaCustomizer` annotation
for directly manipulating the JSONSchemaProps of the annotated resource. This annotation is applied last, after all of the other annotations are processed.

## Features cheatsheet

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
Expand Down Expand Up @@ -429,4 +430,13 @@ public String convertToJson(String input) {
}
}

public void mergePatch(Object updatable, String patch) {
ObjectReader reader = mapper.readerForUpdating(updatable);
try {
reader.readValue(patch);
} catch (JsonProcessingException e) {
throw KubernetesClientException.launderThrowable(e);
}
}

}
Loading