From a28b0bef68c93d6a5d31b6684a054c31de8f2d3a Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Fri, 17 Apr 2026 15:31:53 +0000 Subject: [PATCH 1/7] feat(gcp-auth-extension): Support custom credentials via configuration properties --- .../contrib/gcp/auth/ConfigurableOption.java | 14 ++- ...thAutoConfigurationCustomizerProvider.java | 46 ++++++-- ...toConfigurationCustomizerProviderTest.java | 102 ++++++++++++++++++ 3 files changed, 152 insertions(+), 10 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index aba0ddf118..0e25f4a7d3 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -52,7 +52,19 @@ enum ConfigurableOption { * configured using the environment variable `GOOGLE_OTEL_AUTH_TARGET_SIGNALS` or the system * property `google.otel.auth.target.signals`. */ - GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Authentication Extension"); + GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Authentication Extension"), + + /** + * Represents the Google Cloud Credentials Path option. Can be configured using the environment + * variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property `google.cloud.credentials.path`. + */ + GOOGLE_CLOUD_CREDENTIALS_PATH("Google Cloud Credentials Path"), + + /** + * Represents the Google Cloud Credentials JSON option. Can be configured using the environment + * variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property `google.cloud.credentials.json`. + */ + GOOGLE_CLOUD_CREDENTIALS_JSON("Google Cloud Credentials JSON String"); private final String userReadableName; private final String environmentVariableName; diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index dfde30796c..5a42295511 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -29,7 +29,10 @@ import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Objects; @@ -99,22 +102,47 @@ public class GcpAuthAutoConfigurationCustomizerProvider */ @Override public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { - GoogleCredentials credentials; - try { - credentials = GoogleCredentials.getApplicationDefault(); - } catch (IOException e) { - throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); - } autoConfiguration .addSpanExporterCustomizer( (spanExporter, configProperties) -> - customizeSpanExporter(spanExporter, credentials, configProperties)) + customizeSpanExporter(spanExporter, resolveCredentials(configProperties), configProperties)) .addMetricExporterCustomizer( (metricExporter, configProperties) -> - customizeMetricExporter(metricExporter, credentials, configProperties)) + customizeMetricExporter(metricExporter, resolveCredentials(configProperties), configProperties)) .addResourceCustomizer( (resource, configProperties) -> - customizeResource(resource, credentials, configProperties)); + customizeResource(resource, resolveCredentials(configProperties), configProperties)); + } + + private static GoogleCredentials resolveCredentials(ConfigProperties configProperties) { + Optional credsPath = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional( + configProperties); + if (credsPath.isPresent()) { + try (FileInputStream fis = new FileInputStream(credsPath.get())) { + return GoogleCredentials.fromStream(fis); + } catch (IOException e) { + throw new ConfigurationException("Failed to load credentials from file: " + credsPath.get(), e); + } + } + + Optional credsJson = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional( + configProperties); + if (credsJson.isPresent()) { + try (ByteArrayInputStream bais = + new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { + return GoogleCredentials.fromStream(bais); + } catch (IOException e) { + throw new ConfigurationException("Failed to load credentials from JSON string", e); + } + } + + try { + return GoogleCredentials.getApplicationDefault(); + } catch (IOException e) { + throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); + } } @Override diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 79b070737d..a288939f19 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -51,7 +51,12 @@ import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.time.Duration; import java.time.Instant; import java.util.AbstractMap.SimpleEntry; @@ -243,6 +248,103 @@ void testTraceCustomizerOtlpGrpc() { } } + @Test + void testCredentialsLoadedFromFilePath() throws IOException { + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + + File tempFile = File.createTempFile("gcp-creds", ".json"); + tempFile.deleteOnExit(); + try (Writer writer = Files.newBufferedWriter(tempFile.toPath(), StandardCharsets.UTF_8)) { + writer.write("{}"); + } + + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), tempFile.getAbsolutePath()); + + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify(() -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty()); + } + } + + @Test + void testCredentialsLoadedFromJsonString() throws IOException { + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty(), "{}"); + + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify(() -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty()); + } + } + + @Test + void testFallbackToApplicationDefaultCredentials() throws IOException { + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify(GoogleCredentials::getApplicationDefault, Mockito.atLeastOnce()); + } + } + // TODO: Use parameterized test for testing metrics customizer for http & grpc. @Test void testMetricCustomizerOtlpHttp() { From a25296cc4a0c98d882fc512b640a2d58e472a16e Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Fri, 17 Apr 2026 15:57:25 +0000 Subject: [PATCH 2/7] chore: refactor based on pr review --- .../contrib/gcp/auth/ConfigurableOption.java | 6 +- ...thAutoConfigurationCustomizerProvider.java | 70 ++++--- ...toConfigurationCustomizerProviderTest.java | 189 ++++++++++++------ 3 files changed, 171 insertions(+), 94 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index 0e25f4a7d3..8c6b846e71 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -56,13 +56,15 @@ enum ConfigurableOption { /** * Represents the Google Cloud Credentials Path option. Can be configured using the environment - * variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property `google.cloud.credentials.path`. + * variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property + * `google.cloud.credentials.path`. */ GOOGLE_CLOUD_CREDENTIALS_PATH("Google Cloud Credentials Path"), /** * Represents the Google Cloud Credentials JSON option. Can be configured using the environment - * variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property `google.cloud.credentials.json`. + * variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property + * `google.cloud.credentials.json`. */ GOOGLE_CLOUD_CREDENTIALS_JSON("Google Cloud Credentials JSON String"); diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 5a42295511..72c1454193 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -30,13 +30,16 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; @@ -60,6 +63,8 @@ public class GcpAuthAutoConfigurationCustomizerProvider private static final Logger logger = Logger.getLogger(GcpAuthAutoConfigurationCustomizerProvider.class.getName()); + private static final Map credentialsCache = + Collections.synchronizedMap(new WeakHashMap<>()); private static final String SIGNAL_TARGET_WARNING_FIX_SUGGESTION = String.format( "You may safely ignore this warning if it is intentional, otherwise please configure the '%s' by exporting valid values to environment variable: %s or by setting valid values in system property: %s.", @@ -105,44 +110,51 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { autoConfiguration .addSpanExporterCustomizer( (spanExporter, configProperties) -> - customizeSpanExporter(spanExporter, resolveCredentials(configProperties), configProperties)) + customizeSpanExporter( + spanExporter, resolveCredentials(configProperties), configProperties)) .addMetricExporterCustomizer( (metricExporter, configProperties) -> - customizeMetricExporter(metricExporter, resolveCredentials(configProperties), configProperties)) + customizeMetricExporter( + metricExporter, resolveCredentials(configProperties), configProperties)) .addResourceCustomizer( (resource, configProperties) -> - customizeResource(resource, resolveCredentials(configProperties), configProperties)); + customizeResource( + resource, resolveCredentials(configProperties), configProperties)); } private static GoogleCredentials resolveCredentials(ConfigProperties configProperties) { - Optional credsPath = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional( - configProperties); - if (credsPath.isPresent()) { - try (FileInputStream fis = new FileInputStream(credsPath.get())) { - return GoogleCredentials.fromStream(fis); - } catch (IOException e) { - throw new ConfigurationException("Failed to load credentials from file: " + credsPath.get(), e); - } - } + return credentialsCache.computeIfAbsent( + configProperties, + props -> { + Optional credsPath = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(props); + if (credsPath.isPresent()) { + try (FileInputStream fis = new FileInputStream(credsPath.get())) { + return GoogleCredentials.fromStream(fis); + } catch (IOException e) { + throw new ConfigurationException( + "Failed to load credentials from file: " + new File(credsPath.get()).getName(), + e); + } + } - Optional credsJson = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional( - configProperties); - if (credsJson.isPresent()) { - try (ByteArrayInputStream bais = - new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { - return GoogleCredentials.fromStream(bais); - } catch (IOException e) { - throw new ConfigurationException("Failed to load credentials from JSON string", e); - } - } + Optional credsJson = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(props); + if (credsJson.isPresent()) { + try (ByteArrayInputStream bais = + new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { + return GoogleCredentials.fromStream(bais); + } catch (IOException e) { + throw new ConfigurationException("Failed to load credentials from JSON string", e); + } + } - try { - return GoogleCredentials.getApplicationDefault(); - } catch (IOException e) { - throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); - } + try { + return GoogleCredentials.getApplicationDefault(); + } catch (IOException e) { + throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); + } + }); } @Override diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index a288939f19..086bcdc5ff 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -250,39 +250,44 @@ void testTraceCustomizerOtlpGrpc() { @Test void testCredentialsLoadedFromFilePath() throws IOException { - System.setProperty( - ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); - System.setProperty( - ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); - File tempFile = File.createTempFile("gcp-creds", ".json"); tempFile.deleteOnExit(); try (Writer writer = Files.newBufferedWriter(tempFile.toPath(), StandardCharsets.UTF_8)) { - writer.write("{}"); + writer.write("{}"); } - - System.setProperty( - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), tempFile.getAbsolutePath()); - - prepareMockBehaviorForGoogleCredentials(); - OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = - Mockito.spy(OtlpGrpcSpanExporter.builder()); - List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporter( - mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); - try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) - .thenReturn(mockedGoogleCredentials); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), + tempFile.getAbsolutePath()); - buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - - googleCredentialsMockedStatic.verify(() -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + try { + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify( + () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + } } finally { - System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty()); } } @@ -292,29 +297,32 @@ void testCredentialsLoadedFromJsonString() throws IOException { ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); System.setProperty( ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); - - System.setProperty( - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty(), "{}"); - - prepareMockBehaviorForGoogleCredentials(); - OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = - Mockito.spy(OtlpGrpcSpanExporter.builder()); - List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporter( - mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); - - try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) - .thenReturn(mockedGoogleCredentials); + System.setProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty(), "{}"); - buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - - googleCredentialsMockedStatic.verify(() -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + try { + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify( + () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + } } finally { - System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty()); } } @@ -324,24 +332,79 @@ void testFallbackToApplicationDefaultCredentials() throws IOException { ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); System.setProperty( ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); - - prepareMockBehaviorForGoogleCredentials(); - OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = - Mockito.spy(OtlpGrpcSpanExporter.builder()); - List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporter( - mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); - try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(GoogleCredentials::getApplicationDefault) - .thenReturn(mockedGoogleCredentials); + try { + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify( + GoogleCredentials::getApplicationDefault, Mockito.atLeastOnce()); + googleCredentialsMockedStatic.verify( + () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.never()); + } + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + } + } + + @Test + void testCredentialsPathTakesPrecedenceOverJson() throws IOException { + File tempFile = File.createTempFile("gcp-creds", ".json"); + tempFile.deleteOnExit(); + try (Writer writer = Files.newBufferedWriter(tempFile.toPath(), StandardCharsets.UTF_8)) { + writer.write("{}"); + } - buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - - googleCredentialsMockedStatic.verify(GoogleCredentials::getApplicationDefault, Mockito.atLeastOnce()); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), + tempFile.getAbsolutePath()); + System.setProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty(), "{}"); + + try { + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + .thenReturn(mockedGoogleCredentials); + + buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + + googleCredentialsMockedStatic.verify( + () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + googleCredentialsMockedStatic.verify( + GoogleCredentials::getApplicationDefault, Mockito.never()); + } + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty()); } } From ef176b58590f8cc72faab125ffca54f5d3973731 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Fri, 17 Apr 2026 18:11:31 +0200 Subject: [PATCH 3/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ...thAutoConfigurationCustomizerProvider.java | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 72c1454193..12a656cbb6 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -123,38 +123,46 @@ metricExporter, resolveCredentials(configProperties), configProperties)) } private static GoogleCredentials resolveCredentials(ConfigProperties configProperties) { - return credentialsCache.computeIfAbsent( - configProperties, - props -> { - Optional credsPath = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(props); - if (credsPath.isPresent()) { - try (FileInputStream fis = new FileInputStream(credsPath.get())) { - return GoogleCredentials.fromStream(fis); - } catch (IOException e) { - throw new ConfigurationException( - "Failed to load credentials from file: " + new File(credsPath.get()).getName(), - e); - } - } + synchronized (credentialsCache) { + GoogleCredentials cachedCredentials = credentialsCache.get(configProperties); + if (cachedCredentials != null) { + return cachedCredentials; + } + + GoogleCredentials resolvedCredentials = loadCredentials(configProperties); + credentialsCache.put(configProperties, resolvedCredentials); + return resolvedCredentials; + } + } + + private static GoogleCredentials loadCredentials(ConfigProperties props) { + Optional credsPath = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(props); + if (credsPath.isPresent()) { + try (FileInputStream fis = new FileInputStream(credsPath.get())) { + return GoogleCredentials.fromStream(fis); + } catch (IOException e) { + throw new ConfigurationException( + "Failed to load credentials from file: " + new File(credsPath.get()).getName(), e); + } + } - Optional credsJson = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(props); - if (credsJson.isPresent()) { - try (ByteArrayInputStream bais = - new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { - return GoogleCredentials.fromStream(bais); - } catch (IOException e) { - throw new ConfigurationException("Failed to load credentials from JSON string", e); - } - } + Optional credsJson = + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(props); + if (credsJson.isPresent()) { + try (ByteArrayInputStream bais = + new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { + return GoogleCredentials.fromStream(bais); + } catch (IOException e) { + throw new ConfigurationException("Failed to load credentials from JSON string", e); + } + } - try { - return GoogleCredentials.getApplicationDefault(); - } catch (IOException e) { - throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); - } - }); + try { + return GoogleCredentials.getApplicationDefault(); + } catch (IOException e) { + throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e); + } } @Override From c5cafe213eeee2919817e79af0ac972e93153295 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Thu, 7 May 2026 14:13:48 +0000 Subject: [PATCH 4/7] chore: address pr feedback --- .../contrib/gcp/auth/ConfigurableOption.java | 10 +- ...thAutoConfigurationCustomizerProvider.java | 31 +++--- .../contrib/gcp/auth/GoogleAuthException.java | 4 +- ...toConfigurationCustomizerProviderTest.java | 100 ++++++++++++++---- 4 files changed, 106 insertions(+), 39 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index 8c6b846e71..c63b47a17f 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -55,15 +55,17 @@ enum ConfigurableOption { GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Authentication Extension"), /** - * Represents the Google Cloud Credentials Path option. Can be configured using the environment - * variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property + * Specifies the path to a Google Cloud service account JSON key file. The path can be either + * absolute or relative to the current working directory. Can be configured using the + * environment variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property * `google.cloud.credentials.path`. */ GOOGLE_CLOUD_CREDENTIALS_PATH("Google Cloud Credentials Path"), /** - * Represents the Google Cloud Credentials JSON option. Can be configured using the environment - * variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property + * Specifies the raw JSON content of a Google Cloud service account key. This is useful when + * credentials are not stored in a file but are available as a string. Can be configured using + * the environment variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property * `google.cloud.credentials.json`. */ GOOGLE_CLOUD_CREDENTIALS_JSON("Google Cloud Credentials JSON String"); diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 12a656cbb6..24bc02042e 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -11,6 +11,7 @@ import static java.util.stream.Collectors.toMap; import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.auto.service.AutoService; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason; @@ -49,8 +50,9 @@ * integration. * *

This class is registered as a service provider using {@link AutoService} and is responsible - * for customizing the OpenTelemetry configuration for GCP specific behavior. It retrieves Google - * Application Default Credentials (ADC) and adds them as authorization headers to the configured + * for customizing the OpenTelemetry configuration for GCP specific behavior. It retrieves + * credentials (either explicit service account keys from configuration or falling back to + * Application Default Credentials (ADC)) and adds them as authorization headers to the configured * {@link SpanExporter}. It also sets default properties and resource attributes for GCP * integration. * @@ -83,8 +85,8 @@ public class GcpAuthAutoConfigurationCustomizerProvider * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to * GCP Telemetry API are possible from the configured OTLP exporter. * - *

This method attempts to retrieve Google Application Default Credentials (ADC) and performs - * the following: + *

This method attempts to retrieve credentials (either from user-specified configuration or + * falling back to ADC) and performs the following: * *

    *
  • Verifies whether the configured OTLP endpoint (base or signal specific) is a known GCP @@ -135,26 +137,29 @@ private static GoogleCredentials resolveCredentials(ConfigProperties configPrope } } - private static GoogleCredentials loadCredentials(ConfigProperties props) { + private static GoogleCredentials loadCredentials(ConfigProperties configProperties) { Optional credsPath = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(props); + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(configProperties); if (credsPath.isPresent()) { - try (FileInputStream fis = new FileInputStream(credsPath.get())) { - return GoogleCredentials.fromStream(fis); + File file = new File(credsPath.get()); + if (!file.exists()) { + throw new ConfigurationException("Credentials file not found: " + file.getName()); + } + try (FileInputStream fis = new FileInputStream(file)) { + return ServiceAccountCredentials.fromStream(fis); } catch (IOException e) { - throw new ConfigurationException( - "Failed to load credentials from file: " + new File(credsPath.get()).getName(), e); + throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); } } Optional credsJson = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(props); + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(configProperties); if (credsJson.isPresent()) { try (ByteArrayInputStream bais = new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { - return GoogleCredentials.fromStream(bais); + return ServiceAccountCredentials.fromStream(bais); } catch (IOException e) { - throw new ConfigurationException("Failed to load credentials from JSON string", e); + throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); } } diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GoogleAuthException.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GoogleAuthException.java index 2f6335f528..1303e10b79 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GoogleAuthException.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GoogleAuthException.java @@ -29,7 +29,9 @@ enum Reason { /** Indicates a failure to retrieve Google Application Default Credentials. */ FAILED_ADC_RETRIEVAL("Unable to retrieve Google Application Default Credentials."), /** Indicates a failure to retrieve Google Application Default Credentials. */ - FAILED_ADC_REFRESH("Unable to refresh Google Application Default Credentials."); + FAILED_ADC_REFRESH("Unable to refresh Google Application Default Credentials."), + /** Indicates a failure to create credentials from provided source. */ + FAILED_CREDENTIAL_CREATION("Unable to create credentials from provided source."); private final String message; diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 086bcdc5ff..ffecdcb76f 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -18,6 +18,7 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeKey; @@ -96,7 +97,7 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { private static final String DUMMY_GCP_QUOTA_PROJECT_ID = "my-gcp-quota-project-id"; private static final Random TEST_RANDOM = new Random(); - @Mock private GoogleCredentials mockedGoogleCredentials; + @Mock private ServiceAccountCredentials mockedGoogleCredentials; @Captor private ArgumentCaptor>> traceHeaderSupplierCaptor; @Captor private ArgumentCaptor>> metricHeaderSupplierCaptor; @@ -273,16 +274,16 @@ void testCredentialsLoadedFromFilePath() throws IOException { configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); - try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + try (MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + serviceAccountCredentialsMockedStatic + .when(() -> ServiceAccountCredentials.fromStream(any(InputStream.class))) .thenReturn(mockedGoogleCredentials); buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - googleCredentialsMockedStatic.verify( - () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + serviceAccountCredentialsMockedStatic.verify( + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); @@ -308,16 +309,16 @@ void testCredentialsLoadedFromJsonString() throws IOException { configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); - try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + try (MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + serviceAccountCredentialsMockedStatic + .when(() -> ServiceAccountCredentials.fromStream(any(InputStream.class))) .thenReturn(mockedGoogleCredentials); buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - googleCredentialsMockedStatic.verify( - () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + serviceAccountCredentialsMockedStatic.verify( + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); @@ -343,7 +344,10 @@ void testFallbackToApplicationDefaultCredentials() throws IOException { mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { + Mockito.mockStatic(GoogleCredentials.class); + MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + googleCredentialsMockedStatic .when(GoogleCredentials::getApplicationDefault) .thenReturn(mockedGoogleCredentials); @@ -352,8 +356,8 @@ void testFallbackToApplicationDefaultCredentials() throws IOException { googleCredentialsMockedStatic.verify( GoogleCredentials::getApplicationDefault, Mockito.atLeastOnce()); - googleCredentialsMockedStatic.verify( - () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.never()); + serviceAccountCredentialsMockedStatic.verify( + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.never()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); @@ -388,15 +392,18 @@ void testCredentialsPathTakesPrecedenceOverJson() throws IOException { mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class)) { - googleCredentialsMockedStatic - .when(() -> GoogleCredentials.fromStream(any(InputStream.class))) + Mockito.mockStatic(GoogleCredentials.class); + MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + + serviceAccountCredentialsMockedStatic + .when(() -> ServiceAccountCredentials.fromStream(any(InputStream.class))) .thenReturn(mockedGoogleCredentials); buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - googleCredentialsMockedStatic.verify( - () -> GoogleCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + serviceAccountCredentialsMockedStatic.verify( + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); googleCredentialsMockedStatic.verify( GoogleCredentials::getApplicationDefault, Mockito.never()); } @@ -408,6 +415,57 @@ void testCredentialsPathTakesPrecedenceOverJson() throws IOException { } } + @Test + void testCredentialsFileNodeFoundThrowsConfigurationException() { + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), "/non/existent/path.json"); + + try { + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + + assertThatThrownBy(() -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("Credentials file not found"); + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty()); + } + } + + @Test + void testMalformedCredentialsJsonThrowsGoogleAuthException() { + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty(), "malformed_json"); + + try { + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + + try (MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + serviceAccountCredentialsMockedStatic + .when(() -> ServiceAccountCredentials.fromStream(any(InputStream.class))) + .thenThrow(new IOException("Malformed JSON")); + + assertThatThrownBy(() -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)) + .isInstanceOf(GoogleAuthException.class) + .hasMessageContaining(GoogleAuthException.Reason.FAILED_CREDENTIAL_CREATION.getMessage()); + } + } finally { + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getSystemProperty()); + } + } + // TODO: Use parameterized test for testing metrics customizer for http & grpc. @Test void testMetricCustomizerOtlpHttp() { From d228216ef753c6773782aebce470e396484d3a7f Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Thu, 7 May 2026 14:16:48 +0000 Subject: [PATCH 5/7] chore: run spotless --- .../contrib/gcp/auth/ConfigurableOption.java | 8 ++--- ...thAutoConfigurationCustomizerProvider.java | 6 ++-- ...toConfigurationCustomizerProviderTest.java | 35 +++++++++++-------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index c63b47a17f..266156f829 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -56,16 +56,16 @@ enum ConfigurableOption { /** * Specifies the path to a Google Cloud service account JSON key file. The path can be either - * absolute or relative to the current working directory. Can be configured using the - * environment variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property + * absolute or relative to the current working directory. Can be configured using the environment + * variable `GOOGLE_CLOUD_CREDENTIALS_PATH` or the system property * `google.cloud.credentials.path`. */ GOOGLE_CLOUD_CREDENTIALS_PATH("Google Cloud Credentials Path"), /** * Specifies the raw JSON content of a Google Cloud service account key. This is useful when - * credentials are not stored in a file but are available as a string. Can be configured using - * the environment variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property + * credentials are not stored in a file but are available as a string. Can be configured using the + * environment variable `GOOGLE_CLOUD_CREDENTIALS_JSON` or the system property * `google.cloud.credentials.json`. */ GOOGLE_CLOUD_CREDENTIALS_JSON("Google Cloud Credentials JSON String"); diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 24bc02042e..dce11d05e2 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -139,7 +139,8 @@ private static GoogleCredentials resolveCredentials(ConfigProperties configPrope private static GoogleCredentials loadCredentials(ConfigProperties configProperties) { Optional credsPath = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional(configProperties); + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getConfiguredValueAsOptional( + configProperties); if (credsPath.isPresent()) { File file = new File(credsPath.get()); if (!file.exists()) { @@ -153,7 +154,8 @@ private static GoogleCredentials loadCredentials(ConfigProperties configProperti } Optional credsJson = - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional(configProperties); + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_JSON.getConfiguredValueAsOptional( + configProperties); if (credsJson.isPresent()) { try (ByteArrayInputStream bais = new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index ffecdcb76f..8486ae1597 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -283,7 +283,8 @@ void testCredentialsLoadedFromFilePath() throws IOException { buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); serviceAccountCredentialsMockedStatic.verify( - () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), + Mockito.atLeastOnce()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); @@ -318,7 +319,8 @@ void testCredentialsLoadedFromJsonString() throws IOException { buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); serviceAccountCredentialsMockedStatic.verify( - () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), + Mockito.atLeastOnce()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); @@ -344,10 +346,10 @@ void testFallbackToApplicationDefaultCredentials() throws IOException { mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class); - MockedStatic serviceAccountCredentialsMockedStatic = - Mockito.mockStatic(ServiceAccountCredentials.class)) { - + Mockito.mockStatic(GoogleCredentials.class); + MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + googleCredentialsMockedStatic .when(GoogleCredentials::getApplicationDefault) .thenReturn(mockedGoogleCredentials); @@ -392,10 +394,10 @@ void testCredentialsPathTakesPrecedenceOverJson() throws IOException { mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = - Mockito.mockStatic(GoogleCredentials.class); - MockedStatic serviceAccountCredentialsMockedStatic = - Mockito.mockStatic(ServiceAccountCredentials.class)) { - + Mockito.mockStatic(GoogleCredentials.class); + MockedStatic serviceAccountCredentialsMockedStatic = + Mockito.mockStatic(ServiceAccountCredentials.class)) { + serviceAccountCredentialsMockedStatic .when(() -> ServiceAccountCredentials.fromStream(any(InputStream.class))) .thenReturn(mockedGoogleCredentials); @@ -403,7 +405,8 @@ void testCredentialsPathTakesPrecedenceOverJson() throws IOException { buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); serviceAccountCredentialsMockedStatic.verify( - () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), Mockito.atLeastOnce()); + () -> ServiceAccountCredentials.fromStream(any(InputStream.class)), + Mockito.atLeastOnce()); googleCredentialsMockedStatic.verify( GoogleCredentials::getApplicationDefault, Mockito.never()); } @@ -422,11 +425,12 @@ void testCredentialsFileNodeFoundThrowsConfigurationException() { System.setProperty( ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); System.setProperty( - ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), "/non/existent/path.json"); + ConfigurableOption.GOOGLE_CLOUD_CREDENTIALS_PATH.getSystemProperty(), + "/non/existent/path.json"); try { OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - + assertThatThrownBy(() -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)) .isInstanceOf(ConfigurationException.class) .hasMessageContaining("Credentials file not found"); @@ -448,7 +452,7 @@ void testMalformedCredentialsJsonThrowsGoogleAuthException() { try { OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - + try (MockedStatic serviceAccountCredentialsMockedStatic = Mockito.mockStatic(ServiceAccountCredentials.class)) { serviceAccountCredentialsMockedStatic @@ -457,7 +461,8 @@ void testMalformedCredentialsJsonThrowsGoogleAuthException() { assertThatThrownBy(() -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)) .isInstanceOf(GoogleAuthException.class) - .hasMessageContaining(GoogleAuthException.Reason.FAILED_CREDENTIAL_CREATION.getMessage()); + .hasMessageContaining( + GoogleAuthException.Reason.FAILED_CREDENTIAL_CREATION.getMessage()); } } finally { System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()); From e96eb35415cd9389d63be548749ff2edd6773eb6 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Thu, 7 May 2026 22:12:34 +0000 Subject: [PATCH 6/7] chore: add scope for SA --- .../auth/GcpAuthAutoConfigurationCustomizerProvider.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index dce11d05e2..eb8f46cf27 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -147,7 +147,8 @@ private static GoogleCredentials loadCredentials(ConfigProperties configProperti throw new ConfigurationException("Credentials file not found: " + file.getName()); } try (FileInputStream fis = new FileInputStream(file)) { - return ServiceAccountCredentials.fromStream(fis); + return ServiceAccountCredentials.fromStream(fis) + .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); } @@ -159,7 +160,8 @@ private static GoogleCredentials loadCredentials(ConfigProperties configProperti if (credsJson.isPresent()) { try (ByteArrayInputStream bais = new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { - return ServiceAccountCredentials.fromStream(bais); + return ServiceAccountCredentials.fromStream(bais) + .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); } From a63594df89dc443a10986f9e72aefbe94d5ed095 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Fri, 8 May 2026 12:56:45 +0000 Subject: [PATCH 7/7] chore: run spotless --- .../auth/GcpAuthAutoConfigurationCustomizerProvider.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index eb8f46cf27..ac8eb5fec2 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -148,7 +148,8 @@ private static GoogleCredentials loadCredentials(ConfigProperties configProperti } try (FileInputStream fis = new FileInputStream(file)) { return ServiceAccountCredentials.fromStream(fis) - .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); + .createScoped( + Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); } @@ -161,7 +162,8 @@ private static GoogleCredentials loadCredentials(ConfigProperties configProperti try (ByteArrayInputStream bais = new ByteArrayInputStream(credsJson.get().getBytes(StandardCharsets.UTF_8))) { return ServiceAccountCredentials.fromStream(bais) - .createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); + .createScoped( + Collections.singletonList("https://www.googleapis.com/auth/cloud-platform")); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_CREDENTIAL_CREATION, e); }