API Script Guide

Instana API Script Reference

Global variables

The following predefined global variables accelerate script development:

APIs Details
$http Send HTTP request in API script
$attributes Manage customized attributes
$network Set up proxy by using this network utility
$synthetic Access environment and runtime variables
$env Access environment and runtime variables
$util Utility functions

How to write a REST API test case with JavaScript

  • One or more requests can be sent in a single API script. The API script supports node v16. Therefore, v16-supported features can be used in the scripts. The Instana API script uses $http to send an HTTP request, and $http is based on the request. For more information, see request.

    The syntax is as follows:

    const assert = require('assert');
    
    $http.get('https://httpbin.org/get',
    function(err, response, body) {
        if (err) {
            console.error(err);
            return;
        }
        assert.equal(response.statusCode, 200, 'Expected a 200 OK response');
    });
    
    $http({
        uri: 'https://httpbin.org/json',
        method: 'GET'
    },
    function(err, response, body) {
        if (err) {
            console.error(err);
            return;
        }
        assert.equal(response.statusCode, 200, 'Expected a 200 OK response');
    });
    
  • To validate the results, import the assert module by using const assert=require('assert'); and call an assert method to validate the endpoint response. The assert method is shown in the following example:

    const assert=require('assert');
    
    assert.ok(response && response.statusCode == 200, 'request failed ,error is ' + error);
    

    Validate response content. See the following example:

    let bodyObj = typeof body == 'string': JSON.parse(body) : body;
    assert.ok(bodyObj.id != null, 'Resource create failed, not resource ID');
    

    For more assert APIs, see assert API. Another module to validate the result is chai.

  • Use console to debug the script. The logs contents can be seen in the Instana UI.

    console.info
    console.log
    console.error
    console.warn
    
  • Use the following syntax for a GET request:

    const assert = require('assert');
    
    let baseUrl = 'https://reqres.in/api/messages';
    // Make GET request
    $http.get(baseUrl, {},
    function(error, response) {
        // Validate the response code, if assertion fails, log "failed to get message " plus results as error message on synthetic dashboard
        assert.ok(response && response.statusCode == 200, 'failed to get message' + error);
    });
    
  • Use the following syntax for a POST form request:

    let baseUrl = 'http://localhost:3000';
    
    // Define endpoint URL
    let url = baseUrl + '/messages';
    // Define form content data
    let formData = {
    'text': 'Hi Meg is here'
    };
    // Define headers such as "context-type"
    let header1 = {
    'contex-type': 'application/json'
    };
    
    // Make POST request
    $http.post(url, {
        header: header1,
        form: formData
    },
    function(error, response) {
        // Validate the response code, if assertion fails, log "failed to create message, error is " plus results as error message on synthetic dashboard
        assert.ok(response && response.statusCode == 200, 'failed to create message, error is ' + error);
    });
    
  • Use the following syntax for a POST JSON request:

    // Send Json content
    let baseUrl = 'https://reqres.in';
    
    // Define endpoint URL
    let url = baseUrl + '/api/users';
    
    // Define JSON data
    let data = {
        'job': 'leader',
        'name': 'morpheus'
    };
    // Define headers
    let header1 = {
        'contex-type': 'application/json'
    };
    
    // Make POST request
    $http.post(url, {
        header: header1,
        json: data
    },
    function(error, response) {
        // Validate the response code, if assertion fails, log "failed to create message, error is " plus 
        // results as error message on synthetic dashboard
        assert.ok(response && response.statusCode == 201, 'failed to create message, error is ' + error);
    });
    
  • Use the following syntax to send a TLS/SSL Protocol request with a certificate:

    const cert = '<client.crt string >';
    const key = '<client.key string >';
    const ca = '<ca.cert.pem string >';
    
    const options = {
        url: 'https://api.some-server.com/',
        cert: cert,
        key: key,
        passphrase: '<password>',
        ca: ca
    };
    
    $http.get(options, function(error, response, body) {
        // validate code
        assert.equal(response.statusCode, 200, 'expect a 200 respose Code');
    });
    

    Note: You are suggested to store the key/cert/passphrase text in user credentials, which will be supported in the future release.

  • Use the following syntax to send a basic authentication request:

    $http.get('http://some.server.com/', {
        // Define authentication credentials
        'auth': {
            'user': 'username',
            'pass': 'password',
            'sendImmediately': false
        }
    });
    

    Note: You are suggested to store the user/pass text in user credentials, which will be supported in the future release.

  • Use the following syntax to send a bear authentication request. Set the bearer value in the auth parameter. The value can be either a string or a function that returns a string.

    $http.get('http://some.server.com/', {
        'auth': {
            'bearer': 'authToken'
        }
    });
    

    Note: You are suggested to store the bear authentication text in user credentials, which will be supported in the future release.

  • Use the following syntax to send a request with HTTP proxy:

    $http.get('http://www.ibm.com', {
        'proxy': 'http://PROXY_HOST:PROXY_PORT'
    },
    function(error, response, body) {
        // console.log(body);
    });
    
  • Optional: You can add servername in the request to get the correct SSL certificate from a specific server. Use the following syntax to send request to SNI-enabled web server:

    $http.get({
        url: 'https://some.server.name',
        servername: 'some.server.name'
    },
    function(error, response, body) {
        console.log(response.statusCode);
    });
    
  • Make asynchronous requests as follows:

    const assert = require('assert');
    
    $http.get('https://httpbin.org/json', (error, response, body) => {
        if (error) {
            console.error(error);
            return;
        }
    
        assert.equal(response.statusCode, 200, 'expect 200 statusCode');
        let json = JSON.parse(body);
        assert.equal(json.slideshow.author, 'Yours Truly', 'They are equal');
    });
    
    $http.get('https://httpbin.org/get', (error, response, body) => {
        if (error) {
            console.error(error);
            return;
        }
        assert.equal(response.statusCode, 200, 'expect 200 statusCode');
    });
    
  • Make synchronous requests by using either of the following ways:

    • Use callbacks

      const assert = require('assert');
      
      $http.get('https://httpbin.org/json', (error, response, body) => {
          if (error) {
              console.error(error);
              return;
          }
          assert.strictEqual(response.statusCode, 200, 'expect 200');
      
          // send another request
          $http({
              url: 'https://httpbin.org/put',
              method: 'PUT',
              body: JSON.parse(body),
              json: true
          }, (error, response, body) => {
              if (error) {
                  console.error(error);
                  return;
              }
              assert.strictEqual(response.statusCode, 200, 'expect 200');
              assert.equal(body.json.slideshow.author, 'Yours Truly', 'expect body got from previous request');
          });
      });
      
      
    • Use async and await. The async and await keywords enable asynchronous, promise-based behavior. This helps your scripts to be written in a cleaner style, and you don't need to explicitly configure promise chains.

      const assert = require('assert');
      
      // define a new function sending http request using Promise
      // and return request result
      function httpRequest(options) {
          let p = new Promise((resolve, reject) => {
              $http(options, function(error, response, body) {
              if (error)
                  reject(error);
              else
                  resolve({
                  response: response,
                  body: body
                  });
              });
          });
      
          return p;
          }
      
          async function doRequests() {
              try {
                  // first request
                  let result1 = await httpRequest({
                      url: 'https://httpbin.org/json'
                  });
                  assert.equal(result1.response.statusCode, 200, 'expect 200');
      
                  // second request
                  let result2 = await httpRequest({
                      url: 'http://httpbin.org/put',
                      method: 'PUT',
                      body: JSON.parse(result1.body),
                      json: true
                  });
                  assert.strictEqual(result2.response.statusCode, 200, 'expect 200');
                  assert.equal(result2.body.json.slideshow.author, 'Yours Truly', 'expect body got from previous request');
              } catch (e) {
                  // deal error
                  console.error(e);
              }
          }
      
          // send request
          doRequests();
      

Import optional module

To import a supported module, follow the standard procedure about node.js importing. See the following example:

const crypto = require('crypto-js');

Supported core modules

The supported core modules are as follows:

  • assert
  • async_hooks
  • buffer
  • constants
  • crypto
  • dgram
  • dns
  • domain
  • events
  • fs
  • http
  • http2
  • https
  • module
  • net
  • os
  • path
  • perf_hooks
  • punycode
  • querystring
  • stream
  • string_decoder
  • timers
  • tls
  • trace_events
  • tty
  • url
  • util
  • zlib

Supported third-party modules

The supported third-party modules are as follows:

  • assert-plus 1.0.0
  • atob 2.1.2
  • @aws-sdk/client-s3 3.317.0
  • @babel/core 7.18.5
  • basic-ftp 4.6.6
  • body-parser 1.20.0
  • btoa 1.2.1
  • clone 0.1.19
  • colors 1.4.0
  • consoleplusplus 1.4.4
  • crypto-js 4.1.1
  • debug 2.6.9
  • extend 3.0.2
  • faker 5.5.3
  • got 11.8.6
  • joi 17.6.0
  • js-yaml 3.14.1
  • jsprim 1.4.2
  • kafkajs 2.2.3
  • ldapauth-fork 5.0.5
  • lodash 4.17.21
  • moment 2.29.4
  • net-snmp 3.8.2
  • node-stream-zip 1.15.0
  • protocol-buffers 4.2.0
  • q 1.5.1
  • request 2.88.2
  • should 13.2.3
  • ssh2-sftp-client 7.2.3
  • sshpk 1.17.0
  • swagger-parser 8.0.4
  • text-encoding 0.7.0
  • thrift 0.14.2
  • tough-cookie 4.0.0
  • underscore 1.13.4
  • unzipper 0.10.11
  • url-parse 1.5.10
  • urllib 2.38.0
  • uuid 3.4.0
  • validator 13.7.0
  • ws 7.5.8
  • xml2js 0.4.23

Other APIs

$synthetic

You can use $synthetic.var_name to access environment and runtime variables in the script. See the following predefined variables:

  • $synthetic.LOCATION or $synthetic.pop: Used to access the location label, which is the first part in the controller.location variable.
  • $synthetic.TEST_ID or $synthetic.id: Used to access the identifier of Synthetic test that is executed.
  • $synthetic.TEST_NAME or $synthetic.testName: Used to access the test label of Synthetic test that is executed.
  • $synthetic.TIME_ZONE or $synthetic.timeZone: Used to access the time zone of the PoP that runs the Synthetic test.
  • $synthetic.JOB_ID or $synthetic.taskId: Used to access the identifier of playback task, and it is also the result ID.
  • $synthetic.testType: Used to access the test type.
  • $synthetic.description: Used to access the test description. You can also define custom environment variables in values.yaml of the helm chart. See the following example:
controller:
  customProperties: "tag1=value1;tag2=value2"

Then, use $synthetic.tag1 and $synthetic.tag2 in the script to access these custom variables.

To access the custom properties that are defined in the customProperties section of the test definition, you can use $synthetic.labels.xxx to access the property value in the script. See the following example:

// to print test custom tags/labels
console.log('Test Label $synthetic.labels.Team: ' + $synthetic.labels.Team);
console.log('Test Label $synthetic.labels.Purpose: ' + $synthetic.labels.Purpose);

$env

To access environment variables in the script, use $env.MY_ENV_VARIABLE.

  • $env.LOCATION: Used to access the location label, which is the first part in controller.location variable.
  • $env.TEST_ID or $env.MONITOR_ID: Used to access the identifier of the Synthetic test.
  • $env.TEST_NAME: Used to access the test label of the Synthetic test.
  • $env.TIME_ZONE: Used to access time zone of the PoP running the Synthetic test.
  • $env.JOB_ID: Used to access the identifier of playback task.
  • $env.MONITOR_TYPE: Used to access the test type, which is the same as $synthetic.testType.

Note: The values of $env.LOCATION, $env.TEST_ID, $env.TEST_NAME, $env.TIME_ZONE, and $env.JOB_ID are the same as variables in $synthetic for compatibility.

The custom environment variables can be defined in the values.yaml file of helm chart, for example:

controller:
  customProperties: "tag1=value1;tag2=value2"

Then, use $env.tag1 and $env.tag2 in the script to access these custom variables.

$attributes

Use $attributes to add or get custom attributes to monitor data. API Script developers can add custom key/value pair data as custom attributes. The custom data serves as an addition to the default attributes. These custom attributes are also stored in the monitor results with other default monitor result attributes. To access the custom attributes, use $attributes in the Instana API script:

$attributes.set('tag1', 'value1') // sets tag tag1 to value value1
let v = $attributes.get('tag1') // sets variable v to the value of tag1

$network

The following APIs are supported in Instana synthetic monitoring for configuring proxy server:

  • $network.setProxy(string proxy): Used to set a proxy server to be used for all requests (HTTP, HTTPS).
  • $network.setProxyForHttp(string proxy): Used to set a proxy server to be used for only HTTP requests.
  • $network.setProxyForHttps(string proxy): Used to set a proxy server to be used for only HTTPs requests.
  • $network.clearProxy(): Used to remove the proxy configuration.
  • $network.getProxy(): Used to return the proxy configuration.

The following example uses proxy:

const assert = require('assert');

// Proxy with auth
$network.setProxy('http://username:password@proxyhost:port');
// $network.setProxyForHttp('http://proxyhost:port');
// $network.setProxyForHttps('http://proxyhost:port');

// retrieve proxy
// let proxy = $network.getProxy();

$util.secrets

Use this security API to redact sensitive data.

All known sensitive information is masked with the * character before the information is written into logging files and sent to backends.

Synthetic PoP does not collect HTTP header and body data. If you want to redact secrets from URLs, use the command $util.secrets.setURLSecretsRegExps in your script. See the following example:

// to redact 'key', 'password' and 'secret' query parameters in URL
$util.secrets.setURLSecretsRegExps([/key/i, /password/i, /secret/i]);

After this API is called, the URL https://example.com/accounts/status?key=mykey123&secret=mysecret is collected and displayed as https://example.com/accounts/status?key=*&secret=* in the Instana UI.

Test and debug script locally

synthetic-api-script is a node module to develop and debug API script in local environment. For more information, see the readme file.

Test and debug API script with script-cli

Use the script-cli command as shown in the following example:

# Usage:
# test a single script
script-cli <script_name>
# test bundled scripts
script-cli -d <dir> <script-entry-file>
# Example:
script-cli examples/example1.js
script-cli -d examples/bundle-example1 index.js

Create a single API script test

After a Synthetic script is created, use synthetic-api-script to create an API script, and convert the script to string with the script-cli command:

# Usage:
script-cli -s <script-file-path>
# Example:
script-cli -s examples/http-get.js

Result example:

{
  "syntheticType":"HTTPScript",
  "script":"var assert = require(\"assert\");\n\nconst requestURL = 'https://httpbin.org/get';\n\n$http.get(requestURL, {\n strictSSL: false, // false for self signed certificate\n },\n function (err, response, body) {\n if (err) throw err;\n assert.equal(response.statusCode, 200, \"Expected a 200 OK response\");\n console.info('Request URL %s, statusCode: %d', requestURL, response.statusCode);\n }\n);\n"
}

Then, copy and paste the script content to build the following example API script test JSON.

{
  "label": "APIScript_Test",
  "active": true,
  "testFrequency": 1,
  "locations": ["DemoPoP1_saas_instana_test"],
  "configuration": {
    "syntheticType": "HTTPScript",
    "script": "converted script string"
  }
}

Create a bundled API script test

If the business logic is complex, do not contain everything in a single script because it's not a good experience for developers, especially managing multiple script files in a Git repository. Bundle scripts in a compressed file by using the script-cli command:

# Usage:
script-cli -z <bundle-script-folder> <entry-script>
# Example:
script-cli -z bundle-example1 index.js

Fill scriptFile and bundle in the test configuration with the output of the script-cli command:

  • scriptFile is the entry point of the bundled scripts.
  • bundle is the compressed file encoded with base64.

To create Synthetic bundled test, fill test payload as shown in the following example:

{
  "label": "APIScriptBundle_Test",
  "active": true,
  "testFrequency": 5,
  "locations": ["DemoPoP1_saas_instana_test"],
  "configuration": {
    "syntheticType": "HTTPScript",
    "scripts": {
      "scriptFile": "index.js",
      "bundle": "zipped scripts encoded with base64"
    }
  }
}

Script Examples

Example 1: Instana API Script to test httpbin GET/POST/PUT/DELETE APIs (async calls)

You can use the Instana API script to test httpbin calls in asynchronized way as follows:

var assert=require('assert');


var options = {
    uri: 'https://httpbin.org/get',
    strictSSL: false,
    headers: {
        'Additional-Header': 'Additional-Header-Value'
    }
};
$http.get(options, function(error, response, body){
    console.log("Sample API - GET, response code: " + response.statusCode);
    assert.ok(response.statusCode==200, "GET status is " + response.statusCode + ", it should be 200");
    var bodyObj = JSON.parse(body);
    assert.ok(bodyObj.url=="https://httpbin.org/get", "httpbin.org REST API GET URL verify failed");
});

var postOptions = {
    uri: 'https://httpbin.org/post',
    json: {
        "name1": "this is the first data",
        "name2": "second data"
    },
    strictSSL: false,
    headers:{"accept": "application/json"}
};
$http.post(postOptions, function (err, response, body) {
    console.log("Sample API - POST, response code: " + response.statusCode);
    assert.ok(response.statusCode==200, "POST status is " + response.statusCode + ", it should be 200");

    assert.equal(body.json.name1, 'this is the first data', 'Expected this is the first data');
    assert.equal(body.json.name2, 'second data', 'Expected second data');
  }
);


var putOptions = {
    uri: 'https://httpbin.org/put',
    json: {
        "name1": 'this is the first data',
        "name2": 'second data'
    },
    strictSSL: false,
    headers:{"accept": "application/json"}
};
$http.put(putOptions, function(error,response,body){
    console.log("Sample API - PUT, response code: " + response.statusCode);
    assert.ok(response.statusCode==200, "PUT status is " + response.statusCode + ", it should be 200");

    assert.ok(body.url=="https://httpbin.org/put", "httpbin.org REST API PUT URL verify failed");
    assert.ok(body.json.name2=="second data", "httpbin.org REST API PUT URL verify failed");
});


var deleteOptions = {
    uri: 'https://httpbin.org/delete',
    strictSSL: false,
    headers:{"accept": "application/json"}
};
$http.delete(deleteOptions, function(error,response,body){
    console.log("Sample API - DELETE, response code: " + response.statusCode);
    assert.ok(response.statusCode==200, "DELETE status is " + response.statusCode + ", it should be 200");
    var bodyObj = JSON.parse(body);
    assert.ok(bodyObj.url=="https://httpbin.org/delete", "httpbin.org REST API DELETE URL verify failed");
});

function printInformation() {
 // to print environment variables
   console.log('Environment Variable $env.TEST_ID: ' + $env.TEST_ID);
   console.log('Environment Variable $env.MONITOR_ID: ' + $env.MONITOR_ID);
   console.log('Environment Variable $env.TEST_NAME: ' + $env.TEST_NAME);
   console.log('Environment Variable $env.LOCATION: ' + $env.LOCATION);
   console.log('Environment Variable $env.TIME_ZONE: ' + $env.TIME_ZONE);
   console.log('Environment Variable $env.JOB_ID: ' + $env.JOB_ID);

   console.log('Environment Variable $synthetic.id: ' + $synthetic.id);
   console.log('Environment Variable $synthetic.taskId: ' + $synthetic.taskId);
   console.log('Environment Variable $synthetic.pop: ' + $synthetic.pop);
   console.log('Environment Variable $synthetic.testType: ' + $synthetic.testType);
   console.log('Environment Variable $synthetic.testName: ' + $synthetic.testName);
   console.log('Environment Variable $synthetic.description: ' + $synthetic.description);
   console.log('Environment Variable $synthetic.timeZone: ' + $synthetic.timeZone);

   // to print test custom tags/labels
   console.log('Test Label $synthetic.labels.Team: ' + $synthetic.labels.Team);
   console.log('Test Label $synthetic.labels.Purpose: ' + $synthetic.labels.Purpose);

   // to set custom tags dynamically
   $attributes.set('custom_tag1', 'value1');

}
printInformation();

Example 2: Instana API script to test public IP service (sync calls)

You can use the Instana API script to test the public IP service in synchronized way as follows:

const assert = require('assert');
// A common function to send http request using Promise and return request result
function httpRequest(options) {
  return new Promise((resolve, reject) => {
    $http(options, function(error, response, body) {
      if (error)
        reject(error);
      else
        resolve({
          response: response,
          body: body
        });
    });
  });
}
async function main() {
  	let result = {};

	// Use await to make multiple HTTP calls in sequence

	// Below example shows how to make HTTP POST call synchronously
	/*
	var postOptions = {
	  method: 'POST',
	  uri: 'https://httpbin.org/post',
	  json: {
		"name1": "this is the first data",
		"name2": "second data"
	  },
	  strictSSL: false,
	  headers:{"accept": "application/json"}
	};
	let r = await httpRequest(postOptions);
	*/

	// Make HTTP Get calls synchronously

	// Get Public IP for this PoP geo-location
	let r1 = await httpRequest({url: 'https://api.ipify.org?format=json'});
	assert.equal(r1.response.statusCode, 200, 'Get public IP address: statusCode should be 200');
	result.IP = JSON.parse(r1.body).ip.trim();

	// Get country by IP
	let r2 = await httpRequest({url: 'https://ipinfo.io/' + result.IP + '/country'});
	assert.equal(r2.response.statusCode, 200, 'Get country: statusCode should be 200');
	result.Country = r2.body.trim();

	// Get city by IP
	let r3 = await httpRequest({url: 'https://ipinfo.io/' + result.IP + '/city'});
	assert.equal(r3.response.statusCode, 200, 'Get city: statusCode should be 200');
	result.City = r3.body.trim();

	// get Latitude, Longitude by IP
	let r4 = await httpRequest({url: 'https://ipinfo.io/' + result.IP + '/loc'});
	assert.equal(r4.response.statusCode, 200, 'Get geo position: statusCode should be 200');
	result.Latitude = r4.body.split(',')[0].trim();
	result.Longitude = r4.body.split(',')[1].trim();

	console.info('List PoP geo-location data by calling public Rest APIs');
	console.info('Public IP:', result.IP);
	console.info('Country:', result.Country);
	console.info('City:', result.City);
	console.info('Latitude:', result.Latitude);
	console.info('Logintude:', result.Longitude);

	console.info('List PoP environment variables using $synthetic API');
	console.info('Test ID:', $synthetic.TEST_ID);
	console.info('Test Name:', $synthetic.TEST_NAME);
	console.info('Location:', $synthetic.LOCATION);
	console.info('TimeZone:', $synthetic.TIME_ZONE);
	console.info('Job ID:', $synthetic.JOB_ID);

}

main();

For more API script examples, see synthetic API script.