Intermediate

Integrating ITSM into Ansible Network Workflows

Drive network configuration and troubleshooting through ServiceNow and Jira ITSM with Ansible.

Prerequisites: Intermediate networking experience (CCNA/CCNP equivalent), basic ServiceNow and Jira knowledge, familiarity with the Automating Networks with Ansible course series


Table of Contents

  1. Course Overview
  2. Basic ITSM Tasks with Ansible in ServiceNow
  3. Automating Network Configuration via ServiceNow ITSM
  4. Automating Network Troubleshooting via ServiceNow ITSM
  5. Basic ITSM Tasks with Ansible in Jira
  6. Automating Network Configuration via Jira ITSM
  7. Automating Network Troubleshooting via Jira ITSM
  8. Reference Tables
  9. Summary and Best Practices

1. Course Overview

This course explores how Ansible, the IT automation platform, can integrate with popular ITSM (IT Service Management) tools — ServiceNow and Jira — to automate network workflows. The fictional company Globomantics serves as the running example throughout the course.

Added Value of Ansible/ITSM Integration

Business ProblemAnsible + ITSM Solution
Manual implementation of recurring network changesAutomatic deployment based on Change Requests
High MTTR (Mean Time To Resolution)Dynamic network data collection triggered by an Incident
Manual incident triageAutomatic analysis and resolution by Ansible
Tickets not updated after interventionAutomatic ticket update by Ansible

Course Overall Architecture

flowchart TD
    A[ITSM Tool\nServiceNow / Jira] -->|Change Request\nIncident| B[Ansible Control Node]
    B -->|ansible-galaxy| C[Collections\nservicenow.itsm\ncommunity.general]
    B -->|Playbooks| D[Network Devices\nCisco IOS / Routers]
    D -->|show commands\nCEF table| B
    B -->|Update ticket\nWork notes| A
    E[Webhook\nBusiness Rule / JQL] -->|HTTP POST| F[FastAPI\nWebhook Receiver]
    F -->|ansible-runner| B
    A -->|Event trigger| E

Technical Prerequisites

  • Intermediate networking experience (1 to 3 years, CCNA/CCNP equivalent)
  • Knowledge of the OSPF protocol (Open Shortest Path First)
  • Basic knowledge of ServiceNow and Jira (ticket creation/management)

2. Basic ITSM Tasks with Ansible in ServiceNow

2.1 Installing the ServiceNow Collection

The servicenow.itsm collection is the entry point for all Ansible ↔ ServiceNow interactions.

Installation via Command Line

# Check installed collections
ansible-galaxy collection list

# Install the latest version
ansible-galaxy collection install servicenow.itsm

# Install a specific version (recommended for reproducibility)
ansible-galaxy collection install servicenow.itsm:==1.2.0

Best Practice: requirements.yaml File

Manage dependencies via a versioned file in Git:

# collections/requirements.yaml
---
collections:
  - name: servicenow.itsm
    version: "==1.2.0"
  - name: cisco.ios
    version: ">=2.0.0"
# Install all declared dependencies
ansible-galaxy collection install -r collections/requirements.yaml

Best practice: Always version requirements.yaml in the Git repository. This ensures reproducibility and clarifies dependencies for the whole team.

2.2 Configuring the Ansible User Account

So that ServiceNow can distinguish manual actions from automated actions, it is recommended to create a dedicated account for Ansible.

Configuration Steps in ServiceNow

  1. Navigate to User Administration → Users
  2. Click New to create a new user
  3. Fill in:
    • User ID: ansible
    • First name: ansible
    • Password: store in a vault (e.g. Ansible Vault, HashiCorp Vault)
    • Email: distribution address for the automation team
  4. Assign appropriate roles and groups (e.g. itil, network)
  5. Enable email notifications to monitor Ansible actions

Security: Never store the password in plain text in inventory variables. Use Ansible Vault or a secrets manager for production environments.

2.3 Managing Problems, Incidents, and Change Requests

The instance Parameter — Common to All Modules

All modules in the servicenow.itsm collection require an instance parameter:

vars:
  sn_instance:
    host: "https://myinstance.service-now.com"
    username: "ansible"
    password: "{{ vault_sn_password }}"

Opening, Modifying, and Closing a Problem

# playbooks/module-2/servicenow_problems.yaml
---
- name: Manage a Problem in ServiceNow
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Create a new Problem
      servicenow.itsm.problem:
        instance: "{{ sn_instance }}"
        state: new
        short_description: "OSPF connectivity loss on Boston router"
        impact: medium
        urgency: medium
        assigned_to: ansible
        other:
          watch_list: "admin@globomantics.com"
      register: problem_result

    - name: Display Problem number
      ansible.builtin.debug:
        var: problem_result.record.number

    - name: Update Problem (work in progress)
      servicenow.itsm.problem:
        instance: "{{ sn_instance }}"
        state: assess
        number: "{{ problem_result.record.number }}"
        other:
          work_notes: "Ansible started analysis. Checking OSPF table."

    - name: Resolve the Problem
      servicenow.itsm.problem:
        instance: "{{ sn_instance }}"
        state: closed
        number: "{{ problem_result.record.number }}"
        resolution_code: fix_applied
        cause_notes: "Missing OSPF declaration on interface GigabitEthernet4"
        fix_notes: "Added command 'network 192.168.4.0 0.0.0.255 area 0' in router ospf"

Opening, Modifying, and Closing an Incident

# playbooks/module-2/servicenow_incidents.yaml
---
- name: Manage an Incident in ServiceNow
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Create a new Incident
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        state: new
        caller: ansible
        short_description: "Connectivity loss between 192.168.1.10 and 192.168.4.10"
        impact: high
        urgency: high
        other:
          watch_list: "noc@globomantics.com"
      register: incident_result

    - name: Display Incident number
      ansible.builtin.debug:
        var: incident_result.record.number

    - name: Assign and set to in-progress
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        state: in_progress
        number: "{{ incident_result.record.number }}"
        assigned_to: ansible

    - name: Add work notes
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        number: "{{ incident_result.record.number }}"
        other:
          work_notes: "Ingress router identified: Atlanta. Interface GigabitEthernet4: administratively down."

    - name: Resolve the Incident
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        state: resolved
        number: "{{ incident_result.record.number }}"
        close_code: Solved
        close_notes: "GigabitEthernet4 interface brought up. Connectivity restored."

Opening, Modifying, and Closing a Change Request

# playbooks/module-2/servicenow_change_requests.yaml
---
- name: Manage a Change Request in ServiceNow
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Create a new Change Request
      servicenow.itsm.change_request:
        instance: "{{ sn_instance }}"
        state: new
        type: normal
        requested_by: ansible
        short_description: "Announce network 192.168.4.0/24 in OSPF area 0"
        description: "Add command network 192.168.4.0 0.0.0.255 area 0 on Boston router"
        priority: moderate
        risk: low
        impact: low
        other:
          cmdb_ci: "{{ cmdb_ci_hex_id }}"
      register: cr_result

    - name: Display Change Request number
      ansible.builtin.debug:
        var: cr_result.record.number

ServiceNow Ticket Type Workflows

stateDiagram-v2
    direction LR

    state "Problem Workflow" as PW {
        [*] --> new
        new --> assess
        assess --> root_cause_analysis
        root_cause_analysis --> fix_in_progress
        fix_in_progress --> closed
    }

    state "Incident Workflow" as IW {
        [*] --> new2: new
        new2 --> in_progress
        in_progress --> resolved
        resolved --> closed2: closed
    }

    state "Change Request Workflow" as CRW {
        [*] --> new3: new
        new3 --> assess2: assess
        assess2 --> authorize
        authorize --> scheduled
        scheduled --> implement
        implement --> review
        review --> closed3: closed
    }

3. Automating Network Configuration via ServiceNow ITSM

Globomantics Use Case

Globomantics identified that announcing new networks in OSPF is a recurring, low-risk change — and therefore ideal for automation. The goal is to automatically deploy network configurations based on Change Requests scheduled in ServiceNow.

Change Request → Network Configuration Integration Flow

flowchart TD
    A[Network engineer\ncreates a Change Request\nin ServiceNow] --> B[Change Request\nscheduled with\nmaintenance window]
    B --> C{Ansible cron job\nchecks CRs\nevery hour}
    C -->|No CR scheduled| C
    C -->|CR found| D[Ansible retrieves the CR\nand assigns it to itself]
    D --> E[Parse CMDB info:\nrouter + OSPF subnet]
    E --> F{Validation:\nis config already\nin place?}
    F -->|Already configured| G[Update CR:\nalready configured]
    F -->|Not configured| H[Apply OSPF config\non target router]
    H --> I{OSPF LSDB validation:\nnetwork visible?}
    I -->|Success| J[Update CR:\nstate → review\nwork notes]
    I -->|Failure| K[Update CR:\nconfiguration applied\nbut not validated]
    J --> L[CR closed ✓]
    K --> M[Manual escalation]

3.1 Fetching Scheduled Change Requests

# playbooks/module-3/c2_servicenow_fetch_scheduled_change_requests.yaml
---
- name: Fetch Scheduled Change Requests
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Retrieve CRs scheduled and awaiting implementation
      servicenow.itsm.change_request_info:
        instance: "{{ sn_instance }}"
        query:
          - state: = scheduled
            assignment_group: = Network
      register: scheduled_crs

    - name: Take ownership of the first available CR
      servicenow.itsm.change_request:
        instance: "{{ sn_instance }}"
        number: "{{ scheduled_crs.records[0].number }}"
        state: implement
        assigned_to: ansible
      when: scheduled_crs.records | length > 0

    - name: Store CR info for subsequent plays
      ansible.builtin.set_fact:
        change_request_record: "{{ scheduled_crs.records[0] }}"
        cacheable: true
      when: scheduled_crs.records | length > 0

Scheduling with cron

# Crontab — run every hour
0 * * * * /usr/bin/ansible-playbook /opt/ansible/playbooks/module-3/c2_servicenow_fetch_scheduled_change_requests.yaml >> /var/log/ansible/cron.log 2>&1

3.2 Applying a Simple Configuration

The ServiceNow CMDB (Configuration Management Database) contains information about network equipment. Ansible queries the CMDB recursively to identify the router and subnet to configure.

# playbooks/module-3/c3_servicenow_apply_simple_configuration.yaml (excerpt)
---
- name: Apply OSPF configuration from a Change Request
  hosts: "{{ ospf_router }}"
  gather_facts: false

  tasks:
    - name: Retrieve CI info from ServiceNow CMDB
      servicenow.itsm.configuration_item_info:
        instance: "{{ sn_instance }}"
        sys_id: "{{ hostvars['localhost']['change_request_record']['cmdb_ci']['value'] }}"
      register: ci_info
      delegate_to: localhost

    - name: Extract subnet and wildcard mask
      ansible.builtin.set_fact:
        network_address: "{{ ci_info.record.ip_address | ansible.netcommon.ipaddr('network') }}"
        wildcard_mask: "{{ ci_info.record.ip_address | ansible.netcommon.ipaddr('wildcard') }}"
        ospf_router_host: "{{ ci_info.record.ip_address }}"

    - name: Validate that config does not already exist
      cisco.ios.ios_command:
        commands:
          - "show ip ospf database | include {{ network_address }}"
      register: ospf_check

    - name: Apply OSPF configuration
      cisco.ios.ios_config:
        lines:
          - "network {{ network_address }} {{ wildcard_mask }} area 0"
        parents: "router ospf 1"
      when: ospf_check.stdout[0] == ""

3.3 Updating the Change Request

Three possible scenarios after applying the configuration:

# Scenario 1: Configuration applied and validated
- name: Update CR — success
  servicenow.itsm.change_request:
    instance: "{{ sn_instance }}"
    number: "{{ hostvars['localhost']['change_request_record']['number'] }}"
    state: review
    assignment_group: Network
    other:
      work_notes: |
        ✅ OSPF configuration successfully applied on {{ inventory_hostname }}.
        Network {{ network_address }}/{{ prefix_length }} visible in the OSPF LSDB.
        Running config after change:
        {{ ospf_config_after.stdout[0] }}

# Scenario 2: Configuration applied but not validated in the LSDB
- name: Update CR — applied but not validated
  servicenow.itsm.change_request:
    instance: "{{ sn_instance }}"
    number: "{{ hostvars['localhost']['change_request_record']['number'] }}"
    state: review
    other:
      work_notes: |
        ⚠️ Configuration applied on {{ inventory_hostname }} but network
        {{ network_address }} not visible in the OSPF LSDB.
        Escalation required.

# Scenario 3: Configuration already in place
- name: Update CR — already configured
  servicenow.itsm.change_request:
    instance: "{{ sn_instance }}"
    number: "{{ hostvars['localhost']['change_request_record']['number'] }}"
    state: review
    other:
      work_notes: |
        ℹ️ Network {{ network_address }} is already present in the OSPF LSDB.
        No changes required.

3.4 Applying a Complex Configuration

For complex changes that cannot be modeled in the CMDB, the Change Request fields (implementation_plan, backout_plan, test_plan) directly contain the CLI commands.

# Create the Change Request with plans
- name: Create a CR with implementation/backout/test plans
  servicenow.itsm.change_request:
    instance: "{{ sn_instance }}"
    state: new
    type: normal
    short_description: "Complex OSPF configuration on Boston router"
    other:
      cmdb_ci: "{{ cmdb_ci_boston_router }}"
      implementation_plan: |
        router ospf 1
          network 192.168.4.0 0.0.0.255 area 0
      backout_plan: |
        router ospf 1
          no network 192.168.4.0 0.0.0.255 area 0
      test_plan: |
        show ip ospf database | include 192.168.4.0
        show ip route ospf | include 192.168.4
# Apply and automatic rollback
- name: Apply the implementation plan
  cisco.ios.ios_config:
    lines: "{{ implementation_plan_lines }}"

- name: Execute the test plan
  cisco.ios.ios_command:
    commands: "{{ test_plan_commands }}"
  register: test_results

- name: Rollback if tests fail
  cisco.ios.ios_config:
    lines: "{{ backout_plan_lines }}"
  when: test_results.stdout | select('ne', '') | list | length == 0

4. Automating Network Troubleshooting via ServiceNow ITSM

Module Overview

This module addresses Globomantics’ main problem: the support team spends too much time manually troubleshooting network connectivity incidents. Ansible can automate this process by responding to ServiceNow Incidents via webhooks.

Pull vs Push Models

flowchart LR
    subgraph Pull["Pull Model (cron job)"]
        A1[ServiceNow] 
        A2[Ansible polls\nregularly]
        A1 -.->|polling| A2
    end
    subgraph Push["Push Model (webhook)"]
        B1[ServiceNow\nBusiness Rule]
        B2[FastAPI\nWebhook Receiver]
        B3[Ansible]
        B1 -->|Instant HTTP POST| B2
        B2 -->|ansible-runner| B3
    end
CriterionPull (cron)Push (webhook)
Reaction timeUp to 1h (cron frequency)Near-instant
ComplexitySimpleRequires an HTTP server
Use caseScheduled Change RequestsUrgent Incidents
Server loadConstant pollingEvent-driven

4.1 Creating a Webhook in ServiceNow

ServiceNow uses Business Rules to execute JavaScript code when database events occur.

// playbooks/module-4/c2_business_rule_webhook.js
// Business Rule: "Ansible Network Automation Webhook"
// Table: incident | Trigger: Insert (new ticket created)

(function executeRule(current, previous) {
    var request = new sn_ws.RESTMessageV2();
    request.setEndpoint("http://ansible-controller.globomantics.com:8000/servicenow");
    request.setHttpMethod("POST");
    request.setRequestHeader("Content-Type", "application/json");

    var body = {
        "incident_number": current.number.toString(),
        "source_ip": current.u_source_ip.toString(),
        "destination_ip": current.u_destination_ip.toString(),
        "short_description": current.short_description.toString(),
        "caller": current.caller_id.getDisplayValue()
    };

    request.setRequestBody(JSON.stringify(body));
    var response = request.execute();
})(current, previous);

Business Rule Configuration:

  • Table: incident
  • When to run: Insert (triggered on incident creation)
  • Conditions: filter on custom fields u_source_ip and u_destination_ip being non-empty

4.2 Creating a Webhook Receiver with FastAPI

# python_apps/fastapi_webhook_handler.py
import uvicorn
import ansible_runner
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

app = FastAPI()
worked_issues = []  # Avoid processing the same incident twice

class ServiceNowWebhook(BaseModel):
    incident_number: str
    source_ip: str
    destination_ip: str
    short_description: Optional[str] = ""

@app.post("/servicenow", status_code=200)
async def servicenow_connectivity_issue_webhook(webhook: ServiceNowWebhook):
    """
    ServiceNow webhook receiver.
    Executes an Ansible playbook in response to a new connectivity incident.
    """
    # Avoid double processing
    if webhook.incident_number in worked_issues:
        return {"status": "already_processed", "incident": webhook.incident_number}
    
    worked_issues.append(webhook.incident_number)

    # Execute the Ansible playbook in the background
    ansible_runner.run_async(
        playbook="playbooks/module-4/c5_servicenow_troubleshoot_connectivity.yaml",
        extravars={
            "incident_number": webhook.incident_number,
            "source_ip": webhook.source_ip,
            "destination_ip": webhook.destination_ip
        },
        project_dir="/opt/ansible"
    )

    return {"status": "accepted", "incident": webhook.incident_number}

if __name__ == "__main__":
    uvicorn.run("fastapi_webhook_handler:app", host="0.0.0.0", port=8000, reload=True)
# Start the FastAPI server
python python_apps/fastapi_webhook_handler.py
# or with uvicorn directly:
uvicorn fastapi_webhook_handler:app --host 0.0.0.0 --port 8000 --reload

Complete Webhook → Ansible Architecture

sequenceDiagram
    actor Technician
    participant SN as ServiceNow
    participant BR as Business Rule
    participant FA as FastAPI
    participant AC as Ansible Controller
    participant R as Network Routers

    Technician->>SN: Creates an Incident<br/>(source_ip, dest_ip)
    SN->>BR: Triggers the Business Rule<br/>(trigger: Insert)
    BR->>FA: HTTP POST /servicenow<br/>{"incident_number": "INC0001234",<br/>"source_ip": "192.168.1.10",<br/>"dest_ip": "192.168.4.10"}
    FA-->>BR: HTTP 200 OK
    FA->>AC: ansible_runner.run_async(playbook)
    AC->>R: show ip cef 192.168.4.10<br/>(identify ingress/egress router)
    R-->>AC: CEF table results
    AC->>R: ping source 192.168.1.10<br/>ping source 192.168.4.10
    R-->>AC: ICMP results
    AC->>SN: Update Incident<br/>(work notes with results)
    SN-->>Technician: Email notification

4.3 Automatic Incident Troubleshooting

Level 1 — Basic Information Collection

# playbooks/module-4/c4_basic_troubleshooting.yaml (excerpt)
---
- name: Identify ingress/egress routers
  hosts: routers
  gather_facts: false

  tasks:
    - name: Use the CEF table to identify the next hop
      cisco.ios.ios_command:
        commands:
          - "show ip cef {{ destination_ip }} detail"
      register: cef_output

    - name: Add work note to the Incident — ingress router
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        number: "{{ incident_number }}"
        other:
          work_notes: |
            📍 Ingress router identified: {{ inventory_hostname }}
            Management address: {{ ansible_host }}
            IOS version: {{ ios_facts.ansible_net_version }}
            Active interfaces: {{ ios_facts.ansible_net_interfaces | length }}
      delegate_to: localhost
      when: inventory_hostname == ingress_router

Level 2 — Advanced ICMP Reachability Tests

# playbooks/module-4/c5_advanced_troubleshooting.yaml (excerpt)
---
- name: Ping tests from ingress and egress routers
  hosts: "{{ ingress_router }},{{ egress_router }}"
  gather_facts: false

  tasks:
    - name: Ping to source from ingress router
      cisco.ios.ios_ping:
        dest: "{{ source_ip }}"
        count: 5
      register: ping_source_result
      when: inventory_hostname == ingress_router

    - name: Ping to destination from egress router
      cisco.ios.ios_ping:
        dest: "{{ destination_ip }}"
        count: 5
      register: ping_dest_result
      when: inventory_hostname == egress_router

    - name: Update Incident with ICMP results
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        number: "{{ incident_number }}"
        other:
          work_notes: |
            🔍 ICMP tests from {{ inventory_hostname }}:
            → Ping to {{ source_ip }}: 
              Success: {{ ping_source_result.packet_loss == '0%' | ternary('✅', '❌') }}
              Packet loss: {{ ping_source_result.packet_loss }}
      delegate_to: localhost

4.4 Automatic Incident Resolution

This scenario handles the case where a network interface is administratively down.

# playbooks/module-4/c6_automatic_incident_resolution.yaml
---
- name: Automatic resolution — interface administratively down
  hosts: routers
  gather_facts: false

  tasks:
    - name: Assign Incident to Ansible (in progress)
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        number: "{{ incident_number }}"
        state: in_progress
        assigned_to: ansible
        other:
          work_notes: "Ansible automation has taken over this incident. Analysis in progress."
      delegate_to: localhost
      run_once: true

    - name: Identify interfaces facing source/destination
      cisco.ios.ios_command:
        commands:
          - "show ip cef {{ source_ip }} detail"
          - "show ip cef {{ destination_ip }} detail"
      register: cef_results

    - name: Check administrative status of interfaces
      cisco.ios.ios_command:
        commands:
          - "show interfaces {{ relevant_interface }} | include administratively"
      register: interface_status

    - name: Bring up the interface if it is administratively down
      cisco.ios.ios_config:
        lines:
          - "no shutdown"
        parents: "interface {{ relevant_interface }}"
      when: "'administratively down' in interface_status.stdout[0]"
      register: fix_applied

    - name: Resolve Incident if fix was applied
      servicenow.itsm.incident:
        instance: "{{ sn_instance }}"
        number: "{{ incident_number }}"
        state: resolved
        close_code: Solved
        close_notes: |
          ✅ Ansible automatic resolution:
          Interface {{ relevant_interface }} on {{ inventory_hostname }}
          was administratively down. Interface brought up (no shutdown).
          Connectivity between {{ source_ip }} and {{ destination_ip }} restored.
      delegate_to: localhost
      when: fix_applied.changed

ITSM/Ansible Approval Pipeline

flowchart TD
    A[New Incident created\nin ServiceNow] --> B[Webhook triggered\nBusiness Rule]
    B --> C[FastAPI receives\nthe webhook]
    C --> D[ansible-runner\nexecutes the playbook]
    D --> E[Identify ingress/egress routers\nvia CEF table]
    E --> F[Collect network\nfacts]
    F --> G{Interface\nadministratively\ndown?}
    G -->|Yes| H[Apply no shutdown\non the interface]
    H --> I[Validate\nICMP connectivity]
    I --> J{Connectivity\nrestored?}
    J -->|Yes| K[Resolve Incident\nstate → resolved\nclose_code: Solved]
    J -->|No| L[Escalate\nwork_notes with\nfull context]
    G -->|No| M[Add work notes\nwith ICMP troubleshooting\nresults]
    M --> N[Incident remains open\nfor manual handling]
    K --> O[Technician notified\nby email ✓]
    L --> O
    N --> O

5. Basic ITSM Tasks with Ansible in Jira

5.1 Installing the Jira Collection

The Jira module is included in the community.general collection:

# Install the community.general collection
ansible-galaxy collection install community.general

# Specific version used in this course
ansible-galaxy collection install community.general:==4.1.0 --force

# Via requirements.yaml (recommended)
# collections/requirements.yaml
---
collections:
  - name: servicenow.itsm
    version: "==1.2.0"
  - name: community.general
    version: "==4.1.0"
  - name: cisco.ios
    version: ">=2.0.0"

5.2 Creating a Jira API Key

Unlike ServiceNow, Jira Cloud does not support dedicated service accounts. Access is done via an API key associated with a user account.

Steps to Create an API Key

  1. Log in to Jira → click the avatar (top right corner)
  2. Navigate to Atlassian account settings → Security
  3. Under API token → click Create and manage API tokens
  4. Click Create API token → enter a descriptive label
  5. Copy the generated token and store it in Ansible Vault

Security: In production, create a dedicated licensed user account for Ansible. The API key must be stored as a secret (Ansible Vault, HashiCorp Vault, etc.) and never in plain text in code.

Ansible Variables for Jira

# group_vars/all/jira.yaml
jira_uri: "https://globomantics.atlassian.net"
jira_user: "ansible@globomantics.com"
jira_api_key: "{{ vault_jira_api_key }}"  # Encrypted with Ansible Vault
jira_project: "GLOB"  # Jira project key

5.3 Managing Problems, Incidents, and Change Requests

community.general.jira Module — Common Parameters

ParameterDescriptionExample
uriJira instance URLhttps://globomantics.atlassian.net
usernameAccount email addressansible@globomantics.com
passwordAPI key (not the password){{ vault_jira_api_key }}
projectProject keyGLOB
operationAction to performcreate, edit, transition, comment
issuetypeTicket type[System] Problem, [System] Incident, [System] Change

Opening, Modifying, and Closing a Jira Problem

# playbooks/module-5/c4_jira_problems.yaml
---
- name: Manage a Problem in Jira
  hosts: localhost
  gather_facts: false

  tasks:
    - name: Create a new Problem
      community.general.jira:
        uri: "{{ jira_uri }}"
        username: "{{ jira_user }}"
        password: "{{ jira_api_key }}"
        project: "{{ jira_project }}"
        operation: create
        summary: "OSPF connectivity loss on Boston router"
        description: "Investigation required on OSPF table. Network 192.168.4.0/24 missing."
        issuetype: "[System] Problem"
      register: jira_problem

    - name: Display Problem key
      ansible.builtin.debug:
        var: jira_problem.meta.key

    - name: Assign and set to in-progress
      community.general.jira:
        uri: "{{ jira_uri }}"
        username: "{{ jira_user }}"
        password: "{{ jira_api_key }}"
        issue: "{{ jira_problem.meta.key }}"
        operation: edit
        fields:
          assignee:
            name: "{{ jira_user }}"

    - name: Transition to "Under Investigation"
      community.general.jira:
        uri: "{{ jira_uri }}"
        username: "{{ jira_user }}"
        password: "{{ jira_api_key }}"
        issue: "{{ jira_problem.meta.key }}"
        operation: transition
        status: "Under Investigation"

    - name: Add a comment
      community.general.jira:
        uri: "{{ jira_uri }}"
        username: "{{ jira_user }}"
        password: "{{ jira_api_key }}"
        issue: "{{ jira_problem.meta.key }}"
        operation: comment
        comment: |
          Ansible analysis: Missing OSPF configuration identified.
          Fix applied: network 192.168.4.0 0.0.0.255 area 0

    - name: Close the Problem
      community.general.jira:
        uri: "{{ jira_uri }}"
        username: "{{ jira_user }}"
        password: "{{ jira_api_key }}"
        issue: "{{ jira_problem.meta.key }}"
        operation: transition
        status: "Closed"
        fields:
          resolution:
            name: "Done"

Opening a Jira Change Request with Scheduling

# playbooks/module-5/c6_jira_change_requests.yaml (excerpt)
- name: Create a Change Request
  community.general.jira:
    uri: "{{ jira_uri }}"
    username: "{{ jira_user }}"
    password: "{{ jira_api_key }}"
    project: "{{ jira_project }}"
    operation: create
    summary: "Network 192.168.4.0/24 announcement in OSPF area 0"
    issuetype: "[System] Change"
  register: jira_cr

- name: Schedule the maintenance window
  community.general.jira:
    uri: "{{ jira_uri }}"
    username: "{{ jira_user }}"
    password: "{{ jira_api_key }}"
    issue: "{{ jira_cr.meta.key }}"
    operation: edit
    fields:
      # Custom field names (may vary depending on the instance)
      customfield_10044: "2024-02-04T07:00:00.000-0500"  # start_date
      customfield_10045: "2024-02-04T11:00:00.000-0500"  # end_date

- name: Transition to "Awaiting Implementation"
  community.general.jira:
    uri: "{{ jira_uri }}"
    username: "{{ jira_user }}"
    password: "{{ jira_api_key }}"
    issue: "{{ jira_cr.meta.key }}"
    operation: transition
    status: "Awaiting Implementation"

6. Automating Network Configuration via Jira ITSM

The Jira flow is similar to the ServiceNow flow, with the following adaptations:

Key Differences: ServiceNow vs Jira for Change Requests

AspectServiceNowJira
Equipment identificationCMDB sys_id (hexadecimal)Custom fields in the ticket
OSPF fieldscmdb_ci → Recursive CMDBcustomfield_10058 (subnet), customfield_10059 (router FQDN)
Complex plansimplementation_plan, backout_plan, test_plancustomfield_10041, customfield_10042, customfield_10043
Statusesnew → scheduled → implement → review → closedReview → Awaiting Implementation → Implementing → Completed

Fetching Scheduled Jira Change Requests

# playbooks/module-6/c2_jira_fetch_scheduled_change_requests.yaml (excerpt)
- name: Search for scheduled CRs using JQL
  community.general.jira:
    uri: "{{ jira_uri }}"
    username: "{{ jira_user }}"
    password: "{{ jira_api_key }}"
    operation: search
    jql: >
      project = GLOB
      AND issuetype = "[System] Change"
      AND status = "Awaiting Implementation"
      AND cf[10044] <= now()
      AND cf[10045] >= now()
    fields:
      - summary
      - status
      - customfield_10058
      - customfield_10059
  register: scheduled_jira_crs

- name: Extract subnet and router from custom fields
  ansible.builtin.set_fact:
    network_address: >-
      {{ scheduled_jira_crs.meta.issues[0].fields.customfield_10058
         | ansible.netcommon.ipaddr('network') }}
    wildcard_mask: >-
      {{ scheduled_jira_crs.meta.issues[0].fields.customfield_10058
         | ansible.netcommon.ipaddr('wildcard') }}
    target_router: >-
      {{ scheduled_jira_crs.meta.issues[0].fields.customfield_10059 }}

Reference Network Topology (Globomantics)

flowchart LR
    subgraph LAN["Globomantics LAN"]
        SRC["Source Host\n192.168.1.10"]
        DST["Destination Host\n192.168.4.10"]
    end

    subgraph ROUTERS["OSPF Area 0 Domain"]
        ATL["Atlanta Router\nGigabitEthernet4 → 192.168.1.x"]
        BOS["Boston Router\nGigabitEthernet4 → 192.168.4.0/24"]
        HUB["Hub Router\n(transit)"]
    end

    subgraph ANSIBLE["Ansible Control Node"]
        AC["ansible-playbook\nFastAPI"]
    end

    SRC --- ATL
    ATL --- HUB
    HUB --- BOS
    BOS --- DST
    AC -.->|SSH / API| ATL
    AC -.->|SSH / API| BOS
    AC -.->|SSH / API| HUB

7. Automating Network Troubleshooting via Jira ITSM

Creating a Webhook in Jira

Jira uses native WebHook configuration (no Business Rules like ServiceNow).

Configuration Steps

  1. Navigate to Jira settings → System → WebHooks (Advanced section)
  2. Click Create a WebHook
  3. Configure:
    • Name: Ansible Network Automation
    • URL: https://ansible-controller.globomantics.com:443/jira?issue=${issue.key}
    • Events: check Issue → created
    • JQL filter: project = GLOB AND issuetype = "[System] Incident" AND status = Open AND cf[10060] is not EMPTY

Jira Cloud limitation: The webhook must use HTTPS on port 443. If the Ansible server is on-premises, a reverse proxy solution (nginx, Cloudflare Tunnel, etc.) is required.

FastAPI Receiver for Jira

# python_apps/fastapi_webhook_handler.py (Jira section)
from fastapi import FastAPI
from pydantic import BaseModel
import ansible_runner

app = FastAPI()
worked_issues = []

class JiraWebhook(BaseModel):
    issue_key: str  # Passed via the URL parameter ${issue.key}

@app.post("/jira", status_code=200)
async def jira_connectivity_issue_webhook(issue_key: str):
    """
    Jira webhook receiver.
    The issue_key is passed via the query parameter in the webhook URL.
    """
    if issue_key in worked_issues:
        return {"status": "already_processed"}

    worked_issues.append(issue_key)

    # Execute the troubleshooting playbook
    ansible_runner.run_async(
        playbook="playbooks/module-7/c4_jira_basic_troubleshooting.yaml",
        extravars={
            "jira_issue_key": issue_key
        },
        project_dir="/opt/ansible"
    )

    return {"status": "accepted", "issue": issue_key}

Complete Jira/Ansible Integration Flow

sequenceDiagram
    actor Support
    participant J as Jira ITSM
    participant WH as Jira WebHook
    participant FA as FastAPI
    participant AC as Ansible Controller
    participant R as Cisco IOS Routers

    Support->>J: Creates an Incident<br/>(customfield_10060: src_ip)<br/>(customfield_10061: dst_ip)
    J->>WH: Triggers the webhook<br/>(JQL filter: Incident + Open)
    WH->>FA: HTTP POST<br/>/jira?issue=GLOB-123
    FA-->>WH: HTTP 200 OK
    FA->>AC: ansible_runner.run_async<br/>(jira_issue_key=GLOB-123)
    AC->>J: Retrieves fields<br/>customfield_10060/10061
    J-->>AC: source_ip, destination_ip
    AC->>R: show ip cef<br/>ingress/egress identification
    R-->>AC: CEF table output
    AC->>R: cisco.ios.ios_ping<br/>(ICMP tests)
    R-->>AC: Ping results
    AC->>R: show interfaces<br/>(check admin status)
    R-->>AC: Interface status
    AC->>R: no shutdown<br/>(if interface down)
    AC->>J: comment/transition<br/>Resolution or escalation
    J-->>Support: Email notification

8. Reference Tables

Ansible ServiceNow ITSM Modules

ModuleDescriptionKey state Values
servicenow.itsm.problemManage Problemsnew, assess, root_cause_analysis, fix_in_progress, closed
servicenow.itsm.incidentManage Incidentsnew, in_progress, resolved, closed
servicenow.itsm.change_requestManage Change Requestsnew, assess, authorize, scheduled, implement, review, closed
servicenow.itsm.change_request_infoQuery Change RequestsN/A (read only)
servicenow.itsm.configuration_item_infoQuery the CMDBN/A (read only)
servicenow.itsm.problem_infoQuery ProblemsN/A (read only)
servicenow.itsm.incident_infoQuery IncidentsN/A (read only)

Jira community.general.jira Module Operations

operationDescription
createCreate a new ticket
editModify fields of an existing ticket
transitionChange the status (workflow)
commentAdd a comment
searchSearch tickets via JQL
attachAttach a file
linkLink two tickets

ServiceNow vs Jira ITSM Comparison

FeatureServiceNowJira ITSM
Ansible Collectionservicenow.itsmcommunity.general (jira module)
AuthenticationUsername + PasswordUsername + API Key
Dedicated accountService account possibleAPI key tied to a user
WebhooksBusiness Rules (JavaScript)Native WebHooks with JQL
CMDBNative, integratedCustom fields in tickets
Webhook URL limitationHTTP/HTTPS, any portHTTPS only, port 443
Equipment identificationsys_id (CMDB)Custom fields (customfield_XXXXX)

Ansible Network Modules Used

ModuleDescriptionTypical Usage
cisco.ios.ios_commandRun show commandsInformation gathering, validation
cisco.ios.ios_configApply configurationChange deployment
cisco.ios.ios_pingPerform ICMP pingsConnectivity tests
cisco.ios.ios_factsCollect IOS factsVersion, interfaces, routes
ansible.builtin.set_factRegister variablesSharing between plays
ansible.netcommon.ipaddrIP address filtersExtract network/wildcard

Typical Inventory Variables

# inventory/group_vars/routers.yaml
ansible_network_os: cisco.ios.ios
ansible_connection: network_cli
ansible_become: true
ansible_become_method: enable

# inventory/group_vars/all/vault.yaml (Ansible Vault encrypted)
vault_sn_password: "{{ vault_sn_password }}"
vault_jira_api_key: "{{ vault_jira_api_key }}"

# inventory/hosts.yaml
all:
  children:
    routers:
      hosts:
        atlanta:
          ansible_host: 10.0.0.1
        boston:
          ansible_host: 10.0.0.2
        hub:
          ansible_host: 10.0.0.3
    localhost:
      hosts:
        localhost:
          ansible_connection: local

9. Summary and Best Practices

What You Learned

mindmap
  root((Ansible\n+ ITSM))
    ServiceNow
      servicenow.itsm
        Problems
        Incidents
        Change Requests
        CMDB queries
      Business Rules
        JavaScript webhook
      Workflows
        Automated Change
        Auto-resolved Incident
    Jira ITSM
      community.general.jira
        Problems
        Incidents
        Change Requests
        JQL search
      Native WebHooks
        JQL Filter
      Custom Fields
        OSPF subnet/router
        Config plans
    Architecture
      Pull model
        Cron job
        Polling
      Push model
        FastAPI
        ansible-runner
        Webhooks
    Network
      OSPF automation
        Network announcement
        LSDB Validation
        Rollback
      Troubleshooting
        CEF table
        ICMP tests
        Interface recovery

Best Practices Summary

DomainBest Practice
SecurityStore all credentials in Ansible Vault or a secrets manager
ITSM accountCreate a dedicated account for Ansible (ServiceNow) or use a separate API key (Jira)
DependenciesVersion collections/requirements.yaml in Git
IdempotencyAlways validate before applying (avoid redundant changes)
TraceabilitySystematically update tickets with actions performed
RollbackAlways include a backout_plan for complex changes
Webhook safetyMaintain a worked_issues list to avoid double processing
Cron vs WebhookCron for scheduled CRs, webhook for urgent incidents
CMDBUse the ServiceNow CMDB to model equipment and subnets
LoggingConfigure ansible-runner to log executions in production

Complete Reference Architecture

flowchart TD
    subgraph ITSM["ITSM Tools"]
        SN["ServiceNow\n(Business Rules\n+ CMDB)"]
        JR["Jira ITSM\n(WebHooks\n+ Custom Fields)"]
    end

    subgraph TRIGGER["Triggers"]
        CR["Scheduled Change Request\n(cron job)"]
        INC["New Incident\n(webhook push)"]
    end

    subgraph AUTOMATION["Automation Layer"]
        CRON["Cron Job\n(every hour)"]
        FA["FastAPI\nWebhook Receiver\n(port 8000/443)"]
        AR["ansible-runner\n(Python API)"]
    end

    subgraph ANSIBLE["Ansible"]
        PB["Playbooks\n.yaml"]
        COL["Collections\nservicenow.itsm\ncommunity.general\ncisco.ios"]
        INV["Inventory\n+ Vault"]
    end

    subgraph NETWORK["Target Network"]
        R1["Atlanta Router\nGigE4 → 192.168.1.x"]
        R2["Boston Router\nGigE4 → 192.168.4.x"]
        R3["Hub Router"]
    end

    SN -->|CR scheduled| CR
    JR -->|CR scheduled| CR
    SN -->|Incident created| INC
    JR -->|Incident created| INC

    CR --> CRON
    INC --> FA

    CRON --> AR
    FA --> AR
    AR --> PB

    PB --> COL
    PB --> INV
    COL -->|SSH/API| R1
    COL -->|SSH/API| R2
    COL -->|SSH/API| R3

    R1 -.->|Results| PB
    R2 -.->|Results| PB
    R3 -.->|Results| PB

    PB -->|Ticket update| SN
    PB -->|Ticket update| JR

Additional Use Cases Identified in the Course

Beyond the OSPF example used in this training, these patterns are applicable to many network scenarios:

  • Provisioning a new VLAN on access switches
  • Adding a prefix to a BGP route-map
  • Adding rules to a firewall security policy (ACL, zone-based policy)
  • QoS configuration changes following a CR
  • Automatic recovery of a degraded EtherChannel interface
  • Automatic validation of STP convergence after a change

Search Terms

integrating · itsm · ansible · network · workflows · networking · web · servers · systems · security · jira · servicenow · change · configuration · opening · requests · via · automating · closing · modifying · request · webhook · architecture · collection

Interested in this course?

Contact us to book it or get a custom training plan for your team.