Open RajeevMasseyTR opened 1 month ago
@pgressa , @sdelamo , @graemerocher
attach a full example that reproduces the issue with steps to reproduce
Sure @graemerocher , let's walk through setting up a multi-module project where the api-module
depends on the common-module
. We will also ensure that the api-module
correctly implements the common-module
and has the required bootstrap.yml
configuration.
Your project structure will look like this:
root-project │ ├── common-module │ └── src/main/java/com/example/common │ └── AwsParameterStoreConfigurationLoader.java │ └── build.gradle │ ├── api-module │ └── src/main/java/com/example/api │ └── Application.java │ └── src/main/resources │ └── bootstrap.yml │ └── build.gradle │ ├── settings.gradle └── build.gradle
settings.gradle
rootProject.name = 'root-project'
include 'common-module', 'api-module'
build.gradle
plugins {
id 'java'
}
subprojects {
apply plugin: 'java'
sourceCompatibility = '17'
targetCompatibility = '17'
repositories {
mavenCentral()
}
}
common-module/build.gradle
dependencies {
implementation "io.micronaut:micronaut-runtime"
implementation "io.micronaut.aws:micronaut-aws-distributed-configuration:4.2.1"
annotationProcessor "io.micronaut:micronaut-inject-java"
annotationProcessor "io.micronaut:micronaut-validation"
}
compileJava {
options.annotationProcessorPath = configurations.annotationProcessor
}
common-module/src/main/java/com/example/common/AwsParameterStoreConfigurationLoader.java
@BootstrapContextCompatible
@Singleton
@Requires(beans = {SSMSecretManager.class, JsonMapper.class})
@Requires(property = "aws.ssm.config.path")
@Requires(property = "aws.ssm.config.description", defaultValue = "AWS Configuration Client")
public class AwsParameterStoreConfigurationLoader implements ConfigurationClient {
private static final Logger LOG = LoggerFactory.getLogger(AwsParameterStoreConfigurationLoader.class);
public static final String RPC_AWS_SSM_CONFIGURATION_NAME = "aws-env";
@Property(name = "aws.ssm.config.description", defaultValue = "AWS Configuration Client")
private String description;
@Property(name = "aws.ssm.config.path")
private String configurationParameterStorePath;
private final SSMSecretManager ssmSecretManager;
private final JsonMapper jsonMapper;
private final ApplicationContext context;
private final ConcurrentHashMap<String, Object> cache;
AwsParameterStoreConfigurationLoader(SSMSecretManager ssmSecretManager,
JsonMapper jsonMapper,
ApplicationContext context) {
this.ssmSecretManager = ssmSecretManager;
this.jsonMapper = jsonMapper;
this.context = context;
this.cache = new ConcurrentHashMap<>();
}
@PostConstruct
private void init() {
loadConfiguration();
}
@Override
public Publisher<PropertySource> getPropertySources(Environment environment) {
return Mono.just(getCustomPropertySource());
}
@Override
public @NonNull String getDescription() {
return description;
}
private Object getProperty(String key) {
return cache.computeIfAbsent(key, k -> {
try {
loadConfiguration();
return cache.get(k);
} catch (Exception e) {
LOG.error("Error fetching configuration from SSM for key: " + key, e);
throw new RuntimeException("Error fetching configuration from SSM for key: " + key, e);
}
});
}
private void loadConfiguration() {
try {
if (cache.isEmpty()) {
String jsonConfig = ssmSecretManager.getSecretValue(configurationParameterStorePath);
Map<String, Object> configMap = jsonMapper.readValue(jsonConfig, Map.class);
configMap.forEach(cache::put);
PropertySource resolvedPropertySource = PropertySource.of(AWS_SSM_CONFIGURATION_NAME, configMap, PropertySource.PropertyConvention.ENVIRONMENT_VARIABLE);
context.getEnvironment().addPropertySource(resolvedPropertySource);
context.getEnvironment().refresh();
verifyConfiguration(configMap.keySet());
}
} catch (Exception e) {
throw new RuntimeException("Error loading configuration from SSM", e);
}
}
private void verifyConfiguration(Set<String> requiredProperties) {
var missingProperties = requiredProperties.stream().filter(property -> !context.getEnvironment().containsProperty(property)).collect(Collectors.toList());
if (!missingProperties.isEmpty()) {
String errorMessage = "The following required properties are missing: " + String.join(", ", missingProperties);
throw new IllegalStateException(errorMessage);
}
LOG.info("All required properties have been successfully loaded and verified.");
}
private PropertySource getCustomPropertySource() {
if (cache.isEmpty()) {
loadConfiguration();
verifyConfiguration(cache.keySet());
}
return PropertySource.of(AWS_SSM_CONFIGURATION_NAME, cache.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)), PropertySource.PropertyConvention.ENVIRONMENT_VARIABLE);
}
}
api-module/build.gradle
dependencies {
implementation project(':common-module')
implementation "io.micronaut:micronaut-runtime"
annotationProcessor "io.micronaut:micronaut-inject-java"
annotationProcessor "io.micronaut:micronaut-validation"
}
compileJava {
options.annotationProcessorPath = configurations.annotationProcessor
}
api-module/src/main/java/com/example/api/Application.java
package com.example.api;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class);
}
}
api-module/src/main/resources/bootstrap.yml
yaml
micronaut:
config-client:
enabled: true
aws:
ssm:
config:
path: "/your/ssm/parameter/path"
description: "AWS Configuration Client"
Verifying the logs to ensure that the AwsParameterStoreConfigurationLoader
is invoked and the configuration is loaded from AWS SS. It showed that DistributedPropertySourceLocator was not able to find AwsParameterStoreConfigurationLoader configration client
.{"timestamp":"2024-07-18 22:48:43.942","level":"INFO","thread":"main","logger":"io.micronaut.context.DefaultApplicationContext$RuntimeConfiguredEnvironment","message":"Established active environments: [ec2]","context":"default"}
{"timestamp":"2024-07-18 22:48:43.995","level":"INFO","thread":"main","logger":"io.micronaut.context.DefaultApplicationContext$BootstrapEnvironment","message":"Established active environments: [ec2]","context":"default"}
{"timestamp":"2024-07-18 22:48:54.942","level":"INFO","thread":"main","logger":"io.micronaut.context.DefaultBeanContext","message":"Reading bootstrap environment configuration","context":"default"}
{"timestamp":"2024-07-18 22:50:47.033","level":"INFO","thread":"main","logger":"io.micronaut.discovery.client.config.DistributedPropertySourceLocator","message":"Resolved 0 configuration sources from client: compositeConfigurationClient()","context":"default"}
I'm not asking for a walkthrough, I am asking you to upload an example with steps to reproduce
Is the snakeyaml
dependency declared anywhere?
I think this https://docs.micronaut.io/4.5.3/guide/#config also applies for the bootstrap config.
@graemerocher https://github.com/rajeev-massey/demo-micronaut . Almost the same steps followed minus linking it to the SSM instead manually putting the JSON
the common module needs to declare:
implementation("io.micronaut.discovery:micronaut-discovery-client")
we should probably document this better.
Issue description
Hi I have been dealing with this issue for a long time and need a solution. This code is in the common module
also have this dependency implementation "io.micronaut.aws:micronaut-aws-distributed-configuration" in the common module. Now another module called the api-module implements the common project and has the bootstrap.yml with configurations :
But even then, No Distributed Property Source has been identified. No property has been injected