Eyeglass Solutions Publication
Eyeglass to ServiceNow API Integration for Cluster and Configuration Synchronization with ServiceNow CMDB
Home

 

 

Overview

ServiceNow CMDB integration allows cluster storage inventory to be synchronized into a custom CI schema to capture key properties of the cluster including hardware serial numbers, capacity, usage and SMB and NFS exports along with protection status and Quota usage.    Customers can now fully leverage ServiceNow ITSM with NAS storage. Eyeglass collects the inventory of PowerScale Cluster and populates an XML file name servicenow.xml. In this document we use that XML file as the input for importing into ServiceNow CMDB. 

 Support Statement

  1. NOTE:  This documentation is provided "as is" without support for 3rd party software.  The level of support for this integration guide is best effort without any SLA on response time.  No 3rd party product support can be provided by Superna directly.  3rd party components require support contracts

Capabilities

  1. This integration enables customers to create incidents against shares and exports
  2. Monitor cluster storage capacity and usage within ServiceNow
  3. Manage Maintenance windows to calculator business impacted services
  4. Link services and applications to IT resources based on dependencies to file storage
  5. Fully integrate cluster NAS storage into ITSM workflows with ServiceNow


 

Configuration Diagram

The diagram below shows the setup

 

 

 

 

Processes:

 

  1. Eyeglass collects PowerScale Cluster Inventory and populates servicenow.xml file
  2. Through the scheduler set in Eyeglass crontab, python scripts will perform the following:
    1. Convert servicenow.xml into 4 Excel files:
      1. Excel file for Cluster
      2. Excel file for Node
      3. Excel file for SMB Share
      4. Excel file for NFS Export
    2. By using REST API, the script will attach those Excel Files to ServiceNow Data Sources
  3. ServiceNow Integration Hub - Imports
    1. Schedule to automatically import from Data Sources
    2. Transform and Map to Customized Classes and populate the CMDB

 

 

 

Mapping Class

For this integration we are creating new customized CI Classes with the following structures:

  1. Parent Class: IGLS_PowerScale_Cluster
  2. Child Class-1: IGLS_PowerScale_Node (Member of - relationship to IGLS_PowerScale_Cluster)
  3. Child Class-2: IGLS_PowerScale_Share (Contained by - relationship to IGLS_PowerScale_Cluster)
  4. Child Class-3: IGLS_PowerScale_Export (Contained by - relationship to IGLS_PowerScale_Cluster)

 

Creating Customized CI Class Procedure

  1. Open CI Class Manager
  2. Click Hierarchy to expand the CI Classes list. Then select the class that the new class is extended from.
    1. For the new Parent Class IGLS_PowerScale_Cluster,  we create as a new Class directly under the top Configuration Item Parent Folder Structure
        1. For the new Children Classes (IGLS_PowerScale_Node, IGLS_PowerScale_Share, IGLS_PowerScale_Export), we create as new Classes under the new Parent Class IGLS_PowerScale_Cluster
    2. To create a new class, select the parent class (refer to point 2a and 2b) ,  right click and select  Add Child Class.

  1. The Add Child Class option appears only if the selected class is extendable. On the Provide Basic Info tab, fill out the information and then click Next
  2. Description of the fields:

 

FieldDescription
Display name
    1. A unique label for the class. Example: we can set it as IGLS_PowerScale_Cluster. The label appears on the list and forms views for the class.
Table name

Automatically populated based on the table label and the prefix string 'u_cmdb_ci'.

You cannot modify the prefix; however, you can modify the rest of the table name. The name can contain only lowercase, alphanumeric ASCII characters and underscores (_).

Maximum string length is 80 characters.

DescriptionExplanation of the purpose of the class.
IconThe icon associated with the class.
Extensible
    1. Indicator of whether this class can be extended. We enable this option for the Parent Class that we create - e.g. IGLS_PowerScale_Cluster is the Parent Class (Refer to the diagram above) . For the Children Classes (e.g. IGLS_PowerScale_Node, IGLS_PowerScale_Share, IGLS_PowerScale_Export) we can disable this option as we do not need to extend this class further
Principal ClassDenotes whether this class is included in the Principal Class filter. If this class is included in the Principal Class filter, then CIs from this class appear in CI list views when the Principal Class filter is applied. This is optional.
  1. Example:

 

 

  1. After we click the Next button, we can accept all the default options for the remaining configuration until it completes the creation of the new CI Class.
  2. Once the new CI Class is created, we can edit to modify the setting. For the Parent Class IGLS_PowerScale_Cluster, we need to add additional attributes.
  3. On the Add Attributes tab, click the + sign and enter details for each new class column. Then click Next.
  4.  

    Notes: We only need to add the following Attributes for the Parent Class IGLS_PowerScale Class (for the Children Classes we will use the inherited attributes from Parent Class:

 

AttributesTypesDescription
Cluster_NameStringPowerScale Cluster Name
Cluster_GUIDStringPowerScale Cluster GUID
Cluster_LocalSerialStringPowerScale Cluster Local Serial Number
Cluster_OFSv_BuildStringPowerScale Cluster OneFS version Build
Cluster_OFSv_ReleaseStringPowerScale Cluster OneFS version Release
Cluster_OFSv_TypeStringPowerScale Cluster OneFS version Type
Cluster_OFSv_VersionStringPowerScale Cluster OneFS version Version
Cluster_OFSv_RevisionIntegerPowerScale Cluster OneFS version Revision
Node_HealthStringPowerScale Node Health
Node_SerialNumberStringPowerScale Node Serial Number
Node_HDDsizeStringPowerScale Node HDD Size
Node_HDDusedStringPowerScale Node HDD Used
Node_HDDavailStringPowerScale Node HDD Avail
Share_NameStringPowerScale Share Name
Share_PathStringPowerScale Share Path
Share_Zone StringPowerScale Share Zone
Share_StatusStringPowerScale Share Status
Export_PathsStringPowerScale Export Paths
Export_ZoneStringPowerScale Export Zone
Export_StatusStringPowerScale Export Status

 

 

  1. For the Children Classes, we set the Relationship as follows in the Suggested Relationship section:

 

 

Child

Child Relationship to Parent

Parent

Parent Relationship to Child

IGLS_PowerScale_Node

Member of:IGLS_PowerScale_Cluster

Member:

IGLS_PowerScale_Share

Contained by:

IGLS_PowerScale_Cluster

Contain:

IGLS_PowerScale_ExportContained by:

IGLS_PowerScale_Cluster

Contain:

 

 

Example:

 

 

  1. Once we have completed the creation of all those 4 CI Classes, then we configure Integration Import Jobs

 

CI Class Relationship View

To view CI Class Relationship View between Parent and Children, we can click the Suggested Relationships menu from the Parent CI Class

 

Integration Import Configuration

 

 

  1. Open IntegrationHub - Import tool
  2. Specify the name of the Integration Data Import and provide the Short Description.
  3. We need to create 4 Integration Import jobs:

 

 

 Integration Import Name
1IGLS-PowerScale-Cluster
2IGLS-PowerScale-Node
3IGLS-PowerScale-Share
4IGLS-PowerScale-Export

 

 

  1. Example

 

 

  1. Specify the Source Type Excel File

 

 

  1. Select and Upload the initial Excel File manually

 

 

  1. Next step is to configure the mapping. In the Map to Target tab, click Add a Mapping Button

  1. Select the newly created Class as the target for this mapping

 

 

Integration Import Name

Mapping Target Class

IGLS-PowerScale-Cluster

IGLS_PowerScale_Cluster
IGLS-PowerScale-NodeIGLS_PowerScale_Node
IGLS-PowerScale-ShareIGLS_PowerScale_Share
IGLS-PowerScale-ExportIGLS_PowerScale_Export

 

 

  1. Click the Target table data mapping name from this UI to open the Mapping Configuration window

 

 

  1. Configure the Mapping and Save the mapping configuration

 

Mapping:

Integration Import: IGLS-PowerScale-Cluster

 

TargetMatch (Enable / Disable)*Source
nameEnablename
Cluster_NameDisable (Default)name
Cluster_GUIDDisable (Default)guid
Cluster_LocalSerialDisable (Default)local_serial
Cluster_OFSv_BuildDisable (Default)build
Cluster_OFSv_ReleaseDisable (Default)release
Cluster_OFSv_TypeDisable (Default)type
Cluster_OFSv_VersionDisable (Default)version
Cluster_OFSv_RevisionDisable (Default)revision

 

Integration Import: IGLS-PowerScale-Node

 

TargetMatch (Enable / Disable)*Source
nameEnablename\serial_number
Cluster_NameDisable (Default)name
Cluster_GUIDDisable (Default)guid
Node_HealthDisable (Default)health
Node_SerialNumberDisable (Default)serial_number
Node_HDDsizeDisable (Default)HDD_size
Node_HDDusedDisable (Default)HDD_used
Node_HDDavailDisable (Default)HDD_avail

 

Integration Import: IGLS-PowerScale-Share

 

TargetMatch (Enable / Disable)*Source
nameEnablename\zone\name2
Cluster_NameDisable (Default)name
Cluster_GUIDDisable (Default)guid
Share_NameDisable (Default)name2
Share_PathDisable (Default)path
Share_Zone Disable (Default)zone
Share_StatusDisable (Default)status

 

Integration Import: IGLS-PowerScale-Export

 

TargetMatch (Enable / Disable)*Source
nameEnablename\zone3\paths
Cluster_NameDisable (Default)name
Cluster_GUIDDisable (Default)guid
Export_Pathspathspaths
Export_Zonezone3zone3
Export_Statusstatus4status4

 

*Match Toggle Switch:

Toggle switch to specify fields where the system should check for matching data and, if found, update existing records instead of creating new ones.

Once completed, click the Save button to save the configuration.

 

  1. Set Import Schedule

 

Example: run periodically - every hour

 

 

 

Data Source Table Name and Table Sys ID

 

For each ServiceNow Data Source we need to get the Table Sys ID for attaching the Excel file into the corresponding Data Source.

 

To get the Table Sys ID, we can copy that sys id from Data Source Menu and then select  => Copy sys id

 

 

 

 

ServiceNow CMDB

CMDB CI Search

For searching CIs from ServiceNow CMDB:

 

  1. Switch to the CMDB Workspace view

 

 

  1. Specify CI Search Criteria and click the Search button
    1. Class
    2. Field
    3. Operator
    4. Value
    5. Example:

 

 

  1. Example of CI Search result:

 

 

  1. We can customize the column  view of the result. Click Edit Columns button from the menu

 

 

  1. Select columns to view, and then click the OK button

 

  1. Example:

 

 

 

CMDB Customized View

For setting CMDB Customized View:

 

  1. Switch to the CMDB Workspace view

 

 

  1. Click the List button from the left hand side of the CMDB menu, and click My Lists tab

 

 

  1. Click Add New List button at the bottom of the CMDB workspace

 

 

  1. We can create a new list from existing or create our own. Example for creating a new list based on our own settings. Specify List Name, Source (CMDB table) , Columns. Then click Create button.

 

 

  1. We can see our list result. Example:

 

 

 

Eyeglass Configuration

 

Prerequisites

  1. Configure servicenow output directory
    1. login to eyeglass VM
    2. mv /srv/www/htdocs/{servicenow} /srv/www/htdocs/servicenow 
  2. Install additional python modules
    1. login as eyeglass admin user
    2. pip install pandas 
    3. pip install openpyxl 


The following components are configured in Eyeglass

  1. XML to Excel format Conversion
  2. Attach Excel files to ServiceNow Data Sources
  3. Schedule jobs through Crontabs

XML to Excel format Conversion

We need to convert the servicenow.xml file into 4 Excel files:

  • Excel file for Cluster
  • Excel file for Node
  • Excel file for SMB Share
  • Excel file for NFS Export

 

We use the following python scripts to perform that tasks:


 

Python Scripts

Task

igls-convert-cluster.py

Convert servicenow.xml ⇒ igls-powerscale-cluster.xlsx

igls-convert-node.py

Convert servicenow.xml ⇒ igls-powerscale-node.xlsx

igls-convert-share.py

Convert servicenow.xml ⇒ igls-powerscale-share.xlsx

igls-convert-export.py

Convert servicenow.xml ⇒ igls-powerscale-export.xlsx

 

 

We place those python scripts in /home/admin directory and will be run as admin user

 

igls-convert-cluster.py

 

===============

 

import xml.etree.ElementTree as ET

import pandas as pd

 

# Define the file path for the XML data

xml_file_path = '/srv/www/htdocs/servicenow/servicenow.xml'  # Replace with the actual file path

 

# Parse the XML data from the file

tree = ET.parse(xml_file_path)

root = tree.getroot()

 

# Initialize lists to store data

clusters = []

 

# Extract data from XML

for element in root.findall('.//element'):

    cluster_name = element.find('.//cluster/name').text

    cluster_guid = element.find('.//cluster/guid').text

    cluster_local_serial = element.find('.//cluster/local_serial').text

    cluster_type = element.find('.//cluster/onefs_version/type').text

    cluster_release = element.find('.//cluster/onefs_version/release').text

    cluster_build = element.find('.//cluster/onefs_version/build').text

    cluster_version = element.find('.//cluster/onefs_version/version').text

    cluster_revision = element.find('.//cluster/onefs_version/revision').text

 

    clusters.append([cluster_name, cluster_guid, cluster_local_serial, cluster_type, cluster_release, cluster_build, cluster_version, cluster_revision])

 

# Create a DataFrame from the extracted data

df = pd.DataFrame(clusters, columns=['name', 'guid', 'local_serial', 'type', 'release', 'build', 'version', 'revision'])

 

# Save the DataFrame to an Excel file

output_file = '/home/admin/igls-powerscale-cluster.xlsx'

df.to_excel(output_file, index=False)

 

print(f'Data saved to {output_file}')

 

==============

 

igls-convert-node.py

 

 

========================

 

import xml.etree.ElementTree as ET

import pandas as pd

 

# Define the file path for the XML data

xml_file_path = '/srv/www/htdocs/servicenow/servicenow.xml'  # Replace with the actual file path

 

# Parse the XML data from the file

tree = ET.parse(xml_file_path)

root = tree.getroot()

 

# Initialize lists to store data

clusters = []

nodes = []

 

# Extract data from XML

for element in root.findall('.//element'):

    cluster_name = element.find('.//cluster/name').text

    cluster_guid = element.find('.//cluster/guid').text

    cluster_local_serial = element.find('.//cluster/local_serial').text

    cluster_type = element.find('.//cluster/onefs_version/type').text

    cluster_release = element.find('.//cluster/onefs_version/release').text

    cluster_build = element.find('.//cluster/onefs_version/build').text

    cluster_version = element.find('.//cluster/onefs_version/version').text

    cluster_revision = element.find('.//cluster/onefs_version/revision').text

    node_elements = element.findall('.//nodes/node')

    

    for node_element in node_elements:

        node_serialnumber = node_element.find('serial_number').text

        node_health = node_element.find('health').text

        node_hddsize = node_element.find('HDD_size').text

        node_hddused = node_element.find('HDD_used').text

        node_hddavail = node_element.find('HDD_avail').text

        

        nodes.append([cluster_name, cluster_guid, node_serialnumber, node_health, node_hddsize, node_hddused, node_hddavail])

    

 

# Create a DataFrame from the extracted data

df = pd.DataFrame(nodes, columns=['name', 'guid', 'serial_number', 'health', 'HDD_size', 'HDD_used', 'HDD_avail'])

 

# Save the DataFrame to an Excel file

output_file = '/home/admin/igls-powerscale-node.xlsx'

df.to_excel(output_file, index=False)

 

print(f'Data saved to {output_file}')

 

 

========================

 

igls-convert-share.py

 

===================

 

import xml.etree.ElementTree as ET

import pandas as pd

 

# Define the file path for the XML data

xml_file_path = '/srv/www/htdocs/servicenow/servicenow.xml'  # Replace with the actual file path

 

# Parse the XML data from the file

tree = ET.parse(xml_file_path)

root = tree.getroot()

 

# Initialize lists to store data

clusters = []

shares = []

 

# Extract data from XML

for element in root.findall('.//element'):

    cluster_name = element.find('.//cluster/name').text

    cluster_guid = element.find('.//cluster/guid').text

    cluster_local_serial = element.find('.//cluster/local_serial').text

    cluster_type = element.find('.//cluster/onefs_version/type').text

    cluster_release = element.find('.//cluster/onefs_version/release').text

    cluster_build = element.find('.//cluster/onefs_version/build').text

    cluster_version = element.find('.//cluster/onefs_version/version').text

    cluster_revision = element.find('.//cluster/onefs_version/revision').text

    share_elements = element.findall('.//shares/share')

    

       

    for share_element in share_elements:

        share_name = share_element.find('name').text

        share_status = share_element.find('status').text

        share_path = share_element.find('path').text

        share_zone = share_element.find('zone').text

            

        shares.append([cluster_name, cluster_guid, cluster_type, share_name, share_status, share_path, share_zone])

 

# Create a DataFrame from the extracted data

df = pd.DataFrame(shares, columns=['name', 'guid', 'type', 'name2', 'status', 'path', 'zone'])

 

# Save the DataFrame to an Excel file

output_file = '/home/admin/igls-powerscale-share.xlsx'

df.to_excel(output_file, index=False)

 

print(f'Data saved to {output_file}')

 

 

===================

 

 

igls-convert-export.py

 

===================

 

import xml.etree.ElementTree as ET

import pandas as pd

 

# Define the file path for the XML data

xml_file_path = '/srv/www/htdocs/servicenow/servicenow.xml'  # Replace with the actual file path

 

# Parse the XML data from the file

tree = ET.parse(xml_file_path)

root = tree.getroot()

 

# Initialize lists to store data

clusters = []

exports = []

 

# Extract data from XML

for element in root.findall('.//element'):

    cluster_name = element.find('.//cluster/name').text

    cluster_guid = element.find('.//cluster/guid').text

    cluster_local_serial = element.find('.//cluster/local_serial').text

    cluster_type = element.find('.//cluster/onefs_version/type').text

    cluster_release = element.find('.//cluster/onefs_version/release').text

    cluster_build = element.find('.//cluster/onefs_version/build').text

    cluster_version = element.find('.//cluster/onefs_version/version').text

    cluster_revision = element.find('.//cluster/onefs_version/revision').text

    export_elements = element.findall('.//exports/export')

    

    for export_element in export_elements:

        export_paths = export_element.find('paths').text

        export_status = export_element.find('status').text

        export_zone = export_element.find('zone').text

            

        exports.append([cluster_name, cluster_guid, cluster_type, export_paths, export_status, export_zone])

 

# Create a DataFrame from the extracted data

df = pd.DataFrame(exports, columns=['name', 'guid', 'type', 'paths', 'status3', 'zone4'])

 

# Save the DataFrame to an Excel file

output_file = '/home/admin/igls-powerscale-export.xlsx'

df.to_excel(output_file, index=False)

 

print(f'Data saved to {output_file}')

 

 

===================

 

 

 

 

Attach Excel files to ServiceNow Data Sources

Once the Excel files are generated, the next step is to attach those files to ServiceNow Data Sources by using the REST API (attachment) commands.

 

 

We use the following python scripts to perform that tasks:

 

 

 

 

Python ScriptsTask
igls-snapi-cluster.pyAttach the igls-powerscale-cluster.xlsx to ServiceNow IGLS-PowerScale-Cluster Data Source
igls-snapi-node.pyAttach the igls-powerscale-node.xlsx  to ServiceNow IGLS-PowerScale-Node Data Source
igls-snapi-share.pyAttach the igls-powerscale-share.xlsx  to ServiceNow IGLS-PowerScale-Share Data Source
igls-snapi-export.pyAttach the igls-powerscale-export.xlsx  to ServiceNow IGLS-PowerScale-Export Data Source

 

 

 

 

We place those python scripts in  /home/admin directory and will be run as admin user.

 

 

igls-snapi-cluster.py

 

=========

 

import requests

 

# Replace these with your own ServiceNow instance details and credentials

instance_url = "https://<servicenow-instance-name>.service-now.com"  # Specify ServiceNow instance URL

username = "<servicenow-user-api>"    # specify user to execute this api

password = "<servicenow-password-api>"  # specify user password to execute this api

 

# Set the URL for the ServiceNow REST API

rest_api_url = f"{instance_url}/api/now/attachment"

 

# Specify the table and table_sys_id where you want to attach the file

table_name = "sys_data_source"  # Replace with the target table name

table_sys_id = "<specify-target-table-sys-id>"  # Replace with the target table sys_id

 

# Specify the file you want to attach

file_name = "igls-powerscale-cluster.xlsx"

file_path = f"/home/admin/{file_name}"

 

# Create a session for making authenticated requests

session = requests.Session()

session.auth = (username, password)

 

# Set proper headers

headers = {"Content-Type":"application/json","Accept":"application/json"}

 

# Set sysparm_limit

sysparm_query = f"file_name={file_name}"

 

# Check if an existing attachment exists and delete it

check_url = f'{rest_api_url}?sysparm_limit=2&sysparm_query={sysparm_query}'

existing_attachment_response = session.get(check_url, headers=headers)

 

if existing_attachment_response.status_code == 200:

    existing_attachments = existing_attachment_response.json().get("result")

    

    # Delete existing attachments

    for attachment in existing_attachments:

        if attachment.get("table_sys_id") == table_sys_id:

            sys_id = attachment.get("sys_id")

            delete_attachment_url = f"{rest_api_url}/{sys_id}"

            delete_response = session.delete(delete_attachment_url)

        

            if delete_response.status_code == 204:

                print(f"Existing attachment with sys_id {sys_id} deleted.")

            else:

                print(f"Failed to delete existing attachment with sys_id {sys_id}. Status code: {delete_response.status_code}")

else:

    print(f"Failed to check for existing attachment. Status code: {existing_attachment_response.status_code}")

    print(existing_attachment_response.text)

 

# Send a POST request to upload the new file

new_attachment_url = f"{rest_api_url}/file?table_name={table_name}&table_sys_id={table_sys_id}&file_name={file_name}"

 

data = open(f'{file_path}', 'rb').read()

 

response = session.post(new_attachment_url, headers=headers, data=data)

 

# Check if the request was successful

if response.status_code == 201:

    attachment_info = response.json().get("result")

    sys_id = attachment_info.get("sys_id")

    print(f"Attachment uploaded successfully. sys_id: {sys_id}")

else:

    print(f"Failed to upload attachment. Status code: {response.status_code}")

    print(response.text)

 

 

=========

 

 

 

 

igls-snapi-node.py

 

 

=========

 

import requests

 

# Replace these with your own ServiceNow instance details and credentials

instance_url = "https://<servicenow-instance-name>.service-now.com"  # Specify ServiceNow instance URL

username = "<servicenow-user-api>"    # specify user to execute this api

password = "<servicenow-password-api>"  # specify user password to execute this api

 

# Set the URL for the ServiceNow REST API

rest_api_url = f"{instance_url}/api/now/attachment"

 

# Specify the table and table_sys_id where you want to attach the file

table_name = "sys_data_source"  # Replace with the target table name

table_sys_id = "<specify-target-table-sys-id>"  # Replace with the target table sys_id

 

# Specify the file you want to attach

file_name = "igls-powerscale-node.xlsx"

file_path = f"/home/admin/{file_name}"

 

# Create a session for making authenticated requests

session = requests.Session()

session.auth = (username, password)

 

# Set proper headers

headers = {"Content-Type":"application/json","Accept":"application/json"}

 

# Set sysparm_limit

sysparm_query = f"file_name={file_name}"

 

# Check if an existing attachment exists and delete it

check_url = f'{rest_api_url}?sysparm_limit=2&sysparm_query={sysparm_query}'

existing_attachment_response = session.get(check_url, headers=headers)

 

if existing_attachment_response.status_code == 200:

    existing_attachments = existing_attachment_response.json().get("result")

    

    # Delete existing attachments

    for attachment in existing_attachments:

        if attachment.get("table_sys_id") == table_sys_id:

            sys_id = attachment.get("sys_id")

            delete_attachment_url = f"{rest_api_url}/{sys_id}"

            delete_response = session.delete(delete_attachment_url)

        

            if delete_response.status_code == 204:

                print(f"Existing attachment with sys_id {sys_id} deleted.")

            else:

                print(f"Failed to delete existing attachment with sys_id {sys_id}. Status code: {delete_response.status_code}")

else:

    print(f"Failed to check for existing attachment. Status code: {existing_attachment_response.status_code}")

    print(existing_attachment_response.text)

 

# Send a POST request to upload the new file

new_attachment_url = f"{rest_api_url}/file?table_name={table_name}&table_sys_id={table_sys_id}&file_name={file_name}"

 

data = open(f'{file_path}', 'rb').read()

 

response = session.post(new_attachment_url, headers=headers, data=data)

 

# Check if the request was successful

if response.status_code == 201:

    attachment_info = response.json().get("result")

    sys_id = attachment_info.get("sys_id")

    print(f"Attachment uploaded successfully. sys_id: {sys_id}")

else:

    print(f"Failed to upload attachment. Status code: {response.status_code}")

    print(response.text)

 

 

=========

 

 

 

igls-snapi-share.py

 

 

========

 

import requests

 

# Replace these with your own ServiceNow instance details and credentials

instance_url = "https://<servicenow-instance-name>.service-now.com"  # Specify ServiceNow instance URL

username = "<servicenow-user-api>"    # specify user to execute this api

password = "<servicenow-password-api>"  # specify user password to execute this api

 

# Set the URL for the ServiceNow REST API

rest_api_url = f"{instance_url}/api/now/attachment"

 

# Specify the table and table_sys_id where you want to attach the file

table_name = "sys_data_source"  # Replace with the target table name

table_sys_id = "<specify-target-table-sys-id>"  # Replace with the target table sys_id

 

# Specify the file you want to attach

file_name = "igls-powerscale-share.xlsx"

file_path = f"/home/admin/{file_name}"

 

# Create a session for making authenticated requests

session = requests.Session()

session.auth = (username, password)

 

# Set proper headers

headers = {"Content-Type":"application/json","Accept":"application/json"}

 

# Set sysparm_limit

sysparm_query = f"file_name={file_name}"

 

# Check if an existing attachment exists and delete it

check_url = f'{rest_api_url}?sysparm_limit=2&sysparm_query={sysparm_query}'

existing_attachment_response = session.get(check_url, headers=headers)

 

if existing_attachment_response.status_code == 200:

    existing_attachments = existing_attachment_response.json().get("result")

    

    # Delete existing attachments

    for attachment in existing_attachments:

        if attachment.get("table_sys_id") == table_sys_id:

            sys_id = attachment.get("sys_id")

            delete_attachment_url = f"{rest_api_url}/{sys_id}"

            delete_response = session.delete(delete_attachment_url)

        

            if delete_response.status_code == 204:

                print(f"Existing attachment with sys_id {sys_id} deleted.")

            else:

                print(f"Failed to delete existing attachment with sys_id {sys_id}. Status code: {delete_response.status_code}")

else:

    print(f"Failed to check for existing attachment. Status code: {existing_attachment_response.status_code}")

    print(existing_attachment_response.text)

 

# Send a POST request to upload the new file

new_attachment_url = f"{rest_api_url}/file?table_name={table_name}&table_sys_id={table_sys_id}&file_name={file_name}"

 

data = open(f'{file_path}', 'rb').read()

 

response = session.post(new_attachment_url, headers=headers, data=data)

 

# Check if the request was successful

if response.status_code == 201:

    attachment_info = response.json().get("result")

    sys_id = attachment_info.get("sys_id")

    print(f"Attachment uploaded successfully. sys_id: {sys_id}")

else:

    print(f"Failed to upload attachment. Status code: {response.status_code}")

    print(response.text)

 

 

=========

 

 

 

 

igls-snapi-export.py

 

 

=====

 

import requests

 

# Replace these with your own ServiceNow instance details and credentials

instance_url = "https://<servicenow-instance-name>.service-now.com"  # Specify ServiceNow instance URL

username = "<servicenow-user-api>"    # specify user to execute this api

password = "<servicenow-password-api>"  # specify user password to execute this api

 

# Set the URL for the ServiceNow REST API

rest_api_url = f"{instance_url}/api/now/attachment"

 

# Specify the table and table_sys_id where you want to attach the file

table_name = "sys_data_source"  # Replace with the target table name

table_sys_id = "<specify-target-table-sys-id>"  # Replace with the target table sys_id

 

# Specify the file you want to attach

file_name = "igls-powerscale-export.xlsx"

file_path = f"/home/admin/{file_name}"

 

# Create a session for making authenticated requests

session = requests.Session()

session.auth = (username, password)

 

# Set proper headers

headers = {"Content-Type":"application/json","Accept":"application/json"}

 

# Set sysparm_limit

sysparm_query = f"file_name={file_name}"

 

# Check if an existing attachment exists and delete it

check_url = f'{rest_api_url}?sysparm_limit=2&sysparm_query={sysparm_query}'

existing_attachment_response = session.get(check_url, headers=headers)

 

if existing_attachment_response.status_code == 200:

    existing_attachments = existing_attachment_response.json().get("result")

    

    # Delete existing attachments

    for attachment in existing_attachments:

        if attachment.get("table_sys_id") == table_sys_id:

            sys_id = attachment.get("sys_id")

            delete_attachment_url = f"{rest_api_url}/{sys_id}"

            delete_response = session.delete(delete_attachment_url)

        

            if delete_response.status_code == 204:

                print(f"Existing attachment with sys_id {sys_id} deleted.")

            else:

                print(f"Failed to delete existing attachment with sys_id {sys_id}. Status code: {delete_response.status_code}")

else:

    print(f"Failed to check for existing attachment. Status code: {existing_attachment_response.status_code}")

    print(existing_attachment_response.text)

 

# Send a POST request to upload the new file

new_attachment_url = f"{rest_api_url}/file?table_name={table_name}&table_sys_id={table_sys_id}&file_name={file_name}"

 

data = open(f'{file_path}', 'rb').read()

 

response = session.post(new_attachment_url, headers=headers, data=data)

 

# Check if the request was successful

if response.status_code == 201:

    attachment_info = response.json().get("result")

    sys_id = attachment_info.get("sys_id")

    print(f"Attachment uploaded successfully. sys_id: {sys_id}")

else:

    print(f"Failed to upload attachment. Status code: {response.status_code}")

    print(response.text)

 

 

 

 

=====

 

 

 

Schedule jobs through Crontabs

We combine all the python scripts into a single python script for easier scheduling.

Example: servicenow.py

 

=================

import subprocess

 

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-convert-cluster.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-convert-node.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-convert-share.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-convert-export.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-snapi-cluster.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-snapi-node.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-snapi-share.py", shell=True)

subprocess.run("/opt/.pyenv/shims/python3 /home/admin/igls-snapi-export.py", shell=True)

 

==============================

 

As admin user, create a crontab

crontab -e

 

Example to schedule to run every 30 minutes

30 * * * * /opt/.pyenv/shims/python3 /home/admin/servicenow.py 2>&1 > /home/admin/mycronout.log

 

 

© Superna Inc