diff --git a/config.js b/config.js index a36f3636f..9886ccc1e 100644 --- a/config.js +++ b/config.js @@ -90,14 +90,14 @@ module.exports = { // projects. ignoreContextHeader: false, + // The contents of a key file. If this field is set, its contents will be + // used for authentication instead of your application default credentials. + credentials: null, + // A path to a key file relative to the current working directory. If this // field is set, the contents of the pointed file will be used for // authentication instead of your application default credentials. - keyFilename: null, - - // The contents of a key file. If this field is set, its contents will be - // used for authentication instead of your application default credentials. - // If keyFilename is also set, the value of credentials will be ignored. - credentials: null + // If credentials is also set, the value of keyFilename will be ignored. + keyFilename: null } }; diff --git a/index.js b/index.js index f1b337b9a..211048bcc 100644 --- a/index.js +++ b/index.js @@ -22,16 +22,18 @@ var filesLoadedBeforeTrace = Object.keys(require.cache); // patched before any user-land modules get loaded. require('continuation-local-storage'); -var SpanData = require('./src/span-data.js'); -var common = require('@google/cloud-diagnostics-common'); -var semver = require('semver'); +var common = require('@google-cloud/common'); var constants = require('./src/constants.js'); -var util = require('./src/util.js'); +var gcpMetadata = require('gcp-metadata'); +var semver = require('semver'); +var traceUtil = require('./src/util.js'); +var SpanData = require('./src/span-data.js'); +var util = require('util'); var modulesLoadedBeforeTrace = []; for (var i = 0; i < filesLoadedBeforeTrace.length; i++) { - var moduleName = util.packageNameFromPath(filesLoadedBeforeTrace[i]); + var moduleName = traceUtil.packageNameFromPath(filesLoadedBeforeTrace[i]); if (moduleName && moduleName !== '@google/cloud-trace' && modulesLoadedBeforeTrace.indexOf(moduleName) === -1) { modulesLoadedBeforeTrace.push(moduleName); @@ -69,12 +71,18 @@ var phantomTraceAgent = { var agent = phantomTraceAgent; var initConfig = function(projectConfig) { - var util = require('util'); var config = {}; util._extend(config, require('./config.js').trace); util._extend(config, projectConfig); if (process.env.hasOwnProperty('GCLOUD_TRACE_LOGLEVEL')) { - config.logLevel = process.env.GCLOUD_TRACE_LOGLEVEL; + var envLogLevel = parseInt(process.env.GCLOUD_TRACE_LOGLEVEL, 10); + if (!isNaN(envLogLevel)) { + config.logLevel = envLogLevel; + } else { + console.error('Warning: Ignoring env var GCLOUD_TRACE_LOGLEVEL as it ' + + 'contains an non-integer log level: ' + + process.env.GCLOUD_TRACE_LOGLEVEL); + } } if (process.env.hasOwnProperty('GCLOUD_PROJECT')) { config.projectId = process.env.GCLOUD_PROJECT; @@ -122,10 +130,22 @@ var publicAgent = { } var config = initConfig(projectConfig); + if (!config.enabled) { return this; } - var logger = common.logger.create(config.logLevel, '@google/cloud-trace'); + + var logLevel = config.logLevel; + if (logLevel < 0) { + logLevel = 0; + } else if (logLevel >= common.logger.LEVELS.length) { + logLevel = common.logger.LEVELS.length - 1; + } + var logger = common.logger({ + level: common.logger.LEVELS[logLevel], + tag: '@google/cloud-trace' + }); + if (!semver.satisfies(process.versions.node, '>=0.12')) { logger.error('Tracing is only supported on Node versions >=0.12'); return this; @@ -151,25 +171,36 @@ var publicAgent = { } if (typeof config.projectId === 'undefined') { - // Queue the work to acquire the projectNumber (potentially from the + // Queue the work to acquire the projectId (potentially from the // network.) - common.utils.getProjectNumber(headers, function(err, project) { + gcpMetadata.project({ + property: 'project-id', + headers: headers + }, function(err, response, projectId) { + if (response && response.statusCode !== 200) { + if (response.statusCode === 503) { + err = new Error('Metadata service responded with a 503 status ' + + 'code. This may be due to a temporary server error; please try ' + + 'again later.'); + } else { + err = new Error('Metadata service responded with the following ' + + 'status code: ' + response.statusCode); + } + } if (err) { // Fatal error. Disable the agent. logger.error('Unable to acquire the project number from metadata ' + 'service. Please provide a valid project number as an env. ' + - 'variable, or through config.projectId passed to start().' + - err); + 'variable, or through config.projectId passed to start(). ' + + 'Disabling trace agent. ' + err); publicAgent.stop(); return; } - config.projectId = project; + config.projectId = projectId; }); - } else if (typeof config.projectId === 'number') { - config.projectId = config.projectId.toString(); } else if (typeof config.projectId !== 'string') { - logger.error('config.projectId, if provided, must be' + - ' a string or number. Disabling trace agent.'); + logger.error('config.projectId, if provided, must be a string. ' + + 'Disabling trace agent.'); return this; } diff --git a/package.json b/package.json index 006aa4262..ab9c920ba 100644 --- a/package.json +++ b/package.json @@ -32,12 +32,14 @@ "nock": "^9.0.0", "proxyquire": "^1.4.0", "request": "^2.61.0", + "retry-request": "^1.3.2", "timekeeper": "^1.0.0", "tmp": "0.0.31" }, "dependencies": { - "@google/cloud-diagnostics-common": "0.3.0", + "@google-cloud/common": "^0.11.0", "continuation-local-storage": "^3.1.4", + "gcp-metadata": "^0.1.0", "lodash.findindex": "^4.4.0", "lodash.isequal": "^4.0.0", "methods": "^1.1.1", diff --git a/src/trace-writer.js b/src/trace-writer.js index a70efee7e..843eedd3d 100644 --- a/src/trace-writer.js +++ b/src/trace-writer.js @@ -16,7 +16,8 @@ 'use strict'; -var utils = require('@google/cloud-diagnostics-common').utils; +var gcpMetadata = require('gcp-metadata'); +var util = require('@google-cloud/common').util; var traceLabels = require('./trace-labels.js'); var pjson = require('../package.json'); var constants = require('./constants.js'); @@ -42,7 +43,8 @@ function TraceWriter(logger, config) { this.config_ = config; /** @private {function} authenticated request function */ - this.request_ = utils.authorizedRequestFactory(SCOPES, { + this.request_ = util.makeAuthenticatedRequestFactory({ + scopes: SCOPES, credentials: config.credentials, keyFile: config.keyFilename }); @@ -59,7 +61,7 @@ function TraceWriter(logger, config) { // Schedule periodic flushing of the buffer, but only if we are able to get // the project number (potentially from the network.) var that = this; - that.getProjectNumber(function(err, project) { + that.getProjectId(function(err, project) { if (err) { return; } // ignore as index.js takes care of this. that.scheduleFlush_(project); }); @@ -100,7 +102,10 @@ TraceWriter.prototype.stop = function() { TraceWriter.prototype.getHostname = function(cb) { var that = this; - utils.getHostname(headers, function(err, hostname) { + gcpMetadata.instance({ + property: 'hostname', + headers: headers + }, function(err, response, hostname) { if (err && err.code !== 'ENOTFOUND') { // We are running on GCP. that.logger_.warn('Unable to retrieve GCE hostname.', err); @@ -111,7 +116,10 @@ TraceWriter.prototype.getHostname = function(cb) { TraceWriter.prototype.getInstanceId = function(cb) { var that = this; - utils.getInstanceId(headers, function(err, instanceId) { + gcpMetadata.instance({ + property: 'id', + headers: headers + }, function(err, response, instanceId) { if (err && err.code !== 'ENOTFOUND') { // We are running on GCP. that.logger_.warn('Unable to retrieve GCE instance id.', err); @@ -120,6 +128,33 @@ TraceWriter.prototype.getInstanceId = function(cb) { }); }; +/** + * Returns the project ID if it has been cached and attempts to load + * it from the enviroment or network otherwise. + * + * @param {function(?, number):?} callback an (err, result) style callback + */ +TraceWriter.prototype.getProjectId = function(callback) { + var that = this; + if (that.config_.projectId) { + callback(null, that.config_.projectId); + return; + } + + gcpMetadata.project({ + property: 'project-id', + headers: headers + }, function(err, response, projectId) { + if (err) { + callback(err); + return; + } + that.logger_.info('Acquired ProjectId from metadata: ' + projectId); + that.config_.projectId = projectId; + callback(null, projectId); + }); +}; + /** * Ensures that all sub spans of the provided spanData are * closed and then queues the span data to be published. @@ -151,7 +186,7 @@ TraceWriter.prototype.writeSpan = function(spanData) { TraceWriter.prototype.queueTrace_ = function(trace) { var that = this; - that.getProjectNumber(function(err, project) { + that.getProjectId(function(err, project) { if (err) { that.logger_.info('No project number, dropping trace.'); return; // ignore as index.js takes care of this. @@ -228,30 +263,6 @@ TraceWriter.prototype.publish_ = function(projectId, json) { }); }; -/** - * Returns the project number if it has been cached and attempts to load - * it from the enviroment or network otherwise. - * - * @param {function(?, number):?} callback an (err, result) style callback - */ -TraceWriter.prototype.getProjectNumber = function(callback) { - var that = this; - if (that.config_.projectId) { - callback(null, that.config_.projectId); - return; - } - - utils.getProjectNumber(headers, function(err, project) { - if (err) { - callback(err); - return; - } - that.logger_.info('Acquired ProjectId from metadata: ' + project); - that.config_.projectId = project; - callback(null, project); - }); -}; - /** * Export TraceWriter. * FIXME(ofrobots): TraceWriter should be a singleton. We should export diff --git a/test/hooks/test-hooks-index.js b/test/hooks/test-hooks-index.js index a31b96fdb..6073fa7a9 100644 --- a/test/hooks/test-hooks-index.js +++ b/test/hooks/test-hooks-index.js @@ -36,9 +36,9 @@ describe('findModuleVersion', function() { }); it('should work with namespaces', function() { - var modulePath = findModulePath('@google/cloud-diagnostics-common', module); + var modulePath = findModulePath('@google-cloud/common', module); var truePackage = - require('../../node_modules/@google/cloud-diagnostics-common/package.json'); + require('../../node_modules/@google-cloud/common/package.json'); assert.equal(findModuleVersion(modulePath, Module._load), truePackage.version); }); }); diff --git a/test/standalone/test-agent-metadata.js b/test/standalone/test-agent-metadata.js index 56b7a30c1..0a03bc002 100644 --- a/test/standalone/test-agent-metadata.js +++ b/test/standalone/test-agent-metadata.js @@ -16,9 +16,9 @@ 'use strict'; +var proxyquire = require('proxyquire'); var assert = require('assert'); var nock = require('nock'); -var agent = require('../..'); var traceLabels = require('../../src/trace-labels.js'); nock.disableNetConnect(); @@ -26,6 +26,20 @@ nock.disableNetConnect(); delete process.env.GCLOUD_PROJECT; describe('agent interaction with metadata service', function() { + var agent; + + before(function() { + // Setup: Monkeypatch gcp-metadata to not ask for retries at all. + var retryRequest = require('retry-request'); + proxyquire('gcp-metadata', { + 'retry-request': function(requestOps, callback) { + return retryRequest(requestOps, { + retries: 0 + }, callback); + } + }); + agent = require('../..'); + }); afterEach(function() { agent.stop(); @@ -34,7 +48,7 @@ describe('agent interaction with metadata service', function() { it('should stop when the project number cannot be acquired', function(done) { nock.disableNetConnect(); var scope = nock('http://metadata.google.internal') - .get('/computeMetadata/v1/project/numeric-project-id') + .get('/computeMetadata/v1/project/project-id') .times(2) .reply(404, 'foo'); @@ -49,7 +63,7 @@ describe('agent interaction with metadata service', function() { it('should activate with projectId from metadata service', function(done) { nock.disableNetConnect(); var scope = nock('http://metadata.google.internal') - .get('/computeMetadata/v1/project/numeric-project-id') + .get('/computeMetadata/v1/project/project-id') .times(2) .reply(200, '1234'); agent.start({logLevel: 0}); @@ -64,7 +78,7 @@ describe('agent interaction with metadata service', function() { it('should not query metadata service when config.projectId is set', function() { nock.disableNetConnect(); - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); }); it('should not query metadata service when env. var. is set', function() { @@ -81,7 +95,7 @@ describe('agent interaction with metadata service', function() { .times(1) .reply(200, 'host'); - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -100,7 +114,7 @@ describe('agent interaction with metadata service', function() { .times(1) .reply(200, '1729'); - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -114,7 +128,7 @@ describe('agent interaction with metadata service', function() { it('shouldn\'t add id or hostname labels if not present', function(done) { nock.disableNetConnect(); - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -131,7 +145,7 @@ describe('agent interaction with metadata service', function() { process.env.GAE_MODULE_NAME = 'foo'; process.env.GAE_MODULE_VERSION = '20151119t120000'; process.env.GAE_MINOR_VERSION = '91992'; - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -150,7 +164,7 @@ describe('agent interaction with metadata service', function() { process.env.GAE_MODULE_NAME = 'default'; process.env.GAE_MODULE_VERSION = '20151119t130000'; process.env.GAE_MINOR_VERSION = '81818'; - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -176,7 +190,7 @@ describe('agent interaction with metadata service', function() { .reply(200, 'host'); delete process.env.GAE_MODULE_NAME; - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); @@ -198,14 +212,14 @@ describe('agent interaction with metadata service', function() { .reply(404); delete process.env.GAE_MODULE_NAME; - agent.start({projectId: 0, logLevel: 0}); + agent.start({projectId: '0', logLevel: 0}); setTimeout(function() { agent.private_().namespace.run(function() { var spanData = agent.private_().createRootSpanData('name', 5, 0); spanData.close(); + scope.done(); assert.equal(spanData.span.labels[traceLabels.GAE_MODULE_NAME], require('os').hostname()); - scope.done(); done(); }); }, 500); diff --git a/test/standalone/test-config-credentials.js b/test/standalone/test-config-credentials.js index 759778ff1..e1bfc7ed6 100644 --- a/test/standalone/test-config-credentials.js +++ b/test/standalone/test-config-credentials.js @@ -94,25 +94,19 @@ describe('test-config-credentials', function() { }); }); - it('should ignore credentials if keyFilename is provided', function(done) { - var correctCredentials = require('../fixtures/gcloud-credentials.json'); + it('should ignore keyFilename if credentials is provided', function(done) { + var correctCredentials = { + client_id: 'a', + client_secret: 'b', + refresh_token: 'c', + type: 'authorized_user' + }; var config = { bufferSize: 2, samplingRate: 0, - keyFilename: path.join('test', 'fixtures', 'gcloud-credentials.json'), - credentials: { - client_id: 'a', - client_secret: 'b', - refresh_token: 'c', - type: 'authorized_user' - } + credentials: correctCredentials, + keyFilename: path.join('test', 'fixtures', 'gcloud-credentials.json') }; - ['client_id', 'client_secret', 'refresh_token'].forEach(function (field) { - assert(correctCredentials.hasOwnProperty(field)); - assert(config.credentials.hasOwnProperty(field)); - assert.notEqual(config.credentials[field], - correctCredentials[field]); - }); var agent = require('../..').start(config); nock.disableNetConnect(); var scope = nock('https://accounts.google.com') diff --git a/test/standalone/test-invalid-project-id.js b/test/standalone/test-invalid-project-id.js index 8d0a2f492..66ab6e892 100644 --- a/test/standalone/test-invalid-project-id.js +++ b/test/standalone/test-invalid-project-id.js @@ -23,7 +23,7 @@ var agent = require('../..'); describe('index.js', function() { it('should complain when config.projectId is not a string or number', function() { - agent.start({projectId: 0, enabled: true, logLevel: 0}); + agent.start({projectId: '0', enabled: true, logLevel: 0}); assert(agent.isActive()); agent.stop(); agent.start({projectId: {test: false}, enabled: true, logLevel: 0}); diff --git a/test/standalone/test-no-self-tracing.js b/test/standalone/test-no-self-tracing.js index 02b6d8a45..55b379257 100644 --- a/test/standalone/test-no-self-tracing.js +++ b/test/standalone/test-no-self-tracing.js @@ -33,7 +33,7 @@ describe('test-no-self-tracing', function() { var scope = nock('http://metadata.google.internal') .get('/computeMetadata/v1/instance/hostname').reply(200) .get('/computeMetadata/v1/instance/id').reply(200) - .get('/computeMetadata/v1/project/numeric-project-id').reply(200); + .get('/computeMetadata/v1/project/project-id').reply(200); var agent = require('../..').start(); require('http'); // Must require http to force patching of the module var oldDebug = agent.private_().logger.debug; @@ -53,7 +53,7 @@ describe('test-no-self-tracing', function() { .get('/computeMetadata/v1/instance/id').reply(200); var apiScope = nock('https://cloudtrace.googleapis.com') .patch('/v1/projects/0/traces').reply(200); - var agent = require('../..').start({ projectId: 0, bufferSize: 1 }); + var agent = require('../..').start({ projectId: '0', bufferSize: 1 }); agent.private_().traceWriter.request_ = request; require('http'); // Must require http to force patching of the module var oldDebug = agent.private_().logger.debug;