Override variables and beans after they are read or created

66 views Asked by At

I have this in my-microservice-configuration.yml in my configuration service

spring:
  servlet:
    multipart:
      max-file-size: 5MB
      max-request-size: 10MB
  datasource:
    url: jdbc:sqlserver://www.xxx.yyy.zzz:1433;databaseName=my_database_name;encrypt=true;trustServerCertificate=true;
    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
    testWhileIdle: true
    testOnBorrow: true
    ...

In my Hashicorp Vault Server I have the secrets:

{
    "request_id": "193255e4-e7d3-781b-70fa-bb9ce516a930",
    "lease_id": "",
    "lease_duration": 2764800,
    "renewable": false,
    "data": {
        "spring.datasource.password": "myDBPassword",
        "spring.datasource.username": "myDBUser"
    },
    "warnings": null
}

Now, I have this Component that execute this method after all environments variables has been read.

@Component
@RequiredArgsConstructor
@Slf4j
public class RunAfterStartupUtil {

    @EventListener(ApplicationReadyEvent.class)
    public void runAfterStartup() {
        log.info("Microservice started and apparently ready to receive requests.");
    }
}

I read this article https://www.baeldung.com/spring-boot-configure-data-source-programmatic with this code:

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource getDataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("org.h2.Driver");
        dataSourceBuilder.url("jdbc:h2:mem:test");
        dataSourceBuilder.username("SA");
        dataSourceBuilder.password("");
        return dataSourceBuilder.build();
    }
}

QUESTION:

After my microservice has initialized everything, I need overwrite directly in my microservice (can be in the method runAfterStartup()), the variables (both from the Vault Server and the Server Config) and/or components created to point to another database, with other credentials. And Obviously use them.

NOTE: I can't disable the config server nor vault server because I'm using a lot of other variables of them.

How could I do it?

1

There are 1 answers

0
devatherock On

Is there a specific reason you need to initialize the DataSource bean first pointing to H2 DB and then switch it to the desired DB? If there isn't, you can directly initialize the bean pointing to your required DB(SQLServer) with credentials from vault, which can be injected using spring-cloud-config-server.

I used Postgres for my test, but the config required should be similar for SQLServer as well. All the config and secrets added under the prefix spring.datasource can be accessed via a DataSourceProperties bean which can then be used to initialize the DataSource. Below are all the changes I made:

Gradle dependencies:

    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
    implementation group: 'org.postgresql', name: 'postgresql'
    implementation(platform(group: 'org.springframework.cloud', name: 'spring-cloud-dependencies', version: '2023.0.0'))
    implementation group: 'org.springframework.cloud', name: 'spring-cloud-config-server'
    implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-bootstrap'

application.yml:

spring:
  datasource:
    driverClassName: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/testdb

bootstrap.yml: (Contains config for spring cloud, will be read by spring-cloud-starter-bootstrap)

spring:
  profiles:
    active: vault
  cloud:
    config:
      name: spring-boot-demo
      failFast: true
      server:
        bootstrap: true
        vault:
          host: localhost
          port: 8200
          token: my-vault-token-id
          kvVersion: 2

Bean initialization:

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    
    @Bean
    public DataSource getDataSource(DataSourceProperties dataSourceProperties) {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName(dataSourceProperties.getDriverClassName());
        dataSourceBuilder.url(dataSourceProperties.getUrl());
        dataSourceBuilder.username(dataSourceProperties.getUsername());
        dataSourceBuilder.password(dataSourceProperties.getPassword());
        return dataSourceBuilder.build();
    }
}

I added the secrets to my vault under /secret/data/spring-boot-demo, with spring-boot-demo being the value of spring.cloud.config.name specified in bootstrap.yml. I did notice that my secret values in vault exist under a nested data field, probably because the vault was using KV secrets engine v2. The secret value is below:

{
    "request_id": "f228f9a3-35c5-c094-afd8-80d4d9645fff",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
        "data": {
            "spring.datasource.password": "testpwd",
            "spring.datasource.username": "testuser"
        },
        "metadata": {
            "created_time": "2024-01-31T02:22:12.979975179Z",
            "custom_metadata": null,
            "deletion_time": "",
            "destroyed": false,
            "version": 1
        }
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null
}

You can find all the changes I made on github