Prerequisites: Intermediate networking experience (CCNA/CCNP equivalent), basic ServiceNow and Jira knowledge, familiarity with the Automating Networks with Ansible course series
Table of Contents
- Course Overview
- Basic ITSM Tasks with Ansible in ServiceNow
- Automating Network Configuration via ServiceNow ITSM
- Automating Network Troubleshooting via ServiceNow ITSM
- Basic ITSM Tasks with Ansible in Jira
- Automating Network Configuration via Jira ITSM
- Automating Network Troubleshooting via Jira ITSM
- Reference Tables
- 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 Problem | Ansible + ITSM Solution |
|---|---|
| Manual implementation of recurring network changes | Automatic deployment based on Change Requests |
| High MTTR (Mean Time To Resolution) | Dynamic network data collection triggered by an Incident |
| Manual incident triage | Automatic analysis and resolution by Ansible |
| Tickets not updated after intervention | Automatic 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.yamlin 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
- Navigate to User Administration → Users
- Click New to create a new user
- 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
- User ID:
- Assign appropriate roles and groups (e.g.
itil,network) - 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
| Criterion | Pull (cron) | Push (webhook) |
|---|---|---|
| Reaction time | Up to 1h (cron frequency) | Near-instant |
| Complexity | Simple | Requires an HTTP server |
| Use case | Scheduled Change Requests | Urgent Incidents |
| Server load | Constant polling | Event-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_ipandu_destination_ipbeing 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
- Log in to Jira → click the avatar (top right corner)
- Navigate to Atlassian account settings → Security
- Under API token → click Create and manage API tokens
- Click Create API token → enter a descriptive label
- 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
| Parameter | Description | Example |
|---|---|---|
uri | Jira instance URL | https://globomantics.atlassian.net |
username | Account email address | ansible@globomantics.com |
password | API key (not the password) | {{ vault_jira_api_key }} |
project | Project key | GLOB |
operation | Action to perform | create, edit, transition, comment |
issuetype | Ticket 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
| Aspect | ServiceNow | Jira |
|---|---|---|
| Equipment identification | CMDB sys_id (hexadecimal) | Custom fields in the ticket |
| OSPF fields | cmdb_ci → Recursive CMDB | customfield_10058 (subnet), customfield_10059 (router FQDN) |
| Complex plans | implementation_plan, backout_plan, test_plan | customfield_10041, customfield_10042, customfield_10043 |
| Statuses | new → scheduled → implement → review → closed | Review → 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
- Navigate to Jira settings → System → WebHooks (Advanced section)
- Click Create a WebHook
- 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
- Name:
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
| Module | Description | Key state Values |
|---|---|---|
servicenow.itsm.problem | Manage Problems | new, assess, root_cause_analysis, fix_in_progress, closed |
servicenow.itsm.incident | Manage Incidents | new, in_progress, resolved, closed |
servicenow.itsm.change_request | Manage Change Requests | new, assess, authorize, scheduled, implement, review, closed |
servicenow.itsm.change_request_info | Query Change Requests | N/A (read only) |
servicenow.itsm.configuration_item_info | Query the CMDB | N/A (read only) |
servicenow.itsm.problem_info | Query Problems | N/A (read only) |
servicenow.itsm.incident_info | Query Incidents | N/A (read only) |
Jira community.general.jira Module Operations
operation | Description |
|---|---|
create | Create a new ticket |
edit | Modify fields of an existing ticket |
transition | Change the status (workflow) |
comment | Add a comment |
search | Search tickets via JQL |
attach | Attach a file |
link | Link two tickets |
ServiceNow vs Jira ITSM Comparison
| Feature | ServiceNow | Jira ITSM |
|---|---|---|
| Ansible Collection | servicenow.itsm | community.general (jira module) |
| Authentication | Username + Password | Username + API Key |
| Dedicated account | Service account possible | API key tied to a user |
| Webhooks | Business Rules (JavaScript) | Native WebHooks with JQL |
| CMDB | Native, integrated | Custom fields in tickets |
| Webhook URL limitation | HTTP/HTTPS, any port | HTTPS only, port 443 |
| Equipment identification | sys_id (CMDB) | Custom fields (customfield_XXXXX) |
Ansible Network Modules Used
| Module | Description | Typical Usage |
|---|---|---|
cisco.ios.ios_command | Run show commands | Information gathering, validation |
cisco.ios.ios_config | Apply configuration | Change deployment |
cisco.ios.ios_ping | Perform ICMP pings | Connectivity tests |
cisco.ios.ios_facts | Collect IOS facts | Version, interfaces, routes |
ansible.builtin.set_fact | Register variables | Sharing between plays |
ansible.netcommon.ipaddr | IP address filters | Extract 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
| Domain | Best Practice |
|---|---|
| Security | Store all credentials in Ansible Vault or a secrets manager |
| ITSM account | Create a dedicated account for Ansible (ServiceNow) or use a separate API key (Jira) |
| Dependencies | Version collections/requirements.yaml in Git |
| Idempotency | Always validate before applying (avoid redundant changes) |
| Traceability | Systematically update tickets with actions performed |
| Rollback | Always include a backout_plan for complex changes |
| Webhook safety | Maintain a worked_issues list to avoid double processing |
| Cron vs Webhook | Cron for scheduled CRs, webhook for urgent incidents |
| CMDB | Use the ServiceNow CMDB to model equipment and subnets |
| Logging | Configure 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