Skip to content

Migrating Legacy Network Automation to Modern Frameworks

Migrating Legacy Network Automation to Modern Frameworks: A Step-by-Step Guide


This post is part of our ongoing series on network automation best practices, grounded in the PRIME Framework and PRIME Philosophy.

Transparency Note

Examples, scenarios, and any outcome figures in this article are provided for education and are based on enterprise delivery experience or anonymised composite scenarios unless explicitly identified as direct Nautomation Prime client outcomes.

Why This Blog Exists

Legacy scripts are everywhere—but they’re hard to maintain, scale, and secure. This post shows how to migrate to modern frameworks (Nornir, PyATS, Ansible) and adopt PRIME-aligned best practices for sustainable automation.


Why Migrate?

  • Reduce technical debt and maintenance burden
  • Improve reliability, security, and scalability
  • Enable new features and integrations
  • Empower your team to maintain and extend automation
  • Reduce vendor lock-in and increase flexibility

Migration Approach: Structured Steps

Step 1: Inventory Existing Scripts

  • List all automation scripts and their functions
  • Identify dependencies, pain points, and security risks
  • Map out undocumented logic and tribal knowledge
  • Measure baseline metrics (execution time, error rates, maintenance burden)

Step 2: Define Requirements

  • What must the new solution do? (features, scale, compliance, security, integrations)
  • Identify gaps in current workflows and desired improvements
  • Get stakeholder input (ops team, security, network engineering)
  • Prioritize high-ROI improvements

Step 3: Choose a Modern Framework

  • Nornir for Pythonic parallelism and custom logic
  • PyATS for validation, testing, and compliance gates
  • Ansible for declarative config management and onboarding
  • Consider hybrid approaches for complex environments
  • Evaluate learning curve and team skills

Step 4: Refactor in Stages

  • Start with core logic, then add features and integrations
  • Modularize code for reuse and testability
  • Use version control, code reviews, and CI/CD pipelines
  • Automate linting, testing, and deployment
  • Plan for incremental rollout (don't switch everything at once)

Step 5: Test and Validate

  • Unit, integration, and mock device tests
  • Compare outputs with legacy scripts and real devices
  • Validate error handling, rollbacks, and edge cases
  • Test against multiple device types and OS versions

Step 6: Document and Train

  • Update runbooks, user guides, and architecture diagrams
  • Train the team on new workflows, tools, and best practices
  • Provide runbooks, troubleshooting guides, and onboarding materials
  • Hold knowledge transfer sessions and create onboarding materials
  • Ensure at least 2 team members can maintain each script


Example: Refactoring a Backup Script

Before:

  • Monolithic Python script, hardcoded credentials, no error handling, no logging
1
2
3
4
5
6
7
8
9
# legacy_backup.py
import paramiko
def backup(device):
   ssh = paramiko.SSHClient()
   ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
   ssh.connect(device, username='admin', password='cisco')
   stdin, stdout, stderr = ssh.exec_command('show running-config')
   with open(f'{device}.cfg', 'w') as f:
      f.write(stdout.read().decode())

After:

  • Modular Nornir workflow, environment variables, structured logging, error handling, test coverage
# modern_backup.py
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
import os, logging
logging.basicConfig(level=logging.INFO)
nr = InitNornir(config_file="config.yaml")
def backup(task):
   result = task.run(task=netmiko_send_command, command_string="show running-config")
   with open(f"{task.host}.cfg", "w") as f:
      f.write(result.result)
   logging.info(f"Backed up {task.host}")
# modern_backup.py
from nornir import InitNornir
from nornir_netmiko.tasks import netmiko_send_command
import os, logging
from datetime import datetime

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def backup(task):
   """Backup device configuration using Netmiko."""
   try:
      result = task.run(task=netmiko_send_command, command_string="show running-config")

      # Save to file with timestamp
      timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
      filename = f"backups/{task.host}_{timestamp}.cfg"
      os.makedirs('backups', exist_ok=True)

      with open(filename, 'w') as f:
         f.write(result.result)

      logger.info(f"✓ Backed up {task.host} to {filename}")
      return result
   except Exception as e:
      logger.error(f"✗ Backup failed for {task.host}: {e}", exc_info=True)
      raise

nr = InitNornir(config_file="config.yaml")
results = nr.run(task=backup)

Key Improvements:

  • Credentials in config file, not hardcoded
  • Comprehensive error handling and logging
  • Parallel execution (all devices backed up concurrently)
  • Timestamped backups for version tracking
  • Easy to extend and test
  • Production-ready

Migration Strategy: Staged Timeline

Phase 1 (Weeks 1-4): Planning & Preparation

  • List all automation scripts and analyze them
  • Measure baseline metrics (speed, reliability, maintenance burden)
  • Survey team about pain points
  • Select target framework (Nornir, Ansible, PyATS)
  • Define success criteria

Phase 2 (Weeks 5-8): Build Infrastructure

  • Create Git repository with proper structure
  • Set up inventory management (NetBox or YAML)
  • Configure secrets management (Vault, env vars)
  • Set up CI/CD pipeline (GitHub Actions, GitLab CI)
  • Create testing infrastructure (unit tests, mock devices)

Phase 3 (Weeks 9-16): Pilot Migration

  • Select 1-2 low-risk, high-value scripts
  • Completely refactor to new framework
  • Write comprehensive tests
  • Document everything
  • Deploy to production with monitoring
  • Train team on new approach
  • Collect feedback and iterate

Phase 4 (Weeks 17+): Scale & Continuous Improvement

  • Migrate remaining scripts in batches
  • Reuse patterns and plugins
  • Build shared libraries
  • Integrate with monitoring and alerting
  • Plan next evolution

Framework-Specific Migration Patterns

Ansible Migration: Config Management

# playbooks/vlan_deployment.yml
---
- name: Deploy VLANs (idempotent)
  hosts: switches
  gather_facts: no
  tasks:
    - name: Ensure VLANs exist
      cisco.ios.ios_vlan:
        vlan_id: "{{ item.vlan_id }}"
        name: "{{ item.name }}"
        state: present
      loop:
        - { vlan_id: 100, name: "Users" }
        - { vlan_id: 200, name: "Servers" }
    - name: Report status
      debug:
        msg: "✓ VLANs deployed on {{ inventory_hostname }}"

Nornir Migration: Complex Workflows

# tasks/deployment_workflow.py
from nornir import InitNornir

def pre_change_check(task):
   """Validate device before making changes."""
   # Check NTP sync, BGP up, etc.
   pass

def apply_change(task):
   """Apply the configuration change."""
   # Push config using netmiko, scrapli, etc.
   pass

def post_change_verify(task):
   """Verify change was applied correctly."""
   # After pushing config, verify it took effect
   pass

def rollback_on_failure(task):
   """Rollback if verification fails."""
   if not task.host.get('change_verified'):
      # Push previous config
      pass

nr = InitNornir(config_file="config.yaml")
nr.run(task=pre_change_check)
nr.run(task=apply_change)
nr.run(task=post_change_verify)
nr.run(task=rollback_on_failure)

PyATS Migration: Validation

# tests/compliance_check.py
from pyats.topology import loader
from pyats.aetest import Testcase

class ComplianceTests(Testcase):
   def setup(self):
      self.testbed = loader.load('testbed.yaml')

   def test_ntp_sync(self):
      """Verify NTP synchronization on all devices."""
      for device_name, device in self.testbed.devices.items():
         device.connect()
         result = device.parse('show ntp status')
         assert result.get('clock_state') == 'synchronized'
         device.disconnect()

Testing During Migration

Unit Test Example

import pytest
from legacy_to_modern import backup_task

def test_backup_creates_file(tmp_path):
   """Test backup creates a file."""
   # Mock task and run backup
   # Assert file was created
   pass

def test_backup_error_handling():
   """Test error handling."""
   # Mock connection error
   # Assert proper exception raised
   pass

Integration Test with Mock Devices

1
2
3
4
5
6
7
@pytest.mark.integration
def test_backup_with_mock_device():
   """Test backup against mock network device."""
   # Create mock device response
   # Run backup workflow
   # Verify output matches expected format
   pass

Common Migration Pitfalls

  • Big bang migration: Don't rewrite everything at once. Do it in stages.
  • Skipping tests: New framework means new tests. Don't skip them.
  • Ignoring team feedback: If team doesn't like it, it won't be adopted.
  • Premature optimization: Get it working first, optimize later.
  • Documentation debt: Document as you go.
  • No rollback plan: Always be able to revert if needed.

PRIME in Action: Sustainable Modernization

  • Transparency: Document every change, decision, and migration step
  • Measurability: Track migration progress, test coverage, and outcomes with dashboards
  • Ownership: Empower your team to maintain, extend, and refactor as needs evolve
  • Safety: Test and validate at every stage, automate rollbacks and error handling
  • Empowerment: Provide training, support, and clear onboarding for new team members

Summary: Blog Takeaways

  • Migrating to modern frameworks reduces risk and increases value
  • Follow a structured, PRIME-aligned process
  • Document, test, and empower your team for long-term success
  • Use modular code, CI/CD, and automated testing for sustainable modernization
  • Plan for incremental migration and continuous improvement
  • Start with pilot scripts, measure success, then scale

Next Steps

  1. Inventory your current automation (list what you have)
  2. Measure baseline metrics (speed, errors, maintenance burden)
  3. Select a target framework based on your needs
  4. Start with one pilot script
  5. Build tests and documentation
  6. Deploy and measure success
  7. Scale to remaining scripts

The journey from legacy to modern automation is transformative—it frees your team from technical debt and enables rapid innovation. Start today.


📣 Want More?