A library of functions for administering Apigee from nodejs.
Do you want to automate the administration or management of Apigee from Nodejs? This library helps you do that.
Example:
To create a new developer in Apigee:
const apigeejs = require('apigee-edge-js'),
apigee = apigeejs.apigee;
const options = {
org : config.org,
user: config.username,
password: config.password
};
apigee.connect(options)
.then ( org => {
const options = {
developerEmail : "[email protected]",
lastName : "Dimaggio",
firstName : "Josephine",
userName : "JD1"
};
return org.developers.create(options)
.then( result => console.log('ok. developer: ' + JSON.stringify(result)) )
})
.catch ( error => console.log('error: ' + error) );
You can also tell the library to read credentials from .netrc:
const apigeejs = require('apigee-edge-js'),
apigee = apigeejs.apigee;
let options = { org : config.org, netrc: true };
apigee.connect(options).then(...);
For customers who have SSO (SAML) enabled for their Apigee SaaS organization, you can
obtain a token with a
passcode.
This requires that you first sign-in with the browser using the interactive
experience, then visit https://zoneName.login.apigee.com/passcode
to obtain a
passcode. Then:
const apigeejs = require('apigee-edge-js'),
apigee = apigeejs.apigee;
let options = { org : config.org, passcode: 'abcdefg', user: '[email protected]' };
apigee.connect(options).then(...);
For Apigee X or hybrid,
obtain a token with gcloud auth print-access-token
and then :
const apigeejs = require('apigee-edge-js'),
apigee = apigeejs.apigee;
let options = { org : 'my-org', apigeex: true, token: 'kjkdjskjskjs.abcdefg' };
apigee.connect(options).then(...);
The methods on the various objects accept callbacks, and return promises. In code you write that uses this library, it's probably best if you choose one or the other. Promises are probably better, because they're more succinct. But some people prefer callbacks. Here's an example using old-school callbacks instead of ES6 promises:
const apigeejs = require('apigee-edge-js'),
utility = apigeejs.utility,
apigee = apigeejs.apigee;
const options = {
mgmtServer: config.mgmtserver,
org : config.org,
user: config.username,
password: config.password
};
apigee.connect(options, function(e, org){
if (e) {
console.log(e);
console.log(e.stack);
process.exit(1);
}
const options = {
developerEmail : "[email protected]",
lastName : "Dimaggio",
firstName : "Josephine",
userName : "JD1"
};
org.developers.create(options, function(e, result){
if (e) {
utility.logWrite(JSON.stringify(e));
process.exit(1);
}
utility.logWrite(sprintf('ok. developer: %s', JSON.stringify(result, null, 2)));
});
});
There are numerous working examples at a companion repo: apigee-edge-js-examples.
This library and the example tools included here are not an official Google product. Support is available on a best-effort basis via github or community.apigee.com . Pull requests are welcomed.
You do not need to clone this repo in order to use the library. Youc an just get the module via npm install:
npm install apigee-edge-js
To start, you call apigee.connect(). This will connect to an Apigee organization. If it is a SaaS organization, this method will try to find a stashed OAuth token and if not will get an OAuth token.
-
If you use callbacks, the callback will receive
(e, org)
, where e is an error, possibly null, and org is an Organization object -
If you use promises, the promise will resolve with the value of an Organization object
The organization object has the following members, each a hash with various child members as functions:
member | functions |
---|---|
(self) | get, getProperties, addProperties, removeProperties, setConsumerSecretLength, setConsumerKeyLength |
environments | get, getVhosts, getVhost, createVhost, deleteVhost |
proxies | get, del, deploy, undeploy, import, export, getRevisions, getDeployments, getResourcesForRevision, getPoliciesForRevision, getProxyEndpoints |
caches | get, create, del |
kvms | get, create, put, del |
resourcefiles | get, create, update, del |
sharedflows | get, del, deploy, undeploy, import, export, getRevisions, getDeployments, getResourcesForRevision, getPoliciesForRevision |
flowhooks | get, put |
products | get, create, del |
developers | get, create, del,revoke, approve |
keystores | get, create, del, import key and cert, create references |
targetservers | get, create, del, disable, enable, update |
developerapps | get, create, del, revoke, approve, update |
appcredentials | find, add, del, revoke, approve, add/remove product, update attrs |
audits | get |
stats | get |
specs | get, getMeta, list, create, update, del |
companies | get, create |
companyapps | get, create |
companydevelopers | get |
maskconfigs | get, set, add/update, remove |
Each child function gets invoked as a function returning a promise: fn(options)
, or in old-school callback style: fn(options, callback)
.
As you can see from the function list above, pretty much all the basic stuff you want to do with Apigee administration is here. There are some gaps (for example around companies and companyapps); we can fill those in as need arises. (Pull requests welcomed)
You can examine the examples directory for some example code illustrating some practical possibilities. A few specific code examples are shown here.
Pull requests are welcomed, for the code or for examples.
One disclaimer:
- The spec module wraps the /dapi API, which is at this moment undocumented and unsupported, and subject to change. It works today, but the spec module may stop functioning at any point. Use it at your own risk!
Nodejs v10.15.1 or later. The library and tests use Promises, spread/rest operators, and other ES6+ things.
using promises:
apigeeOrg.proxies.export({name:'proxyname'})
.then ( result => {
fs.writeFileSync(path.join('/Users/foo/export', result.filename), result.buffer);
console.log('ok');
})
.catch( error => console.log(util.format(error)) );
In the case of an error, the catch() will get the Error object. There will be an additional member on the reason object: result. The result is the payload send back, if any.
using callbacks:
apigeeOrg.proxies.export({name:'proxyname'}, function(e,result) {
if (e) {
console.log("ERROR:\n" + JSON.stringify(e, null, 2));
return;
}
fs.writeFileSync(path.join('/Users/foo/export', result.filename), result.buffer);
console.log('ok');
});
promises:
apigeeOrg.proxies.export({name:'proxyname', revision:3})
.then ( result => {
fs.writeFileSync(path.join('/Users/foo/export', result.filename), result.buffer);
console.log('ok');
});
callbacks:
apigeeOrg.proxies.export({name:'proxyname', revision:3}, function(e,result) {
if (e) {
console.log("ERROR:\n" + JSON.stringify(e, null, 2));
return;
}
fs.writeFileSync(path.join('/Users/foo/export', result.filename), result.buffer);
console.log('ok');
});
promises:
var options = {
mgmtServer: mgmtserver,
org : orgname,
user: username,
password:password
};
apigee.connect(options)
.then ( org =>
org.proxies.import({name:opt.options.name, source:'/tmp/path/dir'})
.then ( result =>
console.log('import ok. %s name: %s r%d', term, result.name, result.revision) ) )
.catch ( error => {
console.log(util.format(error));
});
callbacks:
var options = {
mgmtServer: mgmtserver,
org : orgname,
user: username,
password:password
};
apigee.connect(options, function(e, org){
if (e) {
console.log(JSON.stringify(e, null, 2));
process.exit(1);
}
org.proxies.import({name:opt.options.name, source:'/tmp/path/dir'}, function(e, result) {
if (e) {
console.log('error: ' + JSON.stringify(e, null, 2));
if (result) { console.log(JSON.stringify(result, null, 2)); }
process.exit(1);
}
console.log('import ok. %s name: %s r%d', term, result.name, result.revision);
});
var options = {
name: 'proxy1',
revision: 2,
environment : 'test'
};
org.proxies.deploy(options)
.then( result => console.log('deployment succeeded.') )
.catch( error => console.log('deployment failed. ' + error) );
org.proxies.getRevisions({name:'proxyname-here'})
then( result => {
console.log('revisions: ' + JSON.stringify(result)); // eg, [ "1", "2", "3"]
var latestRevision = result[result.length-1];
...
});
This uses an Array.reduce() with a series of promises, each of which appends an item to an array of results.
apigee.connect(options)
.then( org => {
common.logWrite('connected');
org.proxies.get({})
.then( items => {
const reducer = (promise, proxyname) =>
promise .then( accumulator =>
org.proxies
.get({ name: proxyname })
.then( ({revision}) => [ ...accumulator, {proxyname, revision:revision[revision.length-1]} ] )
);
return items
.reduce(reducer, Promise.resolve([]))
.then( arrayOfResults => console.log(JSON.stringify(arrayOfResults)) );
});
})
.catch( e => console.error(e) );
Same approach as above.
apigee.connect(options)
.then( org => {
common.logWrite('connected');
org.proxies.get({})
.then( items => {
const reducer = (promise, proxyname) =>
promise .then( accumulator =>
org.proxies
.get({ name: proxyname })
.then( ({revision}) => [ ...accumulator, {proxyname, count:revision.length} ] )
);
return items
.reduce(reducer, Promise.resolve([]))
.then( arrayOfResults => console.log(JSON.stringify(arrayOfResults)) );
});
})
.catch( e => console.error(e) );
using callbacks:
var options = {
environment : 'test',
name : 'keystore1'
};
org.keystores.create(options, function(e, result){
if (e) { ... }
console.log('ok. created');
options.certFile = './mycert.cert';
options.keyFile = './mykey.pem';
options.alias = 'alias1';
options.keyPassword = 'optional password for key file';
org.keystores.importCert(options, function(e, result){
if (e) { ... }
console.log('ok. key and cert stored.');
});
});
callbacks:
const apigeejs = require('apigee-edge-js');
const apigee = apigeejs.apigee;
var options = {org : 'ORGNAME', netrc: true, verbosity : 1 };
apigee.connect(options, function(e, org) {
console.log('org: ' + org.conn.orgname);
org.maskconfigs.get({name: 'default'}, function(e, body) {
console.log(JSON.stringify(body));
org.maskconfigs.set({ json : '$.store.book[*].author' }, function(e, body) {
console.log(JSON.stringify(body));
org.maskconfigs.add({ xml : '/apigee:Store/Employee' }, function(e, body) {
console.log(JSON.stringify(body));
org.maskconfigs.remove({ remove : ['xPathsFault','jSONPathsFault'] }, function(e, body) {
console.log(JSON.stringify(body));
org.maskconfigs.add({ variables : 'dino_var' }, function(e, body) {
console.log(JSON.stringify(body));
org.maskconfigs.add({ namespaces : { prefix:'apigee', value:'urn://apigee' } }, function(e, body) {
console.log(JSON.stringify(body));
});
});
});
});
});
});
});
with ES6 promises:
const apigeejs = require('apigee-edge-js');
const apigee = apigeejs.apigee;
var options = {org : 'ORGNAME', netrc: true, verbosity : 1 };
apigee.connect(options)
.then ( (org) => {
console.log('org: ' + org.conn.orgname);
org.maskconfigs.get({name: 'default'})
.then( (result) => console.log(JSON.stringify(result)) )
.then( () => org.maskconfigs.set({ json : '$.store.book[*].author' }) )
.then( (result) => console.log(JSON.stringify(result)) )
.then( () => org.maskconfigs.add({ xml : '/apigee:Store/Employee' }) )
.then( (result) => console.log(JSON.stringify(result)) )
.then( () => org.maskconfigs.remove({ remove : ['xPathsFault','jSONPathsFault'] }) )
.then( (result) => console.log(JSON.stringify(result)) )
.then( () => org.maskconfigs.add({ variables : 'dino_var' }) )
.then( (result) => console.log(JSON.stringify(result)) )
.then( () => org.maskconfigs.add({ namespaces : { prefix:'apigee', value:'urn://apigee' } })
.then( (result) => console.log(JSON.stringify(result)) )
})
.catch ( e => console.log(e) );
ES6 promises:
const apigeejs = require('apigee-edge-js');
const apigee = apigeejs.apigee;
var options = {org : 'ORGNAME', netrc: true, verbosity : 1 };
apigee.connect(options)
.then ( org => {
console.log('org: ' + org.conn.orgname);
return org.targetservers.create({
environment : 'test',
target : {
name : 'targetserver1',
host: "api.example.com",
port: 8080,
sSLInfo : { enabled : false }
}
});
})
.catch ( e => console.log(e) );
apigee.connect(connectOptions)
.then ( org => {
const options = {
developerEmail,
name : entityName,
apiProduct : apiProducts[0]
};
org.developerapps.create(options)
.then( result => {
...
})
.catch( e => {
console.log('failed to create: ' + e);
});
});
apigee.connect(connectOptions)
.then ( org => {
const attributes = {
updatedBy : 'apigee-edge-js',
updateDate: new Date().toISOString()
};
return org.developerapps.update({ developerEmail, app, attributes })
.then ( result => console.log('new attrs: ' + JSON.stringify(result.attributes)) );
})
.catch( e => console.log('failed to update: ' + util.format(e)) );
function loadKeyIntoMap(org) {
var re = new RegExp('(?:\r\n|\r|\n)', 'g');
var pemcontent = fs.readFileSync(opt.options.pemfile, "utf8").replace(re,'\n');
var options = {
env: opt.options.env,
kvm: opt.options.mapname,
key: opt.options.entryname,
value: pemcontent
};
common.logWrite('storing new key \'%s\'', opt.options.entryname);
return org.kvms.put(options)
.then( _ => common.logWrite('ok. the key was loaded successfully.'));
}
apigee.connect(common.optToOptions(opt))
.then ( org => {
common.logWrite('connected');
return org.kvms.get({ env })
.then( maps => {
if (maps.indexOf(mapname) == -1) {
// the map does not yet exist
common.logWrite('Need to create the map');
return org.kvms.create({ env: opt.options.env, name: opt.options.mapname, encrypted:opt.options.encrypted})
.then( _ => loadKeyIntoMap(org) );
}
common.logWrite('ok. the required map exists');
return loadKeyIntoMap(org);
});
})
.catch( e => console.log('Error: ' + util.format(e)));
apigee.connect(connectOptions)
.then ( org =>
org.specs.create({ name: 'mySpec', filename: '~/foo/bar/spec1.yaml' })
.then( r => {
console.log();
console.log(r);
}) )
.catch( e => console.log('failed to create: ' + util.format(e)) );
See the examples directory for a set of working example tools. Or you can examine the test directory for code that exercises the library.
To run tests you should create a file called testConfig.json and put it in the toplevel dir of the repo. It should have contents like this:
{
"org" : "my-org-name",
"user": "[email protected]",
"password": "password-goes-here",
"verbosity": 1
}
or:
{
"org" : "my-org-name",
"netrc": true
}
The latter example will retrieve administrative credentials for Apigee from your .netrc file.
Then, to run tests:
npm test
or
node_modules/mocha/bin/mocha
To run a specific subset of tests, specify a regex to the grep option:
node_modules/mocha/bin/mocha --grep "^Cache.*"
-
Is this an official Google product? Is it supported?
No. Neither this library nor the related example tools are an official Google product. Support for either is available on a best-effort basis via github or community.apigee.com .
-
What is this thing good for?
If your team builds nodejs scripts to perform administrative operations on your Apigee organization, you may want to use this library. It provides a wrapper of basic operations to allow you to import and deploy proxies, create products or developers or applications, populate KVMs, create caches, and so on.
-
Does it have a wrapper for creating a virtualhost?
No, that's one thing it does not help with, at this time. Let me know if you think that's important.
-
How does the library authenticate to Apigee ?
The library obtains an oauth token using the standard client_id and secret for administrative operations. The library caches the token into a filesystem file, for future use. The library runtime automatically caches the token, and refreshes the token as necessary, even during a single long-running script.
By the way, you can use the cached token in other programs. For example, you could use the ./refreshToken.js script (See the examples) to obtain a fresh token, then use a bash script to read that token cache and perform some curl commands. Or just run refreshToken.js every morning and then any other program you want to use could pull the token from the cache.
-
Could I use this thing in my Angular or React-based webapp?
No, I haven't built it for that use case. It relies on node's
fs
module, and there are probably other dependencies that would prevent it from working correctly in a browser / webpack environment.If the community thinks this is important, let me know. I can take a look.
This material is Copyright (c) 2016-2021 Google LLC, and is licensed under the Apache 2.0 source license.
- The tests are a work in progress