6
votes

I'm attempting to build code into our base pom which autoconfigures Spring Cloud Config server lookup through Eureka. We're doing this to avoid templating .yml properties for developers building microservices. For example, we want to java config all behavior triggered from these properties:

spring:
  application:
    name: MyMicroservice
  cloud:
    config:
      enabled: true
    server:
      prefix: /diagnostics/admin/config
    failFast: true
    discovery:
      enabled: true
      serviceId: echo

management:
  context-path: /diagnostics/admin

eureka:
  password: password
  client:
    serviceUrl:
      defaultZone: http://user:${eureka.password}@localhost:8761/eureka/
  instance:
    leaseRenewalIntervalInSeconds: 10
    statusPageUrlPath: /diagnostics/admin/info
    healthCheckUrlPath: /diagnostics/admin/health

After much experimenting, the following approach mostly works except for the Eureka-discovered config server (resulting in no overridden config properties):

@Order(-1)
public class AdditionalBootstrapPropertySourceLocator implements PropertySourceLocator {

    @Override
    public PropertySource<?> locate(Environment environment) {
        Map<String, Object> theBootstrapYmlConfig = new HashMap<>();
        theBootstrapYmlConfig.put("spring.cloud.config.enabled", new Boolean(true));
        theBootstrapYmlConfig.put("spring.cloud.config.server.prefix", "/diagnostics/admin/config");
        theBootstrapYmlConfig.put("spring.cloud.config.failFast", new Boolean(true));
        theBootstrapYmlConfig.put("spring.cloud.config.discovery.enabled", new Boolean(true));
        theBootstrapYmlConfig.put("spring.cloud.config.discovery.serviceId", "echo");

        theBootstrapYmlConfig.put("management.context-path", "/diagnostics/admin");

        theBootstrapYmlConfig.put("eureka.client.serviceUrl.defaultZone", "http://user:password@localhost:8761/eureka/");
        theBootstrapYmlConfig.put("eureka.instance.leaseRenewalIntervalInSeconds", new Integer(10));
        theBootstrapYmlConfig.put("eureka.instance.statusPageUrlPath", "/diagnostics/admin/info");
        theBootstrapYmlConfig.put("eureka.instance.healthCheckUrlPath", "/diagnostics/admin/health");

        return new MapPropertySource("myExtraBootstrap", theBootstrapYmlConfig);    
    }    
}

And I seem to need this Bean as well:

@ConditionalOnWebApplication
@Configuration
@Import(EurekaClientAutoConfiguration.class)
public class WorkfrontDiscoveryClientConfigServiceBootstrapConfiguration {

    @Bean
    @ConditionalOnClass({ DiscoveryClient.class, ConfigServicePropertySourceLocator.class })
    @ConditionalOnMissingBean
    DiscoveryClientConfigServiceBootstrapConfiguration discoveryClientConfigServiceBootstrapConfiguration() {
        DiscoveryClientConfigServiceBootstrapConfiguration discoveryClientConfigServiceBootstrapConfiguration =
                 new DiscoveryClientConfigServiceBootstrapConfiguration();
        return discoveryClientConfigServiceBootstrapConfiguration;
    }

}

Finally, I put both into spring.factories to ensure they are constructed. The problem is that the PropertySourceLocator is never used to construct the call within ConfigServicePropertySourceLocator to retrieve the properties. No matter what I do, I cant seem to match the behaviors that specifying the properties within bootstrap.yml would produce.

Edit 4 days later

The key factor (and restriction) here is the ability to look up the config server through Eureka. In the current spring cloud release (1.0.2), the property source is retrieved and constructed too early in the spring initialization cycle for the config-lookup-through-eureka java property source config I have above. Plus if the Eureka server is slow or not available at bootstrap startup time, the Config server property source is never reconstructed when Eureka finally comes up. This in my mind is a bug.

I solved this all by eliminating the concept of looking up the config server through Eureka, and requiring this minimum config in bootstrap.yml:

spring:
  application:
    name: MyMicroservice
  cloud:
    config:
      uri: http://localhost:8888/diagnostics/admin/config

eureka:
  client:
    serviceUrl:
      defaultZone: http://user:password@localhost:8761/eureka/

and then setting the rest in the java AdditionalBootstrapPropertySourceLocator

Edit 30 days later

Java-configing bootstrap properties continues to be a challenge. I'm doing this because I'm developing a framework without templating or code generation (the premise of spring boot). I've added spring-retry to the mix and client-to-config gets retried but re-registration to Eureka does not. This is why Eureka-first had to be abandoned for me. I'd put my vote in for integrating spring-retry into the Eureka registration process so I can go back to Eureka-first for my framework. Still on Spring Cloud 1.0.2.

Edit 100 days later

Update for where we ended up. Continuing along our mantra of avoiding property templating, enforcing policies and practices within code .. and continuing without a Eureka-first concept, we abandoned PropertySourceLocator and simply used a SpringApplicationRunListener as follows:

public class OurFrameworkProperties implements SpringApplicationRunListener {
  :
  public void started() {
    if (TestCaseUtils.isRunningFromTestCase()) {
      System.setProperty("spring.cloud.config.failFast", "false");
      System.setProperty("spring.cloud.config.enabled", "false");
      System.setProperty("eureka.client.enabled", "false");
    } else {
      // set production values same way
    }
  }
}

A caution that this started() actually gets called twice inside the spring code (once not passing any program arguments btw) everytime your Spring application runs or gets an Actuator refresh().

2
Is it possible you could just add a @PropertySource? All that manual schlepping of map keys looks fragile to me anway. - Dave Syer
The point was to java config this automatically for developers who specify our base pom as their pom's parent. We didn't want to rely on tribal knowledge for developers to remember to add a file called 'bootstrap.yml' or any other file to their classpath or working directory. This way they'd be registered automatically with Eureka, get our standardized config overrides from the config server automatically, and share a common management context path. The code was templated from projects.spring.io/spring-cloud/… . - RubesMN
After investigation, there seems to be a bug. Updates Eureka provides as to the Config server URI does not trigger the restTemplate call to resolve the remote property source within ConfigServicePropertySourceLocator.locate(). If I go back to configuring using bootstrap.yml (and remove failFast), if Eureka is not immediately available by the time context init finished -> the config server property source overrides are never created - RubesMN
Eureka might not be available I get that. Does it work when you /refresh in that case? - Dave Syer
BTW you don't have to rely on tribal knowledge to include a properties file - that's the point of using @ProeprtySource. - Dave Syer

2 Answers

0
votes

If your PropertySourceLocator is listed inspring.factories (I assume as a BootstrapConfiguration) then it needs to be a @Component (or maybe even a @Configuration).

0
votes

you have to set this properties in the boostrap.properties

eureka.instance.metadataMap.configPath: /your-app-name

and comment this one

#spring.cloud.config.uri=http://localhost:8888/

and obviously it must be also this

eureka.client.serviceUrl.defaultZone: ${EUREKA_URI:http://localhost:8761/eureka}
eureka.client.instance.preferIpAddress: true

according with the documentation https://cloud.spring.io/spring-cloud-config/multi/multi__spring_cloud_config_client.html#discovery-first-bootstrap