@depup/elastic-apm-node 4.15.0-depup.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +26 -0
- package/NOTICE.md +442 -0
- package/README.md +48 -0
- package/changes.json +78 -0
- package/index.d.ts +398 -0
- package/index.js +11 -0
- package/lib/InflightEventSet.js +53 -0
- package/lib/activation-method.js +119 -0
- package/lib/agent.js +941 -0
- package/lib/apm-client/apm-client.js +313 -0
- package/lib/apm-client/http-apm-client/CHANGELOG.md +271 -0
- package/lib/apm-client/http-apm-client/README.md +485 -0
- package/lib/apm-client/http-apm-client/central-config.js +41 -0
- package/lib/apm-client/http-apm-client/container-info.js +111 -0
- package/lib/apm-client/http-apm-client/detect-hostname.js +96 -0
- package/lib/apm-client/http-apm-client/index.js +1975 -0
- package/lib/apm-client/http-apm-client/logging.js +31 -0
- package/lib/apm-client/http-apm-client/ndjson.js +20 -0
- package/lib/apm-client/http-apm-client/truncate.js +434 -0
- package/lib/apm-client/noop-apm-client.js +73 -0
- package/lib/async-hooks-polyfill.js +58 -0
- package/lib/cloud-metadata/aws.js +175 -0
- package/lib/cloud-metadata/azure.js +123 -0
- package/lib/cloud-metadata/callback-coordination.js +159 -0
- package/lib/cloud-metadata/gcp.js +133 -0
- package/lib/cloud-metadata/index.js +175 -0
- package/lib/config/config.js +458 -0
- package/lib/config/normalizers.js +701 -0
- package/lib/config/schema.js +1007 -0
- package/lib/constants.js +35 -0
- package/lib/errors.js +303 -0
- package/lib/filters/sanitize-field-names.js +69 -0
- package/lib/http-request.js +249 -0
- package/lib/instrumentation/azure-functions.js +519 -0
- package/lib/instrumentation/context.js +56 -0
- package/lib/instrumentation/dropped-spans-stats.js +112 -0
- package/lib/instrumentation/elasticsearch-shared.js +63 -0
- package/lib/instrumentation/express-utils.js +91 -0
- package/lib/instrumentation/generic-span.js +322 -0
- package/lib/instrumentation/http-shared.js +424 -0
- package/lib/instrumentation/ids.js +39 -0
- package/lib/instrumentation/index.js +1127 -0
- package/lib/instrumentation/modules/@apollo/server.js +30 -0
- package/lib/instrumentation/modules/@aws-sdk/client-dynamodb.js +143 -0
- package/lib/instrumentation/modules/@aws-sdk/client-s3.js +230 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sns.js +197 -0
- package/lib/instrumentation/modules/@aws-sdk/client-sqs.js +336 -0
- package/lib/instrumentation/modules/@elastic/elasticsearch.js +343 -0
- package/lib/instrumentation/modules/@hapi/hapi.js +221 -0
- package/lib/instrumentation/modules/@opentelemetry/api.js +86 -0
- package/lib/instrumentation/modules/@opentelemetry/sdk-metrics.js +79 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/commands-queue.js +178 -0
- package/lib/instrumentation/modules/@redis/client/dist/lib/client/index.js +49 -0
- package/lib/instrumentation/modules/@smithy/smithy-client.js +198 -0
- package/lib/instrumentation/modules/_lambda-handler.js +40 -0
- package/lib/instrumentation/modules/apollo-server-core.js +49 -0
- package/lib/instrumentation/modules/aws-sdk/dynamodb.js +155 -0
- package/lib/instrumentation/modules/aws-sdk/s3.js +184 -0
- package/lib/instrumentation/modules/aws-sdk/sns.js +232 -0
- package/lib/instrumentation/modules/aws-sdk/sqs.js +361 -0
- package/lib/instrumentation/modules/aws-sdk.js +76 -0
- package/lib/instrumentation/modules/bluebird.js +93 -0
- package/lib/instrumentation/modules/cassandra-driver.js +280 -0
- package/lib/instrumentation/modules/elasticsearch.js +191 -0
- package/lib/instrumentation/modules/express-graphql.js +66 -0
- package/lib/instrumentation/modules/express-queue.js +28 -0
- package/lib/instrumentation/modules/express.js +162 -0
- package/lib/instrumentation/modules/fastify.js +172 -0
- package/lib/instrumentation/modules/finalhandler.js +41 -0
- package/lib/instrumentation/modules/generic-pool.js +85 -0
- package/lib/instrumentation/modules/graphql.js +256 -0
- package/lib/instrumentation/modules/handlebars.js +22 -0
- package/lib/instrumentation/modules/http.js +112 -0
- package/lib/instrumentation/modules/http2.js +320 -0
- package/lib/instrumentation/modules/https.js +68 -0
- package/lib/instrumentation/modules/ioredis.js +94 -0
- package/lib/instrumentation/modules/jade.js +18 -0
- package/lib/instrumentation/modules/kafkajs.js +476 -0
- package/lib/instrumentation/modules/knex.js +91 -0
- package/lib/instrumentation/modules/koa-router.js +74 -0
- package/lib/instrumentation/modules/koa.js +15 -0
- package/lib/instrumentation/modules/memcached.js +99 -0
- package/lib/instrumentation/modules/mimic-response.js +45 -0
- package/lib/instrumentation/modules/mongodb/lib/cmap/connection_pool.js +40 -0
- package/lib/instrumentation/modules/mongodb-core.js +206 -0
- package/lib/instrumentation/modules/mongodb.js +259 -0
- package/lib/instrumentation/modules/mysql.js +200 -0
- package/lib/instrumentation/modules/mysql2.js +140 -0
- package/lib/instrumentation/modules/pg.js +148 -0
- package/lib/instrumentation/modules/pug.js +18 -0
- package/lib/instrumentation/modules/redis.js +176 -0
- package/lib/instrumentation/modules/restify.js +52 -0
- package/lib/instrumentation/modules/tedious.js +159 -0
- package/lib/instrumentation/modules/undici.js +270 -0
- package/lib/instrumentation/modules/ws.js +59 -0
- package/lib/instrumentation/noop-transaction.js +81 -0
- package/lib/instrumentation/run-context/AbstractRunContextManager.js +215 -0
- package/lib/instrumentation/run-context/AsyncHooksRunContextManager.js +106 -0
- package/lib/instrumentation/run-context/AsyncLocalStorageRunContextManager.js +73 -0
- package/lib/instrumentation/run-context/BasicRunContextManager.js +82 -0
- package/lib/instrumentation/run-context/RunContext.js +151 -0
- package/lib/instrumentation/run-context/index.js +23 -0
- package/lib/instrumentation/shimmer.js +123 -0
- package/lib/instrumentation/span-compression.js +239 -0
- package/lib/instrumentation/span.js +621 -0
- package/lib/instrumentation/template-shared.js +43 -0
- package/lib/instrumentation/timer.js +84 -0
- package/lib/instrumentation/transaction.js +571 -0
- package/lib/lambda.js +992 -0
- package/lib/load-source-map.js +100 -0
- package/lib/logging.js +212 -0
- package/lib/metrics/index.js +92 -0
- package/lib/metrics/platforms/generic/index.js +40 -0
- package/lib/metrics/platforms/generic/process-cpu.js +22 -0
- package/lib/metrics/platforms/generic/process-top.js +157 -0
- package/lib/metrics/platforms/generic/stats.js +34 -0
- package/lib/metrics/platforms/generic/system-cpu.js +51 -0
- package/lib/metrics/platforms/linux/index.js +19 -0
- package/lib/metrics/platforms/linux/stats.js +213 -0
- package/lib/metrics/queue.js +90 -0
- package/lib/metrics/registry.js +52 -0
- package/lib/metrics/reporter.js +119 -0
- package/lib/metrics/runtime.js +77 -0
- package/lib/middleware/connect.js +16 -0
- package/lib/opentelemetry-bridge/OTelBridgeNonRecordingSpan.js +150 -0
- package/lib/opentelemetry-bridge/OTelBridgeRunContext.js +124 -0
- package/lib/opentelemetry-bridge/OTelContextManager.js +82 -0
- package/lib/opentelemetry-bridge/OTelSpan.js +344 -0
- package/lib/opentelemetry-bridge/OTelTracer.js +201 -0
- package/lib/opentelemetry-bridge/OTelTracerProvider.js +25 -0
- package/lib/opentelemetry-bridge/README.md +244 -0
- package/lib/opentelemetry-bridge/index.js +15 -0
- package/lib/opentelemetry-bridge/oblog.js +23 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/README.md +3 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/internal/validators.js +52 -0
- package/lib/opentelemetry-bridge/opentelemetry-core-mini/trace/TraceState.js +109 -0
- package/lib/opentelemetry-bridge/otelutils.js +99 -0
- package/lib/opentelemetry-bridge/setup.js +76 -0
- package/lib/opentelemetry-metrics/ElasticApmMetricExporter.js +285 -0
- package/lib/opentelemetry-metrics/index.js +50 -0
- package/lib/parsers.js +225 -0
- package/lib/propwrap.js +147 -0
- package/lib/stacktraces.js +537 -0
- package/lib/symbols.js +15 -0
- package/lib/tracecontext/index.js +118 -0
- package/lib/tracecontext/traceparent.js +185 -0
- package/lib/tracecontext/tracestate.js +388 -0
- package/lib/wildcard-matcher.js +52 -0
- package/loader.mjs +7 -0
- package/package.json +299 -0
- package/start.d.ts +8 -0
- package/start.js +29 -0
- package/types/aws-lambda.d.ts +98 -0
- package/types/connect.d.ts +23 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
const URL = require('url').URL;
|
|
9
|
+
const { httpRequest } = require('../http-request');
|
|
10
|
+
const DEFAULT_BASE_URL = new URL('/', 'http://169.254.169.254:80');
|
|
11
|
+
|
|
12
|
+
function sanitizeHttpHeaderValue(value) {
|
|
13
|
+
// no newlines, carriage returns, or ascii characters outside of 32 (\x20) - 127 (\x7F)
|
|
14
|
+
const newValue = value.replace(/[\r\n]/g, '').replace(/[^\x20-\x7F]/g, '');
|
|
15
|
+
return newValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Logic for making request to /latest/dynamic/instance-identity/document
|
|
20
|
+
*
|
|
21
|
+
* The headers parameter allow us to, if needed, set the IMDSv2 token
|
|
22
|
+
*/
|
|
23
|
+
function getMetadataAwsWithHeaders(
|
|
24
|
+
baseUrl,
|
|
25
|
+
headers,
|
|
26
|
+
connectTimeoutMs,
|
|
27
|
+
resTimeoutMs,
|
|
28
|
+
logger,
|
|
29
|
+
cb,
|
|
30
|
+
) {
|
|
31
|
+
const options = {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
timeout: resTimeoutMs,
|
|
34
|
+
connectTimeout: connectTimeoutMs,
|
|
35
|
+
};
|
|
36
|
+
if (headers) {
|
|
37
|
+
options.headers = headers;
|
|
38
|
+
}
|
|
39
|
+
const url = baseUrl + 'latest/dynamic/instance-identity/document';
|
|
40
|
+
const req = httpRequest(url, options, function (res) {
|
|
41
|
+
const finalData = [];
|
|
42
|
+
res.on('data', function (data) {
|
|
43
|
+
finalData.push(data);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
res.on('end', function (data) {
|
|
47
|
+
let result;
|
|
48
|
+
try {
|
|
49
|
+
result = formatMetadataStringIntoObject(finalData.join(''));
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.trace(
|
|
52
|
+
'aws metadata server responded, but there was an ' +
|
|
53
|
+
'error parsing the result: %o',
|
|
54
|
+
err,
|
|
55
|
+
);
|
|
56
|
+
cb(err);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
cb(null, result);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
req.on('timeout', function () {
|
|
64
|
+
req.destroy(new Error('request to metadata server timed out'));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
req.on('connectTimeout', function () {
|
|
68
|
+
req.destroy(new Error('could not ping metadata server'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
req.on('error', function (err) {
|
|
72
|
+
cb(err);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
req.end();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fetches metadata from either an IMDSv2 or IMDSv1 endpoint
|
|
80
|
+
*
|
|
81
|
+
* Attempts to fetch a token for an IMDSv2 service call. If this call
|
|
82
|
+
* is a success, then we call the instance endpoint with the token. If
|
|
83
|
+
* this call fails, then we call the instance endpoint without the token.
|
|
84
|
+
*
|
|
85
|
+
* A "success" to the token endpoint means an HTTP status code of 200
|
|
86
|
+
* and a non-zero-length return value for the token.
|
|
87
|
+
*
|
|
88
|
+
* https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
|
|
89
|
+
*/
|
|
90
|
+
function getMetadataAws(
|
|
91
|
+
connectTimeoutMs,
|
|
92
|
+
resTimeoutMs,
|
|
93
|
+
logger,
|
|
94
|
+
baseUrlOverride,
|
|
95
|
+
cb,
|
|
96
|
+
) {
|
|
97
|
+
const baseUrl = baseUrlOverride || DEFAULT_BASE_URL;
|
|
98
|
+
const url = baseUrl + 'latest/api/token';
|
|
99
|
+
const options = {
|
|
100
|
+
method: 'PUT',
|
|
101
|
+
headers: {
|
|
102
|
+
'X-aws-ec2-metadata-token-ttl-seconds': 300,
|
|
103
|
+
},
|
|
104
|
+
timeout: resTimeoutMs,
|
|
105
|
+
connectTimeout: connectTimeoutMs,
|
|
106
|
+
};
|
|
107
|
+
const req = httpRequest(url, options, function (res) {
|
|
108
|
+
const finalData = [];
|
|
109
|
+
res.on('data', function (data) {
|
|
110
|
+
finalData.push(data);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
res.on('end', function () {
|
|
114
|
+
const token = sanitizeHttpHeaderValue(finalData.join(''));
|
|
115
|
+
const headers = {};
|
|
116
|
+
if (token && res.statusCode === 200) {
|
|
117
|
+
// uses return value from call to latest/api/token as token,
|
|
118
|
+
// and takes extra steps to ensure characters are valid
|
|
119
|
+
headers['X-aws-ec2-metadata-token'] = token;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getMetadataAwsWithHeaders(
|
|
123
|
+
baseUrl,
|
|
124
|
+
headers,
|
|
125
|
+
connectTimeoutMs,
|
|
126
|
+
resTimeoutMs,
|
|
127
|
+
logger,
|
|
128
|
+
cb,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
req.on('timeout', function () {
|
|
133
|
+
req.destroy(new Error('request for metadata token timed out'));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
req.on('connectTimeout', function () {
|
|
137
|
+
req.destroy(
|
|
138
|
+
new Error('socket connection to metadata token server timed out'),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
req.on('error', function (err) {
|
|
143
|
+
cb(err);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
req.end();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Builds metadata object
|
|
151
|
+
*
|
|
152
|
+
* Takes the response from a /latest/dynamic/instance-identity/document
|
|
153
|
+
* service request and formats it into the cloud metadata object
|
|
154
|
+
*/
|
|
155
|
+
function formatMetadataStringIntoObject(string) {
|
|
156
|
+
const data = JSON.parse(string);
|
|
157
|
+
const metadata = {
|
|
158
|
+
account: {
|
|
159
|
+
id: String(data.accountId),
|
|
160
|
+
},
|
|
161
|
+
instance: {
|
|
162
|
+
id: String(data.instanceId),
|
|
163
|
+
},
|
|
164
|
+
availability_zone: String(data.availabilityZone),
|
|
165
|
+
machine: {
|
|
166
|
+
type: String(data.instanceType),
|
|
167
|
+
},
|
|
168
|
+
provider: 'aws',
|
|
169
|
+
region: String(data.region),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return metadata;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { getMetadataAws };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
const URL = require('url').URL;
|
|
9
|
+
const { httpRequest } = require('../http-request');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_BASE_URL = new URL('/', 'http://169.254.169.254:80');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks for metadata server then fetches data
|
|
15
|
+
*
|
|
16
|
+
* The getMetadataAzure method will fetch cloud metadata information
|
|
17
|
+
* from Amazon's IMDSv1 endpoint and return (via callback)
|
|
18
|
+
* the formatted metadata.
|
|
19
|
+
*
|
|
20
|
+
* Before fetching data, the server will be "pinged" by attempting
|
|
21
|
+
* to connect via TCP with a short timeout (`connectTimeoutMs`).
|
|
22
|
+
*
|
|
23
|
+
* https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=windows
|
|
24
|
+
*/
|
|
25
|
+
function getMetadataAzure(
|
|
26
|
+
connectTimeoutMs,
|
|
27
|
+
resTimeoutMs,
|
|
28
|
+
logger,
|
|
29
|
+
baseUrlOverride,
|
|
30
|
+
cb,
|
|
31
|
+
) {
|
|
32
|
+
const baseUrl = baseUrlOverride || DEFAULT_BASE_URL;
|
|
33
|
+
const options = {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
timeout: resTimeoutMs,
|
|
36
|
+
connectTimeout: connectTimeoutMs,
|
|
37
|
+
headers: {
|
|
38
|
+
Metadata: 'true',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const url = baseUrl.toString() + 'metadata/instance?api-version=2020-09-01';
|
|
43
|
+
|
|
44
|
+
const req = httpRequest(url, options, function (res) {
|
|
45
|
+
const finalData = [];
|
|
46
|
+
res.on('data', function (data) {
|
|
47
|
+
finalData.push(data);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
res.on('end', function (data) {
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = formatMetadataStringIntoObject(finalData.join(''));
|
|
54
|
+
} catch (err) {
|
|
55
|
+
logger.trace(
|
|
56
|
+
'azure metadata server responded, but there was an ' +
|
|
57
|
+
'error parsing the result: %o',
|
|
58
|
+
err,
|
|
59
|
+
);
|
|
60
|
+
cb(err);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
cb(null, result);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
req.on('timeout', function () {
|
|
68
|
+
req.destroy(new Error('request to azure metadata server timed out'));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
req.on('connectTimeout', function () {
|
|
72
|
+
req.destroy(new Error('could not ping azure metadata server'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
req.on('error', function (err) {
|
|
76
|
+
cb(err);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
req.end();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds metadata object
|
|
84
|
+
*
|
|
85
|
+
* Takes the response from /metadata/instance?api-version=2020-09-01
|
|
86
|
+
* service request and formats it into the cloud metadata object
|
|
87
|
+
*/
|
|
88
|
+
function formatMetadataStringIntoObject(string) {
|
|
89
|
+
const metadata = {
|
|
90
|
+
account: {
|
|
91
|
+
id: null,
|
|
92
|
+
},
|
|
93
|
+
instance: {
|
|
94
|
+
id: null,
|
|
95
|
+
name: null,
|
|
96
|
+
},
|
|
97
|
+
project: {
|
|
98
|
+
name: null,
|
|
99
|
+
},
|
|
100
|
+
availability_zone: null,
|
|
101
|
+
machine: {
|
|
102
|
+
type: null,
|
|
103
|
+
},
|
|
104
|
+
provider: 'azure',
|
|
105
|
+
region: null,
|
|
106
|
+
};
|
|
107
|
+
const parsed = JSON.parse(string);
|
|
108
|
+
if (!parsed.compute) {
|
|
109
|
+
return metadata;
|
|
110
|
+
}
|
|
111
|
+
const data = parsed.compute;
|
|
112
|
+
metadata.account.id = String(data.subscriptionId);
|
|
113
|
+
metadata.instance.id = String(data.vmId);
|
|
114
|
+
metadata.instance.name = String(data.name);
|
|
115
|
+
metadata.project.name = String(data.resourceGroupName);
|
|
116
|
+
metadata.availability_zone = String(data.zone);
|
|
117
|
+
metadata.machine.type = String(data.vmSize);
|
|
118
|
+
metadata.region = String(data.location);
|
|
119
|
+
|
|
120
|
+
return metadata;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { getMetadataAzure };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
const { EventEmitter } = require('events');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Coordinates fetching of metadata from multiple providers
|
|
12
|
+
*
|
|
13
|
+
* Implements event based coordination for fetching metadata
|
|
14
|
+
* from multiple providers. The first provider to return
|
|
15
|
+
* a non-error result "wins". When this happens the CallbackCoordination
|
|
16
|
+
* object will emit a `result` event.
|
|
17
|
+
*
|
|
18
|
+
* If all the metadata providers fail to return a result, then the
|
|
19
|
+
* object will emit an error event indicating failure to collect metadata
|
|
20
|
+
* from any event.
|
|
21
|
+
*
|
|
22
|
+
* Since a scheduled callback may (accidently) fail to call its own
|
|
23
|
+
* callback function, the CallbackCoordination object includes its
|
|
24
|
+
* own timeout timer to avoid deadlock situation.
|
|
25
|
+
*/
|
|
26
|
+
class CallbackCoordination extends EventEmitter {
|
|
27
|
+
constructor(maxWaitMS = -1, logger) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
this.logger = logger;
|
|
31
|
+
// how many results have we seen
|
|
32
|
+
this.resultCount = 0;
|
|
33
|
+
this.expectedResults = 0;
|
|
34
|
+
this.errors = [];
|
|
35
|
+
this.scheduled = [];
|
|
36
|
+
this.done = false;
|
|
37
|
+
this.started = false;
|
|
38
|
+
this.timeout = null;
|
|
39
|
+
if (maxWaitMS !== -1) {
|
|
40
|
+
this.timeout = setTimeout(() => {
|
|
41
|
+
if (!this.done) {
|
|
42
|
+
this.complete();
|
|
43
|
+
this.logger.warn(
|
|
44
|
+
'cloud metadata requests timed out, using default values instead',
|
|
45
|
+
);
|
|
46
|
+
const error = new CallbackCoordinationError(
|
|
47
|
+
'callback coordination reached timeout',
|
|
48
|
+
this.errors,
|
|
49
|
+
);
|
|
50
|
+
this.emit('error', error);
|
|
51
|
+
}
|
|
52
|
+
}, maxWaitMS);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Finishes coordination
|
|
58
|
+
*
|
|
59
|
+
* Marks as `done`, cleans up timeout (if necessary)
|
|
60
|
+
*/
|
|
61
|
+
complete() {
|
|
62
|
+
this.done = true;
|
|
63
|
+
if (this.timeout) {
|
|
64
|
+
clearTimeout(this.timeout);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Accepts and schedules a callback function
|
|
70
|
+
*
|
|
71
|
+
* Callback will be in the form
|
|
72
|
+
* function(fetcher) {
|
|
73
|
+
* //... work to fetch data ...
|
|
74
|
+
*
|
|
75
|
+
* // this callback calls the recordResult method
|
|
76
|
+
* // method of the fetcher
|
|
77
|
+
* fetcher.recordResult(error, result)
|
|
78
|
+
* }
|
|
79
|
+
*
|
|
80
|
+
* Method also increments expectedResults counter to keep track
|
|
81
|
+
* of how many callbacks we've scheduled
|
|
82
|
+
*/
|
|
83
|
+
schedule(fetcherCallback) {
|
|
84
|
+
if (this.started) {
|
|
85
|
+
this.logger.error('Can not schedule callback, already started');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.expectedResults++;
|
|
90
|
+
this.scheduled.push(fetcherCallback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Starts processing of the callbacks scheduled by the `schedule` method
|
|
95
|
+
*/
|
|
96
|
+
start() {
|
|
97
|
+
this.started = true;
|
|
98
|
+
// if called with nothing, send an error through so we don't hang
|
|
99
|
+
if (this.scheduled.length === 0) {
|
|
100
|
+
const error = new CallbackCoordinationError('no callbacks to run');
|
|
101
|
+
this.recordResult(error);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const cb of this.scheduled) {
|
|
105
|
+
process.nextTick(cb.bind(null, this));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Receives calls from scheduled callbacks.
|
|
111
|
+
*
|
|
112
|
+
* If called with a non-error, the method will emit a `result` event
|
|
113
|
+
* and include the results as an argument. Only a single result
|
|
114
|
+
* is emitted -- if other callbacks respond with a result this method
|
|
115
|
+
* will ignore them.
|
|
116
|
+
*
|
|
117
|
+
* If called by _all_ scheduled callbacks without a non-error, this method
|
|
118
|
+
* will issue the error event.
|
|
119
|
+
*/
|
|
120
|
+
recordResult(error, result) {
|
|
121
|
+
// console.log('.')
|
|
122
|
+
this.resultCount++;
|
|
123
|
+
if (error) {
|
|
124
|
+
this.errors.push(error);
|
|
125
|
+
if (this.resultCount >= this.expectedResults && !this.done) {
|
|
126
|
+
this.complete();
|
|
127
|
+
// we've made every request without success, signal an error
|
|
128
|
+
const error = new CallbackCoordinationError(
|
|
129
|
+
'no response from any callback, no cloud metadata will be set (normal outside of cloud env.)',
|
|
130
|
+
this.errors,
|
|
131
|
+
);
|
|
132
|
+
this.logger.debug('no cloud metadata servers responded');
|
|
133
|
+
this.emit('error', error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!error && result && !this.done) {
|
|
138
|
+
this.complete();
|
|
139
|
+
this.emit('result', result);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Error for CallbackCoordination class
|
|
146
|
+
*
|
|
147
|
+
* Includes the individual errors from each callback
|
|
148
|
+
* of the CallbackCoordination object
|
|
149
|
+
*/
|
|
150
|
+
class CallbackCoordinationError extends Error {
|
|
151
|
+
constructor(message, allErrors = []) {
|
|
152
|
+
super(message);
|
|
153
|
+
this.allErrors = allErrors;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = {
|
|
158
|
+
CallbackCoordination,
|
|
159
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright Elasticsearch B.V. and other contributors where applicable.
|
|
3
|
+
* Licensed under the BSD 2-Clause License; you may not use this file except in
|
|
4
|
+
* compliance with the BSD 2-Clause License.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const URL = require('url').URL;
|
|
10
|
+
const JSONBigInt = require('json-bigint');
|
|
11
|
+
const { httpRequest } = require('../http-request');
|
|
12
|
+
|
|
13
|
+
const DEFAULT_BASE_URL = new URL('/', 'http://metadata.google.internal:80');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks for metadata server then fetches data
|
|
17
|
+
*
|
|
18
|
+
* The getMetadataGcp function will fetch cloud metadata information
|
|
19
|
+
* from Amazon's IMDSv1 endpoint and return (via callback)
|
|
20
|
+
* the formatted metadata.
|
|
21
|
+
*
|
|
22
|
+
* Before fetching data, the server will be "pinged" by attempting
|
|
23
|
+
* to connect via TCP with a short timeout. (`connectTimeoutMs`)
|
|
24
|
+
*
|
|
25
|
+
* https://cloud.google.com/compute/docs/storing-retrieving-metadata
|
|
26
|
+
*/
|
|
27
|
+
function getMetadataGcp(
|
|
28
|
+
connectTimeoutMs,
|
|
29
|
+
resTimeoutMs,
|
|
30
|
+
logger,
|
|
31
|
+
baseUrlOverride,
|
|
32
|
+
cb,
|
|
33
|
+
) {
|
|
34
|
+
const baseUrl = baseUrlOverride || DEFAULT_BASE_URL;
|
|
35
|
+
const options = {
|
|
36
|
+
method: 'GET',
|
|
37
|
+
timeout: resTimeoutMs,
|
|
38
|
+
connectTimeout: connectTimeoutMs,
|
|
39
|
+
headers: {
|
|
40
|
+
'Metadata-Flavor': 'Google',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const url = baseUrl + 'computeMetadata/v1/?recursive=true';
|
|
44
|
+
const req = httpRequest(url, options, function (res) {
|
|
45
|
+
const finalData = [];
|
|
46
|
+
res.on('data', function (data) {
|
|
47
|
+
finalData.push(data);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
res.on('end', function (data) {
|
|
51
|
+
if (res.statusCode !== 200) {
|
|
52
|
+
logger.debug('gcp metadata: unexpected statusCode: %s', res.statusCode);
|
|
53
|
+
cb(
|
|
54
|
+
new Error(
|
|
55
|
+
'error fetching gcp metadata: unexpected statusCode: ' +
|
|
56
|
+
res.statusCode,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Note: We could also guard on the response having the
|
|
62
|
+
// 'Metadata-Flavor: Google' header as done by:
|
|
63
|
+
// https://github.com/googleapis/gcp-metadata/blob/v6.0.0/src/index.ts#L109-L112
|
|
64
|
+
|
|
65
|
+
let result;
|
|
66
|
+
try {
|
|
67
|
+
result = formatMetadataStringIntoObject(finalData.join(''));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
logger.debug(
|
|
70
|
+
'gcp metadata server responded, but there was an ' +
|
|
71
|
+
'error parsing the result: %o',
|
|
72
|
+
err,
|
|
73
|
+
);
|
|
74
|
+
cb(err);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
cb(null, result);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
req.on('timeout', function () {
|
|
82
|
+
req.destroy(new Error('request to metadata server timed out'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
req.on('connectTimeout', function () {
|
|
86
|
+
req.destroy(new Error('could not ping metadata server'));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
req.on('error', function (err) {
|
|
90
|
+
cb(err);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
req.end();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds metadata object
|
|
98
|
+
*
|
|
99
|
+
* Convert a GCP Cloud Engine VM metadata response
|
|
100
|
+
* (https://cloud.google.com/compute/docs/metadata/default-metadata-values)
|
|
101
|
+
* to the APM intake cloud metadata object
|
|
102
|
+
* (https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#gcp-metadata).
|
|
103
|
+
*
|
|
104
|
+
* See discussion about big int values here:
|
|
105
|
+
* https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties
|
|
106
|
+
* This implementation is using the same 'json-bigint' library as 'gcp-metadata'.
|
|
107
|
+
*/
|
|
108
|
+
function formatMetadataStringIntoObject(string) {
|
|
109
|
+
const data = JSONBigInt.parse(string);
|
|
110
|
+
|
|
111
|
+
// E.g., 'projects/513326162531/zones/us-west1-b' -> 'us-west1-b'
|
|
112
|
+
const az = data.instance.zone.split('/').pop();
|
|
113
|
+
|
|
114
|
+
const metadata = {
|
|
115
|
+
provider: 'gcp',
|
|
116
|
+
instance: {
|
|
117
|
+
id: data.instance.id.toString(), // We expect this to be a BigInt.
|
|
118
|
+
name: data.instance.name,
|
|
119
|
+
},
|
|
120
|
+
project: {
|
|
121
|
+
id: data.project.projectId,
|
|
122
|
+
},
|
|
123
|
+
availability_zone: az,
|
|
124
|
+
region: az.slice(0, az.lastIndexOf('-')), // 'us-west1-b' -> 'us-west1'
|
|
125
|
+
machine: {
|
|
126
|
+
type: data.instance.machineType.split('/').pop(),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
return metadata;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = { getMetadataGcp };
|