Skip to content

Commit 1503d82

Browse files
Try resolving GCP_PROJECT with ServiceOptions if not provided
1 parent fa6a9c7 commit 1503d82

File tree

4 files changed

+86
-10
lines changed

4 files changed

+86
-10
lines changed

gcp-auth-extension/README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,13 @@ The extension can be configured either by environment variables or system proper
3434

3535
Here is a list of required and optional configuration available for the extension:
3636

37-
#### Required Config
37+
#### Optional Config
3838

3939
- `GOOGLE_CLOUD_PROJECT`: Environment variable that represents the Google Cloud Project ID to which the telemetry needs to be exported.
4040

4141
- Can also be configured using `google.cloud.project` system property.
42-
- This is a required option, the agent configuration will fail if this option is not set.
43-
44-
#### Optional Config
42+
- If neither of these options are set, the extension will attempt to use the `ServiceOptions` class from `google-cloud-core` to determine the project ID. The `ServiceOptions` has a comprehensive logic to determine the project ID, which includes checking the environment variable `GOOGLE_CLOUD_PROJECT`, the `gcloud` configuration, the local metadata server, and the `GOOGLE_APPLICATION_CREDENTIALS` file.
43+
- **Important Note**: The agent configuration will fail if this option is not set or cannot be inferred.
4544

4645
- `GOOGLE_CLOUD_QUOTA_PROJECT`: Environment variable that represents the Google Cloud Quota Project ID which will be charged for the GCP API usage. To learn more about a *quota project*, see the [Quota project overview](https://cloud.google.com/docs/quotas/quota-project) page. Additional details about configuring the *quota project* can be found on the [Set the quota project](https://cloud.google.com/docs/quotas/set-quota-project) page.
4746

gcp-auth-extension/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ dependencies {
2525

2626
// Only dependencies added to `implementation` configuration will be picked up by Shadow plugin
2727
implementation("com.google.auth:google-auth-library-oauth2-http:1.38.0")
28+
implementation("com.google.cloud:google-cloud-core:2.58.1")
2829

2930
// Test dependencies
3031
testCompileOnly("com.google.auto.service:auto-service-annotations")

gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55

66
package io.opentelemetry.contrib.gcp.auth;
77

8-
import static io.opentelemetry.api.common.AttributeKey.stringKey;
98
import static java.util.Arrays.stream;
109
import static java.util.stream.Collectors.joining;
1110
import static java.util.stream.Collectors.toMap;
1211

1312
import com.google.auth.oauth2.GoogleCredentials;
1413
import com.google.auto.service.AutoService;
14+
import com.google.cloud.ServiceOptions;
15+
import io.opentelemetry.api.common.AttributeKey;
1516
import io.opentelemetry.api.common.Attributes;
1617
import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason;
1718
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
@@ -25,6 +26,7 @@
2526
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
2627
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
2728
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
29+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
2830
import io.opentelemetry.sdk.metrics.export.MetricExporter;
2931
import io.opentelemetry.sdk.resources.Resource;
3032
import io.opentelemetry.sdk.trace.export.SpanExporter;
@@ -111,7 +113,12 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) {
111113
.addMetricExporterCustomizer(
112114
(metricExporter, configProperties) ->
113115
customizeMetricExporter(metricExporter, credentials, configProperties))
114-
.addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
116+
.addResourceCustomizer(
117+
(resource, configProperties) -> {
118+
String gcpProjectId = getGoogleProjectId(configProperties);
119+
120+
return customizeResource(resource, gcpProjectId);
121+
});
115122
}
116123

117124
@Override
@@ -228,10 +235,35 @@ private static Map<String, String> getRequiredHeaderMap(
228235
}
229236

230237
// Updates the current resource with the attributes required for ingesting OTLP data on GCP.
231-
private static Resource customizeResource(Resource resource, ConfigProperties configProperties) {
232-
String gcpProjectId =
233-
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(configProperties);
234-
Resource res = Resource.create(Attributes.of(stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
238+
private static Resource customizeResource(Resource resource, String gcpProjectId) {
239+
Resource res =
240+
Resource.create(
241+
Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
235242
return resource.merge(res);
236243
}
244+
245+
/**
246+
* Retrieves the Google Cloud Project ID from the configuration properties, falling back to
247+
* google-cloud-core's ServiceOptions project ID resolution if not explicitly set.
248+
*
249+
* @param configProperties The configuration properties containing the GCP project ID.
250+
* @return The Google Cloud Project ID.
251+
*/
252+
@Nonnull
253+
static String getGoogleProjectId(ConfigProperties configProperties) {
254+
String googleProjectId =
255+
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValueWithFallback(
256+
configProperties, ServiceOptions::getDefaultProjectId);
257+
258+
if (googleProjectId == null || googleProjectId.isEmpty()) {
259+
throw new ConfigurationException(
260+
String.format(
261+
"GCP Authentication Extension not configured properly: %s not configured. Configure it by exporting environment variable %s or system property %s",
262+
ConfigurableOption.GOOGLE_CLOUD_PROJECT,
263+
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getEnvironmentVariable(),
264+
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()));
265+
}
266+
267+
return googleProjectId;
268+
}
237269
}

gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
4343
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
4444
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
45+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
4546
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
4647
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
4748
import io.opentelemetry.sdk.common.CompletableResultCode;
@@ -541,6 +542,49 @@ public void testTargetSignalsBehavior(TargetSignalBehavior testCase) {
541542
}
542543
}
543544

545+
@Test
546+
void testThrowsExceptionIfGoogleProjectIsNotFound() {
547+
// Clear the system property to ensure it does not interfere with the test
548+
System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty());
549+
System.clearProperty("GOOGLE_CLOUD_PROJECT");
550+
551+
DefaultConfigProperties configProperties =
552+
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));
553+
554+
assertThrows(
555+
ConfigurationException.class,
556+
() -> GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
557+
}
558+
559+
@Test
560+
void testResolveGoogleProjectIdFromSystemProperty() {
561+
System.setProperty(
562+
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID);
563+
564+
DefaultConfigProperties configProperties =
565+
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));
566+
567+
assertEquals(
568+
DUMMY_GCP_RESOURCE_PROJECT_ID,
569+
GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
570+
}
571+
572+
@Test
573+
void testResolveGoogleProjectIdFromServiceOptions() {
574+
// Clear the system property to ensure it does not interfere with the test
575+
System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty());
576+
// Property that the extension expects is google.cloud.project, ServiceOptions works with
577+
// GOOGLE_CLOUD_PROJECT, so this will highlight the fallback to ServiceOptions
578+
System.setProperty("GOOGLE_CLOUD_PROJECT", DUMMY_GCP_RESOURCE_PROJECT_ID);
579+
580+
DefaultConfigProperties configProperties =
581+
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));
582+
583+
assertEquals(
584+
DUMMY_GCP_RESOURCE_PROJECT_ID,
585+
GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
586+
}
587+
544588
/** Test cases specifying expected behavior for GOOGLE_OTEL_AUTH_TARGET_SIGNALS */
545589
private static Stream<Arguments> provideTargetSignalBehaviorTestCases() {
546590
return Stream.of(

0 commit comments

Comments
 (0)