Spring Boot (2.5.9) has problem to access password from the GCP Secret Manager using spring-cloud-gcp-starter-secretmanager
ver 2.0.8 throwing error
AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultFeignClientConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.google.protobuf.ByteString$LiteralByteString] to type [java.lang.String]
for a passsword declared in the application.properties as
webservices.security.basic.user.password=${sm://my-password}
when I will replace it with regular string or even env variable it will work fine.
Failing part of the code looks like:
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import feign.Retryer;
import feign.auth.BasicAuthRequestInterceptor;
import feign.codec.ErrorDecoder;
/**
* Default feign client configuration. Includes retry policies, basic auth user name and password, and HTTP status decoder.
* @author Greg Meyer
* @since 6.0
*/
public class DefaultFeignClientConfiguration
{
@Value("${webservices.retry.backoff.multiplier:3}")
protected double backoffMultiplier;
@Value("${webservices.retry.backoff.initialBackoffInterval:100}")
protected long initialBackoffInterval;
@Value("${webservices.retry.backoff.maxInterval:20000}")
protected long maxInterval;
@Value("${webservices.security.basic.user.name:}")
protected String user;
@Value("${webservices.security.basic.user.password:}")
protected String pass;
/**
* Creates an instance of the a the default HTTP status translator.
* @return An instance of the a the default HTTP status translator
*/
@Bean
public ErrorDecoder feignClientErrorDecoder()
{
return new DefaultErrorDecoder();
}
/**
* Creates an instance of BasicAuth interceptor configured with a username and password. This bean is only created if the
* "webservices.security.basic.user.name" property is set.
* @return An instance of BasicAuth interceptor configured with a username and password
*/
@Bean
@ConditionalOnProperty(name="webservices.security.basic.user.name", matchIfMissing=false)
public BasicAuthRequestInterceptor basicAuthRequestInterceptor()
{
return new BasicAuthRequestInterceptor(user, pass);
}
/**
* Creates an instance of a back off policy used in conjuntion with the retry policy.
* @return An instance of a back off policy
*/
@Bean
public LoadBalancedRetryFactory backOffPolciyFactory()
{
return new LoadBalancedRetryFactory()
{
@Override
public BackOffPolicy createBackOffPolicy(String service)
{
final ExponentialBackOffPolicy backoffPolicy = new ExponentialBackOffPolicy();
backoffPolicy.setMultiplier(backoffMultiplier);
backoffPolicy.setInitialInterval(initialBackoffInterval);
backoffPolicy.setMaxInterval(maxInterval);
return backoffPolicy;
}
};
}
/**
* Creates a default http retry policy.
* @return A default http retry policy.
*/
@Bean
public Retryer retryer()
{
/*
* Default retryer config
*/
return new Retryer.Default(200, 1000, 5);
}
}
Any thoughts?
The problem is that most likely, Feign autoconfiguration happens early on, before GcpSecretManagerEnvironmentPostProcessor
had a chance to run and introduce ByteString
converters.
Basically, the solution that works is to implement a Custom Converter which implements the Generic Conversion Service and register the Converter in the main method before calling SpringApplication.run
.
public static void main(String[] args)
{
((DefaultConversionService)DefaultConversionService.getSharedInstance()).addConverter(new CustomConverter());
SpringApplication.run(STAApplication.class, args);
}
and custom converter:
@Component
public class CustomConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return Collections.singleton(new ConvertiblePair(ByteString.class, String.class));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType,
TypeDescriptor targetType) {
if (sourceType.getType() == String.class) {
return source;
}
try {
source = ((ByteString) source).toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return source;
}
}