Two environments in the same application - spring-boot

Is it possible to build Environment for different profile (with relayted property sources)?
For example: while app running with prod profile i want to have configuration beans for dev profile.
I am using spring boot 2 (with new Binder API)
Any help is appreciated.
P.S.: I wand same configuration objects but with profile specific values.
Example:
application.yml
spring:
profiles: dev
server:
address: 127.0.0.1
---
spring:
profiles: prod
server:
address: 192.168.1.120
Config bean:
#Component
#ConfigurationProperties("server")
#Validated
public static class ServerConf {
private final InetAddress address;
...
}
main goal is to have ServerConf as a bean related to active profile and set of objects of ServerConf class related to specific profile or set of beans like ServerConfProd, ServerConfDev
Ideally i am looking for something similar to this:
StandardEnvironment env = new StandardEnvironment();
env.setActiveProfiles("prod");
MutablePropertySources propertySources = env.getPropertySources();
propertySources.addLast(new ResourcePropertySource("classpath:application-prod.properties"));
propertySources.addLast(new ResourcePropertySource("classpath:application.properties"));
ServerConf prodServerConf = Binder.get(env).bind("server", Bindable.of(ServerConf.class)).get();
It works but has a lot of disadvantages: validation would not work, property sources manually set ...

Yes, you can set multiple active profiles as follow:
spring.prifiles.active:
- prod
- dev
With this approach al beans defined with #Profiles("prod") and #Profiles("dev") will be initialized. Be aware that there shouldn't be any amboguous bean definition.
If you prefer to just set prod as active profile, you can still tell Spring to include other profiles:
spring.profiles.include:
- dev
- other
For more reference take a look at profiles chapter
UPDATE
Your idea won't work: one property will override the other.
I would handle serverConf.address as a Map:
application.yml
spring:
profiles: dev
server:
addresses:
dev: 127.0.0.1
---
spring:
profiles: prod
server:
addresses:
prod: 192.168.1.120
ServerConf.java
#Component
#ConfigurationProperties("server")
#Validated
public class ServerConf {
private final Map<String, InetAddress> addresses = new HashMap<>();
//...
}
In this way, if you activate both profiles you'll get a map with 2 keys (dev and prod). I personally find it a bit ugly, but should solve your problem.

Related

read application.yml properties from specific profile

We have some default secrets set in our application.yml that are used in environments such as development and tests etc:
application.yml
spring.profiles.default: development
spring.profiles.active: development
foo:
secret: admin
---
spring:
config.activate.on-profile: test
logging:
level.root: DEBUG
---
spring:
config.activate.on-profile: production
logging:
level.root: INFO
Now, in our production environment we overwrite the secrets by setting environment variables.
However, we want to ensure that our application fails booting if any secret from the application.yml was not properly overwritten by environment variables, to prevent default secrets in production.
For now we check it like this:
#Profile("production")
#Validated
#Configuration
#ConfigurationProperties("foo")
class FooConfigSecrets {
class FooConfigSecrets(
private val environment: Environment,
) {
lateinit var secret: String
#AssertTrue
fun isSecretValid(): Boolean {
if (isNotProductionEnvironment()) return true
// How can we lookup the value set in application.yml (`admin`) in `production` environment instead of hard coding it?
return secret isSecretSetAndNotDefault(secret.user.name, "admin")
}
fun isSecretSetAndNotDefault(secret: String, default: String): Boolean {
return StringUtils.hasText(secret) &&
!(secret.startsWith("\${") && secret.endsWith("}")) &&
secret != default
}
fun isNotProductionEnvironment() = !environment.activeProfiles.contains("production")
}
That works fine, but if anyone ever changes the default password from admin to e.g. test, it is easily missed to also update it in this validation class. Hence, I am wondering, how we could lookup the secret's default value admin instead of hard coding it?

Spring ConfigurationProperties not loading values(default) from application.yml

PropertyLoader class does not load the default value for "testValue" from application.yml
#ConfigurationProperties(prefix="my-property")
#Component
public class PropertyLoader {
String testValue;
public String getTestValue() {
return testValue;
}
public void setTestValue(String testValue) {
this.testValue = testValue;
}
}
application.yml
spring:
profiles:
active: default
my-property:
testValue: random
You should use #Value(„${testValue}“) over testValue field.
https://www.baeldung.com/spring-value-annotation
Answering my on question as I figured it out after spending some time on it.
Never set active profile in application.yml, in my case someone added active profile to default, as soon as the spring property loader sees the spring.profiles.active it searches for application-{avtive-profile}.yml, in my case application-default.yml since it does not find a file by that name it does not load the default values from application.yml

How to automatically initialize the DB schema, on a spring boot + eclipselink + postgresql integration testing

I'm trying to set up an integration testing framework for my Java 11, spring boot + eclipselink + postgresql application.
I use docker-maven-plugin to spin off a new postgresql DB container each time, and I'm running the integration tests using maven-failsafe-plugin. All good with this approach, except for the DB schema initialization: I'm not able to initialize that schema.
This is how my application-it.yaml looks like:
# Graceful shutdown delay
application:
shutdownhook:
delay-msec: 500
# Spring Boot management endpoint configuration
management:
port: 9199
info:
git:
mode: full
# Spring Boot enpoints Management endpoint
endpoints:
metrics:
sensitive: false
env:
sensitive: false
health:
enabled: false
# short value just for testing
estaGracefulShutdownWaitSeconds: 5
# Application-specific configuration
sample-application:
server-name: Sample App Under Test
logging:
config: classpath:log4j2-spring.xml
spring:
datasource:
platform: postgresql
url: jdbc:postgresql://localhost:${it-database.port}/postgres
username: postgres
password: postgres
schema: classpath:schema-it.sql
data: classpath:data-it.sql
driver-class-name: org.postgresql.Driver
initialization-mode: always
The schema-it.sql and data-it.sql are ok and available in the class path. There are no errors reported on their execution during the IT run. But still the schema doesn't get created (I know that because first the IT test are failing complain that the task table is missing and I'm able to access that test DB and i can see there are no table created in there).
The unit test looks like this:
#RunWith(SpringRunner.class)
#ActiveProfiles("it")
#SpringBootTest
public class TaskRepositoryIT {
#Autowired
private TaskRepository taskRepository;
#Test
public void testGetTasks() {
List<Task> tasks = taskRepository.findAll();
Assert.assertTrue(tasks.isEmpty());
}
}
Any idea how I can instruct spring to generate that schema based on schema-it.sql content and then populated it with the data-it.sql data?
Thanks
Try with #Sql annotation which provide's way to populate database from .sql files when performing tests. U should use it as in following snippet:
#RunWith(SpringRunner.class)
#ActiveProfiles("it")
#SpringBootTest
#Sql({"/schema-it.sql", "/schema-it.sql"})
public class TaskRepositoryIT {
#Autowired private TaskRepository taskRepository;
#Test
public void testGetTasks() {
List<Task> tasks = taskRepository.findAll();
Assert.assertTrue(tasks.isEmpty());
}
}
Found my problem. The issues was with the .sql file names.
schema: classpath:schema-it.sql
data: classpath:data-it.sql
The correct manes should have been:
schema: classpath:schema-postgresql.sql
data: classpath:data-postgresql.sql
Because platform is set to postgresql.

Can't load a test-properties.yaml file with the #TestPropertySource with Spring Boot, JUnit5 and Kotlin

I have a class that is injected with the application.yaml properties located in the src/main/resources directory.
I wrote a test that is asserting if those properties are injected as expected.
The test does work fine but it is using the application.yaml file from src/main/resources.
I want it to use the test-application.yaml file from src/test/resources to use some bogus values.
I followed many guides and read many questions here on Stackoverflow but could not get any of those approaches to work. Maybe because I mixed up different solution's annotations or whatever.
Status Quo
I am using the spring versions provided by the spring management system
id("io.spring.dependency-management") version "1.0.9.RELEASE"
Edit:
Also I use the following relevant dependencies:
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-webflux")
The property file src/main/resources/application.yaml:
controlroom:
ftpConnections:
foo-importer:
host: ftp.stage.company.com
port: 22
user: foo-ftp
password: password
bar-importer:
host: ftp.stage.company.com
port: 22
user: bar-ftp
password: password
This is the property configuration holder class:
#Configuration
#ConfigurationProperties(prefix = "controlroom")
class FtpConnectionsProvider(
) {
lateinit var ftpConnections: Map<String, Map<String, String>>
}
And this is the test class that currently is injecting the properties from main/resources/application.yaml:
#ExtendWith(SpringExtension::class)
#SpringBootTest
internal class FtpConnectionsProviderTest(
#Autowired
val ftpConnectionsProvider: FtpConnectionsProvider
) {
#Test
fun `should fill FtpConnectionsProvider with properties from yaml`() {
assertThat(ftpConnectionsProvider.ftpConnections["foo-importer"]).containsAllEntriesOf(
mapOf(
"host" to "ftp.stage.company.com",
"port" to "22",
"user" to "foo-ftp",
"password" to "password"
))
assertThat(ftpConnectionsProvider.ftpConnections["bar-importer"]).containsAllEntriesOf(
mapOf(
"host" to "ftp.stage.company.com",
"port" to "22",
"user" to "bar-ftp",
"password" to "password"
))
}
}
How to use the test-application.yaml ?
Now I wonder how I can give the test the instruction to only use src/test/resources/test-application.yaml.
First Try
#ExtendWith(SpringExtension::class)
#SpringBootTest
#TestPropertySource("classpath:test-application.yaml")
internal class FtpConnectionsProviderTest
Result: Just ignores it and keeps using src/main/resources/application.yaml
Second Try
People say that #TestPropertySource can't read .yaml files and therefore I added the #ContextConfiguration annotation since this was the suggested fix.
#ExtendWith(SpringExtension::class)
#SpringBootTest
#TestPropertySource("classpath:test-application.yaml")
#ContextConfiguration(initializers=[ConfigFileApplicationContextInitializer::class])
internal class FtpConnectionsProviderTest
Result: Just ignores it and keeps using src/main/kotlin/resources/application.yaml
Nothing seems to work. I do not grasp why this is such a big deal and why it is so complicated to find a solution
Have you tried placing a application.yaml file under src/test/kotlin/resources? That should work without any special config / annotations.
Also, if you want to have / override certain properties within a yaml file called testfoo, you can place these props in a application-testfoo.yaml in the same directory, and the file will be used if you activate a testfoo Spring profile.
Example:
src/test/kotlin/resources/application.yaml:
controlroom:
ftpConnections:
foo-importer:
host: ftp.stage.company.com
port: 22
user: foo-ftp
password: password
bar-importer:
host: ftp.stage.company.com
port: 22
user: bar-ftp
password: password
src/test/kotlin/resources/application-testfoo.yaml:
controlroom:
ftpConnections:
foo-importer:
user: test-foo-ftp
If you active the testfoo profile, controlroom.ftpConnections.foo-importer.user will be overridden. Is that what you want?
Alternatively, as an even simpler solution if you just want to replace a couple of your production prop values: Don't create a src/test/kotlin/resources/application.yaml at all. Just create the src/test/kotlin/resources/application-testfoo.yaml and it should override the values from src/main/kotlin/resources/application.yaml (assuming the testfoo profile is active)! That works in Java with my applications.properties anyways - I see no reason why it should work any differently in Kotlin.
You cannot load yaml file using #TestPropertySource.

application.properties spring boot value injection

I'm working on the REST API with spring boot. I want to use git in my project. in the file application.properties I have the database Url, username and password that I do not want to push on git. I don't know how can I create a file which contains my database configuration and how to inject those configurations in the application.properties .
application.properties
## Server Properties
server.port= 5000
## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url= jdbc:mysql://localhost:3306/MyApp?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
spring.datasource.username= user
spring.datasource.password= pass
Spring picks up configuration properties not only from the application.properties but also from command line arguments, JAVA System-properties or from environmental-variables.
See complete list here: Spring Externalized Configuration.
So - for reference - you can keep the properties in the application.properties file with some default values (like in your example) in order to let other users know what kind of properties they can set for your application.
But instead of setting your real values there, you can either pass the variable to your application as arguments, like
-Dspring.datasource.username=user -Dspring.datasource.password= pass
or you can set them as environmental variables.
You can even create multiple configuration with different settings. If Spring cannot find a variable in the current configuration, then it will pick it up from application.properties (or from the other sources - see above)
first you should add application.properties to .ignore file like this
application.properties
if you will just connect to database you won't need to inject values by hand you just write it in application.properties
but if you want to put values in properties file and use it in Application
package com.microservice.test.limitservice;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#Component
#ConfigurationProperties("limit-service")
public class Configuration {
private int minimum;
private int maximum;
public int getMinimum() {
return minimum;
}
public void setMinimum(int minimum) {
this.minimum = minimum;
}
public int getMaximum() {
return maximum;
}
public void setMaximum(int maximum) {
this.maximum = maximum;
}
}
and how to inject it simply
#Autowired
private Configuration configuration;
the application.properties file could be like this
limit-service.minimum=56333445
limit-service.maximum=6500
you should notice that it start with as example limit-service
and #ConfigurationProperties("**limit-service**")
And if you want to store your configuration in application.properties secure
you can see this link Spring Boot how to hide passwords in properties file

Resources