Skip to content
Closed
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
4 changes: 4 additions & 0 deletions services/azure-servicebus/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import static io.quarkiverse.azure.servicebus.deployment.ServiceBusDevServicesConfig.PREFIX;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.BooleanSupplier;

Expand Down Expand Up @@ -84,10 +82,6 @@ interface EmulatorConfig {
*/
String DEFAULT_CONFIG_FILE_NAME = "config.json";

/**
* Location in the classpath from where the fallback configuration file can be loaded.
*/
String FALLBACK_CONFIG_FILE_RESOURCE_PATH = "azure/servicebus-emulator/default-config.json";
String EXAMPLE_CONFIG_FILE_URL = "https://github.com/Azure/azure-service-bus-emulator-installer/blob/main/ServiceBus-Emulator/Config/Config.json";

/**
Expand Down Expand Up @@ -117,7 +111,7 @@ interface EmulatorConfig {
String imageName();

/**
* The name of the property to configure a custom Service Bus emulator configuration file location.
* The name of the property to configure a custom Service Bus emulator configuration file.
*/
String CONFIG_KEY_CONFIG_FILE_PATH = PREFIX + ".config-file-path";

Expand All @@ -138,19 +132,6 @@ interface EmulatorConfig {
* configuration file</a>
*/
Optional<String> configFilePath();

/**
* The verified path to a Service Bus emulator configuration file.
* The default configuration file is only considered if no {@link #configFilePath()} is configured.
*
* @return an Optional containing the path to the configuration file
* or an empty Optional if the file does not exist.
*/
default Optional<Path> effectiveConfigFilePath() {
return configFilePath().isPresent()
? configFilePath().map(path -> Path.of(CONFIG_FILE_DIRECTORY, path)).filter(Files::exists)
: Optional.of(Path.of(CONFIG_FILE_DIRECTORY, DEFAULT_CONFIG_FILE_NAME)).filter(Files::exists);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static io.quarkiverse.azure.servicebus.runtime.ServiceBusConfig.CONFIG_KEY_CONNECTION_STRING;
import static io.quarkiverse.azure.servicebus.runtime.ServiceBusConfig.CONFIG_KEY_NAMESPACE;

import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -81,7 +82,6 @@ To use the Service Bus Dev Services, you must accept the license terms of the Se
SERVICEBUS_EULA_URL, MSSQL_SERVER_EULA_URL, CONFIG_KEY_LICENSE_ACCEPTED, CONFIG_KEY_DEVSERVICE_ENABLED))));
return true;
}

return false;
}

Expand All @@ -107,7 +107,7 @@ private List<RunningDevService> startServiceBusEmulator(ServiceBusDevServicesCon
boolean useSharedNetwork = !devServicesSharedNetworkBuildItem.isEmpty();
log.debug((useSharedNetwork ? "Using" : "Not using") + " a shared network");

MountableFile configFile = configFile(devServicesConfig.emulator());
MountableFile configFile = mountableConfigFile(devServicesConfig.emulator());
log.debugf("Azure Service Bus emulator uses configuration file at '%s'", configFile.getResolvedPath());

MSSQLServerContainer<?> database = new MSSQLServerContainer<>(devServicesConfig.database().imageName())
Expand Down Expand Up @@ -146,36 +146,23 @@ public Integer getMappedPort(int originalPort) {
return List.of(databaseDevService, emulatorDevService);
}

private MountableFile configFile(EmulatorConfig emulatorConfig) {
Optional<Path> effectiveConfigFilePath = emulatorConfig.effectiveConfigFilePath();
private MountableFile mountableConfigFile(EmulatorConfig emulatorConfig) {
try {
var emulatorConfigResolver = new ServiceBusEmulatorConfigResolver(emulatorConfig.configFilePath());
Optional<MountableFile> mountableConfigFile = emulatorConfigResolver.getMountableConfigFile();

if (effectiveConfigFilePath.isPresent()) {
// either configured location or default location.
return MountableFile.forHostPath(effectiveConfigFilePath.get());
} else {
if (isConfigFilePathConfigured(emulatorConfig)) {
throw configurationExceptionForMissingConfigFile(emulatorConfig);
} else {
return fallbackConfiguration();
if (mountableConfigFile.isEmpty()) {
logFallbackConfigurationUsage();
}
}
}

private static boolean isConfigFilePathConfigured(EmulatorConfig emulatorConfig) {
return emulatorConfig.configFilePath().isPresent();
}

private ConfigurationException configurationExceptionForMissingConfigFile(EmulatorConfig emulatorConfig) {
return new ConfigurationException(String.format(
"""
The Azure Service Bus emulator configuration file was not found at the location specified with '%s'.
Either add a configuration file at '%s/%s' or disable the Service Bus Dev Services with '%s=false'.
""",
EmulatorConfig.CONFIG_KEY_CONFIG_FILE_PATH, EmulatorConfig.CONFIG_FILE_DIRECTORY,
emulatorConfig.configFilePath().get(), CONFIG_KEY_DEVSERVICE_ENABLED));
return mountableConfigFile
.orElse(ServiceBusEmulatorConfigResolver.getFallbackConfiguration());
} catch (FileNotFoundException e) {
throw configurationExceptionForMissingConfigFile(emulatorConfig);
}
}

private MountableFile fallbackConfiguration() {
private void logFallbackConfigurationUsage() {
log.warnf(
"""
To use the Dev Services for Azure Service Bus, a configuration file for the Azure Service Bus emulator must be provided.
Expand All @@ -186,15 +173,36 @@ private MountableFile fallbackConfiguration() {
EmulatorConfig.EXAMPLE_CONFIG_FILE_URL);
log.warn(
"Azure Service Bus emulator launching with a fallback configuration that provides a queue 'queue' and a topic 'topic' with a subscription 'subscription'.");
return MountableFile.forClasspathResource(EmulatorConfig.FALLBACK_CONFIG_FILE_RESOURCE_PATH);
}

private static ConfigurationException configurationExceptionForMissingConfigFile(EmulatorConfig emulatorConfig) {
return new ConfigurationException(String.format(
"""
The Azure Service Bus emulator configuration file was not found at the location specified with '%s'.
Either add a configuration file at '%s/%s' or disable the Service Bus Dev Services with '%s=false'.
""",
EmulatorConfig.CONFIG_KEY_CONFIG_FILE_PATH, EmulatorConfig.CONFIG_FILE_DIRECTORY,
emulatorConfig.configFilePath().get(), CONFIG_KEY_DEVSERVICE_ENABLED));
}

@BuildStep(onlyIf = { DevServicesConfig.Enabled.class, ServiceBusDevServicesConfig.Enabled.class, IsDevelopment.class })
public HotDeploymentWatchedFileBuildItem watchEmulatorConfigFile(ServiceBusDevServicesConfig devServicesConfig) {
Optional<Path> configFilePath = devServicesConfig.emulator().effectiveConfigFilePath();
try {
return watchConfigFile(devServicesConfig.emulator());
} catch (FileNotFoundException e) {
// Configuration errors are treated at dev service startup,
// so we can safely ignore them here.
return null;
}
}

private HotDeploymentWatchedFileBuildItem watchConfigFile(EmulatorConfig emulatorConfig) throws FileNotFoundException {
var emulatorConfigResolver = new ServiceBusEmulatorConfigResolver(emulatorConfig.configFilePath());

Optional<Path> configFile = emulatorConfigResolver.getConfigFile();

if (configFilePath.isPresent()) {
Path path = configFilePath.get();
if (configFile.isPresent()) {
Path path = configFile.get();
log.debugf("Watching '%s' for changes", path);
return new HotDeploymentWatchedFileBuildItem(path.toAbsolutePath().toString());
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package io.quarkiverse.azure.servicebus.deployment;

import static io.quarkiverse.azure.servicebus.deployment.ServiceBusDevServicesConfig.EmulatorConfig.CONFIG_FILE_DIRECTORY;
import static io.quarkiverse.azure.servicebus.deployment.ServiceBusDevServicesConfig.EmulatorConfig.DEFAULT_CONFIG_FILE_NAME;

import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;

import org.testcontainers.utility.MountableFile;

class ServiceBusEmulatorConfigResolver {

/**
* Location in the classpath from where the fallback configuration file can be loaded.
*/
private static final String FALLBACK_CONFIG_FILE_RESOURCE_PATH = "azure/servicebus-emulator/default-config.json";

private final Path path;
private final boolean exists;

/**
* Creates a {@link ServiceBusEmulatorConfigResolver} for the given configuration file path.
*
* @param configFilePath the config file path to search in the default configuration directory
* @throws FileNotFoundException if a config file path was given and the file does not exist
*
* @see ServiceBusDevServicesConfig.EmulatorConfig#CONFIG_KEY_CONFIG_FILE_PATH
*/
public ServiceBusEmulatorConfigResolver(Optional<String> configFilePath) throws FileNotFoundException {
this(configFilePath, CONFIG_FILE_DIRECTORY);
}

/**
* Creates a {@link ServiceBusEmulatorConfigResolver} for the given configuration file path in a custom
* configuration directory.
* For tests only.
*
* @param configFilePath the config file path to search in {@code configFileDirectory}
* @param configFileDirectory the root directory for Service Bus emulator configuration files, relative to the project root
* @throws FileNotFoundException if a config file path was given and the file does not exist
*
* @see ServiceBusDevServicesConfig.EmulatorConfig#CONFIG_KEY_CONFIG_FILE_PATH
*/
ServiceBusEmulatorConfigResolver(Optional<String> configFilePath, String configFileDirectory) throws FileNotFoundException {
this.path = Path.of(configFileDirectory, configFilePath.orElse(DEFAULT_CONFIG_FILE_NAME));
this.exists = Files.exists(path);
if (configFilePath.isPresent() && !exists) {
throw new FileNotFoundException(
String.format("Azure Service Bus emulator configuration file '%s' does not exist", path));
}
}

/**
* Gets the verified {@link Path} to an existing Service Bus emulator configuration file.
* <p>
* The configuration file is resolved according to these rules:
* <ul>
* <li>If a config file path is configured and the config file exists, the path
* to that file is returned.</li>
* <li>If no config file path is configured but a config file exists at the
* default location, the path to it is returned.</li>
* <li>If no config file path is configured and no default config file exists,
* an empty {@link Optional} is returned.</li>
* </ul>
*
* @see ServiceBusDevServicesConfig.EmulatorConfig#CONFIG_KEY_CONFIG_FILE_PATH
* @see ServiceBusDevServicesConfig.EmulatorConfig#DEFAULT_CONFIG_FILE_NAME
* @see ServiceBusDevServicesConfig.EmulatorConfig#CONFIG_FILE_DIRECTORY
*/
public Optional<Path> getConfigFile() {
return exists ? Optional.of(path) : Optional.empty();
}

/**
* Returns the Service Bus emulator configuration file as a {@link MountableFile} that can be
* mounted into a container.
* <p>
* Resolution of the config file is delegated to {@link #getConfigFile()}.
*/
public Optional<MountableFile> getMountableConfigFile() {
return getConfigFile().map(MountableFile::forHostPath);
}

public static MountableFile getFallbackConfiguration() {
return MountableFile.forClasspathResource(FALLBACK_CONFIG_FILE_RESOURCE_PATH);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package io.quarkiverse.azure.servicebus.deployment;

import static io.quarkiverse.azure.servicebus.deployment.ServiceBusDevServicesConfig.EmulatorConfig.DEFAULT_CONFIG_FILE_NAME;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.testcontainers.utility.MountableFile;

class ServiceBusEmulatorConfigResolverTest {

@Test
@DisplayName("Path unconfigured and no file exists at default location")
void noDefaultFileExists() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.empty(),
"src/test/resources/servicebus-emulator-empty");

assertThat(configResolver.getConfigFile())
.isEmpty();
}

@Test
@DisplayName("Path unconfigured but file exists at default location")
void fileExistsInDefaultLocation() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.empty(),
"src/test/resources/servicebus-emulator");

assertThat(configResolver.getConfigFile())
.hasValueSatisfying(path -> assertThat(path)
.endsWith(Path.of("servicebus-emulator/config.json")));
}

@Test
@DisplayName("Path configured to existing file at default location")
void fileExistsInDefaultLocationAndIsConfigured() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.of(DEFAULT_CONFIG_FILE_NAME),
"src/test/resources/servicebus-emulator");

assertThat(configResolver.getConfigFile())
.hasValueSatisfying(path -> assertThat(path)
.endsWith(Path.of("servicebus-emulator/config.json")));
}

@Test
@DisplayName("Path configured to existing file at custom location")
void customFileExists() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.of("custom-config.json"),
"src/test/resources/servicebus-emulator");

assertThat(configResolver.getConfigFile())
.hasValueSatisfying(path -> assertThat(path)
.endsWith(Path.of("servicebus-emulator/custom-config.json")));
}

@Test
@DisplayName("Path configured to non-existent file at default location")
void customFileAtDefaultLocationDoesNotExist() {
assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(
() -> new ServiceBusEmulatorConfigResolver(Optional.of(DEFAULT_CONFIG_FILE_NAME),
"src/test/resources/servicebus-emulator-empty"));
}

@Test
@DisplayName("Path configured to non-existent file at custom location")
void customFileDoesNotExist() {
assertThatExceptionOfType(FileNotFoundException.class)
.isThrownBy(
() -> new ServiceBusEmulatorConfigResolver(Optional.of("does-not-exist"),
"src/test/resources/servicebus-emulator"));
}

@Test
@DisplayName("MountableFile for existing file")
void mountableFileForExistingFile() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.of("custom-config.json"),
"src/test/resources/servicebus-emulator");

assertThat(configResolver.getMountableConfigFile())
.hasValueSatisfying(mountableFile -> assertThat(mountableFile.getResolvedPath())
.endsWith("custom-config.json"));
}

@Test
@DisplayName("MountableFile if no config file exists")
void mountableFileForNonExistingFile() throws FileNotFoundException {
var configResolver = new ServiceBusEmulatorConfigResolver(Optional.empty(),
"src/test/resources/servicebus-emulator-empty");

assertThat(configResolver.getMountableConfigFile())
.isEmpty();
}

@Test
@DisplayName("Fallback configuration is loaded from the classpath")
void fallbackConfiguration() {
MountableFile fallbackConfiguration = ServiceBusEmulatorConfigResolver.getFallbackConfiguration();

assertThat(fallbackConfiguration.getResolvedPath())
.endsWith("default-config.json");
}
}
Loading