Network automation with Ansible and NAPALM revolutionizes enterprise network operations by enabling consistent, reliable, and scalable configuration management. This comprehensive guide explores advanced automation techniques, infrastructure-as-code practices, and production-ready frameworks for enterprise network environments.

Enterprise Network Automation

Section 1: Advanced Ansible Network Automation

Ansible provides powerful network automation capabilities through specialized modules and playbooks designed for network device management.

Comprehensive Ansible Network Framework

# ansible.cfg - Optimized for network automation
[defaults]
inventory = inventory/hosts.yml
host_key_checking = False
timeout = 30
gather_facts = False
retry_files_enabled = False
stdout_callback = yaml
bin_ansible_callbacks = True

[persistent_connection]
connect_timeout = 60
command_timeout = 30

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
pipelining = True
control_path = /tmp/ansible-ssh-%%h-%%p-%%r

---
# inventory/hosts.yml - Dynamic inventory structure
all:
  children:
    core_switches:
      hosts:
        core-sw-01:
          ansible_host: 10.1.1.10
          ansible_network_os: ios
          device_type: cisco_ios
          site: datacenter_a
          role: core
        core-sw-02:
          ansible_host: 10.1.1.11
          ansible_network_os: ios
          device_type: cisco_ios
          site: datacenter_a
          role: core
      vars:
        ansible_connection: network_cli
        ansible_user: "{{ vault_network_username }}"
        ansible_password: "{{ vault_network_password }}"
        ansible_become: yes
        ansible_become_method: enable
        ansible_become_password: "{{ vault_enable_password }}"
    
    access_switches:
      hosts:
        access-sw-[01:20]:
          ansible_host: 10.2.1.[10:29]
          ansible_network_os: ios
          device_type: cisco_ios
          site: office_floor_[1:4]
          role: access
      vars:
        ansible_connection: network_cli
        ansible_user: "{{ vault_network_username }}"
        ansible_password: "{{ vault_network_password }}"
    
    routers:
      children:
        edge_routers:
          hosts:
            edge-rtr-01:
              ansible_host: 192.168.1.1
              ansible_network_os: iosxr
              device_type: cisco_iosxr
              site: edge_site_a
              role: edge
        wan_routers:
          hosts:
            wan-rtr-[01:05]:
              ansible_host: 172.16.1.[1:5]
              ansible_network_os: junos
              device_type: juniper_junos
              site: wan_site_[a:e]
              role: wan
  
  vars:
    ntp_servers:
      - 10.1.1.100
      - 10.1.1.101
    dns_servers:
      - 10.1.1.200
      - 10.1.1.201
    syslog_server: 10.1.1.300
    snmp_community: "{{ vault_snmp_community }}"

---
# playbooks/site.yml - Main orchestration playbook
- name: Enterprise Network Configuration Management
  hosts: all
  gather_facts: False
  strategy: free
  serial: "{{ serial_execution | default('20%') }}"
  
  pre_tasks:
    - name: Verify device connectivity
      wait_for_connection:
        timeout: 60
      tags: [always, connectivity]
    
    - name: Gather device facts
      ios_facts:
        gather_subset: all
      when: ansible_network_os == 'ios'
      tags: [facts]
    
    - name: Create backup directory
      file:
        path: "{{ backup_dir }}/{{ inventory_hostname }}"
        state: directory
      delegate_to: localhost
      run_once: True
      tags: [backup]

  tasks:
    - name: Include device-specific tasks
      include_tasks: "tasks/{{ ansible_network_os }}.yml"
      tags: [configuration]
    
    - name: Backup running configuration
      include_tasks: tasks/backup.yml
      tags: [backup]
    
    - name: Validate configuration
      include_tasks: tasks/validate.yml
      tags: [validation]

  post_tasks:
    - name: Generate compliance report
      include_tasks: tasks/compliance_report.yml
      tags: [compliance]
    
    - name: Send notification
      include_tasks: tasks/notification.yml
      when: send_notifications | default(true)
      tags: [notification]

---
# playbooks/tasks/ios.yml - Cisco IOS specific tasks
- name: Configure global settings
  ios_config:
    lines:
      - "hostname {{ inventory_hostname }}"
      - "ip domain-name {{ domain_name }}"
      - "service timestamps debug datetime msec"
      - "service timestamps log datetime msec"
      - "service password-encryption"
      - "no ip http server"
      - "no ip http secure-server"
    backup: yes
    backup_options:
      filename: "{{ inventory_hostname }}_running_config_{{ ansible_date_time.iso8601_basic_short }}.txt"
      dir_path: "{{ backup_dir }}/{{ inventory_hostname }}"
  tags: [global_config]

- name: Configure NTP
  ios_ntp:
    server: "{{ item }}"
    state: present
  loop: "{{ ntp_servers }}"
  tags: [ntp]

- name: Configure DNS
  ios_config:
    lines:
      - "ip name-server {{ dns_servers | join(' ') }}"
  tags: [dns]

- name: Configure SNMP
  ios_config:
    lines:
      - "snmp-server community {{ snmp_community }} RO"
      - "snmp-server location {{ site }}"
      - "snmp-server contact {{ snmp_contact | default('Network Operations') }}"
  tags: [snmp]

- name: Configure syslog
  ios_config:
    lines:
      - "logging host {{ syslog_server }}"
      - "logging trap informational"
      - "logging source-interface {{ management_interface | default('GigabitEthernet0/0') }}"
  tags: [syslog]

- name: Configure VLANs
  ios_vlans:
    config: "{{ vlans }}"
    state: merged
  when: vlans is defined and role == 'access'
  tags: [vlans]

- name: Configure interfaces
  ios_interfaces:
    config: "{{ interfaces }}"
    state: merged
  when: interfaces is defined
  tags: [interfaces]

- name: Configure L3 interfaces
  ios_l3_interfaces:
    config: "{{ l3_interfaces }}"
    state: merged
  when: l3_interfaces is defined and role in ['core', 'edge']
  tags: [l3_interfaces]

---
# group_vars/all.yml - Global variables
backup_dir: "./backups"
domain_name: "enterprise.local"
management_interface: "GigabitEthernet0/0"
snmp_contact: "netops@enterprise.com"

# Security settings
security_config:
  enable_ssh: true
  ssh_version: 2
  ssh_timeout: 300
  console_timeout: 5
  enable_password_encryption: true
  banner_motd: |
    ********************************************************************************
    * WARNING: This system is for authorized use only. All activities are logged. *
    ********************************************************************************

# QoS configuration
qos_policies:
  voice:
    class_map: "voice-traffic"
    policy_map: "voice-policy"
    bandwidth_percent: 30
  video:
    class_map: "video-traffic"
    policy_map: "video-policy"
    bandwidth_percent: 25
  data:
    class_map: "data-traffic"
    policy_map: "data-policy"
    bandwidth_percent: 45

---
# group_vars/core_switches.yml - Core switch specific variables
vlans:
  - vlan_id: 10
    name: "USERS"
  - vlan_id: 20
    name: "SERVERS"
  - vlan_id: 30
    name: "MGMT"
  - vlan_id: 99
    name: "NATIVE"

l3_interfaces:
  - name: "Vlan10"
    ipv4:
      - address: "10.10.0.1/24"
  - name: "Vlan20"
    ipv4:
      - address: "10.20.0.1/24"
  - name: "Vlan30"
    ipv4:
      - address: "10.30.0.1/24"

spanning_tree:
  mode: "rapid-pvst"
  portfast_default: true
  bpduguard_default: true

hsrp_groups:
  - group: 10
    ip: "10.10.0.1"
    priority: 110
    preempt: true
  - group: 20
    ip: "10.20.0.1"
    priority: 110
    preempt: true

---
# playbooks/deploy_change.yml - Change management playbook
- name: Network Change Deployment with Rollback Capability
  hosts: "{{ target_hosts | default('all') }}"
  gather_facts: False
  serial: "{{ batch_size | default(1) }}"
  
  vars:
    change_id: "{{ ansible_date_time.epoch }}"
    rollback_enabled: "{{ enable_rollback | default(true) }}"
    validation_timeout: "{{ validation_wait | default(300) }}"
    
  pre_tasks:
    - name: Validate change window
      fail:
        msg: "Change deployment outside of approved maintenance window"
      when: 
        - maintenance_window is defined
        - not (ansible_date_time.hour | int >= maintenance_window.start and 
               ansible_date_time.hour | int <= maintenance_window.end)
      tags: [validation]
    
    - name: Create change tracking
      copy:
        content: |
          Change ID: {{ change_id }}
          Target: {{ inventory_hostname }}
          Start Time: {{ ansible_date_time.iso8601 }}
          Operator: {{ ansible_user_id | default('automated') }}
          Change Type: {{ change_type | default('configuration') }}
        dest: "{{ backup_dir }}/changes/{{ change_id }}_{{ inventory_hostname }}.log"
      delegate_to: localhost
      tags: [tracking]

  tasks:
    - name: Pre-change validation
      include_tasks: tasks/pre_change_validation.yml
      tags: [pre_validation]
    
    - name: Create configuration checkpoint
      ios_config:
        backup: yes
        backup_options:
          filename: "{{ inventory_hostname }}_pre_change_{{ change_id }}.cfg"
          dir_path: "{{ backup_dir }}/{{ inventory_hostname }}"
      tags: [checkpoint]
    
    - name: Apply configuration changes
      include_tasks: "{{ change_playbook }}"
      when: change_playbook is defined
      register: change_result
      tags: [apply_changes]
    
    - name: Post-change validation
      include_tasks: tasks/post_change_validation.yml
      tags: [post_validation]
    
    - name: Update change tracking
      lineinfile:
        path: "{{ backup_dir }}/changes/{{ change_id }}_{{ inventory_hostname }}.log"
        line: |
          End Time: {{ ansible_date_time.iso8601 }}
          Status: {{ 'SUCCESS' if change_result.failed is not defined else 'FAILED' }}
        create: yes
      delegate_to: localhost
      tags: [tracking]

  rescue:
    - name: Rollback configuration on failure
      include_tasks: tasks/rollback.yml
      when: rollback_enabled
      tags: [rollback]
    
    - name: Escalate failure
      include_tasks: tasks/escalation.yml
      tags: [escalation]

Section 2: NAPALM Integration and Advanced Device Management

NAPALM (Network Automation and Programmability Abstraction Layer with Multivendor support) provides vendor-neutral network device management.

Enterprise NAPALM Framework

from napalm import get_network_driver
from napalm.base.exceptions import ConnectionException, CommandErrorException
import json
import logging
import time
from typing import Dict, List, Any, Optional
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass

@dataclass
class DeviceConfig:
    hostname: str
    device_type: str
    ip_address: str
    username: str
    password: str
    enable_password: Optional[str] = None
    timeout: int = 30
    optional_args: Optional[Dict] = None

class EnterpriseNAPALMManager:
    def __init__(self, config_file: str = None):
        self.devices = {}
        self.connections = {}
        self.logger = self._setup_logging()
        self.validation_engine = ConfigValidationEngine()
        self.compliance_checker = ComplianceChecker()
        self.change_tracker = ChangeTracker()
        
        if config_file:
            self.load_device_inventory(config_file)
    
    def _setup_logging(self):
        """Setup comprehensive logging"""
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('network_automation.log'),
                logging.StreamHandler()
            ]
        )
        return logging.getLogger(__name__)
    
    def add_device(self, device_config: DeviceConfig):
        """Add device to management inventory"""
        self.devices[device_config.hostname] = device_config
        self.logger.info(f"Added device {device_config.hostname} to inventory")
    
    def connect_device(self, hostname: str) -> bool:
        """Establish connection to network device"""
        if hostname not in self.devices:
            self.logger.error(f"Device {hostname} not found in inventory")
            return False
        
        device_config = self.devices[hostname]
        
        try:
            # Get NAPALM driver
            driver = get_network_driver(device_config.device_type)
            
            # Prepare connection parameters
            connection_params = {
                'hostname': device_config.ip_address,
                'username': device_config.username,
                'password': device_config.password,
                'timeout': device_config.timeout,
                'optional_args': device_config.optional_args or {}
            }
            
            # Add enable password for Cisco devices
            if device_config.enable_password:
                connection_params['optional_args']['secret'] = device_config.enable_password
            
            # Create connection
            device = driver(**connection_params)
            device.open()
            
            self.connections[hostname] = device
            self.logger.info(f"Successfully connected to {hostname}")
            return True
            
        except ConnectionException as e:
            self.logger.error(f"Failed to connect to {hostname}: {e}")
            return False
        except Exception as e:
            self.logger.error(f"Unexpected error connecting to {hostname}: {e}")
            return False
    
    def disconnect_device(self, hostname: str):
        """Disconnect from network device"""
        if hostname in self.connections:
            try:
                self.connections[hostname].close()
                del self.connections[hostname]
                self.logger.info(f"Disconnected from {hostname}")
            except Exception as e:
                self.logger.error(f"Error disconnecting from {hostname}: {e}")
    
    def get_device_facts(self, hostname: str) -> Dict[str, Any]:
        """Retrieve comprehensive device facts"""
        if hostname not in self.connections:
            if not self.connect_device(hostname):
                return {}
        
        device = self.connections[hostname]
        
        try:
            facts = device.get_facts()
            self.logger.info(f"Retrieved facts for {hostname}")
            return facts
        except Exception as e:
            self.logger.error(f"Failed to get facts for {hostname}: {e}")
            return {}
    
    def backup_configuration(self, hostname: str, backup_dir: str = "./backups") -> bool:
        """Backup device configuration"""
        if hostname not in self.connections:
            if not self.connect_device(hostname):
                return False
        
        device = self.connections[hostname]
        
        try:
            # Get running configuration
            config = device.get_config(retrieve='running')
            
            # Create backup filename with timestamp
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            backup_filename = f"{backup_dir}/{hostname}_running_{timestamp}.cfg"
            
            # Save configuration to file
            os.makedirs(backup_dir, exist_ok=True)
            with open(backup_filename, 'w') as f:
                f.write(config['running'])
            
            self.logger.info(f"Configuration backed up for {hostname}: {backup_filename}")
            return True
            
        except Exception as e:
            self.logger.error(f"Failed to backup configuration for {hostname}: {e}")
            return False
    
    def deploy_configuration(self, hostname: str, config_file: str, 
                           commit: bool = False, rollback_timeout: int = 300) -> Dict[str, Any]:
        """Deploy configuration with validation and rollback capability"""
        if hostname not in self.connections:
            if not self.connect_device(hostname):
                return {'success': False, 'error': 'Connection failed'}
        
        device = self.connections[hostname]
        result = {
            'success': False,
            'changes': {},
            'validation_errors': [],
            'rollback_performed': False
        }
        
        try:
            # Read configuration file
            with open(config_file, 'r') as f:
                new_config = f.read()
            
            # Pre-deployment validation
            validation_result = self.validation_engine.validate_config(
                hostname, new_config
            )
            
            if not validation_result['valid']:
                result['validation_errors'] = validation_result['errors']
                return result
            
            # Create checkpoint for rollback
            checkpoint_id = self.create_checkpoint(hostname)
            
            # Load configuration
            device.load_merge_candidate(config=new_config)
            
            # Get diff
            diff = device.compare_config()
            result['changes'] = diff
            
            if diff:
                if commit:
                    # Commit configuration
                    device.commit_config()
                    
                    # Post-deployment validation
                    validation_passed = self.post_deployment_validation(
                        hostname, rollback_timeout
                    )
                    
                    if not validation_passed:
                        # Rollback configuration
                        self.rollback_to_checkpoint(hostname, checkpoint_id)
                        result['rollback_performed'] = True
                        result['success'] = False
                        result['error'] = 'Post-deployment validation failed'
                    else:
                        result['success'] = True
                        self.change_tracker.record_change(
                            hostname, config_file, diff
                        )
                else:
                    # Discard changes (dry run)
                    device.discard_config()
                    result['success'] = True
                    result['dry_run'] = True
            else:
                result['success'] = True
                result['no_changes'] = True
                
        except Exception as e:
            self.logger.error(f"Configuration deployment failed for {hostname}: {e}")
            result['error'] = str(e)
            
            # Attempt rollback on error
            try:
                device.discard_config()
                result['rollback_performed'] = True
            except:
                pass
        
        return result
    
    def mass_deployment(self, config_map: Dict[str, str], 
                       max_workers: int = 5, commit: bool = False) -> Dict[str, Any]:
        """Deploy configurations to multiple devices in parallel"""
        results = {}
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # Submit deployment tasks
            future_to_hostname = {
                executor.submit(
                    self.deploy_configuration, 
                    hostname, 
                    config_file, 
                    commit
                ): hostname
                for hostname, config_file in config_map.items()
            }
            
            # Collect results
            for future in as_completed(future_to_hostname):
                hostname = future_to_hostname[future]
                try:
                    result = future.result()
                    results[hostname] = result
                    
                    if result['success']:
                        self.logger.info(f"Successfully deployed to {hostname}")
                    else:
                        self.logger.error(f"Deployment failed for {hostname}: {result.get('error', 'Unknown error')}")
                        
                except Exception as e:
                    self.logger.error(f"Exception during deployment to {hostname}: {e}")
                    results[hostname] = {'success': False, 'error': str(e)}
        
        return results
    
    def compliance_check(self, hostname: str, compliance_rules: Dict[str, Any]) -> Dict[str, Any]:
        """Perform comprehensive compliance checking"""
        if hostname not in self.connections:
            if not self.connect_device(hostname):
                return {'compliant': False, 'error': 'Connection failed'}
        
        device = self.connections[hostname]
        
        try:
            # Get current configuration
            current_config = device.get_config(retrieve='running')['running']
            
            # Get device facts
            facts = device.get_facts()
            
            # Perform compliance check
            compliance_result = self.compliance_checker.check_compliance(
                hostname, current_config, facts, compliance_rules
            )
            
            return compliance_result
            
        except Exception as e:
            self.logger.error(f"Compliance check failed for {hostname}: {e}")
            return {'compliant': False, 'error': str(e)}

class ConfigValidationEngine:
    """Advanced configuration validation engine"""
    
    def __init__(self):
        self.validators = {
            'syntax': SyntaxValidator(),
            'security': SecurityValidator(),
            'best_practices': BestPracticesValidator(),
            'consistency': ConsistencyValidator()
        }
    
    def validate_config(self, hostname: str, config: str) -> Dict[str, Any]:
        """Comprehensive configuration validation"""
        result = {
            'valid': True,
            'errors': [],
            'warnings': [],
            'validation_details': {}
        }
        
        for validator_name, validator in self.validators.items():
            validation_result = validator.validate(hostname, config)
            result['validation_details'][validator_name] = validation_result
            
            if validation_result['errors']:
                result['valid'] = False
                result['errors'].extend(validation_result['errors'])
            
            if validation_result['warnings']:
                result['warnings'].extend(validation_result['warnings'])
        
        return result

class SecurityValidator:
    """Security configuration validator"""
    
    def __init__(self):
        self.security_rules = {
            'password_encryption': {
                'required_lines': ['service password-encryption'],
                'forbidden_lines': ['no service password-encryption']
            },
            'ssh_configuration': {
                'required_lines': ['ip ssh version 2'],
                'forbidden_lines': ['ip ssh version 1']
            },
            'http_server': {
                'required_lines': ['no ip http server', 'no ip http secure-server'],
                'forbidden_lines': ['ip http server']
            },
            'console_timeout': {
                'pattern': r'line con 0\s+exec-timeout \d+ \d+',
                'min_timeout': 5
            }
        }
    
    def validate(self, hostname: str, config: str) -> Dict[str, Any]:
        """Validate security configuration"""
        result = {
            'errors': [],
            'warnings': [],
            'passed_checks': [],
            'failed_checks': []
        }
        
        config_lines = config.lower().split('\n')
        
        for rule_name, rule_config in self.security_rules.items():
            check_result = self._check_security_rule(
                rule_name, rule_config, config_lines
            )
            
            if check_result['passed']:
                result['passed_checks'].append(rule_name)
            else:
                result['failed_checks'].append(rule_name)
                if check_result['severity'] == 'error':
                    result['errors'].append(check_result['message'])
                else:
                    result['warnings'].append(check_result['message'])
        
        return result
    
    def _check_security_rule(self, rule_name: str, rule_config: Dict, 
                           config_lines: List[str]) -> Dict[str, Any]:
        """Check individual security rule"""
        if 'required_lines' in rule_config:
            for required_line in rule_config['required_lines']:
                if not any(required_line.lower() in line for line in config_lines):
                    return {
                        'passed': False,
                        'severity': 'error',
                        'message': f"Security rule '{rule_name}': Missing required configuration '{required_line}'"
                    }
        
        if 'forbidden_lines' in rule_config:
            for forbidden_line in rule_config['forbidden_lines']:
                if any(forbidden_line.lower() in line for line in config_lines):
                    return {
                        'passed': False,
                        'severity': 'error',
                        'message': f"Security rule '{rule_name}': Forbidden configuration found '{forbidden_line}'"
                    }
        
        return {'passed': True, 'severity': 'info', 'message': f"Security rule '{rule_name}' passed"}

class ComplianceChecker:
    """Enterprise compliance checking engine"""
    
    def check_compliance(self, hostname: str, config: str, facts: Dict, 
                        rules: Dict[str, Any]) -> Dict[str, Any]:
        """Check device compliance against enterprise policies"""
        compliance_result = {
            'compliant': True,
            'compliance_score': 0,
            'total_checks': 0,
            'passed_checks': 0,
            'failed_checks': [],
            'details': {}
        }
        
        for rule_category, rule_list in rules.items():
            category_result = self._check_category_compliance(
                hostname, config, facts, rule_category, rule_list
            )
            
            compliance_result['details'][rule_category] = category_result
            compliance_result['total_checks'] += category_result['total']
            compliance_result['passed_checks'] += category_result['passed']
            
            if category_result['failed_rules']:
                compliance_result['compliant'] = False
                compliance_result['failed_checks'].extend(
                    category_result['failed_rules']
                )
        
        # Calculate compliance score
        if compliance_result['total_checks'] > 0:
            compliance_result['compliance_score'] = (
                compliance_result['passed_checks'] / 
                compliance_result['total_checks'] * 100
            )
        
        return compliance_result
    
    def _check_category_compliance(self, hostname: str, config: str, facts: Dict,
                                 category: str, rules: List[Dict]) -> Dict[str, Any]:
        """Check compliance for a specific category"""
        result = {
            'total': len(rules),
            'passed': 0,
            'failed_rules': [],
            'rule_details': {}
        }
        
        for rule in rules:
            rule_result = self._evaluate_rule(hostname, config, facts, rule)
            result['rule_details'][rule['name']] = rule_result
            
            if rule_result['passed']:
                result['passed'] += 1
            else:
                result['failed_rules'].append({
                    'rule': rule['name'],
                    'category': category,
                    'severity': rule.get('severity', 'medium'),
                    'description': rule.get('description', ''),
                    'remediation': rule.get('remediation', '')
                })
        
        return result

class ChangeTracker:
    """Track and audit network changes"""
    
    def __init__(self, audit_file: str = "network_changes.log"):
        self.audit_file = audit_file
        self.logger = logging.getLogger(f"{__name__}.ChangeTracker")
    
    def record_change(self, hostname: str, config_file: str, diff: str):
        """Record network change for audit trail"""
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        
        change_record = {
            'timestamp': timestamp,
            'hostname': hostname,
            'config_file': config_file,
            'operator': os.getenv('USER', 'unknown'),
            'diff': diff,
            'change_id': self._generate_change_id()
        }
        
        # Write to audit log
        with open(self.audit_file, 'a') as f:
            f.write(f"{json.dumps(change_record)}\n")
        
        self.logger.info(f"Recorded change for {hostname}: {change_record['change_id']}")
    
    def _generate_change_id(self) -> str:
        """Generate unique change identifier"""
        import uuid
        return str(uuid.uuid4())[:8]

This comprehensive guide demonstrates enterprise-grade network automation using Ansible and NAPALM with advanced configuration management, validation frameworks, compliance checking, and change tracking capabilities. The examples provide production-ready patterns for implementing reliable, scalable network automation in enterprise environments.