The app works by leveraging the Meraki Dashboard API and Webhooks.
Dashboard API for CMDB Sync
A scheduled job is run to import Meraki data via the API. Once imported, the data is then mapped to the related CMDB tables.
Scheduled Job
SG-Meraki Devices
Runs according to the user defined schedule
Data Source
The job uses a script to obtain the Meraki data.
Copyx_caci_sg_meraki_merakidevices
Copy(function loadData(import_set_table) {
// this tool will hash the incoming record and if it is a duplicate of a record that has already been
// processed the new record will not be written to the import set table and processed
//var inserter = new sn_cmdb_int_util.DuplicateRowProcessor("288088d680821010f8772cdfe9d5f8b5", import_set_table);
var sg_util = new x_caci_sg_meraki.SGConnectorMerakiUtils();
var orgStream = sn_fd.FlowAPI.executeDataStreamAction('x_caci_sg_meraki.merakiorganization');
while (orgStream.hasNext()) {
// Get a single item from the data stream.
var org = orgStream.next();
//networks
var networksStreamInput = {};
networksStreamInput['orgid'] = org.id;
var networksStream = sn_fd.FlowAPI.executeDataStreamAction('x_caci_sg_meraki.sgmerakigetnetworks', networksStreamInput);
var networks_array = [];
while (networksStream.hasNext()) {
var networks = networksStream.next();
var network_obj = {};
network_obj.id = networks['id'];
network_obj.name = networks['name'];
networks_array.push(network_obj);
}
//gs.info('networks_array' + networks_array.toString());
//devicestatus
var devicestatusStreamInput = {};
devicestatusStreamInput['orgid'] = org.id;
var devicestatusStream = sn_fd.FlowAPI.executeDataStreamAction('x_caci_sg_meraki.sgmerakigetdevicestatuses', devicestatusStreamInput);
var devicestatus_array = [];
while (devicestatusStream.hasNext()) {
var devicestatus = devicestatusStream.next();
var devicestatus_obj = {};
devicestatus_obj.serial = devicestatus['serial'];
devicestatus_obj.networkId = devicestatus['networkId'];
devicestatus_obj.lastReportedAt = devicestatus['lastReportedAt'];
devicestatus_obj.status = devicestatus['status'];
devicestatus_array.push(devicestatus_obj);
}
//gs.info('devicestatus_array' + devicestatus_array.toString());
// the only input to the second data stream is the id of the organization
var deviceStreamInput = {};
deviceStreamInput['orgid'] = org.id;
var deviceStream = sn_fd.FlowAPI.executeDataStreamAction('x_caci_sg_meraki.sgmerakigetdevices', deviceStreamInput);
while (deviceStream.hasNext()) {
var device = deviceStream.next();
device['organizationId'] = org.id;
device['organization_name'] = org.name;
device['organization_url'] = org.url;
device['network_name'] = sg_util.searchArray(networks_array, 'id', device['networkId']).name;
var devStatus = {};
devStatus = sg_util.searchArray(devicestatus_array, 'serial', device['serial']);
if (device['serial'] == devStatus.serial &&
device['networkId'] == devStatus.networkId &&
device['serial'] != null &&
device['networkId'] != null) {
device['device_status'] = devStatus.status;
device['lastReportedAt'] = devStatus.lastReportedAt;
}
import_set_table.insert(device);
}
networksStream.close();
deviceStream.close();
}
orgStream.close();
})(import_set_table);
Use this Postman Collection to easily test the following API endpoints.
Authentication
The app will use the provided read-only Meraki API key to pull all related records associated with it.
Organizations
GET
/organizations
Response Data
Copy[
{
"id": "2930418",
"name": "Customer 1",
"url": "https://dashboard.meraki.com/o/sdfAd/manage/organization/overview"
},
{
"id": "8768683",
"name": "Customer 2",
"url": "https://dashboard.meraki.com/o/VjjsAd/manage/organization/overview"
}
]
Networks
GET
/organizations/{organizationId}/networks
Response Data
Copy[
{
"id": "L_123456",
"organizationId": "2930418",
"name": "Long Island Office",
"timeZone": "America/Los_Angeles",
"tags": [ "tag1", "tag2" ],
"productTypes": [
"appliance",
"switch",
"wireless"
],
"enrollmentString": "long-island-office"
}
]
Devices
GET
/organizations/{organizationId}/devices
Response Data
Copy[
{
"name": "My AP",
"lat": 37.4180951010362,
"lng": -122.098531723022,
"address": "1600 Pennsylvania Ave",
"notes": "My AP's note",
"tags": " recently-added ",
"networkId": "N_24329156",
"serial": "Q234-ABCD-5678",
"model": "MR34",
"mac": "00:11:22:33:44:55",
"lanIp": "1.2.3.4",
"firmware": "wireless-25-14"
}
]
Device Statuses
GET
/organizations/{organizationId}/devices/statuses
Response Data
Copy[
{
"name": "My AP",
"serial": "Q234-ABCD-5678",
"mac": "00:11:22:33:44:55",
"publicIp": "123.123.123.1",
"networkId": "N_24329156",
"status": "online",
"lastReportedAt": "2018-02-11T00:00:00.090210Z",
"lanIp": "1.2.3.4",
"gateway": "1.2.3.5",
"ipType": "dhcp",
"primaryDns": "8.8.8.8",
"secondaryDns": "8.8.4.4"
}
]
Webhooks Alerts for Incident Creation
A scripted service will provide a REST endpoint exposing a URL based upon your instance and configured user account.
Priority of incidents can be defined by adding/modifying records in the Device Alerts lookup table found in the System Policy application menu.
Example Webhook URL
https://username:password@venXXX.service-now.com/api/x_caci_sg_meraki/sgmeraki_device_alerts
Where the structure is as follows:
- role : x_caci_sg_meraki.user
- username: username
- password : meraki
- instance: venXXX.service-now.com
- api service: /api/x_caci_sg_meraki/sgmeraki_device_alerts
Note: Use alpha-numeric passwords to avoid URL parsing errors
API Resource
API ID: meraki_device_alerts
Path: /api/x_caci_sg_meraki/sgmeraki_device_alerts
Method: POST
API Script Details
Copy(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
// Parse Meraki Alerts Webhook Data
var requestBody = request.body.dataString;
//var requestString = requestBody.dataString;
var requestParsed = {};
gs.info('SNAlerts-' + requestBody);
//gs.info('SNAlerts-'+ requestString);
requestParsed = JSON.parse(requestBody);
var merakiAlert = requestParsed;
var grp = gs.getProperty('x_caci_ciscomeraki.default_alert_group');
var ciId = '';
var ci = new GlideRecord('cmdb_ci');
ci.addQuery('serial_number', requestParsed.deviceSerial);
ci.query();
if (ci.next()) {
ciId = ci.getUniqueValue();
}
var inc = new GlideRecord('incident');
inc.newRecord();
inc.contact_type = 'monitoring';
inc.category = 'Alert';
if (grp != '')
inc.assignment_group = grp;
inc.short_description = requestParsed.alertType;
inc.x_caci_ciscomeraki_device_alert = requestParsed.alertType;
inc.description = 'Alert ' + requestParsed.alertType + ' received for device: ' + requestParsed.deviceName + ' SN: ' + requestParsed.deviceSerial + '\n';
var level = 0;
var myString = '';
function getMerakiAPIOrganizations() {
new CiscoMerakiUtils().getOrganizations();
}
for (var name in requestParsed) {
if (typeof requestParsed[name] == 'object') {
myString += "\n" + " ".repeat(level * 4) + name + ": ";
var stuff = new CiscoMerakiUtils().parseLevel1(requestParsed[name], 1);
myString += stuff;
} else {
myString += "\n" + " ".repeat(level * 4) + name + ": " + requestParsed[name];
}
}
inc.description += myString;
if (ciId != '')
inc.cmdb_ci = ciId;
inc.insert();
})(request, response);