Skip to content

Commit

Permalink
feat: Jackson module to handle Go (de)serialization nuances
Browse files Browse the repository at this point in the history
Signed-off-by: Marc Nuri <[email protected]>
  • Loading branch information
manusa committed Jul 18, 2024
1 parent 250638e commit f15c2cf
Show file tree
Hide file tree
Showing 20 changed files with 206 additions and 322 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

#### Bugs
* Fix #6038: Support for Gradle configuration cache
* Fix #6110: VolumeSource (and other file mode fields) in Octal are correctly interpreted

#### Improvements
* Fix #6008: removing the optional dependency on bouncy castle
* Fix #5264: Remove deprecated `Config.errorMessages` field
* Fix #6110: VolumeSource's Octal `defaultMode` notation in yaml not properly converted to json

#### Dependency Upgrade
* Fix #6052: Removed dependency on no longer maintained com.github.mifmif:generex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.fabric8.kubernetes.api.model.runtime.RawExtension;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.internal.KubernetesDeserializer;
import io.fabric8.kubernetes.model.jackson.GoCompatibilityModule;
import io.fabric8.kubernetes.model.jackson.UnmatchedFieldTypeModule;
import org.snakeyaml.engine.v2.api.Dump;
import org.snakeyaml.engine.v2.api.DumpSettings;
Expand Down Expand Up @@ -89,7 +90,7 @@ public KubernetesSerialization(ObjectMapper mapper, boolean searchClassloaders)
}

protected void configureMapper(ObjectMapper mapper) {
mapper.registerModules(new JavaTimeModule(), unmatchedFieldTypeModule);
mapper.registerModules(new JavaTimeModule(), new GoCompatibilityModule(), unmatchedFieldTypeModule);
mapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.model.jackson.GoCompatibilityModule;
import io.fabric8.kubernetes.model.jackson.UnmatchedFieldTypeModule;

import java.io.InputStream;
Expand Down Expand Up @@ -88,7 +89,7 @@ public static ObjectMapper yamlMapper() {
if (YAML_MAPPER == null) {
YAML_MAPPER = new ObjectMapper(
new YAMLFactory().disable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID));
YAML_MAPPER.registerModules(UNMATCHED_FIELD_TYPE_MODULE);
YAML_MAPPER.registerModules(new GoCompatibilityModule(), UNMATCHED_FIELD_TYPE_MODULE);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ spec:
- name: application-code
configMap:
name: conf
defaultMode: 0555
defaultMode: 0o555
items:
- key: key1
path: target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,11 @@
*/
package io.fabric8.kubernetes.model.jackson;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.module.SimpleModule;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GoCompatibilityModule extends SimpleModule {

public class IntegerOctalHandlerUtil {
private static final Pattern OCTAL_NUMBER = Pattern.compile("(0[oO]?)[0-7]+");

private IntegerOctalHandlerUtil() {
}

public static Integer createIntegerValue(JsonNode node) {
String textValue = node.textValue();
if (textValue != null) {
Matcher octalNumberMatcher = OCTAL_NUMBER.matcher(textValue);
if (octalNumberMatcher.matches()) {
return Integer.valueOf(textValue.substring(octalNumberMatcher.group(1).length()), 8);
}
}
return node.intValue();
public GoCompatibilityModule() {
addDeserializer(Integer.class, new GoIntegerDeserializer());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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.kubernetes.model.jackson;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GoIntegerDeserializer extends StdDeserializer<Integer> implements ContextualDeserializer {

private static final Pattern OCTAL = Pattern.compile("(0[oO]?)([0-7]+)");
private static final GoIntegerDeserializer APPLICABLE_INSTANCE = new GoIntegerDeserializer(true);
private static final Set<String> APPLICABLE_FIELDS = new HashSet<>(Arrays.asList("mode", "defaultMode"));

private final boolean applicable;

protected GoIntegerDeserializer() {
this(false);
}

private GoIntegerDeserializer(boolean applicable) {
super(Integer.class);
this.applicable = applicable;
}

@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final String value = p.readValueAs(String.class);
if (value == null) {
return null;
}
if (applicable) {
final Matcher matcher = OCTAL.matcher(value);
if (matcher.find()) {
return Integer.valueOf(matcher.group(2), 8);
}
}
return _parseInteger(ctxt, value);
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
if (APPLICABLE_FIELDS.contains(property.getName())) {
return APPLICABLE_INSTANCE;
}
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.kubernetes.model.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.Data;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;

class GoIntegerDeserializerTest {

private ObjectMapper context;

@BeforeEach
void setUp() {
context = new ObjectMapper();
context.registerModule(new GoCompatibilityModule());
}

@Test
void defaultConstructorDoesntParseOctals() throws Exception {
final Integer result = new GoIntegerDeserializer()
.deserialize(context.createParser("\"0555\""), context.getDeserializationContext());
assertThat(result).isEqualTo(555);
}

@Nested
@TestInstance(PER_CLASS)
class Applicable {

@ParameterizedTest(name = "{index}: with '{'\"{0}\": {1}'}' parses as {2}")
@MethodSource
void parsesOctals(String fieldName, String content, Integer expected) throws Exception {
final IntegerFieldsContainer result = context
.readValue(String.format("{\"%s\": %s}", fieldName, content), IntegerFieldsContainer.class);
assertThat(result).hasFieldOrPropertyWithValue(fieldName, expected);
}

private Stream<Arguments> parsesOctals() {
return Stream.of("mode", "defaultMode")
.flatMap(field -> Stream.of(
Arguments.of(field, "null", null),
Arguments.of(field, "\"0555\"", 365),
Arguments.of(field, "\"0o555\"", 365),
Arguments.of(field, "\"0O555\"", 365),
Arguments.of(field, "\"555\"", 555),
Arguments.of(field, "\"0888\"", 888),
Arguments.of(field, "\"0o12\"", 10),
Arguments.of(field, "\"0O12\"", 10)));
}
}

//
@Nested
class NotApplicable {

@Test
void parsesOctalsAsDecimal() throws Exception {
final IntegerFieldsContainer result = context
.readValue("{\"notApplicable\": \"0555\"}", IntegerFieldsContainer.class);
assertThat(result).hasFieldOrPropertyWithValue("notApplicable", 555);
}

@Test
void throwsExceptionForInvalidOctal() {
assertThatThrownBy(() -> context.readValue("{\"mode\": \"0o955\"}", IntegerFieldsContainer.class))
.isInstanceOf(InvalidFormatException.class);
}

@Test
void throwsExceptionForOctalWithSeparator() {
assertThatThrownBy(() -> context.readValue("{\"notApplicable\": \"0o555\"}", IntegerFieldsContainer.class))
.isInstanceOf(InvalidFormatException.class);
}
}

@Data
private static final class IntegerFieldsContainer {
private Integer mode;
private Integer defaultMode;
private Integer notApplicable;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,6 @@ func main() {
Serializer: "io.fabric8.kubernetes.api.model.MicroTimeSerDes.Serializer.class",
Deserializer: "io.fabric8.kubernetes.api.model.MicroTimeSerDes.Deserializer.class",
},
"kubernetes_core_ConfigMapVolumeSource": &schemagen.JavaSerDeDescriptor{
Deserializer: "io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceDeserializer.class",
},
"kubernetes_core_SecretVolumeSource": &schemagen.JavaSerDeDescriptor{
Deserializer: "io.fabric8.kubernetes.api.model.SecretVolumeSourceDeserializer.class",
},
"kubernetes_core_DownwardAPIVolumeSource": &schemagen.JavaSerDeDescriptor{
Deserializer: "io.fabric8.kubernetes.api.model.DownwardAPIVolumeSourceDeserializer.class",
},
"kubernetes_core_ProjectedVolumeSource": &schemagen.JavaSerDeDescriptor{
Deserializer: "io.fabric8.kubernetes.api.model.ProjectedVolumeSourceDeserializer.class",
},
}

for definitionKey, descriptor := range serdes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.ToString;
import lombok.experimental.Accessors;

@JsonDeserialize(using = io.fabric8.kubernetes.api.model.ConfigMapVolumeSourceDeserializer.class)
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"defaultMode",
Expand Down Expand Up @@ -52,7 +52,7 @@ public class ConfigMapVolumeSource implements Editable<ConfigMapVolumeSourceBuil

/**
* No args constructor for use in serialization
*
*
*/
public ConfigMapVolumeSource() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.ToString;
import lombok.experimental.Accessors;

@JsonDeserialize(using = io.fabric8.kubernetes.api.model.DownwardAPIVolumeSourceDeserializer.class)
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"defaultMode",
Expand All @@ -46,7 +46,7 @@ public class DownwardAPIVolumeSource implements Editable<DownwardAPIVolumeSource

/**
* No args constructor for use in serialization
*
*
*/
public DownwardAPIVolumeSource() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.ToString;
import lombok.experimental.Accessors;

@JsonDeserialize(using = io.fabric8.kubernetes.api.model.ProjectedVolumeSourceDeserializer.class)
@JsonDeserialize(using = com.fasterxml.jackson.databind.JsonDeserializer.None.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"defaultMode",
Expand All @@ -46,7 +46,7 @@ public class ProjectedVolumeSource implements Editable<ProjectedVolumeSourceBuil

/**
* No args constructor for use in serialization
*
*
*/
public ProjectedVolumeSource() {
}
Expand Down
Loading

0 comments on commit f15c2cf

Please sign in to comment.