Skip to content

Commit 41fa127

Browse files
committed
resource-detector-gcp
1 parent fc11a5a commit 41fa127

File tree

7 files changed

+222
-71
lines changed

7 files changed

+222
-71
lines changed

detectors/node/opentelemetry-resource-detector-gcp/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"author": "OpenTelemetry Authors",
2828
"license": "Apache-2.0",
2929
"engines": {
30-
"node": ">=14"
30+
"node": "^18.19.0 || >=20.6.0"
3131
},
3232
"files": [
3333
"build/src/**/*.js",
@@ -43,7 +43,7 @@
4343
"devDependencies": {
4444
"@opentelemetry/api": "^1.0.0",
4545
"@opentelemetry/contrib-test-utils": "^0.45.1",
46-
"@opentelemetry/instrumentation-http": "^0.57.2",
46+
"@opentelemetry/instrumentation-http": "^0.200.0-dev.0",
4747
"@types/mocha": "10.0.10",
4848
"@types/node": "18.18.14",
4949
"@types/semver": "7.5.8",
@@ -56,8 +56,8 @@
5656
"@opentelemetry/api": "^1.0.0"
5757
},
5858
"dependencies": {
59-
"@opentelemetry/core": "^1.0.0",
60-
"@opentelemetry/resources": "^1.10.0",
59+
"@opentelemetry/core": "^2.0.0-dev.0",
60+
"@opentelemetry/resources": "^2.0.0-dev.0",
6161
"@opentelemetry/semantic-conventions": "^1.27.0",
6262
"gcp-metadata": "^6.0.0"
6363
},

detectors/node/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts

Lines changed: 52 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@
1515
*/
1616

1717
import * as gcpMetadata from 'gcp-metadata';
18-
import { context, diag } from '@opentelemetry/api';
18+
import { context } from '@opentelemetry/api';
1919
import { suppressTracing } from '@opentelemetry/core';
2020
import {
21-
DetectorSync,
2221
ResourceDetectionConfig,
23-
Resource,
24-
ResourceAttributes,
25-
IResource,
22+
ResourceDetector,
23+
DetectedResource,
24+
DetectedResourceAttributes,
2625
} from '@opentelemetry/resources';
2726
import {
2827
CLOUDPROVIDERVALUES_GCP,
@@ -42,61 +41,52 @@ import {
4241
* Cloud Platform and return a {@link Resource} populated with metadata about
4342
* the instance. Returns an empty Resource if detection fails.
4443
*/
45-
class GcpDetector implements DetectorSync {
46-
detect(_config?: ResourceDetectionConfig): IResource {
44+
class GcpDetector implements ResourceDetector {
45+
detect(_config?: ResourceDetectionConfig): DetectedResource {
4746
const attributes = context.with(suppressTracing(context.active()), () =>
4847
this._getAttributes()
4948
);
50-
return new Resource({}, attributes);
49+
return { attributes };
5150
}
5251

5352
/**
54-
* Attempts to connect and obtain instance configuration data from the GCP metadata service.
55-
* If the connection is successful it returns a promise containing a {@link ResourceAttributes}
56-
* object with instance metadata. Returns a promise containing an
57-
* empty {@link ResourceAttributes} if the connection or parsing of the metadata fails.
53+
* Asynchronously gather GCP cloud metadata.
5854
*/
59-
private async _getAttributes(): Promise<ResourceAttributes> {
60-
if (!(await gcpMetadata.isAvailable())) {
61-
diag.debug('GcpDetector failed: GCP Metadata unavailable.');
62-
return {};
63-
}
64-
65-
const [projectId, instanceId, zoneId, clusterName, hostname] =
66-
await Promise.all([
67-
this._getProjectId(),
68-
this._getInstanceId(),
69-
this._getZone(),
70-
this._getClusterName(),
71-
this._getHostname(),
72-
]);
55+
private _getAttributes(): DetectedResourceAttributes {
56+
const isAvail = gcpMetadata.isAvailable();
7357

74-
const attributes: ResourceAttributes = {};
75-
attributes[SEMRESATTRS_CLOUD_ACCOUNT_ID] = projectId;
76-
attributes[SEMRESATTRS_HOST_ID] = instanceId;
77-
attributes[SEMRESATTRS_HOST_NAME] = hostname;
78-
attributes[SEMRESATTRS_CLOUD_AVAILABILITY_ZONE] = zoneId;
79-
attributes[SEMRESATTRS_CLOUD_PROVIDER] = CLOUDPROVIDERVALUES_GCP;
58+
const attributes: DetectedResourceAttributes = {
59+
[SEMRESATTRS_CLOUD_PROVIDER]: (async () => {
60+
return await isAvail ? CLOUDPROVIDERVALUES_GCP : undefined;
61+
})(),
62+
[SEMRESATTRS_CLOUD_ACCOUNT_ID]: this._getProjectId(isAvail),
63+
[SEMRESATTRS_HOST_ID]: this._getInstanceId(isAvail),
64+
[SEMRESATTRS_HOST_NAME]: this._getHostname(isAvail),
65+
[SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: this._getZone(isAvail),
66+
};
8067

81-
if (process.env.KUBERNETES_SERVICE_HOST)
82-
this._addK8sAttributes(attributes, clusterName);
68+
// Add resource attributes for K8s.
69+
if (process.env.KUBERNETES_SERVICE_HOST) {
70+
attributes[SEMRESATTRS_K8S_CLUSTER_NAME] = this._getClusterName(isAvail);
71+
attributes[SEMRESATTRS_K8S_NAMESPACE_NAME] = (async () => {
72+
return await isAvail ? process.env.NAMESPACE : undefined;
73+
})();
74+
attributes[SEMRESATTRS_K8S_POD_NAME] = (async () => {
75+
return await isAvail ? process.env.HOSTNAME : undefined;
76+
})();
77+
attributes[SEMRESATTRS_CONTAINER_NAME] = (async () => {
78+
return await isAvail ? process.env.CONTAINER_NAME : undefined;
79+
})();
80+
}
8381

8482
return attributes;
8583
}
8684

87-
/** Add resource attributes for K8s */
88-
private _addK8sAttributes(
89-
attributes: ResourceAttributes,
90-
clusterName: string
91-
): void {
92-
attributes[SEMRESATTRS_K8S_CLUSTER_NAME] = clusterName;
93-
attributes[SEMRESATTRS_K8S_NAMESPACE_NAME] = process.env.NAMESPACE ?? '';
94-
attributes[SEMRESATTRS_K8S_POD_NAME] = process.env.HOSTNAME ?? '';
95-
attributes[SEMRESATTRS_CONTAINER_NAME] = process.env.CONTAINER_NAME ?? '';
96-
}
97-
9885
/** Gets project id from GCP project metadata. */
99-
private async _getProjectId(): Promise<string> {
86+
private async _getProjectId(isAvail: Promise<boolean>): Promise<string | undefined> {
87+
if (!(await isAvail)) {
88+
return undefined;
89+
}
10090
try {
10191
return await gcpMetadata.project('project-id');
10292
} catch {
@@ -105,7 +95,10 @@ class GcpDetector implements DetectorSync {
10595
}
10696

10797
/** Gets instance id from GCP instance metadata. */
108-
private async _getInstanceId(): Promise<string> {
98+
private async _getInstanceId(isAvail: Promise<boolean>): Promise<string | undefined> {
99+
if (!(await isAvail)) {
100+
return undefined;
101+
}
109102
try {
110103
const id = await gcpMetadata.instance('id');
111104
return id.toString();
@@ -115,7 +108,10 @@ class GcpDetector implements DetectorSync {
115108
}
116109

117110
/** Gets zone from GCP instance metadata. */
118-
private async _getZone(): Promise<string> {
111+
private async _getZone(isAvail: Promise<boolean>): Promise<string | undefined> {
112+
if (!(await isAvail)) {
113+
return undefined;
114+
}
119115
try {
120116
const zoneId = await gcpMetadata.instance('zone');
121117
if (zoneId) {
@@ -128,7 +124,10 @@ class GcpDetector implements DetectorSync {
128124
}
129125

130126
/** Gets cluster name from GCP instance metadata. */
131-
private async _getClusterName(): Promise<string> {
127+
private async _getClusterName(isAvail: Promise<boolean>): Promise<string | undefined> {
128+
if (!(await isAvail)) {
129+
return undefined;
130+
}
132131
try {
133132
return await gcpMetadata.instance('attributes/cluster-name');
134133
} catch {
@@ -137,7 +136,10 @@ class GcpDetector implements DetectorSync {
137136
}
138137

139138
/** Gets hostname from GCP instance metadata. */
140-
private async _getHostname(): Promise<string> {
139+
private async _getHostname(isAvail: Promise<boolean>): Promise<string | undefined> {
140+
if (!(await isAvail)) {
141+
return undefined;
142+
}
141143
try {
142144
return await gcpMetadata.instance('hostname');
143145
} catch {

detectors/node/opentelemetry-resource-detector-gcp/src/detectors/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414
* limitations under the License.
1515
*/
1616

17-
export * from './GcpDetector';
17+
export { gcpDetector } from './GcpDetector';

detectors/node/opentelemetry-resource-detector-gcp/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
export * from './detectors';
17+
export { gcpDetector } from './detectors';
1818

1919
// Internal - used for tests only
2020
export { resetIsAvailableCache } from 'gcp-metadata';

detectors/node/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
assertContainerResource,
3232
assertEmptyResource,
3333
} from '@opentelemetry/contrib-test-utils';
34+
import { detectResources } from '@opentelemetry/resources';
3435

3536
const HEADERS = {
3637
[HEADER_NAME.toLowerCase()]: HEADER_VALUE,
@@ -77,15 +78,13 @@ describe('gcpDetector', () => {
7778
.reply(200, () => 'my-project-id', HEADERS)
7879
.get(ZONE_PATH)
7980
.reply(200, () => 'project/zone/my-zone', HEADERS)
80-
.get(CLUSTER_NAME_PATH)
81-
.reply(404)
8281
.get(HOSTNAME_PATH)
8382
.reply(200, () => 'dev.my-project.local', HEADERS);
8483
const secondaryScope = nock(SECONDARY_HOST_ADDRESS)
8584
.get(INSTANCE_PATH)
8685
.reply(200, {}, HEADERS);
8786

88-
const resource = gcpDetector.detect();
87+
const resource = detectResources({ detectors: [gcpDetector] });
8988
await resource.waitForAsyncAttributes?.();
9089

9190
secondaryScope.done();
@@ -124,7 +123,7 @@ describe('gcpDetector', () => {
124123
.get(INSTANCE_PATH)
125124
.reply(200, {}, HEADERS);
126125

127-
const resource = gcpDetector.detect();
126+
const resource = detectResources({ detectors: [gcpDetector] });
128127
await resource.waitForAsyncAttributes?.();
129128

130129
secondaryScope.done();
@@ -144,6 +143,9 @@ describe('gcpDetector', () => {
144143
});
145144

146145
it('should return resource and empty data for non-available metadata attributes', async () => {
146+
// Set KUBERNETES_SERVICE_HOST to have the implementation call
147+
// CLUSTER_NAME_PATH, to be able to test it handling the HTTP 413.
148+
process.env.KUBERNETES_SERVICE_HOST = 'my-host';
147149
const scope = nock(HOST_ADDRESS)
148150
.get(INSTANCE_PATH)
149151
.reply(200, {}, HEADERS)
@@ -161,7 +163,7 @@ describe('gcpDetector', () => {
161163
.get(INSTANCE_PATH)
162164
.reply(200, {}, HEADERS);
163165

164-
const resource = gcpDetector.detect();
166+
const resource = detectResources({ detectors: [gcpDetector] });
165167
await resource.waitForAsyncAttributes?.();
166168

167169
secondaryScope.done();
@@ -175,7 +177,7 @@ describe('gcpDetector', () => {
175177
});
176178

177179
it('returns empty resource if not detected', async () => {
178-
const resource = gcpDetector.detect();
180+
const resource = detectResources({ detectors: [gcpDetector] });
179181
await resource.waitForAsyncAttributes?.();
180182
assertEmptyResource(resource);
181183
});

detectors/node/opentelemetry-resource-detector-gcp/test/detectors/GcpDetectorIntegration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
SimpleSpanProcessor,
3939
} from '@opentelemetry/sdk-trace-base';
4040
import { NodeSDK } from '@opentelemetry/sdk-node';
41-
import { IResource } from '@opentelemetry/resources';
41+
import { detectResources } from '@opentelemetry/resources';
4242

4343
describe('[Integration] GcpDetector', () => {
4444
it('should not start spans for detector requests', async () => {
@@ -63,7 +63,7 @@ describe('[Integration] GcpDetector', () => {
6363
// tracing being exported. We do the detection outside the SDK constructor to have such
6464
// scenario.
6565
const { gcpDetector } = require('../../build/src/detectors/GcpDetector');
66-
const resource = gcpDetector.detect() as IResource;
66+
const resource = detectResources({ detectors: [gcpDetector] });
6767
await resource.waitForAsyncAttributes?.();
6868

6969
// Wait for the next loop to let the span close properly

0 commit comments

Comments
 (0)