How to create Spring Cloud Config Client with env specific configuration? - spring-boot

I have facing an issue with Spring Cloud Config Server and Eureka Server Profiling.
Let's say I have 3 services with their name ("spring.application.name") as :
myapp-svc
myapp-spring-cloud-config-svc
myapp-spring-eureka-svc
I want to deploy each service in 2 regions ( dev and prod ). In Dev region, each service will run on localhost and in prod it will have some different url. 'myapp-spring-cloud-config-svc' in dev region will point to local git repo, while in prod region it will point to remote git repo.I can have 2 configurations:
1) When I start 'myapp-svc' service in local, it should connect to 'myapp-spring-cloud-config-svc' in dev.
I can do this by setting spring.cloud.config.uri = .
But the issue with this set up is that the property needs to be defined in bootstrap.properties.
So, If deploy 'myapp-svc' to prod, I will have to change config uri there to point it to prod config service which in turn would need another build creation.
This doesn't seem like a good solution, what if I have 50 app related services, I can't change this property in each one of them before prod deployment.
I tried setting spring.cloud.config.uri in application-dev.properties of 'myapp-svc' but it doesn't work. As per docs, it must be changed in bootstrap.
So, how do I implement this without having to create new build for prod ?
2) I can first call eureka and then using eureka I can call config service here.
The problem here is also same.
If I use eureka to look up config then "eureka.client.serviceUrl.defaultZone" must be defined in "bootstrap.yml".
See this:https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_client.html
So, in this case too, I need to change eureka url before deploying this service to prod.
Please help me on this...!!
Here is how, the properties, yml looks like for each of the above mentioned services:
1) myapp-svc:
1.1)bootstrap.yml
spring:
application:
name: myapp-svc
cloud:
config:
discovery:
enabled: true
serviceId: myapp-spring-cloud-config-svc
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8762/eureka/
server:
port: 8082
2) myapp-spring-cloud-config-svc:
2.1)application-dev.properties:
spring.cloud.config.server.git.uri=file:///C:/config-repo
eureka.client.serviceUrl.defaultZone=http://localhost:8762/eureka
2.2)application-prod.properties:
spring.cloud.config.server.git.uri=https://github.com/<mygit Repo>
2.3)bootstrap.proerties:
spring.application.name=myapp-spring-cloud-config-svc
server.port=8888
3) myapp-spring-eureka-svc
3.1)bootstrap.proerties
spring.application.name=myapp-spring-eureka-svc
server.port=8762

1) You can have profile specific bootstrap-<profile>.properties (like for application-<profile>.properties) for each supported profile to avoid rebuilding your application for each env. Then just pass application profile using to your application during start-up. Spring will load correct bootstrap-<profile>.properties and will connect to proper configuration server (or eureka, etc). Example:
java -jar your-app.jar --spring.profiles.active=dev
2) You can pass your URLs externally as custom properties (same as with profile above) and have smth like this in bootstrap.properties. Example:
spring.cloud.config.uri=${config.server.url}
then pass --config.server.url= ... during start-up.
3) You can pass Spring properties in the same way during start-up. Example:
java -jar your-app.jar --spring.cloud.config.uri= ...
4) You can use system env variables. Example:
spring.cloud.config.uri=${SYSTEM_ENV_CLOUD_CONFIG_URI}

Related

Profile based Spring Cloud Config Server client with optional local developer properties

My Goal:
Having the Spring Cloud Config Server import active and provide a way for developers to have an optional property file on their machine.
My Config Server is up and running using org.springframework.cloud:spring-cloud-config-server:3.1.3 and #EnableConfigServer on the main class. Http requests to the concrete endpoint yield the expected result. This server should provide important environment configurations for a developer for his/her local setup.
$ curl http://localhost:8888/test-application/dev
{"name":"test-application","profiles":["dev"],"label":null,"version":null,"state":null,"propertySources":[{"name":"classpath:/cfg/test-application/application-dev.yml","source":{"server.port":1111}},{"name":"classpath:/cfg/test-application/application.yml","source":{"server.port":8000}}]}
Where localhost:8888 is my Config Server and test-application ist the name of the client application (defined via spring.application.name). The provided dev is the currently active profile on the client (The dev profile indicates a locally running software, there is no dev environment).
Clients configuration:
application.yml
spring:
application:
name: test-application
config:
import:
- configserver:http://localhost:8888 # <- pull the configuration from the configserver
- optional:file:/absolute/path/to/the/project/root/ # <- if there are any additional configuration files, use them
The client uses the following dependencies:
org.springframework.boot:spring-boot-starter-web:2.7.0
org.springframework.cloud:spring-cloud-starter-bootstrap:3.1.3
org.springframework.cloud:spring-cloud-starter:3.1.3
org.springframework.cloud:spring-cloud-starter-config:3.1.3
As shown above, the "base" application.yml configured server.port=8000 where the profile specific is set to server.port=1111. When reading the documentation this behaviour is correct. But my local developer configuration contains server.port=2222. This was ignored.
Here comes the problem:
When starting the client application, i can see the following log statements:
Fetching config from server at : http://localhost:8888
Located environment: name=test-application, profiles=[default], label=null, version=null, state=null
Located property source: [BootstrapPropertySource {name='bootstrapProperties-configClient'}, BootstrapPropertySource {name='bootstrapProperties-classpath:/cfg/test-application/application-dev.yml'}, BootstrapPropertySource {name='bootstrapProperties-classpath:/cfg/test-application/application.yml'}]
The following 1 profile is active: "dev"
Tomcat initialized with port(s): 1111 (http)
Initializing Spring embedded WebApplicationContext
Tomcat started on port(s): 1111 (http) with context path ''
Started TestApplicationKt in 2.234 seconds (JVM running for 2.762)
The configuration evaluation result from spring was to choose port 1111 instead of the wanted 2222 within the application-dev.yml located in the project root (developer config).
Wrapped up:
Three config files:
config server application.yml (port: 8000)
config server application-dev.yml (port: 1111)
project root developer config application-dev.yml (port: 2222) <- I want this file to have precedence over the other two.
When running the debugger, i see these found property sources within the injected Environment bean. The wanted developer file is within this list and the property source content is correct (server.port=2222).
Does anyone have an idea how to solve this problem?
I have created a project that reproduces this exact behaviour. Link to GitHub.
Thanks in advance!
Is the dev profile active when application is running in dev environment or it's also active when running locally?
Ideally you should have 2 different profiles for dev and local and you can enable config server only when dev profile is active.
You can rename your local property file as application-local.yml and disable spring cloud config when profile local is active.
You can put below code in bootstrap.yml(create new file beside application.yml) and remove config server configurations from application.yml
---
spring:
application:
name: test-application
config:
activate:
on-profile: dev
import:
- configserver:http://localhost:8888 # <- pull the configuration from the configserver
- optional:file:/absolute/path/to/the/project/root/ # <- if there are any additional configuration files, use them
---
spring:
application:
name: test-application
config:
activate:
on-profile: local
cloud:
config:
enabled: false
EDIT: Actual fix
Thanks for sharing reproducible example.
All you need to do is remove local application-dev.yml location reference from client application.yml and add it to configServer application.yml as shown in belolow screenshots.
/client/src/main/resources/application.yml
And put it in configServer application.yml at the end of search-locations. Order matters so please make sure you put overriding location/s at the end.
/configServer/src/main/resources/application.yml
Build and start both the applications and you will see client application server started on port 2222

SpringBoot project, Environment Variable in application.yml always empty

I'm trying to run a SpringBoot project that runs with docker-compose. i started its dependencies (Redis, MongoDB ,and RabbitMQ) with docker-compose up
and i'm building the project and running it with these commands
mvn clean package -DskipTests && mvn spring-boot:run
I keep having these errors :
Error processing condition on org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2RestOp erationsConfiguration$RequestScopedConfiguration
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'OAUTH_CLIENTID' in value "${OAUTH_CLIENTID}"
In the docker-compose file, the values of the environment variables are defined in the environment section.
environment:
- RABBIT_HOST=rabbitstomp
- RABBIT_USER=guest
- RABBIT_PASS=user
- MONGO_HOST=mongodb://localhost:27017
- OAUTH_CLIENTID=nz-kek
- OAUTH_CLIENT_SECRET=DzXZxeOZOJHFZIUhObSpsne
- SSO_HOST=https://webweb.com
- CORS_HOSTS=HOST1,HOST2
- SES_HOST=ses
- SES_PORT=6000
- REDIS_HOST=localhost
- REDIS_PORT=6379
This is how application.yml looks like :
spring.data.mongodb:
database: ${DB_NAME} #notificationdb
uri: ${MONGO_HOST}
security:
oauth2:
resource:
jwk:
key-set-uri: ${auth-server:${SSO_HOST}}/keys
token-info-uri: ${auth-server:${SSO_HOST}}/userinfo
client:
client-id: ${OAUTH_CLIENTID}
client-secret: ${OAUTH_CLIENT_SECRET}`
So when running the project without docker-compose, am i supposed to put the values in the application.yml ?
i also tried mvn spring-boot:run -Dspring-boot.run.arguments=--path.to.value=value1, but i'm not sure how the path should be with variables like key-set-uri: ${auth-server:${SSO_HOST}}/keys
Spring has support for providing default values in the configuration via the PlaceholderConfigurerSupport. The default value is what comes after the :. In your case, you should write:
client-id: ${OAUTH_CLIENTID:yourDevelopmentDefaultClientID}
If you use the #Value annotation to inject the values, you have support to SpEL for using expressions of the type: #{someExpression} for more complex cases.
UPDATE:
In your case, I believe you are reversing the position of the variables. The correct should be:
key-set-uri: ${SSO_HOST:auth-server}/keys
Here is what it means: first, it will try to use the SSO_HOST environment variable, which is provided to the container through docker-compose. In case this variable is not provided to the process, Spring will use auth-server as the address of the server. It seems to me that this address is visible only inside the docker-compose network, so if you are running your app outside this network, the auth-server address will not be visible. Do you know where is the auth server? Is it another docker container? Is it running on localhost?
Some interesting reference: https://www.baeldung.com/spring-value-defaults
Pass env variables in docker compose as object not a list:
environment:
RABBIT_HOST: rabbitstomp
RABBIT_USER: guest
RABBIT_PASS: user
MONGO_HOST: mongodb://localhost:27017
OAUTH_CLIENTID: nz-kek
OAUTH_CLIENT_SECRET: DzXZxeOZOJHFZIUhObSpsne
SSO_HOST: https://webweb.com
CORS_HOSTS: HOST1,HOST2
SES_HOST: ses
SES_PORT: 6000
REDIS_HOST: localhost
REDIS_PORT: 6379`

Read values from consul while bootstrap spring boot

I have question is there any way to retrieve certain values and inject them to bootstrap.yml while application is coming up.
I have configuration file like this:
spring:
application:
name: myApp
cloud:
consul:
enabled: true
host: localhost
port: 8500
config:
enabled: true
datasource:
url: jdbc:oracle:thin:#localhost:1111:XXXX
username: ${nameOfVariable1}
password: ${nameOfVariable1}
driver-class-name: oracle.jdbc.OracleDriver
For example, I need to configure embedded tomcat port, or DB credentials, I don't want to put it hardcoded in .yml properties file, instead I want to put some variable name in .yml so Spring will go and bring value from Consul. Is it possible?
You can use Spring Cloud Consul Config project that helps to load configuration into the Spring Environment during the special "bootstrap" phase.
3 steps:
add pom dependency: spring-cloud-starter-consul-config
enable consul config: spring.cloud.consul.config.enabled=true
add some config in consul kv in specific folder, such as key: config/testConsulApp/server.port, value:8081
and then start the sample web app, it will listen 8081.
more detail at spring cloud consul doc.
and demo code here

Disabling config server for tests

I am using microservice architecture where the services are done with spring boot, to control them, I am using zuul as a proxy, eureka as register and config server as the configuration provider.
In the microservices I configure my bootstrap.yml file as follows:
spring:
application:
name: portal-gca-server-${MYENV:local}
cloud:
config:
uri: http://localhost:9090
As seen above, the config file name that I look for in the config server is according to my environment variable, otherwise I get the local profile.
This works fine, but when I run the tests on the development machine it will never work, because instead of searching for the test profile it searches for the location, because the dev machine has no environment variable.
I know I could register the environment variable to run the tests but that's not the intention or else I would have to do this all the time to run tests and to run the local application.
Is there any way to solve this? I already tried to use the annotations:
webEnvironment = SpringBootTest.WebEnvironment.MOCK
#ActiveProfiles("test")
#TestPropertySource(locations="classpath:application-test.properties")
None of them any good, because the first thing the application does when executed is to fetch the information in git according to bootstrap.yml.
Has anyone ever experienced this?
Go to your bootstrap.yml and then add :
spring:
cloud:
config:
enabled: false

Shared Config(at git) between SPring Boot Services

I am developing spring boot services.
So lets say I have 3 Services A, B, C with dev and prod profiles each. Now I am keeping the configuration for these services at GIT and using config server to fetch the configuration.
So when I run A service in prod mode, the config file A-prod.properties is getting used.
Now I want to keep some common configuration which will be used by all 3 Services say common-prod.properties. How should I do that?
I have tried this:
Config Server :
spring:
cloud:
config:
server:
git:
uri: http://gitPaath/Configs.git
username: <username>
password: <pass>
cloneOnStart: true
searchPaths: "{common}"
My properties are file are in Git Repo in this order :
- A-prod.properties
- A-dev.properties
- B-prod.properties
- A-dev.properties
- C-prod.properties
- C-dev.properties
- common
- common-prod.propeties
- common-dev.properties
You can put all shared properties between services in application-<profile>.properties file.
In your case, make application-prod.properties and application-dev.properties files and put these file on root folder or any folder that is searchable by config server.
You can find more details in "Sharing Configuration With All Applications" section in here.

Resources