REST API example code

This page contains examples of code calling the BMC Discovery REST API. As well as helping with the specific scenarios in the examples, the code can be read to familiarize yourself with some general principles for working with the API.

Each example contains full, working code including error handling. Most of the examples embed the appliance name and permanent token near the top of the listing for convenience - you will need to change these before running the code. The final example shows how to request an expiring token for a BMC Discovery user and then use it immediately.

A variety of endpoints are used for the examples, and full details of how to use each endpoint can be found in the endpoints list. Some of the examples use endpoints or features introduced in version 1.1 of the API.

Examples are provided in the following languages:

Node (JavaScript)

The examples were tested using version 6 of Node.js. The Request library is used to simplify the HTTP calls, so you'll need to ensure you have that installed.

If your BMC Discovery appliance uses a self-signed certificate, it must be added on the client machine as a trusted root certificate. This might not be convenient for API testing, so our examples try to mitigate this by disabling SSL validation:

agentOptions: {
                 rejectUnauthorized: false
              }

This approach is NOT recommended in production scripts.

Powershell

The examples were tested with version 5 of Powershell.

There is a known limitation of the Invoke-RestMethod cmdlet: if HTTPS is used, server certificate validation cannot be disabled. Therefore if your BMC Discovery appliance uses a self-signed certificate, it must be added on the client machine as a trusted root certificate. This might not be convenient for API testing, so our examples try to mitigate this by disabling SSL validation:

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

However, this approach is known to not work in certain cases. If you encounter the following error:

The underlying connection was closed: An unexpected error occurred on a send.

you should either add the server certificate to the trusted certificate store, or use HTTP.

Security considerations

Enabling REST API access over HTTP is not recommended in production and should only be used for testing purposes. API requests contain your Authentication token in an HTTP header, and are passed in plain text when using HTTP. If you enable HTTP access to experiment with the API, do not forget to revert the configuration.



The examples

Search the data

Run a search query and navigate the paginated results.

 View example

Based on a real issue, this example executes a search query to find all discovered Windows Hosts that do not have one of a certain list of patches installed.

An initial call to /data/search is made, which returns the first page of results along with some metadata. In the metadata is a "next" link, which is used to request the next page of results. This pattern is repeated until the last page of results, which does not contain a "next" link.

As each results page is consumed, the contents are converted to csv and written to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME
 
const QUERY = `SEARCH Host AS all_hosts
               WHERE os_class = "Windows"
               SEARCH Patch
               WHERE name in ['KB4012598', 'KB4012212', 'KB4012215', 'KB4012213', 'KB4012216', 'KB4012214',
                              'KB4012217', 'KB4012216', 'KB4012606', 'KB4013198', 'KB4013429', 'KB4012598', 'KB4011981']
               TRAVERSE InstalledSoftware:HostedSoftware:Host:Host AS patched_hosts
               SEARCH IN (all_hosts - patched_hosts)
               SHOW name, os, os_version, join(#InferredElement:Inference:Associate:DiscoveryAccess.endpoint, ',') as "IP Address"`;
 
 
// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});
 
 
// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}
 
function get(options, callback) {
    request.get(options, function (error, response, body) {
        if (error) {
            fail(error);
        }
 
        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}
 
function print_csv(results_page) {
    // https://stackoverflow.com/a/29976603
    let csv_lines = results_page.map(function(results_row) {
 
       return JSON.stringify(results_row);
    })
    .join('\n')
    .replace(/(^\[)|(\]$)/mg, '');
 
    console.log(csv_lines);
}
 
// Application code
 
function success_callback(write_headings, body) {
    // We searched for one node kind so expecting one result set back
    const results_page = body[0];
 
    // Output headings just once
    if (results_page.headings.length && write_headings) {
        console.log('"' + results_page.headings.join('","') + '"');
    }
 
    // Write out this page
    print_csv(results_page.results);
 
    // Are there more pages?
    if (results_page.next) {
        get({uri: results_page.next}, success_callback.bind(this, false));
    }
}
 
// Run the query, getting back first page of results.
// Our callback will keep asking for the next page.
get( {uri: `${BASE_PATH}/data/search`,
      qs: {query: QUERY}}, success_callback.bind(this, true));

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$QUERY = @"
        SEARCH Host AS all_hosts
        WHERE os_class = "Windows"
        SEARCH Patch
        WHERE name in ['KB4012598', 'KB4012212', 'KB4012215', 'KB4012213',
                       'KB4012216', 'KB4012214', 'KB4012217', 'KB4012216',
                       'KB4012606', 'KB4013198', 'KB4013429', 'KB4012598',
                       'KB4011981']
        TRAVERSE InstalledSoftware:HostedSoftware:Host:Host AS patched_hosts
        SEARCH IN (all_hosts - patched_hosts)
        SHOW name, os, os_version,
             join(#InferredElement:Inference:Associate:DiscoveryAccess.endpoint,
                  ',') as "IP Address"
"@


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function restful_get {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)

    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}

    try {
        return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}

function search_data {
    Param([string] $base_path,
          [string] $token,
          [string] $query)

    # We are going to output search results as CSV, and objects are easier
    # to convert to CSV than lists, so we are using "format=object" to get
    # a list of objects rather than a list of lists
    $result = restful_get -url "$base_path/data/search" -token $token `
                          -params @{"query" = $query; "format" = "object"}

    # We searched for one node kind so expecting one result set back
    $all_results = $result[0].results

    while ($result[0].next) {
        $result = restful_get -url $result[0].next -token $token
        $all_results += $result[0].results
    }

    return $all_results
}

$result = search_data -base_path $BASE_PATH -token $TOKEN `
                      -query $QUERY

echo $result | Select-Object "name", "os", "os_version", "IP Address" | ConvertTo-Csv -NoTypeInformation

Create an IP address scan

Create a discovery run, wait until the run is complete, and then retrieve results.

 View example

An initial POST call to /discovery/runs is made, which returns the uuid of the newly created snapshot discovery run. We then poll for the status of the new run at /discovery/runs/{uuid}, until the 'finished' field of the returned status is true.

At this point the run has completed and we can then call /discovery/runs/{uuid}/inferred to retrieve the counts of devices inferred in this run. These are written to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const IP_SCAN = {
    scan_kind: 'IP',
    ranges: ['192.168.0.*'], // UPDATE ME
    label: 'Test network'
};


// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
        if (error) {
            fail(error);
        }

        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function get(options, callback) {
    options.method = 'GET';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}

// Application code

function get_inferred(run_id) {
    get({uri: `${BASE_PATH}/discovery/runs/${run_id}/inferred`}, function (body) {
        console.log('Inferred devices:');
        for (var device in body) {
            console.log(device, body[device].count);
        }
    });
}

function check_status(run_id) {
    // Wait for 3 seconds
    setTimeout(function () {
        console.log('Checking discovery run status...')

        // Retrieve discovery run status
        get({uri: `${BASE_PATH}/discovery/runs/${run_id}`},
            function (body) {
                if (body.finished) {
                    // Retrieve inferred devices if finished
                    get_inferred(run_id);
                }
                else {
                    // Keep checking status otherwise
                    setImmediate(check_status, run_id);
                }
            });
    }, 3000);
}

// Start the discovery, receive run id.
// check_status will be called until the run is finished.
post({uri: `${BASE_PATH}/discovery/runs`, body: IP_SCAN},
    function (body) {
        check_status(body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME
 
$IP_SCAN = @{"scan_kind" = "IP";
             "ranges"    = @("192.168.0.*"); # UPDATE ME
             "label"     = "Test network"}
 
 
# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
 
################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
 
 
function restful_get {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)
 
    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}
 
    try {
        return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}
 
function restful_post {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)
 
    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}
 
    $json = $params | ConvertTo-Json
 
    try {
        return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}
 
function scan {
    Param([string]$base_path,
          [string]$token,
          [hashtable]$params)
 
    # Start the run
    $new_run = restful_post -url "$base_path/discovery/runs" `
                            -token $token -params $params
    $run_id = $new_run.uuid
 
    # Poll run status every 3 seconds until the run is finished
    do {
        Start-Sleep -s 3
        Write-Host "Checking discovery run status..."
        $result = restful_get -url "$base_path/discovery/runs/$run_id" `
                              -token $token
    } while (!$result.finished)
 
    # See what the scan found
    return restful_get -url "$base_path/discovery/runs/$run_id/inferred" `
                       -token $token
}
 
$result = scan -base_path $BASE_PATH -token $TOKEN -params $IP_SCAN

echo "Inferred devices:"
foreach($device in $result.PsObject.Properties) {
    echo "    $($device.Name) $($device.Value.count)"
}

Create a cloud credential

Use cloud metadata to understand how to create a cloud credential.

 View example

This example creates a new credential to allow BMC Discovery to scan Amazon Web Services. This is achieved with a single call to the /vault/credentials endpoint. However, the more interesting part is finding which parameters are needed in that call.

The documentation for /vault/credentials lists a large number of fields, explaining that the set of required fields in a request depends on the type(s) of the credential being created. However, the documentation also says that credentials used to access cloud providers may require additional fields not listed. This is because TKU knowledge updates may add support for discovering new cloud providers, or extend support for existing providers. The /discovery/cloud_metadata endpoint is provided to be used as a reference when developing applications that manage cloud credentials or start cloud runs. This endpoint returns a list of cloud providers, along with parameters needed for scanning each provider and managing credentials for each provider. The endpoint is intended to be used interactively while writing your client application, not embedded in your application itself.

In this case we are interested in creating a credential for Amazon Web Services, so we call /discovery/cloud_metadata and examine the 'cred_params' field for the 'aws' provider. The actual results you see may vary by BMC Discovery and TKU version, but our version shows ten parameters:

{
    "cred_params": [
      {
        "allowed_values": [
          "..."
        ],
        "description": "Amazon Web Services Access Key ID. This is required unless a CyberArk query is used.",
        "is_list": false,
        "mandatory": false,
        "name": "aws.access_key_id",
        "type": "str"
      },
      {
        "allowed_values": [],
        "description": "Amazon Web Services Access Key Secret. This is required unless a CyberArk query is used.",
        "is_list": false,
        "mandatory": false,
        "name": "aws.access_key_secret",
        "type": "str"
      },
      {
        "allowed_values": [],
        "description": "CyberArk query to retrieve AWS Access Key",
        "is_list": false,
        "mandatory": false,
        "name": "aws.access_key.cyberark",
        "type": "str"
      },


...

Although none of the parameters are marked as mandatory, looking at the parameter descriptions we see that aws.access_key_id and aws.access_key_secret are actually required unless CyberArk is being used. Assuming we don't want to use CyberArk in this example, we must provide these two parameters to successfully create a credential for Amazon Web Services. This is in addition to the standard types and label fields which are used for all credential types.

Therefore, our application should issue a POST request to /vault/credentials with the following JSON body:

{
    "types": ["aws"],
    "label": "Test AWS credential",
    "aws.access_key_id": "your key id",
    "aws.access_key_secret": "your key secret"
}

The endpoint will reply with the ID of the newly created credential, which we write to stdout.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const AWS_CREDENTIAL = {
    types: ['aws'],
    label: 'Test AWS credential', // UPDATE ME
    'aws.access_key_id': 'your key id',  // UPDATE ME
    'aws.access_key_secret': 'your key secret'  // UPDATE ME
};


// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function post(options, callback) {
    request.post(options, function (error, response, body) {
        if (error) {
            fail(error);
        }

        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

// Application code

// Create the credential, print out credential id.
post({uri: `${BASE_PATH}/vault/credentials`, body: AWS_CREDENTIAL},
    function (body) {
        console.log("Created credential id:", body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$AWS_CREDENTIAL = @{"types" = @("aws");
                    "label" = "Test AWS credential"; # UPDATE ME
                    "aws.access_key_id" = "your key id"; # UPDATE ME
                    "aws.access_key_secret" = "your key secret"} # UPDATE ME


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function restful_post {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)

    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}

    $json = $params | ConvertTo-Json

    try {
        return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}

$result = restful_post -url "$BASE_PATH/vault/credentials" `
                       -token $TOKEN -params $AWS_CREDENTIAL

echo "Created credential id: $($result.uuid)"

Create a cloud scan

Use cloud metadata to understand how to discover a cloud provider.

 View example

Enable Cloud Discovery

In order to use cloud discovery, you have to enable it first. See Managing licenses for details.

This example creates a snapshot discovery run against Amazon Web Services and prints out counts of devices inferred from this run. The code is similar to the IP address scan example. A POST call to /discovery/runs is made, which returns the uuid of the newly created snapshot discovery run. We now poll for the status of the new run at /discovery/runs/{uuid}, until the 'finished' field of the returned status is true. At this point the run has completed and we can then call /discovery/runs/{uuid}/inferred to retrieve the counts of devices inferred in this run. These are written to stdout.

However, the interesting part is deciding which parameters to pass in the initial /discovery/runs call. Looking at the endpoint's documentation, as we are scanning a cloud provider the scan_kind field must be set to "Cloud". The documentation says the scan_params field now also needs to be supplied. The "provider" field must first be set to "aws", but in a similar way to the cloud credential example (see that for more details), we now use the /discovery/cloud_metadata endpoint as a reference to see which other fields are needed. Examining the "scan_params" field returned from the metadata endpoint we see (your output may vary):

{    
    "scan_params": [
      {
        "allowed_values": {
          "fddaa835070ad676013e89485e421dd3": "Test AWS credential"
        },
        "description": "Credential for the scan",
        "is_list": false,
        "mandatory": true,
        "name": "credential",
        "type": "str"
      },
      {
        "allowed_values": {
          "ap-northeast-1": "Asia Pacific (Tokyo)",
          "ap-northeast-2": "Asia Pacific (Seoul)",
          "ap-south-1": "Asia Pacific (Mumbai)",
          "ap-southeast-1": "Asia Pacific (Singapore)",
          "ap-southeast-2": "Asia Pacific (Sydney)",
          "ca-central-1": "Canada (Central)",
          "eu-central-1": "EU (Frankfurt)",
          "eu-west-1": "EU (Ireland)",
          "eu-west-2": "EU (London)",
          "sa-east-1": "South America (São Paulo)",
          "us-east-1": "US East (N. Virginia)",
          "us-east-2": "US East (Ohio)",
          "us-west-1": "US West (N. California)",
          "us-west-2": "US West (Oregon)"
        },
        "description": "List of regions to scan",
        "is_list": true,
        "mandatory": true,
        "name": "region",
        "type": "str"
      }
    ]
    
    ...
 

We see that there are two additional parameters needed to control the AWS scan, both of which are mandatory. The first field is "credential", which is the AWS cloud credential that BMC Discovery should use during the scan when accessing the AWS APIs. The "allowed_values" lists all suitable credentials in the system. In this case we see the single credential that we created in the earlier example. The second field is "region", which tells BMC Discovery which region(s) to scan. As this field has an "is_list" value set to true, we must supply one or more values in a list, picking from the allowed values. In both cases, the field value used should be the unique left hand side of the allowed values, not the descriptive string on the right.

Therefore, our application should issue a POST request to /discovery/runs with the following JSON body:

{
    "scan_kind": "Cloud",
    "scan_params": {
        "provider": "aws",
        "credential": "your aws credential id",
        "region": ["your aws region, e.g us-west-1"]
    },
    "label": "AWS scan"
}


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME

const AWS_SCAN = {
    scan_kind: 'Cloud',
    scan_params: {
        provider: 'aws',
        credential: 'your aws credential id',  // UPDATE ME
        region: ['your aws region, e.g us-west-1']  // UPDATE ME
    },
    label: 'AWS scan'
};

// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
        if (error) {
            fail(error);
        }

        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function get(options, callback) {
    options.method = 'GET';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}

// Application code

function get_inferred(run_id) {
    get({uri: `${BASE_PATH}/discovery/runs/${run_id}/inferred`}, function (body) {
        console.log('Inferred devices:');
        for (var device in body) {
            console.log(device, body[device].count);
        }
    });
}

function check_status(run_id) {
    // Wait for 3 seconds
    setTimeout(function () {
        console.log('Checking discovery run status...')

        // Retrieve discovery run status
        get({uri: `${BASE_PATH}/discovery/runs/${run_id}`},
            function (body) {
                if (body.finished) {
                    // Retrieve inferred devices if finished
                    get_inferred(run_id);
                }
                else {
                    // Keep checking status otherwise
                    setImmediate(check_status, run_id);
                }
            });
    }, 3000);
}

// Start the discovery, receive run id.
// check_status will be called until the run is finished.
post({uri: `${BASE_PATH}/discovery/runs`, body: AWS_SCAN},
    function (body) {
        check_status(body.uuid);
    });

$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME

$AWS_SCAN = @{"scan_kind" = "Cloud";
              "scan_params" = @{"provider" = "aws";
                                "credential" = "your aws credential id"; # UPDATE ME
                                "region" = @("your aws region, e.g us-west-1") # UPDATE ME
                               };
              "label" = "AWS scan"}


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }


function restful_get {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)

    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}

    try {
        return Invoke-RestMethod -Uri $url -Headers $headers -Body $params `
                                 -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}

function restful_post {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)

    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}

    $json = $params | ConvertTo-Json

    try {
        return Invoke-RestMethod -Method Post -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}

function scan {
    Param([string]$base_path,
          [string]$token,
          [hashtable]$params)

    # Start the run
    $new_run = restful_post -url "$base_path/discovery/runs" `
                            -token $token -params $params
    $run_id = $new_run.uuid

    # Poll run status every 3 seconds until the run is finished
    do {
        Start-Sleep -s 3
        Write-Host "Checking discovery run status..."
        $result = restful_get -url "$base_path/discovery/runs/$run_id" `
                              -token $token
    } while (!$result.finished)

    # See what the scan found
    return restful_get -url "$base_path/discovery/runs/$run_id/inferred" `
                       -token $token
}

$result = scan -base_path $BASE_PATH -token $TOKEN -params $AWS_SCAN
echo "Inferred devices:"
foreach($device in $result.PsObject.Properties) {
    echo "    $($device.Name) $($device.Value.count)"
}

Upload a TKU to the appliance

Upload a TKU file to the appliance and wait for it to be processed.

 View example

This example uploads a knowledge file to BMC Discovery by making a POST call to the /knowledge/{filename} endpoint. The path to the file is supplied as a command line argument:

> node example.js path_to_knowledge_file
> powershell example.ps1 path_to_knowledge_file

The file contents may be a single pattern module, a zip of pattern files, or a full TKU zip file as released by BMC each month. The {filename} part of the endpoint's url is built up from the extension of the file supplied, while the file contents is sent as binary data (the other format supported by this endpoint is multipart/form-data).

If the upload passes initial validation, the example then polls the /knowledge/status endpoint until the upload has been fully processed (including activating any patterns uploaded). Polling stops when the 'last_result' field of the response is not empty. At this point we print out the result of the upload and any messages received.

TKUs often contain new Network Devices or Product Content. These extend the discovery capabilities of the system, including adding support for new cloud providers. However they require BMC Discovery services to be restarted, which will not happen by default. If your uploaded file requires a service restart, the initial validation will fail and no changes will be made to the knowledge in the system. In this case you can resubmit your request to the /knowledge/{filename} endpoint, setting the allow_restart flag to true (the default is false). In this example, the flag is exposed via an optional named command line argument, "allow_restart", which takes a slightly different form depending on the language being used:


> node example.js --allow_restart path_to_knowledge_file


> powershell example.ps1 -allow_restart true path_to_knowledge_file

A possible extension of this example would be to output the progress as the upload is processed.


const BASE_PATH = 'https://appliance/api/v1.1';  // UPDATE ME
const TOKEN = 'your_token';  // UPDATE ME
  
// Set up some defaults for every call to the API
var request = require('request').defaults({auth: {'bearer': TOKEN},
                                           json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});
    
const fs = require('fs');
const path = require('path');
  
// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}
  
function rest(options, callback) {
    request(options, function (error, response, body) {
        if (error) {
            fail(error);
        }
  
        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            
            // If request was not sending json then response
            // will not automatically be converted to json
            if (body && !options.json) {
                body = JSON.parse(body);
            }
            
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}
  
function get(options, callback) {
    options.method = 'GET';
    rest(options, callback);
}
  
function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}
  
function parse_command_line() {
    let args = process.argv.slice(2);
  
    if (args.length == 0) {
        fail('File path required');
    }
    let file_path = args[0];
    let allow_restart = false;
    
    if (args.length > 1) {
        allow_restart = args[0] === '--allow_restart';
        file_path = args[1];
    }
  
    return {
        'file_path': file_path,
        'allow_restart': allow_restart
    }
}
  
// Application code
  
function check_status() {
    // Wait for 3 seconds
    setTimeout(function () {
        console.log('Checking upload status...')
  
        // Retrieve discovery run status
        get({uri: `${BASE_PATH}/knowledge/status`},
            function (body) {
                if (body.last_result) {
                    // Print out results if finished
                    console.log('Result:', body.last_result);
                    console.log('Progress:');
                    for (var i in body.messages) {
                        console.log(body.messages[i]);
                    }
                }
                else {
                    // Keep checking status otherwise
                    setImmediate(check_status);
                }
            });
    }, 3000);
}
  
// Parse command line and read the knowledge file
var options = parse_command_line();
try {
    var file_contents = fs.readFileSync(options.file_path);
} catch(e) {
    fail(e);
}
  
// Remove directory paths ready to append file name to url
const file_name = path.basename(options.file_path)
  
// Upload the file.
//
// 'json' must be set to false for this request as we
// do NOT want to convert the file contents to json.
post({
    uri: `${BASE_PATH}/knowledge/${file_name}`,
    body: file_contents,
    json: false,
    qs: {
        'allow_restart': options.allow_restart
    },
    headers: {
        'Content-Type': 'application/octet-stream'
    }},
    function (body) {
        // check_status will be called until upload is processed.
        check_status();
    });

param([string]$allow_restart = "false",
      [Parameter(Mandatory = $true, Position = 0)]
      [string]$FILEPATH)
      
$BASE_PATH = "https://appliance/api/v1.1" # UPDATE ME
$TOKEN = "your token" # UPDATE ME
 
 
# Make sure urlencoding utility is loaded
Add-Type -AssemblyName System.Web
 
# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
 
################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
 
 
function get_tku_status {
    Param([string]$base_path,
          [string]$token)
 
    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}
 
    try {
        return Invoke-RestMethod -Uri "$base_path/knowledge/status" `
                                 -Headers $headers -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}
 
 
function upload_file {
    Param([string]$base_path,
          [string]$token,
          [string]$file_path,
          [string]$allow_restart)
 
    $file_name = [System.Web.HttpUtility]::UrlEncode(([io.fileinfo]$file_path).Name)
 
    $headers = @{"Authorization" = "Bearer $token";
                 "Accept"        = "application/json";
                 "Content-Type"  = "application/octet-stream"}
 
    $file_contents = Get-Content $file_path -Enc Byte -raw
 
 
    $uri = "$base_path/knowledge/$file_name"
    if ($allow_restart -ne "false") {
        $uri = $uri + "?allow_restart=true"
    }
 
 
    try {
        return Invoke-RestMethod -Method Post -Uri $uri -Headers $headers `
                                 -Body $file_contents -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
            # Unlike other endpoints, knowledge upload has an additional error
            # field to indicate that knowledge upload failed because it requires
            # service restart. In this case we want explicit user confirmation.
            if ($temp.restart_required) {
                $err = $err + ' Rerun the script with "-allow_restart true"'
            }
        } catch {}
        Write-Error $err
        exit
    }
}
 
function upload_tku {
    Param([string]$base_path,
          [string]$token,
          [string]$file_path,
          [string]$allow_restart)
 
    # Upload the file
    upload_file -base_path $base_path -token $token `
                -file_path $file_path -allow_restart $allow_restart
 
    # Poll update status every 3 seconds and print out the result once done
    do {
        Start-Sleep -s 3
        Write-Host "Checking upload status..."
        $result = get_tku_status -base_path $base_path -token $token
    } while (!$result.last_result)
 
    echo "Result: $($result.last_result)"
    echo "Progress:"
    foreach ($message in $result.messages) {
        echo $message
    }
}
 
if (!$FILEPATH) {
    Write-Error "Please supply a filename"
    exit
}
if ($allow_restart -ne "false" -and $allow_restart -ne "true") {
    Write-Error 'allow_restart must be either "true" or "false"'
    exit
}
 
upload_tku -base_path $BASE_PATH -token $TOKEN -file_path $FILEPATH `
           -allow_restart $allow_restart

Generate and use an expiring token

Use the username and password of a BMC Discovery user to obtain and use an expiring API token.

 View example

Previous examples have embedded a permanent API token in their scripts. In this example we pass the username and password of a valid BMC Discovery user to the REST API and receive back an expiring token. From this point on we can use the token in subsequent API requests, in the same way as in other examples. This approach is suitable for interactive scripts that are to be run on behalf of different users.

First, the username and password are read from command line arguments:

> node example.js your_username your_password
> powershell example.ps1 your_username your_password

The, to obtain the expiring token we issue a POST call to the /api/token endpoint with the following parameters (note these are NOT JSON):

 "grant_type": "password"
 "username": "your_username"
 "password": "your_password"

If the supplied credentials are valid and the BMC Discovery user is permitted to access the REST API, the response will contain a token within a JSON object:

{
    "access_token": "<your_token>",
    "expires_in": 3600,
    "token_type": "bearer"
}

The example then shows that the token works by trying to set the status of the discovery process, issuing a PATCH call to the /discovery endpoint. Note that some users may not have permission to access this endpoint.

Expiring tokens are valid for one hour.


const APPLIANCE = "https://appliance";  // UPDATE ME


const BASE_PATH = `${APPLIANCE}/api/v1.1`;

// Set up some defaults for every call to the API
var request = require('request').defaults({json: true,
                                           agentOptions: {
                                               rejectUnauthorized: false
                                           }});


// Helper functions
function fail(message) {
    console.error(`ERROR: ${message}`);
    process.exit(1);
}

function rest(options, callback) {
    request(options, function (error, response, body) {
        if (error) {
            fail(error);
        }

        if (response.statusCode === 200) {
            callback(body);
        }
        else {
            let msg = `Request to ${options.uri} failed with status ${response.statusCode}`;
            if (body && body.message) {
                msg = `${body.message} (${msg})`;
            }
            else if (response.statusMessage) {
                msg = `${response.statusMessage} (${msg})`;
            }
            fail(msg);
        }
    });
}

function patch(options, callback) {
    options.method = 'PATCH';
    rest(options, callback);
}

function post(options, callback) {
    options.method = 'POST';
    rest(options, callback);
}


function parse_command_line() {
    let args = process.argv.slice(2);

    if (args.length < 2) {
        fail('Username and password required');
    }
    let username = args[0];
    let password = args[1];

    return {
        'username': username,
        'password': password
    }
}

// Application code

function start_discovery() {
    patch({uri: `${BASE_PATH}/discovery`, body: {status: 'running'}},
        function () {});
}


var options = parse_command_line();


const GRANT = {
    grant_type: 'password',
    username: options.username,
    password: options.password
};


// Obtain the token. Note that unlike all other endpoints,
// this one gets parameters as x-www-form-urlencoded
post({uri: `${APPLIANCE}/api/token`, form: GRANT},
    function (body) {
        // Token obtained, now we can add authentication to request object
        request = request.defaults({ auth: {'bearer': body.access_token} });

        // Start discovery
        start_discovery();
    });

# This script accepts two mandatory command line arguments: USERNAME
# and PASSWORD

param([string]$USERNAME,
      [string]$PASSWORD)


$APPLIANCE = "https://appliance" # UPDATE ME


# Tell powershell not to default to insecure protocols!
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12

################################################################################
################################################################################
###                                                                          ###
###                                WARNING                                   ###
### The following line disables server certificate validation to avoid       ###
### installing CA certificate for the server. It should only be present for  ###
### testing. You should never disable certificate validation in production.  ###
###                                                                          ###
################################################################################
################################################################################
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }

function get_token {
    Param([string]$appliance,
          [string]$username,
          [string]$password)

    # Make the call to authenticate and get a temporary token for subsequent calls

    $body = @{"grant_type" = "password";
              "username" = $username;
              "password" = $password}

    try {
        $result = Invoke-RestMethod -Method Post -Uri "$appliance/api/token" `
                                    -Body $body -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }

    return $result.access_token
}

function restful_patch {
    Param([string]$url,
          [string]$token,
          [hashtable]$params)

    $headers = @{"Accept" = "application/json";
                 "Authorization" = "Bearer $token"}

    $json = $params | ConvertTo-Json

    try {
        return Invoke-RestMethod -Method Patch -Uri $url -Headers $headers `
                                 -Body $json -ErrorVariable rest_error
    } catch {
        $err = $_
        try {
            $temp = $rest_error.Message | ConvertFrom-Json
            if ($temp.message) {
                $err = $temp.message
            }
        } catch {}
        Write-Error $err
        exit
    }
}



if (!$USERNAME -or !$PASSWORD) {
    Write-Error "Please supply a username and a password"
    exit
}

$token = get_token -appliance $APPLIANCE `
                          -username $USERNAME -password $PASSWORD

restful_patch -url "$APPLIANCE/api/v1.1/discovery" -token $token `
              -params @{"status" = "running"}





Was this page helpful? Yes No Submitting... Thank you

Comments