LogicMonitor API and Alexa voice commands.
Amazon’s Alexa is a fantastic tool for working with APIs including LogicMonitor’s. The Amazon developer tools make this remarkably easy to script, but it can be a bit of a challenge the first time through. Let’s change that and walk through setting up a Alexa app that will acknowledge LogicMonitor Alerts through a API. Credit: I found this Youtube tutorial from Jordan Leigh very informative and used it as a base for building this app To my understanding, a Alexa Applications has two parts. * The Skill which includes the interaction model - setup the intents (functions) the app will executeand Utterances - which are the words and phrases that will trigger the functions * The function itself that will build and call the API. Once done, you will need to load the LogicMonitor Alexa skill by saying "Alexa load LogicMonitor". Then perform task by saying the pre-programmed utterance. Let's build a LogicMonitor Alexa Skill! This example uses the RPC API for simplicity and to illustrate how Alexa Skills are configured. The RPC API will eventually be depreciated, for long-term functionality it is recommended using the REST API. An example of using the REST API in a Python script is below the JavaScript Skill Function Code. *By request and for the brave, awalkthrough of creating a REST API in Python is below the APC Javascript skill Let’s start by creating the LogicMonitor Alexa Skill Login to https://developer.amazon.com, create an account if needed. And click on Alexa link at the top. Next press the “Get Started” button under “Alexa Skill Kit” For Skill information I used the below values. Skill type = Custom Interaction Model Name = LogicMonitor Invocation Name = LogicMonitor Next is the Interaction Model that will set up the intents or what I would call functions. This is in json format. We will create two intents for this walk through. One to acknowledge Alerts and a second to acknowledge Events, since these are two different API calls. Let’s take a look at one in detail "intent": "acknowledgeAlert", "slots":[ { "name": "AlertID", "type": "AMAZON.NUMBER" "intent": "acknowledgeAlert", names the intent. acknowledgeAlert will need to be defined in the function we will create later as well as the utterances so Alexa will know when to execute this intent. SLOTS are variables or the strings we can pass to our function to include in our API. They need to be a pre-defined type like AMAZON.NUMBER or a custom slot type. This walk through will pass alert numbers and we will use the amazon.number slot. More information on this can be found at https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference The full attached intent schema can be copy\pasted into your intent schema Next is the Sample Utterances. This is the fun part where we setup the voice commands. Let’s take a look acknowledgeAlert - This is the intent to be executed and the utterance has to start with the intent. The utterance also has to include the Invocation Name we setup previously which is “LogicMonitor”. The rest are common phrases the end user may use to invoke the intent with the slot passed in {}. You can setup as many utterances for an intent as needed. An example of the spoken utterances will be “Alexa Acknowledge LogicMonitor Alert 64532 This should send an API acknowledging LMD64532 Next is the Endpoint Configuration where we call the actual function. Lets pause here and create the function. This walk through will create a Java Script function on Amazons LAMBDA service. Sign into https://aws.amazon.com/, create an account if needed. Search for “LAMBDA” and press the “Create a Lambda Function” For the Select Blue print, add “Alexa” to the filter and select “Blank Function” Select “Alexa Skill Set” for Configured triggers For the configure Function, lets name it “LogicMonitor” and set the runtime to Node.js. You can copy\paste the attached LogicMonitor function into the window, but lets take closer look. This is basically a java script with case statement for “LaunchRequest” a default intent, and IntentRequest to call or two intents. Lets walk through our “AcknowlegeAlert” intent case "acknowledgeAlert": var AlertIDstr = (event.request.intent.slots.AlertID.value); var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=*******&ids=LMD"+AlertIDstr; var body = ""; https.get(endpoint, (response) => { response.on('data', (chunk) => { body += chunk }); response.on('end', () => { var data = JSON.parse(body); var status = data.status; var errmsg = data.errmsg if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true) ) ) } else{ context.succeed( generateResponse( buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true) ) ) } }); }); break; case "acknowledgeAlert": this is were we tie the Interaction mode, Utterances, and function together. var AlertIDstr = (event.request.intent.slots.AlertID.value); - This is how we get the Alert ID slot (Variable) defined as AlertIDstr inside of the java script. var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts? - this is how we define our API call. Note the end of the string &ids=LMD"+AlertIDstr; I am hardcodeing the LMD into the string and only passing the AlertIDstr wich will be a number i.e.64521. You’ll of course need to change the url to match your portal. I’m using the RPC API and more information can be read at https://www.logicmonitor.com/support/rpc-api-developers-guide/manage-alerts/acknowledge-alerts/ The https.get will send the URL and var data = JSON.parse(body); Will load the JSON file returned from LogicMonitor. We need to read the JSON file to ensure the API call was a success or if there was an error. Status=200 is error, so there is a simple if (status != 200) { statement to verify success and determine an appropriate buildSpeechletResponse return. If the status=200 Alexa will say “Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true)” or “Failed to acknowlege alert LMD65432 with error [access denied]. If the status is other than 200 Alexa will say “Alert LMD65432 has been acknowledged.’ Save the function and use the action on the main page to get the ARN string. Now, back to the Skill, we can add are function (Endpoint) using the ARN string and continue. Next is testing and this is the fun part where we put everything together. Next is the testing, and this is the anotherfun part. Find an active alert in your LogicMonitor portal and type in the Utterance according to the Intent Schema. I am going to use LMD2033 soIn my case it will be “acknowledge logicmonitor alert 2033” and press “Ask LogicMonitor” Go ahead and press the “Listen” button to hear Alexa’s response. That’s it, now it’s possible to acknowledge LogicMonitor Events and Alerts using Alexa voice commands. Intent Schema { "intents":[ { "intent": "acknowledgeAlert", "slots":[ { "name": "AlertID", "type": "AMAZON.NUMBER" } ] }, { "intent": "acknowlegeEvent", "slots":[ { "name": "EventID", "type": "AMAZON.NUMBER" } ] } ] } LogicMonitor Java Script Function var https = require('https'); exports.handler = (event, context) => { try { if (event.session.new) { // New Session console.log("NEW SESSION"); } switch (event.request.type) { case "LaunchRequest": // Launch Request console.log(`LAUNCH REQUEST`); context.succeed( generateResponse( buildSpeechletResponse("Welcome to LogicMonitor, I can acknowlege alerts and Events", true), {} ) ); break; case "IntentRequest": // Intent Request console.log(`INTENT REQUEST`); switch(event.request.intent.name) { case "acknowledgeAlert": var AlertIDstr = (event.request.intent.slots.AlertID.value); var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme=LMD"+AlertIDstr; var body = ""; https.get(endpoint, (response) => { response.on('data', (chunk) => { body += chunk }); response.on('end', () => { var data = JSON.parse(body); var status = data.status; var errmsg = data.errmsg if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowlege alert L M D"+AlertIDSt+" with error "+ errmsg, true) ) ) } else{ context.succeed( generateResponse( buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true) ) ) } }); }); break; case "acknowlegeEvent": var EventIDstr = (event.request.intent.slots.EventID.value); var evendpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme&eids=LME"+EventIDstr; var evbody = ""; https.get(evendpoint, (response) => { response.on('data', (chunk) => { evbody += chunk }); response.on('end', () => { var data = JSON.parse(evbody); var status = data.status; var errmsg = data.errmsg; if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowlege event L M E"+EventIDstr+" with error "+ errmsg, true) ) ); } else{ context.succeed( generateResponse( buildSpeechletResponse("Event L M E"+EventIDstr+" has been acknowledged", true) ) ); } }); }); break; case "SessionEndedRequest": // Session Ended Request console.log(`SESSION ENDED REQUEST`); break; default: context.fail(`INVALID REQUEST TYPE: ${event.request.type}`); } } } catch(error) { context.fail(`Exception: ${error}`) } }; // Helpers buildSpeechletResponse = (outputText, shouldEndSession) => { return { outputSpeech: { type: "PlainText", text: outputText }, shouldEndSession: shouldEndSession }; }; generateResponse = (speechletResponse, sessionAttributes) => { return { version: "1.0", sessionAttributes: sessionAttributes, response: speechletResponse }; }; You should now have a working RPC javascript Alexa LogicMonitor Skill set For the brave: OK that was the easy part, lets take a look at running Python and the LogicMonitor REST API. The difficult part with the REST API is the authentication, which requires importing of Modules. This isn't native to Amazon LAMBDA so we we have to build or own environment. I used a terminal in a Ubuntu environment for easy access to the PIP installer. I also used this excellent A minimalist SDK for developing skills for the Amazon Echo's ASK - Alexa Skills Kit using Amazon Web Services's Python Lambda Functions.written by Anjishnu Kumar . I'm not going to walk through step-by-step as Anjishnu's readme does an excellent job. I will share my pain-points I had when setting this up. Getting the Modules installed. Amazon's Docs, plus a walkthrough in Anjishunu's readme file. Cliff notes version, you need to use PIP to install the modules to a the script directory. Once everything is installed we will ZIP the directory and upload to LAMBDA. First make a directory in your terminal window with mkdir logicmonitor (or whatever you would like to call the skill) and go through Step 1 in the Readme File. Next we install the modules. This two we have to worry about are: from ask import alexa import requests The first is just the alexa SDK and as in Anuishunu's readme the command it pip install ask-alexa-pykit --target logicmonitor Net is request and the command is pip install requests --target logicmonitor You will see the Ask and Request folders in the directory. You can skip step 2 and 3 as we already have a intent schema from the previous example. Step 4 is the creation of the lambda_funtion.py. You can use mine (Shown below) as a template. Save the file aslambda_function.py in the logicmonitor folder. Step 5 is compressing the logicmonitor folder or as in the Readme ask-lambda.zip, ether name is fine. Step 6 is creating the LAMBDA funtion. WATCH THE RUNTIME. I lost a hour because Amazon switched Python 2.7 back to Node.js, and I notice it kept switching back when I re-uploaded my code. The other gotcha is the handler,lambda_function.lambda_handler is expecting a python script named lambda_function just like logicmonitor.lambda_handler would expect a script named logicmonitor. Hopefully this is enough information to write your own LogicMonitor Alexa Skill! -Enjoy! from ask import alexa import requests import hashlib import base64 import time import hmac def lambda_handler(request_obj, context=None): ''' This is the main function to enter to enter into this code. If you are hosting this code on AWS Lambda, this should be the entry point. Otherwise your server can hit this code as long as you remember that the input 'request_obj' is JSON request converted into a nested python object. ''' metadata = {'user_name': 'SomeRandomDude'} # add your own metadata to the request using key value pairs ''' inject user relevant metadata into the request if you want to, here. e.g. Something like : ... metadata = {'user_name' : some_database.query_user_name(request.get_user_id())} Then in the handler function you can do something like - ... return alexa.create_response('Hello there {}!'.format(request.metadata['user_name'])) ''' return alexa.route_request(request_obj, metadata) @alexa.default def default_handler(request): """ The default handler gets invoked if no handler is set for a request type """ return alexa.respond('Just ask').with_card('Hello World') @alexa.request("LaunchRequest") def launch_request_handler(request): ''' Handler for LaunchRequest ''' return alexa.create_response(message="Hello Welcome to Logic Monitor, I can Acknowledge alerts and events!") @alexa.request("SessionEndedRequest") def session_ended_request_handler(request): return alexa.create_response(message="Goodbye!") @alexa.intent('acknowledgeAlert') def acknowlege_alert(request): alertid = request.slots["AlertID"] alertidstr = str(alertid) # Account Info AccessId = '4R9CX5fQf3MrKvjLS59T' AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9' Company = 'lmjeffwoeber' # Request Info httpVerb = 'POST' resourcePath = '/alert/alerts/LMD'+alertidstr+'/ack' queryParams = '' data = '{"ackComment":"acked with Alexa"}' # Construct URL url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams # Get current time in milliseconds epoch = str(int(time.time() * 1000)) # Concatenate Request details requestVars = httpVerb + epoch + data + resourcePath # Construct signature signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest()) # Construct headers auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch headers = {'Content-Type': 'application/json', 'Authorization': auth} # Make request response = requests.post(url, data=data, headers=headers) if response.status_code !=200: return alexa.create_response(message="I'm sorry, I was unable to acknowledge Alert L M D"+alertidstr) else: return alexa.create_response(message="Alert acknowledge!") @alexa.intent('acknowlegeEvent') def acknowlege_event(request): eventid = request.slots["EventID"] eventidstr = str(eventid) # Account Info AccessId = '4R9CX5fQf3MrKvjLS59T' AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9' Company = 'lmjeffwoeber' # Request Info httpVerb = 'POST' resourcePath = '/alert/alerts/LME'+eventidstr+'/ack' queryParams = '' data = '{"ackComment":"acked with alexa"}' # Construct URL url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams # Get current time in milliseconds epoch = str(int(time.time() * 1000)) # Concatenate Request details requestVars = httpVerb + epoch + data + resourcePath # Construct signature signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest()) # Construct headers auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch headers = {'Content-Type': 'application/json', 'Authorization': auth} # Make request response = requests.post(url, data=data, headers=headers) if response.status_code !=200: return alexa.create_response(message="I'm sorry, I was unable to acknowledge Event L M E"+eventidstr) else: return alexa.create_response(message="Event acknowledge!")30Views1like0Comments