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

Allow quarkus.application.name to be overridden for Spring Cloud Config Server #41035

Merged
merged 1 commit into from
Jun 9, 2024
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 @@ -36,6 +36,13 @@ public interface SpringCloudConfigClientConfig {
@WithDefault("http://localhost:8888")
String url();

/**
* Name of the application on Spring Cloud Config server.
* Could be a list of names to load multiple files (value separated by a comma)
*/
@WithDefault("${quarkus.application.name:}")
String name();

/**
* The label to be used to pull remote configuration properties.
* The default is set on the Spring Cloud Config Server
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
import org.jboss.logging.Logger;

import io.quarkus.arc.runtime.appcds.AppCDSRecorder;
import io.quarkus.runtime.util.StringUtil;
import io.quarkus.spring.cloud.config.client.runtime.Response.PropertySource;
import io.smallrye.config.ConfigSourceContext;
import io.smallrye.config.ConfigSourceFactory.ConfigurableConfigSourceFactory;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.common.MapBackedConfigSource;

public class SpringCloudConfigClientConfigSourceFactory
Expand All @@ -39,10 +39,10 @@ public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext context
return sources;
}

ConfigValue applicationName = context.getValue("quarkus.application.name");
if (applicationName == null || applicationName.getValue() == null) {
String applicationName = config.name();
if (StringUtil.isNullOrEmpty(applicationName)) {
log.warn(
"No attempt will be made to obtain configuration from the Spring Cloud Config Server because the application name has not been set. Consider setting it via 'quarkus.application.name'");
"No attempt will be made to obtain configuration from the Spring Cloud Config Server because the application name has not been set. Consider setting it via 'quarkus.spring-cloud-config.name'");
return sources;
}

Expand All @@ -58,10 +58,10 @@ public Iterable<ConfigSource> getConfigSources(final ConfigSourceContext context
for (String profile : profiles) {
Response response;
if (connectionTimeoutIsGreaterThanZero || readTimeoutIsGreaterThanZero) {
response = client.exchange(applicationName.getValue(), profile).await()
response = client.exchange(applicationName, profile).await()
.atMost(config.connectionTimeout().plus(config.readTimeout().multipliedBy(2)));
} else {
response = client.exchange(applicationName.getValue(), profile).await().indefinitely();
response = client.exchange(applicationName, profile).await().indefinitely();
}

if (response.getProfiles().contains(profile)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package io.quarkus.spring.cloud.config.client.runtime;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;

import org.apache.commons.io.IOUtils;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;

import io.quarkus.arc.runtime.appcds.AppCDSRecorder;
import io.smallrye.config.ConfigSourceContext;

class SpringCloudConfigClientConfigSourceFactoryTest {

private static final int MOCK_SERVER_PORT = 9300;
private static final WireMockServer wireMockServer = new WireMockServer(MOCK_SERVER_PORT);

@BeforeAll
static void start() {

wireMockServer.start();
}

@AfterAll
static void stop() {

wireMockServer.stop();
}

@Test
void testExtensionDisabled() {

// Arrange
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(false, "foo", MOCK_SERVER_PORT, true);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

// Act
final Iterable<ConfigSource> configSourceIterable = factory.getConfigSources(context, config);

// Assert
assertThat(configSourceIterable).isEmpty();
}

@Test
void testNameNotProvided() {

// Arrange
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(true, null, MOCK_SERVER_PORT, true);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

// Act
final Iterable<ConfigSource> configSourceIterable = factory.getConfigSources(context, config);

// Assert
assertThat(configSourceIterable).isEmpty();
}

@Test
void testInAppCDsGeneration() {

// Arrange
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(true, "foo", MOCK_SERVER_PORT, true);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

System.setProperty(AppCDSRecorder.QUARKUS_APPCDS_GENERATE_PROP, "true");

// Act
final Iterable<ConfigSource> configSourceIterable = factory.getConfigSources(context, config);

// Clear property, because not necessary any more
System.clearProperty(AppCDSRecorder.QUARKUS_APPCDS_GENERATE_PROP);

// Assert
assertThat(configSourceIterable).isEmpty();
}

@Test
void testFailFastDisable() {

// Arrange
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(true, "unknown-application", 1234, false);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

Mockito.when(context.getProfiles()).thenReturn(List.of("dev"));

// Act
final Iterable<ConfigSource> configSourceIterable = factory.getConfigSources(context, config);

// Assert
assertThat(configSourceIterable).isEmpty();
}

@Test
void testFailFastEnabled() {

// Arrange
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(true, "unknown-application", 1234, true);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

Mockito.when(context.getProfiles()).thenReturn(List.of("dev"));

// Act + Assert
assertThatThrownBy(() -> factory.getConfigSources(context, config)).isInstanceOf(RuntimeException.class)
.hasMessageContaining("Unable to obtain configuration from Spring Cloud Config Server at");
}

@Test
void testBasic() throws IOException {

// Arrange
final String profile = "dev";
final ConfigSourceContext context = Mockito.mock(ConfigSourceContext.class);
final SpringCloudConfigClientConfig config = configForTesting(true, "foo", MOCK_SERVER_PORT, true);
final SpringCloudConfigClientConfigSourceFactory factory = new SpringCloudConfigClientConfigSourceFactory();

Mockito.when(context.getProfiles()).thenReturn(List.of(profile));
wireMockServer.stubFor(WireMock.get(String.format("/%s/%s/%s", config.name(), profile, config.label().get()))
.willReturn(WireMock.okJson(getJsonStringForApplicationAndProfile(config.name(), profile))));

// Act
final Iterable<ConfigSource> configSourceIterable = factory.getConfigSources(context, config);

// Assert
assertThat(configSourceIterable).hasSize(4);
assertThat(configSourceIterable).isInstanceOf(List.class);

final List<ConfigSource> configSourceList = (List<ConfigSource>) configSourceIterable;
assertThat(configSourceList.get(0)).satisfies(cs -> {
assertThat(cs.getName()).isEqualTo("https://github.com/spring-cloud-samples/config-repo/application.yml");
assertThat(cs.getOrdinal()).isEqualTo(450);
assertThat(cs.getProperties()).contains(entry("%dev.info.description", "Spring Cloud Samples"),
entry("%dev.foo", "baz"), entry("%dev.info.url", "https://github.com/spring-cloud-samples"),
entry("%dev.eureka.client.serviceUrl.defaultZone", "http://localhost:8761/eureka/"));
});

assertThat(configSourceList.get(1)).satisfies(cs -> {
assertThat(cs.getName()).isEqualTo("https://github.com/spring-cloud-samples/config-repo/foo.properties");
assertThat(cs.getOrdinal()).isEqualTo(451);
assertThat(cs.getProperties()).contains(entry("%dev.foo", "from foo props"),
entry("%dev.democonfigclient.message", "hello spring io"));
});

assertThat(configSourceList.get(2)).satisfies(cs -> {
assertThat(cs.getName())
.isEqualTo("https://github.com/spring-cloud-samples/config-repo/application-dev.yml");
assertThat(cs.getOrdinal()).isEqualTo(452);
assertThat(cs.getProperties()).contains(entry("%dev.my.prop", "from application-dev.yml"));
});

assertThat(configSourceList.get(3)).satisfies(cs -> {
assertThat(cs.getName()).isEqualTo("https://github.com/spring-cloud-samples/config-repo/foo-dev.yml");
assertThat(cs.getOrdinal()).isEqualTo(453);
assertThat(cs.getProperties()).contains(entry("%dev.foo", "from foo development"),
entry("%dev.democonfigclient.message", "hello from dev profile"), entry("%dev.bar", "spam"));
});
}

private SpringCloudConfigClientConfig configForTesting(final boolean isEnabled, final String appName,
final int serverPort, final boolean isFailFastEnabled) {

final SpringCloudConfigClientConfig config = Mockito.mock(SpringCloudConfigClientConfig.class);
when(config.enabled()).thenReturn(isEnabled);
when(config.name()).thenReturn(appName);
when(config.url()).thenReturn("http://localhost:" + serverPort);
when(config.label()).thenReturn(Optional.of("master"));
when(config.failFast()).thenReturn(isFailFastEnabled);
when(config.connectionTimeout()).thenReturn(Duration.ZERO);
when(config.readTimeout()).thenReturn(Duration.ZERO);
when(config.username()).thenReturn(Optional.empty());
when(config.password()).thenReturn(Optional.empty());
when(config.trustStore()).thenReturn(Optional.empty());
when(config.keyStore()).thenReturn(Optional.empty());
when(config.trustCerts()).thenReturn(false);
when(config.headers()).thenReturn(new HashMap<>());

return config;
}

private String getJsonStringForApplicationAndProfile(final String applicationName, final String profile)
throws IOException {

return IOUtils.toString(
this.getClass().getResourceAsStream(String.format("/%s-%s.json", applicationName, profile)),
Charset.defaultCharset());
}
}