Problem using Spring Cloud Vault with #DynamicPropertySource - java

When using TestContainers to start a Vault container, the port that is exposed by the container is randomly selected during startup.
#Container
static VaultContainer vaultContainer = new VaultContainer<>("vault:1.7.2")
.withVaultToken(TOKEN)
.withInitCommand("secrets enable --path foo kv-v2")
.withInitCommand("kv put foo/app bar=foo");
Using a #DynamicPropertySource to override properties
#DynamicPropertySource
static void addProperties(DynamicPropertyRegistry registry) {
registry.add("spring.cloud.vault.host",()->vaultContainer.getHost());
registry.add("spring.cloud.vault.port",()->vaultContainer.getFirstMappedPort());
registry.add("spring.cloud.vault.uri",()->"http://"+vaultContainer.getHost()+":"+vaultContainer.getFirstMappedPort());
registry.add("spring.cloud.vault.token",()->TOKEN);
}
does not work since Spring Cloud Vault does not seem to "see" the added properties.
The issue is present in Spring-Boot 2.5.1 and Spring-Cloud-Vault-Config 3.0.3.
A small project showing the issue can be found on GitHub.
Am I doing something wrong or is there an alternative way to override the configuration?
When using Spring-Vault with a #VaultPropertySource instead of Spring-Cloud-Vault things work as expected.

According to:
https://github.com/spring-cloud/spring-cloud-vault/issues/602#event-4926845049
it's a spring-framework issue.

Related

How to read spring boot application property information inside the library

I am developing a common library for our projects. It is a maven project. Now It does not have any dependency related to Spring.
I am including the above library in the Spring boot maven project as a dependency.
As Spring Boot application runs, I need to read the value from Spring boot application.properties inside the common library.
Assume that I have environment key inside the application.properties.
environment = STAGE
I need the value of the environment inside the library.
I would suggest, you should add provision in your library to accept external configurations. You can create a Configuration class in your library which will store all required properties. Then you can create an instance of Configuration and provide it to other classes in your library. I see many libraries follows same pattern. Let's take example of Elasticsearch client which requires Elasticsearch URL:
Elasticsearch client
String elasticsearchUrl = environment.getRequiredProperty("elasticsearch-url");
ClientConfiguration clientConfiguration =
ClientConfiguration.builder().connectedTo(elasticsearchUrl).build();
RestHighLevelClient client = RestClients.create(clientConfiguration).rest();
In above example elasticsearch-url is accepted through application.properties file and then provided to Elasticsearch client library.
In the Spring Boot Application
#SpringBootApplication
public class ExampleMsApplication implements ApplicationRunner {
#Value("${props.env}")
private String env;
public static void main(String[] args) {
SpringApplication.run(ExampleMsApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
System.setProperty("env", env);
}
}
application.properties
props.env=local
In the library wherever its required, i am using the below code. Whatever i set in the spring boot application using System.setProperty , i am able to get using System.getProperty
System.getProperty("env")
You can create a #ConfigurationProperties class and create /resources/META-INF/spring.factories file with following property:-
org.springframework.boot.autoconfigure.EnableAutoConfiguration=Full classified name of your class

Getting following error while migrating Spring boot version from 2.1.6 to Spring boot 2.4

Getting following error while migrating my project from Spring boot version 2.1.6 to 2.4
No more pattern data allowed after {*...} or ** pattern element
Code Snippet where error is coming
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**/test/**")
}
}
I know Spring boot disable AntPathMatcher in version 2.4 , so I tried this option as well
Spring.mvc.pathpattern.matching-strategy= ant_path_matcher
but even this is not working
Any help is much appreciated
With the newest version 1.5.12 of springdoc-openapi-ui the problem is solved for me.
the reason:
The error is a path wildcard problem. The search found that the path wildcard has changed after the spring upgrade to 5.3. The official explanation is "In Spring MVC, the path was previously analyzed by AntPathMatcher, but it was changed to use PathPatternParser introduced in WebFlux from Spring 5.3.0.".
solve:
The specific solution is to change /**/*.css to /*/*.css. Multiple files may be involved in the project, and they must be changed.
Set this in your application.properties. Either that or just edit the patterns.
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
Please note that in newer versions of Spring the correct property is spring.mvc.pathmatch.matching-strategy (and not spring.mvc.pathpattern.matching-strategy)
More info:
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties.web.spring.mvc.pathmatch.matching-strategy
Relevant issue: https://github.com/spring-projects/spring-boot/issues/28791
It seems using CAPS is working:
spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER

server.servletPath=/* not working in spring-boot.version 2.1.7.RELEASE

I have been using the below property in the application.properties file with spring-boot.version 1.5.6.RELEASE without any issues.
server.servletPath=/*
This was a workaround to enable a method in a library class which uses the function getPathInfo() of javax.servlet.http.HttpServletRequest to get a valid value instead of null.
I had to go with this workaround since there is no support of that library jar anymore.
This workaround started failing when I upgraded my application to spring-boot.version 2.1.7.RELEASE
server.servletPath is changed to spring.mvc.servletPath from Spring Boot 2 onwards.
I tried setting the below property and it did not work
spring.mvc.servletPath=/*
I also tried the below function in my configuration class and it did not work.
#Bean
public DispatcherServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet,
ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(
dispatcherServlet, "/*");
registration.setName("dispatcherServlet");
registration.setLoadOnStartup(-1);
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
Could you please provide a working solution for this property using spring-boot.version 2.1.7.RELEASE?
Thanks,
Dhinu
The correct setting for newer spring versions is:
spring.mvc.servlet.path=/some/path
This changes the mapping of the DispatcherServlet, so all resources served by spring are mapped to this path.
If you set:
server.servlet.contextPath=/some/path
The whole web context is changed.
The main difference is that setting the dispatcher servlet path allows you to register additional servlets on other paths while with context path set, spring boot's tomcat can only serve content below that context path.
Use the following config property on latest spring boot version:
server.servlet.contextPath=/*

Spring boot on Google appengine standard - REST API

I'm trying to run my code on local appengine simulator through the command mvn appengine:run and there is no error, it is just that, it can't find any of the RestController (eg: No file found for: /setiaalam/amenities).
Also, there is no Spring Boot logo been display from the startup, so I suspect I need to specific the servlet init for it? It is running fine in my own Apache Tomcat Eclipse environment, but this is only working if I were to 'run' the main class.
To be more specific, there is no custom servlet i'm creating, I just want to migrate it to Google Cloud AppEngine Standard - although no error, there is no Spring Boot startup logo at all. Trying to access any of the GET API that works locally using Postmen always return 404. No issue when try to access it from previous Apache Tomcat localhost.
Yes, I'm following the github guideline here:
Link to Github for Spring boot Google Appengine Standard
It didn't mention anything on modifying the web.xml.
And I missing something here?
Code (The main app):
#SpringBootApplication
#EnableJpaAuditing
public class SetiaAlamApplication{
public static void main(String[] args) {
SpringApplication.run(SetiaAlamApplication.class, args);
}
}
Code(1 of the controller):
#RestController
#RequestMapping("/setiaalam")
public class AmenityController {
#Autowired
AmenityDAO amenityDAO;
//service to get all amenities
#GetMapping("/amenities")
public List<Amenity> getAllAmenities(){
return amenityDAO.findAll();
}
Code (The needed SpringBootServletInitializer):
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SetiaAlamApplication.class);
}
}
The application.properties:
# Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:mysql://ipaddress:3306/setiaalam?
useSSL=false&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
spring.datasource.username = hidden
spring.datasource.password = hidden
# Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen
database
spring.jpa.properties.hibernate.dialect =
org.hibernate.dialect.MySQL5Dialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
Ok, issue fixed. How I fix it?
Step 1
Download the latest Eclipse.
Step 2
This follow by adding Spring Tool feature from the Eclipse Market. Please take note, if you can't find the Eclipse Market, you need to add that 1st. Simple google will guide you on how to do it.
Step 3
Then follow by adding Google Cloud Tool (from Eclipse Market).
Step 4
Once added, create a new Spring project (through Eclipse as previously I didn't but using spring.io).
Step 5
Based on the generated single main application, change the needed configuration on the pom.xml according to the Google guide for Google App Engine Standard.
Step 6
Once done, create a simple 1 controller that return just a single String text. Deploy locally - on Jetty of course, once working, move 1 by 1 all the classes to this new project.
Important Notes
Of course, if your project like mine that require more and extra dependency such as Google API, Hibernate, you need to manually add it into the pom.xml.
Also, for sure you will encounter the issue from Eclipse 'Unable to change Dynamic Web Module facet', i follow the simple guide of changing the project file org.eclipse.wst.common.project.facet.core.xml accordinly to 3.0 for the jst.web.
Thanks

Using Spring boot/cloud with Amazon AWS lambda does not inject values

I have an AWS lambda RequestHandler class which is invoked directly by AWS. Eventually I need to get it working with Spring Boot because I need it to be able to retrieve data from Spring Cloud configuration server.
The problem is that the code works if I run it locally from my own dev environment but fails to inject config values when deployed on AWS.
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class MyClass implements com.amazonaws.services.lambda.runtime.RequestHandler<I, O> {
public O handleRequest(I input, Context context) {
ApplicationContext applicationContext = new SpringApplicationBuilder()
.main(getClass())
.showBanner(false)
.web(false)
.sources(getClass())
.addCommandLineProperties(false)
.build()
.run();
log.info(applicationContext.getBean(SomeConfigClass.class).foo);
// prints cloud-injected value when running from local dev env
//
// prints "${path.to.value}" literal when running from AWS
// even though Spring Boot starts successfully without errors
}
}
#Configuration
public class SomeConfigClass {
#Value("${path.to.value}")
public String foo;
}
src/main/resources/bootstrap.yml:
spring:
application:
name: my_service
cloud:
config:
uri: http://my.server
failFast: true
profile: localdev
What have I tried:
using regular Spring MVC, but this doesn't have integration with #Value injection/Spring cloud.
using #PropertySource - but found out it doesn't support .yml files
verified to ensure the config server is serving requests to any IP address (there's no IP address filtering)
running curl to ensure the value is brought back
verified to ensure that .jar actually contains bootstrap.yml at jar root
verified to ensure that .jar actually contains Spring Boot classes. FWIW I'm using Maven shade plugin which packages the project into a fat .jar with all dependencies.
Note: AWS Lambda does not support environment variables and therefore I can not set anything like spring.application.name (neither as environment variable nor as -D parameter). Nor I can control the underlying classes which actually launch MyClass - this is completely transparent to the end user. I just package the jar and provide the entry point (class name), rest is taken care of.
Is there anything I could have missed? Any way I could debug this better?
After a bit of debugging I have determined that the issue is with using the Maven Shade plugin. Spring Boot looks in its autoconfigure jar for a META-INF/spring.factories jar see here for some information on this. In order to package a Spring Boot jar correctly you need to use the Spring Boot Maven Plugin and set it up to run during the maven repackage phase. The reason it works in your local IDE is because you are not running the Shade packaged jar. They do some special magic in their plugin to get things in the right spot that the Shade plugin is unaware of.
I was able to create some sample code that initially was not injecting values but works now that I used the correct plugin. See this GitHub repo to check out what I have done.
I did not connect it with Spring Cloud but now that the rest of the Spring Boot injection is working I think it should be straightforward.
As I mentioned in the comments you may want to consider just a simple REST call to get the cloud configuration and inject it yourself to save on the overhead of loading a Spring application with every request.
UPDATE: For Spring Boot 1.4.x you must provide this configuration in the Spring Boot plugin:
<configuration>
<layout>MODULE</layout>
</configuration>
If you do not then by default the new behavior of the plugin is to put all of your jars under BOOT-INF as the intent is for the jar to be executable and have the bootstrap process load it. I found this out while addressing adding a warning for the situation that was encountered here. See https://github.com/spring-projects/spring-boot/issues/5465 for reference.

Categories