Skip to content

Commit 7600dd8

Browse files
authored
feat(crd-generator): process generated CRDs before they are written out (6828)
feat: process generated CRDs but before they are written out Fixes #6827 --- refactor: unify handling of post-processor with rest of configuration
1 parent 2b1e65d commit 7600dd8

File tree

7 files changed

+124
-33
lines changed

7 files changed

+124
-33
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#### New Features
1616

17+
* Fix #6827: Add CRDPostProcessor to process generated CRDs before they are written out
18+
1719
#### _**Note**_: Breaking changes
1820

1921
### 7.1.0 (2025-01-30)

crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
public class CRDGenerator {
4949

5050
private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class);
51+
private static final CRDPostProcessor nullProcessor = new CRDPostProcessor() {
52+
};
5153
private final Map<String, AbstractCustomResourceHandler> handlers = new HashMap<>(2);
5254
private CRDOutput<? extends OutputStream> output;
5355
private boolean parallel;
@@ -56,10 +58,10 @@ public class CRDGenerator {
5658
private KubernetesSerialization kubernetesSerialization;
5759
private Map<String, CustomResourceInfo> infos;
5860
private boolean minQuotes = false;
61+
private CRDPostProcessor postProcessor = nullProcessor;
5962

6063
public CRDGenerator inOutputDir(File outputDir) {
61-
output = new DirCRDOutput(outputDir);
62-
return this;
64+
return withOutput(new DirCRDOutput(outputDir));
6365
}
6466

6567
public CRDGenerator withOutput(CRDOutput<? extends OutputStream> output) {
@@ -88,6 +90,11 @@ public CRDGenerator withObjectMapper(ObjectMapper mapper, KubernetesSerializatio
8890
return this;
8991
}
9092

93+
public CRDGenerator withPostProcessor(CRDPostProcessor postProcessor) {
94+
this.postProcessor = postProcessor;
95+
return this;
96+
}
97+
9198
public CRDGenerator forCRDVersions(List<String> versions) {
9299
return versions != null && !versions.isEmpty() ? forCRDVersions(versions.toArray(new String[0]))
93100
: this;
@@ -115,9 +122,7 @@ Map<String, AbstractCustomResourceHandler> getHandlers() {
115122
return handlers;
116123
}
117124

118-
// this is public API, so we cannot change the signature, so there is no way to prevent the possible heap pollution
119-
// (we also cannot use @SafeVarargs, because that requires the method to be final, which is another signature change)
120-
@SuppressWarnings("unchecked")
125+
@SafeVarargs
121126
public final CRDGenerator customResourceClasses(Class<? extends HasMetadata>... crClasses) {
122127
if (crClasses == null) {
123128
return this;
@@ -163,6 +168,12 @@ public int generate() {
163168
return detailedGenerate().numberOfGeneratedCRDs();
164169
}
165170

171+
/**
172+
* Generates the CRDs with the provided configuration and returns detailed information about what was generated as a
173+
* {@link CRDGenerationInfo} instance.
174+
*
175+
* @return a {@link CRDGenerationInfo} providing detailed information about what was generated
176+
*/
166177
public CRDGenerationInfo detailedGenerate() {
167178
if (getCustomResourceInfos().isEmpty()) {
168179
LOGGER.warn("No resources were registered with the 'customResources' method to be generated");
@@ -201,9 +212,10 @@ public CRDGenerationInfo detailedGenerate() {
201212

202213
if (parallel) {
203214
handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext())))
204-
.collect(Collectors.toList()).stream().forEach(ForkJoinTask::join);
215+
.collect(Collectors.toList())
216+
.forEach(ForkJoinTask::join);
205217
} else {
206-
handlers.values().stream().forEach(h -> h.handle(info, context.forkContext()));
218+
handlers.values().forEach(h -> h.handle(info, context.forkContext()));
207219
}
208220
}
209221
}
@@ -217,6 +229,10 @@ public CRDGenerationInfo detailedGenerate() {
217229
public void emitCrd(HasMetadata crd, Set<String> dependentClassNames, CRDGenerationInfo crdGenerationInfo) {
218230
final String version = ApiVersionUtil.trimVersion(crd.getApiVersion());
219231
final String crdName = crd.getMetadata().getName();
232+
233+
// post-process the CRD if needed
234+
crd = postProcessor.process(crd, version);
235+
220236
try {
221237
final String outputName = getOutputName(crdName, version);
222238
try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (C) 2015 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.fabric8.crdv2.generator;
17+
18+
import io.fabric8.kubernetes.api.model.HasMetadata;
19+
20+
public interface CRDPostProcessor {
21+
22+
/**
23+
* Processes the specified CRD (passed as {@link HasMetadata} to be able to handle multiple versions of the CRD spec) after
24+
* they are generated but before they are written out
25+
*
26+
* @param crd the CRD to process as a {@link HasMetadata}
27+
* @param crdSpecVersion the CRD specification version of the CRD to process (to be able to cast to the appropriate CRD class)
28+
* @return the modified CRD (though, typically, this would just be the CRD specified as input, modified in place)
29+
*/
30+
default HasMetadata process(HasMetadata crd, String crdSpecVersion) {
31+
return crd;
32+
}
33+
}

crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceValidation;
3939
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
4040
import io.fabric8.kubernetes.client.CustomResource;
41+
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
4142
import io.fabric8.kubernetes.client.utils.Serialization;
4243
import io.fabric8.kubernetes.model.Scope;
4344
import org.junit.jupiter.api.RepeatedTest;
@@ -49,6 +50,7 @@
4950
import java.io.ByteArrayInputStream;
5051
import java.io.ByteArrayOutputStream;
5152
import java.io.File;
53+
import java.io.FileInputStream;
5254
import java.net.URL;
5355
import java.nio.file.Files;
5456
import java.util.ArrayList;
@@ -595,6 +597,38 @@ void checkK8sValidationRules() throws Exception {
595597
assertTrue(outputDir.delete());
596598
}
597599

600+
@Test
601+
void checkPostProcessing() throws Exception {
602+
// generated CRD
603+
final File outputDir = Files.createTempDirectory("crd-").toFile();
604+
final String crdName = CustomResourceInfo.fromClass(Simplest.class).crdName();
605+
606+
final CRDGenerationInfo crdInfo = newCRDGenerator()
607+
.inOutputDir(outputDir)
608+
.customResourceClasses(Simplest.class)
609+
.forCRDVersions("v1")
610+
.withPostProcessor(new CRDPostProcessor() {
611+
@Override
612+
public HasMetadata process(HasMetadata crd, String crdSpecVersion) {
613+
final var meta = crd.getMetadata().edit().addToLabels("foo", "bar").build();
614+
crd.setMetadata(meta);
615+
return crd;
616+
}
617+
})
618+
.detailedGenerate();
619+
620+
final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
621+
final var serialization = new KubernetesSerialization();
622+
final var crd = serialization.unmarshal(new FileInputStream(crdFile), HasMetadata.class);
623+
assertNotNull(crd);
624+
assertEquals(crdName, crd.getMetadata().getName());
625+
assertEquals("bar", crd.getMetadata().getLabels().get("foo"));
626+
627+
// only delete the generated files if the test is successful
628+
assertTrue(crdFile.delete());
629+
assertTrue(outputDir.delete());
630+
}
631+
598632
private CustomResourceDefinitionVersion checkCRD(Class<? extends CustomResource<?, ?>> customResource, String kind,
599633
String plural,
600634
Scope scope) {

doc/CRD-generator-migration-v2.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ _Deprecated since 7.0.0_
1616
_GA since 7.0.0_
1717

1818
- **CRD Generator API v2** - `io.fabric8:crd-generator-api-v2`
19-
_Core implementation of the new generator, based on [Jackson/jsonSchema](https://github.com/FasterXML/jackson-module-jsonSchema)._
19+
_Core implementation of the new generator, based
20+
on [Jackson/jsonSchema](https://github.com/FasterXML/jackson-module-jsonSchema)._
2021
- **CRD Generator Collector** - `io.fabric8:crd-generator-collector`
2122
_Shared component to find and load compiled Custom Resource classes in directories and Jar files._
2223
- **CRD Generator Maven Plugin** - `io.fabric8:crd-generator-maven-plugin`
@@ -101,7 +102,12 @@ myMap:
101102
102103
## Default values for CRD fields can be numeric or boolean
103104
104-
Previously default values defined by `@Default` could only be used on string fields.
105+
Previously default values defined by `@Default` could only be used on string fields.
105106
With CRD Generator v2 defaults can be set on numeric and boolean fields, too.
106107
In the same way is `@JsonProperty(defaultValue)` now working.
107108

109+
## Post-processing CRDs before they are written out to disk
110+
111+
It is now possible to provide a `CRDPostProcessor` implementation when generating CRDs via the
112+
`CRDGenerator.detailedGenerate` method. This allows to process generated CRDs before they are written out to the disk.
113+

junit/kube-api-test/core/src/test/java/io/fabric8/kubeapitest/binary/BinaryDownloaderTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,19 @@ class BinaryDownloaderTest {
3434
BinaryDownloader binaryDownloader = new BinaryDownloader("target", mockBinaryRepo, mockOsInfo);
3535

3636
@Test
37-
void findsLatestBinary() {
38-
when(mockOsInfo.getOSName()).thenReturn("linux");
39-
when(mockOsInfo.getOSArch()).thenReturn("amd64");
37+
void findsLatestBinary() {
38+
when(mockOsInfo.getOSName()).thenReturn("linux");
39+
when(mockOsInfo.getOSArch()).thenReturn("amd64");
4040

41-
when(mockBinaryRepo.listObjectNames()).thenReturn(List.of(
42-
new ArchiveDescriptor("1.17.9","amd64","linux"),
43-
new ArchiveDescriptor("1.26.1","amd64","darwin"),
44-
new ArchiveDescriptor(VERSION,"amd64","linux"))
45-
.stream());
41+
when(mockBinaryRepo.listObjectNames()).thenReturn(List.of(
42+
new ArchiveDescriptor("1.17.9", "amd64", "linux"),
43+
new ArchiveDescriptor("1.26.1", "amd64", "darwin"),
44+
new ArchiveDescriptor(VERSION, "amd64", "linux"))
45+
.stream());
4646

47-
var latest = binaryDownloader.findLatestVersion();
47+
var latest = binaryDownloader.findLatestVersion();
4848

49-
assertThat(latest).isEqualTo(VERSION);
50-
}
49+
assertThat(latest).isEqualTo(VERSION);
50+
}
5151

5252
}

junit/kube-api-test/core/src/test/java/io/fabric8/kubeapitest/kubeconfig/KubeConfigTest.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,18 @@ class KubeConfigTest {
3333
KubeConfig kubeConfig = new KubeConfig(certManagerMock, null);
3434

3535
@Test
36-
void generatesConfigYaml() {
37-
when(certManagerMock.getAPIServerCertPath()).thenReturn(API_CERT_PATH);
38-
when(certManagerMock.getClientCertPath()).thenReturn(CLIENT_KEY_PATH);
39-
when(certManagerMock.getClientKeyPath()).thenReturn(CLIENT_CERT_PATH);
40-
41-
String yaml = kubeConfig.generateKubeConfigYaml(API_SERVER_PORT);
42-
43-
assertThat(yaml)
44-
.contains(""+API_SERVER_PORT)
45-
.contains(API_CERT_PATH)
46-
.contains(CLIENT_CERT_PATH)
47-
.contains(CLIENT_KEY_PATH);
48-
}
36+
void generatesConfigYaml() {
37+
when(certManagerMock.getAPIServerCertPath()).thenReturn(API_CERT_PATH);
38+
when(certManagerMock.getClientCertPath()).thenReturn(CLIENT_KEY_PATH);
39+
when(certManagerMock.getClientKeyPath()).thenReturn(CLIENT_CERT_PATH);
40+
41+
String yaml = kubeConfig.generateKubeConfigYaml(API_SERVER_PORT);
42+
43+
assertThat(yaml)
44+
.contains("" + API_SERVER_PORT)
45+
.contains(API_CERT_PATH)
46+
.contains(CLIENT_CERT_PATH)
47+
.contains(CLIENT_KEY_PATH);
48+
}
4949

5050
}

0 commit comments

Comments
 (0)