Might be related to https://github.com/spring-cloud/spring-cloud-config/issues/1974, though I could not replicate that issue with the latest 3.1 RC.
Setup
- Spring Boot 2.6.0
- Spring Cloud Config 3.1 RC1
- Apache Maven 3.8.x
- OpenJDK 11
Overview
I have a multi-module Apache Maven project that is set up with the following modules:
- bootstrap: contains a
PropertySourceLocatorforBootstrapConfiguration, defined inspring.factoriesfile. - starter: depends on bootstrap, and it's a (servlet-based) web application
- reference: deploys the starter application using the Maven Cargo plugin, deploying into an Apache Tomcat 9.0.55
Runtime
- The starter module declares a configuration class, annotated with
@PropertySource("wa.properties"). Thiswa.propertieson the classpath of the starter module has a setting:cas.authn.syncope.name=Starter - The starter module has a
ServletInitializerthat sets thespring.config.nameproperty to "wa" when building the spring application. - The reference module only has a
wa-embedded.propertiesfile on the classpath with a setting:cas.authn.syncope.name=Embedded - The reference module starts with the spring activated profiles:
embedded,all
Note: the cas.authn.syncope.name is bound to a Java POJO, CasConfigurationProperties, that is annotated with @ConfigurationProperties("cas").
Observation
The following bean in the application exists, simplified for this post:
@Bean
@ConditionalOnMissingBean(name = "something")
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public Something something(ApplicationContext ctx, CasConfigurationProperties cas) {
...
}
- If I look at the contents of
cas.getAuthn().getSyncope().getName()), it shows: "Starter" - If I look at
ctx.getEnvironment().getProperty("cas.authn.syncope.name"), it shows "Embedded".
In other words, property binding used during the bootstrapping process does not match the actual environment for the application's context.
Analysis
-
It appears that when the bootstrap application context is created,
wa-embedded.properties, a profile-specific property is not read. In fact, the only property source that is used for binding iswa.propertiesas part of "localProperties", which I believe comes from@PropertySource("wa.properties"). Nothing else is read or found. -
Then, property binding takes place binding
CasConfigurationPropertiesandcas.authn.syncope.nameinitialized from@PropertySource("wa.properties"). The value of this property is set toStarter. -
Then, the application servlet context is initialized and its environment is post-processed with profiles and the appropriate listener and Spring beans are created. In particular, this bean:
@Bean
@ConditionalOnMissingBean(name = "something")
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public Something something(ApplicationContext ctx, CasConfigurationProperties cas) {
...
}
...shows that ctx is the actual application context with an environment that is post-processed via all profiles and shows ctx.getEnvironment().getProperty("cas.authn.syncope.name") as "Embedded".
However, CasConfigurationProperties was processed using the Bootstrap context only, and its equivalent property shows "Starter".
...which means the bean would be created using the wrong values in CasConfigurationProperties.
Research
This setup works OK using Spring Boot 2.5.6 and Spring Cloud 3.0.5. I don't think anything in Spring Boot has changed that would affect this, but I do see a number of differences in Cloud between 3.0 and 3.1.
Sample
Working on it. I am not sure I can create a reproducer to adequately showcase this, but I will try.
Comment From: ryanjbaxter
We will wait for your sample
Comment From: mmoayyed
Here is a reproducer: https://github.com/mmoayyed/cloud-config-reproducer
Overview
Please note that this is a stripped-down version of the Apache Syncope codebase: https://github.com/apache/syncope. I removed all the other components to make sure the project setup is as lean as it can be, though I suppose it's not quite ideal. That said, the setup should be very simple.
Once you clone the repository,
- From the root, run
mvn clean install -PskipTests - cd into fit/wa-reference and run
mvn -Pdebug
You can tail the logs via tail fit/wa-reference/target/log
The project setup matches the issue description. The project is deployed into Apache Tomcat 9.0.55 via the cargo plugin. Remote debugging is available via port 8001. The application is launched using the embedded and all profiles, and the spring.config.name is set to wa.
Test
The app should be reading wa.properties file, and wa-embedded.properties file. The latter should replace values and overwrite what is found in the former for property binding. That is not the case, and is the heart of this issue.
When it's up (Ignore exceptions related to Syncope registration during startup), you should be able to go to: http://localhost:9080/syncope-wa/
...and see this output:
{
configName: "DefaultSyncopeAuthModuleEmbedded",
handlerNames: "DefaultSyncopeAuthModuleWAStarter", // problem!
envName: "DefaultSyncopeAuthModuleEmbedded"
}
Components
The controller that outputs this data demonstrates the issue, and is as follows:
@RestController("cas")
public static class MyController {
@Autowired
private CasConfigurationProperties properties;
@Autowired
@Qualifier("syncopeAuthenticationHandlers")
private BeanContainer<AuthenticationHandler> syncopeAuthenticationHandlers;
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(produces = "application/json")
public Map value() {
var map = new LinkedHashMap<>();
map.put("configName", properties.getAuthn().getSyncope().getName());
map.put("handlerNames", syncopeAuthenticationHandlers.toList().stream().map(h -> h.getName()).collect(Collectors.joining()));
map.put("envName", applicationContext.getEnvironment().getProperty("cas.authn.syncope.name"));
return map;
}
}
The handlerNames field is fetched from the syncopeAuthenticationHandlers bean. This bean is initialized during the bootstrap process, with only the properties that do come from wa.properties. wa-embedded.properties which is a property file for a specific embedded profile is not used when syncopeAuthenticationHandlers bean is created.
The syncopeAuthenticationHandlers is part of the "cas-server-support-syncope-authentication" module. It's defined as such:
@ConditionalOnMissingBean(name = "syncopeAuthenticationHandlers")
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public BeanContainer<AuthenticationHandler> syncopeAuthenticationHandlers(
final CasConfigurationProperties casProperties, // configuration binding...
final ConfigurableApplicationContext applicationContext,
@Qualifier("syncopePrincipalFactory")
final PrincipalFactory syncopePrincipalFactory,
@Qualifier("syncopePasswordPolicyConfiguration")
final PasswordPolicyContext syncopePasswordPolicyConfiguration,
@Qualifier(ServicesManager.BEAN_NAME)
final ServicesManager servicesManager) {
val syncope = casProperties.getAuthn().getSyncope();
val handlers = Splitter.on(",").splitToList(syncope.getDomain())
.stream()
.map(domain -> {
// here, syncope.getName() does not point to the value in the property profile...
// This should come from wa-embedded.properties.
// Instead, it always comes from the wa.properties
val h = new SyncopeAuthenticationHandler(syncope.getName(), servicesManager,
syncopePrincipalFactory, syncope.getUrl(), domain.trim());
h.setState(syncope.getState());
h.setPasswordEncoder(PasswordEncoderUtils.newPasswordEncoder(syncope.getPasswordEncoder(), applicationContext));
h.setPasswordPolicyConfiguration(syncopePasswordPolicyConfiguration);
val predicate = CoreAuthenticationUtils.newCredentialSelectionPredicate(syncope.getCredentialCriteria());
h.setCredentialSelectionPredicate(predicate);
val transformer = PrincipalNameTransformerUtils.newPrincipalNameTransformer(syncope.getPrincipalTransformation());
h.setPrincipalNameTransformer(transformer);
return h;
})
.map(AuthenticationHandler.class::cast)
.collect(Collectors.toList());
return BeanContainer.of(handlers);
}
Expectation
The endpoint should always produce DefaultSyncopeAuthModuleEmbedded as the value for all 3 fields. This is the current behavior with Spring Boot 2.5.7 and Spring Cloud 3.0.5.
Comment From: mmoayyed
I see 3.1.0 is now released and will try to determine if I can reproduce the same issue with the latest release.
Comment From: ryanjbaxter
I'm guessing you will but let us know
Comment From: mmoayyed
Tried again with Cloud 3.1.0 and Spring Boot 2.6.1. Same issue.
Comment From: mmoayyed
Ran a few more experiments:
- If I rename the
wa-embedded.propertiesfile tobootstrap-embedded.properties, no difference. The file is not read/used for property binding.
I can confirm this using the same Controller:
@RestController("cas")
public static class MyController {
@Autowired
private CasConfigurationProperties properties;
@Autowired
@Qualifier("syncopeAuthenticationHandlers")
private BeanContainer<AuthenticationHandler> syncopeAuthenticationHandlers;
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(produces = "application/json")
public Map value() {
var map = new LinkedHashMap<>();
map.put("configName", properties.getAuthn().getSyncope().getName());
map.put("handlerNames", syncopeAuthenticationHandlers.toList().stream().map(h -> h.getName()).collect(Collectors.joining()));
map.put("envName", applicationContext.getEnvironment().getProperty("cas.authn.syncope.name"));
return map;
}
}
Same output as before.
- If I rename the
wa-embedded.propertiesfile tobootstrap.properties, then the output changes. This time I get:
{
configName: "DefaultSyncopeAuthModuleWAStarter",
handlerNames: "DefaultSyncopeAuthModuleEmbedded",
envName: "DefaultSyncopeAuthModuleWAStarter"
}
This time, handlerNames is initialized correctly using bootstrapping process and bootstrap.properties. However, the other two fields are wrong. Now, the application context's environment does not match the bootstrap properties in the environment.
Comment From: mmoayyed
If I change the configName to "wa" using spring.cloud.bootstrap.name=wa
Then I get:
And then the endpoint shows the following:
{
configName: "DefaultSyncopeAuthModuleEmbedded",
handlerNames: "DefaultSyncopeAuthModuleEmbedded",
envName: "DefaultSyncopeAuthModuleEmbedded"
}
...which is correct.
- Has anything changed in this area in 3.1.x?
- Should
StandardConfigDataLocationResolveralso considerspring.config.namewhen it's looking at configuration names? It appears to be only be aware ofspring.cloud.bootstrap.namewhich by default only looks at bootstrap.
I'll have to do some more testing with everything else, but it seems to be some sort of regression[?]
Comment From: mmoayyed
To clarify, the configName is picked up from the bootstrap listener:
and then it's used by the StandardConfigDataLocationResolver which is created here:
Comment From: cihadbaskoy
I have a similar problem with Spring Boot version 2.6.4 and Spring Cloud Config version 2021.0.1
I found a workaround as adding a configuration as:
spring:
profiles.active: dev
cloud:
config:
profile: ${spring.profiles.active}
In the ConfigServicePropertySourceLocator class, when trying to get the profiles, it reads them from properties and this properties only looks either spring.clod.config.profile OR spring.profiles, it does not check the active profiles.
This is why adding the above lines temporarily solves the problem
The issue is noticed when Spring Cloud Server returns the propertySources in correct order but client can only handle the order of properties without the profiles first.
Comment From: ryanjbaxter
If you could try Spring Cloud 2021.0.8-SNAPSHOT we have made some changes regarding profiles and activation.
See https://docs.spring.io/spring-cloud-commons/docs/3.1.7-SNAPSHOT/reference/html/#application-context-hierarchies
Comment From: spring-cloud-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-cloud-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.