spring-bootkubernetesconfigmapconfigurationproperties

consuming a map from configmap to springboot application


I have added a map in my values.yaml file like below

defaultview:
  journey:
    ce5f164c-ae0f-11e7-9220-c7137b83fb6a: 45
    abc: -1
    pqr: 30

I read this property in configmap like below

defaultview.journey: {{ .Values.defaultview.journey }}

When I check configmap I see entry like below in cm

defaultview.journey:
----
map[abc:-1 ce5f164c-ae0f-11e7-9220-c7137b83fb6a:45 pqr:30]

Java class is trying to bind this property into a Map<String,Integer> like below

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Data
@Component
@ConfigurationProperties(prefix = "defaultview")
public class JourneyViewConfig {

    private Map<String, Integer> Journey = new HashMap<>();

}

I see error thrown while spring boot startup

Failed to bind properties under 'defaultview.journey' to java.util.Map<java.lang.String, java.lang.Integer>:\\n\\n    Property: defaultview.journey\\n    Value: \\\"map[abc:-1 ce5f164c-ae0f-11e7-9220-c7137b83fb6a:45 pqr:30]\\\"\\n    Origin: \\\"defaultview.journey\\\" from property source \\\"<cm name>\\\"\\n    Reason: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [java.util.Map<java.lang.String, java.lang.Integer>

Is there a way to inject Map directly from configmap to spring boot?

Edit 1: I am able to read the values in map by adding this dirty code

private Map<String, Integer> parseConfigValue(String configValue) {
    Map<String, Integer> resultMap = new HashMap<>();

    String trimmedInput = configValue.trim();
    if (trimmedInput.startsWith("map[")) {
        trimmedInput = trimmedInput.substring(4, trimmedInput.length() - 1);
        String[] pairs = trimmedInput.split(" ");

        for (String pair : pairs) {
            String[] keyValue = pair.split(":");
            if (keyValue.length == 2) {
                String key = keyValue[0].trim();
                Integer value = Integer.parseInt(keyValue[1].trim());
                resultMap.put(key, value);
            }
        }

Is there a more subtle way of doing this?


Solution

  • This line is problematic:

    defaultview.journey: {{ .Values.defaultview.journey }}
    

    You're trying to include a structured variable as a simple string, so this is resulting in a ConfigMap that looks like:

    data:
      defaultview.journey_broken: map[abc:-1 ce5f164c-ae0f-11e7-9220-c7137b83fb6a:45 pqr:30]
    

    That's not useful, as it's neither valid YAML nor JSON and probably isn't what your application is expecting.

    You're going to need to use the toYaml function to render your structured variable as a block of YAML text. For example:

    data:
      defaultview.journey: |
    {{ indent 4 (.Values.defaultview.journey | toYaml) }}
    

    Which results in:

    data:
      defaultview.journey: |
        abc: -1
        ce5f164c-ae0f-11e7-9220-c7137b83fb6a: 45
        pqr: 30