Search code examples
javagoogle-cloud-storagecdigoogle-cloud-runquarkus-native

Quarkus native and Google Cloud Storage: "Your default credentials were not found" when injecting Storage via Producer


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?


Solution

  • 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();
        }
    }