Skip to content

Latest commit

 

History

History
670 lines (536 loc) · 21.1 KB

README.md

File metadata and controls

670 lines (536 loc) · 21.1 KB

apigee-edge-js

Apache 2.0 Node Test LastCommit CommitActivity Downloads

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 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) .

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

  1. 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 .

  2. 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.

  3. 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.

  4. 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.

  5. 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

Related