1

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?

JackTheKnife
  • 3,795
  • 8
  • 57
  • 117

1 Answers1

0

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;
    }
}
JackTheKnife
  • 3,795
  • 8
  • 57
  • 117