diff --git a/.circleci/checksum.sh b/.circleci/checksum.sh index af2e0f293e..fa7cab9ae9 100644 --- a/.circleci/checksum.sh +++ b/.circleci/checksum.sh @@ -22,5 +22,6 @@ fi openssl md5 package.json >> $FILE find packages/*/package.json | xargs -I{} openssl md5 {} >> $FILE +find metapackages/*/package.json | xargs -I{} openssl md5 {} >> $FILE sort -o $FILE $FILE diff --git a/.circleci/config.yml b/.circleci/config.yml index e19df84e8a..15b55c5a74 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ node_test_env: &node_test_env cache_1: &cache_1 - key: npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + key: npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 paths: - ./node_modules - ./package-lock.json @@ -25,13 +25,15 @@ cache_1: &cache_1 - packages/opentelemetry-web/node_modules cache_2: &cache_2 - key: npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + key: npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 paths: - packages/opentelemetry-plugin-grpc/node_modules - packages/opentelemetry-plugin-http/node_modules - packages/opentelemetry-plugin-https/node_modules - packages/opentelemetry-exporter-collector/node_modules - packages/opentelemetry-plugin-xml-http-request/node_modules + - packages/opentelemetry-resource-detector-aws/node_modules + - packages/opentelemetry-resource-detector-gcp/node_modules - packages/opentelemetry-resources/node_modules node_unit_tests: &node_unit_tests @@ -53,10 +55,10 @@ node_unit_tests: &node_unit_tests echo "CIRCLE_NODE_VERSION=${CIRCLE_NODE_VERSION}" - restore_cache: keys: - - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - restore_cache: keys: - - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - run: name: Install Root Dependencies command: npm install --ignore-scripts @@ -93,10 +95,10 @@ browsers_unit_tests: &browsers_unit_tests echo "CIRCLE_NODE_VERSION=${CIRCLE_NODE_VERSION}" - restore_cache: keys: - - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-01-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - restore_cache: keys: - - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-F267A71D + - npm-cache-02-{{ .Environment.CIRCLE_JOB }}-{{ checksum "/tmp/checksums.txt" }}-20B74F85 - run: name: Install Root Dependencies command: npm install --ignore-scripts diff --git a/packages/opentelemetry-resource-detector-gcp/README.md b/packages/opentelemetry-resource-detector-gcp/README.md index ccb2af7b09..8f493387b0 100644 --- a/packages/opentelemetry-resource-detector-gcp/README.md +++ b/packages/opentelemetry-resource-detector-gcp/README.md @@ -12,6 +12,8 @@ The OpenTelemetry Resource is an immutable representation of the entity producin ## Installation +The GCP resource detector requires Node.JS 10+ due to a dependency on [`gcp-metadata`](https://www.npmjs.com/package/gcp-metadata) which uses features only available in Node.JS 10+. + ```bash npm install --save @opentelemetry/resource-detector-gcp ``` diff --git a/packages/opentelemetry-resource-detector-gcp/package.json b/packages/opentelemetry-resource-detector-gcp/package.json index c059e00f93..e22b8261bb 100644 --- a/packages/opentelemetry-resource-detector-gcp/package.json +++ b/packages/opentelemetry-resource-detector-gcp/package.json @@ -27,7 +27,7 @@ "author": "OpenTelemetry Authors", "license": "Apache-2.0", "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" }, "files": [ "build/src/**/*.js", @@ -41,24 +41,23 @@ "access": "public" }, "devDependencies": { + "@opentelemetry/core": "^0.10.2", "@types/mocha": "8.0.1", "@types/node": "14.0.27", - "@types/sinon": "9.0.4", + "@types/semver": "7.3.3", "codecov": "3.7.2", "gts": "2.0.2", "mocha": "7.2.0", "nock": "12.0.3", "nyc": "15.1.0", "rimraf": "3.0.2", - "sinon": "9.0.2", "ts-mocha": "7.0.0", "ts-node": "8.10.2", "typescript": "3.9.7" }, "dependencies": { - "@opentelemetry/api": "^0.10.2", - "@opentelemetry/core": "^0.10.2", "@opentelemetry/resources": "^0.10.2", - "gcp-metadata": "^3.5.0" + "gcp-metadata": "^4.1.4", + "semver": "7.3.2" } } diff --git a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts index 2cc793bc16..ed01accf62 100644 --- a/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts +++ b/packages/opentelemetry-resource-detector-gcp/src/detectors/GcpDetector.ts @@ -15,6 +15,7 @@ */ import * as os from 'os'; +import * as semver from 'semver'; import * as gcpMetadata from 'gcp-metadata'; import { Detector, @@ -42,7 +43,10 @@ class GcpDetector implements Detector { * @param config The resource detection config with a required logger */ async detect(config: ResourceDetectionConfigWithLogger): Promise { - if (!(await gcpMetadata.isAvailable())) { + if ( + !semver.satisfies(process.version, '>=10') || + !(await gcpMetadata.isAvailable()) + ) { config.logger.debug('GcpDetector failed: GCP Metadata unavailable.'); return Resource.empty(); } diff --git a/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts b/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts index f2ae581553..4d227919f2 100644 --- a/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts +++ b/packages/opentelemetry-resource-detector-gcp/test/detectors/GcpDetector.test.ts @@ -23,6 +23,7 @@ import { resetIsAvailableCache, } from 'gcp-metadata'; import * as nock from 'nock'; +import * as semver from 'semver'; import { gcpDetector } from '../../src'; import { assertCloudResource, @@ -43,123 +44,126 @@ 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'; -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; - }); +(semver.satisfies(process.version, '>=10') ? describe : describe.skip)( + 'gcpDetector', + () => { + describe('.detect', () => { + before(() => { + nock.disableNetConnect(); + }); - it('should return resource with GCP metadata', async () => { - const scope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .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(CLUSTER_NAME_PATH) - .reply(404); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource: Resource = await gcpDetector.detect({ - logger: new NoopLogger(), + after(() => { + nock.enableNetConnect(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; }); - secondaryScope.done(); - scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', + beforeEach(() => { + resetIsAvailableCache(); + nock.cleanAll(); + delete process.env.KUBERNETES_SERVICE_HOST; + delete process.env.NAMESPACE; + delete process.env.CONTAINER_NAME; + delete process.env.HOSTNAME; }); - assertHostResource(resource, { id: '4520031799277582000' }); - }); - 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); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - secondaryScope.done(); - scope.done(); + it('should return resource with GCP metadata', async () => { + const scope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .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(CLUSTER_NAME_PATH) + .reply(404); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource: Resource = await gcpDetector.detect({ + logger: new NoopLogger(), + }); + secondaryScope.done(); + scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertHostResource(resource, { id: '4520031799277582000' }); }); - assertK8sResource(resource, { - clusterName: 'my-cluster', - podName: 'my-hostname', - namespaceName: 'my-namespace', + + 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); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + 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' }); }); - assertContainerResource(resource, { name: 'my-container-name' }); - }); - it('should return resource and empty data for non-available metadata attributes', async () => { - 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); - const secondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - secondaryScope.done(); - scope.done(); + it('should return resource and empty data for non-available metadata attributes', async () => { + 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); + const secondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + secondaryScope.done(); + scope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: '', + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: '', + }); }); - }); - it('returns empty resource if not detected', async () => { - const resource = await gcpDetector.detect({ logger: new NoopLogger() }); - assertEmptyResource(resource); + it('returns empty resource if not detected', async () => { + const resource = await gcpDetector.detect({ logger: new NoopLogger() }); + assertEmptyResource(resource); + }); }); - }); -}); + } +); diff --git a/packages/opentelemetry-sdk-node/package.json b/packages/opentelemetry-sdk-node/package.json index e87a38d71a..2351f7cfbd 100644 --- a/packages/opentelemetry-sdk-node/package.json +++ b/packages/opentelemetry-sdk-node/package.json @@ -57,13 +57,15 @@ "@opentelemetry/context-async-hooks": "^0.10.2", "@types/mocha": "7.0.2", "@types/node": "14.0.27", + "@types/semver": "7.3.3", "@types/sinon": "9.0.4", "codecov": "3.7.2", - "gcp-metadata": "^3.5.0", + "gcp-metadata": "^4.1.4", "gts": "2.0.2", "istanbul-instrumenter-loader": "3.0.1", "mocha": "7.2.0", "nyc": "15.1.0", + "semver": "7.3.2", "sinon": "9.0.3", "ts-loader": "7.0.5", "ts-mocha": "7.0.0", diff --git a/packages/opentelemetry-sdk-node/test/sdk.test.ts b/packages/opentelemetry-sdk-node/test/sdk.test.ts index 73aeb40b53..34598a2cba 100644 --- a/packages/opentelemetry-sdk-node/test/sdk.test.ts +++ b/packages/opentelemetry-sdk-node/test/sdk.test.ts @@ -15,6 +15,7 @@ */ import * as nock from 'nock'; +import * as semver from 'semver'; import { context, metrics, @@ -84,6 +85,7 @@ describe('Node SDK', () => { before(() => { // Disable attempted load of default plugins Sinon.replace(NodeConfig, 'DEFAULT_INSTRUMENTATION_PLUGINS', {}); + nock.disableNetConnect(); }); beforeEach(() => { @@ -195,69 +197,65 @@ describe('Node SDK', () => { delete process.env.OTEL_RESOURCE_ATTRIBUTES; }); - describe('in GCP environment', () => { - after(() => { - resetIsAvailableCache(); - }); - - it('returns a merged resource', async () => { - const sdk = new NodeSDK({ - autoDetectResources: true, + // GCP detector only works in 10+ + (semver.satisfies(process.version, '>=10') ? describe : describe.skip)( + 'in GCP environment', + () => { + after(() => { + resetIsAvailableCache(); }); - const gcpScope = nock(HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS) - .get(INSTANCE_ID_PATH) - .reply(200, () => 452003179927758, HEADERS) - .get(PROJECT_ID_PATH) - .reply(200, () => 'my-project-id', HEADERS) - .get(ZONE_PATH) - .reply(200, () => 'project/zone/my-zone', HEADERS) - .get(CLUSTER_NAME_PATH) - .reply(404); - const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .reply(200, {}, HEADERS); - const awsScope = nock(AWS_HOST) - .persist() - .put(AWS_TOKEN_PATH) - .matchHeader(AWS_METADATA_TTL_HEADER, '60') - .replyWithError({ code: 'ENOTFOUND' }); - await sdk.detectResources(); - const resource = sdk['_resource']; - - awsScope.done(); - gcpSecondaryScope.done(); - gcpScope.done(); - assertCloudResource(resource, { - provider: 'gcp', - accountId: 'my-project-id', - zone: 'my-zone', - }); - assertHostResource(resource, { id: '452003179927758' }); - assertServiceResource(resource, { - instanceId: '627cc493', - name: 'my-service', - namespace: 'default', - version: '0.0.1', + it('returns a merged resource', async () => { + const sdk = new NodeSDK({ + autoDetectResources: true, + }); + const gcpScope = nock(HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS) + .get(INSTANCE_ID_PATH) + .reply(200, () => 452003179927758, HEADERS) + .get(PROJECT_ID_PATH) + .reply(200, () => 'my-project-id', HEADERS) + .get(ZONE_PATH) + .reply(200, () => 'project/zone/my-zone', HEADERS) + .get(CLUSTER_NAME_PATH) + .reply(404); + const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) + .get(INSTANCE_PATH) + .reply(200, {}, HEADERS); + const awsScope = nock(AWS_HOST) + .persist() + .put(AWS_TOKEN_PATH) + .matchHeader(AWS_METADATA_TTL_HEADER, '60') + .replyWithError({ code: 'ENOTFOUND' }); + await sdk.detectResources(); + const resource = sdk['_resource']; + + awsScope.done(); + gcpSecondaryScope.done(); + gcpScope.done(); + + assertCloudResource(resource, { + provider: 'gcp', + accountId: 'my-project-id', + zone: 'my-zone', + }); + assertHostResource(resource, { id: '452003179927758' }); + assertServiceResource(resource, { + instanceId: '627cc493', + name: 'my-service', + namespace: 'default', + version: '0.0.1', + }); }); - }); - }); + } + ); describe('in AWS environment', () => { it('returns a merged resource', async () => { const sdk = new NodeSDK({ autoDetectResources: true, }); - const gcpScope = nock(HOST_ADDRESS).get(INSTANCE_PATH).replyWithError({ - code: 'ENOTFOUND', - }); - const gcpSecondaryScope = nock(SECONDARY_HOST_ADDRESS) - .get(INSTANCE_PATH) - .replyWithError({ - code: 'ENOTFOUND', - }); const awsScope = nock(AWS_HOST) .persist() .put(AWS_TOKEN_PATH) @@ -271,8 +269,6 @@ describe('Node SDK', () => { .reply(200, () => mockedHostResponse); await sdk.detectResources(); const resource: Resource = sdk['_resource']; - gcpSecondaryScope.done(); - gcpScope.done(); awsScope.done(); assertCloudResource(resource, {