How to use a Spring profile expression? - spring

Spring Boot: 2.0.4.RELEASE
I have specified a Spring Boot multi-profile YAML configuration file:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production | eu-central
server:
address: 192.168.1.120
According to the reference guide, if the production or eu-central profile is active, the server.address property is 192.168.1.120. But when I run this test
#ActiveProfiles({"production"})
#RunWith(SpringRunner.class)
#SpringBootTest
public class ProfileTest {
#Autowired
private Environment environment;
#Value("${server.address}")
private String serverAddress;
#Test
public void testProfile() {
assertThat(environment.getActiveProfiles(), is(new String[] {"production"} ));
assertThat(serverAddress, is("192.168.1.120"));
}
}
it fails:
java.lang.AssertionError:
Expected: is "192.168.1.120"
but: was "192.168.1.100"
at com.example.demo.ProfileTest.testProfile(ProfileTest.java:27)
Why does the test fail and how do I use a Spring profile expression correctly?
By the way, if I remove | eu-central from the spring.profiles key the test passes!

Boris. I've studied your problem. And I think that I've found a bug in Spring-boot. I've reported an issue so you can check it. The problem is SpringBoot always use default value for ${server.address} because framework couldn't recognise profiles: production | eu-central as a List of profiles. If I use
#ActiveProfiles(value = "production | eu-central")
All is ok.
The link on Spring-boot issue here https://github.com/spring-projects/spring-boot/issues/14314
So, you could see the discussion.
Thank you!
UPDATED
I've got an answer from Spring Team on my Issue.
This format is a Spring Framework 5.1 (and therefore a Spring Boot 2.1
feature).
So the answer on your question is
This feature will be available in Spring Boot 2.1.

Related

Spring: Order of profile names with SpringBootTest

SpringBoot 2.7.1 with Spring Cloud Config
I have a test
#ActiveProfiles({"local","h2dbmem"})
#SpringBootTest(properties = {"server.ssl.enabled=false"})
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#TestPropertySource(locations = "classpath:application-integrationtest.properties")
public class SystemTest {
...
}
and I then run Maven to run the test :
mvn -Dspring.profiles.include=cloudconfig clean verify
When the test runs, the console says :
The following 3 profiles are active: "cloudconfig", "local", "h2dbmem"
Although this is exactly what I wanted, that it ends up using the JDBC URL and credentials from the h2dbmem profile and not the JDBC URL and credentials from the cloud-config profile, I could not find documentation on why cloudconfig ( supplied via command-line ) was added as the first entry in the list of active profiles after the profiles supplied via the #ActiveProfiles annotation.
To be honest, I was actually expecting the following order of profiles :
The following 3 profiles are active: "local", "h2dbmem", "cloudconfig"
... where the JDBC URL and credentials from cloudconfig are used, but I am glad it was not.
So question is:
Is there specific documentation on the sequence / order of profiles when they are supplied in multiple ways ( annotation, command-line, etc ) ?
Note I am aware of the order and sequence / preference of properties, which is not what I am asking.

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.

Integration Test with Spring Cloud Config Server

It's my first time implementing a Spring application using Spring Cloud Config framework. The application is working fine, getting configuration properties from repository through the server application. So, at the moment I must write the integration tests to test connection between client application and server and repository. To do it I mean get a value from repository over properties and check the value, also make a request to the server, or if exists, other method inside spring cloud config library to make it.
The problem now, when executing the integrationTest the application can't read the properties from remote. I've created a bootstrap.yml on the integrationTest context but not works. So, I got the errors like: Could not resolve placeholder.
MyIntegrationTest
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest()
#ActiveProfiles("integrationTest")
public class MyIntegrationTest {
#Value("${throttling.request.rate.minute}")
private int MAX_REQUEST_PER_MINUTE;
#Test
public void validateGetRequestRateLimitFromRepository() {
Assert.assertNotEquals(MAX_REQUEST_PER_MINUTE,1L);
}
}
bootstrap.yml
spring:
application:
name: sample-name
---
spring:
profiles: local #also tried integrationTest
cloud:
config:
enabled: true
uri: https://endpoint-uri:443
skipSslValidation: true
name: ${spring.application.name},${spring.application.name}-${spring.profiles}
Also tried:
Set application-integrationTest.yml
Set application.yml
Set bootstrap-integrationTest.yml
Create a *-integrationTest.yml at the repo.
Set #ActiveProfiles("local") on integrationTest class.
Nothing works.

throw exceptions for unknown Spring properties

I made a very silly mistake in my Spring Boot app's YAML config:
---
spring:
profiles: local
...
___
spring:
profiles: foo
...
---
spring:
profiles:
active: bar
include: foo
...
I accidentally tried to use "spring.profiles.active" to set the Spring profiles in the last section. Since this does not mean anything to Spring, the last section was always applied and the foo profile was always turned on.
spring.profiles.active is essentially nonsense, yet the app ran without complaint (until the problematic configuration caused other problems).
I would like to configure Spring somehow to immediately throw an exception when it encounters a config parameter that appears internal (spring...) but is unknown to it. How do I do this?

Environment VM arg passed to bootRun not being picked up by Spring Boot application

I am trying to set the environment of my Spring Boot (1.5.4.RELEASE) application at runtime, but it appears I have something out of alignment.
My application.yml is defined like this:
spring:
profiles.active: ${env:local}
---
spring:
profiles: local
foo: bar
---
spring:
profiles: dev
foo: bar
In a class that I have annotated as #Configuration, I have a method that does the following just so I can show the environment that is being used:
#Value('${spring.profiles.active}')
String activeProfile
#PostConstruct
def bootComplete() {
println "App started with profile: $activeProfile"
}
Under this configuration, when my application starts, I see this in the console:
App started with profile: local
If I modify ${env:local} to be ${env:dev} in my application.yml and I start the application, I see this in the console:
App started with profile: dev
My goal is to start the application with VM arguments to set the active profile at runtime. I am adding the argument: -Denv=dev but it appears that it has no effect on the starting of the application. Can anyone suggest what I might be overlooking here?
I found the solution to my problem. The issue was that I was starting the application using Gradle bootRun. My assumption was that the VM args set there would be used. I am now running the application by calling the class directly, and the VM args are working (both -Denv and -Dspring.profiles.active)
Doing -Denv=env will not make any effect since env is not a property key.
This is how you can do it:
-Dspring.profiles.active=dev

Resources