ServiceNow to Azure DevOps: Working Together in Harmony

Author by Michael Dugan

Now more than ever, this is the age of “Digital Transformation”. ServiceNow and Azure DevOps have been rapidly evolving from a systems integration perspective. As of today, there are ServiceNow tasks and pre-deployment conditions supported within Azure DevOps. But, that’s just a one-way direction between the two systems and specifically focuses on updating a ServiceNow record within the CMDB.

What if you need to drive your processes back into Azure DevOps for cloud infrastructure provisioning? If your organization is focused on a DevOps practice based out of Azure, continue reading to learn more about how ServiceNow can get involved!

What are Web Services?

Let’s start here first. Believe it or not, it seems that most software products or platforms offer web services built-in as a core feature today. This is mostly due to users requiring data to be sent into a system (inbound) or data needs to be sent out to an external source of some kind (outbound). Thankfully, there is a capability which exists today that is already included within ServiceNow and allows for this type of functionality. This can be accomplished by utilizing Outbound REST web services within the ServiceNow platform.

Think of the possibilities… with this type of interaction, virtually anything can be achieved! Now, let’s talk about the concept of an endpoint as a specific definition in a dictionary. The system is expecting data in a specific format to perform an operation, and the system processes the request once the data reaches the specific endpoint.

Steps to Configure a REST Message

To begin, a REST Message must be created within ServiceNow to facilitate the communication of outbound requests. Once a name has been given to the REST Message, the following must be configured for messages to send properly:

  1. Create a REST Message record with mandatory REST endpoint (can be dynamic with variables)
    1. By default, ServiceNow creates a GET HTTP method for you and this is a great way to test out the connection to your REST endpoint
  2. Set up Authentication
    1. ServiceNow supports the following types:
      1. Basic authentication**
      2. Mutual (two-way authentication)
      3. OAuth 2.0
  3. Add additional HTTP Methods (POST, PUT, etc.)

**For this scenario, a basic authentication profile consists of a username and password. Azure DevOps requires an authentication token for the password value. For this scenario, I created a token with both read/write Work Item privileges since the intent is to create work items within Azure DevOps.

rest1.png

Notice that I have two HTTP methods set up with this REST message record. The “GET” method is for retrieving data while the “POST” method is meant for creating something new. In this scenario, there’s a requirement for creating a new User Story so we must use the POST method. Note that the “GET” HTTP method can remain within the system with other methods and isn’t required to be used with every REST call – it’s there on standby for any future request to that specific endpoint.

rest2.png

What sets ServiceNow apart from other vendors is the platform allows the user to test a REST message being sent to the endpoint for test runs. This test message will include the test values in the right-most column of the Variable Substitutions, but values can be overridden when calling the REST message to begin with! Simply call the “name” of the variable substitution record you wish you replace the value with and it will plug and play. We will look at this later in the script I’ve put together…

rest3.png

Endpoints

One other item to keep in mind is there are multiple endpoints for all types of resources, especially within the Azure DevOps world. For our scenario, we’re attempting to create a User Story which inherits from the Work Item class. After referring to the Microsoft documentation on how to set up the content body for the request, it appears that it must be formed in Patch JSON.

resthttp.png

Each block within the inner section of the JSON represents an Azure DevOps property within the inherited class. In the example above, the title and description of the User Story is being set. For a complete list of fields, refer to this Microsoft article to further extend your Content body.

Quick Tips

  • ServiceNow allows for dynamic values to be passed into the various areas of the REST Message. Simply create a Variable Substitution record and call the name in the line of code with ${} syntax (this syntax tells ServiceNow to reference the substitution as a parameter and can take in any value – test values will be used unless overridden)
  • Azure DevOps classes appear to have a $ in front of the name, so be aware that the $ sign must live within the coded

Scheduled Script

Now that the REST Message is set up and properly configured, we can use it! What we can do is monitor for requests coming from the ServiceNow service catalog and create the respective work item in Azure DevOps. The following script was implemented using ServiceNow’s handy Scheduled Script Execution mechanism within the Scheduled Job suite, on a periodic timer.

rest4-(1).png

The script below uses the RESTMessageV2 API within the ServiceNow platform so that we can call the REST Message when data requirements are met. One item to note is the Basic Authentication line of code requires a string data type for the username and password. The intent here was to never expose the password in plain text, so I retrieved the Basic Authentication Profile with a glide record query and decrypted the value which in turn becomes a string value for the API. See below:

gs.log('STARTING SN TO DEVOPS INTEGRATION...', 'SNDevOpsIntegration');
gs.log('Querying for basic auth profile', 'SNDevOpsIntegration');

var cred = new GlideRecord('sys_auth_profile_basic');
cred.addQuery('name', 'INSERT AUTH PROFILE NAME HERE');
cred.query();

if (cred.next())
{
    var username = cred.username.toString(); 
    var Encrypter = new GlideEncrypter();
    var password = Encrypter.decrypt(cred.password);
    gs.log('Found basic auth profile ' + username, 'SNDevOpsIntegration');
}

else
{
    gs.logError('No credential found for REST Message', 'SNDevOpsIntegration');
    throw "No credential found";
}

gs.log('Querying for RITMs', 'SNDevOpsIntegration');
var ritm = new GlideRecord('sc_req_item');
ritm.addQuery('short_description', 'App Request');
ritm.addQuery('state', 1); // Open
ritm.query();
gs.log('Count of RITMs set to: ' + ritm.getRowCount(), 'SNDevOpsIntegration');

while (ritm.next())
{

    gs.log('Processing: ' + ritm.number, 'SNDevOpsIntegration');
    var requestBody;
    var responseBody;
    var status;
    var sm;

    try
    {
        sm = new sn_ws.RESTMessageV2("INSERT REST MESSAGE NAME HERE", "post");  // Might throw exception if message doesn't exist or not visible due to scope.
        sm.setBasicAuth(username,password);
        //sm.setStringParameter("symbol", "NOW");
        sm.setStringParameterNoEscape("organization","INSERT ORG HERE");
        sm.setStringParameterNoEscape("project","INSERT PROJECT HERE");
        sm.setStringParameterNoEscape("title",ritm.short_description);
        sm.setStringParameterNoEscape("description",ritm.description);
        sm.setStringParameterNoEscape("type","$User%20Story"); // Requires $ for type
        sm.setHttpTimeout(10000); // milliseconds -- wait at most 10 seconds for response from http request
        response = sm.execute(); // might throw exception if http connection timed out or some issue with sending request itself because of encryption/decryption of password
        responseBody = response.haveError() ? response.getErrorMessage() : response.getBody();
        status = response.getStatusCode();
    }

    catch (ex)
    {
        responseBody = ex.getMessage();
        status = '500';
    }

    finally
    {
        requestBody = sm ? sm.getRequestBody():null;
    }

    gs.log("Request Body: " + requestBody, 'SNDevOpsIntegration');
    gs.log("Response: " + responseBody, 'SNDevOpsIntegration');
    gs.log("HTTP Status: " + status, 'SNDevOpsIntegration');

    // Add comment to RITM
    gs.log('Updating RITM', 'SNDevOpsIntegration');
    ritm.comments.setJournalEntry('User Story created in Azure DevOps for App Request', 'admin');
    ritm.state = 2; // Work In Progress
    ritm.update();

    gs.log('Finished updating RITM', 'SNDevOpsIntegration');
    gs.log('FINISHED SN TO DEVOPS INTEGRATION', 'SNDevOpsIntegration');
}

Conclusion

The Outbound web services capabilities within ServiceNow are endless and powerful, allowing the system to be used/extended in a variety of ways. What I explained was only one option for integration purposes, but the approach is customizable for any business need.

See below for a summary of what was built to send out a REST Message:

  • REST Message record with mandatory endpoint
  • Basic Authentication Profile
  • Post HTTP Method
    • Inherited authentication from parent
    • HTTP request
    • Patch JSON data for Content body
    • Variable Substitutions for dynamic values
      • description
      • organization
      • project
      • title
      • type

rest5-(1).png

Author

Michael Dugan

Systems Engineer