Skip to content

apiserver

Deva Kumar edited this page Jan 15, 2021 · 6 revisions

create-viya-api: Jump start a REST api server for your SAS Viya programs

The target audience is SAS Viya users/teams interested in making their SAS Viya programs available via a REST API interface. Note that the design allows users to call any SAS Viya REST API.

The figure below is an overview. create-viya-api

The required skills are:

  1. nodejs

    • You will define your API as a javascript object
    • All your handlers are written in nodejs
  2. A basic understanding of REST API and in particular SAS REST APIs

If this approach gets sufficient interest from the community, it can be extended with features like:

  • Accept yaml defintion of the REST API(API First Process)
  • Better validation using Joi
  • Return user friendly error messages
  • Logging
  • And probably a lot more

Benefits of using this package

  1. Define your api end points using javascript

  2. Authentication

    • Accepts a valid Viya token
    • If called from an authenticated browser session it will get a token from Viya
    • By design it does not support client credentials(userid, password) - since 1 and 2 are the more common use cases.
  3. In your handler (where you process the request) you will have access to a context object that has the following:

    • The current valid token
    • All the standard http artifacts - path, query, payload, headers etc..
    • You can use all the cool features of hapi server
    • Access the REST end points via swagger, web applications and any scripting environment.
  4. Deploy the server in a docker container

  5. The start r example has the following end points to jump start your development. These cover some basic scenarios.

    • Cas actions
    • Casl statements
    • Compute service with the user-supplied code
    • Specific program saved in the "server". In this example the code is in a file on the api server, but in a real deployment this code will be in some repository

Creating the project

Please make sure you have the latest LTS version of nodejs.

npx @sassoftware/create-viya-api <projectName>

This will install the application  with some default routes

ex: npx @sassoftware/create-viya-api myapi

Now switch to the project directory, complete the configurations below and use the application.

Key references

  1. @sassoftware/restaf and @sassoftware/restaflib - these are libraries designed to simplify accessing SAS Viya using REST APIs
  2. @sassoftware/viya-apiserverjs - is the api server that handles all the traffic

Background links

  1. hapi - the underlying nodejs based web server.
  2. hapi-swagger - plugin for defining routes and accessing swagger
  3. SAS Viya REST API

Configuring the default setup

Please see Useful Tips to configure your Viya Server. These configurations will allow your applications to access SAS Viya from both from the api server and web applications.

As always you have to do some setup thru the .env and Dockerfile in the project directory. See the .env file for documentation on the other configurations.

To get going I recommend the following:

  1. Register a clientid with a secret with the value secret.

The details on the clientid I use are shown below. Note the grant-types and redirect_uri

{
  scope: [ 'openid', '*' ],
  client_id: 'myapi',
  resource_ids: [ 'none' ],
  authorized_grant_types: [ 'password', 'authorization_code', 'refresh_token' ],   
  redirect_uri: [
    'http://localhost:8080/myapi/api',
    'http://localhost:8080/myapi/logon'
  ],
  autoapprove: [ 'true' ],
  access_token_validity: 86400,
  authorities: [ 'uaa.none' ],
  'use-session': true,
  lastModified: 1609727686858,
  required_user_groups: []
}

2.Configure the .env file

VIYA_SERVER=http://cool.fbi.sashq-d.openstack.sas.com

If you are using a different client and clientsecret than the default then set those values in the .env file

Starting the server

Issue this command

npm start

To run the server under nodejs debugger issue this commad

npm run debug

Running as a container

Edit docker-compose.yaml file and set the appropriate values. Then run this command:

docker-compose up

Now try it out using swagger, Postman or applications

Swagger

Access swagger. For the default application it is [http://localhost:8080/myapi/api] You will be prompted for userid and password. On successful authentication the swagger for your routes will be displayed. You can then access all your routes without any further authentication.

Postman

Get a token using your usual magic dust and then access the end points.

Application

Access the end points from a authenticated browser session. In this case the server will provide a token to your handler

Developing your REST API

All the handlers must be in the handlers directory

Jump starting your handlers

Recommend that you adopt the samples in the handlers directory. The examples cover common use cases

  1. Running sas jobs.
  2. Running casl code.
  3. Running cas actions.

Recommended Policies

These are just recommendations to make it easier for others to follow your logic. It is not essential.

  • Name the handler the same as the route where possible.
  • Export all the handlers thru index.html

Adding a REST API endpoint

Add your new endpoint to the route array in api.js.

The very basic form is:

{
    method: ['GET'],/* can be one or more of these GET,PUT,POST
    path: `${appName}/PARAMS`,
    options: {
        handler: handlers.yourhandler,
        description: 'Some description',
        notes: 'Some notes',
        tags: ['api'],  
        validate: { an optional object for validating request and response}
    }
}

Please see the documentation on routes here on how to specify route and PARAM. For validation see here. This will allow for a lot of the verification to occcur before the handler is invoked.

For POST and PUT please use the validate object. This allows the swaggerUI to display proper prompts for the user

Quick Overview of handlers

Each handler will look like this:

module.exports = async function functionName(req,h){
    const context = req.pre.context;
    let {token, host} = context;

    ...
    <your code  - use the token and host to make your calls to Viya using REST APIs>
    ...

    return something;  /* The function must return either a promise or a value/object */
}

Parameters

  • req -- The request object from hapi. This has all the incoming information. Some of them are also available thru the context object

  • h -- This object is used to send responses back. If you are returning simple object(string, js object) then you will just return that and not use h. But if you want to return special headers, status codes etc thne use h.

Context object

The schema of context object is:

{
    token:  'bearer token'
    host : your viya server 
    path : path used to access this handler
    params: An array of the values of params --if the {params*} was part of the route specification
    query: Any query parameter as an js object
    payload: Any payload if the method is POST or PUT
}

Examples

For examples using restaf to access Viya see the handlers in this directory

A simple example

Below is an example using axios calling the file service.

let axios = require('axios'){
async function simpleExample(req,h) {
    const context = req.pre.context;
    let {token, host} = context;
    
    let config = {
        method: 'GET'
        url: `${host}/files/files`,
        headers: {
          authorization: token
        }
    }

    let result = await axios(config);
    return result.data;

}

Using compute service

A more complete example for accessing compute service. See [https://github.com/sassoftware/restaf/wiki](for documentation on restaf and restaflib)

let restaflib = require('@sassoftware/restaflib');
let setupConnection    = require('../lib/setupConnection');   
let fs = require('fs').promises;
module.exports = async function coolstuff (req,h) {
    return run(req,h) 
        .then (r => {return r;})
        .catch(err => {
            return err;/* need to add Boom support */
        });
};
async function run (req,h) {
    let { computeSetup, computeRun } = restaflib;
    let context = req.pre.context;
    let store = await setupConnection(context);
    let computeSession = await computeSetup(store, null, null);
    let f = context.path.split('/');

    let fname = `./pgm/${f[2]}.sas`;
    let src = await fs.readFile(fname, 'utf8');
    let computeSummary = await computeRun(
        store,
        computeSession,
        src,
        context.payload.input,
        15,2  /* just a place holder values for checking job status */
    );
    let log = await restaflib.computeResults(store, computeSummary, 'log');
    await store.apiCall(computeSession.links('delete'));
    // just return log for the example
    return log;

};

Returning results

Every handler must have a return value. Please hapi documentation for the rules of the road. hapi gives you a lot of control.

Steps

  1. First add your route to the table in api.js
  2. Use one of the other routes in api.js as a guide - also refer to the hapi documentation
  3. Add your handler in the handlers directory.
  4. Now bring up the server and test the new route using Swagger.
  5. Also add a test to the test directory - this package uses Jest.

In the handler you can call any service (Viya or others). You will notice that I follow a certain pattern in my examples. Pick your own programming style.

Testing using jest

The package uses Jest to create tests. Please see the test directory for examples. To avoid storing your password in the test scripts create a token and save it in a file. Then set the environment variable TOKENFILE with the path to this file.

npm test

Swagger configuration

The swagger.json file is used to configure swagger-ui. I do not understand all the options one can set. For this initial release the minimal set seems to work.

Processing of .env file

The order in which the processing:

  1. Read Dockerfile and create environment variables for all ENV fields.
  2. Read .env file and create or override the environment variables.

You can choose to set the environment variables using your system's SET command. In this case do not set the values in .env file or Dockerfile.

Other useful information

  1. Update package.json with your name, repository etc...
  2. set origin for your git remote repository
Clone this wiki locally