I started with a comment on a thread in Cloud Config, but this is more likely a Boot edge case. My original comments under Cloud Config can be found here https://github.com/spring-cloud/spring-cloud-config/issues/1838#issuecomment-1430434353.
We started running into this recently when we finally started upgrading Spring Boot, and I believe the issue is slightly more involved than described. We are running on Spring Boot 2.7.4 and Cloud Config Server 2022.0.1 and we are experiencing different results depending on where we run the application from.
Basic setup: Application files, including application.yml are under /app. Our simple test for variable precedence is to set spring.main.banner-mode to "off" in application.yml and to "console" in cloud config
Example 1:
- Working directory is /app
- application.yml contains spring.config.import=configserver:
- We run java -cp . Application
Result: precedence is broken. The spring banner does not display.
Example 2:
- Working directory is /
- application.yml contains spring.config.import=configserver:
- We run java -cp /app Application
Result: precedence is correct. The spring banner displays.
Example 3:
- Working directory is /app
- application.yml DOES NOT contain spring.config.import=configserver:
- We run java -Dspring.config.import=configserver: -cp . Application
Result: precedence is correct. The spring banner displays.
Example 4:
- Working directory is /
- application.yml DOES NOT contain spring.config.import=configserver:
- We run java -Dspring.config.import=configserver: -cp /app Application
Result: precedence is correct. The spring banner displays.
As you can see, only Example 1 has incorrect precedence. Trace logging in ConfigDataEnvironment shows that the problem seems to stem from considering application.yml twice. Sanitized log output from Example 1 is below.
o.s.b.c.config.ConfigDataEnvironment : Adding initial config data import from location 'optional:file:./;optional:file:./config/;optional:file:./config/*/'
o.s.b.c.config.ConfigDataEnvironment : Adding initial config data import from location 'optional:classpath:/;optional:classpath:/config/'
o.s.b.c.config.ConfigDataEnvironment : Processing initial config data environment contributors without activation context
o.s.b.c.config.ConfigDataEnvironment : Creating config data activation context from initial contributions
o.s.b.c.config.ConfigDataEnvironment : Processing config data environment contributors with initial activation context
o.s.b.c.config.ConfigDataEnvironment : Deducing profiles from current config data environment contributors
o.s.b.c.config.ConfigDataEnvironment : Processing config data environment contributors with profile activation context
o.s.c.c.c.ConfigServerConfigDataLoader : Fetching config from server at : http://localhost:8888
o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=app, profiles=[local], label=null, version=null, state=null
o.s.c.c.c.ConfigServerConfigDataLoader : Fetching config from server at : http://localhost:8888
o.s.c.c.c.ConfigServerConfigDataLoader : Located environment: name=app, profiles=[local], label=null, version=null, state=null
o.s.b.c.config.ConfigDataEnvironment : Applying config data environment contributions
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:file:/data/application.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:file:/data/app/application.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:Config resource 'file [/data/application.yml]' via location '/data/' (document #1)'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:Config resource 'file [/data/application.yml]' via location '/data/' (document #0)'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configClient'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'Config resource 'file [application.yml]' via location 'optional:file:./''
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:file:/data/application.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:file:/data/app/application.yml'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:Config resource 'file [/data/application.yml]' via location '/data/' (document #1)'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configserver:Config resource 'file [/data/application.yml]' via location '/data/' (document #0)'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'configClient'
o.s.b.c.config.ConfigDataEnvironment : Adding imported property source 'Config resource 'class path resource [application.yml]' via location 'optional:classpath:/''
Comment From: scottfrederick
@jhitt25 Thanks for the report. There's quite a bit going on here, including an unusual way of running a Spring Boot application with java -cp and specific conditions on the location of class files and configuration files. It would be much easier for us to see what's going on if you could provide a complete minimal sample with as few dependencies as possible that reproduces the problem, including instructions for how to build the sample and how to lay out the running directory structure. You can share it with us by pushing it to a separate repository on GitHub or by zipping it and attaching it to this issue.
Comment From: jhitt25
It really isn't all that unusual. We're just running an exploded spring boot jar from docker. The 'java -cp' is just setting up the classpath properly since we're no longer running from the boot jar. I'll see what i can do about setting up a minimal example.
Comment From: jhitt25
I've created a working example project for this. It's located at https://github.com/jhitt25/boot-issue-34212 and mirrors the example i listed above.
Comment From: philwebb
I don't think we considered the possibility that a ConfigDataResource could refer to the same underlying file. I think what's happening here is that we load application.yml first as a classpath resource and then again as a file resource. The ConfigDataLoader checks to see if a resource has already been loaded, but I suspect StandardConfigDataResource.equals won't match even if the underling File is the same.
Comment From: philwebb
I think I've fixed this locally, hopefully I should be able to add a test and commit something before the end of the week.