In my Quarkus service project I'm using Google Cloud Storage (GCS) to read from and write to GCS buckets. Recently I have refactored the code regarding GCS access to be able to unit test my service via Google's fake implementation of LocalStorageHelper
.
Here is my CDI producer class that yields an implementation of interface Storage
for both unit test mode or everything else:
@Slf4j
@ApplicationScoped
public class GcpStorageProducer {
// Contains my custom Quarkus configuration items, used for GCP project ID here
@Inject AppRuntime runtime;
@Produces
@ApplicationScoped
@IfBuildProfile("test") // Enabled for tests
public Storage testStorage() {
log.info("Producing GCS service bean for unit test environment");
return LocalStorageHelper.getOptions()
.getService();
}
@Produces
@DefaultBean
@ApplicationScoped
public Storage realStorage() throws IOException { // Default storage when not in tests
log.info("Producing GCS service bean with GCP project {}", runtime.gcpProject());
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
if (credentials.createScopedRequired()) {
credentials = credentials.createScoped("https://www.googleapis.com/auth/devstorage.read_write");
}
return StorageOptions.newBuilder()
.setProjectId(runtime.gcpProject())
.setCredentials(credentials)
.build()
.getService();
}
}
The injection point is trivial:
@Inject Storage storage;
This works as expected in unit tests and in Quarkus JVM mode both locally and on Google Cloud Run. However, in native mode compiled with JDK 17 and Mandrel 23.0.1.2-Final on Google Cloud Run this solution yields the exception
java.io.IOException: Your default credentials were not found. To set up Application Default Credentials for your environment, see https://cloud.google.com/docs/authentication/external/set-up-adc.
I tested without explicit credential setting but that lead to similar results.
To my surprise the code before refactoring with inline Storage
service instantiation works both in native and JVM mode on Cloud Run, it looks as follows like in every guide you find online or at Google GCS docs but is not testable (at least I think):
// ... some code before
Storage storage = StorageOptions.newBuilder()
.setProjectId(runtime.gcpProject())
.build()
.getService();
Blob objectToLoad = storage.get(runtime.sourceBucketName(), filename);
// ... some code after
How are the "Application Default Credentials" not detected when using injection with native binary in Cloud Run?
I've been able to make it work, it seems the dependency com.google.cloud:google-cloud-nio
mixes up something when building a native application.
The solution in my case is to move this dependency to the Maven test scope. I also include the Quarkus Google Cloud Storage extension in compile scope instead of having my own CDI producer for Storage
.
For Quarkus unit tests with @QuarkusTest
I now use a CDI alternative (with Quarkus' @Mock
annotation) in src/test/java
that uses the LocalStorageHelper
as before:
@ApplicationScoped
public class GcpStorageAlternative {
@Mock
@Produces
@ApplicationScoped
public Storage getTestStorage() {
return LocalStorageHelper.customOptions(true)
.getService();
}
}