diff --git a/README.md b/README.md index 1d67a8370..4b56cafbd 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,11 @@ If your application is running outside of Google Cloud Platform, such as locally 1. [Create a new JSON service account key][service-account]. 2. Copy the key somewhere your application can access it. Be sure not to expose the key publicly. - 3. Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to the full path to the key. The debug agent will automatically look for this environment variable. + 3. Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to the full path to the key. The trace agent will automatically look for this environment variable. -3. Alternatively, if you are running your application on a development machine or test environment where you are using the [`gcloud` command line tools][gcloud-sdk], and are logged using `gcloud beta auth application-default login`, you already have sufficient credentials, and a service account key is not required. +If you are running your application on a development machine or test environment where you are using the [`gcloud` command line tools][gcloud-sdk], and are logged using `gcloud beta auth application-default login`, you already have sufficient credentials, and a service account key is not required. + +Alternatively, you may set the `keyFilename` or `credentials` configuration field to the full path or contents to the key file, respectively. Setting either of these fields will override either setting `GOOGLE_APPLICATION_CREDENTIALS` or logging in using `gcloud`. (See the [default configuration](config.js) for more details.) ## Viewing your traces diff --git a/config.js b/config.js index 8f52b188d..a36f3636f 100644 --- a/config.js +++ b/config.js @@ -88,6 +88,16 @@ module.exports = { // this will also cause sampling decisions made by other distributed components to be // ignored. This is useful for aggregating traces generated by different cloud platform // projects. - ignoreContextHeader: false + ignoreContextHeader: false, + + // 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 } }; diff --git a/lib/trace-writer.js b/lib/trace-writer.js index 202f34443..c1adb2efb 100644 --- a/lib/trace-writer.js +++ b/lib/trace-writer.js @@ -29,7 +29,9 @@ headers[constants.TRACE_AGENT_REQUEST_HEADER] = 1; /** * Creates a basic trace writer. - * @param {!Logger} logger + * @param {!Logger} logger The Trace Agent's logger object. + * @param {Object} config A config object containing information about + * authorization credentials. * @constructor */ function TraceWriter(logger, config) { @@ -40,7 +42,10 @@ function TraceWriter(logger, config) { this.config_ = config; /** @private {function} authenticated request function */ - this.request_ = utils.authorizedRequestFactory(SCOPES); + this.request_ = utils.authorizedRequestFactory(SCOPES, { + credentials: config.credentials, + keyFile: config.keyFilename + }); /** @private {Array} stringified traces to be published */ this.buffer_ = []; diff --git a/package.json b/package.json index be2f662d3..f7a50e697 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "tmp": "0.0.28" }, "dependencies": { - "@google/cloud-diagnostics-common": "0.2.5", + "@google/cloud-diagnostics-common": "0.3.0", "continuation-local-storage": "^3.1.4", "lodash.findindex": "^4.4.0", "lodash.isequal": "^4.0.0", diff --git a/test/standalone/test-config-credentials.js b/test/standalone/test-config-credentials.js new file mode 100644 index 000000000..b5787efbf --- /dev/null +++ b/test/standalone/test-config-credentials.js @@ -0,0 +1,142 @@ +/** + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +var path = require('path'); +var assert = require('assert'); +var nock = require('nock'); +var cls = require('../../lib/cls.js'); + +var queueSpans = function(n, privateAgent) { + for (var i = 0; i < n; i++) { + privateAgent.createRootSpanData('name', 1, 0).close(); + } +}; + +process.env.GCLOUD_PROJECT = 0; + +describe('test-config-credentials', function() { + it('should use the keyFilename field of the config object', function(done) { + var credentials = require('../fixtures/gcloud-credentials.json'); + var config = { + bufferSize: 2, + samplingRate: 0, + keyFilename: path.join('test', 'fixtures', 'gcloud-credentials.json') + }; + var agent = require('../..').start(config); + nock.disableNetConnect(); + var scope = nock('https://accounts.google.com') + .intercept('/o/oauth2/token', 'POST', function(body) { + assert.equal(body.client_id, credentials.client_id); + assert.equal(body.client_secret, credentials.client_secret); + assert.equal(body.refresh_token, credentials.refresh_token); + return true; + }).reply(200, { + refresh_token: 'hello', + access_token: 'goodbye', + expiry_date: new Date(9999, 1, 1) + }); + // Since we have to get an auth token, this always gets intercepted second + nock('https://cloudtrace.googleapis.com') + .intercept('/v1/projects/0/traces', 'PATCH', function() { + scope.done(); + agent.stop(); + setImmediate(done); + return true; + }).reply(200); + cls.getNamespace().run(function() { + queueSpans(2, agent.private_()); + }); + }); + + it('should use the credentials field of the config object', function(done) { + var config = { + bufferSize: 2, + samplingRate: 0, + credentials: require('../fixtures/gcloud-credentials.json') + }; + var agent = require('../..').start(config); + nock.disableNetConnect(); + var scope = nock('https://accounts.google.com') + .intercept('/o/oauth2/token', 'POST', function(body) { + assert.equal(body.client_id, config.credentials.client_id); + assert.equal(body.client_secret, config.credentials.client_secret); + assert.equal(body.refresh_token, config.credentials.refresh_token); + return true; + }).reply(200, { + refresh_token: 'hello', + access_token: 'goodbye', + expiry_date: new Date(9999, 1, 1) + }); + // Since we have to get an auth token, this always gets intercepted second + nock('https://cloudtrace.googleapis.com') + .intercept('/v1/projects/0/traces', 'PATCH', function() { + scope.done(); + agent.stop(); + setImmediate(done); + return true; + }).reply(200); + cls.getNamespace().run(function() { + queueSpans(2, agent.private_()); + }); + }); + + it('should ignore credentials if keyFilename is provided', function(done) { + var correctCredentials = require('../fixtures/gcloud-credentials.json'); + 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' + } + }; + ['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') + .intercept('/o/oauth2/token', 'POST', function(body) { + assert.equal(body.client_id, correctCredentials.client_id); + assert.equal(body.client_secret, correctCredentials.client_secret); + assert.equal(body.refresh_token, correctCredentials.refresh_token); + agent.stop(); + return true; + }).reply(200, { + refresh_token: 'hello', + access_token: 'goodbye', + expiry_date: new Date(9999, 1, 1) + }); + // Since we have to get an auth token, this always gets intercepted second + nock('https://cloudtrace.googleapis.com') + .intercept('/v1/projects/0/traces', 'PATCH', function() { + scope.done(); + agent.stop(); + setImmediate(done); + return true; + }).reply(200); + cls.getNamespace().run(function() { + queueSpans(2, agent.private_()); + }); + }); +});