Skip to content

Commit

Permalink
core: read projectId from authClient (#1656)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenplusplus authored Dec 2, 2016
1 parent e66d9ca commit f396c9f
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 202 deletions.
17 changes: 10 additions & 7 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,21 @@ var config = {

### The `config` object

A `config` object requires the following properties:
A `config` object is not required if you are in an environment which supports [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials). This could be your own development machine when using the [gcloud SDK](https://cloud.google.com/sdk) or within Google App Engine and Compute Engine.

1. `projectId`
If this doesn't describe your environment, the `config` object expects the following properties:

If you wish, you can set an environment variable (`GCLOUD_PROJECT`) in place of specifying this inline.

2. One of the following:
1. `config.credentials` object containing `client_email` and `private_key` properties.
2. `config.keyFilename` path to a .json, .pem, or .p12 key file.
1. One of the following:
1. `credentials` object containing `client_email` and `private_key` properties.
2. `keyFilename` path to a .json, .pem, or .p12 key file.
3. `GOOGLE_APPLICATION_CREDENTIALS` environment variable with a full path to your key file.

2. `projectId`

If you wish, you can set an environment variable (`GCLOUD_PROJECT`) in place of specifying this inline. Or, if you have provided a service account JSON key file as the `config.keyFilename` property explained above, your project ID will be detected automatically.

**Note**: When using a .pem or .p12 key file, `config.email` is also required.


[dev-console]: https://console.developers.google.com/project
[gce-how-to]: https://cloud.google.com/compute/docs/authentication#using
4 changes: 1 addition & 3 deletions docs/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,7 @@ <h4>Example: Upload a file to Cloud Storage</h4>
<div hljs hlsj-language="js" ng-show="selected == 'compute engine'">
// Authentication is automatic
// inside Google Compute Engine.
var gcloud = require('google-cloud')({
projectId: 'grape-spaceship-123'
});
var gcloud = require('google-cloud')();

var gcs = gcloud.storage();

Expand Down
34 changes: 29 additions & 5 deletions packages/common/src/grpc-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,6 @@ GrpcService.prototype.request = function(protoOpts, reqOpts, callback) {
return;
}

// Clean up gcloud-specific options.
delete reqOpts.autoPaginate;
delete reqOpts.autoPaginateVal;

var service = this.getService_(protoOpts);

var metadata = this.grpcMetadata;
Expand All @@ -254,6 +250,13 @@ GrpcService.prototype.request = function(protoOpts, reqOpts, callback) {
grpcOpts.deadline = GrpcService.createDeadline_(protoOpts.timeout);
}

try {
reqOpts = util.decorateRequest(reqOpts, { projectId: self.projectId });
} catch(e) {
callback(e);
return;
}

// Retains a reference to an error from the response. If the final callback is
// executed with this as the "response", we return it to the user as an error.
var respError;
Expand Down Expand Up @@ -336,7 +339,6 @@ GrpcService.prototype.requestStream = function(protoOpts, reqOpts) {
}

var objectMode = !!reqOpts.objectMode;
delete reqOpts.objectMode;

var service = this.getService_(protoOpts);
var grpcOpts = {};
Expand All @@ -345,6 +347,15 @@ GrpcService.prototype.requestStream = function(protoOpts, reqOpts) {
grpcOpts.deadline = GrpcService.createDeadline_(protoOpts.timeout);
}

try {
reqOpts = util.decorateRequest(reqOpts, { projectId: this.projectId });
} catch(e) {
setImmediate(function() {
stream.destroy(e);
});
return stream;
}

var retryOpts = {
retries: this.maxRetries,
objectMode: objectMode,
Expand Down Expand Up @@ -416,6 +427,15 @@ GrpcService.prototype.requestWritableStream = function(protoOpts, reqOpts) {
grpcOpts.deadline = GrpcService.createDeadline_(protoOpts.timeout);
}

try {
reqOpts = util.decorateRequest(reqOpts, { projectId: this.projectId });
} catch (e) {
setImmediate(function() {
stream.destroy(e);
});
return stream;
}

var grpcStream = service[protoOpts.method](reqOpts, grpcOpts)
.on('status', function(status) {
var grcpStatus = GrpcService.decorateStatus_(status);
Expand Down Expand Up @@ -724,6 +744,8 @@ GrpcService.structToObj_ = function(struct) {
* @param {?error} callback.err - An error getting an auth client.
*/
GrpcService.prototype.getGrpcCredentials_ = function(callback) {
var self = this;

this.authClient.getAuthClient(function(err, authClient) {
if (err) {
callback(err);
Expand All @@ -735,6 +757,8 @@ GrpcService.prototype.getGrpcCredentials_ = function(callback) {
grpc.credentials.createFromGoogleCredential(authClient)
);

self.projectId = authClient.projectId;

callback(null, credentials);
});
};
Expand Down
3 changes: 1 addition & 2 deletions packages/common/src/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,13 @@ function Service(config, options) {
});

this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(reqCfg);

this.authClient = this.makeAuthenticatedRequest.authClient;
this.baseUrl = config.baseUrl;
this.getCredentials = this.makeAuthenticatedRequest.getCredentials;
this.globalInterceptors = arrify(options.interceptors_);
this.interceptors = [];
this.packageJson = config.packageJson;
this.projectId = options.projectId;
this.projectId = options.projectId || '{{projectId}}';
this.projectIdRequired = config.projectIdRequired !== false;
this.Promise = options.promise || Promise;
}
Expand Down
42 changes: 31 additions & 11 deletions packages/common/src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ util.shouldRetryRequest = shouldRetryRequest;
/**
* Get a function for making authenticated requests.
*
* @throws {Error} If a projectId is requested, but not able to be detected.
*
* @param {object} config - Configuration object.
* @param {boolean=} config.autoRetry - Automatically retry requests if the
* response is related to rate limits or certain intermittent server errors.
Expand Down Expand Up @@ -344,6 +346,17 @@ function makeAuthenticatedRequestFactory(config) {
}

function onAuthenticated(err, authenticatedReqOpts) {
if (!err) {
try {
authenticatedReqOpts = util.decorateRequest(
authenticatedReqOpts,
extend({ projectId: authClient.projectId }, config)
);
} catch(e) {
err = util.missingProjectIdError;
}
}

if (err) {
if (stream) {
stream.destroy(err);
Expand All @@ -354,8 +367,6 @@ function makeAuthenticatedRequestFactory(config) {
return;
}

authenticatedReqOpts = util.decorateRequest(authenticatedReqOpts, config);

if (options && options.onAuthenticated) {
options.onAuthenticated(null, authenticatedReqOpts);
} else {
Expand Down Expand Up @@ -468,6 +479,10 @@ util.makeRequest = makeRequest;
function decorateRequest(reqOpts, config) {
config = config || {};

delete reqOpts.autoPaginate;
delete reqOpts.autoPaginateVal;
delete reqOpts.objectMode;

if (is.object(reqOpts.qs)) {
delete reqOpts.qs.autoPaginate;
delete reqOpts.qs.autoPaginateVal;
Expand All @@ -478,6 +493,19 @@ function decorateRequest(reqOpts, config) {
delete reqOpts.json.autoPaginateVal;
}

for (var opt in reqOpts) {
if (is.string(reqOpts[opt])) {
if (reqOpts[opt].indexOf('{{projectId}}') > -1) {
if (!config.projectId) {
throw util.missingProjectIdError;
}
reqOpts[opt] = reqOpts[opt].replace(/{{projectId}}/g, config.projectId);
}
} else if (is.object(reqOpts[opt])) {
decorateRequest(reqOpts[opt], config);
}
}

return reqOpts;
}

Expand Down Expand Up @@ -530,8 +558,6 @@ util.extendGlobalConfig = extendGlobalConfig;
/**
* Merge and validate API configurations.
*
* @throws {Error} If a projectId is not specified.
*
* @param {object} globalContext - gcloud-level context.
* @param {object} globalContext.config_ - gcloud-level configuration.
* @param {object} localConfig - Service-level configurations.
Expand All @@ -545,13 +571,7 @@ function normalizeArguments(globalContext, localConfig, options) {

var globalConfig = globalContext && globalContext.config_;

var config = util.extendGlobalConfig(globalConfig, localConfig);

if (options.projectIdRequired !== false && !config.projectId) {
throw util.missingProjectIdError;
}

return config;
return util.extendGlobalConfig(globalConfig, localConfig);
}

util.normalizeArguments = normalizeArguments;
Expand Down
Loading

0 comments on commit f396c9f

Please sign in to comment.