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?
Related
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
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.
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.
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.
we want to implement a feature for enabling a user to choose a role in the system, by sending the role he/she wishes to have in the login request.
this feature is meant for testing (creating test-users or assigning roles to existing ones is "impossible" in the customers system) and, of course, should never be deployed to a production environment.
I want to deployment of my application to fail if the property feature.choose-role is set to true AND the spring active profile is set to production.
As we are using springs config-server features, i also want to application to completely stop working if the property is set to true at runtime.
My first attempt was to simply create this Config:
#Configuration
public class FeatureToggleGuardConfig {
#Bean
#RefreshScope
#ConditionalOnProperty(value = "feature.choose-roles", havingValue = "true")
#Profile("production")
public Object preventDeploymentOfRoleChoosingFeatureOnProduction() {
throw new RuntimeException("feature.choose-roles must not be true in production profile!");
}
}
This works if the property is set to true at deployment, but as i understand, will only attempt to refresh the bean if someone actually tries to use it - which will never happen.
Also - i don't think that it would stop the whole application if this just threw a runtime exception when it is used.
in short:
I want to prevent my application to run (or keep running) if at any time, the property feature.choose-roles is true and the active profile is "production".
I do not want to alter production code in order to do this ( if(feature is enables && profile is production) etc.)
Perhaps instead of having a your profile drive some sort of blocker, you can have your profile drive a config bean which says whether or not to use the feature. Then, have the nonProd config read from your property, and have the prod config always return false.
Something like:
public interface ChooseRolesConfig {
boolean allowChoosingRoles();
}
#Profile("!production")
public class NonProdChooseRolesConfig implements ChooseRolesConfig {
#Value("${feature.choose-roles}")
boolean chooseRoles;
#Override
public boolean allowChoosingRoles() {
return chooseRoles;
}
}
#Profile("production")
public class ProdChooseRolesConfig implements ChooseRolesConfig {
#Override
public boolean allowChoosingRoles() {
return false;
}
}
and then just autowire a ChooseRolesConfig object, and call the method, and regardless of what you change feature.choose-roles to using cloud config, it should always be false for prod.
Disclaimer: I blindly wrote this so it might be missing some annotations or something but hopefully you get the idea