My Spring Boot app will have 4 different environments:
Local; running locally on my machine
Dev
UAT
Prod
When a new user registers for my app, the backend sends them an email to verify their address/account. In this email there will be a link; the user clicks the link which verifies them in the database and allows them to now login and use the app.
These links of course have to have the environment built into them:
Locally, the link might be http://localhost:9200/v1/data/accounts/verify?vt=12345
In dev the link might be: http://dev.myapp.example.com/v1/data/accounts/verify?vt=12345
In UAT the link might be: http://uat.myapp.example.com/v1/data/accounts/verify?vt=12345
In Prod the link might be: http://myapp.example.com/v1/data/accounts/verify?vt=12345
In all three environments, the hostname + port are different. Locally I specify localhost:9200 (both localhost + port). Non-locally I don't need to specify port because the app will be running on nodes behind a load balancer. In production I don't need an environment-specific subdomain like dev or uat.
In order for my emails to work correctly in all 4 environments, I need to set an environment variable that is then used by my email generator to generate links correctly.
I could use something homegrown, such as a custom property in application.properties like emailDomain, and set the property different in each environment's properties file.
But I feel like the smart folks that make up the Spring Boot community have probably already solved this problem for me...have they? Is there already a Spring Boot app property I can set that will take care of all this for me?
In the comments, I read that your main concern is being able to update the property without having to modify your .JAR/.WAR or changing some code.
This is possible since you can externalize your Spring boot configuration. If you check the Externalized Configuration docs, you can see that it looks for properties within:
OS environment variables.
...
Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants)
Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants)
Application properties outside of your packaged jar
(application.properties and YAML variants).
Application properties packaged inside your jar (application.properties and YAML variants).
So, ideally, you can put an application.properties file next to your JAR, and update the properties in that file depending on the environment you run on.
Related
I know Springboot applications can use application.properties or application.yaml files to retrieve variables like database connectivity setting, username, password etc.
However, and because of sensitive information, our development team has these only for test environment but not for production environments. These are not available to developers.
These are provided by security team and set up directly on server in server configuration files on Liberty server (these are server.xml files located in the server installation directory).
Developers have their own instance of Liberty server running where they have their own versions of server.xml files. If we could make Springboot read these files, then we could make it mimic production server environments and make transition easier instead of reading local application.properties files.
Is this possible?
If not, what would be a workaround?
First, usually developer/application doesn't need direct access to props like database connectivity setting, username, password because all that is configured in server in data source configuration, so application just needs JNDI name of the datasource to connect to it.
Second, if you use technology that cannot be configured in server, developers and security team should utilize environment variables for such props.
Liberty can read env variables, or also define them via server.env and then utilize in server.xml config for example:
<dataSource jndiName="jdbc/myDS">
...
<properties.db2.jcc serverName="${JDBC_HOST}" portNumber="${JDBC_PORT}" databaseName="${JDBC_DB}" user="${JDBC_USER}" password="${JDBC_PASSWORD}"/>
</dataSource>
Similarly you can use env vars in your springboot either configuring it in application.properties like this:
jdbc.user=${JDBC_USER}
or directly in code:
#Value("${JDBC_USER}")
private String jdbcUser;
Utilizing env variables has additional benefit that you can override them later if deploying in containers for example.
I will talk about alternative solution.
Firstly I do not understand how developer will access production server properties.
And for securing sensitive properties you could use property encryptor tool. Sensitive properties will be in encrypted format in application.properties and during server startup it will decrypt encrypted properties and use accordingly.
Here is a such library for property encrytion library
I am looking for a solution to the problem, which is that having an external Tomcat server with several different applications (war),
I would like to use application Spring Profile (dev, prod) to choose application.properties for a given profile.
To avoid keeping the database configuration in the git repository, I didn't store in the application
application.properties, but I kept them in the $catalina.base directory, where in common.loader
I indicated the path to this directory. This worked until I started deploying several different applications. Then each of them began to use
from the same datasource. One solution is to keep the application.properties in Jenkins and directly in Jobie indicating which one to use,
but I am not sure if this is a good solution.
Option 1: Create a separate instance of tomcat (using same tomcat binary) and place application.properties in instance's classpath. As classpath is separate for every instance, applications don't refer to same application.properties file. Refer to this article to see how to create multiple instances on same tomcat server.
Option 2: Create JNDI datasource on tomcat server and use it in your application to access database. This way, should you choose to, same datasource can be shared across different related applications and/or modules. For more information on how to create JNDI datasource in Tomcat, refer to this link
I have a setup where I am using the following:
Spring Boot 1.5.13 with Spring Cloud Version Edgware.S3
I have Spring Cloud Config Server and my Spring Boot apps are its clients
Each app carries a bootstrap.yml with the config server uri and some other properties.
Running containers on a Docker Swarm
I am currently passing Swarm secrets to the clients via a custom script which reads the files put into /run/secrets/ and creating a /config/bootstrap.properties file. It ends up looking like this:
spring.cloud.config.username=user
spring.cloud.config.password=password
My Docker image's default command is then this:
java -Djava.security.egd=file:/dev/./urandom -jar /${appName}.jar --spring.cloud.bootstrap.location=file:/config/bootstrap.properties"
Great. This is working without a problem. The app, seemingly, reads:
The external bootstrap.properties to read in the config server's credentials
The classpath bootstrap.yml to read in the rest of the config client props
Fetches and reads in the config server's application-appName.yml
Then reads the bundled application.yml from the classpath
Now. I'm moving the apps to Spring Boot 2.0.3 with Finchley.RELEASE and well, this breaks.
What is happening now is:
The external bootstrap.properties is read in to get the config server's credentials
The classpath bootstrap.yml is SKIPPED entirely (UNEXPECTED!)
Fetches and reads in the config server's application-appName.yml
Then reads the bundled application.yml from the classpath
The problem is that the properties that were set in the internal bootstrap.yml are now missing for the app so it blows up on start. I've been able to reproduce it outside the container environment by doing the same thing; point the app to an external bootstrap.properties. If I copy over the bootstrap.yml properties into the bootstrap.properties, then it works just fine. Also, if I don't provide an external properties file, then the internal bootstrap.yml kicks in without a problem. So it's either one or the other!
I'v also tried modifying the bootstrap location to include the default locations but no luck:
-- spring.cloud.bootstrap.location=file:/config/bootstrap.properties,classpath:,classpath:/config,file:,file:config/
Any ideas where to look next? Maybe there is a new spring.cloud.config property I'm missing? Or can anyone confirm which behavior is the correct behavior? Assuming they fixed a potential loophole in Finchley then I can just put this to rest and look for another solution. If it's 'broken' in Finchley, I guess an issue report is in order?
Well, some more digging showed that it looks like this is the new behavior:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Migration-Guide
The behavior of the spring.config.location configuration has been fixed; it previously added a location to the list of default ones, now it replaces the default locations. If you were relying on the way it was handled previously, you should now use spring.config.additional-location instead.
It didn't look to be Spring Cloud specific but I had nothing to lose.
Changing my java command to use this new property did the trick:
--spring.config.additional-location=file:/config/bootstrap.properties
Thanks.
I understand there's multiple ways of handling application property files and profiles in Spring Boot and I've seen multiple questions and answers on how to handle each but I'm trying to find the "best" way of handling it for a new project, if there is one.
The application is hosted in Weblogic 12c on production/pre-prod (with a jndi database connection) and ran locally in tomcat (with hardcoded database details) for development. I'd like it so that when the project is built via gradle and deployed to production it uses the jndi properties file and when ran locally it defaults to the hardcoded datasource with minimal changes required.
src/main/resources/application.properties
# DEV
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# DEV
# PROD
# spring.datasource.jndi-name=
# spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# PROD
From my understanding the recommended way is to externalize the property files and place the required one in a config directory alongside the WAR file for any differing config which is then automatically picked up and used?
You should consider creating multiple profiles. This means: Either multiple properties-Files, or multiple profiles in one file:
See https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
I would recommend to use multiple application-ENV.properties, e.g.
application-prod.properties and application-preprod.properties.
There is always one active profile and settings from the application.properties (without any profile suffix) are used as default values if not overwritten in a specific profile-file.
Depdending on your environment (local, prod etc.) you should set an environment variable (start the java-process/application server with that environment variable), e.g.:
SPRING_PROFILES_ACTIVE=prod
On your local machine you would set:
SPRING_PROFILES_ACTIVE=dev
With this variable you can control, which profile is currently active.
Also consider integrating the active profile into you Continious Integration/Deployment settings.
Please note that putting plain text passwords hardcoded into committed files is not a good idea. Consider using jasypt or a spring cloud config server for your prod database configuraiton or use any mechanism that your cloud provider provides to you if you use any. Microsoft Azure for example provides a KeyVault for sensitive data.
https://cloud.spring.io/spring-cloud-config/multi/multi_spring-cloud-config.html
http://www.jasypt.org/
If you use gradle good solution is to set application.properties and test.properties files and put into them properties for prod and preprod respectively.
Then run application with different vm arguments: -Dspring.profiles.active=test for test.properties and without arguments for application.properties
Use gradle instruments and configure them once for test and prod. For example: gradle bootWar configure without vm arguments, gradle bootWarTest with vm arguments -Dspring.profiles.active=test. Save once you configs and you will create war for different environments only selecting between two buttons in gradle.
Having something like
security.user.password = plainTextPassword
inside Spring Boot’s application.properties is obviously an anti-pattern as it prevents the code from being pushed to a public SCM. In my non-Spring Boot projects I use
security.user.password = ${myPasswordFromMavenSettingsXML}
and put a corresponding <properties/> reference inside my pom.xml.
Using Maven’s resource filter plugin the passwords are replaced at build time so the application have access to actual plain text passwords after it has been build and deployed.
For some reason Maven’s resource filter plugin does not work in this case. Is there a way to not commit plain text passwords to an SCM and let Spring Boot to insert them at build time?
Spring boot has multiple mechanisms to provided externalized configuration. Some examples are command line arguments, environment variables and also application properties outside of your packaged JAR.
What I usually do:
Locally we configured several environment variables. Most (if not all) IDE's allow you to configure environment variables from within the run configuration.
For example if you don't want to expose the spring.datasource.password property you could set an environment variable called SPRING_DATASOURCE_PASSWORD.
When we deploy on another environment, we usually choose to add another application.properties or application.yml file within the same folder as the application JAR/WAR, since Spring boot picks that up as well.
Another solution is to use Spring cloud since it has a config service which can be used as a microservice to provide configuration. The configuration can be versioned using SCM as well, but you can put it on a separate system that is not connected to your source code.