What's new: Automating ServiceNow with Ansible Automation Platform
Tags: ansible, ansiblecollections, servicenow, automation
Published: 2021-10-13 13:17:55

**WORDS BY** Tadej Borovšak

It has been almost half a year since the [XLAB Steampunk] (http://steampunk.si/) and Red Hat Ansible Automation Platform teams developed the first version of the [Red Hat Ansible Certified Content Collection for ServiceNow IT Service Management (ITSM)] (https://steampunk.si/ansible-collections/servicenow/). You may also want to read our [ServiceNow introduction] (https://www.ansible.com/blog/certified-collection-servicenow) if you are not familiar with this already.  So, let’s take a look at what is new since the last release.

We will skip most of the technical details in this post because, let us face it, talking about interactions between API pagination and filtering is not something many people enjoy. Instead, we will focus on the new things users can do using the current 1.2.0 version of this collection.

#Attachment management
ServiceNow records (incidents, problems, change requests, etc.) may contain attachments, but the first version of the ServiceNow Collection did not expose this capability to Ansible Automation Platform users. We changed this in version 1.2.0 when we added the attachments parameter to all non-info modules.

Ansible users can now upload error logs and other artifacts when creating new incidents and other ServiceNow records. They can also add them later to existing ServiceNow records. For example, users might want to attach root cause analysis results when changing the state of the problem.

In the simplest case, attaching files to a record means listing them under the attachments parameter. The following example contains one such playbook task:

```
- name: Attach a file to a problem
  servicenow.itsm.problem:
    number: INC0123456
    attachments:
      - path: /path/to/screenshot.png
      - path: /path/to/log.txt
```

We have also checked to ensure that change detection still works. If a file with a matching name is not yet attached to the record, we upload it. If the attachment already exists in ServiceNow, we only update it if the local and remote files differ.

It is also possible to rename the file during the upload by specifying the name of the remote file. And in cases where Ansible cannot detect the file type from the extension, we can also supply that information manually:

```
- name: Attach a file to a problem
  servicenow.itsm.problem:
    number: INC0123456
    attachments:
      - path: /path/to/error.log
        type: text/plain
        name: specific-error.log
 ```

#Filtering records
Until now, info modules from this ServiceNow collection operated in an all-or-nothing fashion. For example, users could retrieve a single record or list all records. And so, in this 1.2.0 release, we have made things more flexible.  

All info modules gained a new query parameter that allows users to retrieve a subset of records. Users write the query expression using a domain-specific language (DSL) that exposes advanced filtering capabilities in a user-friendly way. The query DSL supports all operators listed in the [ServiceNow documentation] (https://docs.servicenow.com/bundle/quebec-platform-user-interface/page/use/common-ui-elements/reference/r_OpAvailableFiltersQueries.html).

For example, the next task will retrieve all incidents that contain SAP in their short description:

```
- servicenow.itsm.incident_info:
    query:
      - short_description: LIKE SAP
```

It is also possible to combine multiple conditions. The following sample shows how users could fetch only SAP incidents that the admins are already working on:

```
- servicenow.itsm.incident_info:
    query:
      - short_description: LIKE SAP
        state: = in_progress
```

Query DSL also supports combining multiple filters into one. So, for example, if users would like to retrieve resolved problems reported by two different people, we can do it in one task:

```
- servicenow.itsm.incident_info:
    query:
      - caller: = person.one
        state: = resolved
      - caller: = person.two
        state: = resolved
 ```

#Constructed inventory features
The first version of the inventory plugin offered quite a few options for filtering and grouping, but there were two areas it did not address:

1. It did not allow us to construct custom variables from the data in the Configuration Management Database (CMDB).
2. Its configuration options were slightly different from those that the majority of other inventory plugins use.

So we adopted the [standard options] (https://docs.ansible.com/ansible/latest/plugins/inventory.html#using-inventory-plugins) while adding one ServiceNow-specific additional option. This additional option allows Ansible users to filter CMDB items using the same query DSL we discussed previously.

This is a relatively short example that demonstrates most of the options would look like:

```
---
plugin: servicenow.itsm.now
table: cmdb_ci_ec2_instance
columns:
  - ip_address
  - name
  - vm_inst_id
query:
  - operational_status: = 1
    fqdn: ISNOTEMPTY
    manufacturer: STARTSWITH Cisco
keyed_groups:
  - key: environment
    prefix: env_
    separator: ""
groups:
  development: "'devel' in environment"
compose:
  ansible_host: fqdn
```

For more information about available options, you can consult the [constructed reference documentation] (https://docs.ansible.com/ansible/latest/collections/ansible/builtin/constructed_inventory.html#ansible-collections-ansible-builtin-constructed-inventory).

#Updating multiple CMDB items at once
The newly introduced batch module does not add new capabilities. Users can update as many CMDB items as they want using the existing servicenow.itsm.configuration_item  module and Ansible looping. Considering that looping in Ansible can be slower and that each loop iteration needs to establish a new ServiceNow connection, we made some optimizations. 

For example, this is the "old" way of updating a specific configuration item:

```
---
- name: Update a single CMDB item
  hosts: localhost
  gather_facts: false

  vars:
    dataset:
      - id: i-0af01c0123456789a
        address_ipv4: 1.2.3.4
        mac: aa:bb:cc:dd:ee:ff

  tasks:
    - name: Retrieve specific CMDB item
      servicenow.itsm.configuration_item_info:
        sys_class_name: cmdb_ci_ec2_instance
        query:
          - vm_inst_id: = {{ dataset.0.id }}
      register: vm

    - name: Make sure we only grabbed one instance
      ansible.builtin.assert:
        that:
          - vm.records | length == 1

    - name: Update CMDB item with fresh data
      servicenow.itsm.configuration_item:
        sys_class_name: cmdb_ci_ec2_instance
        sys_id: "{{ vm.records.0.sys_id }}"
        ip_address: "{{ dataset.0.address_ipv4 }}"
        mac_address: "{{ dataset.0.mac }}"
```

With the new batch module, users can write this using a single task:

```
---
- name: Update a single CMDB item
  hosts: localhost
  gather_facts: false

  vars:
    dataset:
      - id: i-0af01c0123456789a
        address_ipv4: 1.2.3.4
        mac: aa:bb:cc:dd:ee:ff

  tasks:
    - name: Update data in CMDB
      servicenow.itsm.configuration_item_batch:
        sys_class_name: cmdb_ci_ec2_instance
        id_column_set: vm_inst_id
        dataset: "{{ dataset }}"
        map:
          vm_inst_id: id
          ip_address: address_ipv4
          mac_address: mac
```

Updating multiple items using the "old" method will most likely need some clever tricks like looping over the `include_tasks` invocation. However, users do not require such tricks when using the batch module. They can add as many items they need to the dataset and call it a day.

#Combining things for the win
Filtering support in info modules is useful on its own, and working with transformations in the batch module can be surprisingly effective. But this is not the primary reason why we added those individual features. The real power is realized when we start combining them.

For example, let us have a look at the following playbook that will refresh information about the AWS EC2 instances stored in the ServiceNow CMDB that do not have IP address stored:

```
---
- name: Update a single CMDB item
  hosts: localhost
  gather_facts: false

  vars:
    dataset:
      - id: i-0af01c0123456789a
        address_ipv4: 1.2.3.4
        mac: aa:bb:cc:dd:ee:ff

  tasks:
    - name: Retrieve specific CMDB item
      servicenow.itsm.configuration_item_info:
        sys_class_name: cmdb_ci_ec2_instance
        query:
          - vm_inst_id: = {{ dataset.0.id }}
      register: vm

    - name: Make sure we only grabbed one instance
      ansible.builtin.assert:
        that:
          - vm.records | length == 1

    - name: Update CMDB item with fresh data
      servicenow.itsm.configuration_item:
        sys_class_name: cmdb_ci_ec2_instance
        sys_id: "{{ vm.records.0.sys_id }}"
        ip_address: "{{ dataset.0.address_ipv4 }}"
        mac_address: "{{ dataset.0.mac }}"
```

With the new batch module at their disposal, users can write this using a single task:

```
---
- name: Update CMDB data
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Get all EC2 instances with no IP address
      servicenow.itsm.configuration_item_info:
        sys_class_name: cmdb_ci_ec2_instance
        query:
          - ip_address: ISEMPTY
      register: snow_instances

    - name: Fetch information about instances from AWS
      amazon.aws.ec2_instance_info:
        instance_ids: "{{ snow_instances | map(attribute='vm_inst_id') | list }}"
      register: ec2_instances

    - name: Update CMDB with data from AWS
      servicenow.itsm.configuration_item_batch:
        sys_class_name: cmdb_ci_ec2_instance
        id_column_set: vm_inst_id
        dataset: "{{ ec2_instances.instances }}"
        map:
          vm_inst_id: instance_id
          ip_address: public_ip_adress
          name: tags.Name
```

And so, now you are aware of the newest capabilities in this collection.  Give them a try.  You can download this new certified collection in the [automation hub] (https://console.redhat.com/ansible/automation-hub) if you subscribe to Ansible Automation Platform. If you want to learn more about these features and see how you can drive your automation by integrating Ansible Automation Platform into your existing ServiceNow environment, sign up for [upcoming webinar] (https://alps.devoteam.com/event/devoteam-xlab-steampunk-ansible-automation-for-servicenow/) organized in collaboration with Devoteam.