diff --git a/.all-contributorsrc b/.all-contributorsrc
index 2b85d231..7ebbcdca 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -205,6 +205,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "xperjon",
+ "name": "Jon-Erik Liw",
+ "avatar_url": "https://avatars.githubusercontent.com/u/965777?v=4",
+ "profile": "https://github.com/xperjon",
+ "contributions": [
+ "doc"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/.github/workflows/quarkus-snapshot.yaml b/.github/workflows/quarkus-snapshot.yaml
index 6fd23592..3ff828ca 100644
--- a/.github/workflows/quarkus-snapshot.yaml
+++ b/.github/workflows/quarkus-snapshot.yaml
@@ -26,9 +26,6 @@ jobs:
if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == 'loicmathieu'
steps:
- - name: Install yq
- run: sudo add-apt-repository ppa:rmescandon/yq && sudo apt update && sudo apt install yq -y
-
- name: Set up Java
uses: actions/setup-java@v1
with:
diff --git a/README.md b/README.md
index 40fd1e22..d5caa3b6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Quarkiverse - Quarkus Google Cloud Services
-[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors-)
+[![All Contributors](https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square)](#contributors-)
[![version](https://img.shields.io/maven-central/v/io.quarkiverse.googlecloudservices/quarkus-google-cloud-services-bom)](https://repo1.maven.org/maven2/io/quarkiverse/googlecloudservices/)
[![Build](https://github.com/quarkiverse/quarkus-google-cloud-services/workflows/Build/badge.svg)](https://github.com/quarkiverse/quarkus-google-cloud-services/actions?query=workflow%3ABuild)
@@ -71,6 +71,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
wabrit 💻 |
+ Jon-Erik Liw 📖 |
diff --git a/bom/pom.xml b/bom/pom.xml
index 1245b542..43153c80 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -19,7 +19,7 @@
UTF-8
26.50.0
- 9.4.2
+ 9.4.3
0.31.1
diff --git a/docs/modules/ROOT/pages/firebase-admin.adoc b/docs/modules/ROOT/pages/firebase-admin.adoc
index 0128ad45..1a7e042c 100644
--- a/docs/modules/ROOT/pages/firebase-admin.adoc
+++ b/docs/modules/ROOT/pages/firebase-admin.adoc
@@ -73,7 +73,7 @@ public class FirebaseAuthResourceTest {
This extension also supports Firebase Authentication, allowing you to secure your endpoints using Firebase's authentication mechanisms.
This section describes how to use Firebase Authentication in your Quarkus application.
-Remember that you need to enable the Firebase Authentication service in your Firebase project. `quarkus.google.cloud.firebase.auth.enable` must be set to `true` in your application configuration.
+Remember that you need to enable the Firebase Authentication service in your Firebase project. `quarkus.google.cloud.firebase.auth.enabled` must be set to `true` in your application configuration.
=== Configuration
@@ -122,4 +122,4 @@ public class FirebaseAppResource {
== Configuration Reference
-include::./includes/quarkus-google-cloud-firebase-admin.adoc[]
\ No newline at end of file
+include::./includes/quarkus-google-cloud-firebase-admin.adoc[]
diff --git a/docs/modules/ROOT/pages/firebase-devservices.adoc b/docs/modules/ROOT/pages/firebase-devservices.adoc
index 47fa12e3..dd2a9d96 100644
--- a/docs/modules/ROOT/pages/firebase-devservices.adoc
+++ b/docs/modules/ROOT/pages/firebase-devservices.adoc
@@ -131,13 +131,43 @@ If emulators are configured via the configuration options, a `firebase.json` fil
* Each of the emulators must be exposed on `0.0.0.0` as host as described https://firebase.google.com/docs/emulator-suite/use_hosting#emulators-no-local-host[here]. If this is not done, the Emulators will not be reachable from the Docker host.
* Emulators need to be configured to use the default ports. Customizing the ports on which they run is currently not supported (this might change in a future version).
+== Details on specific Devservices
+
+The following sections provide documentation in interaction with specific emulators.
+
+=== Hosting emulator
+
+If you use the hosting emulator, where Quarkus is the backend, you will need to include a CORS configuration, as REST
+requests will originate from another Origin (host). See the https://quarkus.io/guides/security-cors[Quarkus CORS]
+documentation for more info.
+
+A simple setup would be
+[source,properties]
+----
+"%dev".quarkus.http.cors=true
+"%dev".quarkus.http.cors.origins: /.*/
+----
+
+Note that a redirect from the hosting emulator to the Quarkus instance is currently not supported by the emulator.
+
+=== Auth emulator
+
+You can use the features provided by Mircoprofile JWT (e.g. injecting a `@Claim` value) by including the smallrye-jwt
+extension and disabling smallrye-jwt using the following property. This will prevent SmallRye JWT from handling the JWT
+validation (leaving that to the Firebase Auth module and the Auth emulator), but using the provided JWT to allow injecting
+of these beans.
+
+[source,properties]
+----
+quarkus.smallrye-jwt.enabled=false
+----
+
== Interaction with other extensions
The following extensions support Dev Services which conflicts with the Dev Services exposed by the Firebase Emulators.
* Firestore
* PubSub
-* TODO: Verify Storage
When including this module, these Dev Services will automatically be disabled, as the Firebase emulator should feature wise be on-par or more extensive than the individual emulators.
diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc
index 95883d1d..31cd5553 100644
--- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc
+++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices.adoc
@@ -247,6 +247,23 @@ endif::add-copy-button-to-env-var[]
a|`import-only`, `export-only`, `import-export`
|
+a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-experiments]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-experiments[`quarkus.google.cloud.devservices.firebase.emulator.cli.experiments`]##
+
+[.description]
+--
+Indicates the set of experimental features from firebase to enable (using the firebase experiment:enable command line option).
+
+
+ifdef::add-copy-button-to-env-var[]
+Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EXPERIMENTS+++[]
+endif::add-copy-button-to-env-var[]
+ifndef::add-copy-button-to-env-var[]
+Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EXPERIMENTS+++`
+endif::add-copy-button-to-env-var[]
+--
+|list of string
+|
+
a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug[`quarkus.google.cloud.devservices.firebase.emulator.cli.debug`]##
[.description]
diff --git a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc
index 95883d1d..31cd5553 100644
--- a/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc
+++ b/docs/modules/ROOT/pages/includes/quarkus-google-cloud-firebase-devservices_quarkus.google.adoc
@@ -247,6 +247,23 @@ endif::add-copy-button-to-env-var[]
a|`import-only`, `export-only`, `import-export`
|
+a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-experiments]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-experiments[`quarkus.google.cloud.devservices.firebase.emulator.cli.experiments`]##
+
+[.description]
+--
+Indicates the set of experimental features from firebase to enable (using the firebase experiment:enable command line option).
+
+
+ifdef::add-copy-button-to-env-var[]
+Environment variable: env_var_with_copy_button:+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EXPERIMENTS+++[]
+endif::add-copy-button-to-env-var[]
+ifndef::add-copy-button-to-env-var[]
+Environment variable: `+++QUARKUS_GOOGLE_CLOUD_DEVSERVICES_FIREBASE_EMULATOR_CLI_EXPERIMENTS+++`
+endif::add-copy-button-to-env-var[]
+--
+|list of string
+|
+
a|icon:lock[title=Fixed at build time] [[quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug]] [.property-path]##link:#quarkus-google-cloud-firebase-devservices_quarkus-google-cloud-devservices-firebase-emulator-cli-debug[`quarkus.google.cloud.devservices.firebase.emulator.cli.debug`]##
[.description]
diff --git a/docs/modules/ROOT/pages/pubsub.adoc b/docs/modules/ROOT/pages/pubsub.adoc
index 5f50d6b1..5fdfa62e 100644
--- a/docs/modules/ROOT/pages/pubsub.adoc
+++ b/docs/modules/ROOT/pages/pubsub.adoc
@@ -155,6 +155,28 @@ public class PubSubResource {
}
----
+== AdminClient beans
+
+If you need to perform admin actions on PubSub, you can inject the `com.google.cloud.pubsub.v1.SubscriptionAdminClient` and
+`com.google.cloud.pubsub.v1.TopicAdminClient` as CDI beans as shown in the example below. This is usefull if the basic
+facilities offered by `io.quarkiverse.googlecloudservices.pubsub.QuarkusPubSub` are not sufficient:
+
+[source, java]
+----
+ @Inject
+ SubscriptionAdminClient subscriptionAdminClient;
+
+ @Inject
+ TopicAdminClient topicAdminClient;
+
+ public someMethod() {
+ var pushConfig = ...; // Create com.google.pubsub.v1.PushConfig
+ subscriptionAdminClient.createSubscription("subscription-name", "topic-name", pushConfig, 10 /* ACK deadline */);
+
+ var topics = topicAdminClient.listTopics("my-google-project");
+ }
+----
+
== Dev Service
=== Configuring the Dev Service
diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java
index 08b58ab6..6c2d614e 100644
--- a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java
+++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceConfig.java
@@ -1,6 +1,7 @@
package io.quarkiverse.googlecloudservices.firebase.deployment;
import java.util.Optional;
+import java.util.Set;
import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.FirebaseEmulatorContainer;
import io.quarkus.runtime.annotations.ConfigRoot;
@@ -199,6 +200,12 @@ interface Cli {
*/
Optional importExport();
+ /**
+ * Indicates the set of experimental features from firebase to enable (using the firebase experiment:enable
+ * command line option).
+ */
+ Optional> experiments();
+
/**
* Enable firebase emulators debugging.
*/
diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java
index 9d8d46fd..52b75811 100644
--- a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java
+++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseDevServiceProcessor.java
@@ -67,7 +67,11 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI
// Try starting the container if conditions are met
try {
- devService = startContainerIfAvailable(dockerStatusBuildItem, projectConfig, firebaseBuildTimeConfig,
+ devService = startContainerIfAvailable(
+ dockerStatusBuildItem,
+ closeBuildItem,
+ projectConfig,
+ firebaseBuildTimeConfig,
globalDevServicesConfig.timeout);
} catch (Throwable t) {
LOGGER.warn("Unable to start Firebase dev service", t);
@@ -87,9 +91,11 @@ public DevServicesResultBuildItem start(DockerStatusBuildItem dockerStatusBuildI
* @param dockerStatusBuildItem, Docker status
* @param config, Configuration for the Firebase service
* @param timeout, Optional timeout for starting the service
+ * @param closeBuildItem
* @return Running service item, or null if the service couldn't be started
*/
private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(DockerStatusBuildItem dockerStatusBuildItem,
+ CuratedApplicationShutdownBuildItem closeBuildItem,
FirebaseDevServiceProjectConfig projectConfig,
FirebaseDevServiceConfig config,
Optional timeout) {
@@ -111,7 +117,7 @@ private DevServicesResultBuildItem.RunningDevService startContainerIfAvailable(D
return null;
}
- return startContainer(dockerStatusBuildItem, projectConfig, config, timeout);
+ return startContainer(closeBuildItem, projectConfig, config, timeout);
}
private boolean isEnabled(FirebaseDevServiceConfig config) {
@@ -125,12 +131,12 @@ private boolean isEnabled(FirebaseDevServiceConfig config) {
/**
* Starts the Pub/Sub emulator container with provided configuration.
*
- * @param dockerStatusBuildItem, Docker status
+ * @param closeBuildItem The close build item to handle shutdown of the container
* @param config, Configuration for the Firebase service
* @param timeout, Optional timeout for starting the service
* @return Running service item, or null if the service couldn't be started
*/
- private DevServicesResultBuildItem.RunningDevService startContainer(DockerStatusBuildItem dockerStatusBuildItem,
+ private DevServicesResultBuildItem.RunningDevService startContainer(CuratedApplicationShutdownBuildItem closeBuildItem,
FirebaseDevServiceProjectConfig projectConfig,
FirebaseDevServiceConfig config,
Optional timeout) {
@@ -155,6 +161,8 @@ private DevServicesResultBuildItem.RunningDevService startContainer(DockerStatus
.forEach((e, h) -> LOGGER.info("Google Cloud emulator config property " + e + " set to " + h));
}
+ closeBuildItem.addCloseTask(emulatorContainer::close, true);
+
// Return running service item with container details
return new DevServicesResultBuildItem.RunningDevService(FirebaseBuildSteps.FEATURE,
emulatorContainer.getContainerId(),
diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java
index e3ed09aa..e356965a 100644
--- a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java
+++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilder.java
@@ -79,6 +79,7 @@ private void handleCliConfig(FirebaseDevServiceConfig.Firebase.Emulator.Cli cli,
cli.javaToolOptions().ifPresent(cliConfig::withJavaToolOptions);
cli.emulatorData().map(FirebaseEmulatorConfigBuilder::asPath).ifPresent(cliConfig::withEmulatorData);
cli.importExport().ifPresent(cliConfig::withImportExport);
+ cli.experiments().ifPresent(cliConfig::withExperiments);
cli.debug().ifPresent(cliConfig::withDebug);
cliConfig.done();
diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java
index a7050bd9..ca3cbfc8 100644
--- a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java
+++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/CustomFirebaseConfigReader.java
@@ -10,6 +10,8 @@
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkiverse.googlecloudservices.firebase.deployment.testcontainers.json.Emulators;
@@ -21,6 +23,8 @@
*/
class CustomFirebaseConfigReader {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CustomFirebaseConfigReader.class);
+
private final ObjectMapper objectMapper = new ObjectMapper();
/**
@@ -110,6 +114,8 @@ private Map this.resolvePath(f, customFirebaseJson));
+ LOGGER.debug("Firestore configured with rules file {}", rulesFile);
+ LOGGER.debug("Firestore configured with indexes file {}", indexesFile);
+
return new FirebaseEmulatorContainer.FirestoreConfig(
rulesFile,
indexesFile);
@@ -152,8 +161,11 @@ private FirebaseEmulatorContainer.HostingConfig readHosting(Object hosting, Path
var publicDir = Optional
.ofNullable(hostingMap.get("public"))
+ .or(() -> Optional.ofNullable(hostingMap.get("source")))
.map(f -> this.resolvePath(f, customFirebaseJson));
+ LOGGER.debug("Hosting configured with public directory {}", publicDir);
+
return new FirebaseEmulatorContainer.HostingConfig(
publicDir);
} else {
@@ -170,6 +182,8 @@ private FirebaseEmulatorContainer.StorageConfig readStorage(Object storage, Path
.ofNullable(storageMap.get("rules"))
.map(f -> this.resolvePath(f, customFirebaseJson));
+ LOGGER.debug("Storage configured with rules file {}", rulesFile);
+
return new FirebaseEmulatorContainer.StorageConfig(
rulesFile);
} else {
@@ -192,6 +206,9 @@ private FirebaseEmulatorContainer.FunctionsConfig readFunctions(Object functions
.map(String[].class::cast)
.orElse(new String[0]);
+ LOGGER.debug("Functions will be read from source directory {}", functionsPath);
+ LOGGER.debug("Functions configured with ignore file {}", (Object) ignores);
+
return new FirebaseEmulatorContainer.FunctionsConfig(
functionsPath,
ignores);
diff --git a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java
index 02937f8e..61d13180 100644
--- a/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java
+++ b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java
@@ -185,6 +185,7 @@ public record DockerConfig(
* @param javaToolOptions The options to pass to the java based emulators
* @param emulatorData The path to the directory where to store the emulator data
* @param importExport Specify whether to import, export or do both with the emulator data
+ * @param experiments Firebase experiments to enable on the docker image
* @param debug Whether to run with the --debug flag
*/
public record CliArgumentsConfig(
@@ -193,6 +194,7 @@ public record CliArgumentsConfig(
Optional javaToolOptions,
Optional emulatorData,
ImportExport importExport,
+ Optional> experiments,
boolean debug) {
public static final CliArgumentsConfig DEFAULT = new CliArgumentsConfig(
Optional.empty(),
@@ -200,6 +202,7 @@ public record CliArgumentsConfig(
Optional.empty(),
Optional.empty(),
ImportExport.IMPORT_EXPORT,
+ Optional.of(new HashSet<>()),
false);
}
@@ -413,7 +416,7 @@ public FirebaseConfigBuilder withFirebaseConfig() {
public EmulatorConfig buildConfig() {
if (firebaseConfig == null) {
// Try to autoload the firebase.json configuration
- var defaultFirebaseJson = new File("firebase.json").getAbsoluteFile().toPath();
+ var defaultFirebaseJson = new File("firebase.json").toPath();
LOGGER.info("Trying to automatically read firebase config from {}", defaultFirebaseJson);
@@ -609,6 +612,7 @@ public class CliBuilder {
private String javaToolOptions;
private Path emulatorData;
private ImportExport importExport;
+ private Set experiments;
private boolean debug;
/**
@@ -620,6 +624,7 @@ private CliBuilder() {
this.javaToolOptions = Builder.this.cliArguments.javaToolOptions.orElse(null);
this.emulatorData = Builder.this.cliArguments.emulatorData.orElse(null);
this.importExport = Builder.this.cliArguments.importExport;
+ this.experiments = Builder.this.cliArguments.experiments.orElse(new HashSet<>());
this.debug = Builder.this.cliArguments.debug;
}
@@ -690,6 +695,28 @@ public CliBuilder withDebug(boolean debug) {
return this;
}
+ /**
+ * Add the firebase experiments setting
+ *
+ * @param experiments The experiments to enable
+ * @return The builder
+ */
+ public CliBuilder withExperiments(Set experiments) {
+ this.experiments = new HashSet<>(experiments);
+ return this;
+ }
+
+ /**
+ * Add a single firebase experiment to the set
+ *
+ * @param experiment The experiment to add
+ * @return The builder
+ */
+ public CliBuilder addExperiment(String experiment) {
+ this.experiments.add(experiment);
+ return this;
+ }
+
/**
* Finish the builder
*
@@ -702,6 +729,7 @@ public Builder done() {
Optional.ofNullable(this.javaToolOptions),
Optional.ofNullable(this.emulatorData),
this.importExport,
+ Optional.of(this.experiments),
this.debug);
return Builder.this;
}
@@ -921,8 +949,10 @@ public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) {
.map(Path::toString)
.orElse(new File(FirebaseJsonBuilder.FIREBASE_HOSTING_SUBPATH).getAbsolutePath());
+ LOGGER.debug("Mounting {} to the container hosting path", hostingPath);
+
// Mount volume for static hosting content
- this.withFileSystemBind(hostingPath, containerHostingPath(emulatorConfig), BindMode.READ_ONLY);
+ this.withFileSystemBind(hostingPath, containerHostingPath(emulatorConfig), BindMode.READ_WRITE);
}
if (this.services.containsKey(Emulator.CLOUD_FUNCTIONS)) {
@@ -933,39 +963,62 @@ public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) {
.map(Path::toString)
.orElse(new File(FirebaseJsonBuilder.FIREBASE_FUNCTIONS_SUBPATH).getAbsolutePath());
+ LOGGER.debug("Mounting {} to the container functions sources path", functionsPath);
+
// Mount volume for functions
- this.withFileSystemBind(functionsPath, containerFunctionsPath(emulatorConfig), BindMode.READ_ONLY);
+ this.withFileSystemBind(functionsPath, containerFunctionsPath(emulatorConfig), BindMode.READ_WRITE);
}
}
static String containerHostingPath(EmulatorConfig emulatorConfig) {
- var hostingPath = emulatorConfig.firebaseConfig().hostingConfig().hostingContentDir();
- if (emulatorConfig.customFirebaseJson().isPresent()) {
- var firebaseJsonDir = emulatorConfig.customFirebaseJson().get().getParent();
- hostingPath = hostingPath.map(path -> path.subpath(firebaseJsonDir.getNameCount(), path.getNameCount()));
- }
+ var hostingPath = relativizeToFirebaseJson(
+ emulatorConfig.firebaseConfig().hostingConfig().hostingContentDir(),
+ emulatorConfig);
+ String containerHostingPath;
if (hostingPath.isPresent()) {
var path = hostingPath.get();
if (path.isAbsolute()) {
- return FIREBASE_HOSTING_PATH;
+ containerHostingPath = FIREBASE_HOSTING_PATH;
} else {
- return FIREBASE_ROOT + "/" + hostingPath.get();
+ containerHostingPath = FIREBASE_ROOT + "/" + hostingPath.get();
}
} else {
- return FIREBASE_HOSTING_PATH;
+ containerHostingPath = FIREBASE_HOSTING_PATH;
}
+
+ LOGGER.debug("Container hosting path is {}", containerHostingPath);
+
+ return containerHostingPath;
}
static String containerFunctionsPath(EmulatorConfig emulatorConfig) {
- var functionsPath = emulatorConfig.firebaseConfig().functionsConfig().functionsPath();
- if (emulatorConfig.customFirebaseJson().isPresent()) {
- var firebaseJsonDir = emulatorConfig.customFirebaseJson().get().getParent();
- functionsPath = functionsPath.map(path -> path.subpath(firebaseJsonDir.getNameCount(), path.getNameCount()));
- }
- return FIREBASE_ROOT + "/" + functionsPath
+ var functionsPath = relativizeToFirebaseJson(
+ emulatorConfig.firebaseConfig().functionsConfig().functionsPath(),
+ emulatorConfig);
+
+ var containerFunctionsPath = FIREBASE_ROOT + "/" + functionsPath
.map(Path::toString)
.orElse(FirebaseJsonBuilder.FIREBASE_FUNCTIONS_SUBPATH);
+
+ LOGGER.debug("Container functions path is {}", containerFunctionsPath);
+
+ return containerFunctionsPath;
+ }
+
+ private static Optional relativizeToFirebaseJson(Optional filePath, EmulatorConfig emulatorConfig) {
+ if (emulatorConfig.customFirebaseJson().isPresent()) {
+ var firebaseJsonFile = emulatorConfig.customFirebaseJson().get();
+ var nameCount = firebaseJsonFile.getParent() == null ? 0 : firebaseJsonFile.getParent().getNameCount();
+
+ var result = filePath.map(path -> path.subpath(nameCount, path.getNameCount()));
+
+ LOGGER.debug("Resolved path to be {} relative to the firebase.json file", result);
+
+ return result;
+ } else {
+ return filePath;
+ }
}
private static class FirebaseDockerBuilder {
@@ -998,14 +1051,15 @@ public ImageFromDockerfile build() {
this.initialSetup();
this.authenticateToFirebase();
this.setupJavaToolOptions();
- this.setupUserAndGroup();
this.downloadEmulators();
- this.addFirebaseJson();
- this.includeFirestoreFiles();
- this.includeStorageFiles();
+ this.setupExperiments();
this.setupDataImportExport();
this.setupHosting();
this.setupFunctions();
+ this.addFirebaseJson();
+ this.includeFirestoreFiles();
+ this.includeStorageFiles();
+ this.setupUserAndGroup();
this.runExecutable();
return result;
@@ -1036,10 +1090,14 @@ private void validateConfiguration() {
}
if (emulatorConfig.customFirebaseJson.isPresent()) {
- var hostingDirIsAbsolute = emulatorConfig.firebaseConfig.hostingConfig.hostingContentDir
+ var hostingDir = emulatorConfig.firebaseConfig.hostingConfig.hostingContentDir;
+
+ var hostingDirIsAbsolute = hostingDir
.map(Path::isAbsolute)
.orElse(false);
+ LOGGER.debug("Checking if path {} is absolute --> {}", hostingDir, hostingDirIsAbsolute);
+
if (hostingDirIsAbsolute) {
throw new IllegalStateException(
"When using a custom firebase.json, the hosting path must be relative to the firebase.json file");
@@ -1047,11 +1105,14 @@ private void validateConfiguration() {
var firebasePath = emulatorConfig.customFirebaseJson.get().toAbsolutePath().getParent();
- var hostingDirIsChildOfFirebaseJsonParent = emulatorConfig.firebaseConfig.hostingConfig.hostingContentDir
+ var hostingDirIsChildOfFirebaseJsonParent = hostingDir
.map(Path::toAbsolutePath)
.map(h -> h.startsWith(firebasePath))
.orElse(true);
+ LOGGER.debug("Checking if the hosting path {} is relative to the firebase.json file --> {}", hostingDir,
+ hostingDirIsChildOfFirebaseJsonParent);
+
if (!hostingDirIsChildOfFirebaseJsonParent) {
throw new IllegalStateException(
"When using a custom firebase.json, the hosting path must be in the same subtree as the firebase.json file");
@@ -1059,10 +1120,14 @@ private void validateConfiguration() {
}
if (emulatorConfig.firebaseConfig.functionsConfig.functionsPath.isPresent()) {
- var functionsDirIsAbsolute = emulatorConfig.firebaseConfig.functionsConfig.functionsPath
+ var functionsDir = emulatorConfig.firebaseConfig.functionsConfig.functionsPath;
+ var functionsDirIsAbsolute = functionsDir
.map(Path::isAbsolute)
.orElse(false);
+ LOGGER.debug("Checking if the functions sources dir {} is absolute --> {}", functionsDir,
+ functionsDirIsAbsolute);
+
if (functionsDirIsAbsolute) {
throw new IllegalStateException("Functions path cannot be absolute");
}
@@ -1117,8 +1182,36 @@ private void setupJavaToolOptions() {
toolOptions -> dockerBuilder.env("JAVA_TOOL_OPTIONS", toolOptions));
}
+ private void setupExperiments() {
+ emulatorConfig.cliArguments.experiments().ifPresent(
+ experimentsSet -> {
+ var experiments = String.join(",", experimentsSet);
+ LOGGER.debug("Firebase experiments found, enabling experiments: {}", experiments);
+ dockerBuilder.env("FIREBASE_CLI_EXPERIMENTS", String.join(",", experiments));
+ });
+ }
+
private void addFirebaseJson() {
- dockerBuilder.workDir(FIREBASE_ROOT);
+ /*
+ * Workaround for https://github.com/firebase/firebase-tools/issues/5903#issuecomment-1568239576
+ *
+ * Remove the conditional and just set FIREBASE_ROOT as workdir once the upstream bug is fixed.
+ */
+ if (isEmulatorEnabled(Emulator.FIREBASE_HOSTING)) {
+ var hostingPath = containerHostingPath(emulatorConfig);
+
+ LOGGER.debug(
+ "Hosting emulator detected. Setting workdir to {} as a workaround for an upstream bug in firebase-tools",
+ hostingPath);
+
+ dockerBuilder.workDir(hostingPath);
+ } else {
+ LOGGER.debug("No hosting emulator detected. Using default workdir");
+ dockerBuilder.workDir(FIREBASE_ROOT);
+ }
+ /*
+ * Workaround ends <-- https://github.com/firebase/firebase-tools/issues/5903#issuecomment-1568239576
+ */
emulatorConfig.customFirebaseJson().ifPresentOrElse(
this::includeCustomFirebaseJson,
@@ -1220,6 +1313,8 @@ private void runExecutable() {
List arguments = new ArrayList<>();
arguments.add("emulators:start");
+ arguments.add("--config");
+ arguments.add(FIREBASE_ROOT + "/firebase.json");
emulatorConfig.cliArguments().projectId()
.map(id -> "--project")
@@ -1232,7 +1327,9 @@ private void runExecutable() {
arguments.add("--debug");
}
- if (emulatorConfig.cliArguments().importExport.isDoExport()) {
+ if (emulatorConfig.cliArguments().importExport.isDoImport()) {
+ LOGGER.debug("Import requested. Importing data on startup");
+
emulatorConfig
.cliArguments()
.emulatorData()
@@ -1251,6 +1348,8 @@ private void runExecutable() {
}
if (emulatorConfig.cliArguments().importExport.isDoExport()) {
+ LOGGER.debug("Export requested. Saving data on exit");
+
emulatorConfig
.cliArguments()
.emulatorData()
@@ -1304,11 +1403,22 @@ public void stop() {
* kill (SIGKILL) command instead of a stop (SIGTERM) command. This will kill the container instantly
* and prevent firebase from writing the "--export-on-exit" data to the mounted directory.
*/
+ LOGGER.debug("Requesting to stopping the container to give export a chance to finish");
+
this.getDockerClient().stopContainerCmd(this.getContainerId()).exec();
+ LOGGER.debug("Stopping abd removing the container");
+
super.stop();
}
+ @Override
+ public void close() {
+ LOGGER.debug("Emulator is being closed");
+
+ this.stop();
+ }
+
/**
* Configures the Pub/Sub emulator container.
*/
@@ -1385,7 +1495,7 @@ private void writeOutputFrame(OutputFrame frame, Level level) {
private String getEmulatorEndpoint(Emulator emulator) {
var endpoint = this.getHost() + ":" + emulatorPort(emulator);
- if (emulator.equals(Emulator.REALTIME_DATABASE)) {
+ if (emulator.equals(Emulator.REALTIME_DATABASE) || emulator.equals(Emulator.CLOUD_STORAGE)) {
endpoint = "http://" + endpoint;
}
return endpoint;
diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java
index 49397aca..60febcea 100644
--- a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java
+++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/FirebaseEmulatorConfigBuilderTest.java
@@ -5,6 +5,7 @@
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -37,6 +38,7 @@ void setUp() {
Optional.of("-Xmx"),
Optional.of("data"),
Optional.of(FirebaseEmulatorContainer.ImportExport.EXPORT_ONLY),
+ Optional.of(Set.of("webframeworks")),
Optional.of(true)),
Optional.empty(),
new TestUI(
@@ -89,6 +91,7 @@ void testBuild() {
assertEquals("MY_TOKEN", emulatorConfig.cliArguments().token().orElse(null));
assertEquals("-Xmx", emulatorConfig.cliArguments().javaToolOptions().orElse(null));
assertPathEndsWith("data", emulatorConfig.cliArguments().emulatorData().orElse(null));
+ assertEquals(Set.of("webframeworks"), emulatorConfig.cliArguments().experiments().orElse(null));
assertEquals(FirebaseEmulatorContainer.ImportExport.EXPORT_ONLY, emulatorConfig.cliArguments().importExport());
assertTrue(emulatorConfig.cliArguments().debug());
@@ -174,6 +177,7 @@ record TestCli(
Optional javaToolOptions,
Optional emulatorData,
Optional importExport,
+ Optional> experiments,
Optional debug) implements FirebaseDevServiceConfig.Firebase.Emulator.Cli {
}
diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java
index 77312a87..c0534afa 100644
--- a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java
+++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerCustomConfigTest.java
@@ -10,6 +10,7 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
+import java.util.Set;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
@@ -29,6 +30,7 @@ public class FirebaseEmulatorContainerCustomConfigTest {
firebaseContainer = testContainer.testBuilder()
.withCliArguments()
.withEmulatorData(tempEmulatorDataDir.toPath())
+ .withExperiments(Set.of("webframeworks"))
.done()
.readFromFirebaseJson(new File("src/test/firebase.json").toPath())
.build();
diff --git a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java
index 122cbba3..bf7a85bf 100644
--- a/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java
+++ b/firebase-devservices/deployment/src/test/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainerIntegrationTest.java
@@ -153,6 +153,11 @@ private static void validateEmulatorDataWritten() {
File[] files = emulatorDataDir.listFiles();
assertNotNull(files);
assertTrue(files.length > 0, "Expected files to be present in the emulator data directory");
+
+ // Verify storage files are written
+ File[] storageFiles = new File(emulatorDataDir, "storage_export/blobs").listFiles();
+ assertNotNull(storageFiles);
+ assertTrue(storageFiles.length > 0, "Expected storage files to be present in the storage data directory");
}
@Test
diff --git a/integration-tests/firebase/src/main/resources/application.properties b/integration-tests/firebase/src/main/resources/application.properties
index 7fdb657a..c93b2805 100644
--- a/integration-tests/firebase/src/main/resources/application.properties
+++ b/integration-tests/firebase/src/main/resources/application.properties
@@ -7,3 +7,4 @@ quarkus.google.cloud.devservices.firebase.auth.enabled=true
quarkus.google.cloud.devservices.firebase.firestore.enabled=true
quarkus.google.cloud.devservices.firebase.database.enabled=true
quarkus.google.cloud.devservices.pubsub.enabled=true
+quarkus.google.cloud.devservices.firebase.emulator.cli.experiments=webframeworks
diff --git a/pom.xml b/pom.xml
index ec8b14f1..83111766 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,8 +14,8 @@
3.17.2
3.5.0
- 3.27.1
- 1.45.0-alpha
+ 3.27.2
+ 1.46.0-alpha
scm:git:git@github.com:quarkiverse/quarkus-google-cloud-services.git
diff --git a/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubBuildSteps.java b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubBuildSteps.java
index 13e5741e..bffc8936 100644
--- a/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubBuildSteps.java
+++ b/pubsub/deployment/src/main/java/io/quarkiverse/googlecloudservices/pubsub/deployment/PubSubBuildSteps.java
@@ -1,5 +1,6 @@
package io.quarkiverse.googlecloudservices.pubsub.deployment;
+import io.quarkiverse.googlecloudservices.pubsub.PubSubProducer;
import io.quarkiverse.googlecloudservices.pubsub.QuarkusPubSub;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
@@ -15,6 +16,6 @@ public FeatureBuildItem feature() {
@BuildStep
public AdditionalBeanBuildItem producer() {
- return new AdditionalBeanBuildItem(QuarkusPubSub.class);
+ return new AdditionalBeanBuildItem(QuarkusPubSub.class, PubSubProducer.class);
}
}
diff --git a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java
new file mode 100644
index 00000000..92b3f764
--- /dev/null
+++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java
@@ -0,0 +1,50 @@
+package io.quarkiverse.googlecloudservices.pubsub;
+
+import java.io.IOException;
+
+import jakarta.enterprise.inject.Disposes;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+
+import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
+import com.google.cloud.pubsub.v1.TopicAdminClient;
+
+/**
+ * Producer class for PubSub beans.
+ */
+public class PubSubProducer {
+
+ @Inject
+ QuarkusPubSub quarkusPubSub;
+
+ /**
+ * Makes the subscription admin client available as CDI bean
+ */
+ @Produces
+ public SubscriptionAdminClient subscriptionAdminClient() throws IOException {
+ return SubscriptionAdminClient.create(quarkusPubSub.subscriptionAdminSettings());
+ }
+
+ /**
+ * CDI Dispose method for {@link #subscriptionAdminClient()}. Shouldn't be called directly
+ */
+ public void shutdownSubscriptionAdminClient(@Disposes SubscriptionAdminClient subscriptionAdminClient) {
+ subscriptionAdminClient.close();
+ }
+
+ /**
+ * Makes the topic admin client available as a CDI bean
+ */
+ @Produces
+ public TopicAdminClient topicAdminClient() throws IOException {
+ return TopicAdminClient.create(quarkusPubSub.topicAdminSettings());
+ }
+
+ /**
+ * CDI Dispose method for {@link #topicAdminClient()}. Shouldn't be called directly
+ */
+ public void shutdownTopicAdminClient(@Disposes TopicAdminClient topicAdminClient) {
+ topicAdminClient.close();
+ }
+
+}