From 9257cd39e1d6881e857ee0fb1620bbc7eee6bcb8 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Fri, 29 Aug 2014 16:07:07 -0700 Subject: [PATCH 1/2] datastore: Honor GAE_LONG_APP_ID and GAE_VM to detect App Engine context. --- lib/common/connection.js | 16 ++++++++++++-- lib/common/gae.js | 35 +++++++++++++++++++++++++++++ lib/datastore/dataset.js | 13 ++++++++++- lib/datastore/transaction.js | 16 ++++++++++++-- test/datastore/dataset.js | 43 ++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 lib/common/gae.js diff --git a/lib/common/connection.js b/lib/common/connection.js index 4838d3233a0..4f5a6c2700e 100644 --- a/lib/common/connection.js +++ b/lib/common/connection.js @@ -103,7 +103,9 @@ function Connection(opts) { this.isConnecting = false; - if (opts.credentials) { + if (opts.gaeApp) { + this.gaeApp = opts.gaeApp; + } else if (opts.credentials) { if (opts.credentials.client_email && opts.credentials.private_key) { this.credentials = opts.credentials; } else { @@ -149,8 +151,11 @@ Connection.prototype.connect = function() { */ Connection.prototype.fetchToken = function(callback) { var that = this; - if (!this.opts.keyFilename && !this.credentials) { + // We need to fetch an access token only for regular and + // App Engine prod applications. + if (this.gaeApp && this.gaeApp.isProd) { // We should be on GCE, try to retrieve token from the metadata server. + // This should be GAE prod or dev req({ method: 'get', uri: METADATA_TOKEN_URL, @@ -251,6 +256,13 @@ Connection.prototype.createAuthorizedReq = function(reqOpts, callback) { reqOpts.headers['User-Agent'] = USER_AGENT; } + // devappserver doesn't require authorization, + // skip authorization header. + if (this.gaeApp && !this.gaeApp.isProd) { + callback(null, reqOpts); + return; + } + function onConnected(err) { if (err) { callback(err); diff --git a/lib/common/gae.js b/lib/common/gae.js new file mode 100644 index 00000000000..941684c7f96 --- /dev/null +++ b/lib/common/gae.js @@ -0,0 +1,35 @@ +/** + * Copyright 2014 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. + */ + +/** + * @private + */ + +'use strict'; + +var gae = {}; + +gae.info = function() { + if (process.env.GAE_LONG_APP_ID) { + return { + id: process.env.GAE_LONG_APP_ID, + isProd: process.env.GAE_VM !== undefined + }; + } + return null; +}; + +module.exports = gae; diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index efebe244eba..a754874da2e 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -32,6 +32,12 @@ var conn = require('../common/connection.js'); */ var entity = require('./entity.js'); +/** + * @private + * @type module:common/gae + */ +var gae = require('../common/gae.js'); + /** * @private * @type module:datastore/pb @@ -92,12 +98,17 @@ var SCOPES = [ function Dataset(options) { options = options || {}; + var gaeInfo = gae.info(); + this.id = options.projectId; + if (gaeInfo) { + this.id = gaeInfo.id; + } this.connection = new conn.Connection({ + gaeApp: gaeInfo, credentials: options.credentials, keyFilename: options.keyFilename, scopes: SCOPES }); - this.id = options.projectId; this.namespace = options.namespace; this.transaction = this.createTransaction_(); } diff --git a/lib/datastore/transaction.js b/lib/datastore/transaction.js index ee2f9ac49f1..191c6e55887 100644 --- a/lib/datastore/transaction.js +++ b/lib/datastore/transaction.js @@ -26,15 +26,21 @@ var https = require('https'); /** @type module:datastore/entity */ var entity = require('./entity.js'); +/** @type module:datastore/gae */ +var gae = require('../common/gae.js'); + /** @type module:datastore/pb */ var pb = require('./pb.js'); /** @type module:common/util */ var util = require('../common/util.js'); -/** @const {string} Host to send with API requests. */ +/** @const {string} Host to talk to in prod. */ var GOOGLE_APIS_HOST = 'www.googleapis.com'; +/** @const {string} Host to talk to in GAE dev. */ +var GOOGLE_APIS_DEV_HOST = 'localhost'; + /** @const {string} Non-transaction mode key. */ var MODE_NON_TRANSACTIONAL = 'NON_TRANSACTIONAL'; @@ -428,9 +434,15 @@ Transaction.prototype.mapQuery = function() { Transaction.prototype.makeReq = function(method, req, respType, callback) { // TODO: Handle non-HTTP 200 cases. callback = callback || util.noop; + + var gaeInfo = gae.info(); + var host = GOOGLE_APIS_HOST; + if (gaeInfo && !gaeInfo.isProd) { + host = GOOGLE_APIS_DEV_HOST; + } this.conn.createAuthorizedReq({ method: 'POST', - host: GOOGLE_APIS_HOST, + host: host, path: '/datastore/v1beta2/datasets/' + this.datasetId + '/' + method, headers: { 'content-type': 'application/x-protobuf' diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js index ba441d6dada..6e1015349a2 100644 --- a/test/datastore/dataset.js +++ b/test/datastore/dataset.js @@ -26,6 +26,49 @@ var mockRespGet = require('../testdata/response_get.json'); var Transaction = require('../../lib/datastore/transaction.js'); describe('Dataset', function() { + + describe('constructor', function() { + + beforeEach(function() { + delete process.env.GAE_LONG_APP_ID; + delete process.env.GAE_VM; + }); + + it('should detect GAE prod', function(done) { + process.env.GAE_LONG_APP_ID = 'gae-app-id'; + process.env.GAE_VM = true; + var ds = new datastore.Dataset(); + assert.strictEqual(ds.id, 'gae-app-id'); + ds.transaction.conn.createAuthorizedReq = function(req) { + assert.equal(req.host, 'www.googleapis.com'); + done(); + }; + ds.get(ds.key('Kind', 123), function() {}); + }); + + it('should detect GAE dev', function(done) { + process.env.GAE_LONG_APP_ID = 'gae-app-id'; + var ds = new datastore.Dataset(); + assert.strictEqual(ds.id, 'gae-app-id'); + ds.transaction.conn.createAuthorizedReq = function(req) { + assert.equal(req.host, 'localhost'); + done(); + }; + ds.get(ds.key('Kind', 123), function() {}); + }); + + it ('should not set app id if GAE_LONG_APP_ID is not set', function(done) { + var ds = new datastore.Dataset(); + assert.equal(!!ds.id, false); + ds.transaction.conn.createAuthorizedReq = function(req) { + assert.equal(req.host, 'www.googleapis.com'); + done(); + }; + ds.get(ds.key('Kind', 123), function() {}); + }); + + }); + it('should return a key scoped by namespace', function() { var ds = new datastore.Dataset({ projectId: 'test', namespace: 'my-ns' }); var key = ds.key('Company', 1); From 7a60eb38f230df9324e83d474d8bf526b770a202 Mon Sep 17 00:00:00 2001 From: Burcu Dogan Date: Tue, 2 Sep 2014 14:40:18 -0700 Subject: [PATCH 2/2] datastore: GAE dev/prod support minor styling fixes. --- lib/common/gae.js | 4 ++-- lib/datastore/dataset.js | 2 +- lib/datastore/transaction.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/common/gae.js b/lib/common/gae.js index 941684c7f96..749744dbaf6 100644 --- a/lib/common/gae.js +++ b/lib/common/gae.js @@ -22,10 +22,10 @@ var gae = {}; -gae.info = function() { +gae.getInfo = function() { if (process.env.GAE_LONG_APP_ID) { return { - id: process.env.GAE_LONG_APP_ID, + id: process.env.GAE_LONG_APP_ID, isProd: process.env.GAE_VM !== undefined }; } diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js index a754874da2e..13f1697a917 100644 --- a/lib/datastore/dataset.js +++ b/lib/datastore/dataset.js @@ -98,7 +98,7 @@ var SCOPES = [ function Dataset(options) { options = options || {}; - var gaeInfo = gae.info(); + var gaeInfo = gae.getInfo(); this.id = options.projectId; if (gaeInfo) { this.id = gaeInfo.id; diff --git a/lib/datastore/transaction.js b/lib/datastore/transaction.js index 191c6e55887..05e85c17339 100644 --- a/lib/datastore/transaction.js +++ b/lib/datastore/transaction.js @@ -435,7 +435,7 @@ Transaction.prototype.makeReq = function(method, req, respType, callback) { // TODO: Handle non-HTTP 200 cases. callback = callback || util.noop; - var gaeInfo = gae.info(); + var gaeInfo = gae.getInfo(); var host = GOOGLE_APIS_HOST; if (gaeInfo && !gaeInfo.isProd) { host = GOOGLE_APIS_DEV_HOST;