elastic-apm-node
Version:
The official Elastic APM agent for Node.js
134 lines (118 loc) • 3.77 kB
JavaScript
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/
;
const URL = require('url').URL;
const JSONBigInt = require('json-bigint');
const { httpRequest } = require('../http-request');
const DEFAULT_BASE_URL = new URL('/', 'http://metadata.google.internal:80');
/**
* Checks for metadata server then fetches data
*
* The getMetadataGcp function will fetch cloud metadata information
* from Amazon's IMDSv1 endpoint and return (via callback)
* the formatted metadata.
*
* Before fetching data, the server will be "pinged" by attempting
* to connect via TCP with a short timeout. (`connectTimeoutMs`)
*
* https://cloud.google.com/compute/docs/storing-retrieving-metadata
*/
function getMetadataGcp(
connectTimeoutMs,
resTimeoutMs,
logger,
baseUrlOverride,
cb,
) {
const baseUrl = baseUrlOverride || DEFAULT_BASE_URL;
const options = {
method: 'GET',
timeout: resTimeoutMs,
connectTimeout: connectTimeoutMs,
headers: {
'Metadata-Flavor': 'Google',
},
};
const url = baseUrl + 'computeMetadata/v1/?recursive=true';
const req = httpRequest(url, options, function (res) {
const finalData = [];
res.on('data', function (data) {
finalData.push(data);
});
res.on('end', function (data) {
if (res.statusCode !== 200) {
logger.debug('gcp metadata: unexpected statusCode: %s', res.statusCode);
cb(
new Error(
'error fetching gcp metadata: unexpected statusCode: ' +
res.statusCode,
),
);
return;
}
// Note: We could also guard on the response having the
// 'Metadata-Flavor: Google' header as done by:
// https://github.com/googleapis/gcp-metadata/blob/v6.0.0/src/index.ts#L109-L112
let result;
try {
result = formatMetadataStringIntoObject(finalData.join(''));
} catch (err) {
logger.debug(
'gcp metadata server responded, but there was an ' +
'error parsing the result: %o',
err,
);
cb(err);
return;
}
cb(null, result);
});
});
req.on('timeout', function () {
req.destroy(new Error('request to metadata server timed out'));
});
req.on('connectTimeout', function () {
req.destroy(new Error('could not ping metadata server'));
});
req.on('error', function (err) {
cb(err);
});
req.end();
}
/**
* Builds metadata object
*
* Convert a GCP Cloud Engine VM metadata response
* (https://cloud.google.com/compute/docs/metadata/default-metadata-values)
* to the APM intake cloud metadata object
* (https://github.com/elastic/apm/blob/main/specs/agents/metadata.md#gcp-metadata).
*
* See discussion about big int values here:
* https://github.com/googleapis/gcp-metadata#take-care-with-large-number-valued-properties
* This implementation is using the same 'json-bigint' library as 'gcp-metadata'.
*/
function formatMetadataStringIntoObject(string) {
const data = JSONBigInt.parse(string);
// E.g., 'projects/513326162531/zones/us-west1-b' -> 'us-west1-b'
const az = data.instance.zone.split('/').pop();
const metadata = {
provider: 'gcp',
instance: {
id: data.instance.id.toString(), // We expect this to be a BigInt.
name: data.instance.name,
},
project: {
id: data.project.projectId,
},
availability_zone: az,
region: az.slice(0, az.lastIndexOf('-')), // 'us-west1-b' -> 'us-west1'
machine: {
type: data.instance.machineType.split('/').pop(),
},
};
return metadata;
}
module.exports = { getMetadataGcp };