freundcloud

CI/CD Integration Overview

Introduction

This guide provides platform-agnostic concepts and patterns for integrating ServiceNow with any CI/CD platform. Use this as a foundation before implementing platform-specific integrations.

Integration Architecture

High-Level Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    CI/CD Pipeline                             β”‚
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Build │──→│ Test │──→│ Scan │──→│Stage │──→│Productionβ”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”¬β”€β”€β”˜   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                        β”‚           β”‚         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                         β”‚           β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
                    β”‚                                β”‚
                    β–Ό                                β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚ Create Change        β”‚       β”‚ Update Change        β”‚
         β”‚ Request              β”‚       β”‚ (Deployed)           β”‚
         β”‚                      β”‚       β”‚                      β”‚
         β”‚ POST /change_request β”‚       β”‚ PATCH /change_requestβ”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚ Wait for Approval    β”‚
         β”‚                      β”‚
         β”‚ GET /change_request  β”‚
         β”‚ (Poll or Webhook)    β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
                    β–Ό
              Approved? ──Yes──→ Continue Pipeline
                    β”‚
                    No
                    β”‚
                    β–Ό
              Block/Fail Pipeline

Integration Methods

Method 1: Direct REST API Integration

Description: Pipeline makes direct HTTP calls to ServiceNow REST API

When to Use:

  • Full control over integration logic
  • Simple change management workflows
  • No ServiceNow Integration Hub available
  • Custom retry and error handling needed

Pros:

  • βœ“ Complete control
  • βœ“ No ServiceNow middleware required
  • βœ“ Works with any CI/CD platform
  • βœ“ Easy to debug and test

Cons:

  • βœ— Pipeline must handle all logic
  • βœ— Need to implement retry mechanisms
  • βœ— Authentication management in pipeline
  • βœ— More code to maintain

Example Flow:

Pipeline β†’ ServiceNow REST API
    β”‚
    β”œβ”€β†’ Create change (POST)
    β”œβ”€β†’ Poll for approval (GET, loop)
    β”œβ”€β†’ Update status (PATCH)
    └─→ Close change (PATCH)

Method 2: ServiceNow Integration Hub

Description: ServiceNow Integration Hub orchestrates the integration with pre-built spokes

When to Use:

  • Complex workflows with multiple systems
  • Enterprise ServiceNow deployment
  • Need robust error handling and retry
  • Reusable integration patterns

Pros:

  • βœ“ Pre-built change management spokes
  • βœ“ Built-in error handling and retry
  • βœ“ Visual workflow designer (Flow Designer)
  • βœ“ Reusable across multiple pipelines
  • βœ“ ServiceNow-managed updates

Cons:

  • βœ— Requires Integration Hub license
  • βœ— More initial setup complexity
  • βœ— Limited customization vs. direct API
  • βœ— Debugging can be challenging

Example Flow:

Pipeline β†’ Integration Hub β†’ Change Management Spoke
                β”‚
                β”œβ”€β†’ Create change
                β”œβ”€β†’ Handle approvals
                β”œβ”€β†’ Update CMDB
                └─→ Send notifications

Method 3: Event-Driven (Webhooks)

Description: CI/CD events trigger ServiceNow webhooks, which execute Flow Designer workflows

When to Use:

  • Event-driven architecture preferred
  • Asynchronous processing acceptable
  • Need to trigger multiple ServiceNow workflows
  • Loose coupling between systems

Pros:

  • βœ“ Decoupled systems
  • βœ“ Scalable (async processing)
  • βœ“ No polling required
  • βœ“ Supports fan-out to multiple workflows

Cons:

  • βœ— More complex setup
  • βœ— Harder to debug
  • βœ— Network firewall considerations
  • βœ— Webhook endpoint security critical

Example Flow:

Pipeline Event β†’ Webhook β†’ ServiceNow β†’ Flow Designer Workflow
                                β”‚
                                β”œβ”€β†’ Create change
                                β”œβ”€β†’ Send notification
                                └─→ Update CMDB

Method 4: Platform-Specific Plugins

Description: Use official ServiceNow plugins/extensions for your CI/CD platform

When to Use:

  • Platform has official ServiceNow support
  • Want simplest setup
  • No custom workflow requirements
  • Standard change management patterns

Available Platforms:

  • Azure DevOps: Official ServiceNow extension
  • Jenkins: ServiceNow plugin
  • GitHub Actions: Community actions
  • GitLab: REST API integration (no official plugin)

Pros:

  • βœ“ Easiest setup
  • βœ“ Platform-native configuration
  • βœ“ Maintained by ServiceNow or community
  • βœ“ Built-in best practices

Cons:

  • βœ— Limited to available platforms
  • βœ— Less customization
  • βœ— Plugin update dependencies
  • βœ— May not fit complex workflows

Core Integration Patterns

Pattern 1: Change Request Lifecycle

Complete change management flow:

# Pseudo-code for any CI/CD platform

job: deploy-to-production
  steps:
    - name: Create Change Request
      action: servicenow-create-change
      inputs:
        short_description: "Deploy ${APP_NAME} v${VERSION}"
        assignment_group: "DevOps Team"
        cmdb_ci: "production-k8s-cluster"
        type: "standard"
        implementation_plan: "Deploy via Helm chart"
        backout_plan: "Rollback to previous Helm release"
      outputs:
        change_sys_id: $CHANGE_ID

    - name: Wait for Approval
      action: servicenow-wait-approval
      inputs:
        change_sys_id: $CHANGE_ID
        timeout: 3600  # 1 hour
        poll_interval: 60  # Check every minute

    - name: Deploy Application
      action: kubectl-apply
      inputs:
        manifest: k8s/production/

    - name: Update Change Status
      action: servicenow-update-change
      inputs:
        change_sys_id: $CHANGE_ID
        state: "implement"
        work_notes: "Deployment completed successfully"

    - name: Close Change Request
      action: servicenow-update-change
      inputs:
        change_sys_id: $CHANGE_ID
        state: "closed"
        close_code: "successful"
        close_notes: "Deployment verified in production"

Pattern 2: Emergency Change

Expedited process for critical fixes:

job: emergency-patch
  when: manual  # Requires manual trigger
  steps:
    - name: Create Emergency Change
      action: servicenow-create-change
      inputs:
        type: "emergency"
        priority: "1 - Critical"
        short_description: "EMERGENCY: Security patch ${CVE_ID}"
        risk: "high"
        justification: "Critical security vulnerability"
        # Emergency changes have expedited approval

    - name: Notify On-Call Manager
      action: send-notification
      inputs:
        channel: "slack"
        message: "Emergency change ${CHANGE_ID} requires immediate approval"

    - name: Wait for Expedited Approval
      action: servicenow-wait-approval
      inputs:
        change_sys_id: $CHANGE_ID
        timeout: 300  # 5 minutes only

    - name: Deploy Emergency Fix
      action: deploy

    - name: Close Emergency Change
      action: servicenow-update-change

Pattern 3: Standard Pre-Approved Change

Fast path for low-risk, repeatable changes:

job: deploy-standard-app
  steps:
    - name: Create Standard Change
      action: servicenow-create-change
      inputs:
        type: "standard"
        # Standard changes are pre-approved, no waiting
        short_description: "Deploy ${APP_NAME} (Standard Change)"
        standard_change_template: "app-deployment-template"

    - name: Deploy Immediately
      # No approval wait needed for standard changes
      action: deploy

    - name: Close Change
      action: servicenow-update-change

Pattern 4: Change with Attachments

Include test results and security scan reports:

job: deploy-with-evidence
  steps:
    - name: Run Tests
      action: pytest
      outputs:
        results_file: test-results.xml

    - name: Security Scan
      action: trivy-scan
      outputs:
        scan_file: security-scan.json

    - name: Create Change with Attachments
      action: servicenow-create-change
      inputs:
        short_description: "Deploy with test evidence"
        attachments:
          - name: "Test Results"
            file: test-results.xml
          - name: "Security Scan"
            file: security-scan.json

Pattern 5: Rollback Change

Document rollback as a separate change:

job: rollback-deployment
  when: on_failure  # Triggered by deployment failure
  steps:
    - name: Create Rollback Change
      action: servicenow-create-change
      inputs:
        type: "standard"
        short_description: "ROLLBACK: ${APP_NAME} due to deployment failure"
        parent_change: $ORIGINAL_CHANGE_ID  # Link to failed deployment
        implementation_plan: "Rollback to previous version"

    - name: Execute Rollback
      action: helm-rollback

    - name: Close Rollback Change
      action: servicenow-update-change
      inputs:
        state: "closed"
        close_code: "successful"

ServiceNow REST API Essentials

Authentication

Basic Authentication (not recommended for production):

curl -u username:password \
  https://instance.service-now.com/api/now/table/change_request

OAuth 2.0 (recommended):

# Get token
curl -X POST https://instance.service-now.com/oauth_token.do \
  -d "grant_type=client_credentials" \
  -d "client_id=${CLIENT_ID}" \
  -d "client_secret=${CLIENT_SECRET}"

# Use token
curl -H "Authorization: Bearer ${TOKEN}" \
  https://instance.service-now.com/api/now/table/change_request

Create Change Request

curl -X POST \
  "https://${INSTANCE}.service-now.com/api/now/table/change_request" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "short_description": "Deploy microservice-api v2.3.0",
    "description": "Automated deployment from GitLab pipeline",
    "assignment_group": "DevOps Team",
    "cmdb_ci": "prod-k8s-cluster",
    "type": "standard",
    "priority": "3",
    "risk": "low",
    "implementation_plan": "Deploy via Helm chart",
    "backout_plan": "Helm rollback to previous release",
    "justification": "New features and bug fixes"
  }'

Response:

{
  "result": {
    "sys_id": "a9e0c5f21bf45110d4e27f86624bcb24",
    "number": "CHG0030001",
    "state": "new",
    "approval": "not requested"
  }
}

Get Change Status

curl -X GET \
  "https://${INSTANCE}.service-now.com/api/now/table/change_request/${SYS_ID}" \
  -H "Authorization: Bearer ${TOKEN}"

Update Change Request

curl -X PATCH \
  "https://${INSTANCE}.service-now.com/api/now/table/change_request/${SYS_ID}" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "implement",
    "work_notes": "Deployment in progress"
  }'

Attach File to Change

curl -X POST \
  "https://${INSTANCE}.service-now.com/api/now/attachment/file?table_name=change_request&table_sys_id=${SYS_ID}" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: text/plain" \
  -F "file=@test-results.xml"

Change Request States

Understanding ServiceNow change states:

State Value Description Pipeline Action
New -5 Change created Wait for approval
Assess -4 Under assessment Continue waiting
Authorize -3 Awaiting authorization Continue waiting
Scheduled -2 Approved and scheduled Can proceed
Implement -1 Implementation in progress Deployment happening
Review 0 Post-implementation review Deployment complete
Closed 3 Change closed Final state
Canceled 4 Change canceled Abort deployment

Pipeline Logic:

if state in ['scheduled', 'implement', 'review']:
    proceed_with_deployment()
elif state == 'canceled':
    abort_deployment()
else:
    wait_for_approval()

Error Handling

Retry Strategy

Implement exponential backoff:

import time
import requests

def create_change_with_retry(data, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.post(
                f"https://{instance}.service-now.com/api/now/table/change_request",
                headers=headers,
                json=data,
                timeout=30
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            if attempt == max_retries - 1:
                raise
            wait_time = 2 ** attempt  # Exponential backoff
            time.sleep(wait_time)

Common Error Scenarios

Error Cause Solution
401 Unauthorized Invalid credentials Check token/credentials, refresh if expired
403 Forbidden Insufficient permissions Verify ServiceNow user has change_request role
404 Not Found Invalid endpoint/sys_id Verify URL and change request exists
429 Too Many Requests Rate limiting Implement backoff, reduce request frequency
500 Internal Server Error ServiceNow issue Retry with exponential backoff

Approval Polling Pattern

Best practice for waiting on approvals:

import time

def wait_for_approval(sys_id, timeout=3600, interval=60):
    """
    Poll change request until approved or timeout

    Args:
        sys_id: Change request sys_id
        timeout: Maximum wait time in seconds (default 1 hour)
        interval: Polling interval in seconds (default 60)

    Returns:
        True if approved, False if timeout or rejected
    """
    start_time = time.time()

    while (time.time() - start_time) < timeout:
        change = get_change_request(sys_id)
        state = change['result']['state']
        approval = change['result']['approval']

        # Check if approved (state: scheduled or higher)
        if state in ['scheduled', 'implement', 'review']:
            print(f"Change {sys_id} approved!")
            return True

        # Check if rejected or canceled
        if state == 'canceled' or approval == 'rejected':
            print(f"Change {sys_id} rejected or canceled")
            return False

        # Still pending, wait and retry
        time.sleep(interval)

    # Timeout reached
    print(f"Timeout waiting for approval of change {sys_id}")
    return False

Security Best Practices

Credential Management

Do:

  • βœ“ Store ServiceNow credentials in secrets management (Vault, CI/CD secrets)
  • βœ“ Use OAuth 2.0 with client credentials flow
  • βœ“ Rotate credentials regularly
  • βœ“ Use least-privilege ServiceNow roles
  • βœ“ Audit ServiceNow API access logs

Don’t:

  • βœ— Hardcode credentials in pipeline code
  • βœ— Use admin accounts for API access
  • βœ— Share credentials across teams
  • βœ— Log credentials in pipeline output

Network Security

  • Use HTTPS for all ServiceNow API calls
  • Whitelist CI/CD IPs in ServiceNow if possible
  • Implement mutual TLS for enterprise deployments
  • Use ServiceNow IP allowlists

Performance Optimization

Reduce API Calls

Instead of:

# Multiple API calls
change = create_change(data)
attach_file(change['sys_id'], 'tests.xml')
attach_file(change['sys_id'], 'scan.json')
update_change(change['sys_id'], {'state': 'implement'})

Do:

# Batch operations where possible
change = create_change_with_attachments(data, files=['tests.xml', 'scan.json'])
# Update only when state changes

Caching

Cache ServiceNow metadata (assignment groups, CMDB CIs) that doesn’t change frequently:

# Cache assignment group sys_id
ASSIGNMENT_GROUPS = {
    'DevOps Team': 'a9e0c5f21bf45110d4e27f86624bcb24',
    'Platform Team': 'b2f1d6g32cg56221e5f38g97735cdc35'
}

# Use cached value instead of lookup
change_data['assignment_group'] = ASSIGNMENT_GROUPS['DevOps Team']

Testing ServiceNow Integration

Development Environment

  1. ServiceNow Developer Instance: Free instance for testing
  2. Mock ServiceNow API: Use tools like WireMock for local testing
  3. Test Change Requests: Create and close changes in non-production

Integration Tests

def test_create_change_request():
    """Test change creation"""
    data = {
        "short_description": "Test Change",
        "type": "standard"
    }
    response = create_change(data)
    assert response['result']['number'].startswith('CHG')
    assert response['result']['state'] == 'new'

def test_approval_workflow():
    """Test approval polling"""
    change = create_change({"short_description": "Test", "type": "normal"})
    sys_id = change['result']['sys_id']

    # Simulate approval in ServiceNow
    approve_change(sys_id)  # Test helper function

    # Verify polling detects approval
    approved = wait_for_approval(sys_id, timeout=120)
    assert approved == True

Monitoring and Observability

Metrics to Track

  • Change Creation Time: Time to create change via API
  • Approval Wait Time: Duration waiting for approvals
  • API Error Rate: Percentage of failed ServiceNow API calls
  • Change Success Rate: Percentage of changes closed successfully
  • Pipeline Duration Impact: Extra time added by ServiceNow integration

Alerts

Set up alerts for:

  • ServiceNow API failures (>5% error rate)
  • Approval timeouts (>1 hour wait)
  • Rejected changes (immediate notification)
  • Change creation failures (blocks deployment)

Logging

Log all ServiceNow interactions:

import logging

logger = logging.getLogger('servicenow')

def create_change(data):
    logger.info(f"Creating change request: {data['short_description']}")
    try:
        response = requests.post(url, json=data)
        logger.info(f"Change created: {response.json()['result']['number']}")
        return response.json()
    except Exception as e:
        logger.error(f"Failed to create change: {str(e)}")
        raise

Next Steps

Now that you understand the core concepts, proceed to platform-specific integration guides:

Additional Resources