Skip to content

Commit be79f44

Browse files
Try resolving GCP_PROJECT with ServiceOptions if not provided
1 parent 89b99af commit be79f44

File tree

4 files changed

+88
-10
lines changed

4 files changed

+88
-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.39.1")
28+
implementation("com.google.cloud:google-cloud-core:2.60.3")
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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_TRACES;
1313
import static org.assertj.core.api.Assertions.assertThat;
1414
import static org.assertj.core.api.Assertions.assertThatThrownBy;
15+
import static org.junit.jupiter.api.Assertions.assertEquals;
16+
import static org.junit.jupiter.api.Assertions.assertThrows;
1517
import static org.mockito.ArgumentMatchers.any;
1618
import static org.mockito.Mockito.mock;
1719
import static org.mockito.Mockito.when;
@@ -40,6 +42,7 @@
4042
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
4143
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
4244
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
45+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
4346
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
4447
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
4548
import io.opentelemetry.sdk.common.CompletableResultCode;
@@ -538,6 +541,49 @@ void testTargetSignalsBehavior(TargetSignalBehavior testCase) {
538541
}
539542
}
540543

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

0 commit comments

Comments
 (0)