Chapter 14: Controller-Based Ansible Automation

Learning Objectives

Pre-Quiz — Test Your Prior Knowledge

1. The cisco.dnac Ansible collection communicates with Catalyst Center using which transport?

2. What is the correct order for provisioning a new device in Catalyst Center using Ansible workflow manager modules?

3. When using the cisco.meraki collection, the Ansible control node communicates directly with which endpoint?

4. Which Ansible module is commonly used to automate vManage REST API calls when a purpose-built collection is not available?

5. What is the primary purpose of ansible-vault in a multi-controller automation project?

Section 1: Ansible for Catalyst Center (cisco.dnac Collection)

The cisco.dnac collection is Cisco's official Ansible interface for Catalyst Center (formerly DNA Center). Every module communicates exclusively over HTTPS REST using the Cisco Catalyst Center Python SDK as its transport — no SSH or NETCONF is involved. This means tasks always target localhost and the control node must have the SDK installed.

ansible-galaxy collection install cisco.dnac
pip install dnacentersdk

A minimum Catalyst Center version of 2.3.5.3 is required for workflow manager modules; enhanced features require 2.3.7.9+.

Workflow Manager Modules

The collection's *_workflow_manager modules are idempotent lifecycle managers. They compare desired state against live configuration and make only necessary changes. Running the same playbook twice with state: merged is always safe.

ModuleResource Domain
site_workflow_managerSite hierarchy (Area / Building / Floor)
inventory_workflow_managerDevice inventory (add via SNMP/CLI)
provision_workflow_managerDevice provisioning (Day-0 / Day-N templates)
pnp_workflow_managerPlug-and-Play onboarding (ZTP / Planned / Unclaimed)
network_compliance_workflow_managerCompliance auditing and drift detection
lan_automation_workflow_managerIS-IS discovery and greenfield deployment
rma_workflow_managerHardware replacement (RMA) workflow

Site Hierarchy: Area → Building → Floor

Before any device can be provisioned, the three-level site hierarchy must exist. The parent_name field uses a slash-delimited path from the Global root, which is also referenced by provision_workflow_manager.

- name: Build Area, Building, and Floor
  cisco.dnac.site_workflow_manager:
    dnac_host: "{{ vault_dnac_host }}"
    dnac_username: "{{ vault_dnac_username }}"
    dnac_password: "{{ vault_dnac_password }}"
    dnac_verify: false
    state: merged
    config:
      - site:
          area:
            name: "US-West"
            parent_name: "Global"
          building:
            name: "HQ-Building1"
            parent_name: "Global/US-West"
          floor:
            name: "Floor-1"
            parent_name: "Global/US-West/HQ-Building1"
            rf_model: "Cubes And Walled Offices"

PnP Provisioning Modes

ModeDescriptionUse Case
Zero-Touch (ZTP)Device auto-connects; Catalyst Center pushes config immediatelyNew branch deployments
PlannedPre-configure settings applied when device comes onlineControlled rollouts
UnclaimedDiscover and configure unexpected new devicesDynamic environments

Compliance Checking

The network_compliance_workflow_manager detects configuration drift across four compliance categories:

Catalyst Center Provisioning Sequence
site_workflow_manager
Area→Building→Floor
inventory_workflow_manager
Add device (SNMP/CLI)
pnp / provision
Claim to site
network_compliance
Drift detection
All modules communicate over HTTPS REST — no SSH or NETCONF involved
flowchart LR subgraph Control["Ansible Control Node"] PB["Playbooks / Roles"] VAULT["ansible-vault\n(AES-256 secrets)"] EE["Execution Environment\n(collections + SDK)"] end subgraph Campus["Campus Domain"] DNAC["Cisco Catalyst Center\n(cisco.dnac collection)"] CAM["Campus Devices\n(switches, APs)"] end subgraph Branch["Branch Domain"] MERAKI["Meraki Dashboard API\n(cisco.meraki collection)"] MDEV["Meraki Devices\n(MR, MS, MX)"] end subgraph WAN["WAN Domain"] VMAN["vManage / SD-WAN Manager\n(uri module — REST)"] EDGE["vEdge / cEdge Routers"] end PB -->|"HTTPS REST\n(dnacentersdk)"| DNAC PB -->|"HTTPS REST\n(api.meraki.com)"| MERAKI PB -->|"HTTPS REST\n(session cookie)"| VMAN DNAC -->|"SSH / NETCONF"| CAM MERAKI -->|"Cloud-managed"| MDEV VMAN -->|"IPsec / DTLS overlay"| EDGE VAULT -.->|"injects credentials"| PB EE -.->|"provides modules"| PB

Key Points — Section 1: cisco.dnac

Section 2: Ansible for Meraki (cisco.meraki Collection)

Meraki is cloud-managed: Ansible never connects to individual devices. Every task is an HTTPS call to api.meraki.com from localhost. Authentication uses a Dashboard API key generated from Organization > Settings > Dashboard API access.

ansible-galaxy collection install cisco.meraki
# For full Dashboard API v1.33.0+ surface:
ansible-galaxy collection install meraki.dashboard

State Model

StateAction
presentCreate if absent; update if exists
absentDelete the resource
queryRead and return current resource info

Core Modules

ModuleManages
cisco.meraki.meraki_networkNetworks (create, update, delete, query)
cisco.meraki.meraki_deviceDevices (claim, remove, rename)
cisco.meraki.meraki_mr_ssidWireless SSIDs (auth, encryption, VLAN)
cisco.meraki.meraki_mx_vlanMX appliance VLANs (subnet, DHCP)
cisco.meraki.meraki_mx_site_to_site_firewallSite-to-site VPN firewall rules

Performance: Use Numeric IDs

Always prefer numeric org_id and net_id over name-based parameters. Name-based resolution requires additional API round-trips, which compounds at scale.

Query-Then-Act Pattern with selectattr()

Meraki API responses return lists, not keyed dictionaries. Use Jinja2's selectattr() filter to extract items by attribute:

- name: Extract target network ID by name
  set_fact:
    target_net_id: >-
      {{ network_list.data
         | selectattr('name', 'equalto', 'Branch-Office-NYC')
         | map(attribute='id')
         | list
         | first }}

SSID Configuration

SSIDs are numbered 0–14 per Meraki MR network. Set auth_mode to psk for WPA-PSK or open for open networks; control isolation with ip_assignment_mode ("Bridge mode" or "NAT mode").

sequenceDiagram participant PB as Ansible Playbook (localhost) participant DASH as Meraki Dashboard API (api.meraki.com) participant MDEV as Meraki Devices (cloud-managed) Note over PB,DASH: All communication is HTTPS from localhost PB->>DASH: GET /organizations/{orgId}/networks (state: query) DASH-->>PB: 200 OK — list of network objects Note over PB: selectattr extracts net_id from list PB->>DASH: POST /networks (state: present) DASH-->>PB: 201 Created PB->>DASH: PUT /networks/{netId}/wireless/ssids/0 DASH-->>PB: 200 OK PB->>DASH: PUT /networks/{netId}/appliance/vlans DASH-->>PB: 200 OK DASH->>MDEV: Push config changes (cloud-managed channel) MDEV-->>DASH: Acknowledgement

Key Points — Section 2: cisco.meraki

Section 3: Ansible for SD-WAN (URI Module)

Cisco SD-WAN is managed through the vManage REST API. Because purpose-built modules may not cover every endpoint, the ansible.builtin.uri module is the primary tool — and mastering it teaches the underlying REST pattern that all controller automation relies on.

vManage API Categories

CategoryBase PathPurpose
Monitoring/dataservice/deviceDevice health, reachability, interface stats
Real-Time Monitoring/dataservice/device/bfd/...Live BFD, OMP, tunnel state
Configuration/dataservice/template/Feature templates, device templates, policy
Administration/dataservice/admin/Users, certificates, cluster management

Session-Cookie Authentication (Two-Step)

vManage uses session-cookie authentication. Step 1 POSTs credentials to /j_security_check to obtain a session cookie. Step 2 includes that cookie in the Cookie header of all subsequent requests.

- name: Authenticate to vManage
  uri:
    url: "https://{{ vault_vmanage_host }}/j_security_check"
    method: POST
    body_format: form-urlencoded
    body:
      j_username: "{{ vault_vmanage_user }}"
      j_password: "{{ vault_vmanage_password }}"
    validate_certs: false
    status_code: 200
  register: auth_result

- name: Store session cookie
  set_fact:
    vmanage_session: "{{ auth_result.cookies_string }}"

CSRF Token for State-Changing Calls

All POST/PUT operations require a CSRF token retrieved from GET /dataservice/client/token. Include it as an X-XSRF-TOKEN request header.

Building Idempotency Manually

The uri module has no built-in idempotency. Use a check-before-act pattern: query existing resources, then use a when condition to act only if the resource is absent.

- name: Create VPN template only if absent
  uri:
    url: "https://{{ vault_vmanage_host }}/dataservice/template/feature"
    method: POST
    headers:
      Cookie: "{{ vmanage_session }}"
      X-XSRF-TOKEN: "{{ xsrf_token }}"
      Content-Type: "application/json"
    body_format: json
    body: "{{ lookup('file', 'templates/vpn_template.json') }}"
    validate_certs: false
  when: >-
    existing_templates.json.data
    | selectattr('templateName', 'equalto', 'VPN-0-Internet')
    | list | length == 0
sequenceDiagram participant PB as Ansible Playbook (uri module) participant VM as vManage REST API Note over PB,VM: Step 1 — Authenticate PB->>VM: POST /j_security_check {j_username, j_password} VM-->>PB: 200 OK + Set-Cookie: JSESSIONID=... Note over PB: set_fact: vmanage_session = cookies_string Note over PB,VM: Step 2 — Retrieve CSRF token PB->>VM: GET /dataservice/client/token (Cookie: JSESSIONID) VM-->>PB: 200 OK — {token: "xsrf-token-value"} Note over PB,VM: Step 3 — Read operation PB->>VM: GET /dataservice/device (Cookie: JSESSIONID) VM-->>PB: 200 OK — {data: [...devices...]} Note over PB,VM: Step 4 — State-changing operation PB->>VM: POST /dataservice/template/feature (Cookie + X-XSRF-TOKEN) VM-->>PB: 200 OK — template created

Key Points — Section 3: SD-WAN / URI Module

Section 4: Multi-Controller Automation Patterns

Orchestrating three controllers from a single Ansible project requires deliberate architectural discipline. The core principle: treat each controller as a domain with clear boundaries enforced by Ansible's role and inventory structures.

Inventory: Group by Controller Domain

[catalyst_center]
dnac-primary.corp.com

[meraki_cloud]
localhost

[sdwan_vmanage]
vmanage.corp.com

[catalyst_center:vars]
ansible_connection=local

[meraki_cloud:vars]
ansible_connection=local

[sdwan_vmanage:vars]
ansible_connection=local

All three groups use ansible_connection=local — all communication is HTTPS REST from the control node, not SSH to remote hosts. Meraki uses localhost because there is no on-premises Meraki server.

ansible-vault Credential Management

Store all secrets in an AES-256 encrypted vault file. Never commit plain-text API keys or passwords to version control.

ansible-vault: Plain Text → AES-256 Encrypted
vault.yml (pre-encryption)
vault_dnac_password: "SuperSecret123"
vault_meraki_api_key: "abc123..."
vault_vmanage_password: "SDWANPass!"
ansible-vault encrypt (AES-256 / password-derived key)
vault.yml (on disk)
$ANSIBLE_VAULT;1.1;AES256
3061383439333530626664353961643
3631386535396161663739396436356
3538303932663261663462343864373
...
Referenced as {{ vault_dnac_password }} — decrypted only at runtime with --ask-vault-pass or --vault-password-file

Master Orchestration with import_playbook

# site.yml — Master Multi-Controller Orchestration
- import_playbook: playbooks/catalyst_center/provision_sites.yml
- import_playbook: playbooks/meraki/deploy_networks.yml
- import_playbook: playbooks/sdwan/deploy_templates.yml
- import_playbook: playbooks/sdwan/health_check.yml

Use import_playbook (static, parse-time) for known sequences. Use include_tasks (dynamic, runtime) within roles when task selection depends on variables or conditions.

Error Handling with block/rescue/always

Multi-controller workflows can fail partway through. Use block/rescue/always — analogous to try/catch/finally — for graceful rollback and notifications regardless of outcome.

- block:
    - name: Provision device to Catalyst Center
      cisco.dnac.provision_workflow_manager:
        state: merged
        config:
          - management_ip_address: "{{ device_ip }}"
            site_name_hierarchy: "{{ site_path }}"
  rescue:
    - name: Remove device from inventory (rollback)
      cisco.dnac.inventory_workflow_manager:
        state: deleted
        config:
          - ip_address_list: ["{{ device_ip }}"]
  always:
    - name: Send Webex notification regardless of outcome
      uri:
        url: "{{ vault_webex_webhook }}"
        method: POST
        body_format: json
        body: {text: "Provisioning task completed for {{ device_ip }}"}

Execution Environments for AAP

For Red Hat Ansible Automation Platform at scale, build a custom Execution Environment container bundling all collections and SDK dependencies. AAP Workflow Templates then chain Catalyst Center → Meraki → SD-WAN jobs with conditional branching, RBAC, and event-driven automation.

flowchart LR START(["ansible-playbook site.yml\n--vault-password-file"]) START --> P1 subgraph P1["Phase 1: Campus Infrastructure"] CC1["site_workflow_manager"] --> CC2["inventory_workflow_manager"] --> CC3["provision_workflow_manager"] end P1 --> P2 subgraph P2["Phase 2: Branch Networks"] MK1["meraki_network"] --> MK2["meraki_mx_vlan"] --> MK3["meraki_mr_ssid"] end P2 --> P3 subgraph P3["Phase 3: SD-WAN Overlay"] SW1["POST j_security_check"] --> SW2["uri — deploy templates"] --> SW3["uri — query inventory"] end P3 --> P4 subgraph P4["Phase 4: Validation"] VL1["BFD health assert"] --> VL2["compliance check"] end P4 --> END(["Notify via Webex\nWorkflow complete"]) P1 -->|"block/rescue"| ERR["rescue: rollback"] P3 -->|"block/rescue"| ERR ERR --> END

Key Points — Section 4: Multi-Controller Patterns

Post-Quiz — Consolidate Your Learning

Post-Quiz — Test What You Learned

1. The cisco.dnac Ansible collection communicates with Catalyst Center using which transport?

2. What is the correct order for provisioning a new device in Catalyst Center using Ansible workflow manager modules?

3. When using the cisco.meraki collection, the Ansible control node communicates directly with which endpoint?

4. Which Ansible module is commonly used to automate vManage REST API calls when a purpose-built collection is not available?

5. What is the primary purpose of ansible-vault in a multi-controller automation project?

6. Which Jinja2 filter is essential for extracting a specific resource from a Meraki API list response?

7. In a multi-controller Ansible inventory, why is Meraki listed under a localhost host rather than an actual server address?

8. The vManage session-cookie authentication flow begins with a POST to which endpoint?

9. You need to deploy templates to vManage using a POST request. In addition to the session cookie, what else must you include in the request headers?

10. In the multi-controller block/rescue/always error-handling pattern, when does the always section execute?

11. Which cisco.dnac module should you use to detect configuration drift across devices managed by Catalyst Center?

12. What is the key difference between import_playbook and include_tasks in multi-controller orchestration?

Your Progress

Answer Explanations