Skip to content

Commit 250638e

Browse files
committed
fix (kubernetes-model-generator) : Handle deserialization of defaultMode field in custom deserializers for VolumeSource types
Add custom deserializers to handle correct octal deserialization for these types: - ConfigMapVolumeSource - SecretVolumeSource - DownwardAPIVolumeSource - ProjectedVolumeSource Signed-off-by: Rohan Kumar <[email protected]>
1 parent 202b625 commit 250638e

File tree

24 files changed

+662
-12
lines changed

24 files changed

+662
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#### Improvements
99
* Fix #6008: removing the optional dependency on bouncy castle
1010
* Fix #5264: Remove deprecated `Config.errorMessages` field
11+
* Fix #6110: VolumeSource's Octal `defaultMode` notation in yaml not properly converted to json
1112

1213
#### Dependency Upgrade
1314
* Fix #6052: Removed dependency on no longer maintained com.github.mifmif:generex

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/utils/SerializationTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,28 @@
2727
import io.fabric8.kubernetes.api.model.AnyType;
2828
import io.fabric8.kubernetes.api.model.Config;
2929
import io.fabric8.kubernetes.api.model.ConfigMap;
30+
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource;
3031
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
32+
import io.fabric8.kubernetes.api.model.KeyToPath;
3133
import io.fabric8.kubernetes.api.model.KubernetesList;
3234
import io.fabric8.kubernetes.api.model.KubernetesResource;
3335
import io.fabric8.kubernetes.api.model.Namespace;
3436
import io.fabric8.kubernetes.api.model.ObjectMeta;
3537
import io.fabric8.kubernetes.api.model.Pod;
3638
import io.fabric8.kubernetes.api.model.PodBuilder;
3739
import io.fabric8.kubernetes.api.model.PodSpec;
40+
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
3841
import io.fabric8.kubernetes.api.model.Quantity;
3942
import io.fabric8.kubernetes.api.model.Service;
4043
import io.fabric8.kubernetes.api.model.Toleration;
44+
import io.fabric8.kubernetes.api.model.Volume;
4145
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition;
4246
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.JSONSchemaProps;
4347
import io.fabric8.kubernetes.api.model.apps.Deployment;
48+
import io.fabric8.kubernetes.api.model.batch.v1.CronJob;
49+
import io.fabric8.kubernetes.api.model.batch.v1.CronJobSpec;
50+
import io.fabric8.kubernetes.api.model.batch.v1.JobSpec;
51+
import io.fabric8.kubernetes.api.model.batch.v1.JobTemplateSpec;
4452
import io.fabric8.kubernetes.api.model.coordination.v1.Lease;
4553
import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpec;
4654
import io.fabric8.kubernetes.api.model.runtime.RawExtension;
@@ -310,6 +318,33 @@ void unmarshalWithValidListShouldReturnKubernetesList() {
310318
new Tuple(Pod.class, "v1", "Pod", "a-pod"));
311319
}
312320

321+
@Test
322+
@DisplayName("unmarshal, when integer value has octal literal value, then octal literal value correctly parsed")
323+
void unmarshal_whenIntegerValueHasOctalLiteralValue_thenCorrectlyDeserializeInteger() {
324+
// When
325+
final CronJob cronJob = Serialization.unmarshal(getClass().getResourceAsStream("/serialization/cronjob-octal.yml"),
326+
CronJob.class);
327+
// Then
328+
assertThat(cronJob)
329+
.extracting(CronJob::getSpec)
330+
.extracting(CronJobSpec::getJobTemplate)
331+
.extracting(JobTemplateSpec::getSpec)
332+
.extracting(JobSpec::getTemplate)
333+
.extracting(PodTemplateSpec::getSpec)
334+
.extracting(PodSpec::getVolumes)
335+
.asInstanceOf(InstanceOfAssertFactories.list(Volume.class))
336+
.singleElement()
337+
.extracting(Volume::getConfigMap)
338+
.hasFieldOrPropertyWithValue("defaultMode", Integer.valueOf("0555", 8))
339+
.hasFieldOrPropertyWithValue("name", "conf")
340+
.extracting(ConfigMapVolumeSource::getItems)
341+
.asInstanceOf(InstanceOfAssertFactories.list(KeyToPath.class))
342+
.singleElement()
343+
.hasFieldOrPropertyWithValue("key", "key1")
344+
.hasFieldOrPropertyWithValue("path", "target")
345+
.hasFieldOrPropertyWithValue("mode", Integer.valueOf("0555", 8));
346+
}
347+
313348
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
314349
@JsonSubTypes(@JsonSubTypes.Type(value = Typed.class, name = "x"))
315350
public interface Typeable {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
17+
apiVersion: batch/v1beta1
18+
kind: CronJob
19+
metadata:
20+
name: update-db
21+
spec:
22+
schedule: "*/1 * * * *"
23+
jobTemplate:
24+
spec:
25+
template:
26+
spec:
27+
containers:
28+
- name: update-fingerprints
29+
image: python:3.6.2-slim
30+
command: ["/bin/bash"]
31+
args: ["-c", "python /client/test.py"]
32+
volumeMounts:
33+
- name: application-code
34+
mountPath: /where/ever
35+
restartPolicy: OnFailure
36+
volumes:
37+
- name: application-code
38+
configMap:
39+
name: conf
40+
defaultMode: 0555
41+
items:
42+
- key: key1
43+
path: target
44+
mode: 0555
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.kubernetes.model.jackson;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
public class IntegerOctalHandlerUtil {
24+
private static final Pattern OCTAL_NUMBER = Pattern.compile("(0[oO]?)[0-7]+");
25+
26+
private IntegerOctalHandlerUtil() {
27+
}
28+
29+
public static Integer createIntegerValue(JsonNode node) {
30+
String textValue = node.textValue();
31+
if (textValue != null) {
32+
Matcher octalNumberMatcher = OCTAL_NUMBER.matcher(textValue);
33+
if (octalNumberMatcher.matches()) {
34+
return Integer.valueOf(textValue.substring(octalNumberMatcher.group(1).length()), 8);
35+
}
36+
}
37+
return node.intValue();
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.kubernetes.model.jackson;
17+
18+
import com.fasterxml.jackson.core.JsonProcessingException;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.Arguments;
22+
import org.junit.jupiter.params.provider.MethodSource;
23+
24+
import java.util.stream.Stream;
25+
26+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
27+
28+
class IntegerOctalHandlerUtilTest {
29+
private final ObjectMapper objectMapper = new ObjectMapper();
30+
31+
private static Stream<Arguments> shouldParseOctalNumbersCorrectly() {
32+
return Stream.of(
33+
Arguments.of("\"012\"", Integer.valueOf("012", 8)),
34+
Arguments.of("\"0o12\"", Integer.valueOf("012", 8)),
35+
Arguments.of("\"0O12\"", Integer.valueOf("012", 8)));
36+
}
37+
38+
@ParameterizedTest
39+
@MethodSource
40+
void shouldParseOctalNumbersCorrectly(String input, Integer expectedValue) throws JsonProcessingException {
41+
assertThat(IntegerOctalHandlerUtil.createIntegerValue(objectMapper.readTree(input)))
42+
.isEqualTo(expectedValue);
43+
}
44+
}

kubernetes-model-generator/kubernetes-model-core/cmd/generate/generate.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ func main() {
152152
Serializer: "io.fabric8.kubernetes.api.model.MicroTimeSerDes.Serializer.class",
153153
Deserializer: "io.fabric8.kubernetes.api.model.MicroTimeSerDes.Deserializer.class",
154154
},
155+
"kubernetes_core_ConfigMapVolumeSource": &schemagen.JavaSerDeDescriptor{
156+
Deserializer: "io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceDeserializer.class",
157+
},
158+
"kubernetes_core_SecretVolumeSource": &schemagen.JavaSerDeDescriptor{
159+
Deserializer: "io.fabric8.kubernetes.api.model.SecretVolumeSourceDeserializer.class",
160+
},
161+
"kubernetes_core_DownwardAPIVolumeSource": &schemagen.JavaSerDeDescriptor{
162+
Deserializer: "io.fabric8.kubernetes.api.model.DownwardAPIVolumeSourceDeserializer.class",
163+
},
164+
"kubernetes_core_ProjectedVolumeSource": &schemagen.JavaSerDeDescriptor{
165+
Deserializer: "io.fabric8.kubernetes.api.model.ProjectedVolumeSourceDeserializer.class",
166+
},
155167
}
156168

157169
for definitionKey, descriptor := range serdes {

kubernetes-model-generator/kubernetes-model-core/src/generated/java/io/fabric8/kubernetes/api/model/ConfigMapVolumeSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import lombok.ToString;
2020
import lombok.experimental.Accessors;
2121

22-
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
22+
@JsonDeserialize(using = io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceDeserializer.class)
2323
@JsonInclude(JsonInclude.Include.NON_NULL)
2424
@JsonPropertyOrder({
2525
"defaultMode",

kubernetes-model-generator/kubernetes-model-core/src/generated/java/io/fabric8/kubernetes/api/model/DownwardAPIVolumeSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import lombok.ToString;
2020
import lombok.experimental.Accessors;
2121

22-
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
22+
@JsonDeserialize(using = io.fabric8.kubernetes.api.model.DownwardAPIVolumeSourceDeserializer.class)
2323
@JsonInclude(JsonInclude.Include.NON_NULL)
2424
@JsonPropertyOrder({
2525
"defaultMode",

kubernetes-model-generator/kubernetes-model-core/src/generated/java/io/fabric8/kubernetes/api/model/ProjectedVolumeSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import lombok.ToString;
2020
import lombok.experimental.Accessors;
2121

22-
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
22+
@JsonDeserialize(using = io.fabric8.kubernetes.api.model.ProjectedVolumeSourceDeserializer.class)
2323
@JsonInclude(JsonInclude.Include.NON_NULL)
2424
@JsonPropertyOrder({
2525
"defaultMode",

kubernetes-model-generator/kubernetes-model-core/src/generated/java/io/fabric8/kubernetes/api/model/SecretVolumeSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import lombok.ToString;
2020
import lombok.experimental.Accessors;
2121

22-
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
22+
@JsonDeserialize(using = io.fabric8.kubernetes.api.model.SecretVolumeSourceDeserializer.class)
2323
@JsonInclude(JsonInclude.Include.NON_NULL)
2424
@JsonPropertyOrder({
2525
"defaultMode",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.kubernetes.api.model;
17+
18+
import com.fasterxml.jackson.core.JsonParser;
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.DeserializationContext;
21+
import com.fasterxml.jackson.databind.JsonDeserializer;
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
24+
import java.io.IOException;
25+
26+
import static io.fabric8.kubernetes.model.jackson.IntegerOctalHandlerUtil.createIntegerValue;
27+
28+
public class ConfigMapVolumeSourceDeserializer extends JsonDeserializer<ConfigMapVolumeSource> {
29+
30+
@Override
31+
public ConfigMapVolumeSource deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
32+
throws IOException {
33+
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
34+
ConfigMapVolumeSourceBuilder builder = new ConfigMapVolumeSourceBuilder();
35+
if (node.get("items") != null) {
36+
for (final JsonNode keyToPathNode : node.get("items")) {
37+
builder.addToItems(createKeyToPath(keyToPathNode, jsonParser));
38+
}
39+
}
40+
if (node.get("name") != null) {
41+
builder.withName(node.get("name").textValue());
42+
}
43+
if (node.get("optional") != null) {
44+
builder.withOptional(node.get("optional").booleanValue());
45+
}
46+
if (node.get("defaultMode") != null) {
47+
builder.withDefaultMode(createIntegerValue(node.get("defaultMode")));
48+
}
49+
return builder.build();
50+
}
51+
52+
private KeyToPath createKeyToPath(JsonNode node, JsonParser jsonParser) throws JsonProcessingException {
53+
KeyToPath keyToPath = jsonParser.getCodec().treeToValue(node, KeyToPath.class);
54+
KeyToPathBuilder keyToPathBuilder = new KeyToPathBuilder(keyToPath);
55+
if (node.get("mode") != null) {
56+
keyToPathBuilder.withMode(createIntegerValue(node.get("mode")));
57+
}
58+
return keyToPathBuilder.build();
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.kubernetes.api.model;
17+
18+
import com.fasterxml.jackson.core.JsonParser;
19+
import com.fasterxml.jackson.core.JsonProcessingException;
20+
import com.fasterxml.jackson.databind.DeserializationContext;
21+
import com.fasterxml.jackson.databind.JsonDeserializer;
22+
import com.fasterxml.jackson.databind.JsonNode;
23+
24+
import java.io.IOException;
25+
26+
import static io.fabric8.kubernetes.model.jackson.IntegerOctalHandlerUtil.createIntegerValue;
27+
28+
public class DownwardAPIVolumeSourceDeserializer extends JsonDeserializer<DownwardAPIVolumeSource> {
29+
30+
@Override
31+
public DownwardAPIVolumeSource deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
32+
throws IOException {
33+
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
34+
DownwardAPIVolumeSourceBuilder builder = new DownwardAPIVolumeSourceBuilder();
35+
if (node.get("items") != null) {
36+
for (final JsonNode keyToPathNode : node.get("items")) {
37+
builder.addToItems(createDownwardAPIVolumeFile(keyToPathNode, jsonParser));
38+
}
39+
}
40+
if (node.get("defaultMode") != null) {
41+
builder.withDefaultMode(createIntegerValue(node.get("defaultMode")));
42+
}
43+
return builder.build();
44+
}
45+
46+
private DownwardAPIVolumeFile createDownwardAPIVolumeFile(JsonNode node, JsonParser jsonParser)
47+
throws JsonProcessingException {
48+
DownwardAPIVolumeFile downwardAPIVolumeFile = jsonParser.getCodec().treeToValue(node, DownwardAPIVolumeFile.class);
49+
DownwardAPIVolumeFileBuilder downwardAPIVolumeFileBuilder = new DownwardAPIVolumeFileBuilder(downwardAPIVolumeFile);
50+
if (node.get("mode") != null) {
51+
downwardAPIVolumeFileBuilder.withMode(createIntegerValue(node.get("mode")));
52+
}
53+
return downwardAPIVolumeFileBuilder.build();
54+
}
55+
}

0 commit comments

Comments
 (0)