Skip to content

Commit

Permalink
feat: find service instances by tag (#23)
Browse files Browse the repository at this point in the history
This adds an additional parameter to `getCredentials` permitting specification of a tag by which to filter the service instance.
  • Loading branch information
rwd authored and germanattanasio committed Jan 2, 2019
1 parent 4edacc5 commit 10b13f5
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 100 deletions.
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ $ npm install vcap_services --save

## Usage

```sh
```javascript
var vcapServices = require('vcap_services');
var credentials = vcapServices.getCredentials('personality_insights');
var credentials = vcapServices.findCredentials({ service: 'personality_insights' });
console.log(credentials);
```

Expand Down Expand Up @@ -49,25 +49,33 @@ Output:
### Getting credentials for a specific plan

Get credentials that match a specific service plan (only for `VCAP_SERVICES`).
```sh
```javascript
var vcapServices = require('vcap_services');
var credentials = vcapServices.getCredentials('personality_insights', 'standard');
var credentials = vcapServices.findCredentials({ service: 'personality_insights', instance: { plan: 'standard' } });
console.log(credentials);
```

### Getting credentials for a specific instance
Get credentials that match a specific service instance (replace "YOUR NLC NAME" with the name of your service instance).
```sh
```javascript
var vcapServices = require('vcap_services');
var credentials = vcapServices.getCredentials('natural_language_classifier', null, 'YOUR NLC NAME');
var credentials = vcapServices.findCredentials({ service: 'natural_language_classifier', { instance: { name: 'YOUR NLC NAME' } });
console.log(credentials);
```
### Getting credentials for a specific plan and instance
Get credentials that match a specific service plan and instance (replace "YOUR NLC NAME" with the name of your service instance).
```sh
```javascript
var vcapServices = require('vcap_services');
var credentials = vcapServices.findCredentials({ service: 'natural_language_classifier', instance: { plan: 'standard', name: 'YOUR NLC NAME' } });
console.log(credentials);
```
### Getting credentials for a specific tag
Get credentials that match a specific service tag, regardless of the service type.
```javascript
var vcapServices = require('vcap_services');
var credentials = vcapServices.getCredentials('natural_language_classifier', 'standard', 'YOUR NLC NAME');
var credentials = vcapServices.findCredentials({ instance: { tags: 'object-storage' } });
console.log(credentials);
```
Expand Down
150 changes: 128 additions & 22 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,152 @@

/**
* if VCAP_SERVICES exists or the instance name exists in the
* environemnt, then it returns the credentials
* environment, then it returns the credentials
* for the last service that starts with 'name' or {} otherwise
* If plan is specified it will return the credentials for
* the service instance that match that plan or {} otherwise
* @param String name, service name
* @param String plan, service plan
* @param String iname, instance name
* @param String tag, tag
* @return {Object} the service credentials or {} if
* name is not found in VCAP_SERVICES or instance name
* is set as an environmental variable. Env var must be
* is set as an environmental variable. Env let must be
* upper case.
*/

const getCredentials = function(name, plan, iname) {
const getCredentials = function(name, plan, iname, tag) {
if (process.env.VCAP_SERVICES) {
var services = JSON.parse(process.env.VCAP_SERVICES);
for (var service_name in services) {
let services = JSON.parse(process.env.VCAP_SERVICES);
for (let service_name in services) {
if (service_name.indexOf(name) === 0) {
for (var i = services[service_name].length - 1; i >= 0; i--) {
var instance = services[service_name][i];
if ((!plan || plan === instance.plan) && (!iname || iname === instance.name))
for (let i = services[service_name].length - 1; i >= 0; i--) {
let instance = services[service_name][i];
if ((!plan || plan === instance.plan) && (!iname || iname === instance.name) && (!tag || (instance.tags || []).includes(tag)))
return instance.credentials || {};
}
}
}
}
//Check if env vars were set directly
var env = process.env;
var instance = {};
return iname ? getCredentialsFromInstanceNameEnv(iname) : {};
}

/**
* Find the last service credentials matching service and instance filter(s)
* @example <caption>Filter on service name</caption>
* findCredentials({ service: 'mongodb' })
* @example <caption>Filter on service name and instance name</caption>
* findCredentials({ service: 'mongodb', instance: { name: 'mongo-crm' })
* @example <caption>Filter on instance tag</caption>
* findCredentials({ instance: { tags: 'object-storage' })
* @param {Object} filters - service/instance filters, all of which must match
* @param {string} filters service - service name to filter on
* @param {Object} filters instance - service instance properties to filter on
* @return {Object} - matching credentials, or {} if none found
*/
const findCredentials = function(filters) {
if (!filters || typeof filters != 'object') {
return {};
}

let service_intances = vcapServicesToFindFrom(filters.service);
let instance_filters = filters.instance || {};

for (let i = service_intances.length - 1; i >= 0; i--) {
let instance = service_intances[i];
if (!serviceInstanceMatchesFilters(instance, instance_filters)) {
continue;
}
if (instance.credentials) {
return instance.credentials;
}
}

return instance_filters.name ? getCredentialsFromInstanceNameEnv(instance_filters.name) : {};
}

/**
* Get the VCAP services to find credentials from.
* @example <caption>Service name by string</caption>
* // returns service(s) named "redis", i.e. VCAP_SERVICES.redis
* vcapServicesToFindFrom('redis')
* @example <caption>Service name matching RegExp</caption>
* // returns service(s) named starting with "redis-", e.g. VCAP_SERVICES.redis-db
* vcapServicesToFindFrom(/^redis/)
* @example <caption>All services</caption>
* // returns all services in a flattened array
* vcapServicesToFindFrom()
* @param {(RegExp,string)} [service_filter=] - filter to limit by service name
* @return {Object[]}
*/
const vcapServicesToFindFrom = function(service_filter) {
let vcap_services = process.env.VCAP_SERVICES ? JSON.parse(process.env.VCAP_SERVICES) : {};

if (service_filter instanceof RegExp) {
let services = [];
for (let service_name in vcap_services) {
if (service_filter.test(service_name)) {
services.push(vcap_services[service_name]);
}
}
return [].concat.apply([], services);
} else if (typeof service_filter == 'undefined') {
return [].concat.apply([], Object.values(vcap_services));
} else {
return vcap_services[service_filter] || [];
}
}

if(iname) {
iname = iname.toUpperCase().replace(/[\s&-]/g, '_');
if(env[iname]) {
try {
instance = JSON.parse(env[iname]);
} catch(e) {
console.warn('Error parsing JSON from process.env.' + iname );
console.warn(e);
/**
* Tests whether a service instance matches all supplied filters.
* @example <caption>Match on single string property</caption>
* // returns true
* serviceInstanceMatchesFilters({ name: 'redis-instance' }, { name: 'redis-instance' }))
* @example <caption>Match on multiple string properties</caption>
* // returns false
* serviceInstanceMatchesFilters({ name: 'redis-instance', plan: 'free' }, { name: 'redis-instance', plan: 'standard' }))
* @example <caption>Match on array property</caption>
* // returns true
* serviceInstanceMatchesFilters({ tags: ['storage'] }, { tags: 'storage' }))
* @param {Object} instance - the instance to test.
* @param {Object} filters - the filters to match with, each being a string.
* @return {boolean}
*/
const serviceInstanceMatchesFilters = function(instance, filters) {
for (let filter_property in filters) {
let filter_value = filters[filter_property];
if (!instance[filter_property]) {
return false;
}
if (Array.isArray(instance[filter_property])) {
if (!instance[filter_property].includes(filter_value)) {
return false;
}
} else if (instance[filter_property] != filter_value) {
return false;
}
}
return true;
}

/**
* Fetch credentials from env var named after instance.
* @param {string} iname - the service instance name.
* @return {Object} - credentials if found, else {}.
*/
const getCredentialsFromInstanceNameEnv = function(instance_name) {
let env = process.env;
let instance = {};

instance_name = instance_name.toUpperCase().replace(/[\s&-]/g, '_');
if (env[instance_name]) {
try {
instance = JSON.parse(env[instance_name]);
} catch(e) {
console.warn('Error parsing JSON from process.env.' + instance_name);
console.warn(e);
}
}

return instance;
};

Expand Down Expand Up @@ -77,8 +182,8 @@ const getCredentialsFromLocalConfig = function(serviceLabel, credentials) {
/**
* Helper function used to add credentials bound to cloud functions using wsk service bind
*
* @param {Object} theParams - parameters sent to service
* @param {string} service - name of service in bluemix used to retrieve credentials, used for IAM instances
* @param {Object} params - parameters sent to service
* @param {string} serviceName - name of service in bluemix used to retrieve credentials, used for IAM instances
* @param {string} serviceAltName - alternate name of service used for cloud foundry instances
* @return {Object} - returns parameters modified to include credentials from service bind
*/
Expand Down Expand Up @@ -129,6 +234,7 @@ const getCredentialsForStarter = function(serviceLabel, credsFromFile) {

module.exports = {
getCredentials,
findCredentials,
getCredentialsFromLocalConfig,
getCredentialsForStarter,
getCredentialsFromServiceBind,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"dependencies": {},
"scripts": {
"test": "jshint --exclude node_modules/ **/*.js && mocha --reporter spec --timeout 4000",
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha --reporter spec --timeout 4000",
"coverage": "istanbul cover ./node_modules/mocha/bin/_mocha -- --reporter spec --timeout 4000",
"coveralls": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}
}
Loading

0 comments on commit 10b13f5

Please sign in to comment.