Spring GCP Cloud SQL and GCP Runtime Config processed out of order - spring

Spring Cloud SQL is initialized with an EnvironmentPostProcessor factory and GCP Runtime Config is initialized with a BootstrapConfiguration factory, in that order. But I have Cloud SQL properties configured in the Runtime Config, so it's a little awkward. I see that there is (or used to be) a Spring Boot starter for this (spring-cloud-gcp-starter-config), but it doesn't seem to be maintained anymore. Does anyone even use the Runtime Config service? Here's how I've worked around this:
public class RuntimeConfigEnvironmentPostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
try {
if (Arrays.stream(environment.getActiveProfiles()).noneMatch("local"::equals)) {
GcpConfigProperties gcpConfigProperties = new GcpConfigProperties();
gcpConfigProperties.setEnabled(true);
gcpConfigProperties.setName(environment.getProperty("spring.application.name"));
gcpConfigProperties.setProfile(environment.getActiveProfiles()[
environment.getActiveProfiles().length - 1]);
gcpConfigProperties.setProjectId(environment.getProperty("spring.cloud.gcp.config.project-id"));
PropertySourceLocator locator = new GoogleConfigPropertySourceLocator(
new DefaultGcpProjectIdProvider(),
GoogleCredentials::getApplicationDefault,
gcpConfigProperties);
environment.getPropertySources().addLast(
locator.locate(environment));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
And I registered it in a spring.factories file which is something I've never had to do before. I found a similar issue and someone has worked around it in a similar manner: Spring Cloud Config and Spring Cloud Vault order of initialization
Is there a more elegant way?

Related

Does Spring Boot Actuator have a Java API?

We customize the Spring Boot Actuator Info endpoint to include the application version number generated during our Jenkins build. We're using gradle to do this:
if (project.hasProperty('BUILD_NUMBER')) {
version = "${BUILD_NUMBER}"
} else {
version = "0.0.1-SNAPSHOT"
}
That works great for adding the version to the /info endpoint, but I'd like to access it when the application starts and print it to the application log.
I'm hoping the values are exposed in some property value (similar to spring.profiles.active) or through a Java API. That way, I could do something like this:
public class MyApplication{
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
ConfigurableEnvironment environment = applicationContext.getEnvironment();
System.out.println(environment.getProperty("spring.fancy.path.to.info.version"));
}
}
Looking through the docs, I'm not finding a way to access these values easily in code. Has anyone else had luck with this?
To get exactly the same properties of an actuator endpoint that are exposed through the REST endpoints, you can inject in one of your classes an instance of the respective endpoint class. In your case, the "right" endpoint class would be the InfoEndpoint. There are analogous endpoint classes for metrics, health, etc.
The interface has changed a little between Spring Boot 1.5.x and Spring Boot 2.x. So the exact fully qualified class name or read method name may vary based on the Spring Boot version that you are using. In Boot 1.5.x, you can find most of the endpoints in the org.springframework.boot.actuate.endpoint package.
Roughly, this is how you could build a simple component for reading your version property (assuming that the name of the property inside the info endpoint is simply build.version):
#Component
public class VersionAccessor {
private final InfoEndpoint endpoint;
#Autowired
public VersionAccessor(InfoEndpoint endpoint) {
this.endpoint = endpoint;
}
public String getVersion() {
// Spring Boot 2.x
return String.valueOf(getValueFromMap(endpoint.info()));
// Spring Boot 1.x
return String.valueOf(getValueFromMap(endpoint.invoke()));
}
// the info returned from the endpoint may contain nested maps
// the exact steps for retrieving the right value depends on
// the exact property name(s). Here, we assume that we are
// interested in the build.version property
private Object getValueFromMap(Map<String, Object> info) {
return ((Map<String, Object>) info.get("build")).get("version");
}
}

spring boot actuator connect jmx programmatically

I'd like to use the shutdown endpoint of my Spring Boot 2.0.1 application from the command line. For that I have only added the spring-boot-starter-actuator to my Gradle file and enabled the shutdown endpoint in the configuration.
I also created a very simple tool that tries to connect via JMX to the running application.
Snippet:
String url = "service:jmx:rmi:///jndi/rmi://127.0.01:<which port?>/jmxrmi";
JMXServiceURL serviceUrl = new JMXServiceURL(url);
JMXConnectorFactory.connect(serviceUrl, null); <-- KAPOW!
JMX is working because I can use jconsole to connect locally. I just have no clue how to do it programmatically.
Any other attempts to explicitly set a port as mentioned here didn't work. Any hints?
It's probably easier to enable jolokia rather than using RMI; then you can simply
curl http://localhost:8080/actuator/jolokia/exec/org.springframework.boot:type=Admin,name=SpringApplication/shutdown
EDIT
If you prefer to use RMI, refer to the Spring Framework JMX Documentation.
Server app:
#SpringBootApplication
public class So50392589Application {
public static void main(String[] args) {
SpringApplication.run(So50392589Application.class, args);
}
#Bean
public RmiRegistryFactoryBean rmi() {
RmiRegistryFactoryBean rmi = new RmiRegistryFactoryBean();
rmi.setPort(1099);
return rmi;
}
#Bean
public ConnectorServerFactoryBean server() throws Exception {
ConnectorServerFactoryBean fb = new ConnectorServerFactoryBean();
fb.setObjectName("connector:name=rmi");
fb.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector");
return fb;
}
}
Client app:
#SpringBootApplication
public class JmxClient {
public static void main(String[] args) {
new SpringApplicationBuilder(JmxClient.class)
.web(WebApplicationType.NONE)
.run(args);
}
#Bean
public ApplicationRunner runner(MBeanServerConnection jmxConnector) {
return args -> {
jmxConnector.invoke(new ObjectName("org.springframework.boot:type=Admin,name=SpringApplication"),
"shutdown", new Object[0], new String[0]);
};
}
#Bean
public MBeanServerConnectionFactoryBean jmxConnector() throws Exception {
MBeanServerConnectionFactoryBean jmx = new MBeanServerConnectionFactoryBean();
jmx.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector");
return jmx;
}
}
There is a much simpler approach if you do not need to connect to the app remotely using the jcmd tool introduced in Java SE 7, and the Attach API introduced in Java SE 6.
I've written a blog post that explains this in detail. It's too big to simply copy-paste here, following is the link to the relevant section.
https://blog.asarkar.com/technical/grpc-kubernetes-spring/#jmx
This is not a duplicate answer because when the question was asked, this answer didn't exist. This answer has already been tailored to this question; let's not get trigger happy mods.

How to configure in Spring Boot 2 (w/ WebFlux) two ports for HTTP and HTTPS?

Can anybody tell me how 2 ports (for HTTP and HTTPS) can be configured when using Spring Boot 2 and WebFlux? Any hint is appreciated!
This isn't directly supported by Spring Boot 2 yet.
But, you may be able to get it to work in a couple of ways.
By default, Spring Boot WebFlux uses Netty. If you are already configured for ssl, then Spring Boot will start up and open port 8443 (or whatever you have configured).
Then, to add 8080, you can do:
#Autowired
HttpHandler httpHandler;
WebServer http;
#PostConstruct
public void start() {
ReactiveWebServerFactory factory = new NettyReactiveWebServerFactory(8080);
this.http = factory.getWebServer(this.httpHandler);
this.http.start();
}
#PreDestroy
public void stop() {
this.http.stop();
}
Which is a bit clunky since your https configuration is in one spot (application.yml) and your http configuration is in Java config, but I have tested this myself with a toy application. Not sure how robust of a solution it is, though.
Another option that may work is to try the equivalent of other suggestions, but use the reactive version of the class, which is TomcatReactiveWebServerFactory. I'm not aware of any way to provide more than one connector for it, but you could possibly override its getWebServer method:
#Bean
TomcatReactiveWebServerFactory twoPorts() {
return new TomcatReactiveWebServerFactory(8443) {
#Override
public WebServer getWebServer(HttpHandler httpHandler) {
// .. copy lines from parent class
// .. and add your own Connector, similar to how tutorials describe for servlet-based support
}
}
}
Also, a bit messy, and I have not tried that approach myself.
Of course, keep track of the ticket so you know when Spring Boot 2 provides official support.
Follow the instructions listed in the link provided by jojo_berlin (here's the link). Instead of using his EmbeddedTomcatConfiguration class though, use this below
#Configuration
public class TomcatConfig {
#Value("${server.http.port}")
private int httpPort;
#Bean
public ConfigurableServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
factory.addAdditionalTomcatConnectors(connector);
return factory;
}
}
Actually you can define a second connector as described here . So you can define a https connector as your default and an additional HTTP Connector

Spring Solr connection with Zookeeper using jaas.conf

I am migrating an existing java project to Spring Boot. It connects to Solr via Zookeeper.
It is throwing following error on startup.
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /clusterstate.json
This following configuration works on local, but on other environments Zookeeper requires some authentication (jaas.conf). How do I pass the jaas.conf to Spring Boot such that it can initialize SolrClient correctly?
#Configuration
#EnableSolrRepositories(basePackages = "com.search", multicoreSupport = true)
public class SolrConfig {
#Value("${spring.data.solr.zk-host}")
private String zkHost;
#Bean
public SolrClient solrClient() {
return new CloudSolrClient(zkHost);
}
#Bean
public SolrTemplate solrTemplate(SolrClient solrClient) throws Exception {
return new SolrTemplate(solrClient);
}
}
jaas.conf
Client {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="some_username"
password="some_password";
};
I tried passing credentials as environment properties of zkDigestUsername and zkDigestPassword. In this case there was no exception during Spring Boot startup, but subsequent inserts/reads to Solr is not working.
Any help would be appreciated. Thanks much!
I was able to fix the issue by providing jaas config using the following JVM parameter.
-Djava.security.auth.login.config=/etc/zookeeper/conf/jaas.conf

Spring Cloud Config Client connecting to RabbitMQ

I have been trying to set up a Spring Cloud Config Server/Client. I have been following a few different examples (1, 2). I have the client and server set up correctly and can successfully query localhost:8888/localhost:8080 to see the values in JSON format.
My question is whether Spring Boot will automatically detect these properties provided by Spring Cloud Config Server. For now I am just attempting to connect to a RabbitMQ instance on startup but have had no success despite not having any errors. It does not connect to Rabbit or create the queues/exchanges.
It works when I have an application.properties file locally with the following properties but I wish to get these setting through Spring Cloud Config from a GitHub repository.
spring.rabbitmq.host=178.61.47.---
spring.rabbitmq.port=5672
spring.rabbitmq.username=mqtt
spring.rabbitmq.password=mqtt
I have looked through the questions here/issues on GitHub but can't see anything relating to this.
Code for client class is below:
#EnableAutoConfiguration
#ComponentScan
#SpringBootApplication
public class ConfigRabbitApplication {
final static String queueName = "arduino-weather-queue";
#Autowired
RabbitTemplate rabbitTemplate;
#Bean
Queue queue() {
return new Queue(queueName, true);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("arduino-weather");
}
#Bean
TopicExchange exchange() {
return new TopicExchange("arduino-iot-exchange", true, false);
}
public static void main(String[] args) {
SpringApplication.run(ConfigRabbitApplication.class, args);
}
}
No, spring boot client is not aware that you want to fetch configuration from config-server. That is probably loaded when specific class in on a classpath, thats why you have to add org.springframework.cloud:spring-cloud-starter-config dependency. Its well described here: http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html#_client_side_usage
In case config-server is not on localhost:8888 you will also have to add:
spring.cloud.config.uri: http://myconfigserver.com
to your bootstrap.yml file ( its same as application.yml, just loaded earlier ).

Resources