Skip to content

[gcp-auth-extension]: Try resolving GCP_PROJECT with ServiceOptions if not provided #2109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions gcp-auth-extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@ The extension can be configured either by environment variables or system proper

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

#### Required Config
#### Optional Config

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

- Can also be configured using `google.cloud.project` system property.
- This is a required option, the agent configuration will fail if this option is not set.

#### Optional Config
- 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.
- **Important Note**: The agent configuration will fail if this option is not set or cannot be inferred.

- `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.

Expand Down
1 change: 1 addition & 0 deletions gcp-auth-extension/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies {

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

// Test dependencies
testCompileOnly("com.google.auto.service:auto-service-annotations")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import com.google.auth.oauth2.GoogleCredentials;
import com.google.auto.service.AutoService;
import com.google.cloud.ServiceOptions;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason;
Expand All @@ -21,6 +22,7 @@
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.export.SpanExporter;
Expand Down Expand Up @@ -109,7 +111,12 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) {
.addMetricExporterCustomizer(
(metricExporter, configProperties) ->
customizeMetricExporter(metricExporter, credentials, configProperties))
.addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
.addResourceCustomizer(
(resource, configProperties) -> {
String gcpProjectId = getGoogleProjectId(configProperties);

return customizeResource(resource, gcpProjectId);
});
}

@Override
Expand Down Expand Up @@ -227,12 +234,35 @@ private static Map<String, String> getRequiredHeaderMap(
}

// Updates the current resource with the attributes required for ingesting OTLP data on GCP.
private static Resource customizeResource(Resource resource, ConfigProperties configProperties) {
String gcpProjectId =
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(configProperties);
private static Resource customizeResource(Resource resource, String gcpProjectId) {
Resource res =
Resource.create(
Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
return resource.merge(res);
}

/**
* Retrieves the Google Cloud Project ID from the configuration properties, falling back to
* google-cloud-core's ServiceOptions project ID resolution if not explicitly set.
*
* @param configProperties The configuration properties containing the GCP project ID.
* @return The Google Cloud Project ID.
*/
@Nonnull
static String getGoogleProjectId(ConfigProperties configProperties) {
String googleProjectId =
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValueWithFallback(
configProperties, ServiceOptions::getDefaultProjectId);

if (googleProjectId == null || googleProjectId.isEmpty()) {
throw new ConfigurationException(
String.format(
"GCP Authentication Extension not configured properly: %s not configured. Configure it by exporting environment variable %s or system property %s",
ConfigurableOption.GOOGLE_CLOUD_PROJECT,
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getEnvironmentVariable(),
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty()));
}

return googleProjectId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
import io.opentelemetry.sdk.common.CompletableResultCode;
Expand Down Expand Up @@ -538,6 +539,49 @@ public void testTargetSignalsBehavior(TargetSignalBehavior testCase) {
}
}

@Test
void testThrowsExceptionIfGoogleProjectIsNotFound() {
// Clear the system property to ensure it does not interfere with the test
System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty());
System.clearProperty("GOOGLE_CLOUD_PROJECT");

DefaultConfigProperties configProperties =
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));

assertThrows(
ConfigurationException.class,
() -> GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
}

@Test
void testResolveGoogleProjectIdFromSystemProperty() {
System.setProperty(
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID);

DefaultConfigProperties configProperties =
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));

assertEquals(
DUMMY_GCP_RESOURCE_PROJECT_ID,
GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
}

@Test
void testResolveGoogleProjectIdFromServiceOptions() {
// Clear the system property to ensure it does not interfere with the test
System.clearProperty(ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty());
// Property that the extension expects is google.cloud.project, ServiceOptions works with
// GOOGLE_CLOUD_PROJECT, so this will highlight the fallback to ServiceOptions
System.setProperty("GOOGLE_CLOUD_PROJECT", DUMMY_GCP_RESOURCE_PROJECT_ID);

DefaultConfigProperties configProperties =
DefaultConfigProperties.create(Collections.emptyMap(), Mockito.mock(ComponentLoader.class));

assertEquals(
DUMMY_GCP_RESOURCE_PROJECT_ID,
GcpAuthAutoConfigurationCustomizerProvider.getGoogleProjectId(configProperties));
}

/** Test cases specifying expected behavior for GOOGLE_OTEL_AUTH_TARGET_SIGNALS */
private static Stream<Arguments> provideTargetSignalBehaviorTestCases() {
return Stream.of(
Expand Down
Loading