How to externalize configuration in Spring Boot using profiles? - spring

I have an application where I would like to change a datasource password that is stored in a application.yml file. The password in the YML file is stored such as this:
----
spring:
profiles: production
datasource:
password: prodpassword
Note: I also have profiles for development and stage.
The password prop is set on a class using ConfigurationProperties such as follows:
#Component
#ConfigurationProperties(prefix="datasource")
public class DataSourceConnector {
private password;
public void setPassword(String password) {
this.password = password;
}
Now, I try to override the prodpassword with prodpa$$word via a command line arg but it doesn't work:
java -Dspring.profiles.active=production -jar /usr/share/myapp/myapp-1.0.jar --datasource.password='prodpa$$word'
I also tried creating an identical (except the new password) application.yml file outside of the jar. That doesn't work either.
java -Dspring.profiles.active=production -jar /usr/share/myapp/myapp-1.0.jar --spring.config.location=/usr/share/myapp/
Note: I left out the file name in the location param due to this note from http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-profile-specific-properties:
If you have specified any files in spring.config.location, profile-specific variants of those files will not be considered. Use directories inspring.config.location if you also want to also use profile-specific properties.
How can I override datasource.password within the application.yml of the jar?
Edit:
The application is being started / stopped using supervisorctl.

After changing the config file that contains the java command, supervisorctl must reread the change:
supervisorctl reread
Next, activate the changes with:
supervisorctl update

Related

Spring Boot - Multi Document File based on profile for Tomcat 9.0.54

I have an application.properties file in Spring Boot v2.6.1 where I declared a multi document file notation like below :
spring.profiles.active=#spring.profiles.active#
#---
spring.config.activate.on-profile=prod
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
#---
spring.config.activate.on-profile=dev
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
This works perfectly fine(i.e., picked accordingly) when I run the application in integrated server or IDE by passing spring.profiles.active as prod/dev in VM Arguments.
The same thing doesn't work when I deploy as a war in tomcat by passing in setenv.sh as
export CATALINA_OPTS="$CATALINA_OPTS -Dspring.profiles.active=prod"
it always picks the "org.hibernate.dialect.MySQL8Dialect" instead of "org.hibernate.dialect.Oracle12cDialect"
Any help?
After a day of brain storming, finally I found the solution to pick the respective dialect when using multiple datasources based on the profile.
In my case, Primary datasource is Oracle with UCP and secondary is mySQL for Prod & dev profiles respectively.
As per the question, multi document file notation in application.properties works fine in IDE or integrated tomcat but not in External tomcat when deployed as a WAR.
Below solution works for both (Integrated & External Tomcat)
In MySQL Configuration class, I have set the custom JPA Properties as a tweak.
#Configuration
#Profile(someConstants.ENV_DEV)
public class MySqlConfiguration {
private static final Logger logger = LogManager.getLogger(MySqlConfiguration.class);
#Bean(name = "mySQL")
#Profile(someConstants.ENV_DEV)
#ConfigurationProperties(prefix = "spring.mysql.datasource")
public DataSource dataSource() {
final String METHOD_NAME = ":: DataSource ::";
logger.info(METHOD_NAME + "Initialising the MySQL Connection");
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.mysql.jpa")
public JpaProperties jpaProperties() {
JpaProperties properties = new JpaProperties();
return properties;
}
}
In Application.properties
spring.mysql.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect // this for mySQL
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect // this for Oracle
Moreover, I have done the same setting in setenv.sh file for external tomcat
// for prod
export CATALINA_OPTS="$CATALINA_OPTS -Dspring.profiles.active=prod"
// for dev
#export CATALINA_OPTS="$CATALINA_OPTS -Dspring.profiles.active=dev"
I analysed the logs and now each datasources picks up the respective properties & dialect based on profile, perfectly fine and awesome.
Happy Coding..

Spring spring.profiles.include: doesn't read profile-related properties

There are several ways present to define "active profiles" for a Spring Boot application.
The default one is to pass it through a command line, like this:
java -Dspring.profiles.active=dev,local -jar myapp.jar
it works just fine (as expected): All three sets of profile-related properties will be loaded in proper order:
application.yaml
application-dev.yaml will override the previous one
application-local.yaml will override the previous one as well (these properties will have the most priority)
Based on the idea, that my "local" profile should always "use and overrides" properties from the "dev", let's "hardcode" such behavior.
Let's use the 'spring.profiles.include' feature for this. So, the following lines are added to the 'application-local.yaml':
spring.profiles:
include:
- dev
I expect, now I can pass the "local" profile only in the command line, and the "dev" profile will be applied automatically (with his properties, of course):
java -Dspring.profiles.active=local -jar myapp.jar
But ooop!*: properties from the 'application-dev.yaml' are ignored.
Why? Is it a bug? Is it a feature that forces me to list all profiles in a command line directly?
I'm sure that the behavior around profiles activation should be the same without any difference in how the active-profiles list was passed to Spring Boot framework.
The application:
#SpringBootApplication #EnableConfigurationProperties( MyProps::class )
class SpringApp4
#ConfigurationProperties("my.db") #ConstructorBinding
data class MyProps(val name: String, val url: String, val user: String)
#Component
class MyRunner(val myProps: MyProps, val env: Environment) : CommandLineRunner {
override fun run(vararg args: String) {
println("myProps = $myProps")
println("activeProfiles = ${env.activeProfiles.joinToString()}")
exitProcess(0)
}
}
fun main() { runApplication<SpringApp4>() }
application.yaml:
my.db:
name: "default-name"
url: "default-url"
user: "default-user"
application-dev.yaml:
my.db:
url: "dev-url"
user: "dev-user"
application-local.yaml:
spring.profiles.include:
- dev
my.db:
user: "local-user"
Run1: java -Dspring.profiles.active=dev,local -jar myapp.jar
Correct output:
myProps = MyProps(name=default-name, url=dev-url, user=local-user)
activeProfiles = dev, local
it's correct because the url=dev-url
Run2: java -Dspring.profiles.active=local -jar myapp.jar
Incorrect output:
myProps = MyProps(name=default-name, url=default-url, user=local-user)
activeProfiles = local
It's not correct because the url=default-url and the activeProfiles doesn't contain the "dev" at all.
Help me please to figure out how to use the spring.profiles.include feature in yaml to build a kind of top level profiles that will activate other automatically.
In Run2 You are giving profile as local
i.e
-Dspring.profiles.active=local
So spring will first load application.yml and then application-local.yml
I can see the output is expected.
Since some properties like name and url are not present in application-local.yml, so the values of these fields will be same as present in application.yml
FYI : application.yml is always called irrespective of profile, and then it gets overridden by the profile mentioned in -Dspring.profiles.active property
spring.profiles.include deprecated in Spring Boot 2.4 and no longer works: https://spring.io/blog/2020/08/14/config-file-processing-in-spring-boot-2-4
It caused recursive resource loading; that broke Kubernates ConfigMap so they removed recursion.
Use spring.profiles.active or spring.profiles.group.

override command line Spring properties with bootstrap.yml

i have a tomcat box , which has spring active profile set already, as a command line argument every time a spring app is deployed through catalina.
i am using spring cloud config server , so in config client i specify active profile in bootstrap.yml , but as i mention earlier it is overriden by tomcat command line argument .
how to override the command line argument passed through tomcat , with my boostrap.yml at the time of bootstrap context loading so that i can pass active profile from my bootstrap.yml to config server.
Tomcat set environment command (which i cannot change as i dont have access)
JAVA_OPTS="$JAVA_OPTS -Djava.library.path=/path -Dspring.profiles.active=e2"
bootstrap.yml
spring:
profiles:
active: e2,cron
cloud:
config:
uri: http://localhost:8888
application:
name: heartbeat_monitor.
Command line argument(-Dspring.profiles.active=e2) will always override your properties file, no matter how many hardcoded profiles you specify in your yaml file. I would suggest you to add additional profile to be set programatically at the runtime and keep two property files with -profilename before the .yml extension.
This could be done as follows:
ApplicationMain.java
public static void main(String[] args) {
SpringApplication app = new SpringApplication(DemoApplication.class);
app.setAdditionalProfiles("cron");
app.run(args);
}
bootstrap-e2.yml
// Keep all the properties which is specific to e2 profile.
bootstrap-cron.yml
// Keep all the properties which is specific to cron profile.
In this way, you can keep both the profiles in use .But, if a property is common in both the bootstrap files, then program will pick the property from that bootstrap file whose profile matches with the runtime args : -Dspring.profiles.active

Spring ignores `spring.cloud.config.discovery` set as default properties

I'm trying to create Spring Application without referring to any external files. This is supposed to be a module that you'd then include as a dependency, configure and use to plug in the service into an existing ecosystem. This is how I'm doing that:
Map<String, Object> properties = new HashMap<>();
properties.put("server.address", "0.0.0.0")
properties.put("server.port", 8080)
properties.put("spring.profiles.active", "cloud")
properties.put("spring.application.name", "someApp")
properties.put("spring.cloud.config.failFast", true)
properties.put("spring.cloud.config.discovery.enabled", true)
properties.put("spring.cloud.config.discovery.serviceId", "config")
properties.put("eureka.instance.preferIpAddress", true)
properties.put("eureka.instance.statusPageUrlPath", "/health")
new SpringApplicationBuilder()
.bannerMode(Banner.Mode.OFF)
.properties(properties)
.sources(SpringConfiguration.class)
.web(false)
.registerShutdownHook(true)
.build()
I then go on to provide Eureka default zone in the run command, via environmental variables:
--env eureka_client_serviceUrl_defaultZone='http://some-host:8765/eureka/' --env SPRING_CLOUD_CONFIG_LABEL='dev' --env SPRING_CLOUD_INETUTILS_PREFERRED_NETWORKS='10.0'
Application registers successfully in Eureka, but unfortunately it tries to fetch the config prior to that and it's looking for it under the default URL (http://localhost:8888) instead of fetching config server IP from the registry. And yes, it does work if I put all of those properties in the bootstrap.yml file. Can I somehow make it work without using file-resources?
You are passing the properties using SpringApplicationBuilder which is responsible for SpringApplication and ApplicationContext instances.
From the documentation , the properties provided here will be part of ApplicationContext NOT the BootstrapContext. ApplicationContext is the child of BootstrapContext.
You can read more about the Bootstrap Context here -
http://cloud.spring.io/spring-cloud-commons/1.3.x/single/spring-cloud-commons.html#_the_bootstrap_application_context
Bootstrap.yml/properties is used to configure your Bootstrap Context.
You can look at these properties to change the name or location of the file -
spring.cloud.bootstrap.name - bootstrap(default)
spring.cloud.bootstrap.location
You will have to use a file resource(yml or properties).

#ConfigurationProperties not pulling the external properties file

I've created a personal repository on Git where I have kept my application.properties file.
I've created a cloud config server ('my-config-server') and used the git repository url.
I have bound my spring-boot application that is supposed to access the external properties file with Git repository.
#javax.jws.WebService(
serviceName = "myService",
portName = "my_service",
targetNamespace = "urn://vdc.com/xmlmessaging/SD",
wsdlLocation = "classpath:myService.wsdl",
endpointInterface = "com.my.service.SDType")
#PropertySource("application.properties")
#ConfigurationProperties
public class SDTypeImpl implements SDType {
/*It has various services implementation that use following method**/
private SDObj getObj (BigDecimal value) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
SDTypeImpl.class);
SDObj obj = context.getBean(SDPropertiesUtil.class).getObj(value);
context.close();
return obj;
}
}
Another Class:
public class SDPropertiesUtil {
#Autowired
public Environment env;
public SDObj getObj(BigDecimal value) {
String valueStr = env.getProperty(value.toString());
/*do logic*/
}
My application starts but fails to load properties file from my git repository.
I believe I should have an application.properties at src/main/resources in my application but since I'm using
#PropertySource("application.properties")
#ConfigurationProperties
I'm telling my application to use the application.properties from an external location and do not use internal properties file. But this is not happening. My application is still using the internal properties file.
The source you included doesn't show your app configuration settings to connect to the Config server. Do you mind sharing it?
This is how the config server could be queried from a client app:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
Let's say a Config server points to a Git repo which includes this file: demo-config-client-development.properties
You should be able to query the Config Server as:
curl http://localhost:8101/demo-config-client-development.properties
Assuming Config Server is running in locally and listening on 8181.
Let's also say you have a client app named: demo-config-client that connects to the Config server and runs using the development Spring profile, this app would now be able to read remote properties hosted in a Git repo through a Config server.
A detailed tutorial could be found at my blog at: http://tech.asimio.net/2016/12/09/Centralized-and-Versioned-Configuration-using-Spring-Cloud-Config-Server-and-Git.html

Resources