Search code examples
spring-bootyamlspring-cloudspring-cloud-configspring-cloud-config-server

Spring boot 2.4.x cannot handle multi document yml files from config server


  1. Java version: 8
  2. Spring Boot version: 2.4.1
  3. Spring Cloud version: 2020.0.0, specifically I use a Spring Cloud Config Server connected to GIT and our services are Spring Cloud Config Clients.

I have migrated away from using bootstrap.yml and started using spring.config.import and spring.config.activate.on-profile as mentioned in the documentation here and here

My configuration in my service, who is a client to the config server looks like this:

server.port: 9001
spring:
  application.name: my-rest-service
  config.import: configserver:http://localhost:8888
  cloud.config.profile: ${spring.profiles.active}

My configuration in the config server looks like this:

application.yml (has two documents separated by the ---)

logging:
  file.name: <omitted>
  level:
    root: INFO
---
spring:
  config.activate.on-profile: dev
  logging.level.root: DEBUG

my-rest-sercive.yml (has two documents separated by the ---)

spring:
  datasource:
    driver-class-name: <omitted>
    username: <omitted>
    password: <omitted>
---
spring:
  config.activate.on-profile: dev
  datasource.url: <omitted>

Because there is a profile "dev" active, I successfully get the following 4 configurations from config server:

  • application.yml: general logging level
  • application.yml: specific logging for dev
  • my-rest-sercive.yml: general datasource properties
  • my-rest-sercive.yml: specific datasource url for dev

I can see these 4 sources successfully being fetched when I use my browser or when I debug or in the logs when I lower the loglevel to trace:

o.s.b.c.config.ConfigDataEnvironment     : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\my-rest-service.yml'
o.s.b.c.config.ConfigDataEnvironment     : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\my-rest-service.yml'
o.s.b.c.config.ConfigDataEnvironment     : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\application.yml'
o.s.b.c.config.ConfigDataEnvironment     : Adding imported property source 'configserver:https://git.company.com/path.git/file:C:\configservergit\config\application.yml'

However, notice that because I use multi document yml files, out of these 4 property sources only TWO unique names are used.

In a later step, when Spring creates the data source bean, he complains he cannot find the data source URL. If I debug the spring bean factory I can indeed see that out of the 4 property files returned by the config server, only two have remained (the ones that don't contain the dev profile specific configuration). I assume this is because they have an identical name and they overwrite each other. This is an effect of this piece of code in the MutablePropertySource.class:

public void addLast(PropertySource<?> propertySource) {
    synchronized(this.propertySourceList) {
        this.removeIfPresent(propertySource); <-- this is the culrprit!
        this.propertySourceList.add(propertySource);
    }
} 

This is a breaking change from Spring 2.3/Spring Cloud Hoxton where it correctly collected all properties. I think spring cloud needs to change the config server so that every document within a yml has has a unique name when returned to Spring. This is exactly how Spring Boot handles multi document yml files, by appending the String (documenyt #1) to the property source name

I found an interesting note about profiles and multi document yml, basically saying it is not supported, but this doesn't apply to my use case because my yml files are not profiles based (there is no -{profileName} in the last part of the file name).


Solution

  • This is a known issue with the new release. We can track the issue here on the spring cloud config server github page.

    The workaround seems to be stop using multi document yml files and use multiple distinct files with the profile name in the filename.