Introduction

ServiceNow is the dominant IT Service Management (ITSM) platform in enterprise environments. Integrating Ansible with ServiceNow creates closed-loop automation: incidents trigger remediation playbooks, change requests auto-execute approved changes, and the CMDB stays in sync with actual infrastructure state. The servicenow.itsm collection provides native Ansible modules for the full ServiceNow API.

Install the ServiceNow Collection

ansible-galaxy collection install servicenow.itsm

# Verify
ansible-galaxy collection list servicenow.itsm

Connection Setup

# group_vars/all.yml
servicenow_instance: "{{ vault_snow_instance }}"  # e.g., dev12345.service-now.com
servicenow_username: "{{ vault_snow_username }}"
servicenow_password: "{{ vault_snow_password }}"
# Or use environment variables
# SN_HOST=https://dev12345.service-now.com
# SN_USERNAME=admin
# SN_PASSWORD=secret

Create Incidents Automatically

---
- name: Auto-create incident from monitoring alert
  hosts: localhost
  gather_facts: false
  vars:
    alert_host: "{{ alert_hostname | default('web-server-01') }}"
    alert_message: "{{ alert_msg | default('Service unresponsive') }}"

  tasks:
    - name: Create incident in ServiceNow
      servicenow.itsm.incident:
        state: new
        caller: "ansible-automation"
        short_description: "[AUTO] {{ alert_message }} on {{ alert_host }}"
        description: |
          Automated incident created by Ansible monitoring integration.

          Host: {{ alert_host }}
          Alert: {{ alert_message }}
          Time: {{ ansible_date_time.iso8601 | default(lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ')) }}
          Playbook: {{ ansible_play_name }}
        impact: high
        urgency: high
        category: "Software"
        subcategory: "Operating System"
        assignment_group: "Linux Support"
      register: incident

    - name: Display incident number
      ansible.builtin.debug:
        msg: "Created incident: {{ incident.record.number }}"

    - name: Attempt automated remediation
      ansible.builtin.include_role:
        name: "remediate_{{ alert_type | default('service_restart') }}"
      vars:
        target_host: "{{ alert_host }}"
      register: remediation
      ignore_errors: true

    - name: Update incident with remediation result
      servicenow.itsm.incident:
        number: "{{ incident.record.number }}"
        state: "{{ 'resolved' if remediation is succeeded else 'in_progress' }}"
        close_code: "{{ 'Solved (Permanently)' if remediation is succeeded else omit }}"
        close_notes: "{{ 'Auto-remediated by Ansible' if remediation is succeeded else omit }}"
        work_notes: |
          Automated remediation {{ 'succeeded' if remediation is succeeded else 'failed' }}.
          {% if remediation is failed %}
          Manual intervention required. Escalating to on-call engineer.
          {% endif %}

Manage Change Requests

- name: Create and execute change request
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Create change request
      servicenow.itsm.change_request:
        state: new
        type: standard
        short_description: "Patch {{ target_servers | length }} servers — {{ patch_date }}"
        description: |
          Standard patching window for {{ environment }} servers.
          Servers: {{ target_servers | join(', ') }}
          Maintenance window: {{ patch_date }} {{ patch_time }}
        category: "Software"
        priority: "3 - Moderate"
        assignment_group: "Linux Support"
        risk: moderate
        impact: "3 - Low"
      register: change

    - name: Display change number
      ansible.builtin.debug:
        msg: "Change request: {{ change.record.number }}"

    - name: Wait for approval
      servicenow.itsm.change_request_info:
        number: "{{ change.record.number }}"
      register: change_status
      until: change_status.records[0].state == "scheduled"
      retries: 720
      delay: 60
      when: wait_for_approval | default(false)

    - name: Move to implement
      servicenow.itsm.change_request:
        number: "{{ change.record.number }}"
        state: implement

    - name: Execute patching
      ansible.builtin.include_role:
        name: patching
      vars:
        servers: "{{ target_servers }}"
      register: patch_result

    - name: Close change request
      servicenow.itsm.change_request:
        number: "{{ change.record.number }}"
        state: "{{ 'closed' if patch_result is succeeded else 'canceled' }}"
        close_code: "{{ 'successful' if patch_result is succeeded else 'unsuccessful' }}"
        close_notes: |
          Patching {{ 'completed successfully' if patch_result is succeeded else 'failed' }}.
          Servers patched: {{ target_servers | length }}

Sync CMDB with Inventory

- name: Sync infrastructure to ServiceNow CMDB
  hosts: all
  gather_facts: true
  tasks:
    - name: Update CMDB configuration item
      servicenow.itsm.configuration_item:
        name: "{{ inventory_hostname }}"
        sys_class_name: cmdb_ci_linux_server
        ip_address: "{{ ansible_default_ipv4.address }}"
        os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
        os_version: "{{ ansible_kernel }}"
        cpu_count: "{{ ansible_processor_vcpus }}"
        ram: "{{ ansible_memtotal_mb }}"
        serial_number: "{{ ansible_product_serial | default('N/A') }}"
        environment: "{{ env | default('production') }}"
        operational_status: operational
        attributes:
          ansible_managed: "true"
          last_scan: "{{ ansible_date_time.iso8601 }}"
          uptime_days: "{{ ansible_uptime_seconds | int // 86400 }}"
      delegate_to: localhost

- name: Discover and register new CIs
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Query CMDB for existing servers
      servicenow.itsm.configuration_item_info:
        query:
          - sys_class_name: = cmdb_ci_linux_server
            operational_status: = operational
      register: cmdb_servers

    - name: Find servers in Ansible but not CMDB
      ansible.builtin.set_fact:
        unregistered: "{{ groups['all'] | difference(cmdb_servers.records | map(attribute='name') | list) }}"

    - name: Report unregistered servers
      ansible.builtin.debug:
        msg: "Servers in Ansible inventory but NOT in CMDB: {{ unregistered }}"
      when: unregistered | length > 0

ServiceNow Dynamic Inventory

# servicenow_inventory.yml
plugin: servicenow.itsm.now
instance:
  host: "{{ lookup('env', 'SN_HOST') }}"
  username: "{{ lookup('env', 'SN_USERNAME') }}"
  password: "{{ lookup('env', 'SN_PASSWORD') }}"

table: cmdb_ci_linux_server
fields: [name, ip_address, os, environment, operational_status]

query:
  - operational_status: = operational
  - environment: = production

columns:
  ip_address: ansible_host
  os: os_type

groups:
  linux: "os_type == 'Linux'"
  web_servers: "'web' in name"
  db_servers: "'db' in name"

compose:
  ansible_host: ip_address
# Use ServiceNow as inventory source
ansible-inventory -i servicenow_inventory.yml --list
ansible-playbook -i servicenow_inventory.yml site.yml

Closed-Loop Remediation Workflow

- name: Closed-loop monitoring → SNOW → remediation
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Query open incidents assigned to automation
      servicenow.itsm.incident_info:
        query:
          - state: = in_progress
            assignment_group: = "Ansible Automation"
      register: open_incidents

    - name: Process each incident
      ansible.builtin.include_tasks: remediate-incident.yml
      loop: "{{ open_incidents.records }}"
      loop_control:
        loop_var: incident
        label: "{{ incident.number }}"
# remediate-incident.yml
- name: Parse incident category
  ansible.builtin.set_fact:
    remediation_playbook: >-
      {{ remediation_map[incident.category + '/' + incident.subcategory]
         | default('escalate') }}

- name: Run remediation
  ansible.builtin.command: >
    ansible-playbook playbooks/{{ remediation_playbook }}.yml
    -e "incident_number={{ incident.number }}"
    -e "target_host={{ incident.cmdb_ci.display_value }}"
  register: result
  when: remediation_playbook != 'escalate'
  ignore_errors: true

- name: Update incident
  servicenow.itsm.incident:
    number: "{{ incident.number }}"
    work_notes: >
      Ansible remediation {{ 'completed' if result is succeeded else 'failed' }}
      using playbook: {{ remediation_playbook }}

Conclusion

Ansible + ServiceNow integration creates powerful enterprise automation. The servicenow.itsm collection handles incidents (auto-create from alerts, auto-resolve after remediation), change requests (create, wait for approval, execute, close), and CMDB sync (keep configuration items in sync with actual infrastructure). The dynamic inventory plugin lets you use ServiceNow as your source of truth. The ultimate pattern: closed-loop automation where monitoring triggers an incident, Ansible remediates it, and the incident closes automatically — with full audit trail in ServiceNow.