apigee-edge-js
v0.4.36
Published
nodejs library for the administration API for Apigee (Edge and X and hybrid).
Downloads
487
Readme
apigee-edge-js
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)));
});
});
Many more examples
There are numerous working examples at a companion repo: apigee-edge-js-examples.
This is not an official Google product
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.
Using the Library
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
The Object Model
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 objectIf 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)
.
What is possible here?
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!
Pre-Requisites
Nodejs v10.15.1 or later. The library and tests use Promises, spread/rest operators, and other ES6+ things.
Examples
Export the latest revision of an API Proxy
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');
});
Export a specific revision of an API Proxy
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');
});
Import an API Proxy from a Directory
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);
});
Deploy an API Proxy
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) );
Get the latest revision of an API Proxy
org.proxies.getRevisions({name:'proxyname-here'})
then( result => {
console.log('revisions: ' + JSON.stringify(result)); // eg, [ "1", "2", "3"]
var latestRevision = result[result.length-1];
...
});
Get the latest revision of every API Proxy in an org
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) );
Count the number of revisions of every API Proxy in an org
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) );
Create a Keystore and load a Key and Cert
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.');
});
});
Read and Update Mask Configs for an Organization
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) );
Create a Target Server
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) );
Create a Developer App
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);
});
});
Update attributes on a Developer App
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)) );
Load data from a file into a KVM entry
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)));
Import an OpenAPI Spec
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)) );
Lots More Examples
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 the Tests
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.*"
Frequently Asked Questions
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.
License and Copyright
This material is Copyright (c) 2016-2021 Google LLC, and is licensed under the Apache 2.0 source license.
Bugs
- The tests are a work in progress