Micronaut using AWS Secrets Manager to get properties to connect to Database (JPA)

172 Views Asked by At

I am having a bad time with Micronaut, that is my first project using it whereas I was using SpringBoot before and some things I could do easily on SprintBoot are not that easy with Micronaut.

The most painful issue is connecting with AWS Secrets Manager (SM) and getting properties to connect to the database. The infrastructure guys created one only entry on the SM to get all JDBC database configurations, like this:

entry --> values password: XXYYYXXYYY! dbname: tmtdb engine: postgres port: 5432 host: localhost username: postgres

All right to cope with that, I have written this code:

package config;

import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zaxxer.hikari.HikariConfig;
import io.micronaut.configuration.jdbc.hikari.HikariUrlDataSource;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import io.micronaut.context.annotation.Primary;
import io.micronaut.context.annotation.Requires;
import jakarta.inject.Singleton;
import lombok.Data;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.cfg.Environment;
import org.hibernate.SessionFactory;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

    /**
     * Class for programmatically connect to RDS database using keys from Secrets Manager.
     * @author rdinis
     * @since 2023-07-20
     */
    @Factory
    @Requires(env = "dev")
    @Data
    public class RDSDataSourceConfig {
        private String username = "postgres";
        private String password = System.getenv("DB_PASSWORD");
        private String host = "localhost";
        private String port = "5432";
        private String databaseName = "tmtdb";
    
        private final SecretsManagerClient secretsManagerClient;
    
        public RDSDataSourceConfig(SecretsManagerClient secretsManagerClient) {
            this.secretsManagerClient = secretsManagerClient;
            fetchSecrets();
        }
    
        @Primary
        @Singleton
        @Bean
        public DataSource dataSource() {
            final Configuration hibernateConfig = new Configuration();
            final HikariConfig config = new HikariConfig();
    
            System.out.println("Starting Datasource ...");
    
            config.setJdbcUrl("jdbc:postgresql://" + this.getHost() + ":" + this.getPort() + "/" + this.getDatabaseName());
            config.setDriverClassName("org.postgresql.Driver");
            config.setUsername(this.getUsername());
            config.setPassword(this.getPassword());
            config.setMaximumPoolSize(30);
            config.setAllowPoolSuspension(true);
    
            hibernateConfig.setProperty(Environment.CONNECTION_PROVIDER, "nz.govt.mpi.foc.tmt.config.DefaultConnectionProvider");
    
            System.out.println("Datasource started successfully...");
            return new HikariUrlDataSource(config);
        }
           
        public void fetchSecrets() {
            final var mapper = new ObjectMapper();
            final GetSecretValueRequest request;
            final GetSecretValueResponse response;
            final String secret;
            final JsonNode json;
    
            try {
                request = GetSecretValueRequest.builder()
                  .secretId(System.getenv("SST_RDS_secretArn_Database"))
                  .build();
                response = secretsManagerClient.getSecretValue(request);
                secret = response.secretString();
                json = mapper.readTree(secret);
                this.setHost(json.get("host").textValue());
                this.setPort(String.valueOf(json.get("port").intValue()));
                this.setDatabaseName(json.get("dbname").textValue());
                this.setUsername(json.get("username").textValue());
                this.setPassword(json.get("password").textValue());
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }
    }

The code is starting successfully, and reading the SM. However, the following error happens when calling a service:

 +16854ms Starting Datasource ...
|  +16856ms Datasource started successfully...
|  +16859ms 2023-08-21 00:27:57  INFO  HikariDataSource - HikariPool-1 - Starting...
|  +17172ms 2023-08-21 00:27:57  INFO  HikariDataSource - HikariPool-1 - Start completed.
|  +17527ms 2023-08-21 00:27:58  INFO  Version - HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
|  +17575ms 2023-08-21 00:27:58  WARN  ConnectionProviderInitiator - HHH000181: No appropriate connection provider encountered, assuming application will be supplying connections
|  +17579ms 2023-08-21 00:27:58  WARN  JdbcEnvironmentInitiator - HHH000342: Could not obtain connection to query metadata
|  +17580ms java.lang.UnsupportedOperationException: The application must supply JDBC connections
|  +17580ms     at org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl.getConnection(UserSuppliedConnectionProviderImpl.java:44) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:181) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:68) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:101) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:175) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:173) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:127) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:86) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:479) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:85) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at org.hibernate.boot.MetadataSources.buildMetadata(MetadataSources.java:202) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
|  +17580ms     at io.micronaut.configuration.hibernate.jpa.conf.AbstractHibernateFactory.buildMetadata(AbstractHibernateFactory.java:92) ~[micronaut-hibernate-jpa-4.7.2.jar:4.7.2]
|  +17580ms     at io.micronaut.configuration.hibernate.jpa.conf.SessionFactoryPerDataSourceFactory.buildMetadata(SessionFactoryPerDataSourceFactory.java:88) ~[micronaut-hibernate-jpa-4.7.2.jar:4.7.2]

Notice that the datasource is starging in the first log line: "Starting Datasource ...", and also it is finishing successfully: "Datasource started successfully...".

Also, notice that HikariDatasource is also starting (HikariPool-1 - Starting...) even if I started my own datasource - this can be part of the problem.

Unfortunately, I could not follow the documented way on Micronaut to get the Secrets by the AWS Secrets Manager (SM), because the infra #@#!@#! guy asked me to get the connection properties from only one SM entry :(.

Now what I want is a way to read that SM entry and make a connection to the database. Any kind of help is really welcome.

0

There are 0 best solutions below