Skip to content

Commit 695182a

Browse files
committed
added node version detection
1 parent 165078c commit 695182a

File tree

3 files changed

+154
-119
lines changed

3 files changed

+154
-119
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/otlp-aws-span-exporter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import { OTLPExporterNodeConfigBase } from '@opentelemetry/otlp-exporter-base';
66
import { ProtobufTraceSerializer } from '@opentelemetry/otlp-transformer';
77
import { ReadableSpan } from '@opentelemetry/sdk-trace-base';
88
import { ExportResult } from '@opentelemetry/core';
9+
import { getNodeVersion } from './utils';
910

1011
/**
1112
* This exporter extends the functionality of the OTLPProtoTraceExporter to allow spans to be exported
1213
* to the XRay OTLP endpoint https://xray.[AWSRegion].amazonaws.com/v1/traces. Utilizes the aws-sdk
1314
* library to sign and directly inject SigV4 Authentication to the exported request's headers. <a
1415
* href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-OTLPEndpoint.html">...</a>
16+
*
17+
* This only works with version >=16 Node.js environments.
1518
*/
1619
export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter {
1720
private static readonly SERVICE_NAME: string = 'xray';
@@ -24,7 +27,7 @@ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter {
2427
private signatureV4: any;
2528
private httpRequest: any;
2629

27-
// If there are required dependencies then we enable SigV4 signing. Otherwise skip it
30+
// If the required dependencies are installed then we enable SigV4 signing. Otherwise skip it
2831
private hasRequiredDependencies: boolean = false;
2932

3033
constructor(endpoint: string, config?: OTLPExporterNodeConfigBase) {
@@ -91,6 +94,11 @@ export class OTLPAwsSpanExporter extends OTLPProtoTraceExporter {
9194
}
9295

9396
private initDependencies(): any {
97+
if (getNodeVersion() < 16) {
98+
diag.error('SigV4 signing requires atleast Node major version 16');
99+
return;
100+
}
101+
94102
try {
95103
const awsSdkModule = require('@aws-sdk/credential-provider-node');
96104
const awsCryptoModule = require('@aws-crypto/sha256-js');
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export const getNodeVersion = () => {
5+
const nodeVersion = process.versions.node;
6+
const versionParts = nodeVersion.split('.');
7+
8+
if (versionParts.length === 0) {
9+
return -1;
10+
}
11+
12+
const majorVersion = parseInt(versionParts[0], 10);
13+
14+
if (isNaN(majorVersion)) {
15+
return -1;
16+
}
17+
18+
return majorVersion;
19+
};

aws-distro-opentelemetry-node-autoinstrumentation/test/otlp-aws-span-exporter.test.ts

Lines changed: 126 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { OTLPAwsSpanExporter } from '../src/otlp-aws-span-exporter';
55
import * as sinon from 'sinon';
66
import * as proxyquire from 'proxyquire';
77
import nock = require('nock');
8+
import { getNodeVersion } from '../src/utils';
89

910
const XRAY_OTLP_ENDPOINT = 'https://xray.us-east-1.amazonaws.com';
1011
const AUTHORIZATION_HEADER = 'Authorization';
@@ -15,150 +16,157 @@ const EXPECTED_AUTH_HEADER = 'AWS4-HMAC-SHA256 Credential=test_key/some_date/us-
1516
const EXPECTED_AUTH_X_AMZ_DATE = 'some_date';
1617
const EXPECTED_AUTH_SECURITY_TOKEN = 'test_token';
1718

18-
describe('OTLPAwsSpanExporter', () => {
19-
let sandbox: sinon.SinonSandbox;
20-
let scope: nock.Scope;
21-
let mockModule: any;
19+
const nodeVersion = getNodeVersion();
2220

23-
beforeEach(() => {
24-
scope = nock(XRAY_OTLP_ENDPOINT)
25-
.post('/v1/traces')
26-
.reply((uri: any, requestBody: any) => {
27-
return [200, ''];
28-
});
21+
if (nodeVersion >= 16) {
22+
describe('OTLPAwsSpanExporter', () => {
23+
let sandbox: sinon.SinonSandbox;
24+
let scope: nock.Scope;
25+
let mockModule: any;
2926

30-
sandbox = sinon.createSandbox();
31-
mockModule = proxyquire('../src/otlp-aws-span-exporter', {
32-
'@smithy/signature-v4': {
33-
SignatureV4: class MockSignatureV4 {
34-
sign(req: any) {
35-
req.headers = {
36-
...req.headers,
37-
[AUTHORIZATION_HEADER]: EXPECTED_AUTH_HEADER,
38-
[X_AMZ_DATE_HEADER]: EXPECTED_AUTH_X_AMZ_DATE,
39-
[X_AMZ_SECURITY_TOKEN_HEADER]: EXPECTED_AUTH_SECURITY_TOKEN,
40-
};
27+
beforeEach(() => {
28+
sandbox = sinon.createSandbox();
29+
30+
scope = nock(XRAY_OTLP_ENDPOINT)
31+
.post('/v1/traces')
32+
.reply((uri: any, requestBody: any) => {
33+
return [200, ''];
34+
});
4135

42-
return req;
43-
}
36+
mockModule = proxyquire('../src/otlp-aws-span-exporter', {
37+
'@smithy/signature-v4': {
38+
SignatureV4: class MockSignatureV4 {
39+
sign(req: any) {
40+
req.headers = {
41+
...req.headers,
42+
[AUTHORIZATION_HEADER]: EXPECTED_AUTH_HEADER,
43+
[X_AMZ_DATE_HEADER]: EXPECTED_AUTH_X_AMZ_DATE,
44+
[X_AMZ_SECURITY_TOKEN_HEADER]: EXPECTED_AUTH_SECURITY_TOKEN,
45+
};
46+
47+
return req;
48+
}
49+
},
4450
},
45-
},
46-
'@aws-sdk/credential-provider-node': {
47-
defaultProvider: () => async () => {
48-
return {
49-
accessKeyId: 'test_access_key',
50-
secretAccessKey: 'test_secret_key',
51-
};
51+
'@aws-sdk/credential-provider-node': {
52+
defaultProvider: () => async () => {
53+
return {
54+
accessKeyId: 'test_access_key',
55+
secretAccessKey: 'test_secret_key',
56+
};
57+
},
5258
},
53-
},
59+
});
5460
});
55-
});
5661

57-
afterEach(() => {
58-
sandbox.restore();
59-
});
60-
61-
it('Should inject SigV4 Headers successfully', async () => {
62-
const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces');
62+
afterEach(() => {
63+
sandbox.restore();
64+
});
6365

64-
scope.on('request', (req, interceptor, body) => {
65-
const headers = req.headers;
66-
expect(headers).toHaveProperty(AUTHORIZATION_HEADER.toLowerCase());
67-
expect(headers).toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase());
68-
expect(headers).toHaveProperty(X_AMZ_DATE_HEADER.toLowerCase());
66+
it('Should inject SigV4 Headers successfully', async () => {
67+
const exporter = new mockModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces');
6968

70-
expect(headers[AUTHORIZATION_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_HEADER);
71-
expect(headers[X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_SECURITY_TOKEN);
72-
expect(headers[X_AMZ_DATE_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_X_AMZ_DATE);
69+
scope.on('request', (req, interceptor, body) => {
70+
const headers = req.headers;
71+
expect(headers).toHaveProperty(AUTHORIZATION_HEADER.toLowerCase());
72+
expect(headers).toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase());
73+
expect(headers).toHaveProperty(X_AMZ_DATE_HEADER.toLowerCase());
7374

74-
expect(headers['content-type']).toBe('application/x-protobuf');
75-
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
76-
});
75+
expect(headers[AUTHORIZATION_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_HEADER);
76+
expect(headers[X_AMZ_SECURITY_TOKEN_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_SECURITY_TOKEN);
77+
expect(headers[X_AMZ_DATE_HEADER.toLowerCase()]).toBe(EXPECTED_AUTH_X_AMZ_DATE);
7778

78-
await exporter.export([], () => {});
79-
});
79+
expect(headers['content-type']).toBe('application/x-protobuf');
80+
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
81+
});
8082

81-
describe('Should not inject SigV4 headers if dependencies are missing', () => {
82-
const dependencies = [
83-
'@aws-sdk/credential-provider-node',
84-
'@aws-crypto/sha256-js',
85-
'@smithy/signature-v4',
86-
'@smithy/protocol-http',
87-
];
88-
89-
dependencies.forEach(dependency => {
90-
it(`should not sign headers if missing dependency: ${dependency}`, async () => {
91-
const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces');
92-
93-
scope.on('request', (req, interceptor, body) => {
94-
const headers = req.headers;
95-
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
96-
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
97-
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
98-
99-
expect(headers['content-type']).toBe('application/x-protobuf');
100-
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
101-
});
83+
await exporter.export([], () => {});
84+
});
10285

103-
Object.keys(require.cache).forEach(key => {
104-
delete require.cache[key];
86+
describe('Should not inject SigV4 headers if dependencies are missing', () => {
87+
const dependencies = [
88+
'@aws-sdk/credential-provider-node',
89+
'@aws-crypto/sha256-js',
90+
'@smithy/signature-v4',
91+
'@smithy/protocol-http',
92+
];
93+
94+
dependencies.forEach(dependency => {
95+
it(`should not sign headers if missing dependency: ${dependency}`, async () => {
96+
const exporter = new OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT + '/v1/traces');
97+
98+
scope.on('request', (req, interceptor, body) => {
99+
const headers = req.headers;
100+
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
101+
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
102+
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
103+
104+
expect(headers['content-type']).toBe('application/x-protobuf');
105+
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
106+
});
107+
108+
Object.keys(require.cache).forEach(key => {
109+
delete require.cache[key];
110+
});
111+
const requireStub = sandbox.stub(require('module'), '_load');
112+
requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`));
113+
requireStub.callThrough();
114+
115+
await exporter.export([], () => {});
105116
});
106-
const requireStub = sandbox.stub(require('module'), '_load');
107-
requireStub.withArgs(dependency).throws(new Error(`Cannot find module '${dependency}'`));
108-
requireStub.callThrough();
109-
110-
await exporter.export([], () => {});
111117
});
112118
});
113-
});
114119

115-
it('should not inject SigV4 headers if failure to sign headers', async () => {
116-
scope.on('request', (req, interceptor, body) => {
117-
const headers = req.headers;
118-
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
119-
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
120-
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
120+
it('should not inject SigV4 headers if failure to sign headers', async () => {
121+
scope.on('request', (req, interceptor, body) => {
122+
const headers = req.headers;
123+
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
124+
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
125+
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
121126

122-
expect(headers['content-type']).toBe('application/x-protobuf');
123-
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
124-
});
127+
expect(headers['content-type']).toBe('application/x-protobuf');
128+
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
129+
});
125130

126-
const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', {
127-
'@smithy/signature-v4': {
128-
SignatureV4: class MockSignatureV4 {
129-
sign() {
130-
throw new Error('signing error');
131-
}
131+
const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', {
132+
'@smithy/signature-v4': {
133+
SignatureV4: class MockSignatureV4 {
134+
sign() {
135+
throw new Error('signing error');
136+
}
137+
},
132138
},
133-
},
134-
});
139+
});
135140

136-
const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT);
141+
const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT);
137142

138-
await exporter.export([], () => {});
139-
});
143+
await exporter.export([], () => {});
144+
});
140145

141-
it('should not inject SigV4 headers if failure to retrieve credentials', async () => {
142-
scope.on('request', (req, interceptor, body) => {
143-
const headers = req.headers;
144-
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
145-
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
146-
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
146+
it('should not inject SigV4 headers if failure to retrieve credentials', async () => {
147+
scope.on('request', (req, interceptor, body) => {
148+
const headers = req.headers;
149+
expect(headers).not.toHaveProperty(AUTHORIZATION_HEADER);
150+
expect(headers).not.toHaveProperty(X_AMZ_DATE_HEADER);
151+
expect(headers).not.toHaveProperty(X_AMZ_SECURITY_TOKEN_HEADER);
147152

148-
expect(headers['content-type']).toBe('application/x-protobuf');
149-
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
150-
});
153+
expect(headers['content-type']).toBe('application/x-protobuf');
154+
expect(headers['user-agent']).toMatch(/^OTel-OTLP-Exporter-JavaScript\/\d+\.\d+\.\d+$/);
155+
});
151156

152-
const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', {
153-
'@aws-sdk/credential-provider-node': {
154-
defaultProvider: () => async () => {
155-
throw new Error('credentials error');
157+
const stubbedModule = proxyquire('../src/otlp-aws-span-exporter', {
158+
'@aws-sdk/credential-provider-node': {
159+
defaultProvider: () => async () => {
160+
throw new Error('credentials error');
161+
},
156162
},
157-
},
158-
});
163+
});
159164

160-
const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT);
165+
const exporter = new stubbedModule.OTLPAwsSpanExporter(XRAY_OTLP_ENDPOINT);
161166

162-
await exporter.export([], () => {});
167+
await exporter.export([], () => {});
168+
});
163169
});
164-
});
170+
} else {
171+
it.skip(`Skipping tests - Node.js version ${nodeVersion} is below required version 16`, () => {});
172+
}

0 commit comments

Comments
 (0)