From c52fe5b51009b1c32ab2c4eb83b126304be18da1 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Thu, 28 Mar 2024 11:39:02 +0100 Subject: [PATCH 01/26] Producer methods for TopicAdminClient and SubscriptionAdminClient --- .../pubsub/QuarkusPubSub.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java index 30cf3ec9..bc178106 100644 --- a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java @@ -7,6 +7,7 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; import com.google.api.gax.core.CredentialsProvider; @@ -98,6 +99,14 @@ public SubscriptionAdminSettings subscriptionAdminSettings() throws IOException return builder.build(); } + /** + * Makes the subscription admin client available as CDI bean + */ + @Produces + public SubscriptionAdminClient subscriptionAdminClient() throws IOException { + return SubscriptionAdminClient.create(subscriptionAdminSettings()); + } + /** * Creates a PubSub TopicAdminSettings using the configured project ID. */ @@ -108,6 +117,14 @@ public TopicAdminSettings topicAdminSettings() throws IOException { return builder.build(); } + /** + * Makes the topic admin client available as a CDI bean + */ + @Produces + public TopicAdminClient topicAdminClient() throws IOException { + return TopicAdminClient.create(topicAdminSettings()); + } + /** * Creates a PubSub Topic if not already exist, using the configured project ID. */ From 7ae1a3c940f8fe0cfe0a2d51fee2470b561a353d Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Thu, 28 Mar 2024 17:12:25 +0100 Subject: [PATCH 02/26] Producer methods for TopicAdminClient and SubscriptionAdminClient --- .../googlecloudservices/pubsub/QuarkusPubSub.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java index bc178106..3da991ba 100644 --- a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java @@ -7,6 +7,7 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; @@ -107,6 +108,13 @@ public SubscriptionAdminClient subscriptionAdminClient() throws IOException { return SubscriptionAdminClient.create(subscriptionAdminSettings()); } + /** + * CDI Dispose method for {@link #subscriptionAdminClient()}. Shouldn't be called directly + */ + public void shutdownSubscriptionAdminClient(@Disposes SubscriptionAdminClient subscriptionAdminClient) { + subscriptionAdminClient.close(); + } + /** * Creates a PubSub TopicAdminSettings using the configured project ID. */ @@ -125,6 +133,13 @@ public TopicAdminClient topicAdminClient() throws IOException { return TopicAdminClient.create(topicAdminSettings()); } + /** + * CDI Dispose method for {@link #topicAdminClient()}. Shouldn't be called directly + */ + public void shutdownTopicAdminClient(@Disposes TopicAdminClient topicAdminClient) { + topicAdminClient.close(); + } + /** * Creates a PubSub Topic if not already exist, using the configured project ID. */ From ef4fc9a526d762ae7f4ebfc581a5134c0b5370e1 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Fri, 3 Jan 2025 16:14:20 +0100 Subject: [PATCH 03/26] Fixed review comments --- docs/modules/ROOT/pages/pubsub.adoc | 22 +++++++++ .../pubsub/deployment/PubSubBuildSteps.java | 3 +- .../pubsub/PubSubProducer.java | 49 +++++++++++++++++++ .../pubsub/QuarkusPubSub.java | 32 ------------ 4 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java 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/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..967da977 --- /dev/null +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java @@ -0,0 +1,49 @@ +package io.quarkiverse.googlecloudservices.pubsub; + +import com.google.cloud.pubsub.v1.SubscriptionAdminClient; +import com.google.cloud.pubsub.v1.TopicAdminClient; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import java.io.IOException; + +/** + * 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(); + } + +} diff --git a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java index ca1412d0..225439a7 100644 --- a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/QuarkusPubSub.java @@ -7,8 +7,6 @@ import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Disposes; -import jakarta.enterprise.inject.Produces; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -102,21 +100,6 @@ public SubscriptionAdminSettings subscriptionAdminSettings() throws IOException return builder.build(); } - /** - * Makes the subscription admin client available as CDI bean - */ - @Produces - public SubscriptionAdminClient subscriptionAdminClient() throws IOException { - return SubscriptionAdminClient.create(subscriptionAdminSettings()); - } - - /** - * CDI Dispose method for {@link #subscriptionAdminClient()}. Shouldn't be called directly - */ - public void shutdownSubscriptionAdminClient(@Disposes SubscriptionAdminClient subscriptionAdminClient) { - subscriptionAdminClient.close(); - } - /** * Creates a PubSub TopicAdminSettings using the configured project ID. */ @@ -127,21 +110,6 @@ public TopicAdminSettings topicAdminSettings() throws IOException { return builder.build(); } - /** - * Makes the topic admin client available as a CDI bean - */ - @Produces - public TopicAdminClient topicAdminClient() throws IOException { - return TopicAdminClient.create(topicAdminSettings()); - } - - /** - * CDI Dispose method for {@link #topicAdminClient()}. Shouldn't be called directly - */ - public void shutdownTopicAdminClient(@Disposes TopicAdminClient topicAdminClient) { - topicAdminClient.close(); - } - /** * Creates a PubSub Topic if not already exist, using the configured project ID. */ From a5746f0ac608b291abc21f4903edafc535a78b20 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Fri, 3 Jan 2025 16:39:33 +0100 Subject: [PATCH 04/26] Added debug logging to ease detecting and fixing issues --- .../CustomFirebaseConfigReader.java | 16 ++++++++++++++ .../FirebaseEmulatorContainer.java | 21 ++++++++++++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) 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..740cd2fe 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); @@ -154,6 +163,8 @@ private FirebaseEmulatorContainer.HostingConfig readHosting(Object hosting, Path .ofNullable(hostingMap.get("public")) .map(f -> this.resolvePath(f, customFirebaseJson)); + LOGGER.debug("Hosting configured with public directory {}", publicDir); + return new FirebaseEmulatorContainer.HostingConfig( publicDir); } else { @@ -170,6 +181,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 +205,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 31c96003..6e47d4ca 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 @@ -921,6 +921,8 @@ 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); } @@ -933,6 +935,8 @@ 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); } @@ -1036,10 +1040,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 +1055,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 +1070,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"); } From 820bdbe3deeeed721bdb3ac9a87bb3fdbeead41e Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Fri, 3 Jan 2025 17:02:32 +0100 Subject: [PATCH 05/26] Fix reading the firebase.json file from an absolute path, this conflicts with the Firebase hosting detection. --- .../FirebaseEmulatorContainer.java | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) 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 6e47d4ca..b7506104 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 @@ -413,7 +413,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); @@ -943,33 +943,54 @@ public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) { } 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 { From c371f1838cb05f7c9d82c7d8979f48123a291765 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 19:13:16 +0100 Subject: [PATCH 06/26] Add support for firebase experimental features. --- .../deployment/FirebaseDevServiceConfig.java | 7 ++++ .../FirebaseEmulatorConfigBuilder.java | 1 + .../FirebaseEmulatorContainer.java | 40 +++++++++++++++++++ ...baseEmulatorContainerCustomConfigTest.java | 2 + 4 files changed, 50 insertions(+) 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/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/FirebaseEmulatorContainer.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java index b7506104..7908803a 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(Set.of()), false); } @@ -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; } @@ -1122,6 +1150,18 @@ private void initialSetup() { "mkdir -p " + EMULATOR_DATA_PATH + " && " + "mkdir -p " + EMULATOR_EXPORT_PATH + " && " + "chmod 777 -R /srv/*"); + + emulatorConfig.cliArguments() + .experiments() + .ifPresent(experiments -> { + if (!experiments.isEmpty()) { + var enableExperiments = experiments + .stream() + .map(e -> "firebase experiments:enable " + e) + .collect(Collectors.joining(" && ")); + dockerBuilder.run(enableExperiments); + } + }); } private void downloadEmulators() { 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(); From 5bc2cb072edc0d78ae2f4de20ab8be41c553631d Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 19:17:33 +0100 Subject: [PATCH 07/26] Testing the new firebase experimental feature --- .../deployment/FirebaseEmulatorConfigBuilderTest.java | 4 ++++ .../firebase/src/main/resources/application.properties | 1 + 2 files changed, 5 insertions(+) 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/integration-tests/firebase/src/main/resources/application.properties b/integration-tests/firebase/src/main/resources/application.properties index 1e24c613..20a917b0 100644 --- a/integration-tests/firebase/src/main/resources/application.properties +++ b/integration-tests/firebase/src/main/resources/application.properties @@ -6,3 +6,4 @@ quarkus.google.cloud.access-token-enabled=false quarkus.google.cloud.devservices.firebase.auth.enabled=true quarkus.google.cloud.devservices.firebase.firestore.enabled=true quarkus.google.cloud.devservices.pubsub.enabled=true +quarkus.google.cloud.devservices.firebase.cli.experiments=webframeworks From 9a89711b02f8a7d5b364e5ec8cf8d3435dc6fefa Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 19:17:45 +0100 Subject: [PATCH 08/26] Late formatting fix --- .../googlecloudservices/pubsub/PubSubProducer.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 index 967da977..92b3f764 100644 --- a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java @@ -1,12 +1,13 @@ package io.quarkiverse.googlecloudservices.pubsub; -import com.google.cloud.pubsub.v1.SubscriptionAdminClient; -import com.google.cloud.pubsub.v1.TopicAdminClient; +import java.io.IOException; + import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; -import java.io.IOException; +import com.google.cloud.pubsub.v1.SubscriptionAdminClient; +import com.google.cloud.pubsub.v1.TopicAdminClient; /** * Producer class for PubSub beans. From f50db32c48efae4c70f8994a98774dcfadd7e93a Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 19:27:25 +0100 Subject: [PATCH 09/26] Docs updated --- ...arkus-google-cloud-firebase-devservices.adoc | 17 +++++++++++++++++ ...oud-firebase-devservices_quarkus.google.adoc | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) 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] From 38ecea45ac05bf5cd5d98e5643d497ee49e9e566 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 19:57:49 +0100 Subject: [PATCH 10/26] Added logging Fixed application property --- .../deployment/testcontainers/FirebaseEmulatorContainer.java | 4 +++- .../firebase/src/main/resources/application.properties | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) 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 7908803a..1e823aaa 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 @@ -202,7 +202,7 @@ public record CliArgumentsConfig( Optional.empty(), Optional.empty(), ImportExport.IMPORT_EXPORT, - Optional.of(Set.of()), + Optional.of(new HashSet<>()), false); } @@ -1155,6 +1155,8 @@ private void initialSetup() { .experiments() .ifPresent(experiments -> { if (!experiments.isEmpty()) { + LOGGER.debug("Firebase experiments found, enabling experiments: {}", String.join(",", experiments)); + var enableExperiments = experiments .stream() .map(e -> "firebase experiments:enable " + e) diff --git a/integration-tests/firebase/src/main/resources/application.properties b/integration-tests/firebase/src/main/resources/application.properties index 20a917b0..67e2656b 100644 --- a/integration-tests/firebase/src/main/resources/application.properties +++ b/integration-tests/firebase/src/main/resources/application.properties @@ -6,4 +6,4 @@ quarkus.google.cloud.access-token-enabled=false quarkus.google.cloud.devservices.firebase.auth.enabled=true quarkus.google.cloud.devservices.firebase.firestore.enabled=true quarkus.google.cloud.devservices.pubsub.enabled=true -quarkus.google.cloud.devservices.firebase.cli.experiments=webframeworks +quarkus.google.cloud.devservices.firebase.emulator.cli.experiments=webframeworks From 5a23f3bc6309f0c688640d504530d66619e7a4a6 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 20:29:51 +0100 Subject: [PATCH 11/26] Use env variable to enable experiments --- .../FirebaseEmulatorContainer.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) 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 1e823aaa..866340ed 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 @@ -1051,6 +1051,7 @@ public ImageFromDockerfile build() { this.initialSetup(); this.authenticateToFirebase(); this.setupJavaToolOptions(); + this.setupExperiments(); this.setupUserAndGroup(); this.downloadEmulators(); this.addFirebaseJson(); @@ -1150,20 +1151,6 @@ private void initialSetup() { "mkdir -p " + EMULATOR_DATA_PATH + " && " + "mkdir -p " + EMULATOR_EXPORT_PATH + " && " + "chmod 777 -R /srv/*"); - - emulatorConfig.cliArguments() - .experiments() - .ifPresent(experiments -> { - if (!experiments.isEmpty()) { - LOGGER.debug("Firebase experiments found, enabling experiments: {}", String.join(",", experiments)); - - var enableExperiments = experiments - .stream() - .map(e -> "firebase experiments:enable " + e) - .collect(Collectors.joining(" && ")); - dockerBuilder.run(enableExperiments); - } - }); } private void downloadEmulators() { @@ -1195,6 +1182,15 @@ 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); From 1191bf99bda84a852b3f112d4cab1c2401d80ca9 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 20:38:40 +0100 Subject: [PATCH 12/26] Also allow reading from the source attribute (this is needed to support webframeworks) --- .../deployment/testcontainers/CustomFirebaseConfigReader.java | 1 + 1 file changed, 1 insertion(+) 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 740cd2fe..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 @@ -161,6 +161,7 @@ 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); From 695871eeb7d14c0417a4ca9ad01e00d86f5c9924 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sat, 4 Jan 2025 21:10:42 +0100 Subject: [PATCH 13/26] Changed to read-write mounting. Needed for experimental features. --- .../deployment/testcontainers/FirebaseEmulatorContainer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 866340ed..d54cb49b 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 @@ -952,7 +952,7 @@ public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) { 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)) { @@ -966,7 +966,7 @@ public FirebaseEmulatorContainer(EmulatorConfig emulatorConfig) { 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); } } From 49c7c998c4abd6d87d151f7dbceadff50aa09c77 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sun, 5 Jan 2025 13:45:15 +0100 Subject: [PATCH 14/26] - Workaround added for upstream bug in Firebase-tools - Switched around the ordering of commands to avoid permission denied issues at filesystem level. --- .../FirebaseEmulatorContainer.java | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) 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 d54cb49b..5d8d406a 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 @@ -1051,15 +1051,15 @@ public ImageFromDockerfile build() { this.initialSetup(); this.authenticateToFirebase(); this.setupJavaToolOptions(); - this.setupExperiments(); - 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; @@ -1192,7 +1192,26 @@ private void setupExperiments() { } 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, @@ -1294,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") From ffcc8eda83dd52694438381958607b7a30d7969d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 06:49:37 +0000 Subject: [PATCH 15/26] Bump org.assertj:assertj-core from 3.27.1 to 3.27.2 Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.1 to 3.27.2. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.1...assertj-build-3.27.2) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b5289034..66090c80 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ 3.17.2 3.5.0 - 3.27.1 + 3.27.2 1.45.0-alpha From 5c6f2fe7defc5045d13fc01f5055d41b1f3e4dd3 Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Thu, 9 Jan 2025 15:38:44 -0300 Subject: [PATCH 16/26] Remove yq from quarkus-snapshot.yaml --- .github/workflows/quarkus-snapshot.yaml | 3 --- .../googlecloudservices/pubsub/PubSubProducer.java | 7 ++++--- 2 files changed, 4 insertions(+), 6 deletions(-) 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/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java index 967da977..92b3f764 100644 --- a/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java +++ b/pubsub/runtime/src/main/java/io/quarkiverse/googlecloudservices/pubsub/PubSubProducer.java @@ -1,12 +1,13 @@ package io.quarkiverse.googlecloudservices.pubsub; -import com.google.cloud.pubsub.v1.SubscriptionAdminClient; -import com.google.cloud.pubsub.v1.TopicAdminClient; +import java.io.IOException; + import jakarta.enterprise.inject.Disposes; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; -import java.io.IOException; +import com.google.cloud.pubsub.v1.SubscriptionAdminClient; +import com.google.cloud.pubsub.v1.TopicAdminClient; /** * Producer class for PubSub beans. From 97dce6ce500a19f6908a4b54efbc49106232f141 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:46:24 +0000 Subject: [PATCH 17/26] Bump com.google.firebase:firebase-admin from 9.4.2 to 9.4.3 Bumps [com.google.firebase:firebase-admin](https://github.com/firebase/firebase-admin-java) from 9.4.2 to 9.4.3. - [Release notes](https://github.com/firebase/firebase-admin-java/releases) - [Commits](https://github.com/firebase/firebase-admin-java/compare/v9.4.2...v9.4.3) --- updated-dependencies: - dependency-name: com.google.firebase:firebase-admin dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/pom.xml b/bom/pom.xml index b0d4b19d..08278900 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 From d125683b0fdb21ef733136db72a681a8758ad9db Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Fri, 10 Jan 2025 13:43:22 +0100 Subject: [PATCH 18/26] Some additional documentation to work with the devservices. (#730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Some additional documentation to work with the devservices. * Update docs/modules/ROOT/pages/firebase-devservices.adoc --------- Co-authored-by: Loïc Mathieu --- .../ROOT/pages/firebase-devservices.adoc | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) 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. From 736071009ab1b63dde1930d1a624df3f74b2d0dd Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sun, 12 Jan 2025 23:44:12 +0100 Subject: [PATCH 19/26] Fix for config property for cloud storage. This needs to include "http://" --- .../deployment/testcontainers/FirebaseEmulatorContainer.java | 4 ++++ 1 file changed, 4 insertions(+) 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 5d8d406a..6c10f7bf 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 @@ -1479,6 +1479,10 @@ private void writeOutputFrame(OutputFrame frame, Level level) { } private String getEmulatorEndpoint(Emulator emulator) { + if (emulator.equals(Emulator.CLOUD_STORAGE)) { + return "http://" + this.getHost() + ":" + emulatorPort(emulator); + } + return this.getHost() + ":" + emulatorPort(emulator); } } From 28c3b51874f582742810e3defecccb221daf71ba Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Sun, 12 Jan 2025 23:47:26 +0100 Subject: [PATCH 20/26] Fix handling of import and added logging --- .../testcontainers/FirebaseEmulatorContainer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 6c10f7bf..51ec8cfa 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 @@ -1327,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() @@ -1346,6 +1348,8 @@ private void runExecutable() { } if (emulatorConfig.cliArguments().importExport.isDoExport()) { + LOGGER.debug("Export requested. Saving data on exit"); + emulatorConfig .cliArguments() .emulatorData() From a4314c3e91fab47c90d334944231a12ddc7736b7 Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Mon, 13 Jan 2025 00:41:08 +0100 Subject: [PATCH 21/26] Validate storage files are being written at exit --- .../FirebaseEmulatorContainerIntegrationTest.java | 5 +++++ 1 file changed, 5 insertions(+) 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 From 733470a3193d6874c3c54b1b875eb1566b22afee Mon Sep 17 00:00:00 2001 From: Jeroen Benckhuijsen Date: Mon, 13 Jan 2025 00:45:52 +0100 Subject: [PATCH 22/26] Fix that the docker container is actually being stopped and export commands can run when the dev service is shutdown. --- .../deployment/FirebaseDevServiceProcessor.java | 16 ++++++++++++---- .../FirebaseEmulatorContainer.java | 11 +++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) 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 3f39cf72..eaae2547 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/testcontainers/FirebaseEmulatorContainer.java b/firebase-devservices/deployment/src/main/java/io/quarkiverse/googlecloudservices/firebase/deployment/testcontainers/FirebaseEmulatorContainer.java index 51ec8cfa..bde36472 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 @@ -1403,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. */ From da977023c9dd088f7f78bd9a995130747901d404 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 06:33:05 +0000 Subject: [PATCH 23/26] Bump io.opentelemetry:opentelemetry-opencensus-shim Bumps [io.opentelemetry:opentelemetry-opencensus-shim](https://github.com/open-telemetry/opentelemetry-java) from 1.45.0-alpha to 1.46.0-alpha. - [Release notes](https://github.com/open-telemetry/opentelemetry-java/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-java/commits) --- updated-dependencies: - dependency-name: io.opentelemetry:opentelemetry-opencensus-shim dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 66090c80..f6f42c6e 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 3.17.2 3.5.0 3.27.2 - 1.45.0-alpha + 1.46.0-alpha scm:git:git@github.com:quarkiverse/quarkus-google-cloud-services.git From 9d886b1aa46d24fa208a0e413c46785b347a87e2 Mon Sep 17 00:00:00 2001 From: Jon-Erik Liw Date: Fri, 10 Jan 2025 18:03:46 +0100 Subject: [PATCH 24/26] fix: Updated firebase-admin.adoc to correct typo in property name --- docs/modules/ROOT/pages/firebase-admin.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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[] From 76c4ba95089eab72c065cfc0e44f637a764ac8cc Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:19:39 +0000 Subject: [PATCH 25/26] docs: update README.md [skip ci] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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
wabrit

💻 + Jon-Erik Liw
Jon-Erik Liw

📖 From 6fa6c0bf5087abdbdff9e621885ff8bf6b3158b5 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 10:19:40 +0000 Subject: [PATCH 26/26] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) 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,