-
Notifications
You must be signed in to change notification settings - Fork 2
appserver
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.
The required skills are:
-
nodejs
- You will define your API as a javascript object
- All your handlers are written in nodejs
-
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
-
Define your api end points using javascript
-
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.
-
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.
-
Deploy the server in a docker container
-
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
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.
- @sassoftware/restaf and @sassoftware/restaflib - these are libraries designed to simplify accessing SAS Viya using REST APIs
- @sassoftware/viya-apiserverjs - is the api server that handles all the traffic
- hapi - the underlying nodejs based web server.
- hapi-swagger - plugin for defining routes and accessing swagger
- SAS Viya REST API
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:
- 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
Issue this command
npm start
To run the server under nodejs debugger issue this commad
npm run debug
Edit docker-compose.yaml file and set the appropriate values. Then run this command:
docker-compose up
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.
Get a token using your usual magic dust and then access the end points.
Access the end points from a authenticated browser session. In this case the server will provide a token to your handler
All the handlers must be in the handlers directory
Recommend that you adopt the samples in the handlers directory. The examples cover common use cases
- Running sas jobs.
- Running casl code.
- Running cas actions.
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
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
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 */
}
-
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.
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
}
For examples using restaf to access Viya see the handlers in this directory
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;
}
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;
};
Every handler must have a return value. Please hapi documentation for the rules of the road. hapi gives you a lot of control.
- First add your route to the table in api.js
- Use one of the other routes in api.js as a guide - also refer to the hapi documentation
- Add your handler in the handlers directory.
- Now bring up the server and test the new route using Swagger.
- 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.
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
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.
The order in which the processing:
- Read Dockerfile and create environment variables for all ENV fields.
- 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.
- Update package.json with your name, repository etc...
- set origin for your git remote repository