Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring libraries pick up the ENV used by azure-core/SDK #25220

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,153 @@

package com.azure.spring.cloud.autoconfigure.context;

import com.azure.core.util.Configuration;
import com.azure.spring.cloud.autoconfigure.properties.AzureGlobalProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import static com.azure.core.util.Configuration.PROPERTY_AZURE_AUTHORITY_HOST;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_CLIENT_ID;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_CLIENT_SECRET;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_CLOUD;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_HTTP_LOG_DETAIL_LEVEL;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_PASSWORD;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_REQUEST_RETRY_COUNT;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_SUBSCRIPTION_ID;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_TENANT_ID;
import static com.azure.core.util.Configuration.PROPERTY_AZURE_USERNAME;

/**
* An EnvironmentPostProcessor to set spring.cloud.azure.* properties to Azure SDK global configuration.
* An EnvironmentPostProcessor to convert environment variables predefined by Azure Core and Azure SDKs to Azure Spring
* properties, and add a property source for them as well.
*/
public class AzureGlobalConfigurationEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {

public static final String CREDENTIAL_PREFIX = AzureGlobalProperties.PREFIX + ".credential.";
public static final String PROFILE_PREFIX = AzureGlobalProperties.PREFIX + ".profile.";
private static final Logger LOGGER = LoggerFactory.getLogger(AzureGlobalConfigurationEnvironmentPostProcessor.class);

@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}


enum AzureCoreEnvMapping {

clientId(PROPERTY_AZURE_CLIENT_ID, "credential.client-id"),

clientSecret(PROPERTY_AZURE_CLIENT_SECRET, "credential.client-secret"),

clientCertificatePath(PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH, "credential.client-certificate-path"),

username(PROPERTY_AZURE_USERNAME, "credential.username"),

password(PROPERTY_AZURE_PASSWORD, "credential.password"),

tenantId(PROPERTY_AZURE_TENANT_ID, "profile.tenant-id"),

subscriptionId(PROPERTY_AZURE_SUBSCRIPTION_ID, "profile.subscription-id"),

azureCloud(PROPERTY_AZURE_CLOUD, "profile.cloud"),

authorityHost(PROPERTY_AZURE_AUTHORITY_HOST, "profile.environment.active-directory-endpoint"),

// TODO (xiada): PROPERTY_AZURE_LOG_LEVEL, how to set this to env?

httpLogLevel(PROPERTY_AZURE_HTTP_LOG_DETAIL_LEVEL, "client.logging.level"),

maxRetry(PROPERTY_AZURE_REQUEST_RETRY_COUNT, "retry.max-attempts");

// TODO (xiada): we can't configure http at global level:
// PROPERTY_AZURE_REQUEST_CONNECT_TIMEOUT,
// PROPERTY_AZURE_REQUEST_WRITE_TIMEOUT,
// PROPERTY_AZURE_REQUEST_RESPONSE_TIMEOUT,
// PROPERTY_AZURE_REQUEST_READ_TIMEOUT,
// PROPERTY_NO_PROXY

// TODO (xiada): how to set this proxy?
// proxy(PROPERTY_HTTP_PROXY, PROPERTY_HTTPS_PROXY)


private final String coreEnvName;
private final String springPropertyName;
private final Function<String, String> converter;

AzureCoreEnvMapping(String coreEnvName, String springPropertyName) {
this(coreEnvName, springPropertyName, Function.identity());
}

AzureCoreEnvMapping(String coreEnvName, String springPropertyName, Function<String, String> converter) {
this.coreEnvName = coreEnvName;
this.springPropertyName = "spring.cloud.azure." + springPropertyName;
this.converter = converter;
}
}

enum AzureSdkEnvMapping {
keyVaultSecretEndpoint("AZURE_KEYVAULT_ENDPOINT", "keyvault.secret.endpoint"),
keyVaultCertificateEndpoint("AZURE_KEYVAULT_ENDPOINT", "keyvault.certificate.endpoint");

private final String sdkEnvName;
private final String springPropertyName;
private final Function<String, String> converter;

AzureSdkEnvMapping(String sdkEnvName, String springPropertyName) {
this(sdkEnvName, springPropertyName, Function.identity());
}

AzureSdkEnvMapping(String sdkEnvName, String springPropertyName, Function<String, String> converter) {
this.sdkEnvName = sdkEnvName;
this.springPropertyName = "spring.cloud.azure." + springPropertyName;
this.converter = converter;
}
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
final Configuration globalConfiguration = Configuration.getGlobalConfiguration();

PropertyMapper propertyMapper = PropertyMapper.get().alwaysApplyingWhenNonNull();
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "client-id"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_CLIENT_ID, p));
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "client-secret"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_CLIENT_SECRET, p));
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "client-certificate-path"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_CLIENT_CERTIFICATE_PATH, p));
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "username"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_USERNAME, p));
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "password"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_PASSWORD, p));
propertyMapper.from(environment.getProperty(CREDENTIAL_PREFIX + "managed-identity-client-id"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_CLIENT_ID, p));

propertyMapper.from(environment.getProperty(PROFILE_PREFIX + "tenant-id"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_TENANT_ID, p));
propertyMapper.from(environment.getProperty(PROFILE_PREFIX + "environment.active-directory-endpoint"))
.to(p -> globalConfiguration.put(PROPERTY_AZURE_AUTHORITY_HOST, p));
Map<String, Object> source = new HashMap<>();

for (AzureCoreEnvMapping mapping : AzureCoreEnvMapping.values()) {
if (environment.containsProperty(mapping.coreEnvName)) {
String property = environment.getProperty(mapping.coreEnvName);
source.put(mapping.springPropertyName, mapping.converter.apply(property));
}
}

for (AzureSdkEnvMapping mapping : AzureSdkEnvMapping.values()) {
if (environment.containsProperty(mapping.sdkEnvName)) {
String property = environment.getProperty(mapping.sdkEnvName);
source.put(mapping.springPropertyName, mapping.converter.apply(property));
}
}

if (!source.isEmpty()) {
environment.getPropertySources().addLast(new AzureCoreEnvPropertySource("Azure Core/SDK", source));
} else {
LOGGER.debug("No env predefined by Azure Core/SDKs are set, skip adding the AzureCoreEnvPropertySource.");
}
}

private static class AzureCoreEnvPropertySource extends MapPropertySource {

/**
* Create a new {@code MapPropertySource} with the given name and {@code Map}.
*
* @param name the associated name
* @param source the Map source (without {@code null} values in order to get consistent {@link #getProperty}
* and {@link
* #containsProperty} behavior)
*/
AzureCoreEnvPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.azure.messaging.eventhubs.CheckpointStore;
import com.azure.spring.cloud.autoconfigure.condition.ConditionalOnAnyProperty;
import com.azure.spring.cloud.autoconfigure.eventhubs.properties.AzureEventHubProperties;
import com.azure.spring.core.properties.AzurePropertiesUtils;
import com.azure.spring.eventhubs.core.EventHubProcessorContainer;
import com.azure.spring.eventhubs.core.EventHubsTemplate;
import com.azure.spring.eventhubs.core.processor.DefaultEventHubNamespaceProcessorFactory;
Expand Down Expand Up @@ -48,6 +49,7 @@ public class AzureEventHubMessagingAutoConfiguration {
@ConditionalOnMissingBean
public NamespaceProperties eventHubNamespaceProperties(AzureEventHubProperties properties) {
NamespaceProperties namespaceProperties = new NamespaceProperties();
AzurePropertiesUtils.copyAzureCommonProperties(properties, namespaceProperties);
BeanUtils.copyProperties(properties, namespaceProperties);
return namespaceProperties;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public class HttpClientCP extends ClientCP implements ClientAware.HttpClient {
private Duration writeTimeout;
private Duration responseTimeout;
private Duration readTimeout;
private Duration connectTimeout;
private Integer maximumConnectionPoolSize;
private Duration connectionIdleTimeout;

@Override
public Duration getWriteTimeout() {
Expand Down Expand Up @@ -42,4 +45,28 @@ public Duration getReadTimeout() {
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}

public Duration getConnectTimeout() {
return connectTimeout;
}

public void setConnectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
}

public Integer getMaximumConnectionPoolSize() {
return maximumConnectionPoolSize;
}

public void setMaximumConnectionPoolSize(Integer maximumConnectionPoolSize) {
this.maximumConnectionPoolSize = maximumConnectionPoolSize;
}

public Duration getConnectionIdleTimeout() {
return connectionIdleTimeout;
}

public void setConnectionIdleTimeout(Duration connectionIdleTimeout) {
this.connectionIdleTimeout = connectionIdleTimeout;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.autoconfigure.context;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;

import java.util.Properties;

import static com.azure.core.util.Configuration.PROPERTY_AZURE_CLIENT_ID;

class AzureGlobalConfigurationEnvironmentPostProcessorTest {

@Test
void springPropertyShouldHaveValueIfAzureCoreEnvSet() {
PropertiesPropertySource propertiesPropertySource = buildTestProperties(PROPERTY_AZURE_CLIENT_ID, "test-client-id");

ConfigurableEnvironment environment = getEnvironment(propertiesPropertySource);

Assertions.assertEquals("test-client-id", environment.getProperty(PROPERTY_AZURE_CLIENT_ID));
Assertions.assertEquals("test-client-id", environment.getProperty("spring.cloud.azure.credential.client-id"));
}

@Test
void springPropertyShouldHaveValueIfAzureSdkEnvSet() {
PropertiesPropertySource propertiesPropertySource = buildTestProperties("AZURE_KEYVAULT_ENDPOINT", "test-endpoint");

ConfigurableEnvironment environment = getEnvironment(propertiesPropertySource);

Assertions.assertEquals("test-endpoint", environment.getProperty("AZURE_KEYVAULT_ENDPOINT"));
Assertions.assertEquals("test-endpoint", environment.getProperty("spring.cloud.azure.keyvault.secret.endpoint"));
Assertions.assertEquals("test-endpoint", environment.getProperty("spring.cloud.azure.keyvault.certificate.endpoint"));
}

@Test
void azureCoreEnvShouldNotBeTakenIfSpringPropertiesSet() {
Properties properties = new Properties();
properties.put(PROPERTY_AZURE_CLIENT_ID, "core-client-id");
properties.put("spring.cloud.azure.credential.client-id", "spring-client-id");
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("test-properties", properties);

ConfigurableEnvironment environment = getEnvironment(propertiesPropertySource);

Assertions.assertEquals("core-client-id", environment.getProperty(PROPERTY_AZURE_CLIENT_ID));
Assertions.assertEquals("spring-client-id", environment.getProperty("spring.cloud.azure.credential.client-id"));
}

@Test
void azureSdkEnvShouldNotBeTakenIfSpringPropertiesSet() {
Properties properties = new Properties();
properties.put("AZURE_KEYVAULT_ENDPOINT", "sdk-endpoint");
properties.put("spring.cloud.azure.keyvault.secret.endpoint", "spring-endpoint");
PropertiesPropertySource propertiesPropertySource = new PropertiesPropertySource("test-properties", properties);

ConfigurableEnvironment environment = getEnvironment(propertiesPropertySource);

Assertions.assertEquals("sdk-endpoint", environment.getProperty("AZURE_KEYVAULT_ENDPOINT"));
Assertions.assertEquals("spring-endpoint", environment.getProperty("spring.cloud.azure.keyvault.secret.endpoint"));
}

private PropertiesPropertySource buildTestProperties(String key, String value) {
Properties properties = new Properties();
properties.put(key, value);
return new PropertiesPropertySource("test-properties", properties);
}


private ConfigurableEnvironment getEnvironment(PropertiesPropertySource propertiesPropertySource) {
return getEnvironment(propertiesPropertySource, null);
}

private ConfigurableEnvironment getEnvironment(PropertiesPropertySource propertiesPropertySource,
EnvironmentPostProcessor environmentPostProcessor) {
SpringApplication springApplication = new SpringApplicationBuilder()
.sources(AzureGlobalConfigurationEnvironmentPostProcessorTest.class)
.web(WebApplicationType.NONE).build();

ConfigurableApplicationContext context = springApplication.run();

if (propertiesPropertySource != null) {
context.getEnvironment().getPropertySources().addFirst(propertiesPropertySource);
}

if (environmentPostProcessor == null) {
environmentPostProcessor = new AzureGlobalConfigurationEnvironmentPostProcessor();
}

environmentPostProcessor.postProcessEnvironment(context.getEnvironment(), springApplication);

ConfigurableEnvironment configurableEnvironment = context.getEnvironment();
context.close();

return configurableEnvironment;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,19 @@ interface Client {
* Interface to be implemented by classes that wish to describe a http based client sdk.
*/
interface HttpClient extends Client {

Duration getWriteTimeout();

Duration getResponseTimeout();

Duration getReadTimeout();

Duration getConnectTimeout();

Integer getMaximumConnectionPoolSize();

Duration getConnectionIdleTimeout();

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface Proxy {

String getHostname();

int getPort();
Integer getPort();

String getAuthenticationType();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import com.azure.core.http.ProxyOptions;
import com.azure.spring.core.properties.proxy.HttpProxyProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

Expand All @@ -15,11 +17,13 @@
*/
public final class AzureHttpProxyOptionsConverter implements Converter<HttpProxyProperties, ProxyOptions> {

private static final Logger LOGGER = LoggerFactory.getLogger(AzureHttpProxyOptionsConverter.class);
public static final AzureHttpProxyOptionsConverter HTTP_PROXY_CONVERTER = new AzureHttpProxyOptionsConverter();

@Override
public ProxyOptions convert(HttpProxyProperties proxyProperties) {
if (!StringUtils.hasText(proxyProperties.getHostname())) {
if (!StringUtils.hasText(proxyProperties.getHostname()) || proxyProperties.getPort() == null) {
LOGGER.debug("Proxy hostname or port is not set.");
return null;
}

Expand Down
Loading