From c33ad113ef27e4aa85fb25cacaef7049ea0d8c96 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 19:55:35 +0000 Subject: [PATCH 01/10] Delete the old GcpDetector.ts --- .../src/detectors/GcpDetector.ts | 161 ------------------ 1 file changed, 161 deletions(-) delete mode 100644 packages/resource-detector-gcp/src/detectors/GcpDetector.ts diff --git a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts deleted file mode 100644 index 3a5aa06021..0000000000 --- a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as gcpMetadata from 'gcp-metadata'; -import { context } from '@opentelemetry/api'; -import { suppressTracing } from '@opentelemetry/core'; -import { - ResourceDetectionConfig, - ResourceDetector, - DetectedResource, - DetectedResourceAttributes, -} from '@opentelemetry/resources'; -import { - CLOUDPROVIDERVALUES_GCP, - SEMRESATTRS_CLOUD_ACCOUNT_ID, - SEMRESATTRS_CLOUD_AVAILABILITY_ZONE, - SEMRESATTRS_CLOUD_PROVIDER, - SEMRESATTRS_CONTAINER_NAME, - SEMRESATTRS_HOST_ID, - SEMRESATTRS_HOST_NAME, - SEMRESATTRS_K8S_CLUSTER_NAME, - SEMRESATTRS_K8S_NAMESPACE_NAME, - SEMRESATTRS_K8S_POD_NAME, -} from '@opentelemetry/semantic-conventions'; - -/** - * The GcpDetector can be used to detect if a process is running in the Google - * Cloud Platform and return a {@link Resource} populated with metadata about - * the instance. Returns an empty Resource if detection fails. - */ -class GcpDetector implements ResourceDetector { - detect(_config?: ResourceDetectionConfig): DetectedResource { - const attributes = context.with(suppressTracing(context.active()), () => - this._getAttributes() - ); - return { attributes }; - } - - /** - * Asynchronously gather GCP cloud metadata. - */ - private _getAttributes(): DetectedResourceAttributes { - const isAvail = gcpMetadata.isAvailable(); - - const attributes: DetectedResourceAttributes = { - [SEMRESATTRS_CLOUD_PROVIDER]: (async () => { - return (await isAvail) ? CLOUDPROVIDERVALUES_GCP : undefined; - })(), - [SEMRESATTRS_CLOUD_ACCOUNT_ID]: this._getProjectId(isAvail), - [SEMRESATTRS_HOST_ID]: this._getInstanceId(isAvail), - [SEMRESATTRS_HOST_NAME]: this._getHostname(isAvail), - [SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: this._getZone(isAvail), - }; - - // Add resource attributes for K8s. - if (process.env.KUBERNETES_SERVICE_HOST) { - attributes[SEMRESATTRS_K8S_CLUSTER_NAME] = this._getClusterName(isAvail); - attributes[SEMRESATTRS_K8S_NAMESPACE_NAME] = (async () => { - return (await isAvail) ? process.env.NAMESPACE : undefined; - })(); - attributes[SEMRESATTRS_K8S_POD_NAME] = (async () => { - return (await isAvail) ? process.env.HOSTNAME : undefined; - })(); - attributes[SEMRESATTRS_CONTAINER_NAME] = (async () => { - return (await isAvail) ? process.env.CONTAINER_NAME : undefined; - })(); - } - - return attributes; - } - - /** Gets project id from GCP project metadata. */ - private async _getProjectId( - isAvail: Promise - ): Promise { - if (!(await isAvail)) { - return undefined; - } - try { - return await gcpMetadata.project('project-id'); - } catch { - return ''; - } - } - - /** Gets instance id from GCP instance metadata. */ - private async _getInstanceId( - isAvail: Promise - ): Promise { - if (!(await isAvail)) { - return undefined; - } - try { - const id = await gcpMetadata.instance('id'); - return id.toString(); - } catch { - return ''; - } - } - - /** Gets zone from GCP instance metadata. */ - private async _getZone( - isAvail: Promise - ): Promise { - if (!(await isAvail)) { - return undefined; - } - try { - const zoneId = await gcpMetadata.instance('zone'); - if (zoneId) { - return zoneId.split('/').pop(); - } - return ''; - } catch { - return ''; - } - } - - /** Gets cluster name from GCP instance metadata. */ - private async _getClusterName( - isAvail: Promise - ): Promise { - if (!(await isAvail)) { - return undefined; - } - try { - return await gcpMetadata.instance('attributes/cluster-name'); - } catch { - return ''; - } - } - - /** Gets hostname from GCP instance metadata. */ - private async _getHostname( - isAvail: Promise - ): Promise { - if (!(await isAvail)) { - return undefined; - } - try { - return await gcpMetadata.instance('hostname'); - } catch { - return ''; - } - } -} - -export const gcpDetector = new GcpDetector(); From 335091fb8eb78bee557f8cfc6fa4c00cba3f158c Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 19:56:41 +0000 Subject: [PATCH 02/10] Copy in files exactly from GoogleCloudPlatform/opentelemetry-operations-js --- .../src/detectors/detector.ts | 220 ++++++++++++++++++ .../src/detectors/faas.ts | 81 +++++++ .../src/detectors/gae.ts | 98 ++++++++ .../src/detectors/gce.ts | 90 +++++++ .../src/detectors/gke.ts | 78 +++++++ .../test/detectors/detector.test.ts | 220 ++++++++++++++++++ .../test/detectors/faas.test.ts | 92 ++++++++ .../test/detectors/gae.test.ts | 108 +++++++++ .../test/detectors/gce.test.ts | 106 +++++++++ .../test/detectors/gke.test.ts | 93 ++++++++ 10 files changed, 1186 insertions(+) create mode 100644 packages/resource-detector-gcp/src/detectors/detector.ts create mode 100644 packages/resource-detector-gcp/src/detectors/faas.ts create mode 100644 packages/resource-detector-gcp/src/detectors/gae.ts create mode 100644 packages/resource-detector-gcp/src/detectors/gce.ts create mode 100644 packages/resource-detector-gcp/src/detectors/gke.ts create mode 100644 packages/resource-detector-gcp/test/detectors/detector.test.ts create mode 100644 packages/resource-detector-gcp/test/detectors/faas.test.ts create mode 100644 packages/resource-detector-gcp/test/detectors/gae.test.ts create mode 100644 packages/resource-detector-gcp/test/detectors/gce.test.ts create mode 100644 packages/resource-detector-gcp/test/detectors/gke.test.ts diff --git a/packages/resource-detector-gcp/src/detectors/detector.ts b/packages/resource-detector-gcp/src/detectors/detector.ts new file mode 100644 index 0000000000..f3982af024 --- /dev/null +++ b/packages/resource-detector-gcp/src/detectors/detector.ts @@ -0,0 +1,220 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + CLOUDPLATFORMVALUES_GCP_APP_ENGINE, + CLOUDPLATFORMVALUES_GCP_CLOUD_FUNCTIONS, + CLOUDPLATFORMVALUES_GCP_CLOUD_RUN, + CLOUDPLATFORMVALUES_GCP_COMPUTE_ENGINE, + CLOUDPLATFORMVALUES_GCP_KUBERNETES_ENGINE, + CLOUDPROVIDERVALUES_GCP, + SEMRESATTRS_CLOUD_ACCOUNT_ID, + SEMRESATTRS_CLOUD_AVAILABILITY_ZONE, + SEMRESATTRS_CLOUD_PLATFORM, + SEMRESATTRS_CLOUD_PROVIDER, + SEMRESATTRS_CLOUD_REGION, + SEMRESATTRS_FAAS_INSTANCE, + SEMRESATTRS_FAAS_NAME, + SEMRESATTRS_FAAS_VERSION, + SEMRESATTRS_HOST_ID, + SEMRESATTRS_HOST_NAME, + SEMRESATTRS_HOST_TYPE, + SEMRESATTRS_K8S_CLUSTER_NAME, +} from '@opentelemetry/semantic-conventions'; + +import {AttributeValue, Attributes} from '@opentelemetry/api'; +import { + DetectedResource, + DetectedResourceAttributes, + emptyResource, + Resource, + ResourceDetector, + resourceFromAttributes, +} from '@opentelemetry/resources'; +import * as metadata from 'gcp-metadata'; +import * as faas from './faas'; +import * as gae from './gae'; +import * as gce from './gce'; +import * as gke from './gke'; + +const ATTRIBUTE_NAMES = [ + SEMRESATTRS_CLOUD_PLATFORM, + SEMRESATTRS_CLOUD_AVAILABILITY_ZONE, + SEMRESATTRS_CLOUD_REGION, + SEMRESATTRS_K8S_CLUSTER_NAME, + SEMRESATTRS_HOST_TYPE, + SEMRESATTRS_HOST_ID, + SEMRESATTRS_HOST_NAME, + SEMRESATTRS_CLOUD_PROVIDER, + SEMRESATTRS_CLOUD_ACCOUNT_ID, + SEMRESATTRS_FAAS_NAME, + SEMRESATTRS_FAAS_VERSION, + SEMRESATTRS_FAAS_INSTANCE, +] as const; + +// Ensure that all resource keys are accounted for in ATTRIBUTE_NAMES +type GcpResourceAttributeName = (typeof ATTRIBUTE_NAMES)[number]; +type GcpResourceAttributes = Partial< + Record +>; + +async function detect(): Promise { + if (!(await metadata.isAvailable())) { + return emptyResource(); + } + + // Note the order of these if checks is significant with more specific resources coming + // first. E.g. Cloud Functions gen2 are executed in Cloud Run so it must be checked first. + if (await gke.onGke()) { + return await gkeResource(); + } else if (await faas.onCloudFunctions()) { + return await cloudFunctionsResource(); + } else if (await faas.onCloudRun()) { + return await cloudRunResource(); + } else if (await gae.onAppEngine()) { + return await gaeResource(); + } else if (await gce.onGce()) { + return await gceResource(); + } + + return emptyResource(); +} + +async function gkeResource(): Promise { + const [zoneOrRegion, k8sClusterName, hostId] = await Promise.all([ + gke.availabilityZoneOrRegion(), + gke.clusterName(), + gke.hostId(), + ]); + + return await makeResource({ + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_GCP_KUBERNETES_ENGINE, + [zoneOrRegion.type === 'zone' + ? SEMRESATTRS_CLOUD_AVAILABILITY_ZONE + : SEMRESATTRS_CLOUD_REGION]: zoneOrRegion.value, + [SEMRESATTRS_K8S_CLUSTER_NAME]: k8sClusterName, + [SEMRESATTRS_HOST_ID]: hostId, + }); +} + +async function cloudRunResource(): Promise { + const [faasName, faasVersion, faasInstance, faasCloudRegion] = + await Promise.all([ + faas.faasName(), + faas.faasVersion(), + faas.faasInstance(), + faas.faasCloudRegion(), + ]); + + return await makeResource({ + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_GCP_CLOUD_RUN, + [SEMRESATTRS_FAAS_NAME]: faasName, + [SEMRESATTRS_FAAS_VERSION]: faasVersion, + [SEMRESATTRS_FAAS_INSTANCE]: faasInstance, + [SEMRESATTRS_CLOUD_REGION]: faasCloudRegion, + }); +} + +async function cloudFunctionsResource(): Promise { + const [faasName, faasVersion, faasInstance, faasCloudRegion] = + await Promise.all([ + faas.faasName(), + faas.faasVersion(), + faas.faasInstance(), + faas.faasCloudRegion(), + ]); + + return await makeResource({ + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_GCP_CLOUD_FUNCTIONS, + [SEMRESATTRS_FAAS_NAME]: faasName, + [SEMRESATTRS_FAAS_VERSION]: faasVersion, + [SEMRESATTRS_FAAS_INSTANCE]: faasInstance, + [SEMRESATTRS_CLOUD_REGION]: faasCloudRegion, + }); +} + +async function gaeResource(): Promise { + let zone, region; + if (await gae.onAppEngineStandard()) { + [zone, region] = await Promise.all([ + gae.standardAvailabilityZone(), + gae.standardCloudRegion(), + ]); + } else { + ({zone, region} = await gce.availabilityZoneAndRegion()); + } + const [faasName, faasVersion, faasInstance] = await Promise.all([ + gae.serviceName(), + gae.serviceVersion(), + gae.serviceInstance(), + ]); + + return await makeResource({ + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_GCP_APP_ENGINE, + [SEMRESATTRS_FAAS_NAME]: faasName, + [SEMRESATTRS_FAAS_VERSION]: faasVersion, + [SEMRESATTRS_FAAS_INSTANCE]: faasInstance, + [SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: zone, + [SEMRESATTRS_CLOUD_REGION]: region, + }); +} + +async function gceResource(): Promise { + const [zoneAndRegion, hostType, hostId, hostName] = await Promise.all([ + gce.availabilityZoneAndRegion(), + gce.hostType(), + gce.hostId(), + gce.hostName(), + ]); + + return await makeResource({ + [SEMRESATTRS_CLOUD_PLATFORM]: CLOUDPLATFORMVALUES_GCP_COMPUTE_ENGINE, + [SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: zoneAndRegion.zone, + [SEMRESATTRS_CLOUD_REGION]: zoneAndRegion.region, + [SEMRESATTRS_HOST_TYPE]: hostType, + [SEMRESATTRS_HOST_ID]: hostId, + [SEMRESATTRS_HOST_NAME]: hostName, + }); +} + +async function makeResource(attrs: GcpResourceAttributes): Promise { + const project = await metadata.project('project-id'); + + return resourceFromAttributes({ + [SEMRESATTRS_CLOUD_PROVIDER]: CLOUDPROVIDERVALUES_GCP, + [SEMRESATTRS_CLOUD_ACCOUNT_ID]: project, + ...attrs, + } satisfies GcpResourceAttributes); +} + +/** + * Google Cloud resource detector which populates attributes based on the environment this + * process is running in. If not on GCP, returns an empty resource. + */ +export class GcpDetectorSync implements ResourceDetector { + private async _asyncAttributes(): Promise { + return (await detect()).attributes; + } + + detect(): DetectedResource { + const asyncAttributes = this._asyncAttributes(); + const attributes = {} as DetectedResourceAttributes; + ATTRIBUTE_NAMES.forEach(name => { + // Each resource attribute is determined asynchronously in _gatherData(). + attributes[name] = asyncAttributes.then(data => data[name]); + }); + + return {attributes}; + } +} diff --git a/packages/resource-detector-gcp/src/detectors/faas.ts b/packages/resource-detector-gcp/src/detectors/faas.ts new file mode 100644 index 0000000000..b2747f3612 --- /dev/null +++ b/packages/resource-detector-gcp/src/detectors/faas.ts @@ -0,0 +1,81 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Implementation in this file copied from + * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/faas.go + */ + +import * as metadata from 'gcp-metadata'; + +const ID_METADATA_ATTR = 'id'; +const CLOUD_RUN_CONFIG_ENV = 'K_CONFIGURATION'; +const CLOUD_FUNCTION_TARGET_ENV = 'FUNCTION_TARGET'; +const FAAS_SERVICE_ENV = 'K_SERVICE'; +const FAAS_REVISION_ENV = 'K_REVISION'; +const REGION_METADATA_ATTR = 'region'; + +export async function onCloudRun(): Promise { + return process.env[CLOUD_RUN_CONFIG_ENV] !== undefined; +} + +export async function onCloudFunctions(): Promise { + return process.env[CLOUD_FUNCTION_TARGET_ENV] !== undefined; +} + +/** + * The name of the Cloud Run or Cloud Function. Check that {@link onCloudRun()} or {@link + * onCloudFunctions()} is true before calling this, or it may throw exceptions. + */ +export async function faasName(): Promise { + return lookupEnv(FAAS_SERVICE_ENV); +} + +/** + * The version/revision of the Cloud Run or Cloud Function. Check that {@link onCloudRun()} or + * {@link onCloudFunctions()} is true before calling this, or it may throw exceptions. + */ +export async function faasVersion(): Promise { + return lookupEnv(FAAS_REVISION_ENV); +} + +/** + * The ID for the running instance of a Cloud Run or Cloud Function. Check that {@link + * onCloudRun()} or {@link onCloudFunctions()} is true before calling this, or it may throw + * exceptions. + */ +export async function faasInstance(): Promise { + // May be a bignumber.js BigNumber which can just be converted with toString(). See + // https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties + const id = await metadata.instance(ID_METADATA_ATTR); + return id.toString(); +} + +/** + * The cloud region where the running instance of a Cloud Run or Cloud Function is located. + * Check that {@link onCloudRun()} or {@link onCloudFunctions()} is true before calling this, + * or it may throw exceptions. + */ +export async function faasCloudRegion(): Promise { + const region = await metadata.instance(REGION_METADATA_ATTR); + return region.slice(region.lastIndexOf('/') + 1); +} + +function lookupEnv(key: string): string { + const val = process.env[key]; + if (val === undefined) { + throw new Error(`Environment variable ${key} not found`); + } + return val; +} diff --git a/packages/resource-detector-gcp/src/detectors/gae.ts b/packages/resource-detector-gcp/src/detectors/gae.ts new file mode 100644 index 0000000000..ad6ddd239e --- /dev/null +++ b/packages/resource-detector-gcp/src/detectors/gae.ts @@ -0,0 +1,98 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Implementation in this file copied from + * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/app_engine.go + */ + +import * as metadata from 'gcp-metadata'; +import * as gce from './gce'; +import * as faas from './faas'; + +const GAE_SERVICE_ENV = 'GAE_SERVICE'; +const GAE_VERSION_ENV = 'GAE_VERSION'; +const GAE_INSTANCE_ENV = 'GAE_INSTANCE'; +const GAE_ENV = 'GAE_ENV'; +const GAE_STANDARD = 'standard'; +const ZONE_METADATA_ATTR = 'zone'; + +export async function onAppEngineStandard(): Promise { + return process.env[GAE_ENV] === GAE_STANDARD; +} + +export async function onAppEngine(): Promise { + return process.env[GAE_SERVICE_ENV] !== undefined; +} + +/** + * The service name of the app engine service. Check that {@link onAppEngine()} is true before + * calling this, or it may throw exceptions. + */ +export async function serviceName(): Promise { + return lookupEnv(GAE_SERVICE_ENV); +} + +/** + * The service version of the app engine service. Check that {@link onAppEngine()} is true + * before calling this, or it may throw exceptions. + */ +export async function serviceVersion(): Promise { + return lookupEnv(GAE_VERSION_ENV); +} + +/** + * The service instance of the app engine service. Check that {@link onAppEngine()} is true + * before calling this, or it may throw exceptions. + */ +export async function serviceInstance(): Promise { + return lookupEnv(GAE_INSTANCE_ENV); +} + +/** + * The zone and region in which this program is running. Check that {@link onAppEngine()} is + * true before calling this, or it may throw exceptions. + */ +export async function flexAvailabilityZoneAndRegion(): Promise<{ + zone: string; + region: string; +}> { + return await gce.availabilityZoneAndRegion(); +} + +/** + * The zone the app engine service is running in. Check that {@link onAppEngineStandard()} is + * true before calling this, or it may throw exceptions. + */ +export async function standardAvailabilityZone(): Promise { + const zone = await metadata.instance(ZONE_METADATA_ATTR); + // zone is of the form "projects/233510669999/zones/us15" + return zone.slice(zone.lastIndexOf('/') + 1); +} + +/** + * The region the app engine service is running in. Check that {@link onAppEngineStandard()} is + * true before calling this, or it may throw exceptions. + */ +export async function standardCloudRegion(): Promise { + return await faas.faasCloudRegion(); +} + +function lookupEnv(key: string): string { + const val = process.env[key]; + if (val === undefined) { + throw new Error(`Environment variable ${key} not found`); + } + return val; +} diff --git a/packages/resource-detector-gcp/src/detectors/gce.ts b/packages/resource-detector-gcp/src/detectors/gce.ts new file mode 100644 index 0000000000..00a3cf0e43 --- /dev/null +++ b/packages/resource-detector-gcp/src/detectors/gce.ts @@ -0,0 +1,90 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Implementation in this file copied from + * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/gce.go + */ + +import {diag} from '@opentelemetry/api'; +import * as metadata from 'gcp-metadata'; + +const MACHINE_TYPE_METADATA_ATTR = 'machine-type'; +const ID_METADATA_ATTR = 'id'; +const HOST_NAME_METADATA_ATTR = 'name'; +const ZONE_METADATA_ATTR = 'zone'; + +export async function onGce(): Promise { + try { + await metadata.instance(MACHINE_TYPE_METADATA_ATTR); + return true; + } catch (err) { + diag.debug( + 'Could not fetch metadata attribute %s, assuming not on GCE. Error was %s', + MACHINE_TYPE_METADATA_ATTR, + err, + ); + return false; + } +} + +/** + * The machine type of the instance on which this program is running. Check that {@link + * onGce()} is true before calling this, or it may throw exceptions. + */ +export async function hostType(): Promise { + return metadata.instance(MACHINE_TYPE_METADATA_ATTR); +} + +/** + * The instance ID of the instance on which this program is running. Check that {@link onGce()} + * is true before calling this, or it may throw exceptions. + */ +export async function hostId(): Promise { + // May be a bignumber.js BigNumber which can just be converted with toString(). See + // https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties + const id = await metadata.instance(ID_METADATA_ATTR); + return id.toString(); +} + +/** + * The instance ID of the instance on which this program is running. Check that {@link onGce()} + * is true before calling this, or it may throw exceptions. + */ +export async function hostName(): Promise { + return metadata.instance(HOST_NAME_METADATA_ATTR); +} + +/** + * The zone and region in which this program is running. Check that {@link onGce()} is true + * before calling this, or it may throw exceptions. + */ +export async function availabilityZoneAndRegion(): Promise<{ + zone: string; + region: string; +}> { + const fullZone = await metadata.instance(ZONE_METADATA_ATTR); + + // Format described in + // https://cloud.google.com/compute/docs/metadata/default-metadata-values#vm_instance_metadata + const re = /projects\/\d+\/zones\/(?(?\w+-\w+)-\w+)/; + const {zone, region} = fullZone.match(re)?.groups ?? {}; + if (!zone || !region) { + throw new Error( + `zone was not in the expected format: projects/PROJECT_NUM/zones/COUNTRY-REGION-ZONE. Got ${fullZone}`, + ); + } + + return {zone, region}; +} diff --git a/packages/resource-detector-gcp/src/detectors/gke.ts b/packages/resource-detector-gcp/src/detectors/gke.ts new file mode 100644 index 0000000000..290f7015b0 --- /dev/null +++ b/packages/resource-detector-gcp/src/detectors/gke.ts @@ -0,0 +1,78 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Implementation in this file copied from + * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/gke.go + */ + +import * as metadata from 'gcp-metadata'; +import * as gce from './gce'; + +const KUBERNETES_SERVICE_HOST_ENV = 'KUBERNETES_SERVICE_HOST'; +const CLUSTER_NAME_METADATA_ATTR = 'attributes/cluster-name'; +const CLUSTER_LOCATION_METADATA_ATTR = 'attributes/cluster-location'; + +export async function onGke(): Promise { + return process.env[KUBERNETES_SERVICE_HOST_ENV] !== undefined; +} + +/** + * The instance ID of the instance on which this program is running. Check that {@link onGke()} + * is true before calling this, or it may throw exceptions. + */ +export async function hostId(): Promise { + return await gce.hostId(); +} + +/** + * The name of the GKE cluster in which this program is running. Check that {@link onGke()} is + * true before calling this, or it may throw exceptions. + */ +export async function clusterName(): Promise { + return metadata.instance(CLUSTER_NAME_METADATA_ATTR); +} + +/** + * The location of the cluster and whether the cluster is zonal or regional. Check that {@link + * onGke()} is true before calling this, or it may throw exceptions. + */ +export async function availabilityZoneOrRegion(): Promise<{ + type: 'zone' | 'region'; + value: string; +}> { + const clusterLocation = await metadata.instance( + CLUSTER_LOCATION_METADATA_ATTR, + ); + switch (countChar(clusterLocation, '-')) { + case 1: + return {type: 'region', value: clusterLocation}; + case 2: + return {type: 'zone', value: clusterLocation}; + default: + throw new Error( + `unrecognized format for cluster location: ${clusterLocation}`, + ); + } +} + +function countChar(s: string, char: string): number { + let count = 0; + for (let i = 0; i < s.length; i++) { + if (s[i] === char) { + count += 1; + } + } + return count; +} diff --git a/packages/resource-detector-gcp/test/detectors/detector.test.ts b/packages/resource-detector-gcp/test/detectors/detector.test.ts new file mode 100644 index 0000000000..0837164c32 --- /dev/null +++ b/packages/resource-detector-gcp/test/detectors/detector.test.ts @@ -0,0 +1,220 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as sinon from 'sinon'; +import * as metadata from 'gcp-metadata'; + +import {GcpDetectorSync} from '../../src/detector/detector'; +import { + detectResources, + ResourceDetector, + Resource, +} from '@opentelemetry/resources'; +import * as assert from 'assert'; + +async function detectAndWait(detector: ResourceDetector): Promise { + const resource = detectResources({detectors: [detector]}); + await resource.waitForAsyncAttributes?.(); + return resource; +} + +describe('GcpDetectorSync', () => { + let metadataStub: sinon.SinonStubbedInstance; + let envStub: NodeJS.ProcessEnv; + beforeEach(() => { + metadataStub = sinon.stub(metadata); + metadataStub.isAvailable.resolves(true); + metadataStub.project.withArgs('project-id').resolves('fake-project-id'); + + envStub = sinon.replace(process, 'env', {}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('returns empty resource when metadata server is not available', async () => { + metadataStub.isAvailable.resolves(false); + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, {}); + }); + + describe('detects a GKE resource', () => { + beforeEach(() => { + envStub.KUBERNETES_SERVICE_HOST = 'fake-service-host'; + metadataStub.instance + .withArgs('id') + .resolves(12345) + + .withArgs('attributes/cluster-name') + .resolves('fake-cluster-name'); + }); + + it('zonal', async () => { + metadataStub.instance + .withArgs('attributes/cluster-location') + .resolves('us-east4-b'); + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.availability_zone': 'us-east4-b', + 'cloud.platform': 'gcp_kubernetes_engine', + 'cloud.provider': 'gcp', + 'host.id': '12345', + 'k8s.cluster.name': 'fake-cluster-name', + }); + }); + + it('regional', async () => { + metadataStub.instance + .withArgs('attributes/cluster-location') + .resolves('us-east4'); + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.platform': 'gcp_kubernetes_engine', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'host.id': '12345', + 'k8s.cluster.name': 'fake-cluster-name', + }); + }); + }); + + it('detects a GCE resource', async () => { + metadataStub.instance + .withArgs('id') + .resolves(12345) + + .withArgs('machine-type') + .resolves('fake-machine-type') + + .withArgs('name') + .resolves('fake-name') + + .withArgs('zone') + .resolves('projects/233510669999/zones/us-east4-b'); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.availability_zone': 'us-east4-b', + 'cloud.platform': 'gcp_compute_engine', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'host.id': '12345', + 'host.name': 'fake-name', + 'host.type': 'fake-machine-type', + }); + }); + + it('detects a Cloud Run resource', async () => { + envStub.K_CONFIGURATION = 'fake-configuration'; + envStub.K_SERVICE = 'fake-service'; + envStub.K_REVISION = 'fake-revision'; + metadataStub.instance + .withArgs('id') + .resolves(12345) + + .withArgs('region') + .resolves('projects/233510669999/regions/us-east4'); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.platform': 'gcp_cloud_run', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'faas.instance': '12345', + 'faas.name': 'fake-service', + 'faas.version': 'fake-revision', + }); + }); + + it('detects a Cloud Functions resource', async () => { + envStub.FUNCTION_TARGET = 'fake-function-target'; + envStub.K_SERVICE = 'fake-service'; + envStub.K_REVISION = 'fake-revision'; + metadataStub.instance + .withArgs('id') + .resolves(12345) + + .withArgs('region') + .resolves('projects/233510669999/regions/us-east4'); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.platform': 'gcp_cloud_functions', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'faas.instance': '12345', + 'faas.name': 'fake-service', + 'faas.version': 'fake-revision', + }); + }); + + it('detects a App Engine Standard resource', async () => { + envStub.GAE_ENV = 'standard'; + envStub.GAE_SERVICE = 'fake-service'; + envStub.GAE_VERSION = 'fake-version'; + envStub.GAE_INSTANCE = 'fake-instance'; + metadataStub.instance.withArgs('zone').resolves('us-east4-b'); + metadataStub.instance + .withArgs('region') + .resolves('projects/233510669999/regions/us-east4'); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.availability_zone': 'us-east4-b', + 'cloud.platform': 'gcp_app_engine', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'faas.instance': 'fake-instance', + 'faas.name': 'fake-service', + 'faas.version': 'fake-version', + }); + }); + + it('detects a App Engine Flex resource', async () => { + envStub.GAE_SERVICE = 'fake-service'; + envStub.GAE_VERSION = 'fake-version'; + envStub.GAE_INSTANCE = 'fake-instance'; + metadataStub.instance + .withArgs('zone') + .resolves('projects/233510669999/zones/us-east4-b'); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, { + 'cloud.account.id': 'fake-project-id', + 'cloud.availability_zone': 'us-east4-b', + 'cloud.platform': 'gcp_app_engine', + 'cloud.provider': 'gcp', + 'cloud.region': 'us-east4', + 'faas.instance': 'fake-instance', + 'faas.name': 'fake-service', + 'faas.version': 'fake-version', + }); + }); + + it('detects empty resource when nothing else can be detected', async () => { + // gcp-metadata throws when it can't access the metadata server + metadataStub.instance.rejects(); + metadataStub.project.rejects(); + + const resource = await detectAndWait(new GcpDetectorSync()); + assert.deepStrictEqual(resource.attributes, {}); + }); +}); diff --git a/packages/resource-detector-gcp/test/detectors/faas.test.ts b/packages/resource-detector-gcp/test/detectors/faas.test.ts new file mode 100644 index 0000000000..7414b03fd2 --- /dev/null +++ b/packages/resource-detector-gcp/test/detectors/faas.test.ts @@ -0,0 +1,92 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as sinon from 'sinon'; +import * as metadata from 'gcp-metadata'; + +import * as faas from '../../src/detector/faas'; +import * as assert from 'assert'; +import {BigNumber} from 'bignumber.js'; + +describe('FaaS (Cloud Run/Functions)', () => { + let metadataStub: sinon.SinonStubbedInstance; + let envStub: NodeJS.ProcessEnv; + beforeEach(() => { + metadataStub = sinon.stub(metadata); + envStub = sinon.replace(process, 'env', {}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('detects when running on Cloud Run', async () => { + envStub.K_CONFIGURATION = 'fake-configuration'; + const onCloudRun = await faas.onCloudRun(); + assert(onCloudRun); + }); + it('detects when not running on Cloud Run', async () => { + const onCloudRun = await faas.onCloudRun(); + assert(!onCloudRun); + }); + + it('detects when running on Cloud Functions', async () => { + envStub.FUNCTION_TARGET = 'fake-function-target'; + const onCloudFunctions = await faas.onCloudFunctions(); + assert(onCloudFunctions); + }); + it('detects when not running on Cloud Functions', async () => { + const onCloudFunctions = await faas.onCloudFunctions(); + assert(!onCloudFunctions); + }); + + it('detects FaaS name', async () => { + envStub.K_SERVICE = 'fake-service'; + const faasName = await faas.faasName(); + assert.strictEqual(faasName, 'fake-service'); + }); + + it('detects FaaS version', async () => { + envStub.K_REVISION = 'fake-revision'; + const faasVersion = await faas.faasVersion(); + assert.strictEqual(faasVersion, 'fake-revision'); + }); + + describe('detects FaaS id', () => { + it('as a number', async () => { + metadataStub.instance.withArgs('id').resolves(12345); + + const faasInstance = await faas.faasInstance(); + assert.strictEqual(faasInstance, '12345'); + }); + + it('as a BigNumber', async () => { + metadataStub.instance + .withArgs('id') + .resolves(new BigNumber('2459451723172637654')); + + const faasInstance = await faas.faasInstance(); + assert.strictEqual(faasInstance, '2459451723172637654'); + }); + }); + + it('detects FaaS region', async () => { + metadataStub.instance + .withArgs('region') + .resolves('projects/233510669999/regions/us-east4'); + + const faasRegion = await faas.faasCloudRegion(); + assert.deepStrictEqual(faasRegion, 'us-east4'); + }); +}); diff --git a/packages/resource-detector-gcp/test/detectors/gae.test.ts b/packages/resource-detector-gcp/test/detectors/gae.test.ts new file mode 100644 index 0000000000..544b2d6332 --- /dev/null +++ b/packages/resource-detector-gcp/test/detectors/gae.test.ts @@ -0,0 +1,108 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as sinon from 'sinon'; +import * as metadata from 'gcp-metadata'; + +import * as gae from '../../src/detector/gae'; +import * as assert from 'assert'; + +describe('App Engine (GAE)', () => { + let metadataStub: sinon.SinonStubbedInstance; + let envStub: NodeJS.ProcessEnv; + beforeEach(() => { + metadataStub = sinon.stub(metadata); + envStub = sinon.replace(process, 'env', {}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('detects when running on GAE', async () => { + envStub.GAE_SERVICE = 'fake-service'; + const onGae = await gae.onAppEngine(); + assert(onGae); + }); + it('detects when not running on GAE', async () => { + const onGae = await gae.onAppEngine(); + assert(!onGae); + }); + it('detects when running on GAE standard', async () => { + envStub.GAE_ENV = 'standard'; + const onGaeStandard = await gae.onAppEngineStandard(); + assert(onGaeStandard); + }); + + it('detects GAE service name', async () => { + envStub.GAE_SERVICE = 'fake-service'; + const serviceName = await gae.serviceName(); + assert.strictEqual(serviceName, 'fake-service'); + }); + + it('detects GAE service version', async () => { + envStub.GAE_VERSION = 'fake-version'; + const version = await gae.serviceVersion(); + assert.strictEqual(version, 'fake-version'); + }); + + it('detects GAE service instance', async () => { + envStub.GAE_INSTANCE = 'fake-instance'; + const instance = await gae.serviceInstance(); + assert.strictEqual(instance, 'fake-instance'); + }); + + describe('GAE flex zone and region', () => { + it('detects when correctly formatted', async () => { + metadataStub.instance + .withArgs('zone') + .resolves('projects/233510669999/zones/us-east4-b'); + + const zoneAndRegion = await gae.flexAvailabilityZoneAndRegion(); + assert.deepStrictEqual(zoneAndRegion, { + zone: 'us-east4-b', + region: 'us-east4', + }); + }); + + it('throws when incorrectly formatted', async () => { + metadataStub.instance.withArgs('zone').resolves(''); + + await assert.rejects( + gae.flexAvailabilityZoneAndRegion(), + /zone was not in the expected format/, + ); + }); + }); + + describe('GAE standard zone and region', () => { + it('detects zone', async () => { + metadataStub.instance + .withArgs('zone') + .resolves('projects/233510669999/zones/us15'); + + const zone = await gae.standardAvailabilityZone(); + assert.strictEqual(zone, 'us15'); + }); + + it('detects region', async () => { + metadataStub.instance + .withArgs('region') + .resolves('projects/233510669999/regions/us-east4'); + + const region = await gae.standardCloudRegion(); + assert.deepStrictEqual(region, 'us-east4'); + }); + }); +}); diff --git a/packages/resource-detector-gcp/test/detectors/gce.test.ts b/packages/resource-detector-gcp/test/detectors/gce.test.ts new file mode 100644 index 0000000000..c829b6098d --- /dev/null +++ b/packages/resource-detector-gcp/test/detectors/gce.test.ts @@ -0,0 +1,106 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as sinon from 'sinon'; +import * as metadata from 'gcp-metadata'; + +import * as gce from '../../src/detector/gce'; +import * as assert from 'assert'; +import {BigNumber} from 'bignumber.js'; + +describe('GCE', () => { + let metadataStub: sinon.SinonStubbedInstance; + beforeEach(() => { + metadataStub = sinon.stub(metadata); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('detects when running on GCE', async () => { + metadataStub.instance + .withArgs('machine-type') + .resolves('fake-machine-type'); + + const onGce = await gce.onGce(); + assert(onGce); + }); + + it('detects when not running on GCE', async () => { + metadataStub.instance + .withArgs('machine-type') + .rejects('attribute not found!'); + + const onGce = await gce.onGce(); + assert(!onGce); + }); + + it('detects host type', async () => { + metadataStub.instance + .withArgs('machine-type') + .resolves('fake-machine-type'); + + const hostType = await gce.hostType(); + assert.strictEqual(hostType, 'fake-machine-type'); + }); + + describe('detects host id', () => { + it('as a number', async () => { + metadataStub.instance.withArgs('id').resolves(12345); + + const hostId = await gce.hostId(); + assert.strictEqual(hostId, '12345'); + }); + + it('as a BigNumber', async () => { + metadataStub.instance + .withArgs('id') + .resolves(new BigNumber('2459451723172637654')); + + const hostId = await gce.hostId(); + assert.strictEqual(hostId, '2459451723172637654'); + }); + }); + + it('detects host name', async () => { + metadataStub.instance.withArgs('name').resolves('fake-name'); + + const hostName = await gce.hostName(); + assert.strictEqual(hostName, 'fake-name'); + }); + + describe('zone and region', () => { + it('detects when correctly formatted', async () => { + metadataStub.instance + .withArgs('zone') + .resolves('projects/233510669999/zones/us-east4-b'); + + const zoneAndRegion = await gce.availabilityZoneAndRegion(); + assert.deepStrictEqual(zoneAndRegion, { + zone: 'us-east4-b', + region: 'us-east4', + }); + }); + + it('throws when incorrectly formatted', async () => { + metadataStub.instance.withArgs('zone').resolves(''); + + await assert.rejects( + gce.availabilityZoneAndRegion(), + /zone was not in the expected format/, + ); + }); + }); +}); diff --git a/packages/resource-detector-gcp/test/detectors/gke.test.ts b/packages/resource-detector-gcp/test/detectors/gke.test.ts new file mode 100644 index 0000000000..737a4ab564 --- /dev/null +++ b/packages/resource-detector-gcp/test/detectors/gke.test.ts @@ -0,0 +1,93 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as sinon from 'sinon'; +import * as metadata from 'gcp-metadata'; + +import * as gke from '../../src/detector/gke'; +import * as assert from 'assert'; + +describe('GKE', () => { + let metadataStub: sinon.SinonStubbedInstance; + let envStub: NodeJS.ProcessEnv; + beforeEach(() => { + metadataStub = sinon.stub(metadata); + envStub = sinon.replace(process, 'env', {}); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('detects when running on GKE', async () => { + envStub.KUBERNETES_SERVICE_HOST = 'fake-service-host'; + const onGke = await gke.onGke(); + assert(onGke); + }); + + it('detects when not running on GKE', async () => { + const onGke = await gke.onGke(); + assert(!onGke); + }); + + it('detects host id', async () => { + metadataStub.instance.withArgs('id').resolves(12345); + + const hostId = await gke.hostId(); + assert.strictEqual(hostId, '12345'); + }); + + it('detects cluster name', async () => { + metadataStub.instance + .withArgs('attributes/cluster-name') + .resolves('fake-cluster-name'); + + const clusterName = await gke.clusterName(); + assert.strictEqual(clusterName, 'fake-cluster-name'); + }); + + describe('zone or region', () => { + it('detects region', async () => { + metadataStub.instance + .withArgs('attributes/cluster-location') + .resolves('us-east4'); + + const zoneOrRegion = await gke.availabilityZoneOrRegion(); + assert.deepStrictEqual(zoneOrRegion, {type: 'region', value: 'us-east4'}); + }); + + it('detects zone', async () => { + metadataStub.instance + .withArgs('attributes/cluster-location') + .resolves('us-east4-b'); + + const zoneOrRegion = await gke.availabilityZoneOrRegion(); + assert.deepStrictEqual(zoneOrRegion, { + type: 'zone', + value: 'us-east4-b', + }); + }); + + it('throws when incorrectly formatted', async () => { + metadataStub.instance + .withArgs('attributes/cluster-location') + .resolves(''); + + await assert.rejects( + gke.availabilityZoneOrRegion(), + /unrecognized format for cluster location/, + ); + }); + }); +}); From b6e29851aea9b41ff6b200be6ba1556d47c38765 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 19:58:46 +0000 Subject: [PATCH 03/10] Rename and expose an instance to match repo style --- .../src/detectors/{detector.ts => GcpDetector.ts} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename packages/resource-detector-gcp/src/detectors/{detector.ts => GcpDetector.ts} (96%) diff --git a/packages/resource-detector-gcp/src/detectors/detector.ts b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts similarity index 96% rename from packages/resource-detector-gcp/src/detectors/detector.ts rename to packages/resource-detector-gcp/src/detectors/GcpDetector.ts index f3982af024..664c4dcc73 100644 --- a/packages/resource-detector-gcp/src/detectors/detector.ts +++ b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts @@ -33,7 +33,7 @@ import { SEMRESATTRS_K8S_CLUSTER_NAME, } from '@opentelemetry/semantic-conventions'; -import {AttributeValue, Attributes} from '@opentelemetry/api'; +import { AttributeValue, Attributes } from '@opentelemetry/api'; import { DetectedResource, DetectedResourceAttributes, @@ -152,7 +152,7 @@ async function gaeResource(): Promise { gae.standardCloudRegion(), ]); } else { - ({zone, region} = await gce.availabilityZoneAndRegion()); + ({ zone, region } = await gce.availabilityZoneAndRegion()); } const [faasName, faasVersion, faasInstance] = await Promise.all([ gae.serviceName(), @@ -202,7 +202,7 @@ async function makeResource(attrs: GcpResourceAttributes): Promise { * Google Cloud resource detector which populates attributes based on the environment this * process is running in. If not on GCP, returns an empty resource. */ -export class GcpDetectorSync implements ResourceDetector { +export class GcpDetector implements ResourceDetector { private async _asyncAttributes(): Promise { return (await detect()).attributes; } @@ -215,6 +215,8 @@ export class GcpDetectorSync implements ResourceDetector { attributes[name] = asyncAttributes.then(data => data[name]); }); - return {attributes}; + return { attributes }; } } + +export const gcpDetector = new GcpDetector(); From 2b7fdff4174f857c3126406bfc7df1623dd0437e Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 20:00:59 +0000 Subject: [PATCH 04/10] Delete old GcpDetector.test.ts --- .../test/detectors/GcpDetector.test.ts | 225 ------------------ 1 file changed, 225 deletions(-) delete mode 100644 packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts diff --git a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts deleted file mode 100644 index 49d2ad8c0f..0000000000 --- a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - BASE_PATH, - HEADER_NAME, - HEADER_VALUE, - HOST_ADDRESS, - SECONDARY_HOST_ADDRESS, - resetIsAvailableCache, -} from 'gcp-metadata'; -import * as nock from 'nock'; -import { gcpDetector } from '../../src'; -import { - assertCloudResource, - assertHostResource, - assertK8sResource, - assertContainerResource, - assertEmptyResource, -} from '@opentelemetry/contrib-test-utils'; -import { detectResources } from '@opentelemetry/resources'; - -const HEADERS = { - [HEADER_NAME.toLowerCase()]: HEADER_VALUE, -}; -const INSTANCE_PATH = BASE_PATH + '/instance'; -const INSTANCE_ID_PATH = BASE_PATH + '/instance/id'; -const PROJECT_ID_PATH = BASE_PATH + '/project/project-id'; -const ZONE_PATH = BASE_PATH + '/instance/zone'; -const CLUSTER_NAME_PATH = BASE_PATH + '/instance/attributes/cluster-name'; -const HOSTNAME_PATH = BASE_PATH + '/instance/hostname'; - -describe('gcpDetector', () => { - describe('.detect', () => { - before(() => { - nock.disableNetConnect(); - }); - - after(() => { - nock.enableNetConnect(); - delete process.env.KUBERNETES_SERVICE_HOST; - delete process.env.NAMESPACE; - delete process.env.CONTAINER_NAME; - delete process.env.HOSTNAME; - }); - - beforeEach(() => { - resetIsAvailableCache(); - nock.cleanAll(); - delete process.env.KUBERNETES_SERVICE_HOST; - delete process.env.NAMESPACE; - delete process.env.CONTAINER_NAME; - delete process.env.HOSTNAME; - }); - - it('should return resource with GCP metadata', async () => { - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - // This number is too large to be safely represented by a JS number - // See https://github.com/googleapis/gcp-metadata/tree/fc2f0778138b36285643b2f716c485bf9614611f#take-care-with-large-number-valued-properties - .reply(200, () => '4520031799277581759', HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS) - .get(HOSTNAME_PATH) - .reply(200, () => 'dev.my-project.local', HEADERS); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - - const resource = detectResources({ detectors: [gcpDetector] }); - await resource.waitForAsyncAttributes?.(); - - secondaryScope.done(); - scope.done(); - - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', - }); - assertHostResource(resource, { - id: '4520031799277581759', - name: 'dev.my-project.local', - }); - }); - - it('should populate K8s attributes when KUBERNETES_SERVICE_HOST is set', async () => { - process.env.KUBERNETES_SERVICE_HOST = 'my-host'; - process.env.NAMESPACE = 'my-namespace'; - process.env.HOSTNAME = 'my-hostname'; - process.env.CONTAINER_NAME = 'my-container-name'; - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => '4520031799277581759', HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(200, () => 'my-cluster', HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS) - .get(HOSTNAME_PATH) - .reply(200, () => 'dev.my-project.local', HEADERS); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - - const resource = detectResources({ detectors: [gcpDetector] }); - await resource.waitForAsyncAttributes?.(); - - secondaryScope.done(); - scope.done(); - - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', - }); - assertK8sResource(resource, { - clusterName: 'my-cluster', - podName: 'my-hostname', - namespaceName: 'my-namespace', - }); - assertContainerResource(resource, { name: 'my-container-name' }); - }); - - it('should return resource and empty data for non-available metadata attributes', async () => { - // Set KUBERNETES_SERVICE_HOST to have the implementation call - // CLUSTER_NAME_PATH, to be able to test it handling the HTTP 413. - process.env.KUBERNETES_SERVICE_HOST = 'my-host'; - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(413) - .get(INSTANCE_ID_PATH) - .reply(400, undefined, HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(413) - .get(HOSTNAME_PATH) - .reply(400, undefined, HEADERS); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - - const resource = detectResources({ detectors: [gcpDetector] }); - await resource.waitForAsyncAttributes?.(); - - secondaryScope.done(); - scope.done(); - - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: '', - }); - }); - - it('should return resource and undefined for non-available kubernetes attributes', async () => { - process.env.KUBERNETES_SERVICE_HOST = 'my-host'; - process.env.HOSTNAME = 'my-hostname'; - process.env.CONTAINER_NAME = 'my-container-name'; - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => '4520031799277581759', HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(200, () => 'my-cluster', HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS) - .get(HOSTNAME_PATH) - .reply(200, () => 'dev.my-project.local', HEADERS); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - - const resource = detectResources({ detectors: [gcpDetector] }); - await resource.waitForAsyncAttributes?.(); - - secondaryScope.done(); - scope.done(); - - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', - }); - assertK8sResource(resource, { - clusterName: 'my-cluster', - podName: 'my-hostname', - namespaceName: undefined, - }); - assertContainerResource(resource, { name: 'my-container-name' }); - }); - - it('returns empty resource if not detected', async () => { - const resource = detectResources({ detectors: [gcpDetector] }); - await resource.waitForAsyncAttributes?.(); - assertEmptyResource(resource); - }); - }); -}); From a2d9bc30439c4c7ab5c760e0cc2b2e0ec23a53cc Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 20:05:09 +0000 Subject: [PATCH 05/10] Rename test file to match repo style --- .../{detector.test.ts => GcpDetector.test.ts} | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) rename packages/resource-detector-gcp/test/detectors/{detector.test.ts => GcpDetector.test.ts} (88%) diff --git a/packages/resource-detector-gcp/test/detectors/detector.test.ts b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts similarity index 88% rename from packages/resource-detector-gcp/test/detectors/detector.test.ts rename to packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts index 0837164c32..0104e46ab9 100644 --- a/packages/resource-detector-gcp/test/detectors/detector.test.ts +++ b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts @@ -15,7 +15,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; -import {GcpDetectorSync} from '../../src/detector/detector'; +import { gcpDetector } from '../../src/'; import { detectResources, ResourceDetector, @@ -23,13 +23,13 @@ import { } from '@opentelemetry/resources'; import * as assert from 'assert'; -async function detectAndWait(detector: ResourceDetector): Promise { - const resource = detectResources({detectors: [detector]}); +async function detectAndWait(): Promise { + const resource = detectResources({ detectors: [gcpDetector] }); await resource.waitForAsyncAttributes?.(); return resource; } -describe('GcpDetectorSync', () => { +describe('gcpDetector', () => { let metadataStub: sinon.SinonStubbedInstance; let envStub: NodeJS.ProcessEnv; beforeEach(() => { @@ -46,7 +46,7 @@ describe('GcpDetectorSync', () => { it('returns empty resource when metadata server is not available', async () => { metadataStub.isAvailable.resolves(false); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, {}); }); @@ -65,7 +65,7 @@ describe('GcpDetectorSync', () => { metadataStub.instance .withArgs('attributes/cluster-location') .resolves('us-east4-b'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.availability_zone': 'us-east4-b', @@ -80,7 +80,7 @@ describe('GcpDetectorSync', () => { metadataStub.instance .withArgs('attributes/cluster-location') .resolves('us-east4'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.platform': 'gcp_kubernetes_engine', @@ -106,7 +106,7 @@ describe('GcpDetectorSync', () => { .withArgs('zone') .resolves('projects/233510669999/zones/us-east4-b'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.availability_zone': 'us-east4-b', @@ -130,7 +130,7 @@ describe('GcpDetectorSync', () => { .withArgs('region') .resolves('projects/233510669999/regions/us-east4'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.platform': 'gcp_cloud_run', @@ -153,7 +153,7 @@ describe('GcpDetectorSync', () => { .withArgs('region') .resolves('projects/233510669999/regions/us-east4'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.platform': 'gcp_cloud_functions', @@ -175,7 +175,7 @@ describe('GcpDetectorSync', () => { .withArgs('region') .resolves('projects/233510669999/regions/us-east4'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.availability_zone': 'us-east4-b', @@ -196,7 +196,7 @@ describe('GcpDetectorSync', () => { .withArgs('zone') .resolves('projects/233510669999/zones/us-east4-b'); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, { 'cloud.account.id': 'fake-project-id', 'cloud.availability_zone': 'us-east4-b', @@ -214,7 +214,7 @@ describe('GcpDetectorSync', () => { metadataStub.instance.rejects(); metadataStub.project.rejects(); - const resource = await detectAndWait(new GcpDetectorSync()); + const resource = await detectAndWait(); assert.deepStrictEqual(resource.attributes, {}); }); }); From 11fde0e049062d86afcfdb942bbf226708658bc9 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 22:24:23 +0000 Subject: [PATCH 06/10] fix imports --- packages/resource-detector-gcp/test/detectors/faas.test.ts | 2 +- packages/resource-detector-gcp/test/detectors/gae.test.ts | 4 ++-- packages/resource-detector-gcp/test/detectors/gce.test.ts | 2 +- packages/resource-detector-gcp/test/detectors/gke.test.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/resource-detector-gcp/test/detectors/faas.test.ts b/packages/resource-detector-gcp/test/detectors/faas.test.ts index 7414b03fd2..a0529ee209 100644 --- a/packages/resource-detector-gcp/test/detectors/faas.test.ts +++ b/packages/resource-detector-gcp/test/detectors/faas.test.ts @@ -15,7 +15,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; -import * as faas from '../../src/detector/faas'; +import * as faas from '../../src/detectors/faas'; import * as assert from 'assert'; import {BigNumber} from 'bignumber.js'; diff --git a/packages/resource-detector-gcp/test/detectors/gae.test.ts b/packages/resource-detector-gcp/test/detectors/gae.test.ts index 544b2d6332..5e75381067 100644 --- a/packages/resource-detector-gcp/test/detectors/gae.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gae.test.ts @@ -15,7 +15,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; -import * as gae from '../../src/detector/gae'; +import * as gae from '../../src/detectors/gae'; import * as assert from 'assert'; describe('App Engine (GAE)', () => { @@ -81,7 +81,7 @@ describe('App Engine (GAE)', () => { await assert.rejects( gae.flexAvailabilityZoneAndRegion(), - /zone was not in the expected format/, + /zone was not in the expected format/ ); }); }); diff --git a/packages/resource-detector-gcp/test/detectors/gce.test.ts b/packages/resource-detector-gcp/test/detectors/gce.test.ts index c829b6098d..4910b738a2 100644 --- a/packages/resource-detector-gcp/test/detectors/gce.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gce.test.ts @@ -15,7 +15,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; -import * as gce from '../../src/detector/gce'; +import * as gce from '../../src/detectors/gce'; import * as assert from 'assert'; import {BigNumber} from 'bignumber.js'; diff --git a/packages/resource-detector-gcp/test/detectors/gke.test.ts b/packages/resource-detector-gcp/test/detectors/gke.test.ts index 737a4ab564..c4dac2ee2b 100644 --- a/packages/resource-detector-gcp/test/detectors/gke.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gke.test.ts @@ -15,7 +15,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; -import * as gke from '../../src/detector/gke'; +import * as gke from '../../src/detectors/gke'; import * as assert from 'assert'; describe('GKE', () => { From 8b749d545997b53c8a5ab9fa6e493644c5ec950f Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 22:37:01 +0000 Subject: [PATCH 07/10] Update copyright headers to repo style while maintaining Google copyright --- .../src/detectors/GcpDetector.ts | 29 ++++++++++--------- .../src/detectors/faas.ts | 29 ++++++++++--------- .../src/detectors/gae.ts | 29 ++++++++++--------- .../src/detectors/gce.ts | 29 ++++++++++--------- .../src/detectors/gke.ts | 29 ++++++++++--------- .../test/detectors/GcpDetector.test.ts | 29 ++++++++++--------- .../test/detectors/faas.test.ts | 29 ++++++++++--------- .../test/detectors/gae.test.ts | 29 ++++++++++--------- .../test/detectors/gce.test.ts | 29 ++++++++++--------- .../test/detectors/gke.test.ts | 29 ++++++++++--------- 10 files changed, 160 insertions(+), 130 deletions(-) diff --git a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts index 664c4dcc73..112fd8181b 100644 --- a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts +++ b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { CLOUDPLATFORMVALUES_GCP_APP_ENGINE, diff --git a/packages/resource-detector-gcp/src/detectors/faas.ts b/packages/resource-detector-gcp/src/detectors/faas.ts index b2747f3612..07af7755a4 100644 --- a/packages/resource-detector-gcp/src/detectors/faas.ts +++ b/packages/resource-detector-gcp/src/detectors/faas.ts @@ -1,16 +1,19 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2023 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /** * Implementation in this file copied from diff --git a/packages/resource-detector-gcp/src/detectors/gae.ts b/packages/resource-detector-gcp/src/detectors/gae.ts index ad6ddd239e..5ad5f617db 100644 --- a/packages/resource-detector-gcp/src/detectors/gae.ts +++ b/packages/resource-detector-gcp/src/detectors/gae.ts @@ -1,16 +1,19 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2023 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /** * Implementation in this file copied from diff --git a/packages/resource-detector-gcp/src/detectors/gce.ts b/packages/resource-detector-gcp/src/detectors/gce.ts index 00a3cf0e43..15e4cc5fc2 100644 --- a/packages/resource-detector-gcp/src/detectors/gce.ts +++ b/packages/resource-detector-gcp/src/detectors/gce.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /** * Implementation in this file copied from diff --git a/packages/resource-detector-gcp/src/detectors/gke.ts b/packages/resource-detector-gcp/src/detectors/gke.ts index 290f7015b0..1a9457bb6a 100644 --- a/packages/resource-detector-gcp/src/detectors/gke.ts +++ b/packages/resource-detector-gcp/src/detectors/gke.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ /** * Implementation in this file copied from diff --git a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts index 0104e46ab9..c167e81354 100644 --- a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts +++ b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; diff --git a/packages/resource-detector-gcp/test/detectors/faas.test.ts b/packages/resource-detector-gcp/test/detectors/faas.test.ts index a0529ee209..78e87442df 100644 --- a/packages/resource-detector-gcp/test/detectors/faas.test.ts +++ b/packages/resource-detector-gcp/test/detectors/faas.test.ts @@ -1,16 +1,19 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2023 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; diff --git a/packages/resource-detector-gcp/test/detectors/gae.test.ts b/packages/resource-detector-gcp/test/detectors/gae.test.ts index 5e75381067..15d493d635 100644 --- a/packages/resource-detector-gcp/test/detectors/gae.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gae.test.ts @@ -1,16 +1,19 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2023 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; diff --git a/packages/resource-detector-gcp/test/detectors/gce.test.ts b/packages/resource-detector-gcp/test/detectors/gce.test.ts index 4910b738a2..d3a4102e43 100644 --- a/packages/resource-detector-gcp/test/detectors/gce.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gce.test.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; diff --git a/packages/resource-detector-gcp/test/detectors/gke.test.ts b/packages/resource-detector-gcp/test/detectors/gke.test.ts index c4dac2ee2b..1a5d0a9dbe 100644 --- a/packages/resource-detector-gcp/test/detectors/gke.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gke.test.ts @@ -1,16 +1,19 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * Copyright 2022 Google LLC + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; From c554c62d66a99efcf308927567fd51cbedd73b0f Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 22:45:00 +0000 Subject: [PATCH 08/10] fix lint issues and format with `npm run lint:fix` --- packages/resource-detector-gcp/src/detectors/faas.ts | 2 +- packages/resource-detector-gcp/src/detectors/gce.ts | 12 ++++++------ packages/resource-detector-gcp/src/detectors/gke.ts | 8 ++++---- .../test/detectors/GcpDetector.test.ts | 6 +----- .../test/detectors/faas.test.ts | 2 +- .../resource-detector-gcp/test/detectors/gce.test.ts | 4 ++-- .../resource-detector-gcp/test/detectors/gke.test.ts | 7 +++++-- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/resource-detector-gcp/src/detectors/faas.ts b/packages/resource-detector-gcp/src/detectors/faas.ts index 07af7755a4..30e1bb1e55 100644 --- a/packages/resource-detector-gcp/src/detectors/faas.ts +++ b/packages/resource-detector-gcp/src/detectors/faas.ts @@ -61,7 +61,7 @@ export async function faasVersion(): Promise { export async function faasInstance(): Promise { // May be a bignumber.js BigNumber which can just be converted with toString(). See // https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties - const id = await metadata.instance(ID_METADATA_ATTR); + const id = await metadata.instance(ID_METADATA_ATTR); return id.toString(); } diff --git a/packages/resource-detector-gcp/src/detectors/gce.ts b/packages/resource-detector-gcp/src/detectors/gce.ts index 15e4cc5fc2..a6473bdafd 100644 --- a/packages/resource-detector-gcp/src/detectors/gce.ts +++ b/packages/resource-detector-gcp/src/detectors/gce.ts @@ -20,7 +20,7 @@ * https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/blob/v1.8.0/detectors/gcp/gce.go */ -import {diag} from '@opentelemetry/api'; +import { diag } from '@opentelemetry/api'; import * as metadata from 'gcp-metadata'; const MACHINE_TYPE_METADATA_ATTR = 'machine-type'; @@ -36,7 +36,7 @@ export async function onGce(): Promise { diag.debug( 'Could not fetch metadata attribute %s, assuming not on GCE. Error was %s', MACHINE_TYPE_METADATA_ATTR, - err, + err ); return false; } @@ -57,7 +57,7 @@ export async function hostType(): Promise { export async function hostId(): Promise { // May be a bignumber.js BigNumber which can just be converted with toString(). See // https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties - const id = await metadata.instance(ID_METADATA_ATTR); + const id = await metadata.instance(ID_METADATA_ATTR); return id.toString(); } @@ -82,12 +82,12 @@ export async function availabilityZoneAndRegion(): Promise<{ // Format described in // https://cloud.google.com/compute/docs/metadata/default-metadata-values#vm_instance_metadata const re = /projects\/\d+\/zones\/(?(?\w+-\w+)-\w+)/; - const {zone, region} = fullZone.match(re)?.groups ?? {}; + const { zone, region } = fullZone.match(re)?.groups ?? {}; if (!zone || !region) { throw new Error( - `zone was not in the expected format: projects/PROJECT_NUM/zones/COUNTRY-REGION-ZONE. Got ${fullZone}`, + `zone was not in the expected format: projects/PROJECT_NUM/zones/COUNTRY-REGION-ZONE. Got ${fullZone}` ); } - return {zone, region}; + return { zone, region }; } diff --git a/packages/resource-detector-gcp/src/detectors/gke.ts b/packages/resource-detector-gcp/src/detectors/gke.ts index 1a9457bb6a..0f48f1d138 100644 --- a/packages/resource-detector-gcp/src/detectors/gke.ts +++ b/packages/resource-detector-gcp/src/detectors/gke.ts @@ -56,16 +56,16 @@ export async function availabilityZoneOrRegion(): Promise<{ value: string; }> { const clusterLocation = await metadata.instance( - CLUSTER_LOCATION_METADATA_ATTR, + CLUSTER_LOCATION_METADATA_ATTR ); switch (countChar(clusterLocation, '-')) { case 1: - return {type: 'region', value: clusterLocation}; + return { type: 'region', value: clusterLocation }; case 2: - return {type: 'zone', value: clusterLocation}; + return { type: 'zone', value: clusterLocation }; default: throw new Error( - `unrecognized format for cluster location: ${clusterLocation}`, + `unrecognized format for cluster location: ${clusterLocation}` ); } } diff --git a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts index c167e81354..c59f367e6b 100644 --- a/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts +++ b/packages/resource-detector-gcp/test/detectors/GcpDetector.test.ts @@ -19,11 +19,7 @@ import * as sinon from 'sinon'; import * as metadata from 'gcp-metadata'; import { gcpDetector } from '../../src/'; -import { - detectResources, - ResourceDetector, - Resource, -} from '@opentelemetry/resources'; +import { detectResources, Resource } from '@opentelemetry/resources'; import * as assert from 'assert'; async function detectAndWait(): Promise { diff --git a/packages/resource-detector-gcp/test/detectors/faas.test.ts b/packages/resource-detector-gcp/test/detectors/faas.test.ts index 78e87442df..4629172544 100644 --- a/packages/resource-detector-gcp/test/detectors/faas.test.ts +++ b/packages/resource-detector-gcp/test/detectors/faas.test.ts @@ -20,7 +20,7 @@ import * as metadata from 'gcp-metadata'; import * as faas from '../../src/detectors/faas'; import * as assert from 'assert'; -import {BigNumber} from 'bignumber.js'; +import { BigNumber } from 'bignumber.js'; describe('FaaS (Cloud Run/Functions)', () => { let metadataStub: sinon.SinonStubbedInstance; diff --git a/packages/resource-detector-gcp/test/detectors/gce.test.ts b/packages/resource-detector-gcp/test/detectors/gce.test.ts index d3a4102e43..c4651137d8 100644 --- a/packages/resource-detector-gcp/test/detectors/gce.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gce.test.ts @@ -20,7 +20,7 @@ import * as metadata from 'gcp-metadata'; import * as gce from '../../src/detectors/gce'; import * as assert from 'assert'; -import {BigNumber} from 'bignumber.js'; +import { BigNumber } from 'bignumber.js'; describe('GCE', () => { let metadataStub: sinon.SinonStubbedInstance; @@ -102,7 +102,7 @@ describe('GCE', () => { await assert.rejects( gce.availabilityZoneAndRegion(), - /zone was not in the expected format/, + /zone was not in the expected format/ ); }); }); diff --git a/packages/resource-detector-gcp/test/detectors/gke.test.ts b/packages/resource-detector-gcp/test/detectors/gke.test.ts index 1a5d0a9dbe..620b38072b 100644 --- a/packages/resource-detector-gcp/test/detectors/gke.test.ts +++ b/packages/resource-detector-gcp/test/detectors/gke.test.ts @@ -67,7 +67,10 @@ describe('GKE', () => { .resolves('us-east4'); const zoneOrRegion = await gke.availabilityZoneOrRegion(); - assert.deepStrictEqual(zoneOrRegion, {type: 'region', value: 'us-east4'}); + assert.deepStrictEqual(zoneOrRegion, { + type: 'region', + value: 'us-east4', + }); }); it('detects zone', async () => { @@ -89,7 +92,7 @@ describe('GKE', () => { await assert.rejects( gke.availabilityZoneOrRegion(), - /unrecognized format for cluster location/, + /unrecognized format for cluster location/ ); }); }); From 8cc85bbd69678ee4e16a1131cd79083f99db1bb6 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 22:53:12 +0000 Subject: [PATCH 09/10] suppressTracing during resource detection to satisfy GcpDetectorIntegration.test.ts --- .../resource-detector-gcp/src/detectors/GcpDetector.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts index 112fd8181b..7935c1f843 100644 --- a/packages/resource-detector-gcp/src/detectors/GcpDetector.ts +++ b/packages/resource-detector-gcp/src/detectors/GcpDetector.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { context } from '@opentelemetry/api'; +import { suppressTracing } from '@opentelemetry/core'; import { CLOUDPLATFORMVALUES_GCP_APP_ENGINE, CLOUDPLATFORMVALUES_GCP_CLOUD_FUNCTIONS, @@ -207,7 +209,11 @@ async function makeResource(attrs: GcpResourceAttributes): Promise { */ export class GcpDetector implements ResourceDetector { private async _asyncAttributes(): Promise { - return (await detect()).attributes; + const resource = await context.with( + suppressTracing(context.active()), + detect + ); + return resource.attributes; } detect(): DetectedResource { From f9051c0c5dd6c4ba81e2ba092fc3287ed7de5ec3 Mon Sep 17 00:00:00 2001 From: Aaron Abbott Date: Fri, 29 Aug 2025 23:29:24 +0000 Subject: [PATCH 10/10] update devDependencies --- package-lock.json | 10 ++++++---- packages/resource-detector-gcp/package.json | 5 +++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3e0f408ac9..ae60180c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37042,9 +37042,10 @@ "@types/mocha": "10.0.10", "@types/node": "18.18.14", "@types/semver": "7.5.8", - "nock": "13.3.3", + "@types/sinon": "17.0.4", + "bignumber.js": "9.3.1", "nyc": "17.1.0", - "rimraf": "5.0.10", + "sinon": "15.2.0", "typescript": "5.0.4" }, "engines": { @@ -47731,10 +47732,11 @@ "@types/mocha": "10.0.10", "@types/node": "18.18.14", "@types/semver": "7.5.8", + "@types/sinon": "17.0.4", + "bignumber.js": "9.3.1", "gcp-metadata": "^6.0.0", - "nock": "13.3.3", "nyc": "17.1.0", - "rimraf": "5.0.10", + "sinon": "15.2.0", "typescript": "5.0.4" }, "dependencies": { diff --git a/packages/resource-detector-gcp/package.json b/packages/resource-detector-gcp/package.json index 25311d4271..2367fa7071 100644 --- a/packages/resource-detector-gcp/package.json +++ b/packages/resource-detector-gcp/package.json @@ -52,9 +52,10 @@ "@types/mocha": "10.0.10", "@types/node": "18.18.14", "@types/semver": "7.5.8", - "nock": "13.3.3", + "@types/sinon": "17.0.4", + "bignumber.js": "9.3.1", "nyc": "17.1.0", - "rimraf": "5.0.10", + "sinon": "15.2.0", "typescript": "5.0.4" }, "peerDependencies": {