Import the Annotation Processor into your build.
with Maven:
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-apt</artifactId>
<scope>provided</scope>
</dependency>
with Gradle:
dependencies {
annotationProcessor 'io.fabric8:crd-generator-apt:<version>'
...
}
Now you can define a class
that extends io.fabric8.kubernetes.client.CustomResource
and the corresponding CRD is generated in the folder: target/classes/META-INF/fabric8
For example, for code similar to:
@Group("org.example")
@Version("v1alpha1")
@ShortNames("ex")
public class Example extends CustomResource<ExampleSpec, ExampleStatus> implements Namespaced {}
public class ExampleSpec {
int someValue;
}
public class ExampleStatus {
boolean error;
String message;
}
Running the compile
task will generate 2 files:
target/classes/META-INF/fabric8/examples.org.example-v1.yml
target/classes/META-INF/fabric8/examples.org.example-v1beta1.yml
The schema <plural>.<group>-<CRD spec version>.yml
is used to calculate the file names.
The content of the examples.org.example-v1.yml
looks similar to:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: examples.org.example
spec:
group: org.example
names:
kind: Example
plural: examples
shortNames:
- ex
singular: example
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
spec:
properties:
someValue:
type: integer
type: object
status:
properties:
error:
type: boolean
message:
type: string
type: object
type: object
served: true
storage: true
subresources:
status: {}
The CRDs are generated in a deterministic way: Properties and lists of the schema are ordered, so that it is possible to check the resulting CRDs into source-control without cluttering the history.
If a field or one of its accessors is annotated with com.fasterxml.jackson.annotation.JsonProperty
public class ExampleSpec {
@JsonProperty("myValue")
int someValue;
}
The generated field in the CRD will be named after the value provided in the annotation instead of using the default name derived from the field name, such as:
spec:
properties:
myValue:
type: integer
type: object
If a field or one of its accessors is annotated with com.fasterxml.jackson.annotation.JsonPropertyDescription
public class ExampleSpec {
@JsonPropertyDescription("This is some value")
int someValue;
}
The generated field in the CRD will preserve the provided description, such as:
spec:
properties:
someValue:
description: This is some value
type: integer
type: object
If a field or one of its accessors is annotated with com.fasterxml.jackson.annotation.JsonIgnore
public class ExampleSpec {
@JsonIgnore
int someValue;
}
The field will be skipped in the generated CRD and will not appear in the schema for this type, such as:
spec:
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Default
public class ExampleSpec {
@Default("foo")
String someValue;
}
The field will have the default
property in the generated CRD, such as:
spec:
properties:
someValue:
default: foo
type: string
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Min
public class ExampleSpec {
@Min(-1)
int someValue;
}
The field will have the minimum
property in the generated CRD, such as:
spec:
properties:
someValue:
minimum: -1.0
type: integer
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Max
public class ExampleSpec {
@Max(1)
int someValue;
}
The field will have the maximum
property in the generated CRD, such as:
spec:
properties:
someValue:
maximum: 1.0
type: integer
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Pattern
public class ExampleSpec {
@Pattern("\\b[1-9]\\b")
String someValue;
}
The field will have the pattern
property in the generated CRD, such as:
spec:
properties:
someValue:
pattern: "\\b[1-9]\\b"
type: string
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Nullable
public class ExampleSpec {
@Nullable
String someValue;
}
The field will have the nullable
property in the generated CRD, such as:
spec:
properties:
someValue:
nullable: true
type: string
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.Required
public class ExampleSpec {
@Required
int someValue;
}
The field will be marked as required
in the generated CRD, such as:
spec:
properties:
someValue:
type: integer
required:
- someValue
type: object
If a field or one of its accessors is annotated with io.fabric8.generator.annotation.ValidationRule
public class ExampleSpec {
@ValidationRule("self.startsWith('start-')")
String someValue;
}
The field will have the x-kubernetes-validations
property in the generated CRD, such as:
spec:
properties:
someValue:
type: string
x-kubernetes-validations:
- rule: self.startsWith('start-')
type: object
If a class is annotated with io.fabric8.generator.annotation.ValidationRule
:
@ValidationRule(value="self.someValue.startsWith('start-')")
public class ExampleSpec {
String someValue;
}
The object will have the x-kubernetes-validations
property in the generated CRD, such as:
spec:
properties:
someValue:
type: string
type: object
x-kubernetes-validations:
- rule: self.someValue.startsWith('start-')
Note that all occurences will end up in the resulting list if multiple ValidationRule
annotations are defined on the same field and/or class.
The annotation can also be used on the CustomResource class itself, which allows to define CEL rules on the root-level.
Please look at the example and the resulting CRD to explore all features.
If a field or one of its accessors is annotated with io.fabric8.crd.generator.annotation.PrinterColumn
public class ExampleSpec {
@PrinterColumn(name = "SOME_VALUE", priority = 1)
int someValue;
}
The CRD generator will customize columns shown by the kubectl get
command. Above example adds the SOME_VALUE
column.
- additionalPrinterColumns:
- jsonPath: .spec.someValue
name: SOME_VALUE
type: int
priority: 1
If a field or one of its accessors is annotated with io.fabric8.crd.generator.annotation.SchemaFrom
public class ExampleSpec {
@SchemaFrom(ExampleStatus.class)
int someValue;
}
The CRD generator will substitute the default type inferred from the field and replace it by the computed schema associated with the Java class provided as a value of the SchemaFrom
annotation, as seen below, where ExampleStatus
is the class defined in the example above:
spec:
properties:
someValue:
properties:
error:
type: boolean
message:
type: string
type: object
type: object
If a class is annotated with io.fabric8.crd.generator.annotation.SchemaSwap
@SchemaSwap(originalType = ExampleSpec.class, fieldName = "someValue", targetType = ExampleStatus.class)
public class Example extends CustomResource<ExampleSpec, ExampleStatus> implements Namespaced {}
The CRD generator will perform the same substitution as a SchemaFrom
annotation with value
equal to targetType
was placed on the field named fieldName
in the originalType
class:
spec:
properties:
someValue:
properties:
error:
type: boolean
message:
type: string
type: object
type: object
The name of the field is restricted to the original fieldName
and should be backed by a matching concrete field of the matching class. Getters, setters, and constructors are not taken into consideration.
SchemaSwaps cannot currently be nested - if a more deeply nested class contains a swap for the same class and field, an exception will be thrown.
The SchemaSwap
annotation has an optional depth
property, which is for advanced scenarios involving cyclic references. Since CRDs cannot directly represent cycles, the depth
property may be used to control an unrolling of the representation in your CRD. A depth
of 0
, the default, performs the swap on the field without the originalType
appearing in your CRD. A depth
of n will allow the originalType
to appear in the CRD up to a nesting depth of n
, with the specified field at the nth level terminated by the targetType
.
If a field or one of its accessors is annotated with
com.fasterxml.jackson.annotation.JsonAnyGetter
/com.fasterxml.jackson.annotation.JsonAnySetter
or the field type is com.fasterxml.jackson.databind.JsonNode
public class ExampleSpec {
JsonNode someValue;
@JsonAnyGetter
JsonNode getSomeValue() {
return someValue;
}
@JsonAnySetter
void setSomeValue(JsonNode value) {
this.someValue = value;
}
}
Corresponding x-kubernetes-preserve-unknown-fields: true
will be generated in the output CRD, such as:
spec:
properties:
someValue:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
x-kubernetes-preserve-unknown-fields: true
You can also annotate a field with io.fabric8.crd.generator.annotation.PreserveUnknownFields
:
interface ExampleInterface {}
public class ExampleSpec {
@PreserveUnknownFields
ExampleInterface someValue;
}
will be generated as:
spec:
properties:
someValue:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
If a custom resource class is annotated with io.fabric8.crd.generator.annotation.Annotations
@Annotations({ "one=1", "two=2" })
public class Example extends CustomResource<ExampleSpec, ExampleStatus> implements Namespaced {}
The CRD generator will add the additional annotations
:
metadata:
name: examples.org.example
annotations:
one: "1"
two: "2"
spec:
...
If a custom resource class is annotated with io.fabric8.crd.generator.annotation.Labels
@Labels({ "three=3", "four=4" })
public class Example extends CustomResource<ExampleSpec, ExampleStatus> implements Namespaced {}
The CRD generator will add the additional labels
:
metadata:
name: examples.org.example
labels:
four: "4"
three: "3"
spec:
...
The status subresource will be emitted, if a status type has been defined for the Custom Resource:
@Version("v1")
@Group("sample.fabric8.io")
public class WithStatusSubresource extends CustomResource<ExampleSpec, ExampleStatus> {}
Result:
subresources:
status: {}
The scale subresource
will be emitted, if at least one field is marked with either @SpecReplicas
, @StatusReplicas
or @LabelSelector
.
If used, @SpecReplicas
and @StatusReplicas
are both required to produce a valid CRD. @LabelSelector
is optional.
All three annotations can be used only once per Custom Resource. Further constraints are:
Annotation | Allowed in Spec | Allowed in Status | Required Field Type |
---|---|---|---|
@SpecReplicas |
Yes | No | integer |
@StatusReplicas |
No | Yes | integer |
@LabelSelector |
Yes | Yes | string |
@Version("v1")
@Group("sample.fabric8.io")
public class WithScaleSubresource extends CustomResource<ExampleSpec, ExampleStatus> {}
public class ExampleSpec {
@SpecReplicas
int replicas;
}
public class ExampleStatus {
@StatusReplicas
int replicas;
@LabelSelector
String labelSelector;
}
Result:
kind: "CustomResourceDefinition"
metadata:
name: "withscalesubresources.sample.fabric8.io"
spec:
group: "sample.fabric8.io"
names:
kind: "Replica"
plural: "replicas"
singular: "replica"
scope: "Cluster"
versions:
- name: "v1"
schema:
openAPIV3Schema:
properties:
spec:
properties:
replicas:
type: "integer"
type: "object"
status:
properties:
labelSelector:
type: "string"
replicas:
type: "integer"
type: "object"
type: "object"
served: true
storage: true
subresources:
scale:
labelSelectorPath: ".status.labelSelector"
specReplicasPath: ".spec.replicas"
statusReplicasPath: ".status.replicas"
status: {}
The CRD Generator supports multiple versions of the same kind. In this case a schema for each version will be generated and merged into a single CRD. Keep in mind, that only one version can be marked as stored at the same time!
package io.fabric8.crd.example.multiple.v2;
@Group("sample.fabric8.io")
@Version(value = "v2", storage = true, served = true)
public class Multiple extends CustomResource<MultipleSpec, Void> {}
// -------
package io.fabric8.crd.example.multiple.v1;
@Group("sample.fabric8.io")
@Version(value = "v1", storage = false, served = true, deprecated = true)
public class Multiple extends CustomResource<MultipleSpec, Void> {}
Result:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: multiples.sample.fabric8.io
spec:
group: sample.fabric8.io
names:
kind: Multiple
plural: multiples
singular: multiple
versions:
- name: v2
storage: true
served: true
schema:
openAPIV3Schema:
[...]
- name: v1
storage: false
served: true
deprecated: true
schema:
openAPIV3Schema:
[...]
Annotation | Description |
---|---|
com.fasterxml.jackson.annotation.JsonProperty |
The field is named after the provided value instead of looking up the java field name |
com.fasterxml.jackson.annotation.JsonPropertyDescription |
The provided text is be embedded in the description of the field |
com.fasterxml.jackson.annotation.JsonIgnore |
The field is ignored |
io.fabric8.crd.generator.annotation.PreserveUnknownFields |
The field have x-kubernetes-preserve-unknown-fields: true defined |
com.fasterxml.jackson.annotation.JsonAnyGetter |
The corresponding object have x-kubernetes-preserve-unknown-fields: true defined |
com.fasterxml.jackson.annotation.JsonAnySetter |
The corresponding object have x-kubernetes-preserve-unknown-fields: true defined |
io.fabric8.generator.annotation.Min |
The field defines a validation min |
io.fabric8.generator.annotation.Max |
The field defines a validation max |
io.fabric8.generator.annotation.Pattern |
The field defines a validation pattern |
io.fabric8.generator.annotation.Nullable |
The field is marked as nullable |
io.fabric8.generator.annotation.Required |
The field is marked as required |
io.fabric8.generator.annotation.ValidationRule |
The field or object is validated by a CEL rule |
io.fabric8.crd.generator.annotation.SchemaFrom |
The field type for the generation is the one coming from the annotation |
io.fabric8.crd.generator.annotation.SchemaSwap |
Similar to SchemaFrom, but can be applied at any point in the class hierarchy |
io.fabric8.crd.generator.annotation.Annotations |
Additional annotations in metadata |
io.fabric8.crd.generator.annotation.Labels |
Additional labels in metadata |
io.fabric8.crd.generator.annotation.PrinterColumn |
Customize columns shown by the kubectl get command |
io.fabric8.kubernetes.model.annotation.SpecReplicas |
The field is used in scale subresource as specReplicaPath |
io.fabric8.kubernetes.model.annotation.StatusReplicas |
The field is used in scale subresource as statusReplicaPath |
io.fabric8.kubernetes.model.annotation.LabelSelector |
The field is used in scale subresource as labelSelectorPath |
A field of type com.fasterxml.jackson.databind.JsonNode
is encoded as an empty object with x-kubernetes-preserve-unknown-fields: true
defined.
It's possible to speed up the CRDs generation by using parallel computation. Please note that this feature is experimental, and it may lead to unexpected results.
To enable it, you need to set the io.fabric8.crd.generator.parallel
property to true
in the processor.
with Maven:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Aio.fabric8.crd.generator.parallel=true</arg>
</compilerArgs>
</configuration>
</plugin>
with Gradle:
tasks.withType(JavaCompile) {
options.compilerArgs += ["-Aio.fabric8.crd.generator.parallel=true"]
}